payroll optimization test

This commit is contained in:
Wilson Gomez 2023-04-29 11:14:49 -05:00
parent 60f93248fb
commit c6b3e7b3ef
2 changed files with 148 additions and 77 deletions

View File

@ -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'

View File

@ -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"