trytonpsk-staff_payroll_co/liquidation.py
wilson gomez sanchez 173de783e3 add deductions renta
2021-03-22 16:14:14 -05:00

1155 lines
44 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.
from decimal import Decimal
from datetime import timedelta, date
from trytond import backend
from trytond.model import Workflow, ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.report import Report
from trytond.pyson import Eval, If, Bool
from trytond.wizard import Wizard, StateView, Button, StateTransition, StateReport
from trytond.transaction import Transaction
__all__ = [
'Liquidation', 'Report', 'LiquidationLine', 'LiquidationGroup',
'LiquidationLineMoveLine', 'LiquidationLineAdjustment',
'LiquidationGroupStart', 'LiquidationAdjustmentStart',
'LiquidationAdjustment', 'LiquidationExportStart',
'LiquidationExportReport', 'LiquidationExport'
]
STATES = {'readonly': (Eval('state') != 'draft')}
_ZERO = Decimal('0.0')
BONUS_SERVICE = ['bonus_service']
CONTRACT = [
'bonus_service', 'health', 'retirement', 'unemployment', 'interest',
'holidays'
]
class Liquidation(Workflow, ModelSQL, ModelView):
'Staff Liquidation'
__name__ = 'staff.liquidation'
number = fields.Char('Number', readonly=True, help="Secuence",
select=True)
employee = fields.Many2One('company.employee', 'Employee',
states=STATES, required=True, depends=['state'])
start_period = fields.Many2One('staff.payroll.period', 'Start Period',
required=True, states=STATES)
end_period = fields.Many2One('staff.payroll.period', 'End Period',
required=True, states=STATES, depends=['start_period'])
kind = fields.Selection([
('contract', 'Contract'),
('bonus_service', 'Bonus Service'),
('interest', 'Interest'),
('unemployment', 'Unemployment'),
('vacation', 'Vacation'),
], 'Kind', required=True, states=STATES)
liquidation_date = fields.Date('Liquidation Date', states=STATES,
required=True)
lines = fields.One2Many('staff.liquidation.line', 'liquidation',
'Lines', states=STATES, depends=['employee', 'state'])
gross_payments = fields.Function(fields.Numeric(
'Gross Payments', states=STATES, digits=(16, 2)),
'get_sum_operation')
total_deductions = fields.Function(fields.Numeric(
'Total Deductions', states=STATES, digits=(16, 2)),
'get_sum_operation')
net_payment = fields.Function(fields.Numeric(
'Net Payment', states=STATES, digits=(16, 2)),
'get_net_payment')
time_contracting = fields.Integer('Time Contracting', states=STATES,
depends=['start_period', 'end_period', 'employee'])
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('posted', 'Posted'),
('cancel', 'Cancel'),
], 'State', readonly=True)
company = fields.Many2One('company.company', 'Company', required=True,
states={
'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
},
domain=[
('id', If(Eval('context', {}).contains('company'), '=', '!='),
Eval('context', {}).get('company', 0)),
],
depends=['state'], select=True)
description = fields.Char('Description', states=STATES, select=True)
cause = fields.Char('Cause', states=STATES)
permissons = fields.Char('Permissons', states=STATES)
journal = fields.Many2One('account.journal', 'Journal', required=True,
states=STATES)
currency = fields.Many2One('currency.currency', 'Currency',
required=True, states={
'readonly': ((Eval('state') != 'draft')
| (Eval('lines', [0]) & Eval('currency'))),
},
depends=['state'])
move = fields.Many2One('account.move', 'Move', readonly=True)
contract = fields.Many2One('staff.contract', 'Contract', required=True,
states=STATES, domain=[('employee', '=', Eval('employee'))])
account = fields.Many2One('account.account', 'Account',
required=True, domain=[
('kind', '!=', 'view'),
])
payrolls = fields.Function(fields.Many2Many('staff.payroll',
None, None, 'Payroll', depends=['start_period', 'end_period'],
domain=[
('employee', '=', Eval('employee')),
('kind', '=', 'normal'),
],), 'get_payrolls')
start = fields.Function(fields.Date('Start Date'), 'get_dates')
end = fields.Function(fields.Date('End Date'), 'get_dates')
party_to_pay = fields.Many2One('party.party', 'Party to Pay', states=STATES)
@classmethod
def __setup__(cls):
super(Liquidation, cls).__setup__()
cls._error_messages.update({
'employee_without_salary': ('The employee does not have salary!'),
'wrong_start_end': ('The date end can not smaller than date start'),
'sequence_missing': ('Sequence liquidation is missing!'),
'payroll_not_posted': ('The employee has payrolls does not posted!'),
'error_dates': ('The dates for employee is wrong "%s"!'),
})
cls._transitions |= set((
('draft', 'cancel'),
('cancel', 'draft'),
('confirmed', 'draft'),
('confirmed', 'posted'),
('draft', 'confirmed'),
('posted', 'draft'),
))
cls._buttons.update({
'draft': {
'invisible': Eval('state') == 'draft',
},
'confirm': {
'invisible': Eval('state') != 'draft',
},
'cancel': {
'invisible': Eval('state') != 'draft',
},
'post': {
'invisible': Eval('state') != 'confirmed',
},
'compute_liquidation': {
'invisible': Bool(Eval('lines')),
},
})
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_kind():
return 'contract'
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_currency():
Company = Pool().get('company.company')
company = Transaction().context.get('company')
if company:
company = Company(company)
return company.currency.id
@staticmethod
def default_journal():
Configuration = Pool().get('staff.configuration')
configuration = Configuration(1)
if configuration.default_journal:
return configuration.default_journal.id
@classmethod
def search_rec_name(cls, name, clause):
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [
bool_op,
('employee',) + tuple(clause[1:]),
('number',) + tuple(clause[1:]),
]
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, records):
for rec in records:
rec.set_number()
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('cancel')
def cancel(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('posted')
def post(cls, records):
for rec in records:
rec.create_move()
@classmethod
def create_withholding(cls, liquidation):
rec = liquidation
pool = Pool()
UvtWithholding = pool.get('staff.payroll.uvt_withholding')
WageType = pool.get('staff.wage_type')
wage_tax = WageType.search([('type_concept', '=', 'tax')])
if not wage_tax:
return
wage_tax = wage_tax[0]
deductions_month = sum([
l.amount for l in rec.lines if l.wage.definition != 'payment'
])
salary_full = rec.net_payment
payrolls = {p.end: p for p in rec.payrolls}
if not payrolls:
return
max_date = max(payrolls.keys())
if rec.liquidation_date.month == max_date.month:
payroll = payrolls[max_date]
line_tax = None
for line in payroll.lines:
if line.wage_type.type_concept == 'tax' and line.amount:
line_tax = line
if not line_tax:
deductions_month += payroll.get_deductions_month()
salary_args = payroll.get_salary_full(wage_tax)
salary_full += salary_args['salary']
base_salary_withholding = salary_full - deductions_month
amount_tax = UvtWithholding.compute_withholding(
base_salary_withholding)
amount_tax = rec.currency.round(Decimal(amount_tax))
if amount_tax:
create_tax = {
'sequence': wage_tax.sequence,
'wage': wage_tax.id,
'description': wage_tax.name,
'amount': amount_tax * -1,
'days': rec.time_contracting,
'account': wage_tax.credit_account,
}
cls.write([rec], {
'lines': [('create', [create_tax])]
})
@classmethod
@ModelView.button
def compute_liquidation(cls, records):
for rec in records:
rec.set_liquidation_lines()
cls.create_withholding(rec)
def get_dates(self, name):
if name == 'start':
values = [self.start_period.start]
if self.employee.contract.start_date:
values.append(self.employee.contract.start_date)
res = max(values)
elif name == 'end':
values = [self.end_period.end]
if self.employee.contract.end_date:
values.append(self.employee.contract.end_date)
res = min(values)
return res
def get_payrolls(self, name):
if not self.employee or not self.contract:
return
Payroll = Pool().get('staff.payroll')
date_start, date_end = self._get_dates()
payrolls = Payroll.search([
('employee', '=', self.employee.id),
('start', '>=', date_start),
('end', '<=', date_end),
('contract', '=', self.contract.id),
])
payrolls_ids = [payroll.id for payroll in payrolls]
return payrolls_ids
def create_move(self):
pool = Pool()
Move = pool.get('account.move')
MoveLine = pool.get('account.move.line')
Period = pool.get('account.period')
if self.move:
return
move_lines, grouped = self.get_moves_lines()
if move_lines:
period_id = Period.find(self.company.id, date=self.liquidation_date)
move, = Move.create([{
'journal': self.journal.id,
#'origin': str(self),
'period': period_id,
'date': self.liquidation_date,
'description': self.description,
'lines': [('create', move_lines)],
}])
self.write([self], {'move': move.id})
for ml in move.lines:
if ml.account.id not in grouped.keys() or (
ml.account.kind not in ('payable', 'receivable')):
continue
to_reconcile = [ml]
to_reconcile.extend(grouped[ml.account.id]['lines'])
MoveLine.reconcile(set(to_reconcile))
Move.post([move])
def get_moves_lines(self):
lines_moves = []
to_reconcile = []
grouped = {}
amount = []
for line in self.lines:
if line.move_lines:
for moveline in line.move_lines:
to_reconcile.append(moveline)
if moveline.account.id not in grouped.keys():
grouped[moveline.account.id] = {
'amount': [],
'description': line.description,
'lines': [],
}
grouped[moveline.account.id]['amount'].append(moveline.credit)
grouped[moveline.account.id]['lines'].append(moveline)
amount.append(moveline.credit)
for adjust in line.adjustments:
if adjust.account.id not in grouped.keys():
grouped[adjust.account.id] = {
'amount': [],
'description': adjust.description,
'lines': [],
}
grouped[adjust.account.id]['amount'].append(adjust.amount)
amount.append(adjust.amount)
for account_id, values in grouped.items():
_amount = sum(values['amount'])
debit = _amount
credit = _ZERO
lines_moves.append(self._prepare_line(values['description'],
account_id, debit=debit, credit=credit))
if lines_moves:
lines_moves.append(self._prepare_line(
self.description,
self.account,
credit=sum(amount),
party_to_pay=self.party_to_pay,
))
return lines_moves, grouped
def _prepare_line(self, description, account_id, debit=_ZERO, credit=_ZERO, party_to_pay=None):
if debit < _ZERO:
credit = debit
debit = _ZERO
elif credit < _ZERO:
debit = credit
credit = _ZERO
credit = abs(credit)
debit = abs(debit)
party_id = self.employee.party.id
if party_to_pay:
party_id = self.party_to_pay.id
res = {
'description': description,
'debit': debit,
'credit': credit,
'account': account_id,
'party': party_id,
}
return res
def _get_dates(self):
date_end_contract = None
date_start = self.start_period.start
if self.contract.start_date > self.start_period.start:
date_start = self.contract.start_date
if self.contract.futhermores:
date_end_contract = self.contract.finished_date
date_end = self.end_period.end
if date_end_contract and date_end_contract <= self.end_period.end:
date_end = date_end_contract
elif self.contract.end_date and self.contract.end_date < self.end_period.end:
date_end = self.contract.end_date
return date_start, date_end
def _get_dates_liquidation(self):
# date_end_contract = None
date_start = self.start_period.start
if self.contract.start_date > self.start_period.start:
date_start = self.contract.start_date
# if self.contract.futhermores:
# date_end_contract = self.contract.finished_date
date_end = self.end_period.end
# if date_end_contract and date_end_contract <= self.end_period.end:
# date_end = date_end_contract
# elif self.contract.end_date and self.contract.end_date < self.end_period.end:
# date_end = self.contract.end_date
return date_start, date_end
@classmethod
def get_moves_lines_pending(cls, employee, wage_type, effective_date):
MoveLine = Pool().get('account.move.line')
lines = []
if not wage_type.credit_account:
return
account_id = wage_type.credit_account.id
lines = MoveLine.search([
('move.date', '<=', effective_date),
('credit', '>', 0),
('account', '=', account_id),
('party', '=', employee.party.id),
('reconciliation', '=', None),
])
return lines
def set_liquidation_lines(self):
pool = Pool()
Payroll = pool.get('staff.payroll')
date_start, date_end = self._get_dates_liquidation()
payrolls = Payroll.search([
('employee', '=', self.employee.id),
('start', '>=', date_start),
('end', '<=', date_end),
('contract', '=', self.contract.id),
])
wages = {}
wages_target = {}
print('numero de nominas', len(payrolls))
for payroll in payrolls:
for l in payroll.lines:
if not l.wage_type.contract_finish:
continue
if self.kind == 'contract':
if l.wage_type.type_concept not in CONTRACT:
continue
elif self.kind == 'interest':
if l.wage_type.type_concept != 'interest':
continue
elif self.kind == 'vacation':
if l.wage_type.type_concept != 'holidays':
continue
elif self.kind == 'unemployment':
if l.wage_type.type_concept != 'unemployment':
continue
else:
if l.wage_type.type_concept not in BONUS_SERVICE:
continue
if not l.wage_type.contract_finish:
continue
if l.wage_type.id not in wages_target.keys():
mlines = self.get_moves_lines_pending(
payroll.employee, l.wage_type, date_end
)
if not mlines:
continue
wages_target[l.wage_type.id] = [
l.wage_type.credit_account.id,
mlines,
l.wage_type,
]
# wages.append(l.wage_type.id)
# This looks for lines provisioned before start period
# old_lines_provisioned = MoveLine.search([
# ('party', '=', self.employee.party.id),
# ('move.date', '<', date_start),
# ('reconciliation', '=', None),
# ('account', '=', account_id),
# ])
# lines.extend(old_lines_provisioned)
for (account_id, lines, wage_type) in wages_target.values():
values = []
lines_to_reconcile = []
for line in lines:
values.append(abs(line.debit - line.credit))
lines_to_reconcile.append(line.id)
wages[wage_type.id] = {
'sequence': wage_type.sequence,
'wage': wage_type.id,
'description': wage_type.name,
'amount': sum(values),
'days': self.time_contracting,
'account': account_id,
'move_lines': [('add', lines_to_reconcile)],
'party_to_pay': self.party_to_pay,
}
Liquidation.write([self], {'lines': [('create', wages.values())]})
@fields.depends('start_period', 'end_period', 'contract')
def on_change_with_time_contracting(self):
delta = None
if self.start_period and self.end_period and self.contract:
try:
date_start, date_end = self._get_dates()
print(date_start, date_end)
delta = self.getDifference(date_start, date_end)
print(delta)
except:
# self.raise_user_error('error_dates', self.employee.party.name)
delta = 0
return delta
def countLeapYears(self, d):
years = d.year
# Check if the current year needs to be considered
# for the count of leap years or not
if (d.month <= 2):
years -= 1
# An year is a leap year if it is a multiple of 4,
# multiple of 400 and not a multiple of 100.
return int(years / 4) - int(years / 100) + int(years / 400)
def getDifference(self, start_date, end_date):
# COUNT TOTAL NUMBER OF DAYS BEFORE FIRST DATE 'dt1'
# initialize count using years and day
n1 = start_date.year * 360 + start_date.day
# Add days for months in given date
for i in range(0, start_date.month - 1):
n1 += 30
# Since every leap year is of 360 days,
# Add a day for every leap year
n1 += self.countLeapYears(start_date)
# SIMILARLY, COUNT TOTAL NUMBER OF DAYS BEFORE 'dt2'
n2 = end_date.year * 360 + end_date.day
for i in range(0, end_date.month - 1):
n2 += 30
n2 += self.countLeapYears(end_date)
# return difference between two counts
return (n2 - n1+1)
# def get_time_contracting(self, start_date, end_date):
# sum_days = 0
# print(start_date, end_date)
#
# if end_date.day == 31 or start_date.day == 31:
# end_date = date(end_date.year, end_date.month, 30)
# start_date = date(start_date.year, start_date.month, 30)
#
# delta = (end_date - start_date).days
# if start_date.month == end_date.month:
# return delta
#
# for d in range(delta):
# next_date = start_date + timedelta(days=d)
# if next_date.day == 31:
# continue
# if next_date.month == 2:
# if next_date.day == 29:
# continue
# elif next_date.day == 28:
# sum_days += 2
# else:
# sum_days += 1
# return sum_days
def set_number(self):
if self.number:
return
pool = Pool()
Sequence = pool.get('ir.sequence')
Configuration = pool.get('staff.configuration')
configuration = Configuration(1)
if not configuration.staff_liquidation_sequence:
self.raise_user_error('sequence_missing',)
seq = configuration.staff_liquidation_sequence.id
self.write([self], {'number': Sequence.get_id(seq)})
def get_sum_operation(self, name):
res = []
for line in self.lines:
if not line.amount:
continue
if name == 'gross_payments' and line.wage.definition == 'payment':
res.append(line.amount)
elif name == 'total_deductions' and line.wage.definition != 'payment':
res.append(line.amount)
return sum(res)
def get_net_payment(self, name):
res = (self.gross_payments or 0) - (self.total_deductions or 0)
if res:
return self.currency.round(res)
return 0
def get_currency(self, name):
return self.company.currency.id
class LiquidationLine(ModelSQL, ModelView):
'Staff Liquidation Line'
__name__ = 'staff.liquidation.line'
sequence = fields.Integer('Sequence', required=True)
liquidation = fields.Many2One('staff.liquidation', 'Liquidation',
required=True)
wage = fields.Many2One('staff.wage_type', 'Wage Type', required=True)
description = fields.Char('Description', required=True)
amount = fields.Numeric('Amount', digits=(16, 2), required=True,
depends=['adjustments', 'move_lines'])
days = fields.Integer('Days')
notes = fields.Char('Notes')
account = fields.Many2One('account.account', 'Account')
move_lines = fields.Many2Many('staff.liquidation.line-move.line',
'line', 'move_line', 'Liquidation Line - Move Line',
domain=[
('party', '=', Eval('party')),
('account', '=', Eval('account')),
], depends=['party', 'account'])
party = fields.Function(fields.Many2One('party.party', 'Party'),
'get_party')
adjustments = fields.One2Many('staff.liquidation.line_adjustment',
'staff_liquidation_line', 'Adjustments')
party_to_pay = fields.Many2One('party.party', 'Party to Pay')
@classmethod
def __setup__(cls):
super(LiquidationLine, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
@classmethod
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
table = TableHandler(cls, module_name)
# Migration from 4.0: remove hoard_amount
if table.column_exist('hoard_amount'):
table.drop_column('hoard_amount')
super(LiquidationLine, cls).__register__(module_name)
def get_party(self, name=None):
if self.liquidation.employee:
return self.liquidation.employee.party.id
@fields.depends('wage', 'description', 'amount', 'liquidation',
'_parent_liquidation.employee', '_parent_liquidation.time_contracting',
'_parent_liquidation.start_period', '_parent_liquidation.end_period',
'_parent_liquidation.currency', 'sequence')
def on_change_wage(self):
if not self.wage:
return
self.sequence = self.wage.sequence
self.description = self.wage.name
self.days = self.liquidation.time_contracting
@fields.depends('amount', 'adjustments', 'move_lines')
def on_change_with_amount(self, name=None):
amount_ = 0
if self.adjustments:
amount_ += sum([ad.amount or 0 for ad in self.adjustments])
if self.move_lines:
amount_ += sum([(ml.credit - ml.debit) for ml in self.move_lines])
return amount_
class LiquidationLineAdjustment(ModelSQL, ModelView):
'Liquidation Adjustment'
__name__ = 'staff.liquidation.line_adjustment'
staff_liquidation_line = fields.Many2One('staff.liquidation.line', 'Line',
required=True, select=True)
account = fields.Many2One('account.account', 'Acount',
required=True, domain=[
('company', '=', Eval('context', {}).get('company', -1)),
('kind', '!=', 'view'),
])
description = fields.Char('Description', required=True)
amount = fields.Numeric('Amount', digits=(10,2), required=True)
class LiquidationReport(Report):
__name__ = 'staff.liquidation.report'
class LiquidationLineMoveLine(ModelSQL):
"Liquidation Line - MoveLine"
__name__ = "staff.liquidation.line-move.line"
_table = 'staff_liquidation_line_move_line_rel'
line = fields.Many2One('staff.liquidation.line', 'Line',
ondelete='CASCADE', select=True, required=True)
move_line = fields.Many2One('account.move.line', 'Move Line',
ondelete='RESTRICT', select=True, required=True)
class LiquidationAdjustmentStart(ModelView):
'Create Liquidation Adjustment Start'
__name__ = 'staff.liquidation_adjustment.start'
wage_type = fields.Many2One('staff.wage_type', 'Wage Type', required=True, domain=[
('contract_finish', '=', True)
])
amount = fields.Numeric('Amount', required=True)
account = fields.Many2One('account.account', 'Acount',
required=True, domain=[
('company', '=', Eval('context', {}).get('company', -1)),
('kind', '!=', 'view'),
])
description = fields.Char('Description', required=True)
@fields.depends('wage_type', 'account')
def on_change_wage_type(self):
if self.wage_type and self.wage_type.debit_account:
self.account = self.wage_type.debit_account.id
class LiquidationAdjustment(Wizard):
'Create Liquidation Adjustment'
__name__ = 'staff.liquidation_adjustment'
start = StateView('staff.liquidation_adjustment.start',
'staff_payroll_co.liquidation_adjustment_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Create', 'accept', 'tryton-ok', default=True),
])
accept = StateTransition()
@classmethod
def __setup__(cls):
super(LiquidationAdjustment, cls).__setup__()
cls._error_messages.update({
'liquidation_with_move': ('The liquidation Nro "%s" have move!'),
})
def create_adjustment(self, line):
LineAdjustment = Pool().get('staff.liquidation.line_adjustment')
adjust, = LineAdjustment.create([{
'staff_liquidation_line': line.id,
'account': self.start.account.id,
'description': self.start.description,
'amount': self.start.amount,
}])
return adjust
def transition_accept(self):
pool = Pool()
Liquidation = pool.get('staff.liquidation')
LiquidationLine = pool.get('staff.liquidation.line')
id_ = Transaction().context['active_id']
liquidation, = Liquidation.search([('id', '=', id_)])
line_created = None
if liquidation:
if liquidation.move:
self.raise_user_error('liquidation_with_move', (liquidation.number))
for line in liquidation.lines:
if line.wage.id == self.start.wage_type.id:
if line.amount:
line.amount += self.start.amount
line.save()
line_created = self.create_adjustment(line)
if not line_created:
line, = LiquidationLine.create([{
'sequence': len(liquidation.lines) + 1,
'liquidation': liquidation.id,
'wage': self.start.wage_type.id,
'description': self.start.wage_type.name,
'amount': self.start.amount,
}])
self.create_adjustment(line)
return 'end'
class LiquidationGroupStart(ModelView):
'Liquidation Group Start'
__name__ = 'staff.liquidation_group.start'
start_period = fields.Many2One('staff.payroll.period', 'Start Period',
required=True)
end_period = fields.Many2One('staff.payroll.period', 'End Period',
required=True, depends=['start_period'])
kind = fields.Selection([
('contract', 'Contract'),
('bonus_service', 'Bonus Service'),
('interest', 'Interest'),
('vacation', 'Vacation'),
('unemployment', 'Unemployment'),
], 'Kind', required=True)
liquidation_date = fields.Date('Liquidation Date', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
description = fields.Char('Description', required=True)
account = fields.Many2One('account.account', 'Account', required=True,
domain=[
('kind', '=', 'payable'),
('company', '=', Eval('company'))
])
employees = fields.Many2Many('company.employee', None, None, 'Employee')
party_to_pay = fields.Many2One('party.party', 'Party to Pay', states={
'invisible': Eval('kind') != 'unemployment'
})
@staticmethod
def default_company():
return Transaction().context.get('company')
class LiquidationGroup(Wizard):
'Liquidation Group'
__name__ = 'staff.liquidation_group'
start = StateView('staff.liquidation_group.start',
'staff_payroll_co.liquidation_group_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Accept', 'open_', 'tryton-ok', default=True),
])
open_ = StateTransition()
def transition_open_(self):
pool = Pool()
Liquidation = pool.get('staff.liquidation')
Contract = pool.get('staff.contract')
Employee = pool.get('company.employee')
to_liquidation = []
start_period = self.start.start_period.id
end_period = self.start.end_period.id
kind = self.start.kind
liquidation_date = self.start.liquidation_date
company_id = self.start.company.id
description = self.start.description
currency_id = self.start.company.currency.id
account_id = self.start.account.id
dom_contracts = [('kind', '=', kind)]
if self.start.employees:
employees = self.start.employees
employee_ids = [e.id for e in employees]
dom_contracts.append(('employee', 'in', employee_ids))
else:
employees = Employee.search([])
contracts = Liquidation.search_read(
dom_contracts, fields_names=['contract']
)
contract_ids = [i['contract'] for i in contracts]
for employee in employees:
contracts = Contract.search([
('employee', '=', employee.id),
('id', 'not in', contract_ids),
])
if not contracts:
continue
contract = contracts[0]
if kind == 'unemployment':
wages = [
mw for mw in employee.mandatory_wages
if mw.wage_type.type_concept == 'unemployment'
]
if not wages:
continue
wage = wages[0]
lines = Liquidation.get_moves_lines_pending(
employee, wage.wage_type, self.start.end_period.end
)
if not lines:
continue
lqt_create = {
'start_period': start_period,
'end_period': end_period,
'employee': employee.id,
'contract': contract.id,
'kind': kind,
'liquidation_date': liquidation_date,
'time_contracting': None,
'state': 'draft',
'company': company_id,
'description': description,
'currency': currency_id,
'account': account_id,
'party_to_pay': self.start.party_to_pay.id if self.start.party_to_pay else None
}
liq, = Liquidation.create([lqt_create])
liq.time_contracting = liq.on_change_with_time_contracting()
liq.save()
to_liquidation.append(liq)
Liquidation.compute_liquidation(to_liquidation)
return 'end'
class MoveProvisionBonusServiceStart(ModelView):
'Move Privision Bonus Service Start'
__name__ = 'staff.move_provision_bonus_service.start'
period = fields.Many2One('staff.payroll.period', 'Period',
required=True, domain=[('state', '=', 'open')])
description = fields.Char('Description', required=True)
company = fields.Many2One('company.company', 'Company',
required=True)
wage_type = fields.Many2One('staff.wage_type', 'Wage Types', domain=[
('type_concept', '=', 'bonus_service')
], required=True)
category = fields.Many2One('staff.employee_category', 'Category')
@staticmethod
def default_company():
return Transaction().context.get('company')
class MoveProvisionBonusService(Wizard):
'Move Provision Bonus Service'
__name__ = 'staff.move_provision_bonus_service'
start = StateView('staff.move_provision_bonus_service.start',
'staff_payroll_co.move_provision_bonus_service_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Accept', 'open_', 'tryton-ok', default=True),
])
open_ = StateTransition()
def transition_open_(self):
pool = Pool()
Contract = pool.get('staff.contract')
Move = pool.get('account.move')
AccountPeriod = pool.get('account.period')
configuration = Pool().get('staff.configuration')(1)
journal_id = None
if configuration and configuration.default_journal:
journal_id = configuration.default_journal.id
_end_date = self.start.period.end
_company = self.start.company
provision_wage = self.start.wage_type
period_days = (self.start.period.end - self.start.period.start).days + 1
dom_contract = [
['AND', ['OR', [
('end_date', '>', self.start.period.start),
], [
('end_date', '=', None),
],
]],
]
if self.start.category:
dom_contract.append(
('employee.category', '=', self.start.category.id)
)
for contract in Contract.search(dom_contract):
period_in_month = 1 if period_days > 15 else 2
salary_amount = contract.get_salary_in_date(_end_date)
base_ = salary_amount
move_lines = []
employee = contract.employee
for concept in contract.employee.mandatory_wages:
if concept.wage_type and concept.wage_type.salary_constitute:
if concept.wage_type.type_concept == 'transport':
base_ += concept.wage_type.compute_unit_price({'salary': 0}) * concept.wage_type.default_quantity
if concept.fix_amount:
base_ += concept.fix_amount
period_id = AccountPeriod.find(_company.id, date=_end_date)
provision_amount = provision_wage.compute_unit_price(
{'salary': (round((base_ / period_in_month), 2))}
)
move_lines.extend([
{
'debit': provision_amount,
'credit': 0,
'party': employee.party.id,
'account': provision_wage.debit_account.id
},
{
'debit': 0,
'credit': provision_amount,
'party': employee.party.id,
'account': provision_wage.credit_account.id
}
])
move, = Move.create([{
'journal': journal_id,
# 'origin': str(contract),
'period': period_id,
'company': _company.id,
'date': _end_date,
'state': 'draft',
'description': self.start.description,
'lines': [('create', move_lines)],
}])
return 'end'
class LiquidationExportStart(ModelView):
'Liquidation Export Start'
__name__ = 'staff.liquidation_export.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
department = fields.Many2One('company.department', 'Department')
kind = fields.Selection([
('contract', 'Contract'),
('bonus_service', 'Bonus Service'),
('interest', 'Interest'),
('unemployment', 'Unemployment'),
('vacation', 'Vacation'),
], 'Kind')
@staticmethod
def default_company():
return Transaction().context.get('company')
@fields.depends('start_date')
def on_change_with_end_date(self, name=None):
if self.start_date:
return self.start_date
class LiquidationExport(Wizard):
'Liquidation Export'
__name__ = 'staff.liquidation_export'
start = StateView('staff.liquidation_export.start',
'staff_payroll_co.liquidation_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('staff.liquidation_export.report')
def do_print_(self, action):
department_id = None
if self.start.department:
department_id = self.start.department.id
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'department': department_id,
'kind': self.start.kind,
}
return action, data
def transition_print_(self):
return 'end'
class LiquidationExportReport(Report):
__name__ = 'staff.liquidation_export.report'
@classmethod
def get_domain_liquidation(cls, data):
dom_liquidation = []
return dom_liquidation
@classmethod
def get_context(cls, records, data):
report_context = super(LiquidationExportReport, cls).get_context(
records, data)
pool = Pool()
user = pool.get('res.user')(Transaction().user)
Liquidation = pool.get('staff.liquidation')
Department = pool.get('company.department')
dom_liq = cls.get_domain_liquidation(data)
dom_liq.append(('state', 'in', ['processed', 'posted', 'draft']),)
dom_liq.append(('liquidation_date', '>=', data['start_date']),)
dom_liq.append(('liquidation_date', '<=', data['end_date']),)
if data['department']:
dom_liq.append(
['AND', ['OR', [
('employee.department', '=', data['department']),
('department', '=', None),
], [
('department', '=', data['department']),
],
]]
)
department = Department(data['department']).name
else:
department = None
if data['kind']:
dom_liq.append(('kind', '=', data['kind']))
liquidations = Liquidation.search(dom_liq)
# default_vals = cls.default_values()
sum_gross_payments = []
sum_total_deductions = []
sum_net_payment = []
parties = {}
payments = ['interest', 'holidays', 'bonus_service', 'unemployment']
for liquidation in liquidations:
employee_id = liquidation.employee.id
if employee_id not in parties.keys():
position_employee = liquidation.employee.position.name if liquidation.employee.position else ''
position_contract = liquidation.contract.position.name if liquidation.contract and liquidation.contract.position else ''
parties[employee_id] = {}
parties[employee_id]['employee_code'] = liquidation.employee.code
parties[employee_id]['employee_contract'] = liquidation.contract.id
parties[employee_id]['employee'] = liquidation.employee.party.name
parties[employee_id]['employee_id_number'] = liquidation.employee.party.id_number
parties[employee_id]['employee_position'] = position_contract or position_employee or ''
for line in liquidation.lines:
if line.wage.type_concept in (payments):
concept = line.wage.type_concept
print('pasa por aca')
else:
if line.wage.definition == 'payment' and line.wage_type.receipt:
concept = 'others_payments'
elif line.wage.definition == 'deduction' or \
line.wage.definition == 'discount' and \
line.wage.receipt:
concept = 'others_deductions'
if not concept:
liquidation.raise_user_error('type_concept_not_exists', line.wage.name)
if concept not in parties[employee_id].keys():
parties[employee_id][concept] = 0
parties[employee_id][concept] += line.amount
parties[employee_id]['time_contracting'] = liquidation.time_contracting
parties[employee_id]['net_payment'] = liquidation.net_payment
parties[employee_id]['gross_payments'] = liquidation.gross_payments
parties[employee_id]['total_deductions'] = liquidation.total_deductions
sum_gross_payments.append(liquidation.gross_payments)
sum_total_deductions.append(liquidation.total_deductions)
sum_net_payment.append(liquidation.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['start_date'] = data['start_date']
report_context['end_date'] = data['end_date']
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