X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fhr_payroll%2Fhr_payroll.py;h=e8f96df261bb8ddfd7c90755c23c5dc97f7464e6;hb=dfdbf8cf1700de3fe00bc4c30fe78cb73f19ae51;hp=70bac2a497c409d634702bf48eb1ee1a3d291d73;hpb=c002b2df9e01341f596ed2b697036db028efba31;p=odoo%2Fodoo.git diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index 70bac2a..e8f96df 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -50,6 +50,7 @@ class hr_payroll_structure(osv.osv): 'company_id':fields.many2one('res.company', 'Company', required=True), 'note': fields.text('Description'), 'parent_id':fields.many2one('hr.payroll.structure', 'Parent'), + 'children_ids':fields.one2many('hr.payroll.structure', 'parent_id', 'Children'), } def _get_parent(self, cr, uid, context=None): @@ -119,7 +120,7 @@ class hr_contract(osv.osv): _inherit = 'hr.contract' _description = 'Employee Contract' _columns = { - 'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure', required=True), + 'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure'), 'schedule_pay': fields.selection([ ('monthly', 'Monthly'), ('quarterly', 'Quarterly'), @@ -131,6 +132,10 @@ class hr_contract(osv.osv): ], 'Scheduled Pay', select=True), } + _defaults = { + 'schedule_pay': 'monthly', + } + def get_all_structures(self, cr, uid, contract_ids, context=None): """ @param contract_ids: list of contracts @@ -175,6 +180,7 @@ class hr_salary_rule_category(osv.osv): 'name':fields.char('Name', size=64, required=True, readonly=False), 'code':fields.char('Code', size=64, required=True, readonly=False), 'parent_id':fields.many2one('hr.salary.rule.category', 'Parent', help="Linking a salary category to its parent is used only for the reporting purpose."), + 'children_ids': fields.one2many('hr.salary.rule.category', 'parent_id', 'Children'), 'note': fields.text('Description'), 'company_id':fields.many2one('res.company', 'Company', required=False), } @@ -205,16 +211,22 @@ class one2many_mod2(fields.one2many): class hr_payslip_run(osv.osv): _name = 'hr.payslip.run' + _description = 'Payslip Batches' _columns = { 'name': fields.char('Name', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}), 'slip_ids': fields.one2many('hr.payslip', 'payslip_run_id', 'Payslips', required=False, readonly=True, states={'draft': [('readonly', False)]}), 'state': fields.selection([ ('draft', 'Draft'), ('close', 'Close'), - ], 'State', select=True, readonly=True) + ], 'Status', select=True, readonly=True), + 'date_start': fields.date('Date From', required=True, readonly=True, states={'draft': [('readonly', False)]}), + 'date_end': fields.date('Date To', required=True, readonly=True, states={'draft': [('readonly', False)]}), + 'credit_note': fields.boolean('Credit Note', readonly=True, states={'draft': [('readonly', False)]}, help="If its checked, indicates that all payslips generated from here are refund payslips."), } _defaults = { 'state': 'draft', + 'date_start': lambda *a: time.strftime('%Y-%m-01'), + 'date_end': lambda *a: str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10], } def draft_payslip_run(self, cr, uid, ids, context=None): @@ -259,7 +271,7 @@ class hr_payslip(osv.osv): ('verify', 'Waiting'), ('done', 'Done'), ('cancel', 'Rejected'), - ], 'State', select=True, readonly=True, + ], 'Status', select=True, readonly=True, help='* When the payslip is created the state is \'Draft\'.\ \n* If the payslip is under verification, the state is \'Waiting\'. \ \n* If the payslip is confirmed then state is set to \'Done\'.\ @@ -273,8 +285,8 @@ class hr_payslip(osv.osv): 'note': fields.text('Description', readonly=True, states={'draft':[('readonly',False)]}), 'contract_id': fields.many2one('hr.contract', 'Contract', required=False, readonly=True, states={'draft': [('readonly', False)]}), 'details_by_salary_rule_category': fields.function(_get_lines_salary_rule_category, method=True, type='one2many', relation='hr.payslip.line', string='Details by Salary Rule Category'), - 'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another"), - 'payslip_run_id': fields.many2one('hr.payslip.run', 'Payslip Run', readonly=True, states={'draft': [('readonly', False)]}), + 'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another", readonly=True, states={'draft': [('readonly', False)]}), + 'payslip_run_id': fields.many2one('hr.payslip.run', 'Payslip Batches', readonly=True, states={'draft': [('readonly', False)]}), } _defaults = { 'date_from': lambda *a: time.strftime('%Y-%m-01'), @@ -286,6 +298,14 @@ class hr_payslip(osv.osv): context=context).company_id.id, } + def _check_dates(self, cr, uid, ids, context=None): + for payslip in self.browse(cr, uid, ids, context=context): + if payslip.date_from > payslip.date_to: + return False + return True + + _constraints = [(_check_dates, "Payslip 'Date From' must be before 'Date To'.", ['date_from', 'date_to'])] + def copy(self, cr, uid, id, default=None, context=None): if not default: default = {} @@ -304,23 +324,17 @@ class hr_payslip(osv.osv): def cancel_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'cancel'}, context=context) - def account_check_sheet(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state': 'accont_check'}, context=context) - - def hr_check_sheet(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state': 'hr_check'}, context=context) - def process_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'paid': True, 'state': 'done'}, context=context) def hr_verify_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'verify'}, context=context) - + def refund_sheet(self, cr, uid, ids, context=None): mod_obj = self.pool.get('ir.model.data') wf_service = netsvc.LocalService("workflow") - for id in ids: - id_copy = self.copy(cr, uid, id, {'credit_note': True}, context=context) + for payslip in self.browse(cr, uid, ids, context=context): + id_copy = self.copy(cr, uid, payslip.id, {'credit_note': True, 'name': _('Refund: ')+payslip.name}, context=context) self.compute_sheet(cr, uid, [id_copy], context=context) wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'hr_verify_sheet', cr) wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'process_sheet', cr) @@ -343,9 +357,6 @@ class hr_payslip(osv.osv): 'context': {} } - def verify_sheet(self, cr, uid, ids, context=None): - return self.write(cr, uid, ids, {'state': 'confirm'}, context=context) - def check_done(self, cr, uid, ids, context=None): return True @@ -373,7 +384,7 @@ class hr_payslip(osv.osv): slip_line_pool = self.pool.get('hr.payslip.line') sequence_obj = self.pool.get('ir.sequence') for payslip in self.browse(cr, uid, ids, context=context): - number = sequence_obj.get(cr, uid, 'salary.slip') + number = payslip.number or sequence_obj.get(cr, uid, 'salary.slip') #delete old payslip lines old_slipline_ids = slip_line_pool.search(cr, uid, [('slip_id', '=', payslip.id)], context=context) # old_slipline_ids @@ -471,7 +482,7 @@ class hr_payslip(osv.osv): def _sum_salary_rule_category(localdict, category, amount): if category.parent_id: localdict = _sum_salary_rule_category(localdict, category.parent_id, amount) - localdict['categories'][category.code] = category.code in localdict['categories'] and localdict['categories'][category.code] + amount or amount + localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount return localdict class BrowsableObject(object): @@ -483,7 +494,7 @@ class hr_payslip(osv.osv): self.dict = dict def __getattr__(self, attr): - return self.dict.__getitem__(attr) + return attr in self.dict and self.dict.__getitem__(attr) or 0.0 class InputLine(BrowsableObject): """a class that will be used into the python code, mainly for usability purposes""" @@ -491,9 +502,9 @@ class hr_payslip(osv.osv): if to_date is None: to_date = datetime.now().strftime('%Y-%m-%d') result = 0.0 - self.cr.execute("SELECT sum(quantity) as sum\ + self.cr.execute("SELECT sum(amount) as sum\ FROM hr_payslip as hp, hr_payslip_input as pi \ - WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \ + WHERE hp.employee_id = %s AND hp.state = 'done' \ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s", (self.employee_id, from_date, to_date, code)) res = self.cr.fetchone()[0] @@ -507,7 +518,7 @@ class hr_payslip(osv.osv): result = 0.0 self.cr.execute("SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours\ FROM hr_payslip as hp, hr_payslip_worked_days as pi \ - WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \ + WHERE hp.employee_id = %s AND hp.state = 'done'\ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s", (self.employee_id, from_date, to_date, code)) return self.cr.fetchone() @@ -528,7 +539,7 @@ class hr_payslip(osv.osv): to_date = datetime.now().strftime('%Y-%m-%d') self.cr.execute("SELECT sum(case when hp.credit_note = False then (pl.total) else (-pl.total) end)\ FROM hr_payslip as hp, hr_payslip_line as pl \ - WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \ + WHERE hp.employee_id = %s AND hp.state = 'done' \ AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s", (self.employee_id, from_date, to_date, code)) res = self.cr.fetchone() @@ -536,6 +547,8 @@ class hr_payslip(osv.osv): #we keep a dict with the result because a value can be overwritten by another rule with the same code result_dict = {} + rules = {} + categories_dict = {} blacklist = [] payslip_obj = self.pool.get('hr.payslip') inputs_obj = self.pool.get('hr.payslip.worked_days') @@ -548,11 +561,13 @@ class hr_payslip(osv.osv): for input_line in payslip.input_line_ids: inputs[input_line.code] = input_line + categories_obj = BrowsableObject(self.pool, cr, uid, payslip.employee_id.id, categories_dict) input_obj = InputLine(self.pool, cr, uid, payslip.employee_id.id, inputs) worked_days_obj = WorkedDays(self.pool, cr, uid, payslip.employee_id.id, worked_days) payslip_obj = Payslips(self.pool, cr, uid, payslip.employee_id.id, payslip) + rules_obj = BrowsableObject(self.pool, cr, uid, payslip.employee_id.id, rules) - localdict = {'categories': {}, 'payslip': payslip_obj, 'worked_days': worked_days_obj, 'inputs': input_obj} + localdict = {'categories': categories_obj, 'rules': rules_obj, 'payslip': payslip_obj, 'worked_days': worked_days_obj, 'inputs': input_obj} #get the ids of the structures on the contracts and their parent id as well structure_ids = self.pool.get('hr.contract').get_all_structures(cr, uid, contract_ids, context=context) #get the rules of the structure and thier children @@ -566,16 +581,19 @@ class hr_payslip(osv.osv): for rule in obj_rule.browse(cr, uid, sorted_rule_ids, context=context): key = rule.code + '-' + str(contract.id) localdict['result'] = None + localdict['result_qty'] = 1.0 #check if the rule can be applied if obj_rule.satisfy_condition(cr, uid, rule.id, localdict, context=context) and rule.id not in blacklist: #compute the amount of the rule - amount = obj_rule.compute_rule(cr, uid, rule.id, localdict, context=context) + amount, qty, rate = obj_rule.compute_rule(cr, uid, rule.id, localdict, context=context) #check if there is already a rule computed with that code previous_amount = rule.code in localdict and localdict[rule.code] or 0.0 #set/overwrite the amount computed for this rule in the localdict - localdict[rule.code] = amount + tot_rule = amount * qty * rate / 100.0 + localdict[rule.code] = tot_rule + rules[rule.code] = rule #sum the amount for its salary category - localdict = _sum_salary_rule_category(localdict, rule.category_id, amount - previous_amount) + localdict = _sum_salary_rule_category(localdict, rule.category_id, tot_rule - previous_amount) #create/overwrite the rule in the temporary results result_dict[key] = { 'salary_rule_id': rule.id, @@ -596,9 +614,10 @@ class hr_payslip(osv.osv): 'amount_percentage': rule.amount_percentage, 'amount_percentage_base': rule.amount_percentage_base, 'register_id': rule.register_id.id, - 'total': amount, + 'amount': amount, 'employee_id': contract.employee_id.id, - 'quantity': rule.quantity, + 'quantity': qty, + 'rate': rate, } else: #blacklist this rule and its children @@ -637,7 +656,7 @@ class hr_payslip(osv.osv): 'struct_id': False, } } - if not employee_id: + if (not employee_id) or (not date_from) or (not date_to): return res ttyme = datetime.fromtimestamp(time.mktime(time.strptime(date_from, "%Y-%m-%d"))) employee_id = empolyee_obj.browse(cr, uid, employee_id, context=context) @@ -703,8 +722,8 @@ class hr_payslip_worked_days(osv.osv): _description = 'Payslip Worked Days' _columns = { 'name': fields.char('Description', size=256, required=True), - 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True), - 'sequence': fields.integer('Sequence', required=True,), + 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade', select=True), + 'sequence': fields.integer('Sequence', required=True, select=True), 'code': fields.char('Code', size=52, required=True, help="The code that can be used in the salary rules"), 'number_of_days': fields.float('Number of Days'), 'number_of_hours': fields.float('Number of Hours'), @@ -725,16 +744,16 @@ class hr_payslip_input(osv.osv): _description = 'Payslip Input' _columns = { 'name': fields.char('Description', size=256, required=True), - 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True), - 'sequence': fields.integer('Sequence', required=True,), + 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade', select=True), + 'sequence': fields.integer('Sequence', required=True, select=True), 'code': fields.char('Code', size=52, required=True, help="The code that can be used in the salary rules"), - 'quantity': fields.float('Quantity', help="It is used in computation. For e.g. A rule for sales having 1% commission of basic salary for per product can defined in expression like result = inputs.SASUS.qunatity * contract.wage*0.01."), + 'amount': fields.float('Amount', help="It is used in computation. For e.g. A rule for sales having 1% commission of basic salary for per product can defined in expression like result = inputs.SALEURO.amount * contract.wage*0.01."), 'contract_id': fields.many2one('hr.contract', 'Contract', required=True, help="The contract for which applied this input"), } _order = 'payslip_id, sequence' _defaults = { 'sequence': 10, - 'quantity': 0.0, + 'amount': 0.0, } hr_payslip_input() @@ -745,7 +764,7 @@ class hr_salary_rule(osv.osv): _columns = { 'name':fields.char('Name', size=256, required=True, readonly=False), 'code':fields.char('Code', size=64, required=True, help="The code of salary rules can be used as reference in computation of other rules. In that case, it is case sensitive."), - 'sequence': fields.integer('Sequence', required=True, help='Use to arrange calculation sequence'), + 'sequence': fields.integer('Sequence', required=True, help='Use to arrange calculation sequence', select=True), 'quantity': fields.char('Quantity', size=256, help="It is used in computation for percentage and fixed amount.For e.g. A rule for Meal Voucher having fixed amount of 1€ per worked day can have its quantity defined in expression like worked_days.WORK100.number_of_days."), 'category_id':fields.many2one('hr.salary.rule.category', 'Category', required=True), 'active':fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the salary rule without removing it."), @@ -753,7 +772,7 @@ class hr_salary_rule(osv.osv): 'parent_rule_id':fields.many2one('hr.salary.rule', 'Parent Salary Rule', select=True), 'company_id':fields.many2one('res.company', 'Company', required=False), 'condition_select': fields.selection([('none', 'Always True'),('range', 'Range'), ('python', 'Python Expression')], "Condition Based on", required=True), - 'condition_range':fields.char('Range Based on',size=1024, readonly=False, help='This will use to computer the % fields values, in general its on basic, but You can use all categories code field in small letter as a variable name i.e. hra, ma, lta, etc...., also you can use, static varible basic'), + 'condition_range':fields.char('Range Based on',size=1024, readonly=False, help='This will be used to compute the % fields values; in general it is on basic, but you can also use categories code fields in lowercase as a variable names (hra, ma, lta, etc.) and the variable basic.'), 'condition_python':fields.text('Python Condition', required=True, readonly=False, help='Applied this rule for calculation if condition is true. You can specify condition like basic > 1000.'),#old name = conditions 'condition_range_min': fields.float('Minimum Range', required=False, help="The minimum amount, applied for this rule."), 'condition_range_max': fields.float('Maximum Range', required=False, help="The maximum amount, applied for this rule."), @@ -762,21 +781,12 @@ class hr_salary_rule(osv.osv): ('fix','Fixed Amount'), ('code','Python Code'), ],'Amount Type', select=True, required=True, help="The computation method for the rule amount."), - 'amount_fix': fields.float('Fixed Amount', digits_compute=dp.get_precision('Account'),), - 'amount_percentage': fields.float('Percentage (%)', digits_compute=dp.get_precision('Account'), help='For example, enter 50.0 to apply a percentage of 50%'), + 'amount_fix': fields.float('Fixed Amount', digits_compute=dp.get_precision('Payroll'),), + 'amount_percentage': fields.float('Percentage (%)', digits_compute=dp.get_precision('Payroll Rate'), help='For example, enter 50.0 to apply a percentage of 50%'), 'amount_python_compute':fields.text('Python Code'), 'amount_percentage_base':fields.char('Percentage based on',size=1024, required=False, readonly=False, help='result will be affected to a variable'), 'child_ids':fields.one2many('hr.salary.rule', 'parent_rule_id', 'Child Salary Rule'), - 'register_id':fields.property( - 'hr.contribution.register', - type='many2one', - relation='hr.contribution.register', - string="Contribution Register", - method=True, - view_load=True, - help="Contribution register based on company", - required=False - ), + 'register_id':fields.many2one('hr.contribution.register', 'Contribution Register', help="Eventual third party involved in the salary payment of the employees."), 'input_ids': fields.one2many('hr.rule.input', 'input_id', 'Inputs'), 'note':fields.text('Description'), } @@ -787,8 +797,8 @@ class hr_salary_rule(osv.osv): # payslip: object containing the payslips # employee: hr.employee object # contract: hr.contract object -# rules: rules code (previously computed) -# categories: dictionary containing the computed salary rule categories (sum of amount of all rules belonging to that category). Keys are the category codes. +# rules: object containing the rules code (previously computed) +# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category). # worked_days: object containing the computed worked days. # inputs: object containing the computed inputs. @@ -802,14 +812,14 @@ result = contract.wage * 0.10''', # payslip: object containing the payslips # employee: hr.employee object # contract: hr.contract object -# rules: rules code (previously computed) -# categories: dictionary containing the computed salary rule categories (sum of amount of all rules belonging to that category). Keys are the category codes. +# rules: object containing the rules code (previously computed) +# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category). # worked_days: object containing the computed worked days # inputs: object containing the computed inputs # Note: returned value have to be set in the variable 'result' -result = rules['NET'] > categories['NET'] * 0.10''', +result = rules.NET > categories.NET * 0.10''', 'condition_range': 'contract.wage', 'sequence': 5, 'appears_on_payslip': True, @@ -821,7 +831,7 @@ result = rules['NET'] > categories['NET'] * 0.10''', 'amount_select': 'fix', 'amount_fix': 0.0, 'amount_percentage': 0.0, - 'quantity': '1', + 'quantity': '1.0', } def _recursive_search_of_rules(self, cr, uid, rule_ids, context=None): @@ -838,26 +848,26 @@ result = rules['NET'] > categories['NET'] * 0.10''', #TODO should add some checks on the type of result (should be float) def compute_rule(self, cr, uid, rule_id, localdict, context=None): """ - @param rule_id: id of rule to compute - @param localdict: dictionary containing the environement in which to compute the rule - @return: returns the result of computation as float + :param rule_id: id of rule to compute + :param localdict: dictionary containing the environement in which to compute the rule + :return: returns a tuple build as the base/amount computed, the quantity and the rate + :rtype: (float, float, float) """ rule = self.browse(cr, uid, rule_id, context=context) if rule.amount_select == 'fix': try: - return rule.amount_fix * eval(rule.quantity, localdict) + return rule.amount_fix, eval(rule.quantity, localdict), 100.0 except: raise osv.except_osv(_('Error'), _('Wrong quantity defined for salary rule %s (%s)')% (rule.name, rule.code)) elif rule.amount_select == 'percentage': try: - amount = rule.amount_percentage * eval(rule.amount_percentage_base, localdict) / 100 - return amount * eval(rule.quantity, localdict) + return eval(rule.amount_percentage_base, localdict), eval(rule.quantity, localdict), rule.amount_percentage except: raise osv.except_osv(_('Error'), _('Wrong percentage base or quantity defined for salary rule %s (%s)')% (rule.name, rule.code)) else: try: eval(rule.amount_python_compute, localdict, mode='exec', nocopy=True) - return localdict['result'] + return localdict['result'], 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0 except: raise osv.except_osv(_('Error'), _('Wrong python code defined for salary rule %s (%s) ')% (rule.name, rule.code)) @@ -911,13 +921,27 @@ class hr_payslip_line(osv.osv): _description = 'Payslip Line' _order = 'contract_id, sequence' + def _calculate_total(self, cr, uid, ids, name, args, context): + if not ids: return {} + res = {} + for line in self.browse(cr, uid, ids, context=context): + res[line.id] = float(line.quantity) * line.amount * line.rate / 100 + return res + _columns = { - 'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True), + 'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'), 'salary_rule_id':fields.many2one('hr.salary.rule', 'Rule', required=True), 'employee_id':fields.many2one('hr.employee', 'Employee', required=True), - 'contract_id':fields.many2one('hr.contract', 'Contract', required=True), - 'total': fields.float('Amount', digits_compute=dp.get_precision('Account')), - 'company_contrib': fields.float('Company Contribution', readonly=True, digits_compute=dp.get_precision('Account')), + 'contract_id':fields.many2one('hr.contract', 'Contract', required=True, select=True), + 'rate': fields.float('Rate (%)', digits_compute=dp.get_precision('Payroll Rate')), + 'amount': fields.float('Amount', digits_compute=dp.get_precision('Payroll')), + 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Payroll')), + 'total': fields.function(_calculate_total, method=True, type='float', string='Total', digits_compute=dp.get_precision('Payroll'),store=True ), + } + + _defaults = { + 'quantity': 1.0, + 'rate': 100.0, } hr_payslip_line() @@ -959,7 +983,7 @@ class hr_employee(osv.osv): _columns = { 'slip_ids':fields.one2many('hr.payslip', 'employee_id', 'Payslips', required=False, readonly=True), - 'total_wage': fields.function(_calculate_total_wage, method=True, type='float', string='Total Basic Salary', digits_compute=dp.get_precision('Account'), help="Sum of all current contract's wage of employee."), + 'total_wage': fields.function(_calculate_total_wage, method=True, type='float', string='Total Basic Salary', digits_compute=dp.get_precision('Payroll'), help="Sum of all current contract's wage of employee."), } hr_employee()