trytonpsk-staff_payroll_co/payroll.py
Camilo Sarmiento 0ddaf40df3 minor fix
2020-08-05 11:10:16 -05:00

1954 lines
76 KiB
Python

# 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
from datetime import date
from trytond.model import ModelView, fields, Workflow
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
from trytond.modules.staff_payroll import PayrollReport
__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',
'IncomeWithholdingsStart', 'IncomeWithholdingsReport',
'PayrollExportStart', 'PayrollExport', 'PayrollExportReport'
]
STATES = {'readonly': (Eval('state') != 'draft')}
_ZERO = Decimal('0.0')
PAYMENTS = ['salary', 'bonus', 'reco', 'recf', 'hedo', 'heno',
'dom', 'hedf', 'henf']
SOCIAL_SEGURITY = ['risk', 'health', 'retirement',
'box_family', 'sena', 'icbf']
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)
}
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'
' for the employee!'),
}
)
# 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
@fields.depends('period', 'employee', 'start', 'end', 'contract',
'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
@fields.depends('start', 'end', 'employee', 'period', 'contract')
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]
if payrolls:
# num_subperiod = 30 / config.default_liquidation_period
# if len(payrolls) >= num_subperiod:
for payroll in payrolls:
res += payroll.compute_salary_full(wage)
# 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)
# res['unit_value'] = self._validate_amount_wage(wage, res['unit_value'])
return res
# 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
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)
if event.amount_to_pay:
unit_value = Decimal(event.amount_to_pay)
else:
unit_value = wage.compute_unit_price(salary_args)
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:
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
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
values['department'] = payroll.employee.department.name \
if payroll.employee.department else ''
values['company'] = user.company.party.name
values['legal_salary'] = payroll.contract.get_salary_in_date(
payroll.end)
values['period'] = payroll.period.name
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)
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 \
payroll.employee.project_contract.reference:
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',
'department',
]
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',
'health_provision',
'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
vals['health_provision'] += line.get_expense_amount()
elif line.wage_type.type_concept == 'retirement':
vals['retirement'] += line.amount
vals['retirement_provision'] += line.get_expense_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)
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']
vals['total_benefit'] = vals['unemployment'] + vals['interest'] + vals['holidays'] + vals['bonus_service']
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
def get_domain_payroll(cls, data=None):
Period = Pool().get('staff.payroll.period')
periods = Period.search([
('start', '>=', data['start_period']),
('end', '<=', data['end_period']),
])
periods_ids = [p.id for p in periods]
domain = [
('period', 'in', periods_ids),
('state', '=', 'posted'),
]
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')
LiquidationLine = pool.get('staff.liquidation.line')
domain_ = cls.get_domain_payroll(data)
payrolls = Payroll.search([domain_])
new_objects = {}
index = 0
for payroll in payrolls:
index += 1
party = payroll.employee.party
if party.id in new_objects.keys():
continue
new_objects[party.id] = {
'index': index,
'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,
'_cesanpag':0,
'others_payments':0,
'total_retirement':0,
'total_salary':0,
}
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
report_context['records'] = new_objects.values()
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':
vals['payments'] += line.amount
if line.wage_type.type_concept == 'unemployment':
continue
# vals['unemployment'] += line.amount
elif line.wage_type.type_concept == 'interest':
continue
# vals['interest'] += line.amount
elif line.wage_type.type_concept == 'salary':
vals['salary'] += line.amount
elif line.wage_type.type_concept == 'commission':
vals['payments'] += line.amount
vals['commission'] += line.amount
elif line.wage_type.type_concept == 'allowance':
vals['payments'] += line.amount
vals['allowance'] += line.amount
elif line.wage_type.type_concept == 'extras':
vals['payments'] += line.amount
vals['extras'] += line.amount
elif line.wage_type.type_concept == 'holidays':
vals['holidays'] += line.amount
elif line.wage_type.type_concept == 'bonus':
vals['payments'] += line.amount
vals['bonus'] += line.amount
elif line.wage_type.type_concept == 'bonus_service':
vals['payments'] += line.amount
vals['bonus_service'] += line.amount
elif line.wage_type.type_concept == 'transport':
vals['payments'] += line.amount
vals['transport'] += line.amount
elif line.wage_type.type_concept == 'food':
vals['payments'] += line.amount
vals['food'] += line.amount
elif line.wage_type.type_concept == 'box_family':
vals['box_family'] += line.amount
elif line.wage_type.type_concept == 'risk':
vals['risk'] += line.amount
elif line.wage_type.type_concept == 'other':
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)
# vals['cesanpag'] = vals['unemployment'] + vals['interest']
vals['others_payments'] = (vals['other'] + vals['commission'] +
vals['bonus'] + vals['allowance'] +
vals['various'] + vals['food'])
vals['total_benefit'] = vals['holidays'] + vals['bonus_service']
vals['total_retirement'] = vals['fsp'] + vals['retirement']
vals['total_salary'] = vals['salary'] + vals['extras'] + vals['transport']
return vals
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)
employees = fields.Many2Many('company.employee', None, None, 'Employees')
@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):
employees = []
if self.start.employees:
employees = [e.id for e in self.start.employees]
data = {
'company': self.start.company.id,
'start_period': self.start.start_period.start,
'end_period': self.start.end_period.end,
'employees': employees,
}
return action, data
def transition_print_(self):
return 'end'
class IncomeWithholdingsReport(Exo2276Report):
'Income Withholding Report'
__name__ = 'staff.payroll.income_withholdings_report'
@classmethod
def get_domain_payroll(cls, data=None):
domain_ = super(IncomeWithholdingsReport, cls).get_domain_payroll(data)
if data['employees']:
domain_.append(('employee', 'in', data['employees']))
return domain_
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
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:
employee = payroll.employee
""" extract debit account and party mandatory_wages"""
accdb_party = dict((mw.wage_type.debit_account.code, mw.party) for mw in employee.mandatory_wages
if mw.wage_type.debit_account)
move = payroll.move
for line in move.lines:
"""Check account code in dict account debit and party"""
if line.account.code in accdb_party.keys():
line.party = accdb_party[line.account.code]
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]
if line.account.code not in records.keys():
records[line.account.code] = {
'name': line.account.name,
'lines': []
}
records[line.account.code]['lines'].append(line)
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