trytonpsk-staff_payroll_co/payroll.py

2581 lines
101 KiB
Python
Raw Normal View History

2020-04-16 00:38:42 +02:00
# 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 calendar
2021-08-14 16:56:11 +02:00
import copy
2023-04-29 18:15:18 +02:00
import time
2023-06-09 23:46:10 +02:00
import traceback
2020-04-16 00:38:42 +02:00
from decimal import Decimal
from datetime import date, timedelta
2021-05-24 21:08:24 +02:00
from dateutil.relativedelta import relativedelta
2020-11-04 02:24:58 +01:00
import math
2023-02-01 23:25:10 +01:00
from operator import attrgetter
from functools import lru_cache
2021-08-14 16:56:11 +02:00
2020-12-18 15:37:21 +01:00
from trytond.exceptions import UserError
2021-02-04 20:51:06 +01:00
from trytond.model import ModelView, fields, Workflow, ModelSQL
2020-04-16 00:38:42 +02:00
from trytond.pool import Pool, PoolMeta
from trytond.report import Report
2021-02-23 06:38:03 +01:00
from trytond.wizard import (
Wizard, StateView, Button, StateAction, StateReport, StateTransition
)
2020-04-16 00:38:42 +02:00
from trytond.transaction import Transaction
2022-01-11 20:54:53 +01:00
from trytond.pyson import Eval, Id
2021-06-09 18:07:42 +02:00
from trytond.modules.staff_payroll.period import Period
2022-04-01 00:09:38 +02:00
from trytond.modules.staff_payroll.payroll import PayrollReport, get_dom_contract_period
2021-06-09 18:07:42 +02:00
from trytond.i18n import gettext
2022-05-12 03:43:11 +02:00
from .exceptions import (
GeneratePayrollError, MissingTemplateEmailPayroll,
WageTypeConceptError, RecordDuplicateError
)
2022-03-07 20:38:18 +01:00
from .constants import (
2021-08-14 16:56:11 +02:00
SHEET_FIELDS_NOT_AMOUNT, LIM_UVT_DEDUCTIBLE, ENTITY_ACCOUNTS,
FIELDS_AMOUNT, EXTRAS, SHEET_SUMABLES, LIM_PERCENT_DEDUCTIBLE,
)
2020-04-16 00:38:42 +02:00
2023-05-02 16:49:52 +02:00
# Nimporter is required for impot Nim
2023-05-13 00:36:31 +02:00
# try:
# import nimporter
# import trytond.modules.staff_payroll_co.fpc as fpc # Nim imports!
# except:
# print("Nim or nimporter is not installed")
2023-05-02 16:49:52 +02:00
2020-05-27 21:54:36 +02:00
STATES = {'readonly': (Eval('state') != 'draft')}
2020-11-04 02:16:18 +01:00
2020-04-16 00:38:42 +02:00
_ZERO = Decimal('0.0')
2020-11-04 02:16:18 +01:00
2020-04-16 00:38:42 +02:00
class PayrollLine(metaclass=PoolMeta):
__name__ = "staff.payroll.line"
2021-09-29 17:03:59 +02:00
is_event = fields.Boolean('Is Event')
start_date = fields.Date('Start Date', depends=['is_event'],
states={'invisible': ~Eval('is_event')})
2021-09-29 17:03:59 +02:00
end_date = fields.Date('End Date', depends=['is_event'],
states={'invisible': ~Eval('is_event')})
2021-02-04 20:51:06 +01:00
move_lines = fields.Many2Many('staff.payroll.line-move.line',
2022-05-12 03:43:11 +02:00
'line', 'move_line', 'Payroll Line - Move Line')
2022-01-28 00:07:05 +01:00
origin = fields.Reference("Origin", selection='get_origin')
2023-02-01 23:25:10 +01:00
amount_60_40 = fields.Numeric('Amount 60/40', digits=(16, 2),
depends=['wage_type'])
tax_base = fields.Numeric('Tax Base', digits=(16, 2), states={
'invisible': ~Eval('is_wage_tax', False)}, depends=['wage_type', 'is_wage_tax'])
is_wage_tax = fields.Function(fields.Boolean('Is wage tax'),
'on_change_with_is_wage_tax')
@fields.depends('wage_type')
def on_change_with_is_wage_tax(self, name=None):
if self.wage_type:
return self.wage_type.type_concept == 'tax'
return False
2022-01-28 00:07:05 +01:00
2023-05-10 18:45:07 +02:00
def get_expense_amount(self, wage_type):
expense = super(PayrollLine, self).get_expense_amount(wage_type)
round_amounts = wage_type['round_amounts']
2023-02-01 23:25:10 +01:00
if not round_amounts:
return expense
if round_amounts == 'above_amount':
expense = math.floor(float(expense) / 100.0) * 100
elif round_amounts == 'under_amount':
expense = math.floor(float(expense) / 100.0) * 100
elif round_amounts == 'automatic':
expense = round(expense, -2)
2021-03-10 20:45:28 +01:00
return expense
2022-01-25 22:59:26 +01:00
@classmethod
def delete(cls, lines):
LoanLine = Pool().get('staff.loan.line')
loan_lines = LoanLine.search([
2023-04-29 18:15:18 +02:00
('origin', 'in', [str(l) for l in lines])
2022-01-25 22:59:26 +01:00
])
LoanLine.write([m for m in loan_lines], {'state': 'pending', 'origin': None})
super(PayrollLine, cls).delete(lines)
2022-01-28 00:07:05 +01:00
@classmethod
def _get_origin(cls):
'Return list of Model names for origin Reference'
return ['staff.loan.line', 'staff.event']
@classmethod
def get_origin(cls):
Model = Pool().get('ir.model')
get_name = Model.get_name
models = cls._get_origin()
return [(None, '')] + [(m, get_name(m)) for m in models]
2021-02-04 20:51:06 +01:00
class PayrollLineMoveLine(ModelSQL):
"Payroll Line - MoveLine"
__name__ = "staff.payroll.line-move.line"
_table = 'staff_payroll_line_move_line_rel'
line = fields.Many2One('staff.payroll.line', 'Line',
2021-11-11 06:47:34 +01:00
ondelete='CASCADE', select=True, required=True)
2021-02-04 20:51:06 +01:00
move_line = fields.Many2One('account.move.line', 'Move Line',
2021-11-11 06:47:34 +01:00
ondelete='RESTRICT', select=True, required=True)
2020-04-16 00:38:42 +02:00
class Payroll(metaclass=PoolMeta):
__name__ = "staff.payroll"
last_payroll = fields.Boolean('Last Payroll', states=STATES, select=True)
ibc = fields.Function(fields.Numeric('IBC'), 'on_change_with_ibc')
health_amount = fields.Function(fields.Numeric('EPS Amount'),
2022-05-12 03:43:11 +02:00
'get_non_fiscal_amount')
2020-04-16 00:38:42 +02:00
retirement_amount = fields.Function(fields.Numeric('AFP Amount'),
2022-05-12 03:43:11 +02:00
'get_non_fiscal_amount')
2020-04-16 00:38:42 +02:00
risk_amount = fields.Function(fields.Numeric('ARL Amount'),
2022-05-12 03:43:11 +02:00
'get_non_fiscal_amount')
2020-04-16 00:38:42 +02:00
box_family_amount = fields.Function(fields.Numeric('Box Amount'),
2022-05-12 03:43:11 +02:00
'get_non_fiscal_amount')
absenteeism_days = fields.Integer("Absenteeism Days", states=STATES)
2020-04-16 00:38:42 +02:00
department = fields.Many2One('company.department', 'Department',
2022-05-12 03:43:11 +02:00
required=False, depends=['employee'])
2020-11-28 16:29:35 +01:00
sended_mail = fields.Boolean('Sended Email')
2022-05-12 03:43:11 +02:00
worked_days_effective = fields.Function(
fields.Numeric('Worked Days Effective'), 'get_worked_days_effective')
2022-05-13 15:31:34 +02:00
events = fields.Function(fields.One2Many('staff.event', None,
'Assistance'), 'get_events')
2023-10-26 22:12:54 +02:00
recompute = fields.Boolean('Recompute')
2020-04-16 00:38:42 +02:00
@classmethod
def __setup__(cls):
super(Payroll, cls).__setup__()
cls._buttons.update({
'force_draft': {
'invisible': Eval('state') != 'posted',
'depends': ['state'],
},
})
2020-04-16 00:38:42 +02:00
@fields.depends('end', 'date_effective')
def on_change_with_date_effective(self):
if self.end:
return self.end
@fields.depends('employee', 'department')
def on_change_employee(self):
if self.employee and self.employee.department:
self.department = self.employee.department.id
@classmethod
def copy(cls, records, default=None):
2022-05-12 03:43:11 +02:00
raise RecordDuplicateError(gettext(
'staff_payroll_co.msg_cannot_duplicate_record'))
2023-10-26 22:12:54 +02:00
@classmethod
def process(cls, records):
to_write = []
cache_wage_dict = cls.create_cache_wage_types()
for payroll in records:
if payroll.recompute:
payroll.recompute_lines(cache_wage_dict)
to_write.append(payroll)
if to_write:
cls.write(to_write, {'recompute': False})
super(Payroll, cls).process(records)
@fields.depends('recompute', 'lines')
def on_change_lines(self):
if self.lines:
self.recompute = True
2023-05-10 18:45:07 +02:00
def create_move(self, wage_dict):
super(Payroll, self).create_move(wage_dict)
2021-02-04 20:51:06 +01:00
pool = Pool()
MoveLine = pool.get('account.move.line')
2021-12-23 21:02:37 +01:00
grouped = {}
for line in self.lines:
if line.wage_type.definition != 'payment':
account_id = line.wage_type.credit_account.id
else:
account_id = line.wage_type.debit_account.id
grouped.update({account_id: {
'lines': list(line.move_lines)
}})
2021-02-04 20:51:06 +01:00
for p in self.move.lines:
if p.account.id not in grouped or (
2022-01-19 22:32:04 +01:00
p.account.type.statement not in ('balance')) or p.reconciliation:
2021-02-04 20:51:06 +01:00
continue
2021-12-23 21:02:37 +01:00
to_reconcile = [p] + grouped[p.account.id]['lines']
amount = sum([(r.debit - r.credit) for r in to_reconcile])
if self.company.currency.is_zero(amount):
2021-07-13 22:35:41 +02:00
MoveLine.reconcile(set(to_reconcile))
2021-02-04 20:51:06 +01:00
2020-06-13 01:50:41 +02:00
@fields.depends('period', 'employee', 'start', 'end', 'contract',
2022-05-12 03:43:11 +02:00
'description', 'date_effective', 'last_payroll')
2020-04-16 00:38:42 +02:00
def on_change_period(self):
if not self.period:
return
self.date_effective = self.period.end
self.on_change_employee()
2023-03-27 17:54:02 +02:00
period_start = self.period.start
period_end = self.period.end
2020-11-04 02:16:18 +01:00
# Search last contract
2023-03-27 17:54:02 +02:00
contract = self.search_contract_on_period(self.employee.id, period_start, period_end)
2020-04-16 00:38:42 +02:00
start_date = None
end_date = None
self.contract = contract
contract_end_date = None
2023-10-13 21:49:52 +02:00
contract_start_date = contract.start_date
2020-04-16 00:38:42 +02:00
if contract:
2023-03-27 18:02:14 +02:00
if not contract.finished_date:
2023-03-27 17:54:02 +02:00
end_date = period_end
2020-04-16 00:38:42 +02:00
2023-10-13 21:49:52 +02:00
if period_start >= contract_start_date:
2023-03-27 17:54:02 +02:00
start_date = period_start
2023-10-13 21:49:52 +02:00
elif contract_start_date >= period_start and \
contract_start_date <= period_end:
start_date = contract_start_date
2020-04-16 00:38:42 +02:00
else:
contract_end_date = contract.finished_date if contract.finished_date else contract.end_date
2023-10-13 21:49:52 +02:00
if contract_start_date <= period_start and \
2023-03-27 17:54:02 +02:00
contract_end_date >= period_end:
start_date = period_start
end_date = period_end
2023-10-13 21:49:52 +02:00
elif contract_start_date >= period_start and \
2023-03-27 17:54:02 +02:00
contract_end_date <= period_end:
2023-10-13 21:49:52 +02:00
start_date = contract_start_date
2020-04-16 00:38:42 +02:00
end_date = contract_end_date
2023-10-13 21:49:52 +02:00
elif contract_start_date >= period_start and \
contract_start_date <= period_end and \
2023-03-27 17:54:02 +02:00
contract_end_date >= period_end:
2023-10-13 21:49:52 +02:00
start_date = contract_start_date
2023-03-27 17:54:02 +02:00
end_date = period_end
2023-10-13 21:49:52 +02:00
elif contract_start_date <= period_start and \
2023-03-27 17:54:02 +02:00
contract_end_date >= period_start and \
contract_end_date <= period_end:
start_date = period_start
2020-04-16 00:38:42 +02:00
end_date = contract_end_date
if start_date and end_date:
self.start = start_date
self.end = end_date
if contract_end_date == self.end:
self.last_payroll = True
if not start_date or not end_date:
self.period = None
self.description = None
2021-06-09 18:07:42 +02:00
raise GeneratePayrollError(
2021-11-10 18:56:11 +01:00
gettext('staff_payroll_co.msg_period_without_contract',
employee=self.employee.party.name)
2021-06-09 18:07:42 +02:00
)
2020-04-16 00:38:42 +02:00
if not self.description and self.period:
self.description = self.period.description
2022-01-11 20:10:50 +01:00
def get_worked_days_effective(self, name=None):
2022-03-07 23:06:58 +01:00
res = 0
2022-01-11 20:10:50 +01:00
if self.lines:
days = []
2022-01-11 20:54:53 +01:00
unit = 1
2022-01-11 20:10:50 +01:00
for l in self.lines:
2022-01-11 20:54:53 +01:00
if l.wage_type.type_concept == 'salary':
2022-01-11 20:10:50 +01:00
days.append(l.quantity)
2022-01-11 20:54:53 +01:00
if l.wage_type.uom.id == Id('product', 'uom_hour').pyson():
unit = 8
2022-05-12 03:43:11 +02:00
res = sum(days) / unit
2022-03-07 23:06:58 +01:00
return res
2022-01-11 20:10:50 +01:00
def adjust_partial_sunday(self, quantity):
# Factor = 8 hour sunday / 6 days (monday-saturday)
factor = 1.33
if self.is_first_payroll():
delta_days = (self.end - self.start).days
_sunday = False
for dd in range(delta_days):
next_day = self.start + timedelta(days=dd)
if next_day.weekday() == 6:
_sunday = True
break
if _sunday:
2023-07-28 15:33:06 +02:00
fix_qty = Decimal(self.start.weekday() * factor)
quantity = Decimal(str(round(quantity - fix_qty, 2)))
return quantity
2023-04-29 18:15:18 +02:00
def _get_line_quantity(self, config, quantity_days, wage, extras, discount):
quantity_days = self.get_days(self.start, self.end, wage['adjust_days_worked'])
2020-11-04 02:16:18 +01:00
quantity = super(Payroll, self)._get_line_quantity(
2023-04-29 18:15:18 +02:00
config, quantity_days, wage, extras, discount
2020-11-04 02:16:18 +01:00
)
2023-04-29 18:15:18 +02:00
if config.payment_partial_sunday and wage['type_concept'] == 'salary':
quantity = self.adjust_partial_sunday(quantity)
2020-04-16 00:38:42 +02:00
return quantity
2020-06-13 01:57:05 +02:00
@fields.depends('start', 'end', 'employee', 'period', 'contract')
2023-02-01 23:25:10 +01:00
@lru_cache(maxsize=20)
def get_days(self, start, end, wage_adjust_days_worked=None):
2020-04-16 00:38:42 +02:00
adjust = 1
2023-02-01 23:25:10 +01:00
# adjust_days_worked = False
adjust_days_worked = self.contract.position.adjust_days_worked if self.contract else True
2023-02-01 23:25:10 +01:00
# print('this is', adj)
# if self.contract and self.contract.position:
# adjust_days_worked = self.contract.position.adjust_days_worked
# else:
# if self.employee.position:
# adjust_days_worked = self.employee.position.adjust_days_worked
end_month, end_day = attrgetter('month', 'day')(end)
if (adjust_days_worked) or (wage_adjust_days_worked):
if end_day == 31 or (end_month == 2
and (end_day == 28 or end_day == 29)):
adjust = 31 - end_day
2020-04-16 00:38:42 +02:00
quantity_days = (end - start).days + adjust
if quantity_days < 0:
quantity_days = 0
if start == end:
quantity_days = 1
2023-05-30 23:31:29 +02:00
absenteeism_days = self.absenteeism_days
2023-02-01 23:25:10 +01:00
if absenteeism_days:
quantity_days = quantity_days - absenteeism_days
2020-04-16 00:38:42 +02:00
return quantity_days
def get_salary_full(self, wage):
res = super(Payroll, self).get_salary_full(wage)
salary_full = res['salary']
2023-02-01 23:25:10 +01:00
concepts = ('health', 'retirement', 'risk', 'box_family')
2023-04-29 18:15:18 +02:00
if wage['type_concept'] in concepts:
2023-02-01 23:25:10 +01:00
salary_full += sum(line.amount_60_40 for line in self.lines if line.amount_60_40)
2023-04-29 18:15:18 +02:00
if wage['month_application']:
if self._is_last_payroll_month(self.start, self.period.end):
2020-04-16 00:38:42 +02:00
salary_full_month = self.search_salary_month(wage)
2023-04-29 18:15:18 +02:00
salary_full = salary_full_month
2020-04-16 00:38:42 +02:00
else:
salary_full = _ZERO
2023-04-29 18:15:18 +02:00
if wage['minimal_amount'] and salary_full < wage['minimal_amount']:
2023-05-13 00:36:31 +02:00
salary_full = _ZERO
2023-04-29 18:15:18 +02:00
return {'salary': salary_full}
2020-04-16 00:38:42 +02:00
2023-02-01 23:25:10 +01:00
@lru_cache(maxsize=20)
2020-11-03 23:39:33 +01:00
def is_first_payroll(self):
if self.start <= self.contract.start_date <= self.end:
return True
2020-11-03 23:39:33 +01:00
return False
2023-02-01 23:25:10 +01:00
@lru_cache(maxsize=20)
2023-04-29 18:15:18 +02:00
def _is_last_payroll_month(self, start_date_payroll, end_period_date):
year, month = attrgetter('year', 'month')(start_date_payroll)
2023-02-01 23:25:10 +01:00
_, end_day = calendar.monthrange(year, month)
last_day = date(year, month, end_day)
2023-04-29 18:15:18 +02:00
if last_day == end_period_date:
2020-04-16 00:38:42 +02:00
return True
return False
def _compute_apply_special_salary(self):
MandatoryWages = Pool().get('staff.payroll.mandatory_wage')
mandatory_wages = MandatoryWages.search([
2020-11-03 23:39:33 +01:00
('employee', '=', self.employee.id),
('wage_type.apply_special_salary', '=', True),
2020-04-16 00:38:42 +02:00
])
salary_plus = _ZERO
wages = [w.wage_type for w in mandatory_wages]
for wage in wages:
dom_payroll = [('employee', '=', self.employee.id)]
if wage.type_concept == 'bonus_service':
2023-04-29 18:15:18 +02:00
year = self.start.year
if self.start < date(year, 7, 1):
2020-04-16 00:38:42 +02:00
dom_payroll.extend([
2023-04-29 18:15:18 +02:00
('start', '>=', date(year, 1, 1)),
('start', '<=', date(year, 6, 30)),
2020-04-16 00:38:42 +02:00
])
else:
dom_payroll.extend([
2023-04-29 18:15:18 +02:00
('start', '>=', date(year, 7, 1)),
('start', '<=', date(year, 12, 31)),
2020-04-16 00:38:42 +02:00
])
else:
dom_payroll.extend([
2023-10-26 22:12:54 +02:00
('start', '>=', date(year, 1, 1)),
('start', '<=', date(year, 12, 31)),
2020-04-16 00:38:42 +02:00
])
payrolls = self.search(dom_payroll)
worked_days = Decimal(sum([p.worked_days for p in payrolls]))
2021-11-10 18:56:11 +01:00
precomputed_salary = {
'salary': worked_days * self.employee.salary_day}
2020-04-16 00:38:42 +02:00
salary_plus += wage.compute_formula(
2023-04-29 18:15:18 +02:00
wage.unit_price_formula,
precomputed_salary
2023-10-26 22:12:54 +02:00
)
2020-04-16 00:38:42 +02:00
return salary_plus
def _get_payrolls_month(self):
_, end_day = calendar.monthrange(self.start.year, self.start.month)
start = date(self.start.year, self.start.month, 1)
end = date(self.start.year, self.start.month, end_day)
payrolls = self.search([
('employee', '=', self.employee.id),
('start', '>=', start),
('start', '<=', end),
])
return payrolls
2021-07-21 00:21:59 +02:00
@classmethod
def _get_payrolls_period(cls, employee, contract, start_date, end_date):
2021-05-24 21:08:24 +02:00
_, end_day = calendar.monthrange(end_date.year, end_date.month)
start = date(start_date.year, start_date.month, 1)
end = date(end_date.year, end_date.month, end_day)
2021-07-21 00:21:59 +02:00
payrolls = cls.search([
('employee', '=', employee.id),
2021-05-24 21:08:24 +02:00
('start', '>=', start),
('start', '<=', end),
2021-07-21 00:21:59 +02:00
('contract', '=', contract.id)
2021-05-24 21:08:24 +02:00
])
return payrolls
2023-04-29 18:15:18 +02:00
def _create_payroll_lines(self, config, wages, extras, discounts=None, cache_wage_dict=None):
2021-11-10 18:56:11 +01:00
super(Payroll, self)._create_payroll_lines(
2023-04-29 18:15:18 +02:00
config, wages, extras, discounts, cache_wage_dict)
pool = Pool()
MoveLine = pool.get('account.move.line')
2021-02-04 20:51:06 +01:00
PayrollLine = pool.get('staff.payroll.line')
2021-12-23 21:02:37 +01:00
2021-02-04 20:51:06 +01:00
for line in self.lines:
2022-01-25 20:08:32 +01:00
to_write = {}
2021-12-23 21:02:37 +01:00
2023-09-22 15:45:54 +02:00
if line.wage_type.provision_cancellation and line.wage_type.contract_finish:
2021-11-10 18:56:11 +01:00
amount_line = [m.amount for m in self.lines if m.wage_type
== line.wage_type.provision_cancellation]
2021-02-04 20:51:06 +01:00
move_lines = MoveLine.search([
2021-11-10 18:56:11 +01:00
('account', '=',
line.wage_type.provision_cancellation.credit_account.id),
('reconciliation', '=', None),
('party', '=', self.employee.party.id)
])
2021-02-04 20:51:06 +01:00
lines_to_reconcile = []
values = []
2022-02-02 17:41:18 +01:00
if line.origin and line.wage_type.type_concept == 'holidays':
2021-05-24 21:08:24 +02:00
amount = line.amount
for m in move_lines:
values.append(abs(m.debit - m.credit))
if sum(values) <= amount:
lines_to_reconcile.append(m.id)
2022-01-25 20:08:32 +01:00
to_write = {
2022-05-12 03:43:11 +02:00
'move_lines': [('add', lines_to_reconcile)]
}
2021-05-24 21:08:24 +02:00
else:
for m in move_lines:
values.append(abs(m.debit - m.credit))
lines_to_reconcile.append(m.id)
amount = sum(values) + sum(amount_line)
unit_value = amount
2022-01-25 20:08:32 +01:00
to_write = {
2022-05-12 03:43:11 +02:00
'unit_value': unit_value,
'move_lines': [('add', lines_to_reconcile)]
}
2022-01-25 20:08:32 +01:00
PayrollLine.write([line], to_write)
2022-03-07 20:38:18 +01:00
2022-12-16 20:34:35 +01:00
def process_loans_to_pay(self, LoanLine, PayrollLine, MoveLine):
2022-01-25 20:08:32 +01:00
dom = [
('loan.party', '=', self.employee.party.id),
2022-12-16 20:34:35 +01:00
('loan.wage_type', '!=', None),
2022-01-25 20:08:32 +01:00
('maturity_date', '<=', self.end),
('state', 'in', ['pending', 'partial']),
]
lines_loan = LoanLine.search(dom)
2022-01-28 00:07:05 +01:00
for m, r in zip(lines_loan, range(len(lines_loan))):
2022-01-31 23:12:13 +01:00
party = m.loan.party_to_pay if m.loan.party_to_pay else None
2022-01-28 00:07:05 +01:00
move_lines = MoveLine.search([
('origin', 'in', ['staff.loan.line,' + str(m)]),
])
2022-12-16 20:34:35 +01:00
wage_type = m.loan.wage_type
2022-01-28 00:07:05 +01:00
amount = m.amount
2022-12-16 20:34:35 +01:00
to_create = {
2023-10-26 22:12:54 +02:00
'origin': m,
'party': party,
'quantity': 1,
'uom': wage_type.uom,
'unit_value': amount,
'move_lines': [('add', move_lines)],
'wage_type': wage_type,
'description': wage_type.name,
'payroll': self,
}
2022-12-16 20:34:35 +01:00
line, = PayrollLine.create([to_create])
LoanLine.write([m], {'state': 'paid', 'origin': line})
2020-04-16 00:38:42 +02:00
def search_salary_month(self, wage):
res = _ZERO
payrolls = self._get_payrolls_month()
2020-05-28 02:41:17 +02:00
if payrolls:
2020-04-16 00:38:42 +02:00
for payroll in payrolls:
res += payroll.compute_salary_full(wage)
2020-05-27 21:54:36 +02:00
return res
2023-04-29 18:15:18 +02:00
def get_round_amount(self, round_amounts, unit_value):
if round_amounts == 'above_amount':
2021-03-10 19:56:48 +01:00
unit_value = math.ceil(float(unit_value) / 100.0) * 100
2023-04-29 18:15:18 +02:00
elif round_amounts == 'under_amount':
2021-03-10 19:56:48 +01:00
unit_value = math.floor(float(unit_value) / 100.0) * 100
2023-04-29 18:15:18 +02:00
elif round_amounts == 'automatic':
2021-03-10 19:56:48 +01:00
unit_value = round(unit_value, -2)
return unit_value
2023-04-29 18:15:18 +02:00
# 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
2021-03-10 19:56:48 +01:00
2023-04-29 18:15:18 +02:00
# 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
2020-04-16 00:38:42 +02:00
2023-04-29 18:15:18 +02:00
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)
2023-05-01 12:26:00 +02:00
2023-04-29 18:15:18 +02:00
discounts = self.set_events(cache_wage_dict)
2020-04-16 00:38:42 +02:00
ctx = {
'absenteeism_days': self.absenteeism_days
}
with Transaction().set_context(ctx):
2023-04-29 18:15:18 +02:00
super(Payroll, self).set_preliquidation(config, extras, discounts, cache_wage_dict)
2020-04-16 00:38:42 +02:00
self.save()
2022-05-12 03:43:11 +02:00
if extras.get('wage_shift_fixed'):
2023-01-31 00:37:18 +01:00
# Shifts fixed must be a list of wage_shift_fixed
shifts_fixed = extras.get('wage_shift_fixed')
to_create = []
for wg in shifts_fixed:
to_create.append(
self.get_line(wg['wage'], wg['qty'], wg['unit_value']))
PayrollLine.create(to_create)
2022-05-12 03:43:11 +02:00
2023-04-29 18:15:18 +02:00
# self.update_wage_no_salary()
time_r = time.time()
self.recompute_lines(cache_wage_dict)
time_r2 = time.time()
2023-05-13 00:36:31 +02:00
# print(time_r2-time_r, 'time recompute lines')
2023-04-29 18:15:18 +02:00
if not config.allow_zero_quantities:
2023-05-01 12:26:00 +02:00
lines_delete = [ln for ln in self.lines if ln.quantity == 0]
2023-04-29 18:15:18 +02:00
PayrollLine.delete(lines_delete)
2020-04-16 00:38:42 +02:00
2023-04-29 18:15:18 +02:00
def set_events(self, cache_wage_dict):
2020-04-16 00:38:42 +02:00
pool = Pool()
Event = pool.get('staff.event')
PayrollLine = pool.get('staff.payroll.line')
2023-04-29 18:15:18 +02:00
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',
2022-02-03 15:03:05 +01:00
[
('employee', '=', self.employee),
('state', '=', 'done'),
['OR',
[
2023-04-29 18:15:18 +02:00
('end_date', '>=', start),
('start_date', '<=', start),
2022-02-03 15:03:05 +01:00
],
[
2023-04-29 18:15:18 +02:00
('end_date', '>=', end),
('start_date', '<=', end),
2022-02-03 15:03:05 +01:00
],
[
2023-04-29 18:15:18 +02:00
('start_date', '>=', start),
('end_date', '<=', end),
2022-02-03 15:03:05 +01:00
],
]
],
[
('employee', '=', self.employee),
('state', '=', 'in_progress'),
2023-04-29 18:15:18 +02:00
('start_date', '<=', end),
('unit_price_formula', 'not in', [None, '']),
2022-02-03 15:03:05 +01:00
]
2023-04-29 18:15:18 +02:00
], fields_names=fields_names)
2020-04-16 00:38:42 +02:00
absenteeism_days = 0
discounts = {}
2023-04-29 18:15:18 +02:00
compute_unit_price = Wage.compute_unit_price
compute_formula = Event.compute_formula
2020-04-16 00:38:42 +02:00
for event in events:
2023-04-29 18:15:18 +02:00
event_ = Event(event['id'])
if not event['category.']['payroll_effect']:
2020-04-16 00:38:42 +02:00
continue
2023-04-29 18:15:18 +02:00
start_date = start
2022-02-03 15:03:05 +01:00
end_date = None
2023-04-29 18:15:18 +02:00
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']
2022-02-02 17:41:18 +01:00
qty_pay = self.contract.get_time_days(start_date, end_date)
else:
2023-04-29 18:15:18 +02:00
qty_pay = event['quantity']
if event['absenteeism']:
2022-01-28 00:07:05 +01:00
absenteeism_days += qty_pay
2023-04-29 18:15:18 +02:00
if event['quantity']:
wage = event['category.']['wage_type']
2020-09-09 18:17:47 +02:00
if wage:
2023-04-29 18:15:18 +02:00
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'])
2020-09-09 18:17:47 +02:00
else:
2023-04-29 18:15:18 +02:00
if event['is_vacations']:
2021-11-10 18:56:11 +01:00
unit_value = self.get_salary_average(
2023-04-29 18:15:18 +02:00
start, self.employee, self.contract, wage_)
2021-05-24 21:08:24 +02:00
else:
2023-04-29 18:15:18 +02:00
unit_value = compute_unit_price(wage_['unit_price_formula'], salary_args)
res = self.get_line(wage_, qty_pay, unit_value)
2021-09-29 17:03:59 +02:00
res.update({
'is_event': True,
2022-01-28 00:07:05 +01:00
'start_date': start_date,
'end_date': end_date,
2023-04-29 18:15:18 +02:00
'origin': event_
2021-09-29 17:03:59 +02:00
})
2022-01-28 00:07:05 +01:00
PayrollLine.create([res])
2023-04-29 18:15:18 +02:00
if event['category.']['wage_type_discount'] and event['quantity']:
id_wt_event = event['category.']['wage_type_discount']
2020-05-24 01:28:22 +02:00
if id_wt_event not in discounts.keys():
discounts[id_wt_event] = 0
2023-04-29 18:15:18 +02:00
discounts[id_wt_event] += event['quantity']
2020-04-16 00:38:42 +02:00
self.absenteeism_days = absenteeism_days
self.save()
return discounts
2021-07-21 00:21:59 +02:00
@classmethod
def get_salary_average(cls, end_date, employee, contract, wage):
# end_date = self.start
start_date_contract = contract.start_date
2023-02-01 23:25:10 +01:00
if not contract.variable_salary:
2023-02-02 15:37:14 +01:00
return Decimal(str(round(contract.last_salary / 30 if contract.last_salary else 0, 2)))
2021-08-13 18:35:04 +02:00
if start_date_contract.month != end_date.month:
start_date = end_date + relativedelta(months=-3)
if start_date <= start_date_contract:
start_date = start_date_contract
next_date = date(start_date.year, start_date.month, 1)
average_days_monthly = []
while next_date < end_date + relativedelta(months=-1):
2021-11-10 18:56:11 +01:00
_, end_day = calendar.monthrange(
next_date.year, next_date.month)
payrolls = cls._get_payrolls_period(
employee, contract, next_date, next_date)
2021-08-13 18:35:04 +02:00
salary_base = 0
if payrolls:
salary_base += payrolls[0].compute_salary_full(wage)
average_days_monthly.append(salary_base)
next_date += relativedelta(months=1)
2021-09-17 16:58:35 +02:00
try:
res = sum(average_days_monthly)/len(average_days_monthly)/30
except:
2021-11-11 06:47:34 +01:00
res = contract.salary / 30 if contract.salary else 0
2021-08-13 18:35:04 +02:00
else:
next_date = end_date
2021-11-10 18:56:11 +01:00
payrolls = cls._get_payrolls_period(
employee, contract, next_date, next_date)
2021-05-24 21:08:24 +02:00
if payrolls:
2021-08-13 18:35:04 +02:00
res = payrolls[0].compute_salary_full(wage)/30
2021-05-24 21:08:24 +02:00
2021-08-13 15:43:23 +02:00
return Decimal(str(round(res, 2)))
2021-05-24 21:08:24 +02:00
2023-04-29 18:15:18 +02:00
def update_wage_no_salary(self, cache_wage_dict):
2020-04-16 00:38:42 +02:00
PayrollLine = Pool().get('staff.payroll.line')
2023-02-01 23:25:10 +01:00
att_getter = attrgetter('wage_type.type_concept', 'wage_type.month_application')
tax_base = None
2020-04-16 00:38:42 +02:00
for line in self.lines:
2023-04-29 18:15:18 +02:00
wage = cache_wage_dict[line.wage_type.id]
2023-02-01 23:25:10 +01:00
type_concept, month_application = att_getter(line)
if month_application:
2023-04-29 18:15:18 +02:00
salary_args = self.get_salary_full(wage)
unit_value = line.wage_type.compute_unit_price(wage['unit_price_formula'], salary_args)
2023-02-01 23:25:10 +01:00
if type_concept == 'interest':
2023-04-29 18:15:18 +02:00
unit_value = self._compute_interest(wage, self.start)
2023-02-01 23:25:10 +01:00
elif type_concept == 'tax':
unit_value, tax_base = self._compute_line_tax(line, wage)
if unit_value > 0:
tax_base = Decimal(str(round(tax_base, 2)))
2023-10-03 17:07:10 +02:00
else:
tax_base = None
2020-04-16 00:38:42 +02:00
else:
continue
PayrollLine.write([line], {'unit_value': unit_value, 'tax_base': tax_base})
2020-04-16 00:38:42 +02:00
def _get_payrolls_contract(self):
dom_payroll = [('employee', '=', self.employee.id)]
if not self.contract:
contract = self.search_contract_on_period()
else:
contract = self.contract
dom_payroll.append(
2020-11-03 23:39:33 +01:00
('start', '>=', contract.start_date),
2020-04-16 00:38:42 +02:00
)
if contract.end_date:
dom_payroll.append(
('start', '<=', contract.end_date)
2020-11-03 23:39:33 +01:00
)
2020-04-16 00:38:42 +02:00
payrolls = self.search(dom_payroll)
return [p.id for p in payrolls]
2023-04-29 18:15:18 +02:00
def _compute_interest(self, wage, limit_date=False, name=None):
2023-02-01 23:25:10 +01:00
year = self.start.year
start = date(year, 1, 1)
end = date(year, 12, 31)
2023-04-29 18:15:18 +02:00
concepts_salary_ids = wage['concepts_salary']
2023-02-01 23:25:10 +01:00
start_date, contract_id, employee_id = attrgetter(
'contract.start_date', 'contract.id', 'employee.id')(self)
2020-04-16 00:38:42 +02:00
2023-02-01 23:25:10 +01:00
if start_date > start:
start = start_date
2020-04-16 00:38:42 +02:00
dom_payrolls = [
('start', '>=', start),
2023-02-01 23:25:10 +01:00
('contract', '=', contract_id),
('employee', '=', employee_id),
2020-04-16 00:38:42 +02:00
]
if limit_date:
dom_payrolls.append(('start', '<=', limit_date))
else:
dom_payrolls.append(('start', '<=', end))
payrolls = self.search(dom_payrolls)
2020-10-15 18:22:57 +02:00
2020-04-16 00:38:42 +02:00
payrolls_ids = [p.id for p in payrolls]
2023-02-01 23:25:10 +01:00
time_contracting = sum(p.worked_days for p in payrolls)
2020-10-15 18:22:57 +02:00
2020-04-16 00:38:42 +02:00
salary_hoard = float(self._compute_hoard(
2021-08-14 16:56:11 +02:00
payrolls_ids,
concepts_salary_ids
2020-04-16 00:38:42 +02:00
))
total_interest = salary_hoard * time_contracting * 0.01 / 360
2020-10-15 18:22:57 +02:00
2020-04-16 00:38:42 +02:00
if self.id in payrolls_ids:
payrolls_ids.remove(self.id)
interest_hoard = float(self._compute_hoard(
2023-10-26 22:12:54 +02:00
payrolls_ids,
[wage['id']]
2020-04-16 00:38:42 +02:00
))
2020-10-15 18:22:57 +02:00
2020-04-16 00:38:42 +02:00
interest = total_interest - interest_hoard
2020-10-15 18:22:57 +02:00
2020-04-16 00:38:42 +02:00
if interest < _ZERO:
interest = _ZERO
2021-11-10 19:06:02 +01:00
return Decimal(str(round(interest, 2)))
2021-11-10 18:56:11 +01:00
# return self.currency.round(Decimal(interest))
2020-04-16 00:38:42 +02:00
def _compute_hoard(self, payrolls_ids, wages_ids):
if not payrolls_ids:
return _ZERO
PayrollLine = Pool().get('staff.payroll.line')
2023-04-29 18:15:18 +02:00
lines = PayrollLine.search_read([
2020-04-16 00:38:42 +02:00
('wage_type', 'in', wages_ids),
('payroll', 'in', payrolls_ids),
2023-04-29 18:15:18 +02:00
], fields_names=['amount', 'reconciled'])
return sum(pl['amount'] for pl in lines if not pl['reconciled'])
2020-04-16 00:38:42 +02:00
2023-04-29 18:15:18 +02:00
def recompute_lines(self, cache_wage_dict):
2022-05-11 00:16:51 +02:00
PayrollLine = Pool().get('staff.payroll.line')
amounts_60_40 = []
2023-02-01 23:25:10 +01:00
amounts_60_40_app = amounts_60_40.append
2022-05-11 00:16:51 +02:00
wages_60_40 = []
2023-02-01 23:25:10 +01:00
wages_60_40_app = wages_60_40.append
att_getter = attrgetter(
'amount', 'wage_type.definition',
'wage_type.account_60_40',
'wage_type.salary_constitute')
2022-05-11 00:16:51 +02:00
for line in self.lines:
2023-02-01 23:25:10 +01:00
amount, definition, account_60_40, salary_constitute = att_getter(line)
if definition == 'payment':
if salary_constitute:
amounts_60_40_app(amount)
elif account_60_40:
wages_60_40_app(line)
amounts_60_40_app(amount)
amount_wages_60_40 = sum(pl.amount for pl in wages_60_40)
fourty_percentage_amount = sum(amounts_60_40) * Decimal(0.4)
amount_to_validate = fourty_percentage_amount < amount_wages_60_40
2022-05-11 00:16:51 +02:00
if amounts_60_40 and wages_60_40 and amount_to_validate:
2023-02-01 23:25:10 +01:00
amount_dif = amount_wages_60_40 - fourty_percentage_amount
for wl in wages_60_40:
new_unit_value = Decimal(str(round(
(amount_dif * (wl.amount / amount_wages_60_40)), 2)))
PayrollLine.write([wl], {
'amount_60_40': new_unit_value,
2022-05-11 00:16:51 +02:00
})
2023-04-29 18:15:18 +02:00
super(Payroll, self).recompute_lines(cache_wage_dict)
2020-04-16 00:38:42 +02:00
line_tax = None
2023-02-01 23:25:10 +01:00
# deductions = _ZERO
2020-04-16 00:38:42 +02:00
for line in self.lines:
2023-02-01 23:25:10 +01:00
w_provision_cancellation = line.wage_type.provision_cancellation
2023-09-22 15:45:54 +02:00
if w_provision_cancellation and line.wage_type.contract_finish:
2021-11-10 18:56:11 +01:00
amount_line = [m.amount for m in self.lines if m.wage_type
2023-02-01 23:25:10 +01:00
== w_provision_cancellation]
2021-04-07 22:26:22 +02:00
values = []
for m in line.move_lines:
values.append(abs(m.debit - m.credit))
amount = sum(values) + sum(amount_line)
line.write([line], {
2022-05-12 03:43:11 +02:00
'unit_value': amount,
})
2023-04-29 18:15:18 +02:00
self.update_wage_no_salary(cache_wage_dict)
for line in self.lines:
2023-05-01 12:26:00 +02:00
wage = cache_wage_dict[line.wage_type.id]
2023-04-29 18:15:18 +02:00
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})
2020-04-16 00:38:42 +02:00
2021-03-22 22:14:14 +01:00
def check_limit(self, base, field, value_field):
Configuration = Pool().get('staff.configuration')
configuration = Configuration(1)
if not configuration.uvt_value:
return 0
uvt_config = configuration.uvt_value
2021-11-10 18:56:11 +01:00
lim_percent = LIM_PERCENT_DEDUCTIBLE[field] if field in LIM_PERCENT_DEDUCTIBLE.keys(
) else None
lim_uvt = LIM_UVT_DEDUCTIBLE[field] if field in LIM_UVT_DEDUCTIBLE.keys(
) else None
2023-01-25 16:34:01 +01:00
res = 0
if field == 'dependents' and value_field:
2023-09-30 16:36:14 +02:00
value_percent = lim_percent * value_field / 100 * base
2023-01-25 16:34:01 +01:00
value_uvt = lim_uvt * uvt_config
res = min(value_percent, value_uvt)
2023-09-30 16:36:14 +02:00
elif field == 'exempted_income':
value_limit_uvt = uvt_config * lim_uvt
value_limit_percent = base * lim_percent / 100
res = min(value_limit_uvt, value_limit_percent)
2023-01-25 16:34:01 +01:00
else:
res = value_field
if lim_uvt:
value_limit_uvt = uvt_config * lim_uvt
if value_field > value_limit_uvt:
res = value_limit_uvt
if lim_percent:
value_limit_percent = base * lim_percent / 100
if value_field > value_limit_percent:
res = value_limit_percent
2021-03-22 22:14:14 +01:00
return res
2023-04-29 18:15:18 +02:00
def _compute_line_tax(self, line_tax, wage):
2020-10-08 05:35:51 +02:00
UvtWithholding = Pool().get('staff.payroll.uvt_withholding')
2023-04-29 18:15:18 +02:00
salary_full = self.get_salary_full(wage)
2021-03-22 22:14:14 +01:00
amount_tax = line_tax.amount if line_tax.amount else 0
deductions_month = self.get_deductions_month() - amount_tax
2023-09-30 16:36:14 +02:00
print(salary_full, 'salary')
print(deductions_month, 'deductions')
2020-10-08 05:35:51 +02:00
salary_full = salary_full['salary']
if self.last_payroll:
2020-12-02 23:57:10 +01:00
payrolls_ids = self._get_payrolls_contract()
2021-08-14 16:56:11 +02:00
deductions_fields = [
2023-02-01 23:25:10 +01:00
'fvp_ind', 'health_prepaid', 'fvp', 'afc',
'housing_interest', 'dependents', 'other_income',
2021-08-14 16:56:11 +02:00
]
2023-02-01 23:25:10 +01:00
attrs = attrgetter(*deductions_fields)(self.employee)
2021-03-22 22:14:14 +01:00
deductions_values = {
2023-02-01 23:25:10 +01:00
'fvp_ind': (attrs[0] or _ZERO),
'health_prepaid': (attrs[1] or _ZERO),
'fvp_afc': (attrs[2] or _ZERO) + (attrs[3] or _ZERO),
'housing_interest': (attrs[4] or _ZERO),
'dependents': (attrs[5] or _ZERO),
'other_income': (attrs[6] or _ZERO),
2021-03-22 22:14:14 +01:00
}
for k, v in deductions_values.items():
res = self.check_limit(salary_full, k, v)
deductions_values[k] = res
2021-11-10 18:56:11 +01:00
base_salary_withholding = salary_full - \
deductions_month - deductions_values['fvp_ind']
2023-09-30 16:36:14 +02:00
print(base_salary_withholding, 'base_salary_withholding')
2021-03-22 22:14:14 +01:00
deductions_values.pop('fvp_ind')
deductions_renta = sum(v for v in deductions_values.values())
base_c = base_salary_withholding - deductions_renta
2021-11-10 18:56:11 +01:00
renta25c = self.check_limit(
2023-09-30 16:36:14 +02:00
base_c, 'exempted_income', base_c * LIM_PERCENT_DEDUCTIBLE['exempted_income']/100)
2021-03-22 22:14:14 +01:00
deductions_renta_renta25c = deductions_renta + renta25c
2023-09-30 16:36:14 +02:00
ret_general = self.check_limit(base_salary_withholding, 'renta_deductions', deductions_renta_renta25c)
print(deductions_renta, 'renta')
print(renta25c, 'renta 25')
print(ret_general, 'renta general')
if deductions_renta_renta25c > ret_general:
deductions_renta_renta25c = ret_general
2023-09-30 16:36:14 +02:00
print(deductions_renta_renta25c, 'rr25', base_salary_withholding)
base_salary_withholding -= deductions_renta_renta25c
2021-03-22 22:14:14 +01:00
2020-10-08 05:35:51 +02:00
unit_value = UvtWithholding.compute_withholding(
base_salary_withholding)
unit_value = self.currency.round(Decimal(unit_value))
return unit_value, base_salary_withholding
2020-10-08 05:35:51 +02:00
2020-04-16 00:38:42 +02:00
def get_non_fiscal_amount(self, name=None):
res = _ZERO
concept = name[:-7]
2023-02-01 23:25:10 +01:00
res = sum(pl.amount for pl in self.lines if pl.wage_type.type_concept == concept)
2020-04-16 00:38:42 +02:00
return res
def get_deductions_month(self):
payrolls = self._get_payrolls_month()
sum_deductions = _ZERO
2023-02-01 23:25:10 +01:00
sum_deductions = sum(pl.amount for p in payrolls for pl in p.lines \
if pl.wage_type.definition == 'deduction')
2020-04-16 00:38:42 +02:00
return sum_deductions
@fields.depends('lines')
def on_change_with_ibc(self, name=None):
2021-03-10 19:56:48 +01:00
pool = Pool()
WageType = pool.get('staff.wage_type')
2021-09-29 17:03:59 +02:00
not_concepts = ['transport']
wage_types = WageType.search([
('salary_constitute', '=', True),
2021-10-08 00:29:56 +02:00
('type_concept', 'not in', not_concepts),
2021-09-29 17:03:59 +02:00
])
wage_ids_concepts = list(set([wage.id for wage in wage_types]))
2021-08-14 16:56:11 +02:00
res = []
for line in self.lines:
_type = line.wage_type
2021-09-29 17:03:59 +02:00
if _type and _type.id in wage_ids_concepts:
2021-08-14 16:56:11 +02:00
res.append(line.amount)
return sum(res)
2020-04-16 00:38:42 +02:00
@classmethod
@ModelView.button
2020-04-16 00:38:42 +02:00
@Workflow.transition('draft')
def force_draft(cls, records):
2020-04-16 00:38:42 +02:00
Move = Pool().get('account.move')
for payroll in records:
if payroll.move:
Move.draft([payroll.move.id])
# Move.delete([payroll.move])
2020-04-16 00:38:42 +02:00
2020-11-28 16:29:35 +01:00
def send_payroll_email(self):
pool = Pool()
config = pool.get('staff.configuration')(1)
Template = pool.get('email.template')
template = config.template_email_confirm
if template:
response = Template.send(template, self, self.employee.party.email)
2020-12-18 15:37:21 +01:00
if response and response.status_code == 202:
2020-11-28 16:29:35 +01:00
self.write([self], {'sended_mail': True})
else:
2023-03-03 22:54:59 +01:00
raise UserError(gettext('staff_payroll_co.msg_dont_send_email',
2023-01-02 16:01:52 +01:00
employee=self.employee.party.name))
2020-11-28 16:29:35 +01:00
else:
2021-06-09 18:07:42 +02:00
raise MissingTemplateEmailPayroll(
gettext('staff_payroll_co.msg_template_not_exist'))
2020-11-28 16:29:35 +01:00
2022-05-13 15:31:34 +02:00
def get_events(self, name=None):
res = []
for line in self.lines:
if line.origin and line.origin.__name__ == 'staff.event':
res.append(line.origin.id)
return res
2020-11-28 16:29:35 +01:00
class PayrollSupportDispersionStart(ModelView):
'Payroll Support Dispersion'
__name__ = 'staff.payroll_support_dispersion.start'
company = fields.Many2One('company.company', 'Company', required=True)
department = fields.Many2One('company.department', 'Department')
2021-11-10 18:56:11 +01:00
period = fields.Many2One('staff.payroll.period',
'Start Period', required=True)
2020-11-28 16:29:35 +01:00
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollSupportDispersion(Wizard):
'Payroll Support Dispersion'
__name__ = 'staff.payroll.support_dispersion'
start = StateView('staff.payroll_support_dispersion.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.payroll_support_dispersion_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Send', 'open_', 'tryton-ok', default=True),
])
2020-11-28 16:29:35 +01:00
open_ = StateTransition()
def transition_open_(self):
pool = Pool()
Payroll = pool.get('staff.payroll')
dom = [
('company', '=', self.start.company.id),
('period', '=', self.start.period.id),
('state', 'in', ['processed', 'posted']),
('sended_mail', '=', False)
]
if self.start.department:
dom.append(('department', '=', self.start.department.id))
payrolls_period = Payroll.search(dom)
no_email = []
y_email = []
for payroll in payrolls_period:
email = payroll.employee.party.email
if email:
payroll.send_payroll_email()
y_email.append(payroll.employee.party.full_name,)
else:
no_email.append(payroll.employee.party.full_name,)
return 'end'
2020-04-16 00:38:42 +02:00
class PayrollGlobalStart(ModelView):
'Payroll Global Start'
__name__ = 'staff.payroll_global.start'
start_period = fields.Many2One('staff.payroll.period',
2021-11-10 18:56:11 +01:00
'Start Period', required=True)
2020-04-16 00:38:42 +02:00
end_period = fields.Many2One('staff.payroll.period', 'End Period',
2021-11-10 18:56:11 +01:00
depends=['start_period'])
2020-04-16 00:38:42 +02:00
company = fields.Many2One('company.company', 'Company', required=True)
department = fields.Many2One('company.department', 'Department')
include_finished = fields.Boolean('Include Finished Contract')
@staticmethod
def default_company():
return Transaction().context.get('company')
@fields.depends('start_period')
def on_change_with_end_period(self, name=None):
if self.start_period:
return self.start_period.id
class PayrollGlobal(Wizard):
'Payroll Global'
__name__ = 'staff.payroll.global'
start = StateView('staff.payroll_global.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.payroll_global_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-04-16 00:38:42 +02:00
print_ = StateReport('staff.payroll.global_report')
def do_print_(self, action):
end_period_id = None
department_id = None
if self.start.end_period:
end_period_id = self.start.end_period.id
if self.start.department:
department_id = self.start.department.id
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-04-16 00:38:42 +02:00
'company': self.start.company.id,
'start_period': self.start.start_period.id,
'end_period': end_period_id,
'department': department_id,
'include_finished': self.start.include_finished,
}
return action, data
def transition_print_(self):
return 'end'
class PayrollGlobalReport(Report):
__name__ = 'staff.payroll.global_report'
@classmethod
def get_domain_payroll(cls, data):
dom_payroll = []
return dom_payroll
@classmethod
2021-06-09 18:07:42 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-16 00:38:42 +02:00
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Payroll = pool.get('staff.payroll')
Period = pool.get('staff.payroll.period')
Department = pool.get('company.department')
start_period = Period(data['start_period'])
dom_periods = []
if data['end_period']:
end_period = Period(data['end_period'])
dom_periods.extend([
('start', '>=', start_period.start),
('end', '<=', end_period.end),
2023-09-15 00:44:33 +02:00
])
2020-04-16 00:38:42 +02:00
else:
dom_periods.append(
('id', '=', start_period.id)
2023-09-15 00:44:33 +02:00
)
2020-04-16 00:38:42 +02:00
periods = Period.search(dom_periods)
dom_pay = cls.get_domain_payroll(data)
dom_pay.append(
('period', 'in', [p.id for p in periods]),
)
dom_pay.append(
('state', 'in', ['processed', 'posted', 'draft']),
)
if data['department']:
dom_pay.append(
2023-09-15 00:44:33 +02:00
['AND', [
'OR', [
2020-04-16 00:38:42 +02:00
('employee.department', '=', data['department']),
('department', '=', None),
2020-11-04 02:16:18 +01:00
], [
2023-09-15 00:44:33 +02:00
('department', '=', data['department']),
2020-04-16 00:38:42 +02:00
],
]]
)
department = Department(data['department']).name
else:
department = None
if not data['include_finished']:
dom_pay.append(
('contract.state', '!=', 'finished')
)
payrolls = Payroll.search(dom_pay)
periods_number = len(periods)
default_vals = cls.default_values()
sum_gross_payments = []
sum_total_deductions = []
sum_net_payment = []
parties = {}
2023-04-12 21:18:51 +02:00
payments = ['salary', 'transport', 'extras', 'food', 'bonus', 'commission']
2020-11-04 02:16:18 +01:00
deductions = [
'health', 'retirement', 'tax', 'syndicate',
2023-03-06 15:29:21 +01:00
'fsp', 'acquired_product', 'advance', 'loan'
2020-11-04 02:16:18 +01:00
]
2020-04-16 00:38:42 +02:00
for payroll in payrolls:
employee_id = payroll.employee.id
party_health = ''
party_retirement = ''
if employee_id not in parties.keys():
position_employee = payroll.employee.position.name if payroll.employee.position else ''
position_contract = payroll.contract.position.name if payroll.contract and payroll.contract.position else ''
parties[employee_id] = default_vals.copy()
parties[employee_id]['employee_code'] = payroll.employee.code
parties[employee_id]['employee'] = payroll.employee.party.name
parties[employee_id]['employee_id_number'] = payroll.employee.party.id_number
if payroll.employee.party_health:
party_health = payroll.employee.party_health.name
if payroll.employee.party_retirement:
party_retirement = payroll.employee.party_retirement.name
parties[employee_id]['party_health'] = party_health
parties[employee_id]['party_retirement'] = party_retirement
2021-11-10 18:56:11 +01:00
parties[employee_id]['basic_salary'] = payroll.contract.get_salary_in_date(
payroll.end)
2020-04-16 00:38:42 +02:00
parties[employee_id]['employee_position'] = position_contract or position_employee or ''
for line in payroll.lines:
if line.wage_type.type_concept in (payments + deductions):
concept = line.wage_type.type_concept
else:
if line.wage_type.definition == 'payment' and line.wage_type.receipt:
2020-04-16 00:38:42 +02:00
concept = 'others_payments'
elif line.wage_type.definition == 'deduction' or \
line.wage_type.definition == 'discount' and \
2021-08-14 16:56:11 +02:00
line.wage_type.receipt:
2020-04-16 00:38:42 +02:00
concept = 'others_deductions'
else:
concept = line.wage_type.type_concept
if not concept:
2021-06-09 18:07:42 +02:00
raise WageTypeConceptError(
gettext('staff_payroll_co.msg_type_concept_not_exists', s=line.wage_type.name))
2020-04-16 00:38:42 +02:00
parties[employee_id][concept] += line.amount
2023-10-05 00:11:41 +02:00
parties[employee_id]['worked_days'] += Decimal(math.ceil(payroll.worked_days_effective))
2020-04-16 00:38:42 +02:00
parties[employee_id]['gross_payments'] += payroll.gross_payments
parties[employee_id]['total_deductions'] += payroll.total_deductions
parties[employee_id]['net_payment'] += payroll.net_payment
sum_gross_payments.append(payroll.gross_payments)
sum_total_deductions.append(payroll.total_deductions)
sum_net_payment.append(payroll.net_payment)
2023-09-21 16:32:35 +02:00
report_context['records'] = sorted(parties.values(), key=lambda x: x['employee'])
2020-04-16 00:38:42 +02:00
report_context['department'] = department
report_context['periods_number'] = periods_number
report_context['start_period'] = start_period
report_context['end_period'] = end_period
report_context['company'] = user.company
report_context['user'] = user
report_context['sum_gross_payments'] = sum(sum_gross_payments)
report_context['sum_net_payment'] = sum(sum_net_payment)
report_context['sum_total_deductions'] = sum(sum_total_deductions)
return report_context
@classmethod
def default_values(cls):
WageType = Pool().get('staff.wage_type')
2020-11-03 23:39:33 +01:00
fields_string = [
'employee_code', 'employee', 'employee_salary',
'employee_id_number', 'party_health', 'party_retirement',
'basic_salary',
]
fields_numeric = [
'net_payment', 'worked_days', 'gross_payments', 'total_deductions',
'others_payments', 'others_deductions'
2020-04-16 00:38:42 +02:00
]
2022-03-07 22:42:58 +01:00
lines_fields = dict(WageType.type_concept.selection)
if '' in lines_fields.keys():
2022-03-07 22:29:43 +01:00
lines_fields.pop('')
2020-04-16 00:38:42 +02:00
default_values = {}
for field in fields_string:
default_values.setdefault(field, None)
for field in fields_numeric:
default_values.setdefault(field, Decimal(0))
2022-03-07 22:42:58 +01:00
for field in lines_fields.keys():
2020-04-16 00:38:42 +02:00
default_values.setdefault(field, Decimal(0))
return default_values
class PayrollPaymentStart(ModelView):
'Payroll Payment Start'
__name__ = 'staff.payroll_payment.start'
2021-08-14 16:56:11 +02:00
period = fields.Many2One('staff.payroll.period', 'Period', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
2020-04-16 00:38:42 +02:00
department = fields.Many2One('company.department', 'Department')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollPayment(Wizard):
'Payroll Payment'
__name__ = 'staff.payroll.payment'
start = StateView('staff.payroll_payment.start',
2021-11-11 06:47:34 +01:00
'staff_payroll_co.payroll_payment_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-04-16 00:38:42 +02:00
print_ = StateReport('staff.payroll.payment_report')
def do_print_(self, action):
period = None
department_id = None
if self.start.department:
department_id = self.start.department.id
if self.start.period:
period = self.start.period.id
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-04-16 00:38:42 +02:00
'company': self.start.company.id,
'period': period,
'department': department_id,
}
return action, data
def transition_print_(self):
return 'end'
class PayrollPaymentReport(Report):
__name__ = 'staff.payroll.payment_report'
@classmethod
2021-06-09 18:07:42 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-16 00:38:42 +02:00
user = Pool().get('res.user')(Transaction().user)
Payroll = Pool().get('staff.payroll')
Period = Pool().get('staff.payroll.period')
Department = Pool().get('company.department')
clause = []
start_date = None
end_date = None
period = None
period_name = None
department = None
if data['period']:
period = Period(data['period'])
start_date = period.start
end_date = period.end
clause = [('period', '=', period)]
period_name = period.name
if data['department']:
clause.append(('employee.department', '=', data['department']))
department = Department(data['department']).name
payrolls = Payroll.search(clause)
new_objects = []
sum_net_payment = []
values = {}
for payroll in payrolls:
values = values.copy()
values['employee'] = payroll.employee.party.name
2022-08-16 16:16:17 +02:00
values['employee_department'] = payroll.employee.department.name \
if payroll.employee.department else ''
2020-04-16 00:38:42 +02:00
values['employee_id_number'] = payroll.employee.party.id_number
values['bank_name'] = payroll.employee.party.bank_name
values['bank_account'] = payroll.employee.party.bank_account
2023-09-16 19:23:33 +02:00
values['type_account'] = payroll.employee.party.bank_account_type_string
2020-04-16 00:38:42 +02:00
values['net_payment'] = payroll.net_payment
sum_net_payment.append(payroll.net_payment)
new_objects.append(values)
report_context['records'] = new_objects
report_context['department'] = department
report_context['start_date'] = start_date
report_context['end_date'] = end_date
report_context['period'] = period_name
report_context['company'] = user.company
report_context['user'] = user
report_context['sum_net_payment'] = sum(sum_net_payment)
return report_context
class PayrollPaycheckStart(ModelView):
'Payroll Paycheck Start'
__name__ = 'staff.payroll_paycheck.start'
periods = fields.One2Many('staff.payroll.period', None,
2021-11-10 18:56:11 +01:00
'Periods', add_remove=[], required=True)
2020-04-16 00:38:42 +02:00
company = fields.Many2One('company.company', 'Company', required=True)
department = fields.Many2One('company.department', 'Department')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollPaycheck(Wizard):
'Payroll Paycheck'
__name__ = 'staff.payroll.paycheck'
start = StateView('staff.payroll_paycheck.start',
2021-11-11 06:47:34 +01:00
'staff_payroll_co.payroll_paycheck_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-04-16 00:38:42 +02:00
print_ = StateReport('staff.payroll.paycheck_report')
def do_print_(self, action):
department_id = None
if self.start.department:
department_id = self.start.department.id
periods = [p.id for p in self.start.periods]
2021-10-22 01:01:44 +02:00
periods_name = [p.name for p in self.start.periods]
2020-04-16 00:38:42 +02:00
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-04-16 00:38:42 +02:00
'company': self.start.company.id,
'periods': periods,
2021-10-22 01:01:44 +02:00
'periods_name': periods_name,
2020-04-16 00:38:42 +02:00
'department': department_id,
}
return action, data
def transition_print_(self):
return 'end'
class PayrollPaycheckReport(Report):
__name__ = 'staff.payroll.paycheck_report'
@classmethod
def get_domain_payroll(cls, data):
dom_payroll = [
('period', 'in', data['periods']),
('state', '!=', 'draft'),
]
if data['department']:
dom_payroll.append(
('employee.department', '=', data['department'])
)
return dom_payroll
@classmethod
2021-06-09 18:07:42 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-16 00:38:42 +02:00
pool = Pool()
Payroll = pool.get('staff.payroll')
2021-10-22 01:01:44 +02:00
PayrollLine = pool.get('staff.payroll.line')
2020-04-16 00:38:42 +02:00
Company = pool.get('company.company')
dom_payroll = cls.get_domain_payroll(data)
2021-10-22 01:01:44 +02:00
fields_payroll = [
'id', 'employee.party.name', 'employee.party.id_number',
'contract.start_date', 'contract.end_date', 'date_effective',
2022-02-11 22:47:01 +01:00
'ibc', 'contract.last_salary', 'worked_days', 'employee', 'contract'
2021-10-22 01:01:44 +02:00
]
2021-11-10 18:56:11 +01:00
payrolls = Payroll.search_read(
dom_payroll, fields_names=fields_payroll)
2023-05-10 22:10:18 +02:00
# wage_dict_cache = Payroll.create_cache_wage_types()
2020-04-16 00:38:42 +02:00
today = date.today()
2021-10-22 01:01:44 +02:00
res = {}
wage_type_default = [
'health', 'retirement', 'risk', 'box_family',
'salary', 'fsp', 'icbf', 'sena'
]
for p in payrolls:
key = str(p['employee']) + '_' + str(p['contract'])
try:
res[key]['ibc'] += p['ibc']
res[key]['variation'] += p['ibc']
res[key]['worked_days'] += p['worked_days']
except:
res[key] = p
res[key]['today'] = today
res[key]['ibc'] = p['ibc']
res[key]['worked_days'] = p['worked_days']
res[key]['type_contributor'] = '01'
res[key]['type_id'] = 'CC'
res[key]['type_affiliation'] = 'D'
for w in wage_type_default:
res[key][w + '_amount'] = 0
if w not in ('salary'):
res[key][w + '_code'] = ''
res[key][w + '_name'] = ''
res[key][w + '_rate'] = 0
res[key]['license_amount'] = 0
res[key]['incapacity_amount'] = 0
res[key]['holidays_amount'] = 0
res[key]['variation'] = p['ibc']
res[key]['subtotal'] = 0
payroll_ids = [p['id'] for p in payrolls]
PayrollLine = pool.get('staff.payroll.line')
2020-04-16 00:38:42 +02:00
2021-10-22 01:01:44 +02:00
fields_lines = [
'amount', 'quantity', 'party.name', 'wage_type.type_concept',
'wage_type.unit_price_formula', 'wage_type.expense_formula',
'payroll', 'start_date', 'end_date', 'payroll.employee',
2022-04-16 15:33:06 +02:00
'payroll.contract', 'wage_type.salary_constitute',
2023-05-10 22:10:18 +02:00
'wage_type.concepts_salary', 'wage_type.month_application',
'party.code', 'wage_type.round_amounts', 'wage_type.minimal_amount'
2021-10-22 01:01:44 +02:00
]
dom_line = [
('payroll', 'in', payroll_ids),
2021-11-10 18:56:11 +01:00
['OR',
('wage_type.type_concept', 'in', wage_type_default),
('wage_type.type_concept', 'ilike', 'incapacity%'),
('wage_type.type_concept', 'ilike', 'license%'),
['AND',
('wage_type.type_concept', '=', 'holidays'),
('wage_type.provision_cancellation', '!=', None),
]]
2021-10-22 01:01:44 +02:00
]
order = [('payroll.employee', 'DESC'), ('payroll', 'ASC')]
2021-11-10 18:56:11 +01:00
payroll_lines = PayrollLine.search_read(
dom_line, fields_names=fields_lines, order=order)
2020-04-16 00:38:42 +02:00
total = []
2021-10-22 01:01:44 +02:00
total_append = total.append
for line in payroll_lines:
2021-11-10 18:56:11 +01:00
key = str(line['payroll.']['employee']) + '_' + \
str(line['payroll.']['contract'])
total_append(cls._values_without_move(
line, wage_type_default, res, key))
2021-10-22 01:01:44 +02:00
2020-04-16 00:38:42 +02:00
report_context['records'] = res.values()
report_context['company'] = Company(data['company'])
2021-10-22 01:01:44 +02:00
report_context['total'] = sum(total)
2020-04-16 00:38:42 +02:00
return report_context
@classmethod
2021-10-22 01:01:44 +02:00
def _values_without_move(cls, line, wage_type_default, res, key):
PayrollLine = Pool().get('staff.payroll.line')
total = 0
concept = line['wage_type.']['type_concept']
if concept in wage_type_default and concept != 'salary':
unit_formula = line['wage_type.']['unit_price_formula']
if unit_formula:
2023-05-10 22:10:18 +02:00
try:
unit_formula = Decimal(
(unit_formula[unit_formula.index('*')+1:]).strip())
except:
unit_formula = 0
2021-10-22 01:01:44 +02:00
else:
unit_formula = 0
expense_formula = line['wage_type.']['expense_formula']
if expense_formula:
2021-11-10 18:56:11 +01:00
expense_formula = Decimal(
(expense_formula[expense_formula.index('*')+1:]).strip())
2021-10-22 01:01:44 +02:00
line_ = PayrollLine(line['id'])
2023-05-10 22:10:18 +02:00
expense_amount = line_.get_expense_amount(line['wage_type.'])
2021-10-22 01:01:44 +02:00
res[key][concept + '_amount'] += expense_amount
res[key]['subtotal'] += expense_amount
total += expense_amount
else:
expense_formula = 0
res[key][concept + '_name'] = line['party.']['name'] if line['party.'] else ''
2021-11-10 18:56:11 +01:00
res[key][concept + '_rate'] = unit_formula + \
(expense_formula if expense_formula < 1 else 0)
2022-04-16 15:33:06 +02:00
res[key][concept + '_code'] = line['party.']['code'] if line['party.'] else ''
2021-10-22 01:01:44 +02:00
res[key]['subtotal'] += line['amount']
total += line['amount']
res[key][concept + '_amount'] += line['amount']
elif concept.startswith('license'):
res[key]['license_amount'] += line['amount']
res[key]['variation'] -= line['amount']
# dict_employee[concept]['start_date'] = line['start_date']
# dict_employee[concept]['end_date'] = line['start_date']
elif concept.startswith('incapacity'):
res[key]['incapacity_amount'] += line['amount']
res[key]['variation'] -= line['amount']
elif concept == 'salary':
res[key]['salary_amount'] += line['amount']
res[key]['variation'] -= line['amount']
elif concept == 'holidays':
if line['wage_type.']['salary_constitute']:
res[key]['variation'] -= line['amount']
res[key]['holidays_amount'] += line['amount']
# dict_employee[concept]['start_date'] = line['start_date']
# dict_employee[concept]['end_date'] = line['start_date']
return total
2020-04-16 00:38:42 +02:00
class PayrollSheetStart(ModelView):
'Payroll Sheet Start'
__name__ = 'staff.payroll.sheet.start'
periods = fields.One2Many('staff.payroll.period', None,
2021-11-10 18:56:11 +01:00
'Periods', add_remove=[], required=True)
2020-04-16 00:38:42 +02:00
company = fields.Many2One('company.company', 'Company', required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollSheet(Wizard):
'Payroll Sheet'
__name__ = 'staff.payroll.sheet'
start = StateView('staff.payroll.sheet.start',
2021-11-11 06:47:34 +01:00
'staff_payroll_co.payroll_sheet_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-04-16 00:38:42 +02:00
print_ = StateReport('staff.payroll.sheet_report')
def do_print_(self, action):
periods = [p.id for p in self.start.periods]
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-04-16 00:38:42 +02:00
'company': self.start.company.id,
'periods': periods,
}
return action, data
def transition_print_(self):
return 'end'
class PayrollSheetReport(Report):
__name__ = 'staff.payroll.sheet_report'
@classmethod
def get_domain_payroll(cls, data):
2021-08-14 19:09:43 +02:00
return []
2020-04-16 00:38:42 +02:00
@classmethod
2021-06-09 18:07:42 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-16 00:38:42 +02:00
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Payroll = pool.get('staff.payroll')
Period = pool.get('staff.payroll.period')
periods_names = ''
dom_payroll = cls.get_domain_payroll(data)
if data['periods']:
periods = Period.browse(data['periods'])
periods_names = [p.name + ' / ' for p in periods]
dom_payroll.append([('period', 'in', data['periods'])])
2020-11-04 02:16:18 +01:00
payrolls = Payroll.search(dom_payroll,
2021-11-11 06:47:34 +01:00
order=[
('employee.party.name', 'ASC'),
('period.name', 'ASC')
])
2020-04-16 00:38:42 +02:00
2023-05-10 18:45:07 +02:00
wages_dict = Payroll.create_cache_wage_types()
2020-04-16 00:38:42 +02:00
new_objects = []
default_vals = cls.default_values()
sum_gross_payments = []
sum_total_deductions = []
sum_net_payment = []
item = 0
for payroll in payrolls:
item += 1
2021-08-14 16:56:11 +02:00
values = copy.deepcopy(default_vals)
2020-04-16 00:38:42 +02:00
values['item'] = item
values['employee'] = payroll.employee.party.name
values['id_number'] = payroll.employee.party.id_number
position_name, position_contract = '', ''
if payroll.employee.position:
position_name = payroll.employee.position.name
if payroll.contract and payroll.contract.position:
position_contract = payroll.contract.position.name
values['position'] = position_contract or position_name
2020-05-24 01:28:22 +02:00
values['department'] = payroll.employee.department.name \
if payroll.employee.department else ''
2020-04-16 00:38:42 +02:00
values['company'] = user.company.party.name
2020-05-24 01:28:22 +02:00
values['legal_salary'] = payroll.contract.get_salary_in_date(
payroll.end)
2020-04-16 00:38:42 +02:00
values['period'] = payroll.period.name
2020-06-19 16:42:57 +02:00
salary_day_in_date = payroll.contract.get_salary_in_date(
2021-08-14 16:56:11 +02:00
payroll.end) / 30
2020-06-19 16:42:57 +02:00
values['salary_day'] = salary_day_in_date
2021-11-10 18:56:11 +01:00
values['salary_hour'] = (
salary_day_in_date / 8) if salary_day_in_date else 0
2020-04-16 00:38:42 +02:00
values['worked_days'] = payroll.worked_days
values['gross_payment'] = payroll.gross_payments
2020-11-04 02:16:18 +01:00
2020-04-16 00:38:42 +02:00
# Add compatibility with staff contracting
project = ""
if hasattr(payroll, 'project'):
if payroll.project:
project = payroll.project.name
if hasattr(payroll.employee, 'project_contract'):
if payroll.employee.project_contract and \
2020-05-24 01:28:22 +02:00
payroll.employee.project_contract.reference:
2020-04-16 00:38:42 +02:00
project = payroll.employee.project_contract.reference
values['project'] = project
2023-05-10 18:45:07 +02:00
values.update(cls._prepare_lines(payroll, values, wages_dict))
2020-04-16 00:38:42 +02:00
sum_gross_payments.append(payroll.gross_payments)
sum_total_deductions.append(payroll.total_deductions)
sum_net_payment.append(payroll.net_payment)
new_objects.append(values)
report_context['records'] = new_objects
report_context['periods'] = periods_names
2021-08-14 16:56:11 +02:00
report_context['company'] = user.company.rec_name
2020-04-16 00:38:42 +02:00
report_context['user'] = user
report_context['sum_gross_payments'] = sum(sum_gross_payments)
report_context['sum_net_payment'] = sum(sum_net_payment)
report_context['sum_total_deductions'] = sum(sum_total_deductions)
return report_context
@classmethod
def default_values(cls):
2021-08-14 16:56:11 +02:00
_values = {}
for field in SHEET_FIELDS_NOT_AMOUNT:
_values.setdefault(field, None)
for field in FIELDS_AMOUNT:
_values.setdefault(field, [])
return _values
2020-04-16 00:38:42 +02:00
@classmethod
2023-05-10 18:45:07 +02:00
def _prepare_lines(cls, payroll, vals, wages_dict):
2020-04-16 00:38:42 +02:00
for line in payroll.lines:
2023-05-10 18:45:07 +02:00
wage_id = line.wage_type.id
wage_dict = wages_dict[wage_id]
concept = wage_dict['type_concept']
2021-08-14 16:56:11 +02:00
amount = line.amount
2023-05-10 18:45:07 +02:00
definition = wage_dict['definition']
2021-08-14 16:56:11 +02:00
if definition == 'payment':
if concept == 'extras':
vals['total_extras'].append(amount)
for e in EXTRAS:
2023-05-10 18:45:07 +02:00
if e.upper() in wage_dict['name']:
2021-08-14 16:56:11 +02:00
vals[e].append(line.quantity or 0)
vals['cost_' + e].append(amount)
2020-04-16 00:38:42 +02:00
break
2021-08-14 16:56:11 +02:00
elif concept in FIELDS_AMOUNT:
vals[concept].append(amount)
2020-04-16 00:38:42 +02:00
else:
2021-08-14 16:56:11 +02:00
vals['other'].append(amount)
elif definition == 'deduction':
vals['total_deduction'].append(amount)
if concept == 'health':
vals['health'].append(amount)
2023-05-10 18:45:07 +02:00
vals['health_provision'].append(line.get_expense_amount(wage_dict))
2021-08-14 16:56:11 +02:00
elif concept == 'retirement':
vals['retirement'].append(amount)
2021-11-10 18:56:11 +01:00
vals['retirement_provision'].append(
2023-05-10 18:45:07 +02:00
line.get_expense_amount(wage_dict))
2020-04-16 00:38:42 +02:00
else:
2021-08-14 16:56:11 +02:00
if concept == 'fsp':
vals['fsp'].append(amount)
elif concept == 'tax':
vals['retefuente'].append(amount)
2020-04-16 00:38:42 +02:00
else:
2021-08-14 16:56:11 +02:00
vals['other_deduction'].append(amount)
2020-04-16 00:38:42 +02:00
else:
2021-08-14 16:56:11 +02:00
vals['discount'].append(amount)
2020-04-16 00:38:42 +02:00
print('Warning: Line no processed... ', line.wage_type.name)
2021-08-14 16:56:11 +02:00
for key in SHEET_SUMABLES:
vals[key] = sum(vals[key])
2020-11-04 02:16:18 +01:00
vals['gross_payment'] = sum([
2021-08-14 16:56:11 +02:00
vals['salary'],
vals['total_extras'],
vals['transport'],
vals['food'],
vals['bonus']
2020-11-04 02:16:18 +01:00
])
2021-08-14 16:56:11 +02:00
2020-04-16 00:38:42 +02:00
vals['net_payment'] = vals['gross_payment'] - vals['total_deduction']
vals['ibc'] = vals['gross_payment']
2020-11-04 02:16:18 +01:00
vals['total_benefit'] = sum([
2021-08-14 16:56:11 +02:00
vals['unemployment'],
vals['interest'],
vals['holidays'],
vals['bonus_service'],
])
vals['total_parafiscales'] = sum([
vals['box_family'],
vals['sena'],
vals['icbf']
2020-11-04 02:16:18 +01:00
])
2020-04-16 00:38:42 +02:00
vals['total_ssi'] = vals['retirement_provision'] + vals['risk']
2020-11-04 02:16:18 +01:00
vals['total_cost'] = sum([
2021-08-14 16:56:11 +02:00
vals['total_ssi'],
vals['box_family'],
2021-09-18 17:36:27 +02:00
vals['gross_payment'],
2020-11-04 02:16:18 +01:00
vals['total_benefit']
])
2020-04-16 00:38:42 +02:00
return vals
2020-11-04 02:16:18 +01:00
class PayrollGroupStart(metaclass=PoolMeta):
2020-04-16 00:38:42 +02:00
__name__ = 'staff.payroll_group.start'
department = fields.Many2One('company.department', 'Department')
2021-04-23 00:21:48 +02:00
employees = fields.Many2Many('company.employee', None, None,
2021-11-11 06:47:34 +01:00
'Employees', domain=[('active', '=', True)])
2023-03-25 18:56:59 +01:00
task = fields.Boolean('Programm Task')
2020-04-16 00:38:42 +02:00
2020-11-04 02:16:18 +01:00
class PayrollGroup(metaclass=PoolMeta):
2020-04-16 00:38:42 +02:00
__name__ = 'staff.payroll_group'
2023-05-10 06:40:04 +02:00
def transition_open_nim(self):
2023-05-02 16:49:52 +02:00
Payroll = Pool().get('staff.payroll')
Wage = Pool().get('staff.wage_type')
fields_names = [
'name', 'sequence', 'definition', 'unit_price_formula',
'unit_price_formula', 'salary_constitute', 'expense_formula',
'type_concept'
]
wages = Wage.search_read([], fields_names=fields_names)
2023-05-13 00:36:31 +02:00
# print('wages ===================', type(wages))
2023-05-02 16:49:52 +02:00
wage = {
"id" : 3,
"name" : "User"
}
2023-05-10 06:38:57 +02:00
# Fast Payroll Compute
2023-05-02 16:49:52 +02:00
res = fpc.computePayroll(wage)
print("Running in Nim....", res)
return 'end'
2023-05-10 06:40:04 +02:00
def transition_open_(self):
2023-03-25 18:56:59 +01:00
if self.start.task:
Employee = Pool().get('company.employee')
Payroll = Pool().get('staff.payroll')
Task = Pool().get('payroll.task')
fields = self.start.fields_get()
transaction = Transaction()
context = transaction.context
payrolls_period = Payroll.search([
('period', '=', self.start.period.id),
])
employee_w_payroll = [p.employee.id for p in payrolls_period]
dom_employees = self.get_employees_dom(employee_w_payroll)
employees = Employee.search(dom_employees)
if len(employees) <= 0:
raise UserError(gettext('staff_payroll_co.msg_dont_get_employees'))
args = {}
for k, v in fields.items():
if k == 'task':
continue
if v['type'] == 'many2one':
try:
args[k] = getattr(self.start, k).id
except:
pass
elif v['type'] == 'many2many':
args[k] = [f.id for f in getattr(self.start, k)]
else:
args[k] = getattr(self.start, k)
args['employees'] = [e.id for e in employees]
department = self.start.department.name if self.start.department else ''
data = {
'model': self.__name__,
'method': 'transition_open_',
'user': transaction.user,
'context': context,
'args': args,
'kwargs': None,
}
value = {
'name': self.start.description + department,
'data': data,
'payroll': len(employees),
'state': 'active'
}
Task.create([value])
return 'end'
2023-03-27 16:18:17 +02:00
return super(PayrollGroup, self).transition_open_()
2023-03-25 18:56:59 +01:00
2020-04-16 00:38:42 +02:00
def get_employees_dom(self, employees_w_payroll):
2020-11-04 02:16:18 +01:00
dom = super(PayrollGroup, self).get_employees_dom(employees_w_payroll)
2022-04-01 00:09:38 +02:00
Contract = Pool().get('staff.contract')
dom_contract = get_dom_contract_period(self.start.period.start, self.start.period.end)
contracts = Contract.search(dom_contract)
2022-04-01 02:05:05 +02:00
employee_ids = list(set(contract.employee.id for contract in contracts))
2022-04-01 00:09:38 +02:00
if employee_ids:
dom.append(('id', 'in', employee_ids))
2020-04-16 00:38:42 +02:00
if self.start.department:
2020-11-04 02:16:18 +01:00
dom.append(('department', '=', self.start.department.id))
2021-04-23 00:21:48 +02:00
if self.start.employees:
employees_ids = [e.id for e in self.start.employees]
2021-04-23 16:08:00 +02:00
dom.append(('id', 'in', employees_ids))
2020-11-04 02:16:18 +01:00
return dom
2020-04-16 00:38:42 +02:00
class OpenPayrollByPeriodStart(ModelView):
'Open Payroll By Period Start'
__name__ = 'staff_payroll_co.open_payroll_by_period.start'
company = fields.Many2One('company.company', 'Company', required=True)
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
2021-11-10 18:56:11 +01:00
required=True)
2020-04-16 00:38:42 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_fiscalyear():
FiscalYear = Pool().get('account.fiscalyear')
return FiscalYear.find(
Transaction().context.get('company'), exception=False)
class OpenPayrollByPeriod(Wizard):
'Open Payroll By Period'
__name__ = 'staff_payroll_co.open_payroll_by_period'
start = StateView('staff_payroll_co.open_payroll_by_period.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.open_payroll_by_period_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'open_', 'tryton-ok', default=True),
])
2020-04-16 00:38:42 +02:00
open_ = StateAction('staff_payroll_co.act_payroll_by_period_board')
def do_open_(self, action):
data = {
'company': self.start.company.id,
'fiscalyear': self.start.fiscalyear.id,
}
action['name'] += ' - %s' % self.start.fiscalyear.name
return action, data
def transition_open_(self):
return 'end'
class PayrollByPeriodDynamic(Period):
'Payroll By Period Dynamic'
__name__ = 'staff_payroll_co.payroll_by_period_dynamic'
payrolls = fields.Function(fields.One2Many('staff.payroll', None,
2022-12-21 22:50:01 +01:00
'Payrolls'), 'get_payrolls')
2020-04-16 00:38:42 +02:00
amount_net_payment = fields.Function(fields.Numeric('Amount Net Payment',
2022-12-21 22:50:01 +01:00
digits=(16, 2)), 'get_amount')
2020-04-16 00:38:42 +02:00
amount_total_cost = fields.Function(fields.Numeric('Amount Total Cost',
2022-12-21 22:50:01 +01:00
digits=(16, 2)), 'get_amount')
2020-04-16 00:38:42 +02:00
def get_amount(self, name=None):
res = []
method = name[7:]
for payroll in self.payrolls:
value = getattr(payroll, method)
res.append(value)
return sum(res)
def get_payrolls(self, name=None):
pool = Pool()
Payroll = pool.get('staff.payroll')
payrolls = Payroll.search([
('period', '=', self.id),
('state', 'in', ['processed', 'posted']),
])
return [i.id for i in payrolls]
class Exo2276Start(ModelView):
'Payroll Exo 2276 Start'
__name__ = 'staff.payroll_exo2276.start'
2020-11-04 02:16:18 +01:00
start_period = fields.Many2One('staff.payroll.period', 'Start Period',
2021-11-10 18:56:11 +01:00
required=True)
2020-11-04 02:16:18 +01:00
end_period = fields.Many2One('staff.payroll.period', 'End Period',
2021-11-10 18:56:11 +01:00
required=True)
2020-04-16 00:38:42 +02:00
company = fields.Many2One('company.company', 'Company', required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
class Exo2276(Wizard):
'Payroll Exo 2276'
__name__ = 'staff.payroll_exo2276'
start = StateView('staff.payroll_exo2276.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.payroll_exo2276_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-04-16 00:38:42 +02:00
print_ = StateReport('staff.payroll_exo2276.report')
def do_print_(self, action):
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-04-16 00:38:42 +02:00
'company': self.start.company.id,
'start_period': self.start.start_period.start,
'end_period': self.start.end_period.end,
}
return action, data
def transition_print_(self):
return 'end'
class Exo2276Report(Report):
__name__ = 'staff.payroll_exo2276.report'
@classmethod
2020-04-28 17:33:54 +02:00
def get_domain_payroll(cls, data=None):
Period = Pool().get('staff.payroll.period')
2020-04-16 00:38:42 +02:00
periods = Period.search([
('start', '>=', data['start_period']),
('end', '<=', data['end_period']),
])
periods_ids = [p.id for p in periods]
2020-04-28 17:33:54 +02:00
domain = [
2020-04-16 00:38:42 +02:00
('period', 'in', periods_ids),
('state', '=', 'posted'),
2020-04-28 17:33:54 +02:00
]
return domain
@classmethod
2021-07-29 16:56:07 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-04-28 17:33:54 +02:00
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Payroll = pool.get('staff.payroll')
2021-04-09 19:32:25 +02:00
MoveLine = pool.get('account.move.line')
2022-05-12 23:41:07 +02:00
LiquidationLine = pool.get('staff.liquidation.line')
Period = pool.get('staff.payroll.period')
2021-04-09 19:32:25 +02:00
WageType = pool.get('staff.wage_type')
2020-04-28 17:33:54 +02:00
domain_ = cls.get_domain_payroll(data)
payrolls = Payroll.search([domain_])
2020-04-16 00:38:42 +02:00
new_objects = {}
2020-08-10 19:13:49 +02:00
start_period = data['start_period']
end_period = data['end_period']
2022-05-12 23:41:07 +02:00
# pag_wage = WageType.search_read([
# ('type_concept', 'in', [
# 'unemployment', 'interest', 'holidays']),
# ], fields_names=['credit_account', 'type_concept', 'debit_account'])
# accounts_id = {'cesanpag': [], 'holidays': []}
# for p in pag_wage:
# if p['type_concept'] in ('unemployment', 'interest'):
# accounts_id['cesanpag'].append(p['credit_account'])
# accounts_id['cesanpag'].append(p['debit_account'])
# else:
# accounts_id['holidays'].append(p['debit_account'])
# accounts_id['holidays'].append(p['credit_account'])
2020-04-28 01:20:57 +02:00
index = 0
2020-04-16 00:38:42 +02:00
for payroll in payrolls:
2020-04-28 01:20:57 +02:00
index += 1
2020-04-16 00:38:42 +02:00
party = payroll.employee.party
2020-06-25 03:04:53 +02:00
if party.id in new_objects.keys():
2020-04-16 00:38:42 +02:00
continue
new_objects[party.id] = {
2020-04-28 01:20:57 +02:00
'index': index,
2020-04-16 00:38:42 +02:00
'type_document': party.type_document,
'id_number': party.id_number,
'first_family_name': party.first_family_name,
2021-05-24 21:08:24 +02:00
'second_family_name': party.second_family_name,
2020-04-16 00:38:42 +02:00
'first_name': party.first_name,
2020-08-10 19:13:49 +02:00
'second_name': party.second_name,
2020-04-16 00:38:42 +02:00
'addresses': party.addresses[0].street,
'department_code': party.department_code,
'city_code': party.city_code,
'country_code': party.country_code,
'email': party.email,
'payments': 0,
'cesanpag': 0,
'interest': 0,
'holidays': 0,
'bonus': 0,
'bonus_service': 0,
'transport': 0,
'food': 0,
'total_deduction': 0,
'health': 0,
'retirement': 0,
'fsp': 0,
'retefuente': 0,
'other_deduction': 0,
'discount': 0,
'other': 0,
'various': 0,
2020-08-14 00:36:59 +02:00
'salary': 0,
'extras': 0,
'box_family': 0,
'risk': 0,
'unemployment': 0,
2020-04-16 00:38:42 +02:00
'allowance': 0,
'syndicate': 0,
'commission': 0,
2020-08-14 00:36:59 +02:00
'total_benefit': 0,
'gross_payment': 0,
'_cesanpag': 0,
'others_payments': 0,
'total_retirement': 0,
'total_salary': 0,
2020-04-16 00:38:42 +02:00
}
2021-11-10 18:56:11 +01:00
new_objects[party.id] = cls._prepare_lines(
payrolls, new_objects[party.id], party.id)
2020-06-25 03:04:53 +02:00
_cesanpag = 0
2022-05-13 00:38:07 +02:00
_total_benefit = 0
2022-05-23 18:53:18 +02:00
_other = 0
2023-05-13 00:36:31 +02:00
_tax = 0
2022-05-12 23:41:07 +02:00
lines_liquid = LiquidationLine.search_read([
('liquidation.employee.party', '=', party.id),
('liquidation.liquidation_date', '>=', data['start_period']),
('liquidation.liquidation_date', '<=', data['end_period']),
2023-05-13 00:36:31 +02:00
('wage.type_concept', 'in', ['unemployment', 'interest', 'holidays', 'bonus_service', 'tax'])
2022-05-12 23:41:07 +02:00
], fields_names=['amount', 'wage.type_concept'])
for l in lines_liquid:
if l['wage.']['type_concept'] in ['unemployment', 'interest']:
_cesanpag += l['amount']
2022-05-23 18:53:18 +02:00
elif l['wage.']['type_concept'] == 'convencional_bonus':
_other += l['amount']
2023-05-13 00:36:31 +02:00
elif l['wage.']['type_concept'] == 'tax':
_tax += abs(l['amount'])
2022-05-12 23:41:07 +02:00
else:
2022-05-13 00:38:07 +02:00
_total_benefit += l['amount']
2020-06-25 03:04:53 +02:00
new_objects[party.id]['cesanpag'] = _cesanpag
2022-05-13 00:38:07 +02:00
new_objects[party.id]['total_benefit'] += _total_benefit
2022-05-23 18:53:18 +02:00
new_objects[party.id]['others_payments'] += _other
2023-05-13 00:36:31 +02:00
new_objects[party.id]['retefuente'] += _tax
2020-04-16 00:38:42 +02:00
2020-06-25 03:04:53 +02:00
report_context['records'] = new_objects.values()
2020-08-10 19:13:49 +02:00
report_context['end_period'] = end_period
report_context['start_period'] = start_period
2020-04-16 00:38:42 +02:00
report_context['today'] = date.today()
report_context['company'] = user.company
return report_context
@classmethod
def _prepare_lines(cls, payrolls, vals, party_id):
payroll_ids = [payroll.id for payroll in payrolls]
Lines = Pool().get('staff.payroll.line')
lines = Lines.search([
('payroll', 'in', payroll_ids),
('payroll.employee.party', '=', party_id),
])
for line in lines:
2021-08-14 16:56:11 +02:00
concept = line.wage_type.type_concept
2020-04-16 00:38:42 +02:00
if line.wage_type.definition == 'payment':
2020-06-25 03:04:53 +02:00
vals['payments'] += line.amount
2021-08-14 16:56:11 +02:00
if concept == 'unemployment':
2020-06-25 03:04:53 +02:00
continue
# vals['unemployment'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'interest':
2020-06-25 03:04:53 +02:00
continue
# vals['interest'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'salary':
2020-04-16 00:38:42 +02:00
vals['salary'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'commission':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['commission'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'allowance':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['allowance'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'extras':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['extras'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'holidays':
2021-05-24 21:08:24 +02:00
continue
# vals['holidays'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'bonus':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['bonus'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'bonus_service':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['bonus_service'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'transport':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['transport'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'food':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['food'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'box_family':
2020-04-16 00:38:42 +02:00
vals['box_family'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'risk':
2020-04-16 00:38:42 +02:00
vals['risk'] += line.amount
2022-10-25 15:34:44 +02:00
elif concept in ['other', 'convencional_bonus']:
2020-04-16 00:38:42 +02:00
vals['other'] += line.amount
else:
2021-11-10 18:56:11 +01:00
print('Warning: Line no processed... ',
concept, line.wage_type.name)
2020-04-16 00:38:42 +02:00
vals['various'] += line.amount
elif line.wage_type.definition == 'deduction':
vals['total_deduction'] += line.amount
2021-08-14 16:56:11 +02:00
if concept == 'health':
2020-04-16 00:38:42 +02:00
vals['health'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'retirement':
2020-04-16 00:38:42 +02:00
vals['retirement'] += line.amount
else:
2021-08-14 16:56:11 +02:00
if concept == 'fsp':
2020-04-16 00:38:42 +02:00
vals['fsp'] += line.amount
2021-08-14 16:56:11 +02:00
elif concept == 'tax':
2020-04-16 00:38:42 +02:00
vals['retefuente'] += line.amount
else:
vals['other_deduction'] += line.amount
else:
vals['discount'] += line.amount
print('Warning: Line no processed... ', line.wage_type.name)
2020-06-25 03:04:53 +02:00
# vals['cesanpag'] = vals['unemployment'] + vals['interest']
2021-11-10 18:56:11 +01:00
vals['others_payments'] = (vals['other'] + vals['commission']
+ vals['bonus'] + vals['allowance']
2022-05-19 00:28:04 +02:00
+ vals['various'] + vals['food']
2022-05-23 18:53:18 +02:00
+ vals['transport'])
2022-05-13 00:49:31 +02:00
# vals['total_benefit'] = vals['holidays'] + vals['bonus_service']
2020-06-25 03:04:53 +02:00
vals['total_retirement'] = vals['fsp'] + vals['retirement']
2020-11-04 02:24:58 +01:00
vals['total_salary'] = sum([
2022-05-19 00:28:04 +02:00
vals['salary'], vals['extras']
2020-11-04 02:24:58 +01:00
])
2020-04-16 00:38:42 +02:00
return vals
2021-05-24 21:08:24 +02:00
# vals['unemployment'], vals['interest'], vals['holidays'],
# vals['bonus_service']
2020-04-16 00:38:42 +02:00
2020-04-28 01:20:57 +02:00
class IncomeWithholdingsStart(ModelView):
'Income Withholding Start'
__name__ = 'staff.payroll.income_withholdings.start'
2021-04-09 19:32:25 +02:00
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
2021-11-10 18:56:11 +01:00
required=True)
2020-04-28 01:20:57 +02:00
company = fields.Many2One('company.company', 'Company', required=True)
2020-04-28 23:08:35 +02:00
employees = fields.Many2Many('company.employee', None, None, 'Employees')
2020-04-28 01:20:57 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
2021-04-09 19:32:25 +02:00
@staticmethod
def default_fiscalyear():
FiscalYear = Pool().get('account.fiscalyear')
return FiscalYear.find(
Transaction().context.get('company'), exception=False)
2020-04-28 01:20:57 +02:00
class IncomeWithholdings(Wizard):
'Income Withholding'
__name__ = 'staff.payroll.income_withholdings'
start = StateView('staff.payroll.income_withholdings.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.income_withholdings_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-04-28 01:20:57 +02:00
print_ = StateReport('staff.payroll.income_withholdings_report')
def do_print_(self, action):
2020-04-28 23:08:35 +02:00
employees = []
if self.start.employees:
employees = [e.id for e in self.start.employees]
2020-04-28 01:20:57 +02:00
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-04-28 01:20:57 +02:00
'company': self.start.company.id,
2021-04-09 19:32:25 +02:00
'start_period': self.start.fiscalyear.start_date,
'end_period': self.start.fiscalyear.end_date,
2020-04-28 23:08:35 +02:00
'employees': employees,
2020-04-28 01:20:57 +02:00
}
return action, data
def transition_print_(self):
return 'end'
class IncomeWithholdingsReport(Exo2276Report):
'Income Withholding Report'
__name__ = 'staff.payroll.income_withholdings_report'
2020-04-28 17:33:54 +02:00
@classmethod
def get_domain_payroll(cls, data=None):
2021-08-14 16:56:11 +02:00
dom = super(IncomeWithholdingsReport, cls).get_domain_payroll(data)
2020-04-28 23:08:35 +02:00
if data['employees']:
2021-08-14 16:56:11 +02:00
dom.append(('employee', 'in', data['employees']))
return dom
2020-05-07 22:16:28 +02:00
class ExportMovesReport(PayrollReport):
__name__ = 'staff_payroll.export_moves.report'
@classmethod
2021-06-09 18:07:42 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-05-07 22:16:28 +02:00
config = Pool().get('staff.configuration')(1)
prefix = ''
if config:
prefix = config.staff_payroll_sequence.prefix
# ids = Transaction().context['active_ids']
# moves = []
# for payroll in Payroll.browse(ids):
# if not payroll.move:
# continue
# moves.append(payroll.move)
# print(payroll.number)
report_context['prefix'] = prefix
return report_context
2020-08-04 19:39:26 +02:00
class PayrollExportStart(ModelView):
'Export Payroll Start'
__name__ = 'staff.payroll.export.start'
company = fields.Many2One('company.company', 'Company', required=True)
2020-11-04 02:16:18 +01:00
start_period = fields.Many2One('staff.payroll.period', 'Start Period',
2021-11-10 18:56:11 +01:00
required=True)
2020-11-04 02:16:18 +01:00
end_period = fields.Many2One('staff.payroll.period', 'End Period',
2021-11-10 18:56:11 +01:00
required=True)
2020-08-04 19:39:26 +02:00
department = fields.Many2One('company.department', 'Department',
2021-11-10 18:56:11 +01:00
required=False, depends=['employee'])
2020-08-04 19:39:26 +02:00
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollExport(Wizard):
'Payroll Export'
__name__ = 'staff.payroll.export'
start = StateView('staff.payroll.export.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.payroll_export_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
2020-08-04 19:39:26 +02:00
print_ = StateReport('staff.payroll.export_report')
def do_print_(self, action):
department_id = self.start.department.id \
if self.start.department else None
data = {
2021-06-09 18:07:42 +02:00
'ids': [],
2020-08-04 19:39:26 +02:00
'company': self.start.company.id,
'start_period': self.start.start_period.id,
'end_period': self.start.end_period.id,
'department_id': department_id,
}
return action, data
def transition_print_(self):
return 'end'
class PayrollExportReport(Report):
__name__ = 'staff.payroll.export_report'
@classmethod
def get_domain_payroll(cls, data=None):
dom_payroll = []
return dom_payroll
@classmethod
2021-06-09 18:07:42 +02:00
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
2020-08-04 19:39:26 +02:00
pool = Pool()
company = pool.get('company.company')(data['company'])
Payroll = pool.get('staff.payroll')
Period = pool.get('staff.payroll.period')
dom_payroll = cls.get_domain_payroll()
start_period, = Period.search([('id', '=', data['start_period'])])
end_period, = Period.search([('id', '=', data['end_period'])])
dom_payroll.append([
('period.start', '>=', start_period.start),
('period.end', '<=', end_period.end),
('move', '!=', None)
])
if data['department_id'] not in (None, ''):
dom_payroll.append([
('employee.department', '=', data['department_id'])
])
payrolls = Payroll.search(dom_payroll, order=[('period.name', 'ASC')])
records = {}
for payroll in payrolls:
2020-08-05 18:10:16 +02:00
employee = payroll.employee
""" extract debit account and party mandatory_wages"""
2020-08-06 02:46:26 +02:00
accdb_party = {mw.wage_type.debit_account.id: mw.party
for mw in employee.mandatory_wages
2021-11-10 18:56:11 +01:00
if mw.wage_type.debit_account and mw.party}
2020-08-04 19:39:26 +02:00
move = payroll.move
2020-08-06 02:46:26 +02:00
accountdb_ids = accdb_party.keys()
2020-08-04 19:39:26 +02:00
for line in move.lines:
2020-08-05 18:10:16 +02:00
"""Check account code in dict account debit and party"""
2020-08-06 02:46:26 +02:00
if not line.party:
continue
2020-08-06 03:21:33 +02:00
line_ = {
'date': line.move.date,
'code': '---',
'party': employee.party.id_number,
'description': line.description,
'department': employee.department.name if employee.department else '---',
2020-08-06 03:25:50 +02:00
'amount': line.debit or line.credit,
2020-08-06 03:21:33 +02:00
'type': 'D',
}
2020-08-06 02:46:26 +02:00
if line.debit > 0:
if line.account.id in accountdb_ids:
2020-08-06 03:21:33 +02:00
id_number = accdb_party[line.account.id].id_number
2020-08-06 02:56:58 +02:00
else:
2020-08-06 03:21:33 +02:00
id_number = None
2020-08-06 02:46:26 +02:00
2020-08-05 18:10:16 +02:00
if id_number in ENTITY_ACCOUNTS.keys():
2020-08-06 03:21:33 +02:00
line_['code'] = ENTITY_ACCOUNTS[id_number][1]
else:
line_['code'] = line.account.code
2020-08-06 03:40:22 +02:00
2020-08-06 02:46:26 +02:00
else:
2020-08-06 03:21:33 +02:00
line_['type'] = 'C'
2020-08-06 02:54:17 +02:00
id_number = line.party.id_number
2020-08-06 02:46:26 +02:00
if id_number in ENTITY_ACCOUNTS.keys():
2020-08-06 03:21:33 +02:00
line_['code'] = ENTITY_ACCOUNTS[id_number][0]
else:
line_['code'] = line.account.code
2020-08-06 02:46:26 +02:00
if line.account.code not in records.keys():
records[line.account.code] = {
2020-08-04 19:39:26 +02:00
'name': line.account.name,
'lines': []
}
2020-08-06 03:21:33 +02:00
records[line.account.code]['lines'].append(line_)
2020-08-04 19:39:26 +02:00
report_context['records'] = records
report_context['start_date'] = start_period.name
report_context['end_date'] = end_period.name
report_context['company'] = company.party.name
return report_context
2021-02-20 00:45:08 +01:00
2021-02-23 06:38:03 +01:00
class PayrollsMultiPaymentStart(ModelView):
'Payroll Multi Payment Start'
__name__ = 'staff.payroll_multi_payment.start'
period = fields.Many2One('staff.payroll.period', 'Period',
2021-11-10 18:56:11 +01:00
required=True, domain=[('state', '=', 'open')])
2021-02-23 06:38:03 +01:00
payment_mode = fields.Many2One('account.voucher.paymode', 'Payment Mode',
2021-11-10 18:56:11 +01:00
required=True)
2021-02-23 06:38:03 +01:00
company = fields.Many2One('company.company', 'Company',
2021-11-10 18:56:11 +01:00
required=True)
2021-02-23 06:38:03 +01:00
department = fields.Many2One('company.department', 'Department')
wage_type = fields.Many2One('staff.wage_type', 'Wage Type', domain=[
('definition', '=', 'payment'),
('type_concept', '=', 'salary'),
], required=True)
description = fields.Char('Description')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollsMultiPayment(Wizard):
'Payrolls MultiPayment'
__name__ = 'staff.payroll_multi_payment'
start = StateView('staff.payroll_multi_payment.start',
2021-11-10 18:56:11 +01:00
'staff_payroll_co.payroll_multi_payment_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Accept', 'open_', 'tryton-ok', default=True),
])
2021-02-23 06:38:03 +01:00
open_ = StateTransition()
def transition_open_(self):
pool = Pool()
Payroll = pool.get('staff.payroll')
Voucher = pool.get('account.voucher')
MoveLine = pool.get('account.move.line')
dom_pay = [
('period', '=', self.start.period.id),
('state', '=', 'posted'),
('move', '!=', None),
]
if self.start.department:
dom_pay.append(
2023-09-22 22:06:05 +02:00
('department', '=', self.start.department.id)
2021-02-23 06:38:03 +01:00
)
payrolls = Payroll.search(dom_pay)
moves_payroll_ids = [p.move.id for p in payrolls]
lines = MoveLine.search([
('account', '=', self.start.wage_type.credit_account.id),
('move', 'in', moves_payroll_ids),
('reconciliation', '=', None),
('party', '!=', None),
('credit', '>', 0),
])
line_to_create = []
for line in lines:
line_to_create.append({
'amount': line.credit,
'account': line.account.id,
'party': line.party.id,
'move_line': line.id,
'detail': line.move.origin.number,
'amount_original': line.credit,
})
2021-11-10 18:56:11 +01:00
account_id = Voucher.get_account(
'multipayment', self.start.payment_mode)
2021-02-23 06:38:03 +01:00
journal_id = self.start.payment_mode.journal.id
voucher, = Voucher.create([{
'payment_mode': self.start.payment_mode.id,
'date': self.start.period.end,
'voucher_type': 'multipayment',
'lines': [('create', line_to_create)],
'journal': journal_id,
'state': 'draft',
'account': account_id,
'description': self.start.description,
}])
amount_to_pay = voucher._get_amount_to_pay()
voucher.amount_to_pay = amount_to_pay
voucher.save()
return 'end'
# def transition_create_(self):
# return 'open_'
2023-03-25 18:56:59 +01:00
class PayrollTask(ModelSQL, ModelView):
"Payroll Task"
__name__ = 'payroll.task'
name = fields.Char('Name')
data = fields.Dict(None, "Data", readonly=True)
payroll = fields.Integer("Number Payrolls", readonly=True)
payroll_processed = fields.Integer("Payrolls processed", readonly=True)
percentage = fields.Function(fields.Float("Percentage", digits=(16, 2)), 'get_percentage')
state = fields.Selection([
('active', 'Active'),
('done', 'Done')
], "State")
data_text = fields.Function(fields.Text('Data Text'), 'get_data')
def get_percentage(self, name):
if self.payroll and self.payroll_processed:
return (self.payroll_processed/self.payroll)
def get_data(self, name):
if self.data:
return str(self.data)
@classmethod
def create_payroll_async(cls):
2023-03-27 16:18:17 +02:00
tasks = cls.search([('state', '=', 'active')], limit=1)
if not tasks:
return
task = tasks[0]
2023-03-25 18:56:59 +01:00
Payroll = Pool().get('staff.payroll')
Period = Pool().get('staff.payroll.period')
2023-04-29 18:15:18 +02:00
Configuration = Pool().get('staff.configuration')
config = Configuration(1)
2023-03-25 18:56:59 +01:00
transaction = Transaction()
with transaction.set_user(task.data['user']), \
transaction.set_context(
task.data['context'], _skip_warnings=True):
period_id = task.data['args']['period']
period = Period(period_id)
start, end = attrgetter('start', 'end')(period)
# Remove employees with payroll this period
payrolls_period = Payroll.search([
('period', '=', period_id),
])
2023-06-09 23:46:10 +02:00
cache_wage_dict = Payroll.create_cache_wage_types()
2023-03-25 18:56:59 +01:00
employee_payroll = [p.employee.id for p in payrolls_period]
employees = task.data['args']['employees']
search_contract_on_period = Payroll.search_contract_on_period
payroll_to_create = []
cont = 0
for employee in employees:
if cont == 5:
break
employees.remove(employee)
cont += 1
if employee in employee_payroll:
continue
contract = search_contract_on_period(employee, start, end)
if not contract:
continue
values = task.get_values(contract, start, end)
payroll_to_create.append(values)
data = task.data
data['args']['employees'] = employees
task.data['args']['employees'] = employees
task.payroll_processed = task.payroll - len(employees)
if len(employees) <= 0:
task.state = 'done'
task.data = data
task.save()
wages = None
if len(task.data['args']['wage_types']) > 0:
WageType = Pool().get('staff.wage_type')
wage_types = WageType.browse(task.data['args']['wage_types'])
wages = [
2023-06-09 23:46:10 +02:00
(cache_wage_dict[wage_type.id], None, None) for wage_type in wage_types
2023-03-25 18:56:59 +01:00
]
2023-06-09 23:46:10 +02:00
2023-03-25 18:56:59 +01:00
PayrollCreate = Payroll.create
if payroll_to_create:
payrolls = PayrollCreate(payroll_to_create)
for payroll in payrolls:
try:
2023-06-09 23:46:10 +02:00
payroll.set_preliquidation(config, {}, None, cache_wage_dict)
2023-03-25 18:56:59 +01:00
if wages:
2023-06-09 23:46:10 +02:00
payroll._create_payroll_lines(config, wages, None, {}, cache_wage_dict)
2023-03-25 18:56:59 +01:00
except Exception as e:
print('Fallo al crear nomina : ', e)
2023-06-09 23:46:10 +02:00
traceback.print_exc()
2023-03-25 18:56:59 +01:00
def get_values(self, contract, start_date, end_date):
employee = contract.employee
ct_start_date = contract.start_date
ct_end_date = contract.finished_date
if ct_start_date and ct_start_date >= start_date and \
ct_start_date <= end_date:
start_date = ct_start_date
if ct_end_date and ct_end_date >= start_date and \
ct_end_date <= end_date:
end_date = ct_end_date
values = {
'employee': employee.id,
'period': self.data['args']['period'],
'start': start_date,
'end': end_date,
'description': self.data['args']['description'],
'date_effective': end_date,
'contract': contract.id,
'department': employee.department.id if employee.department else None,
}
if self.data.get('start_extras') and self.data.get('end_extras'):
values['start_extras'] = self.data.get('start_extras')
values['end_extras'] = self.data.get('end_extras')
2023-04-20 15:15:29 +02:00
if hasattr(contract, 'project') and contract.project:
values['project'] = contract.project.id
2023-03-25 18:56:59 +01:00
return values