payroll optimization test

This commit is contained in:
Wilson Gomez 2023-04-29 11:15:18 -05:00
parent 2e63cbd35c
commit 745799a2ec
2 changed files with 168 additions and 148 deletions

View File

@ -2,6 +2,7 @@
# this repository contains the full copyright notices and license terms.
import calendar
import copy
import time
from decimal import Decimal
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
@ -70,7 +71,7 @@ class PayrollLine(metaclass=PoolMeta):
def delete(cls, lines):
LoanLine = Pool().get('staff.loan.line')
loan_lines = LoanLine.search([
('origin', 'in', ['staff.payroll.line,' + str(l.id) for l in lines])
('origin', 'in', [str(l) for l in lines])
])
LoanLine.write([m for m in loan_lines], {'state': 'pending', 'origin': None})
super(PayrollLine, cls).delete(lines)
@ -122,18 +123,6 @@ class Payroll(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super(Payroll, cls).__setup__()
cls._order = [
('period', 'DESC'),
('start', 'DESC'),
]
cls._transitions |= set((
('draft', 'cancel'),
('cancel', 'draft'),
('draft', 'processed'),
('processed', 'posted'),
('posted', 'draft'),
('processed', 'draft'),
))
cls._buttons.update({
'force_draft': {
'invisible': Eval('state') != 'posted',
@ -272,15 +261,13 @@ class Payroll(metaclass=PoolMeta):
return quantity
def _get_line_quantity(self, quantity_days, wage, extras, discount):
Configuration = Pool().get('staff.configuration')
config = Configuration(1)
quantity_days = self.get_days(self.start, self.end, wage.adjust_days_worked)
def _get_line_quantity(self, config, quantity_days, wage, extras, discount):
quantity_days = self.get_days(self.start, self.end, wage['adjust_days_worked'])
quantity = super(Payroll, self)._get_line_quantity(
quantity_days, wage, extras, discount
config, quantity_days, wage, extras, discount
)
if config.payment_partial_sunday and wage.type_concept == 'salary':
if config.payment_partial_sunday and wage['type_concept'] == 'salary':
quantity = self.adjust_partial_sunday(quantity)
return quantity
@ -313,29 +300,20 @@ class Payroll(metaclass=PoolMeta):
def get_salary_full(self, wage):
res = super(Payroll, self).get_salary_full(wage)
# salary_pae = _ZERO
salary_full = res['salary']
concepts = ('health', 'retirement', 'risk', 'box_family')
if wage.type_concept in concepts:
if wage['type_concept'] in concepts:
salary_full += sum(line.amount_60_40 for line in self.lines if line.amount_60_40)
if wage.month_application:
if self._is_last_payroll_month():
if wage['month_application']:
if self._is_last_payroll_month(self.start, self.period.end):
salary_full_month = self.search_salary_month(wage)
salary_full = salary_full_month # + salary_pae
# elif self.last_payroll:
# salary_full = salary_full + salary_pae
salary_full = salary_full_month
else:
salary_full = _ZERO
# else:
# salary_full += salary_pae
salary_full = self.validate_minimal_amount(wage, salary_full)
return {'salary': salary_full}
def validate_minimal_amount(self, wage, amount):
min_amount = wage.minimal_amount
if min_amount and amount < min_amount:
return 0
return amount
if wage['minimal_amount'] and salary_full < wage['minimal_amount']:
salary_full = wage['minimal_amount']
return {'salary': salary_full}
@lru_cache(maxsize=20)
def is_first_payroll(self):
@ -344,11 +322,11 @@ class Payroll(metaclass=PoolMeta):
return False
@lru_cache(maxsize=20)
def _is_last_payroll_month(self):
year, month = attrgetter('year', 'month')(self.start)
def _is_last_payroll_month(self, start_date_payroll, end_period_date):
year, month = attrgetter('year', 'month')(start_date_payroll)
_, end_day = calendar.monthrange(year, month)
last_day = date(year, month, end_day)
if last_day == self.period.end:
if last_day == end_period_date:
return True
return False
@ -363,28 +341,30 @@ class Payroll(metaclass=PoolMeta):
for wage in wages:
dom_payroll = [('employee', '=', self.employee.id)]
if wage.type_concept == 'bonus_service':
if self.start < date(self.start.year, 7, 1):
year = self.start.year
if self.start < date(year, 7, 1):
dom_payroll.extend([
('start', '>=', date(self.start.year, 1, 1)),
('start', '<=', date(self.start.year, 6, 30)),
('start', '>=', date(year, 1, 1)),
('start', '<=', date(year, 6, 30)),
])
else:
dom_payroll.extend([
('start', '>=', date(self.start.year, 7, 1)),
('start', '<=', date(self.start.year, 12, 31)),
('start', '>=', date(year, 7, 1)),
('start', '<=', date(year, 12, 31)),
])
else:
dom_payroll.extend([
('start', '>=', date(self.start.year, 1, 1)),
('start', '<=', date(self.start.year, 12, 31)),
('start', '>=', date(year, 1, 1)),
('start', '<=', date(year, 12, 31)),
])
payrolls = self.search(dom_payroll)
worked_days = Decimal(sum([p.worked_days for p in payrolls]))
precomputed_salary = {
'salary': worked_days * self.employee.salary_day}
salary_plus += wage.compute_formula(
'unit_price_formula',
precomputed_salary)
wage.unit_price_formula,
precomputed_salary
)
return salary_plus
def _get_payrolls_month(self):
@ -411,15 +391,13 @@ class Payroll(metaclass=PoolMeta):
])
return payrolls
def _create_payroll_lines(self, wages, extras, discounts=None):
def _create_payroll_lines(self, config, wages, extras, discounts=None, cache_wage_dict=None):
super(Payroll, self)._create_payroll_lines(
wages, extras, discounts=None)
config, wages, extras, discounts, cache_wage_dict)
pool = Pool()
MoveLine = pool.get('account.move.line')
PayrollLine = pool.get('staff.payroll.line')
LoanLine = pool.get('staff.loan.line')
self.process_loans_to_pay(LoanLine, PayrollLine, MoveLine)
for line in self.lines:
to_write = {}
@ -493,37 +471,42 @@ class Payroll(metaclass=PoolMeta):
res += payroll.compute_salary_full(wage)
return res
def get_round_amount(self, wage, unit_value):
if wage.round_amounts == 'above_amount':
def get_round_amount(self, round_amounts, unit_value):
if round_amounts == 'above_amount':
unit_value = math.ceil(float(unit_value) / 100.0) * 100
elif wage.round_amounts == 'under_amount':
elif round_amounts == 'under_amount':
unit_value = math.floor(float(unit_value) / 100.0) * 100
elif wage.round_amounts == 'automatic':
elif round_amounts == 'automatic':
unit_value = round(unit_value, -2)
return unit_value
def _validate_amount_wage(self, wage, amount):
amount = super(Payroll, self)._validate_amount_wage(wage, amount)
if amount and wage and wage.round_amounts:
amount = self.get_round_amount(wage, amount)
return amount
# def round_amount(self, round_amount, amount):
# amount = super(Payroll, self).round_amount(wage, amount)
# if amount and round_amount:
# amount = self.get_round_amount(wage, amount)
# return amount
def get_line(self, wage, qty, unit_value, party=None):
if unit_value and wage and wage.round_amounts:
unit_value = self.get_round_amount(wage, unit_value)
res = super(Payroll, self).get_line(wage, qty, unit_value, party)
return res
# def get_line(self, wage, qty, unit_value, party=None):
# if unit_value and wage.get('round_amounts'):
# unit_value = self.get_round_amount(wage['round_amounts'], unit_value)
# res = super(Payroll, self).get_line(wage, qty, unit_value, party)
# return res
def set_preliquidation(self, extras, discounts=None):
PayrollLine = Pool().get('staff.payroll.line')
Configuration = Pool().get('staff.configuration')
configuration = Configuration(1)
discounts = self.set_events()
def set_preliquidation(self, config, extras, discounts=None, cache_wage_dict=None):
pool = Pool()
PayrollLine = pool.get('staff.payroll.line')
MoveLine = pool.get('account.move.line')
LoanLine = pool.get('staff.loan.line')
self.process_loans_to_pay(LoanLine, PayrollLine, MoveLine)
# Configuration = Pool().get('staff.configuration')
# configuration = Configuration(1)
discounts = self.set_events(cache_wage_dict)
ctx = {
'absenteeism_days': self.absenteeism_days
}
with Transaction().set_context(ctx):
super(Payroll, self).set_preliquidation(extras, discounts)
super(Payroll, self).set_preliquidation(config, extras, discounts, cache_wage_dict)
self.save()
if extras.get('wage_shift_fixed'):
# Shifts fixed must be a list of wage_shift_fixed
@ -534,92 +517,108 @@ class Payroll(metaclass=PoolMeta):
self.get_line(wg['wage'], wg['qty'], wg['unit_value']))
PayrollLine.create(to_create)
self.update_wage_no_salary()
self.recompute_lines()
for line in self.lines:
if not configuration.allow_zero_quantities and line.quantity == 0:
PayrollLine.delete([line])
# self.update_wage_no_salary()
time_r = time.time()
self.recompute_lines(cache_wage_dict)
time_r2 = time.time()
print(time_r2-time_r, 'time recompute lines')
if not config.allow_zero_quantities:
lines_delete = [ln for ln in self.lines if ln.quantity == 0]
PayrollLine.delete(lines_delete)
def set_events(self):
def set_events(self, cache_wage_dict):
pool = Pool()
Event = pool.get('staff.event')
PayrollLine = pool.get('staff.payroll.line')
events = Event.search(['OR',
Wage = pool.get('staff.wage_type')
start = self.start
end = self.end
fields_names = [
'category.payroll_effect', 'end_date', 'start_date',
'uom.symbol', 'quantity', 'absenteeism', 'category.wage_type',
'amount', 'total_amount', 'unit_price_formula',
'category.wage_type_discount', 'is_vacations'
]
events = Event.search_read(['OR',
[
('employee', '=', self.employee),
('state', '=', 'done'),
['OR',
[
('end_date', '>=', self.start),
('start_date', '<=', self.start),
('end_date', '>=', start),
('start_date', '<=', start),
],
[
('end_date', '>=', self.end),
('start_date', '<=', self.end),
('end_date', '>=', end),
('start_date', '<=', end),
],
[
('start_date', '>=', self.start),
('end_date', '<=', self.end),
('start_date', '>=', start),
('end_date', '<=', end),
],
]
],
[
('employee', '=', self.employee),
('state', '=', 'in_progress'),
('start_date', '<=', self.end),
('start_date', '<=', end),
('unit_price_formula', 'not in', [None, '']),
]
])
], fields_names=fields_names)
absenteeism_days = 0
discounts = {}
compute_unit_price = Wage.compute_unit_price
compute_formula = Event.compute_formula
for event in events:
if not event.category.payroll_effect:
event_ = Event(event['id'])
if not event['category.']['payroll_effect']:
continue
start_date = self.start
start_date = start
end_date = None
if event.end_date and event.uom.symbol == 'd':
if event.start_date > start_date:
start_date = event.start_date
end_date = self.end
if event.end_date and event.end_date < end_date:
end_date = event.end_date
if event['end_date'] and event['uom.']['symbol'] == 'd':
if event['start_date'] > start_date:
start_date = event['start_date']
end_date = end
if event['end_date'] < end_date:
end_date = event['end_date']
qty_pay = self.contract.get_time_days(start_date, end_date)
else:
qty_pay = event.quantity
if event.absenteeism:
qty_pay = event['quantity']
if event['absenteeism']:
absenteeism_days += qty_pay
if event.quantity:
wage = event.category.wage_type
if event['quantity']:
wage = event['category.']['wage_type']
if wage:
salary_args = self.get_salary_full(wage)
if event.amount and event.unit_price_formula:
unit_value = event.compute_formula(salary_args)
if event.amount - event.total_amount < unit_value:
unit_value = event.amount - event.total_amount
event.state = 'done'
event.end_date = self.end
event.save()
elif event.amount:
unit_value = Decimal(event.amount)
wage_ = cache_wage_dict[wage]
salary_args = self.get_salary_full(wage_)
if event['amount'] and event['unit_price_formula']:
unit_value = compute_formula(salary_args)
if event['amount'] - event['total_amount'] < unit_value:
unit_value = event['amount'] - event['total_amount']
event_.state = 'done'
event_.end_date = end
event_.save()
elif event['amount']:
unit_value = Decimal(event['amount'])
else:
if event.is_vacations:
if event['is_vacations']:
unit_value = self.get_salary_average(
self.start, self.employee, self.contract, wage)
start, self.employee, self.contract, wage_)
else:
unit_value = wage.compute_unit_price(salary_args)
res = self.get_line(wage, qty_pay, unit_value)
unit_value = compute_unit_price(wage_['unit_price_formula'], salary_args)
res = self.get_line(wage_, qty_pay, unit_value)
res.update({
'is_event': True,
'start_date': start_date,
'end_date': end_date,
'origin': event
'origin': event_
})
PayrollLine.create([res])
if event.category.wage_type_discount and event.quantity:
id_wt_event = event.category.wage_type_discount.id
if event['category.']['wage_type_discount'] and event['quantity']:
id_wt_event = event['category.']['wage_type_discount']
if id_wt_event not in discounts.keys():
discounts[id_wt_event] = 0
discounts[id_wt_event] += event.quantity
discounts[id_wt_event] += event['quantity']
self.absenteeism_days = absenteeism_days
self.save()
@ -662,18 +661,19 @@ class Payroll(metaclass=PoolMeta):
return Decimal(str(round(res, 2)))
def update_wage_no_salary(self):
def update_wage_no_salary(self, cache_wage_dict):
PayrollLine = Pool().get('staff.payroll.line')
att_getter = attrgetter('wage_type.type_concept', 'wage_type.month_application')
for line in self.lines:
wage = cache_wage_dict[line.wage_type.id]
type_concept, month_application = att_getter(line)
if month_application:
salary_args = self.get_salary_full(line.wage_type)
unit_value = line.wage_type.compute_unit_price(salary_args)
salary_args = self.get_salary_full(wage)
unit_value = line.wage_type.compute_unit_price(wage['unit_price_formula'], salary_args)
if type_concept == 'interest':
unit_value = self._compute_interest(line.wage_type, self.start)
unit_value = self._compute_interest(wage, self.start)
elif type_concept == 'tax':
unit_value = self._compute_line_tax(line)
unit_value = self._compute_line_tax(line, wage)
else:
continue
PayrollLine.write([line], {'unit_value': unit_value})
@ -695,11 +695,11 @@ class Payroll(metaclass=PoolMeta):
payrolls = self.search(dom_payroll)
return [p.id for p in payrolls]
def _compute_interest(self, wage_type, limit_date=False, name=None):
def _compute_interest(self, wage, limit_date=False, name=None):
year = self.start.year
start = date(year, 1, 1)
end = date(year, 12, 31)
concepts_salary_ids = [wt.id for wt in wage_type.concepts_salary]
concepts_salary_ids = wage['concepts_salary']
start_date, contract_id, employee_id = attrgetter(
'contract.start_date', 'contract.id', 'employee.id')(self)
@ -731,7 +731,7 @@ class Payroll(metaclass=PoolMeta):
payrolls_ids.remove(self.id)
interest_hoard = float(self._compute_hoard(
payrolls_ids,
[wage_type.id]
[wage['id']]
))
interest = total_interest - interest_hoard
@ -745,14 +745,15 @@ class Payroll(metaclass=PoolMeta):
if not payrolls_ids:
return _ZERO
PayrollLine = Pool().get('staff.payroll.line')
lines = PayrollLine.search([
lines = PayrollLine.search_read([
('wage_type', 'in', wages_ids),
('payroll', 'in', payrolls_ids),
])
return sum(pl.amount for pl in lines if not pl.reconciled)
], fields_names=['amount', 'reconciled'])
return sum(pl['amount'] for pl in lines if not pl['reconciled'])
def recompute_lines(self):
def recompute_lines(self, cache_wage_dict):
PayrollLine = Pool().get('staff.payroll.line')
time_60_40 = time.time()
amounts_60_40 = []
amounts_60_40_app = amounts_60_40.append
wages_60_40 = []
@ -777,16 +778,20 @@ class Payroll(metaclass=PoolMeta):
for wl in wages_60_40:
new_unit_value = Decimal(str(round(
(amount_dif * (wl.amount / amount_wages_60_40)), 2)))
# unit_value = Decimal(str(round((l.amount - new_unit_value), 2)))
# print(new_unit_value, 'this is unit value')
PayrollLine.write([wl], {
# 'unit_value': unit_value,
'amount_60_40': new_unit_value,
})
super(Payroll, self).recompute_lines()
self.update_wage_no_salary()
time_60_40_2 = time.time()
print(time_60_40_2- time_60_40, 'validar 60 40')
time_r = time.time()
super(Payroll, self).recompute_lines(cache_wage_dict)
time_r2 = time.time()
print(time_r2- time_r, 'validar recom')
line_tax = None
# deductions = _ZERO
time_pr = time.time()
for line in self.lines:
w_provision_cancellation = line.wage_type.provision_cancellation
if w_provision_cancellation:
@ -799,15 +804,28 @@ class Payroll(metaclass=PoolMeta):
line.write([line], {
'unit_value': amount,
})
# elif line.wage_type.definition == 'deduction':
# deductions += line.amount
elif line.wage_type.type_concept == 'tax':
line_tax = line
if line_tax:
unit_value = self._compute_line_tax(line_tax)
line_tax.write([line_tax], {
'unit_value': unit_value, 'quantity': 1
})
time_pr2 = time.time()
print(time_pr2- time_pr, 'validar provision')
time_up = time.time()
self.update_wage_no_salary(cache_wage_dict)
time_up2 = time.time()
print(time_up2- time_up, 'validar update')
# elif line.wage_type.type_concept == 'tax':
# line_tax = line
# if line_tax:
# unit_value = self._compute_line_tax(line_tax)
# line_tax.write([line_tax], {
# 'unit_value': unit_value, 'quantity': 1
# })
time_ro = time.time()
for line in self.lines:
wage = cache_wage_dict[line.wage_type.id]
if not wage['round_amounts']:
continue
unit_value = self.get_round_amount(wage['round_amounts'], line.unit_value)
line.write([line], {'unit_value': unit_value})
time_ro2 = time.time()
print(time_ro2- time_ro, 'validar rounds')
def check_limit(self, base, field, value_field):
Configuration = Pool().get('staff.configuration')
@ -836,9 +854,9 @@ class Payroll(metaclass=PoolMeta):
res = value_limit_percent
return res
def _compute_line_tax(self, line_tax):
def _compute_line_tax(self, line_tax, wage):
UvtWithholding = Pool().get('staff.payroll.uvt_withholding')
salary_full = self.get_salary_full(line_tax.wage_type)
salary_full = self.get_salary_full(wage)
amount_tax = line_tax.amount if line_tax.amount else 0
deductions_month = self.get_deductions_month() - amount_tax
salary_full = salary_full['salary']
@ -2419,6 +2437,8 @@ class PayrollTask(ModelSQL, ModelView):
task = tasks[0]
Payroll = Pool().get('staff.payroll')
Period = Pool().get('staff.payroll.period')
Configuration = Pool().get('staff.configuration')
config = Configuration(1)
transaction = Transaction()
with transaction.set_user(task.data['user']), \
@ -2474,9 +2494,9 @@ class PayrollTask(ModelSQL, ModelView):
for payroll in payrolls:
try:
payroll.set_preliquidation({}, None)
payroll.set_preliquidation(config, {}, None)
if wages:
payroll._create_payroll_lines(wages, None, {})
payroll._create_payroll_lines(config, wages, None, {})
except Exception as e:
print('Fallo al crear nomina : ', e)

View File

@ -359,9 +359,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="model" search="[('model', '=', 'staff.payroll')]"/>
</record>
<record model="ir.model.button-res.group"
id="payroll_force_draft_button_group_account">
id="payroll_force_draft_button_group_account_admin">
<field name="button" ref="payroll_force_draft_button"/>
<field name="group" ref="account.group_account"/>
<field name="group" ref="account.group_account_admin"/>
</record>
</data>