PaySheetTransaction.py 16 KB
Newer Older
Yoshinori Okuji's avatar
Yoshinori Okuji committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
31
from Products.ERP5.Document.Invoice import Invoice
32
from zLOG import LOG
Yoshinori Okuji's avatar
Yoshinori Okuji committed
33

34
class PaySheetTransaction(Invoice):
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
  """
  A paysheet will store data about the salary of an employee
  """

  meta_type = 'ERP5 Pay Sheet Transaction'
  portal_type = 'Pay Sheet Transaction'
  add_permission = Permissions.AddPortalContent
  isPortalContent = 1
  isRADContent = 1

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Global variables
  _transaction_line_portal_type = 'Pay Sheet Transaction Line'
  
  # Default Properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.SimpleItem
                    , PropertySheet.CategoryCore
                    , PropertySheet.Task
                    , PropertySheet.Arrow
                    , PropertySheet.Delivery
                    , PropertySheet.PaySheet
                    , PropertySheet.Movement
                    , PropertySheet.Amount
                    , PropertySheet.XMLObject
                    , PropertySheet.TradeCondition
                    , PropertySheet.DefaultAnnotationLine
                    )

  # Declarative Interface
  __implements__ = ( )


71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

  security.declareProtected(Permissions.AccessContentsInformation,
                          'getRatioQuantityFromReference')
  def getRatioQuantityFromReference(self, ratio_reference=None):
    """
    return the ratio value correponding to the ratio_reference,
    None if ratio_reference not found
    """
    object_ratio_list = self.contentValues(portal_type=\
        'Pay Sheet Model Ratio Line')
    for object in object_ratio_list:
      if object.getReference() == ratio_reference:
        return object.getQuantity()
    return None 

  security.declareProtected(Permissions.AccessContentsInformation,
                          'getRatioQuantityList')
  def getRatioQuantityList(self, ratio_reference_list):
    """
    Return a list of reference_ratio_list correponding values.
    reference_ratio_list is a list of references to the ratio lines
    we want to get.
    """
    if not isinstance(ratio_reference_list, list):
      return [self.getRatioQuantityFromReference(ratio_reference_list)]
    return [self.getRatioQuantityFromReference(reference) \
        for reference in ratio_reference_list]


100 101
  security.declareProtected(Permissions.AddPortalContent,
                          'createPaySheetLine')
102
  def createPaySheetLine(self, cell_list, title='', res='', desc='', 
103
      base_amount_list=None, int_index=None, **kw):
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    '''
    This function register all paysheet informations in paysheet lines and 
    cells. Select good cells only
    '''
    good_cell_list = []
    for cell in cell_list:
      if cell['quantity'] or cell['price']:
        good_cell_list.append(cell)
    if len(good_cell_list) == 0:
      return
    # Get all variation categories used in cell_list
    var_cat_list = []
    for cell in good_cell_list:
      # Don't add a variation category if already in it
      for category in cell['axe_list']:
        if category not in var_cat_list: 
          var_cat_list.append(category)

    # Construct the description
    description = None
    if len(desc) > 0:
      description = desc#'\n'.join(desc)
    # Add a new Pay Sheet Line
    payline = self.newContent(
             portal_type                  = 'Pay Sheet Line',
             title                        = title,
             description                  = description,
             source_section               =  \
                self.getPortalObject().restrictedTraverse(res).getSource(),
             resource                     = res,
             destination_section          = self.getDestinationSection(),
             destination                  = self.getDestinationSection(),
             variation_base_category_list = ('tax_category', 'salary_range'),
             variation_category_list      = var_cat_list,
138
             base_amount_list             = base_amount_list,
139
             int_index                    = int_index,
140 141 142 143 144 145 146 147 148
             **kw)

    base_id = 'movement'
    a = payline.updateCellRange(script_id = 'PaySheetLine_asCellRange',
                                base_id   = base_id)
    # create cell_list
    for cell in good_cell_list:
      cell_cat_list = cell['axe_list']
      paycell = payline.newCell(base_id = base_id, *cell_cat_list)
Fabien Morin's avatar
- typo  
Fabien Morin committed
149
      # if the price aven't be completed, it should be set to 1 (=100%)
150 151 152 153
      if cell['price']:
        price = cell['price']
      else: 
        price = 1
Fabien Morin's avatar
- typo  
Fabien Morin committed
154 155 156 157 158
      paycell.edit( mapped_value_property_list = ('price', 'quantity'),
                    quantity                   = cell['quantity'],
                    price                      = price,
                    force_update               = 1,
                    category_list              = cell_cat_list,
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
                  )

    return payline



  security.declareProtected(Permissions.AddPortalContent,
                          'createEditablePaySheetLineList')
  def createEditablePaySheetLineList(self, listbox, **kw):
    '''
      this script is called by the preview form to ask to the accountable 
      the values  of the editables lines and create corresponding 
      PaySheetLines with this values
    '''
    paysheet = self 
Fabien Morin's avatar
- typo  
Fabien Morin committed
174 175 176
    item_dict = {}
    model_line_id_list = []
    for cell in listbox:
177
      model_line = paysheet.getPortalObject().restrictedTraverse(\
Fabien Morin's avatar
- typo  
Fabien Morin committed
178 179
                                                          cell['model_line'])
      model_line_id = model_line.getId()
180 181
      service      = model_line.getResourceValue()
      service_id   = service.getId()
Fabien Morin's avatar
- typo  
Fabien Morin committed
182 183 184 185
      quantity     = cell['quantity']
      price        = cell['price']
      tax_category = cell['tax_category_relative_url']
      salary_range = cell['salary_range_relative_url']
186 187 188 189 190 191 192 193 194

      variation_category_list = model_line.getVariationCategoryList(\
                                            base_category_list='salary_range')
      salary_range_categories = []
      #for category in resource_variation_category_list:
      for category in variation_category_list:
        if category.startswith('salary_range/'): 
          salary_range_categories.append(category)

Fabien Morin's avatar
- typo  
Fabien Morin committed
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
      new_cell = { 'axe_list' : [tax_category,salary_range],
                   'quantity' : quantity,
                   'price'    : price,
                 }

      if item_dict.has_key(model_line_id):
        # an item for this model_line_id already exists
        item_dict[model_line_id]['cell_list'].append(new_cell)
      else:
        if model_line.getDescription():
          desc = model_line.getDescription()
        else:
          desc = model_line.getResourceValue().getDescription()

        model_line_id_list.append(model_line_id)
        # create a new item
        item_dict[model_line_id]={\
              'title' : model_line.getTitleOrId(),
              'res' : model_line.getResourceValue().getRelativeUrl(),
              'desc' : desc,
              'cell_list' : [new_cell],
              'int_index' : model_line.getFloatIndex(),
              'base_amount_list' : model_line.getBaseAmountList(),
            }

    for model_line_id in model_line_id_list:
      item = item_dict[model_line_id]
      paysheet.createPaySheetLine(title     = item['title'],
                                  res       = item['res'],
                                  desc      = item['desc'],
                                  cell_list = item['cell_list'],
                                  int_index = item['int_index'],
                                  base_amount_list = item['base_amount_list'])

        
        
Yoshinori Okuji's avatar
Yoshinori Okuji committed
231

232 233 234 235 236 237 238 239 240 241 242
  security.declareProtected(Permissions.ModifyPortalContent,
                          'createNotEditablePaySheetLineList')
  def createNotEditablePaySheetLineList(self, **kw):
    '''
      get all data required to create not editable paysheet lines and create it
      editable paysheet lines have been created by a script
    '''

    # Get Precision
    precision = self.getPriceCurrencyValue().getQuantityPrecision()

243 244 245

    # in this dictionary will be saved the current amount corresponding to 
    # the tuple (tax_category, base_amount) :
246 247
    # current_amount = base_amount_current_value_dict[base_amount]
    base_amount_current_value_dict = {}
248 249 250 251 252 253 254 255 256 257

    def sortByIntIndex(a, b):
      return cmp(a.getIntIndex(),
                 b.getIntIndex())


    base_amount_list = self.portal_categories['base_amount'].contentValues()
    base_amount_list.sort(sortByIntIndex)

    # it's important to get the editable lines to know if they contribute to
258 259
    # a base_amount (this is required to do the calcul later)

260 261 262 263 264 265 266 267 268 269 270 271
    # get edited lines:
    paysheetline_list = self.contentValues(portal_type = ['Pay Sheet Line'])

    for paysheetline in paysheetline_list:
      service = paysheetline.getResourceValue()
      base_amount_list = service.getBaseAmountList(base=1)
      for base_amount in base_amount_list:
        paysheetcell_list = paysheetline.contentValues(portal_type = \
                                                            ['Pay Sheet Cell'])
        for paysheetcell in paysheetcell_list:
          tax_category = paysheetcell.getTaxCategory(base=1)
          if tax_category and paysheetcell.getQuantity():
272 273
            if base_amount_current_value_dict.has_key(base_amount):
              old_val = base_amount_current_value_dict[base_amount]
274 275 276
            else:
              old_val = 0
            new_val = old_val + paysheetcell.getQuantity()
Fabien Morin's avatar
Fabien Morin committed
277
            # increment the corresponding amount
278
            base_amount_current_value_dict[base_amount] = new_val
279 280 281

    # get not editables model lines
    model = self.getSpecialiseValue()
282
    model_line_list = model.contentValues(portal_type='Pay Sheet Model Line',
Fabien Morin's avatar
Fabien Morin committed
283
                                          sort_on='int_index')
284
    model_line_list = [line for line in model_line_list if not line.getEditable()]
285 286 287 288

    pay_sheet_line_list = []

    # main loop : find all informations and create cell and PaySheetLines
289 290 291 292 293 294 295
    for model_line in model_line_list:
      cell_list       = []
      # test with predicate if this model line could be applied
      if not model_line.test(self,):
        # This line should not be used
        continue

Fabien Morin's avatar
Fabien Morin committed
296 297 298 299
      service          = model_line.getResourceValue()
      title            = model_line.getTitleOrId()
      int_index        = model_line.getFloatIndex()
      id               = model_line.getId()
300
      base_amount_list = model_line.getBaseAmountList()
Fabien Morin's avatar
Fabien Morin committed
301
      res              = service.getRelativeUrl()
302
      if model_line.getDescription():
Fabien Morin's avatar
Fabien Morin committed
303
        desc = ''.join(model_line.getDescription())
304 305
      # if the model_line description is empty, the payroll service 
      # description is used
Fabien Morin's avatar
typo  
Fabien Morin committed
306
      else: 
Fabien Morin's avatar
Fabien Morin committed
307
        desc = ''.join(service.getDescription())
308 309 310 311 312 313
      
      variation_share_list = model_line.getVariationCategoryList(\
                                      base_category_list=['tax_category',])
      variation_slice_list = model_line.getVariationCategoryList(\
                                      base_category_list=['salary_range',])

314 315
      for slice in variation_slice_list:
        for share in variation_share_list:
316
          cell = model_line.getCell(slice, share)
Fabien Morin's avatar
Fabien Morin committed
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
          if cell is None:
            LOG('createNotEditablePaySheetLineList : cell is None')
            continue
          # get the slice :
          model_slice = model_line.getParentValue().getCell(slice)
          quantity = 0.0
          price = 0.0
          if model_slice is None:
            LOG('createNotEditablePaySheetLineList : model_slice %s is None' %\
                                                                          slice)
            continue
          model_slice_min = model_slice.getQuantityRangeMin()
          model_slice_max = model_slice.getQuantityRangeMax()

          ######################
          # calculation part : #
          ######################

          # get script in this order
          # 1 - model_line script
          # 2 - model script
          # 3 - get the default calculation script

          # get the model line script
          script_name = model_line.getCalculationScriptId()
          if script_name is None:
            # if model line script is None, get the default model script
            script_name = model.getDefaultCalculationScriptId()

          if script_name is None:
            # if no calculation script found, use a default script :
            script_name = 'PaySheetTransaction_defaultCalculationScript'

          if getattr(self, script_name, None) is None:
            raise ValueError, "Unable to find `%s` calculation script" % \
                                                             script_name
          calculation_script = getattr(self, script_name, None)
          quantity=0
          price=0
Fabien Morin's avatar
- typo  
Fabien Morin committed
356
          #LOG('script_name :', 0, script_name)
Fabien Morin's avatar
Fabien Morin committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
          result = calculation_script(\
            base_amount_current_value_dict=base_amount_current_value_dict,
            model_slice_min=model_slice_min, 
            model_slice_max=model_slice_max, 
            cell=cell,)

          quantity = result['quantity']
          price = result['price']

          # Cell creation :
          # Define an empty new cell
          new_cell = { 'axe_list' : [share, slice],
                       'quantity' : quantity,
                       'price'    : price,
                     }
          cell_list.append(new_cell)

374 375 376 377 378 379 380 381 382 383 384 385 386
        # update base participation
        base_participation_list = service.getBaseAmountList(base=1)
        for base_participation in base_participation_list:
          if quantity:
            if base_amount_current_value_dict.has_key(base_participation):
              old_val = base_amount_current_value_dict[base_participation]
            else:
              old_val = 0
            new_val = old_val + quantity

            if price:
              new_val = round((old_val + quantity*price), precision) 
            base_amount_current_value_dict[base_participation] = new_val
387 388 389 390

      if cell_list:
        # create the PaySheetLine
        pay_sheet_line = self.createPaySheetLine(
391 392
                                    title     = title,
                                    res       = res,
393
                                    int_index = int_index,
394 395 396 397
                                    desc      = desc,
                                    base_amount_list = base_amount_list,
                                    cell_list = cell_list,
                                  )
398
        pay_sheet_line_list.append(pay_sheet_line)
399

400 401 402

    # this script is used to add a line that permit to have good accounting 
    # lines
403
    post_calculation_script = getattr(self,
404
                                'PaySheetTransaction_postCalculation', None)
405 406
    if post_calculation_script:
      post_calculation_script()
407 408

    return pay_sheet_line_list