diff --git a/payroll.py b/payroll.py index dfeb546..01f15b0 100644 --- a/payroll.py +++ b/payroll.py @@ -1,8 +1,10 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime +import time from decimal import Decimal from operator import attrgetter +from functools import lru_cache from trytond.model import Workflow, ModelView, ModelSQL, fields from trytond.pyson import Bool, Eval, If, Id from trytond.pool import Pool, PoolMeta @@ -300,13 +302,13 @@ class Payroll(Workflow, ModelSQL, ModelView): return {'salary': salary_full} def compute_salary_full(self, wage): - wages_ids = [s.id for s in wage.concepts_salary] - if wages_ids: - salary_full = sum( - line.amount for line in self.lines if line.wage_type.id in wages_ids) + if wage['salary_constitute'] or not wage['concepts_salary']: + return self.contract.get_salary_in_date(self.end) or 0 + + # wages_ids = [s.id for s in wage.concepts_salary] + salary_full = sum( + line.amount for line in self.lines if line.wage_type.id in wage['concepts_salary']) - else: - salary_full = self.contract.get_salary_in_date(self.end) or 0 return salary_full def create_move(self): @@ -451,7 +453,6 @@ class Payroll(Workflow, ModelSQL, ModelView): if credit_acc and not line_credit_ready: lines_moves[credit_acc.id][party_id]['credit'] += amount_credit except Exception as e: - print(wage_type.name, 'this is wage_type', e) raise WageTypeValidationError( gettext('staff_payroll.bad_configuration_of_wage_type', wage=wage_type.name)) result = [] @@ -468,119 +469,150 @@ class Payroll(Workflow, ModelSQL, ModelView): result.extend(_line) return result - def _create_payroll_lines(self, wages, extras, discounts=None): + def _create_payroll_lines(self, config, wages, extras, discounts=None, cache_wage_dict=None): PayrollLine = Pool().get('staff.payroll.line') + Wage = Pool().get('staff.wage_type') values = [] salary_args = {} - salary_in_date = self.contract.get_salary_in_date(self.end) + # salary_in_date = self.contract.get_salary_in_date(self.end) get_line = self.get_line get_line_quantity = self.get_line_quantity get_line_quantity_special = self.get_line_quantity_special get_salary_full = self.get_salary_full values_append = values.append + compute_unit_price = Wage.compute_unit_price for wage, party, fix_amount in wages: if not fix_amount: + time_salary = time.time() salary_args = get_salary_full(wage) - if wage.salary_constitute: - salary_args['salary'] = salary_in_date - + time_salary2 = time.time() + print(time_salary2-time_salary, 'time salary') # Este metodo instancia podria pasarse a un metodo de clase - unit_value = wage.compute_unit_price(salary_args) + unit_value = compute_unit_price(wage['unit_price_formula'], salary_args) else: unit_value = fix_amount discount = None - if discounts and discounts.get(wage.id): - discount = discounts.get(wage.id) - qty = get_line_quantity_special(wage) - if qty == 0: + if discounts and discounts.get(wage['id']): + discount = discounts.get(wage['id']) + if wage['type_concept'] == 'especial': + qty = get_line_quantity_special(wage) + else: + time_qty = time.time() qty = get_line_quantity( - wage, self.start, self.end, extras, discount + config, wage, self.start, self.end, extras, discount ) + time_qty2 = time.time() + print(time_qty2 - time_qty, 'time qty') + time_line = time.time() line_ = get_line(wage, qty, unit_value, party) + time_line2 = time.time() + print(time_line2-time_line, 'time line') values_append(line_) PayrollLine.create(values) - def set_preliquidation(self, extras, discounts=None): + @classmethod + def create_cache_wage_types(cls): + Wage = Pool().get('staff.wage_type') + fields_names = [ + 'unit_price_formula', 'concepts_salary', 'salary_constitute', + 'name', 'sequence', 'definition', 'unit_price_formula', + 'expense_formula', 'uom', 'default_quantity', 'type_concept', + 'salary_constitute', 'receipt', 'concepts_salary', + 'contract_finish', 'limit_days', 'month_application', + 'minimal_amount', 'adjust_days_worked', 'round_amounts' + ] + wages = Wage.search_read([], fields_names=fields_names) + return {w['id']: w for w in wages} + + def set_preliquidation(self, config, extras, discounts=None, cache_wage_dict=None): wage_salary = [] wage_no_salary = [] - + wage_salary_append = wage_salary.append + wage_no_salary_append = wage_no_salary.append + # if not cache_wage_dict: + # cache_wage_dict = self.create_cache_wage_types() + attr_mandatory = attrgetter('wage_type', 'party', 'fix_amount') for concept in self.employee.mandatory_wages: - wage_type = concept.wage_type - if concept.wage_type.salary_constitute: + wage_type, party, fix_amount = attr_mandatory(concept) + if wage_type.salary_constitute: wage_salary.append( - (wage_type, concept.party, concept.fix_amount)) + (cache_wage_dict[wage_type.id], party, fix_amount)) else: wage_no_salary.append( - (wage_type, concept.party, concept.fix_amount)) + (cache_wage_dict[wage_type.id], party, fix_amount)) - self._create_payroll_lines(wage_salary, extras, discounts) - self._create_payroll_lines(wage_no_salary, extras, discounts) + self._create_payroll_lines(config, wage_salary, extras, discounts, cache_wage_dict) + self._create_payroll_lines(config, wage_no_salary, extras, discounts, cache_wage_dict) - def update_preliquidation(self, extras): + def update_preliquidation(self, extras, cache_wage_dict): + Wage = Pool().get('staff.wage_type') + get_salary_full = self.get_salary_full + compute_unit_price = Wage.compute_unit_price for line in self.lines: - if line.wage_type.salary_constitute: + wage_id = line.wage_type.id + wage = cache_wage_dict[wage_id] + if wage['salary_constitute']: continue - salary_args = self.get_salary_full(line.wage_type) - unit_value = line.wage_type.compute_unit_price(salary_args) - unit_value = self._validate_amount_wage(line.wage_type, unit_value) + salary_args = get_salary_full(wage) + unit_value = compute_unit_price(wage['unit_price_formula'], salary_args) line.write([line], { 'unit_value': unit_value, }) def get_line(self, wage, qty, unit_value, party=None): res = { - 'sequence': wage.sequence, + 'sequence': wage['sequence'], 'payroll': self.id, - 'wage_type': wage.id, - 'description': wage.name, + 'wage_type': wage['id'], + 'description': wage['name'], 'quantity': Decimal(str(round(qty, 2))), 'unit_value': Decimal(str(round(unit_value, 2))), - 'uom': wage.uom, - 'receipt': wage.receipt, + 'uom': wage['uom'], + 'receipt': wage['receipt'], } if party: res['party'] = party.id return res - def _get_line_quantity(self, quantity_days, wage, extras, discount): - Configuration = Pool().get('staff.configuration') - config = Configuration(1) + def _get_line_quantity(self, config, quantity_days, wage, extras, discount): + # Configuration = Pool().get('staff.configuration') + # config = Configuration(1) default_hour_workday = config.default_hour_workday or _DEFAULT_WORK_DAY - quantity = wage.default_quantity or 0 + quantity = wage['default_quantity'] or 0 + uom_id = wage['uom'] + wage_name = wage['name'].lower() if quantity_days < 0: quantity_days = 0 - if wage.uom.id == Id('product', 'uom_day').pyson(): + if uom_id == Id('product', 'uom_day').pyson(): quantity = quantity_days if discount: quantity -= discount - elif wage.uom.id == Id('product', 'uom_hour').pyson(): - if wage.type_concept != 'extras': + elif uom_id == Id('product', 'uom_hour').pyson(): + if wage['type_concept'] != 'extras': quantity = quantity_days * default_hour_workday if discount: quantity -= discount else: key_ = [key for key in extras.keys( - ) if wage.name.lower().count(key) > 0] + ) if wage_name.count(key) > 0] if key_: key_ext = key_[0] extras_ = extras.get(key_ext) else: - extras_ = extras.get((wage.name.lower())) - if extras_ and self.employee.position and self.employee.position.extras: - quantity = extras_ + extras_ = extras.get((wage_name)) + quantity = extras_ return quantity - def get_line_quantity(self, wage, start=None, end=None, extras=None, discount=None): - quantity = wage.default_quantity or 0 + def get_line_quantity(self, config, wage, start=None, end=None, extras=None, discount=None): quantity_days = self.get_days(start, end) quantity = self._get_line_quantity( - quantity_days, wage, extras, discount) + config, quantity_days, wage, extras, discount) return quantity def get_line_quantity_special(self, wage): quantity_days = 0 - if self.contract and self.date_effective and wage.type_concept == 'special': + if self.contract and self.date_effective: quantity_days = (self.date_effective - self.contract.start_date).days if quantity_days > wage.limit_days: @@ -675,13 +707,16 @@ class Payroll(Workflow, ModelSQL, ModelView): quantity_days = 0 return quantity_days - def recompute_lines(self): + def recompute_lines(self, cache_wage_dict): + Wage = Pool().get('staff.wage_type') + compute_unit_price = Wage.compute_unit_price + get_salary_full = self.get_salary_full for line in self.lines: - if not line.wage_type.concepts_salary: + wage = cache_wage_dict[line.wage_type.id] + if not wage['concepts_salary']: continue - salary_args = self.get_salary_full(line.wage_type) - unit_value = line.wage_type.compute_unit_price(salary_args) - unit_value = self._validate_amount_wage(line.wage_type, unit_value) + salary_args = get_salary_full(wage) + unit_value = compute_unit_price(wage['unit_price_formula'], salary_args) line.write([line], {'unit_value': unit_value}) def _validate_amount_wage(self, wage, amount): @@ -744,7 +779,7 @@ class PayrollLine(ModelSQL, ModelView): if self.wage_type.unit_price_formula: salary_args = self.payroll.get_salary_full(self.wage_type) self.unit_value = self.wage_type.compute_unit_price( - salary_args) + self.wage_type.unit_price_formula, salary_args) def get_amount(self, name): return self.on_change_with_amount() @@ -791,9 +826,10 @@ class PayrollLine(ModelSQL, ModelView): def get_expense_amount(self): expense = 0 - if self.wage_type.expense_formula: - salary_args = self.payroll.get_salary_full(self.wage_type) - expense = self.wage_type.compute_expense(salary_args) + wage_type = self.wage_type + if wage_type.expense_formula: + salary_args = self.payroll.get_salary_full(wage_type) + expense = wage_type.compute_expense(salary_args, wage_type.expense_formula) return expense @@ -841,14 +877,18 @@ class PayrollGroup(Wizard): open_ = StateTransition() def transition_open_(self): + time_initial = time.time() pool = Pool() Employee = pool.get('company.employee') Payroll = pool.get('staff.payroll') + Configuration = Pool().get('staff.configuration') + config = Configuration(1) #Remove employees with payroll this period payrolls_period = Payroll.search([ ('period', '=', self.start.period.id), # ('department', '=', self.start.department.id), ]) + cache_wage_dict = Payroll.create_cache_wage_types() # contracts_w_payroll = [p.contract.id for p in payrolls_period] employee_w_payroll = [p.employee.id for p in payrolls_period] @@ -881,16 +921,18 @@ class PayrollGroup(Wizard): cont = 0 for payroll in payrolls: print('contador > ', cont, ' / ', len_payrolls) - try: - cont += 1 - # REVISAR: Esta linea es basicamente innecesaria y costosa en recursos - # payroll.on_change_period() - - payroll.set_preliquidation({}, None) - if wages: - payroll._create_payroll_lines(wages, None, {}) - except: - print('Fallo al crear nomina : ', payroll.employee.party.name) + # try: + cont += 1 + time_pre = time.time() + payroll.set_preliquidation(config, {}, None, cache_wage_dict) + time_pre2 = time.time() + print(time_pre2-time_pre, 'time preliquidation') + if wages: + payroll._create_payroll_lines(config, wages, None, {}, cache_wage_dict) + # except Exception as e: + # print('Fallo al crear nomina : ', payroll.employee.party.name, e) + time_final = time.time() + print(time_final - time_initial, 'final create payroll') return 'end' def get_employees_dom(self, employees_w_payroll): @@ -931,14 +973,20 @@ class PayrollPreliquidation(Wizard): def transition_create_preliquidation(self): Payroll = Pool().get('staff.payroll') + Configuration = Pool().get('staff.configuration') + config = Configuration(1) ids = Transaction().context['active_ids'] + cache_wage_dict = Payroll.create_cache_wage_types() for payroll in Payroll.browse(ids): if payroll.state != 'draft': return if not payroll.lines: - payroll.set_preliquidation({}, None) + time_1 = time.time() + payroll.set_preliquidation(config, {}, None, cache_wage_dict) + time_2 = time.time() + print(time_2-time_1, 'time preliquidation') else: - payroll.update_preliquidation({}) + payroll.update_preliquidation({}, cache_wage_dict) return 'end' diff --git a/wage_type.py b/wage_type.py index e78c7db..665e582 100644 --- a/wage_type.py +++ b/wage_type.py @@ -129,13 +129,15 @@ class WageType(ModelSQL, ModelView): def default_active(): return True - def compute_unit_price(self, args): - return self.compute_formula('unit_price_formula', args) + @classmethod + def compute_unit_price(cls, formula, args): + return cls.compute_formula(formula, args) - def compute_expense(self, args): - return self.compute_formula('expense_formula', args) + @classmethod + def compute_expense(cls, formula, args): + return cls.compute_formula(formula, args) - def compute_formula(self, formula, args=None): + def compute_formula2(self, formula, args=None): ''' Compute a formula field with a salary value as float :return: A decimal @@ -155,6 +157,27 @@ class WageType(ModelSQL, ModelView): raise WageTypeValidationError( gettext('staff_payroll.msg_invalid_formula', formula=formula)) + @classmethod + def compute_formula(cls, formula, args=None): + ''' + Compute a formula field with a salary value as float + :return: A decimal + ''' + # Configuration = Pool().get('staff.configuration') + # configuration = Configuration(1) + # minimum_salary = float(configuration.minimum_salary or 0) + # formula = getattr(self, formula) + if not formula: + return Decimal('0.0') + if args.get('salary') != None: + salary = float(args['salary']) + try: + value = Decimal(str(round(eval(formula), 2))) + return value + except Exception: + raise WageTypeValidationError( + gettext('staff_payroll.msg_invalid_formula', formula=formula)) + class WageTypeSalary(ModelSQL): "Wage Type Salary"