payroll optimization test
This commit is contained in:
parent
60f93248fb
commit
c6b3e7b3ef
192
payroll.py
192
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'
|
||||
|
||||
|
||||
|
|
33
wage_type.py
33
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"
|
||||
|
|
Loading…
Reference in New Issue