trytonpsk-staff_payroll_co/payroll.py

1957 lines
76 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
from decimal import Decimal
2020-06-13 01:50:41 +02:00
from datetime import date
from trytond.model import ModelView, fields, Workflow
2020-04-16 00:38:42 +02:00
from trytond.pool import Pool, PoolMeta
from trytond.report import Report
from trytond.wizard import Wizard, StateView, Button, StateAction, StateReport, StateTransition
from trytond.transaction import Transaction
from trytond.pyson import Eval
from trytond.modules.staff_payroll import Period
import math
2020-05-07 22:16:28 +02:00
from trytond.modules.staff_payroll import PayrollReport
2020-04-16 00:38:42 +02:00
2020-06-13 01:50:41 +02:00
__all__ = [
'Payroll', 'PayrollGlobalStart', 'PayrollGlobal',
'PayrollGlobalReport', 'PayrollPaymentReport', 'PayrollPayment',
'PayrollPaymentStart', 'PayrollPaycheckStart', 'PayrollPaycheckReport',
'PayrollPaycheck', 'PayrollSheetReport', 'PayrollSheet', 'PayrollLine',
'PayrollSheetStart', 'PayrollGroupStart', 'PayrollGroup',
'PayrollByPeriodDynamic', 'OpenPayrollByPeriod', 'OpenPayrollByPeriodStart',
'FixPayrollStart', 'FixPayroll', 'Exo2276Start', 'Exo2276',
'Exo2276Report', 'IncomeWithholdings', 'ExportMovesReport',
2020-08-04 19:39:26 +02:00
'IncomeWithholdingsStart', 'IncomeWithholdingsReport',
'PayrollExportStart', 'PayrollExport', 'PayrollExportReport'
2020-06-13 01:50:41 +02:00
]
2020-04-16 00:38:42 +02:00
2020-05-27 21:54:36 +02:00
STATES = {'readonly': (Eval('state') != 'draft')}
2020-04-16 00:38:42 +02:00
_ZERO = Decimal('0.0')
2020-05-27 21:54:36 +02:00
PAYMENTS = ['salary', 'bonus', 'reco', 'recf', 'hedo', 'heno',
'dom', 'hedf', 'henf']
SOCIAL_SEGURITY = ['risk', 'health', 'retirement',
'box_family', 'sena', 'icbf']
2020-08-05 18:10:16 +02:00
ENTITY_ACCOUNTS = {
'806008394': (23700501, 72056901),
'860066942': (23700502, 72056902),
'890300625': (23700503, 72056903),
'900226715': (23700504, 72056904),
'830003564': (23700505, 72056905),
'800251440': (23700506, 72056906),
'800088702': (23700507, 72056907),
'901097473': (23700508, 72056908),
'900156264': (23700509, 72056909),
'800130907': (23700510, 72056910),
'860045904': (23700511, 72056911),
'890102044': (23700514, 72056914),
'900336004': (23803001, 72057001),
'800149496': (23803002, 72057002),
'800144331': (23803004, 72057004),
'800229739': (23803005, 72057005),
'860013570': (2370100101, 72057201),
'890102002': (2370100102, 72057202),
'890903790': (23700601, 72057101),
'830113831': (23700501, 72056901),
'804002105': (23700513, 72056913),
'900298372': (23700513, 72056913),
'830009783': (23700516, 72056916),
'800148514': (23803003, 72057003),
'891780093': (2370100103, 72057203)
}
2020-04-16 00:38:42 +02:00
class PayrollLine(metaclass=PoolMeta):
__name__ = "staff.payroll.line"
def get_amount(self, name):
amount = super(PayrollLine, self).get_amount(name)
if amount and self.wage_type and self.wage_type.round_amounts:
if self.wage_type.round_amounts == 'above_amount':
amount = math.ceil(float(amount) / 100.0) * 100
if self.wage_type.round_amounts == 'under_amount':
amount = math.floor(float(amount) / 100.0) * 100
if self.wage_type.round_amounts == 'automatic':
amount = round(amount, -2)
return amount
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'),
'get_non_fiscal_amount')
retirement_amount = fields.Function(fields.Numeric('AFP Amount'),
'get_non_fiscal_amount')
risk_amount = fields.Function(fields.Numeric('ARL Amount'),
'get_non_fiscal_amount')
box_family_amount = fields.Function(fields.Numeric('Box Amount'),
'get_non_fiscal_amount')
absenteeism_days = fields.Integer("Absenteeism Days",
states=STATES)
department = fields.Many2One('company.department', 'Department',
required=False, depends=['employee'])
@classmethod
def __setup__(cls):
super(Payroll, cls).__setup__()
table = cls.__table__()
cls._error_messages.update({
'period_without_contract': ('The period selected without contract'
2020-08-06 00:08:20 +02:00
' for the employee!'),
'error_report': ('Error %s !'),
2020-04-16 00:38:42 +02:00
}
)
# cls._sql_constraints += [
# ('employee_period_contract_uniq', Unique(table, table.contract, table.employee, table.period, table.project),
# 'Already exists one payroll for this employee with this contract in this period!'),
# ]
@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
2020-06-13 01:50:41 +02:00
@fields.depends('period', 'employee', 'start', 'end', 'contract',
2020-04-16 00:38:42 +02:00
'description', 'date_effective', 'last_payroll')
def on_change_period(self):
if not self.period:
return
self.date_effective = self.period.end
self.on_change_employee()
#Search last contract
contract = self.search_contract_on_period(self.employee, self.period)
start_date = None
end_date = None
self.contract = contract
contract_end_date = None
if contract:
if not contract.end_date:
end_date = self.period.end
if self.period.start >= contract.start_date:
start_date = self.period.start
elif contract.start_date >= self.period.start and \
contract.start_date <= self.period.end:
start_date = contract.start_date
else:
contract_end_date = contract.finished_date if contract.finished_date else contract.end_date
if contract.start_date <= self.period.start and \
contract_end_date >= self.period.end:
start_date = self.period.start
end_date = self.period.end
elif contract.start_date >= self.period.start and \
contract_end_date <= self.period.end:
start_date = contract.start_date
end_date = contract_end_date
elif contract.start_date >= self.period.start and \
contract.start_date <= self.period.end and \
contract_end_date >= self.period.end:
start_date = contract.start_date
end_date = self.period.end
elif contract.start_date <= self.period.start and \
contract_end_date >= self.period.start and \
contract_end_date <= self.period.end:
start_date = self.period.start
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
self.raise_user_error('period_without_contract')
if not self.description and self.period:
self.description = self.period.description
def _get_line_quantity(self, quantity_days, wage, extras, discount):
quantity_days = self.get_days(self.start, self.end, wage)
quantity = super(Payroll, self)._get_line_quantity(quantity_days, wage, extras, discount)
return quantity
2020-06-13 01:57:05 +02:00
@fields.depends('start', 'end', 'employee', 'period', 'contract')
2020-04-16 00:38:42 +02:00
def get_days(self, start, end, wage=None):
adjust = 1
adjust_days_worked = False
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
if (adjust_days_worked) or (wage and wage.adjust_days_worked):
if end.day == 31 or (end.month == 2 and \
(end.day == 28 or end.day == 29)):
adjust = 31 - end.day
quantity_days = (end - start).days + adjust
if quantity_days < 0:
quantity_days = 0
if start == end:
quantity_days = 1
if Transaction().context.get('absenteeism_days'):
quantity_days = quantity_days - Transaction().context.get('absenteeism_days')
return quantity_days
def get_salary_full(self, wage):
res = super(Payroll, self).get_salary_full(wage)
salary_pae = _ZERO
salary_full = res['salary']
"""
if wage.type_concept in ('health', 'retirement', 'fsp', 'tax', 'box_family') \
and self.kind == 'special':
# PAE: Payroll Accrual Entry
salary_pae = self._compute_apply_special_salary()
if self.last_payroll:
payrolls = self.search([
('employee', '=', self.employee.id),
('start', '>=', date(self.start.year, 1, 1)),
('start', '<=', date(self.start.year, 12, 31)),
])
hoard_holidays = self._compute_hoard(
[p.id for p in payrolls],
['holidays']
)
salary_pae += hoard_holidays
"""
if wage.month_application:
if self._is_last_payroll_month():
salary_full_month = self.search_salary_month(wage)
salary_full = salary_full_month + salary_pae
elif self.last_payroll:
salary_full = salary_full + salary_pae
else:
salary_full = _ZERO
else:
salary_full += salary_pae
salary_full = self.validate_minimal_amount(wage, salary_full)
return {'salary': salary_full}
def validate_minimal_amount(self, wage, amount):
if wage.minimal_amount:
if amount < wage.minimal_amount:
return 0
return amount
def _is_last_payroll_month(self):
_, end_day = calendar.monthrange(self.start.year, self.start.month)
last_day = date(self.start.year, self.start.month, end_day)
if last_day == self.period.end:
return True
return False
def _compute_apply_special_salary(self):
MandatoryWages = Pool().get('staff.payroll.mandatory_wage')
mandatory_wages = MandatoryWages.search([
('employee', '=', self.employee.id),
('wage_type.apply_special_salary', '=', True),
])
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':
if self.start < date(self.start.year, 7, 1):
dom_payroll.extend([
('start', '>=', date(self.start.year, 1, 1)),
('start', '<=', date(self.start.year, 6, 30)),
])
else:
dom_payroll.extend([
('start', '>=', date(self.start.year, 7, 1)),
('start', '<=', date(self.start.year, 12, 31)),
])
else:
dom_payroll.extend([
('start', '>=', date(self.start.year, 1, 1)),
('start', '<=', date(self.start.year, 12, 31)),
])
payrolls = self.search(dom_payroll)
worked_days = Decimal(sum([p.worked_days for p in payrolls]))
precomputed_salary = {'salary': worked_days * self.employee.salary_day}
salary_plus += wage.compute_formula(
'unit_price_formula',
precomputed_salary)
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
def search_salary_month(self, wage):
res = _ZERO
Configuration = Pool().get('staff.configuration')
config = Configuration(1)
payrolls = self._get_payrolls_month()
# payrolls_contract = {}
# contract_id_payrolls = [payroll.contract.id for payroll in payrolls]
2020-05-28 02:41:17 +02:00
if payrolls:
2020-04-16 00:38:42 +02:00
# num_subperiod = 30 / config.default_liquidation_period
# if len(payrolls) >= num_subperiod:
for payroll in payrolls:
res += payroll.compute_salary_full(wage)
2020-05-27 21:54:36 +02:00
# if payroll.contract:
# payrolls_contract[payroll.contract.id] = res
return res
def get_line(self, wage, qty, unit_value, party=None):
res = super(Payroll, self).get_line(wage, qty, unit_value, party)
2020-05-28 02:41:17 +02:00
# res['unit_value'] = self._validate_amount_wage(wage, res['unit_value'])
2020-04-16 00:38:42 +02:00
return res
2020-05-28 02:41:17 +02:00
# def _validate_amount_wage(self, wage, amount):
# config = Pool().get('staff.configuration')(1)
# salary_in_date = self.contract.get_salary_in_date(self.end)
# if config and config.minimum_salary and \
# wage.type_concept == 'transport' and \
# salary_in_date >= (config.minimum_salary * 2):
# amount = 0
# if wage.type_concept in SOCIAL_SEGURITY and \
# amount < wage.minimal_unit_price:
# amount = wage.minimal_unit_price
# return amount
2020-05-27 21:54:36 +02:00
2020-04-16 00:38:42 +02:00
def set_preliquidation(self, extras, discounts=None):
discounts = self.set_events()
ctx = {
'absenteeism_days': self.absenteeism_days
}
with Transaction().set_context(ctx):
super(Payroll, self).set_preliquidation(extras, discounts)
self.save()
self.update_wage_no_salary()
def set_events(self):
pool = Pool()
Event = pool.get('staff.event')
PayrollLine = pool.get('staff.payroll.line')
events = Event.search([
('employee', '=', self.employee),
('start_date', '<=', self.end),
('end_date', '>=', self.start),
('state', '=', 'done'),
])
days = 0
events_lines_to_create = []
absenteeism_days = 0
discounts = {}
for event in events:
if not event.category.payroll_effect:
continue
if event.absenteeism:
absenteeism_days += event.days
if event.quantity_pay:
qty_pay = event.quantity_pay
wage = event.category.wage_type
salary_args = self.get_salary_full(wage)
2020-05-14 01:21:22 +02:00
if event.amount_to_pay:
unit_value = Decimal(event.amount_to_pay)
else:
unit_value = wage.compute_unit_price(salary_args)
2020-04-16 00:38:42 +02:00
res = self.get_line(wage, qty_pay, unit_value)
events_lines_to_create.append(res)
if event.category.wage_type_discount and event.quantity_discount:
2020-05-24 01:28:22 +02:00
id_wt_event = event.category.wage_type_discount.id
if id_wt_event not in discounts.keys():
discounts[id_wt_event] = 0
discounts[id_wt_event] += event.quantity_discount
2020-04-16 00:38:42 +02:00
self.absenteeism_days = absenteeism_days
self.save()
PayrollLine.create(events_lines_to_create)
return discounts
def update_wage_no_salary(self):
PayrollLine = Pool().get('staff.payroll.line')
for line in self.lines:
if line.wage_type.month_application:
salary_args = self.get_salary_full(line.wage_type)
unit_value = line.wage_type.compute_unit_price(salary_args)
if line.wage_type.type_concept == 'interest':
unit_value = self._compute_interest(line.wage_type)
else:
continue
PayrollLine.write([line], {'unit_value': unit_value})
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(
('start', '>=', contract.start_date),
)
if contract.end_date:
dom_payroll.append(
('start', '<=', contract.end_date)
)
payrolls = self.search(dom_payroll)
return [p.id for p in payrolls]
def _compute_interest(self, wage_type, limit_date=False):
start = date(self.start.year, 1, 1)
end = date(self.start.year, 12, 31)
concepts_salary_ids = [wt.id for wt in wage_type.concepts_salary]
if self.contract.start_date > start:
start = self.contract.start_date
dom_payrolls = [
('start', '>=', start),
('employee', '=', self.employee.id),
]
if limit_date:
dom_payrolls.append(('start', '<=', limit_date))
else:
dom_payrolls.append(('start', '<=', end))
payrolls = self.search(dom_payrolls)
payrolls_ids = [p.id for p in payrolls]
time_contracting = sum([p.worked_days for p in payrolls])
salary_hoard = float(self._compute_hoard(
payrolls_ids,
concepts_salary_ids
))
total_interest = salary_hoard * time_contracting * 0.01 / 360
if self.id in payrolls_ids:
payrolls_ids.remove(self.id)
interest_hoard = float(self._compute_hoard(
payrolls_ids,
[wage_type.id]
))
interest = total_interest - interest_hoard
if interest < _ZERO:
interest = _ZERO
return self.currency.round(Decimal(interest))
def _compute_hoard(self, payrolls_ids, wages_ids):
if not payrolls_ids:
return _ZERO
PayrollLine = Pool().get('staff.payroll.line')
lines = PayrollLine.search([
('wage_type', 'in', wages_ids),
('payroll', 'in', payrolls_ids),
])
return sum([l.amount for l in lines if not l.reconciled])
def recompute_lines(self):
super(Payroll, self).recompute_lines()
self.update_wage_no_salary()
UvtWithholding = Pool().get('staff.payroll.uvt_withholding')
line_tax = None
deductions = _ZERO
for line in self.lines:
if line.wage_type.definition == 'deduction':
deductions += line.amount
if line.wage_type.type_concept == 'tax':
line_tax = line
if line_tax:
salary_full = self.get_salary_full(line_tax.wage_type)
deductions_month = self.get_deductions_month()
salary_full = salary_full['salary']
payrolls_ids = self._get_payrolls_contract()
if self.last_payroll:
hoard_holidays = self._compute_hoard(
payrolls_ids,
['holidays']
)
salary_full += hoard_holidays
base_salary_withholding = salary_full - deductions_month
unit_value = UvtWithholding.compute_withholding(
base_salary_withholding)
unit_value = self.currency.round(Decimal(unit_value))
line_tax.write([line_tax], {'unit_value': unit_value})
def get_non_fiscal_amount(self, name=None):
res = _ZERO
concept = name[:-7]
for line in self.lines:
if line.wage_type.type_concept == concept:
res += line.amount
return res
def get_deductions_month(self):
payrolls = self._get_payrolls_month()
sum_deductions = _ZERO
for payroll in payrolls:
for line in payroll.lines:
if line.wage_type.definition == 'deduction':
sum_deductions += line.amount
return sum_deductions
@fields.depends('lines')
def on_change_with_ibc(self, name=None):
concepts = ['salary', 'transport', 'extras', 'bonus', 'commission', 'bonus_service', 'advance']
res = _ZERO
for l in self.lines:
if l.wage_type and l.wage_type.type_concept in concepts and \
l.wage_type.definition == 'payment' and \
l.wage_type.salary_constitute == True:
res += l.amount
return res
@classmethod
@Workflow.transition('draft')
def draft(cls, records):
Move = Pool().get('account.move')
for payroll in records:
if payroll.move:
Move.draft([payroll.move.id])
#Move.delete([payroll.move])
class PayrollGlobalStart(ModelView):
'Payroll Global Start'
__name__ = 'staff.payroll_global.start'
start_period = fields.Many2One('staff.payroll.period',
'Start Period', required=True)
end_period = fields.Many2One('staff.payroll.period', 'End Period',
depends=['start_period'])
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',
'staff_payroll_co.payroll_global_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
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 = {
'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
def get_context(cls, records, data):
report_context = super(PayrollGlobalReport, cls).get_context(records, data)
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Payroll = pool.get('staff.payroll')
PayrollLine = pool.get('staff.payroll.line')
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),
])
else:
dom_periods.append(
('id', '=', start_period.id)
)
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(
['AND',
['OR', [
('employee.department', '=', data['department']),
('department', '=', None),
],[
('department', '=', data['department']),
],
]]
)
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 = {}
payments = ['salary', 'transport', 'extras', 'food', 'bonus']
deductions = ['health', 'retirement', 'tax', 'syndicate', 'fsp', 'acquired_product']
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
# parties[employee_id]['employee_salary'] = payroll.employee.salary
parties[employee_id]['basic_salary'] = payroll.contract.get_salary_in_date(payroll.end)
# payroll_lines = PayrollLine.search([
# ('payroll', '=', payroll.id),
# ('wage_type.type_concept', '=', 'salary'),
# ])
# if payroll_lines:
# basic_salary = math.ceil(payroll_lines[0].unit_value * 240)
# parties[employee_id]['basic_salary'] = cls.round_basic_salary(basic_salary)
# else:
# parties[employee_id]['basic_salary'] = payroll.contract.salary
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.type_concept == 'commission':
concept = 'commission'
elif line.wage_type.type_concept == 'loan':
concept = 'loan'
elif line.wage_type.type_concept == 'advance':
concept = 'advance'
elif line.wage_type.definition == 'payment' and line.wage_type.receipt:
concept = 'others_payments'
elif line.wage_type.definition == 'deduction' or \
line.wage_type.definition == 'discount' and \
line.wage_type.receipt:
concept = 'others_deductions'
else:
concept = line.wage_type.type_concept
parties[employee_id][concept] += line.amount
parties[employee_id]['worked_days'] += payroll.worked_days
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)
employee_dict = {e['employee']: e for e in parties.values()}
report_context['records'] = sorted(employee_dict.items(), key=lambda t: t[0])
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')
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']
lines_fields = dict(WageType.type_concept.selection).keys()
if '' in lines_fields:
lines_fields.remove('')
default_values = {}
for field in fields_string:
default_values.setdefault(field, None)
for field in fields_numeric:
default_values.setdefault(field, Decimal(0))
for field in lines_fields:
default_values.setdefault(field, Decimal(0))
return default_values
# @classmethod
# def round_basic_salary(cls, salary):
# basic_salary = math.ceil(salary)
# string_basic_salary = str(basic_salary)[len(str(basic_salary))-1]
# if string_basic_salary == '9':
# return basic_salary + 1
# elif string_basic_salary == '1':
# return basic_salary - 1
# else:
# return basic_salary
class PayrollPaymentStart(ModelView):
'Payroll Payment Start'
__name__ = 'staff.payroll_payment.start'
period = fields.Many2One('staff.payroll.period', 'Period',
required=True)
company = fields.Many2One('company.company', 'Company',
required=True)
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',
'staff_payroll_co.payroll_payment_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
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 = {
'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
def get_context(cls, records, data):
report_context = super(PayrollPaymentReport, cls).get_context(records, data)
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
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
values['type_account'] = payroll.employee.party.bank_account_type
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,
'Periods', add_remove=[], required=True)
company = fields.Many2One('company.company', 'Company', required=True)
department = fields.Many2One('company.department', 'Department')
values_without_move = fields.Boolean('Values Without Move')
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollPaycheck(Wizard):
'Payroll Paycheck'
__name__ = 'staff.payroll.paycheck'
start = StateView('staff.payroll_paycheck.start',
'staff_payroll_co.payroll_paycheck_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
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]
data = {
'company': self.start.company.id,
'periods': periods,
'department': department_id,
'values_without_move': self.start.values_without_move,
}
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
def get_context(cls, records, data):
report_context = super(PayrollPaycheckReport, cls).get_context(records, data)
pool = Pool()
Payroll = pool.get('staff.payroll')
Period = pool.get('staff.payroll.period')
Company = pool.get('company.company')
dom_payroll = cls.get_domain_payroll(data)
payrolls = Payroll.search(dom_payroll)
res = {}
today = date.today()
periods = [p.name for p in Period.browse(data['periods'])]
total = []
for payroll in payrolls:
employee_id = payroll.employee.id
health_amount = cls.get_amount_move(payroll, 'health')
retirement_amount = cls.get_amount_move(payroll, 'retirement')
risk_amount = cls.get_amount_move(payroll, 'risk')
box_family_amount = cls.get_amount_move(payroll, 'box_family')
if data['values_without_move']:
health_amount = cls._values_without_move(payroll, 'health')
retirement_amount = cls._values_without_move(payroll, 'retirement')
risk_amount = cls._values_without_move(payroll, 'risk')
box_family_amount = cls._values_without_move(payroll, 'box_family')
subtotal = sum([health_amount, retirement_amount, risk_amount, box_family_amount])
if employee_id not in res.keys():
eps_code = afp_code = arl_code = box_code = ''
eps_name = afp_name = arl_name = box_name = ''
if payroll.employee.party_health:
eps_code = payroll.employee.party_health.legal_code
eps_name = payroll.employee.party_health.name
if payroll.employee.party_retirement:
afp_code = payroll.employee.party_retirement.legal_code
afp_name = payroll.employee.party_retirement.name
if payroll.employee.party_risk:
arl_code = payroll.employee.party_risk.legal_code
arl_name = payroll.employee.party_risk.name
if payroll.employee.party_box_family:
box_code = payroll.employee.party_box_family.legal_code
box_name = payroll.employee.party_box_family.name
res[employee_id] = {
'type_contributor': '01',
'type_id': 'CC',
'id_number': payroll.employee.party.id_number,
'employee': payroll.employee.party.name,
'type_affiliation': 'D',
'year': payroll.date_effective.year,
'date': today,
'eps_code': eps_code,
'eps_name': eps_name,
'afp_code': afp_code,
'afp_name': afp_name,
'arl_code': arl_code,
'arl_name': arl_name,
'box_code': box_code,
'box_name': box_name,
'ibc': payroll.ibc,
'eps_amount': health_amount,
'afp_amount': retirement_amount,
'arl_amount': risk_amount,
'box_amount': box_family_amount,
'subtotal': subtotal
}
else:
res[employee_id]['eps_amount'] += health_amount
res[employee_id]['afp_amount'] += retirement_amount
res[employee_id]['arl_amount'] += risk_amount
res[employee_id]['box_amount'] += box_family_amount
res[employee_id]['ibc'] += payroll.ibc
res[employee_id]['subtotal'] += subtotal
total.append(subtotal)
report_context['total'] = sum(total)
report_context['records'] = res.values()
report_context['periods'] = periods
report_context['company'] = Company(data['company'])
return report_context
@classmethod
def _values_without_move(cls, payroll, kind):
res = 0
for line in payroll.lines:
if line.wage_type.type_concept == kind:
res += line.amount
# if kind == 'retirement':
# res += Decimal((float(line.amount) * 0.16) / 0.04)
# else:
# res += line.amount
return res
@classmethod
def get_amount_move(cls, payroll, kind):
res = _ZERO
party = None
if kind == 'health':
party = payroll.employee.party_health
elif kind == 'retirement':
party = payroll.employee.party_retirement
elif kind == 'risk':
party = payroll.employee.party_risk
else:
party = payroll.employee.party_box_family
if party and payroll.move:
for line in payroll.move.lines:
if line.party.id == party.id:
res = line.credit
break
return res
class PayrollSheetStart(ModelView):
'Payroll Sheet Start'
__name__ = 'staff.payroll.sheet.start'
periods = fields.One2Many('staff.payroll.period', None,
'Periods', add_remove=[], required=True)
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',
'staff_payroll_co.payroll_sheet_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('staff.payroll.sheet_report')
def do_print_(self, action):
periods = [p.id for p in self.start.periods]
data = {
'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):
dom_payroll = []
return dom_payroll
@classmethod
def get_context(cls, records, data):
report_context = super(PayrollSheetReport, cls).get_context(records, data)
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Payroll = pool.get('staff.payroll')
PayrollLine = pool.get('staff.payroll.line')
Period = pool.get('staff.payroll.period')
clause = []
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'])])
payrolls = Payroll.search(dom_payroll, order=[('employee.party.name', 'ASC'), ('period.name', 'ASC')])
new_objects = []
default_vals = cls.default_values()
sum_gross_payments = []
sum_total_deductions = []
sum_net_payment = []
item = 0
for payroll in payrolls:
item += 1
values = default_vals.copy()
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(
payroll.end)/30
values['salary_day'] = salary_day_in_date
values['salary_hour'] = (salary_day_in_date / 8) if salary_day_in_date else 0
# values['salary_day'] = payroll.employee.salary_day
# values['salary_hour'] = (payroll.employee.salary_day / 8)
2020-04-16 00:38:42 +02:00
values['worked_days'] = payroll.worked_days
values['gross_payment'] = payroll.gross_payments
# 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
values.update(cls._prepare_lines(payroll, values))
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
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):
fields_no_amount = [
'item',
'employee',
'id_number',
'position',
'legal_salary',
'salary_day',
'salary_hour',
'worked_days',
'period',
2020-05-24 01:28:22 +02:00
'department',
2020-04-16 00:38:42 +02:00
]
fields_amount = [
'salary',
'reco',
'recf',
'hedo',
'heno',
'dom',
'hedf',
'henf',
'cost_reco',
'cost_recf',
'cost_hedo',
'cost_heno',
'cost_dom',
'cost_hedf',
'cost_henf',
'bonus',
'total_extras',
'gross_payment',
'health',
'retirement',
'food',
'transport',
'fsp',
'retefuente',
'other_deduction',
'total_deduction',
'ibc',
'net_payment',
'box_family',
'box_family',
'unemployment',
'interest',
'holidays',
'bonus_service',
'discount',
'other',
'total_benefit',
'risk',
2020-06-04 22:19:36 +02:00
'health_provision',
2020-04-16 00:38:42 +02:00
'retirement_provision',
'total_ssi',
'total_cost',
'sena',
'icbf',
'acquired_product',
]
if '' in fields_no_amount:
fields_no_amount.remove('')
default_values = {}
for field in fields_no_amount:
default_values.setdefault(field, None)
for field in fields_amount:
default_values.setdefault(field, Decimal(0))
return default_values
@classmethod
def _prepare_lines(cls, payroll, vals):
extras =[
'reco',
'recf',
'hedo',
'heno',
'dom',
'hedf',
'henf',
]
for line in payroll.lines:
if line.wage_type.definition == 'payment':
if line.wage_type.type_concept == 'salary':
vals['salary'] += line.amount
elif line.wage_type.type_concept == 'extras':
vals['total_extras'] += line.amount
for e in extras:
if e.upper() in line.wage_type.name:
vals[e] += line.quantity
vals['cost_' + e] += line.amount
break
elif line.wage_type.type_concept == 'risk':
vals['risk'] += line.amount
elif line.wage_type.type_concept == 'box_family':
vals['box_family'] += line.amount
elif line.wage_type.type_concept == 'unemployment':
vals['unemployment'] += line.amount
elif line.wage_type.type_concept == 'interest':
vals['interest'] += line.amount
elif line.wage_type.type_concept == 'holidays':
vals['holidays'] += line.amount
elif line.wage_type.type_concept == 'bonus':
vals['bonus'] += line.amount
elif line.wage_type.type_concept == 'bonus_service':
vals['bonus_service'] += line.amount
elif line.wage_type.type_concept == 'transport':
vals['transport'] += line.amount
elif line.wage_type.type_concept == 'food':
vals['food'] += line.amount
elif line.wage_type.type_concept == 'sena':
vals['sena'] += line.amount
elif line.wage_type.type_concept == 'icbf':
vals['icbf'] += line.amount
elif line.wage_type.type_concept == 'acquired_product':
vals['acquired_product'] += line.amount
else:
print('Warning: Line no processed... ', line.wage_type.name)
vals['other'] += line.amount
elif line.wage_type.definition == 'deduction':
vals['total_deduction'] += line.amount
if line.wage_type.type_concept == 'health':
vals['health'] += line.amount
2020-05-24 01:28:22 +02:00
vals['health_provision'] += line.get_expense_amount()
2020-04-16 00:38:42 +02:00
elif line.wage_type.type_concept == 'retirement':
vals['retirement'] += line.amount
2020-05-24 01:28:22 +02:00
vals['retirement_provision'] += line.get_expense_amount()
2020-04-16 00:38:42 +02:00
else:
if line.wage_type.type_concept == 'fsp':
vals['fsp'] += line.amount
elif line.wage_type.type_concept == 'tax':
vals['retefuente'] += line.amount
else:
vals['other_deduction'] += line.amount
else:
vals['discount'] += line.amount
print('Warning: Line no processed... ', line.wage_type.name)
vals['gross_payment'] = vals['salary'] + vals['total_extras'] + vals['transport'] + vals['food'] + vals['bonus']
vals['net_payment'] = vals['gross_payment'] - vals['total_deduction']
vals['ibc'] = vals['gross_payment']
2020-05-24 01:28:22 +02:00
vals['total_benefit'] = vals['unemployment'] + vals['interest'] + vals['holidays'] + vals['bonus_service']
2020-04-16 00:38:42 +02:00
vals['total_ssi'] = vals['retirement_provision'] + vals['risk']
vals['total_cost'] = vals['total_ssi'] + vals['box_family'] + vals['net_payment'] + vals['total_benefit']
return vals
class PayrollGroupStart:
__metaclass__ = PoolMeta
__name__ = 'staff.payroll_group.start'
department = fields.Many2One('company.department', 'Department')
class PayrollGroup:
__metaclass__ = PoolMeta
__name__ = 'staff.payroll_group'
def get_employees_dom(self, employees_w_payroll):
dom_employees = super(PayrollGroup, self).get_employees_dom(employees_w_payroll)
if self.start.department:
dom_employees.append(
('department', '=', self.start.department.id),
)
return dom_employees
# def contract_out_date(self, employee):
# if employee.contract.end_date and \
# employee.contract.end_date < self.start.period.start:
# return True
# return False
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',
required=True)
@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',
'staff_payroll_co.open_payroll_by_period_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'open_', 'tryton-ok', default=True),
])
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,
'Payrolls'), 'get_payrolls')
amount_net_payment = fields.Function(fields.Numeric('Amount Net Payment',
digits=(16, 2)), 'get_amount')
amount_total_cost = fields.Function(fields.Numeric('Amount Total Cost',
digits=(16, 2)), 'get_amount')
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 FixPayrollStart(ModelView):
'Fix Payroll Start'
__name__ = 'staff.fix_payroll.start'
start_period = fields.Many2One('staff.payroll.period',
'Start Period', required=True)
end_period = fields.Many2One('staff.payroll.period', 'End Period',
depends=['start_period'], required=True)
employee = fields.Many2One('company.employee', 'Employee')
wage_type = fields.Many2One('staff.wage_type', 'Wage Type', required=True)
project = fields.Many2One('project.work', 'Project', domain=[
('type', '=', 'project')
])
class FixPayroll(Wizard):
'Fix Payroll'
__name__ = 'staff.fix_payroll'
start = StateView('staff.fix_payroll.start',
'staff_payroll_co.fix_payroll_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Accept', 'open_', 'tryton-ok', default=True),
])
open_ = StateTransition()
def transition_open_(self):
pool = Pool()
Payroll = pool.get('staff.payroll')
Move = pool.get('account.move')
Employee = pool.get('company.employee')
dom_start = [
('end', '=', '2018-12-31'),
('project', '=', self.start.project.id)
]
if self.start.employee:
dom_start.append(('employee', '=', self.start.employee.id))
payrolls = Payroll.search(dom_start)
for payroll in payrolls:
dom_payrolls = [
('start', '>=', self.start.start_period.start),
('start', '<=', self.start.end_period.start),
('state', '=', 'posted'),
('employee', '=', payroll.employee.id),
('contract', '=', payroll.contract.id),
]
_payrolls = Payroll.search(dom_payrolls, order=[('start', 'ASC')])
move_ids = [ p.move.id for p in _payrolls]
Move.draft(move_ids)
amount = 0
for _payroll in _payrolls:
if not _payroll.move:
continue
# _payroll.move.draft([_payroll.move.id])
# _payroll.move.save()
for line in _payroll.lines:
if line.wage_type.id == self.start.wage_type.id:
self.recompute_wage_type(line)
# _payroll.save()
# amount = line.amount
# acc_debit_id = line.wage_type.debit_account.id
# acc_credit_id = line.wage_type.credit_account.id
break
debit_ready = credit_ready = False
# for mline in _payroll.move.lines:
# if debit_ready and credit_ready:
# break
# if mline.reconciliation:
# continue
# # if mline.account.id in [acc_debit_id, acc_credit_id]:
# # if mline.account.id == acc_credit_id:
# # mline.write([mline], {'credit': amount})
# # credit_ready = True
# # elif mline.account.id == acc_debit_id:
# # mline.write([mline], {'debit': amount})
# # acc_debit_id = True
# payroll.process([payroll])
# payroll.post([payroll])
# if _payroll.move.state == 'posted':
# continue
# if debit_ready and credit_ready:
# _payroll.move.write([payroll.move], {
# 'state': 'posted',
# })
# if payroll.move and line.wage_type.type_concept in ['risk', 'box_family']:
# self.fix_move(payroll.move, line.wage_type, line.amount)
return 'end'
def _compute_wage_type(self, wage_type, payroll):
salary_args = payroll.get_salary_full(wage_type)
return wage_type.compute_unit_price(salary_args)
def recompute_wage_type(self, pline):
Line = Pool().get('account.move.line')
wage_type = pline.wage_type
unit_value = None
salary_args = {}
salary = []
if wage_type.type_concept in ['risk', 'box_family']:
unit_value = self._compute_wage_type(wage_type, pline.payroll)
if wage_type.type_concept == 'interest':
unit_value = pline.payroll._compute_interest(pline.wage_type, pline.payroll.start)
elif wage_type.type_concept == 'holidays':
concepts_salary_ids = [cs.id for cs in wage_type.concepts_salary]
for line in pline.payroll.lines:
if line.wage_type.id in concepts_salary_ids:
salary.append(line.amount)
salary_args['salary'] = sum(salary)
unit_value = wage_type.compute_unit_price(salary_args)
if not unit_value:
return
pline.write([pline], {'unit_value': unit_value})
pline.on_change_with_amount()
if pline.payroll.move:
# Check that line is not reconciled
move = pline.payroll.move
debit_account = wage_type.debit_account.id
credit_account = wage_type.credit_account.id
debit_ready = False
credit_ready = False
vals = []
to_process = [l for l in move.lines if l.account.id in (debit_account, credit_account)]
for line in to_process:
if line.reconciliation or len(to_process) == 1:
return
# if len(to_process) > 1:
for line in to_process:
# if debit_ready and credit_ready:
# break
# if line.reconciliation:
# break
if line.account.id == debit_account and line.debit != pline.amount:
Line.write([line], {'debit': pline.amount})
elif line.account.id == credit_account and line.credit != pline.amount:
Line.write([line], {'credit': pline.amount})
# vals[line] = {}
# if line.debit > 0:
# vals = {'debit': pline.amount}
# debit_ready = True
# else:
# vals = {'credit': pline.amount}
# # vals = {'credit': pline.amount}
# credit_ready = True
# move.write([move], {'state': 'draft'})
# if len(vals) == 2:
# for l, v in vals:
move.write([move], {
'state': 'posted',
})
def fix_move(self, move, wage_type, amount):
accounts = []
if wage_type.credit_account:
accounts.append(wage_type.credit_account.id)
if wage_type.debit_account:
accounts.append(wage_type.debit_account.id)
move.write([move], {
'state': 'draft',
})
for line in move.lines:
if line.account.id not in accounts:
continue
if line.debit > 0:
line.debit = amount
else:
line.credit = amount
line.save()
move.write([move], {
'state': 'posted',
})
class Exo2276Start(ModelView):
'Payroll Exo 2276 Start'
__name__ = 'staff.payroll_exo2276.start'
start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True)
end_period = fields.Many2One('staff.payroll.period', 'End Period', required=True)
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',
'staff_payroll_co.payroll_exo2276_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('staff.payroll_exo2276.report')
def do_print_(self, action):
data = {
'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
def get_context(cls, records, data):
report_context = super(Exo2276Report, cls).get_context(records, data)
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Payroll = pool.get('staff.payroll')
2020-06-25 03:04:53 +02:00
LiquidationLine = pool.get('staff.liquidation.line')
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-06-25 03:04:53 +02:00
2020-04-16 00:38:42 +02:00
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,
'second_family_name':party.second_family_name,
'first_name': party.first_name,
'second_name':party.second_name,
'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,
'salary':0,
'extras':0,
'box_family':0,
'risk':0,
'unemployment':0,
'allowance': 0,
'syndicate': 0,
'commission': 0,
'total_benefit':0,
'gross_payment':0,
2020-06-25 03:04:53 +02:00
'_cesanpag':0,
'others_payments':0,
'total_retirement':0,
'total_salary':0,
2020-04-16 00:38:42 +02:00
}
2020-06-25 03:04:53 +02:00
new_objects[party.id] = cls._prepare_lines(payrolls, new_objects[party.id], party.id)
_cesanpag = 0
lines_liquid = LiquidationLine.search([
('liquidation.employee.party', '=', party.id),
('liquidation.state', '=', 'posted'),
('liquidation.liquidation_date', '>=', data['start_period']),
('liquidation.liquidation_date', '<=', data['end_period']),
('wage.type_concept', 'in', ['unemployment', 'interest']),
])
if lines_liquid:
_cesanpag = sum([l.amount for l in lines_liquid])
new_objects[party.id]['cesanpag'] = _cesanpag
2020-04-16 00:38:42 +02:00
2020-06-25 03:04:53 +02:00
report_context['records'] = new_objects.values()
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:
if line.wage_type.definition == 'payment':
2020-06-25 03:04:53 +02:00
vals['payments'] += line.amount
2020-06-24 18:45:58 +02:00
if line.wage_type.type_concept == 'unemployment':
2020-06-25 03:04:53 +02:00
continue
# vals['unemployment'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'interest':
2020-06-25 03:04:53 +02:00
continue
# vals['interest'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'salary':
2020-04-16 00:38:42 +02:00
vals['salary'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'commission':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['commission'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'allowance':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['allowance'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'extras':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['extras'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'holidays':
2020-04-16 00:38:42 +02:00
vals['holidays'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'bonus':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['bonus'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'bonus_service':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['bonus_service'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'transport':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['transport'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'food':
2020-04-16 00:38:42 +02:00
vals['payments'] += line.amount
vals['food'] += line.amount
elif line.wage_type.type_concept == 'box_family':
vals['box_family'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'risk':
2020-04-16 00:38:42 +02:00
vals['risk'] += line.amount
2020-06-24 18:45:58 +02:00
elif line.wage_type.type_concept == 'other':
2020-04-16 00:38:42 +02:00
vals['other'] += line.amount
else:
print('Warning: Line no processed... ', line.wage_type.type_concept, line.wage_type.name)
vals['various'] += line.amount
elif line.wage_type.definition == 'deduction':
vals['total_deduction'] += line.amount
if line.wage_type.type_concept == 'health':
vals['health'] += line.amount
elif line.wage_type.type_concept == 'retirement':
vals['retirement'] += line.amount
else:
if line.wage_type.type_concept == 'fsp':
vals['fsp'] += line.amount
elif line.wage_type.type_concept == 'tax':
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']
vals['others_payments'] = (vals['other'] + vals['commission'] +
vals['bonus'] + vals['allowance'] +
vals['various'] + vals['food'])
2020-04-16 00:38:42 +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']
vals['total_salary'] = vals['salary'] + vals['extras'] + vals['transport']
2020-04-16 00:38:42 +02:00
return vals
2020-04-28 01:20:57 +02:00
class IncomeWithholdingsStart(ModelView):
'Income Withholding Start'
__name__ = 'staff.payroll.income_withholdings.start'
start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True)
end_period = fields.Many2One('staff.payroll.period', 'End Period', required=True)
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')
class IncomeWithholdings(Wizard):
'Income Withholding'
__name__ = 'staff.payroll.income_withholdings'
start = StateView('staff.payroll.income_withholdings.start',
'staff_payroll_co.income_withholdings_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
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 = {
'company': self.start.company.id,
'start_period': self.start.start_period.start,
'end_period': self.start.end_period.end,
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):
domain_ = super(IncomeWithholdingsReport, cls).get_domain_payroll(data)
2020-04-28 23:08:35 +02:00
if data['employees']:
domain_.append(('employee', 'in', data['employees']))
2020-04-28 17:33:54 +02:00
return domain_
2020-05-07 22:16:28 +02:00
class ExportMovesReport(PayrollReport):
__name__ = 'staff_payroll.export_moves.report'
@classmethod
def get_context(cls, records, data):
report_context = super(ExportMovesReport, cls).get_context(records, data)
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)
start_period = fields.Many2One('staff.payroll.period', 'Start Period', required=True)
end_period = fields.Many2One('staff.payroll.period', 'End Period', required=True)
department = fields.Many2One('company.department', 'Department',
required=False, depends=['employee'])
@staticmethod
def default_company():
return Transaction().context.get('company')
class PayrollExport(Wizard):
'Payroll Export'
__name__ = 'staff.payroll.export'
start = StateView('staff.payroll.export.start',
'staff_payroll_co.payroll_export_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('staff.payroll.export_report')
def do_print_(self, action):
department_id = self.start.department.id \
if self.start.department else None
data = {
'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
def get_context(cls, records, data):
report_context = super(PayrollExportReport, cls).get_context(records, data)
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-05 23:51:20 +02:00
accdb_party = dict((mw.wage_type.debit_account.id, mw.party) for mw in employee.mandatory_wages
2020-08-06 00:22:18 +02:00
if mw.wage_type.debit_account and mw.party)
2020-08-04 19:39:26 +02:00
move = payroll.move
for line in move.lines:
2020-08-05 18:10:16 +02:00
"""Check account code in dict account debit and party"""
2020-08-05 23:51:20 +02:00
if line.account.id in accdb_party.keys():
line.party = accdb_party[line.account.id]
2020-08-05 18:10:16 +02:00
if line.party:
# id_number = line.party.id_number.split('-')
id_number = line.party.id_number
if id_number in ENTITY_ACCOUNTS.keys():
if line.debit:
line.account.code = ENTITY_ACCOUNTS[id_number][1]
elif line.credit:
line.account.code = ENTITY_ACCOUNTS[id_number][0]
2020-08-06 00:03:35 +02:00
else:
2020-08-06 00:12:58 +02:00
print('Tercero encontrado en lista de entidades ' + id_number)
2020-08-06 00:12:28 +02:00
if line.account.id not in records.keys():
2020-08-06 00:14:24 +02:00
records[line.account.id] = {
2020-08-04 19:39:26 +02:00
'name': line.account.name,
'lines': []
}
2020-08-06 00:12:28 +02:00
records[line.account.id]['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