############################################################################## # # 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 from Products.ERP5.Document.Invoice import Invoice from zLOG import LOG class PaySheetTransaction(Invoice): """ 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__ = ( ) security.declareProtected(Permissions.AccessContentsInformation, 'getRatioQuantityFromReference') def getRatioQuantityFromReference(self, ratio_reference=None): """ return the ratio value correponding to the ratio_reference, or description if ratio value is empty, 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] security.declareProtected(Permissions.AddPortalContent, 'createPaySheetLine') def createPaySheetLine(self, cell_list, title='', res='', desc='', base_amount_list=None, int_index=None, **kw): ''' 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, base_amount_list = base_amount_list, int_index = int_index, **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) # if the quantity aven't be completed, it should be set to 1 (=100%) if cell['price']: price = cell['price'] else: price = 1 paycell.edit( mapped_value_property_list = ('price', 'quantity') , quantity = cell['quantity'] , price = price , force_update = 1 , category_list = cell_cat_list ) 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 paysheet_items = {} for line in listbox: model_line = paysheet.getPortalObject().restrictedTraverse(\ line['model_line']) service = model_line.getResourceValue() service_id = service.getId() quantity = line['quantity'] price = line['price'] tax_category = line['tax_category_relative_url'] 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) # perhaps here it will be necesary to raise an error ? if not paysheet_items.has_key(service_id): paysheet_items[service_id] = { 'title' : model_line.getTitleOrId(), 'desc' : [], 'base_amount_list' : model_line.getBaseAmountList(), 'res' : service.getRelativeUrl(), 'int_index' : model_line.getFloatIndex(), 'cell_list' : [] } # create cells if a value has been entered by accountable if quantity or price: for salary_range in salary_range_categories: # Define an empty new cell new_cell = None new_cell = { 'axe_list' : [tax_category,salary_range] , 'quantity' : quantity , 'price' : price } # Add the cell to the conresponding paysheet item if new_cell: paysheet_items[service_id]['cell_list'].append(new_cell) # Save the comment as description if model_line.getDescription(): paysheet_items[service_id]['desc'].append(\ model_line.getDescription()) else: resource_description = \ model_line.getResourceValue().getDescription() paysheet_items[service_id]['desc'].append(resource_description) # Create a paysheet item for each service with user data in it for item in paysheet_items.values(): if item['cell_list']: if len(item['desc']) > 0: desc = '\n'.join(item['desc']) else: desc = None paysheet.createPaySheetLine( title = item['title'], res = item['res'], desc = desc, cell_list = item['cell_list'], int_index = item['int_index'], base_amount_list=item['base_amount_list'],) 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() def getDictList(category_list): ''' This method return a list of dict wich are composed with the given category as key and an integer as value. It's used to find datas in a two-dimensional table ''' list_of_dict = [] for category in category_list: base_amount_list=[x.getRelativeUrl() for x in\ self.portal_categories[category].objectValues()] list_of_dict.append(dict([[base, base_amount_list.index(base)] \ for base in base_amount_list])) return list_of_dict def getEmptyTwoDimentionalTable(nb_columns=0, nb_rows=0): ''' create a two-dimentional table with nb_columns columns and nb_rows rows all item of this table are set to None ex : with nb_columns=2 and nb_rows=4, this function returns : [[None, None, None, None], [None, None, None, None]] ''' base_amount = [] for i in range(nb_columns): line = [] for j in range(nb_rows): line.append(None) base_amount.append(line) return base_amount # this dict permit to have the index of the category in the table column, row = getDictList(['tax_category', 'base_amount']) base_amount_table = getEmptyTwoDimentionalTable(nb_columns=len(column), nb_rows=len(row)) 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 # a base_amount (this is required to do the calcul later) # 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: if not row.has_key(base_amount): raise ValueError, "Unable to find `%s` base_amount category" % \ base_amount 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(): old_val = base_amount_table[column[tax_category]][row[base_amount]] if old_val is not None: new_val = old_val + paysheetcell.getQuantity() else: new_val = paysheetcell.getQuantity() base_amount_table[column[tax_category]][row[base_amount]] = new_val # get not editables model lines model = self.getSpecialiseValue() model_line_list = model.contentValues(portal_type='Pay Sheet Model Line', sort_on='int_index') model_line_list = [line for line in model_line_list if not line.getEditable()] pay_sheet_line_list = [] employee_tax_amount = 0 # main loop : find all informations and create cell and PaySheetLines 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 service = model_line.getResourceValue() title = model_line.getTitleOrId() int_index = model_line.getFloatIndex() id = model_line.getId() base_amount_list = model_line.getBaseAmountList() res = service.getRelativeUrl() if model_line.getDescription(): desc = ''.join(model_line.getDescription()) # if the model_line description is empty, the payroll service # description is used else: desc = ''.join(service.getDescription()) variation_share_list = model_line.getVariationCategoryList(\ base_category_list=['tax_category',]) variation_slice_list = model_line.getVariationCategoryList(\ base_category_list=['salary_range',]) for share in variation_share_list: base_application = None for slice in variation_slice_list: #get the amount of application for this line base_application_list = model_line.getBaseAmountList(base=1) if base_application is None: base_application = 0 for base in model_line.getBaseAmountList(base=1): if base_amount_table[column[share]][row[base]] is not None: base_application += \ base_amount_table[column[share]][row[base]] cell = model_line.getCell(slice, share) if cell is not None: # get the slice : model_slice = None model_slice = model_line.getParentValue().getCell(slice) quantity = 0.0 price = 0.0 if model_slice is not None: model_slice_min = model_slice.getQuantityRangeMin() model_slice_max = model_slice.getQuantityRangeMax() quantity = cell.getQuantity() price = cell.getPrice() ###################### # calculation part : # ###################### script_name = model.getLocalizedCalculationScriptId() if script_name is None or \ getattr(self, script_name, None) is None: # if no calculation script found, use a default method : if not quantity: if base_application <= model_slice_max: quantity = base_application-model_slice_min else: quantity = model_slice_max-model_slice_min else: localized_calculation_script = getattr(self, script_name, None) quantity, price = localized_calculation_script(\ paysheet=self, model_slice_min = model_slice_min, model_slice_max=model_slice_max, quantity=quantity, price=price, model_line=model_line) # Cell creation : # Define an empty new cell new_cell = { 'axe_list' : [share, slice] , 'quantity' : quantity , 'price' : price } cell_list.append(new_cell) #XXX this is a hack to have the net salary base_list = model_line.getResourceValue().getBaseAmountList() if price is not None and 'employee_share' in share and\ not ('base_salary' in base_list or\ 'bonus' in base_list or\ 'gross_salary' in base_list): employee_tax_amount += round((price * quantity), precision) # update base participation base_participation_list = service.getBaseAmountList(base=1) for base_participation in base_participation_list: old_val = \ base_amount_table[column[share]][row[base_participation]] if quantity: if old_val is not None: new_val = old_val + quantity else: new_val = quantity if price: if old_val is not None: new_val = round((old_val + quantity*price), precision) base_amount_table[column[share]][row[base_participation]]= \ new_val base_amount_table[column[share]][row[base_participation]] = \ new_val if cell_list: # create the PaySheetLine pay_sheet_line = self.createPaySheetLine( title = title, res = res, int_index = int_index, desc = desc, base_amount_list = base_amount_list, cell_list = cell_list, ) pay_sheet_line_list.append(pay_sheet_line) # this script is used to add a line that permit to have good accounting # lines localized_add_end_line_script = getattr(self, 'PaySheetTransaction_addEndLine', None) if localized_add_end_line_script: localized_add_end_line_script(employee_tax_amount) return pay_sheet_line_list