forked from Mapan/odoo17e
269 lines
12 KiB
Python
269 lines
12 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import defaultdict
|
|
from datetime import date
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.tools.float_utils import float_round
|
|
|
|
|
|
class HrPayslip(models.Model):
|
|
_inherit = 'hr.payslip'
|
|
|
|
l10n_ch_after_departure_payment = fields.Selection([
|
|
('N', 'After Departure Payment'),
|
|
('NK', 'After Departure Payment with correction')],
|
|
string="Code change, correction-payment after departure",
|
|
help="Additional payment after leaving in the current year with maximum salary and third-party benefits")
|
|
l10n_ch_lpp_not_insured = fields.Boolean(
|
|
compute="_compute_l10n_ch_lpp_not_insured", store=True, readonly=False
|
|
)
|
|
l10n_ch_is_code = fields.Char(compute="_compute_l10n_ch_is_code", string="IS Code", store=True)
|
|
l10n_ch_is_model = fields.Selection(
|
|
string="IS Model",
|
|
selection=[('monthly', 'Monthly'), ('yearly', 'Yearly')],
|
|
compute="_compute_l10n_ch_is_model",
|
|
store=True)
|
|
l10n_ch_is_log_line_ids = fields.One2many('hr.payslip.is.log.line', 'payslip_id')
|
|
l10n_ch_avs_status = fields.Selection([
|
|
('youth', 'Youth'),
|
|
('exempted', 'Exempted'),
|
|
('retired', 'Retired'),
|
|
], string="AVS Status", compute='_compute_l10n_ch_avs_status', store=True)
|
|
l10n_ch_pay_13th_month = fields.Boolean(string="Pay Thirteen Month", compute="_compute_l10n_ch_pay_13th_month", store=True, readonly=False)
|
|
|
|
@api.depends('payslip_run_id.l10n_ch_pay_13th_month')
|
|
def _compute_l10n_ch_pay_13th_month(self):
|
|
payslip_to_recompute = self.env['hr.payslip']
|
|
for payslip in self:
|
|
if payslip.company_id.country_id.code != "CH" or payslip.state in ['paid', 'done'] or not payslip.payslip_run_id:
|
|
continue
|
|
if payslip.l10n_ch_pay_13th_month != payslip.payslip_run_id.l10n_ch_pay_13th_month:
|
|
if payslip.state == "waiting":
|
|
payslip_to_recompute += payslip
|
|
payslip.l10n_ch_pay_13th_month = payslip.payslip_run_id.l10n_ch_pay_13th_month
|
|
if payslip_to_recompute:
|
|
payslip_to_recompute.compute_sheet()
|
|
|
|
@api.depends('contract_id.l10n_ch_avs_status')
|
|
def _compute_l10n_ch_avs_status(self):
|
|
for payslip in self:
|
|
contract = payslip.contract_id
|
|
if payslip.state not in ['draft', 'verify'] or self.env.context.get('ch_skip_payslip_update'):
|
|
continue
|
|
payslip.l10n_ch_avs_status = contract.l10n_ch_avs_status
|
|
|
|
@api.depends('contract_id.l10n_ch_is_model')
|
|
def _compute_l10n_ch_is_model(self):
|
|
for payslip in self:
|
|
contract = payslip.contract_id
|
|
if payslip.state not in ['draft', 'verify']:
|
|
continue
|
|
payslip.l10n_ch_is_model = contract.l10n_ch_is_model
|
|
|
|
@api.depends('contract_id.employee_id', 'contract_id.l10n_ch_has_withholding_tax')
|
|
def _compute_l10n_ch_is_code(self):
|
|
for payslip in self:
|
|
contract = payslip.contract_id
|
|
if payslip.state not in ['draft', 'verify']:
|
|
continue
|
|
if not contract.l10n_ch_has_withholding_tax:
|
|
payslip.l10n_ch_is_code = False
|
|
continue
|
|
|
|
e = contract.employee_id
|
|
canton = e.l10n_ch_canton
|
|
if canton == "EX":
|
|
canton = contract.l10n_ch_location_unit_id.canton
|
|
|
|
if contract.l10n_ch_is_predefined_category:
|
|
payslip.l10n_ch_is_code = f"{canton}-{contract.l10n_ch_is_predefined_category}{'Y' if e.l10n_ch_church_tax else 'N'}"
|
|
else:
|
|
payslip.l10n_ch_is_code = f"{canton}-{e.l10n_ch_tax_scale}{int(min(e.children, 9))}{'Y' if e.l10n_ch_church_tax else 'N'}"
|
|
|
|
@api.depends('contract_id.l10n_ch_lpp_not_insured', 'state')
|
|
def _compute_l10n_ch_lpp_not_insured(self):
|
|
for payslip in self:
|
|
if payslip.company_id.country_id.code != "CH" or payslip.state in ['paid', 'done']:
|
|
continue
|
|
payslip.l10n_ch_lpp_not_insured = payslip.contract_id.l10n_ch_lpp_not_insured
|
|
|
|
def _get_payslip_line_total(self, amount, quantity, rate, rule):
|
|
total = super()._get_payslip_line_total(amount, quantity, rate, rule)
|
|
if self.company_id.country_id.code != "CH" or not rule.l10n_ch_5_cents_rounding:
|
|
return total
|
|
total = float_round(total, precision_rounding=0.01, rounding_method="HALF-UP")
|
|
if total % 0.05 >= 0.025:
|
|
return total + 0.05 - (total % 0.05)
|
|
return total - (total % 0.05)
|
|
|
|
def _filter_out_of_contracts_payslips(self):
|
|
return super()._filter_out_of_contracts_payslips().filtered(lambda p: not p.l10n_ch_after_departure_payment)
|
|
|
|
def _get_base_local_dict(self):
|
|
res = super()._get_base_local_dict()
|
|
if self.struct_id.code == "CHMONTHLY":
|
|
date_from = date(self.date_from.year, 1, 1)
|
|
date_to = self.date_from
|
|
payslips_to_correct = self.env['hr.employee.is.line'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('correction_date', '>=', self.date_from),
|
|
('correction_date', '<', self.date_to)
|
|
]).payslips_to_correct
|
|
|
|
if self.l10n_ch_after_departure_payment:
|
|
reference_payslip = self.env['hr.payslip'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('state', 'in', ['done', 'paid']),
|
|
('struct_id.code', '=', 'CHMONTHLY'),
|
|
('l10n_ch_after_departure_payment', '=', False),
|
|
], order="date_from DESC", limit=1)
|
|
if reference_payslip.date_from.year != self.date_from.year:
|
|
date_from = date(reference_payslip.date_from.year, 1, 1)
|
|
date_to = self.date_from
|
|
res.update({
|
|
'reference_is_slip': reference_payslip
|
|
})
|
|
res.update({
|
|
'previous_payslips': self.env['hr.payslip'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('date_from', '>=', date_from),
|
|
('date_to', '<', date_to),
|
|
('state', 'in', ['done', 'paid']),
|
|
('struct_id.code', '=', 'CHMONTHLY'),
|
|
]),
|
|
'payslips_to_correct': payslips_to_correct,
|
|
})
|
|
return res
|
|
|
|
def _record_attachment_payment(self, attachments, slip_lines):
|
|
self.ensure_one()
|
|
sign = -1 if self.credit_note else 1
|
|
amount = slip_lines.total if not attachments.deduction_type_id.is_quantity else slip_lines.quantity
|
|
attachments.record_payment(sign * abs(amount))
|
|
|
|
def compute_sheet(self):
|
|
swiss_payslips = self.filtered(lambda p: p.struct_id.country_id.code == "CH")
|
|
swiss_payslips.l10n_ch_is_log_line_ids.unlink()
|
|
result = super().compute_sheet()
|
|
self.env['hr.payslip.is.log.line'].create(swiss_payslips._get_is_log_lines())
|
|
return result
|
|
|
|
def action_refresh_from_work_entries(self):
|
|
if any(p.state not in ['draft', 'verify'] for p in self):
|
|
super().action_refresh_from_work_entries()
|
|
else:
|
|
payslips = self.filtered(lambda p: p.struct_id.country_id.code == "CH")
|
|
payslips._compute_l10n_ch_pay_13th_month()
|
|
payslips._compute_l10n_ch_avs_status()
|
|
payslips._compute_l10n_ch_is_model()
|
|
payslips._compute_l10n_ch_is_code()
|
|
payslips._compute_l10n_ch_lpp_not_insured()
|
|
super().action_refresh_from_work_entries()
|
|
|
|
def _l10n_ch_get_as_days_count(self):
|
|
self.ensure_one()
|
|
payslip = self
|
|
contract = self.contract_id
|
|
if 'FORCEASDAYS' in payslip.input_line_ids.mapped('code'):
|
|
return payslip._get_input_line_amount('FORCEASDAYS')
|
|
if contract.date_start == self.date_to:
|
|
return 1
|
|
if contract.date_end and contract.date_end == self.date_from:
|
|
return 1
|
|
if contract.date_start > payslip.date_from and contract.date_end and contract.date_end < payslip.date_to:
|
|
return 30 - ((contract.date_start - payslip.date_from).days + 1) - (payslip.date_to - contract.date_end).days
|
|
if contract.date_start > payslip.date_from:
|
|
return 31 - contract.date_start.day
|
|
if contract.date_end and contract.date_end < payslip.date_to and not contract.date_end < payslip.date_from:
|
|
return contract.date_end.day
|
|
if contract.date_end and contract.date_end < payslip.date_from:
|
|
return 0
|
|
return 30
|
|
|
|
def _get_is_log_lines(self):
|
|
line_vals = []
|
|
rules_to_log = ['ISSALARY', 'ISDTSALARY', 'ISDTSALARYPERIODIC', 'ISDTSALARYAPERIODIC']
|
|
for payslip in self:
|
|
for line in payslip.line_ids:
|
|
if line.code in rules_to_log:
|
|
log_line = {
|
|
'is_code': payslip.l10n_ch_is_code,
|
|
'code': line.code,
|
|
'amount': line.total,
|
|
'payslip_id': payslip.id,
|
|
'date': payslip.date_from
|
|
}
|
|
line_vals.append(log_line)
|
|
return line_vals
|
|
|
|
def _get_is_log_line_values(self):
|
|
|
|
result = defaultdict(lambda: defaultdict(float))
|
|
if not self:
|
|
return result
|
|
|
|
self.env.flush_all()
|
|
self.env.cr.execute("""
|
|
SELECT
|
|
pl.is_code,
|
|
pl.code,
|
|
SUM(pl.amount) as total
|
|
FROM hr_payslip_is_log_line pl
|
|
JOIN hr_payslip p
|
|
ON p.id IN %s
|
|
AND ((pl.corrected_slip_id = p.id AND pl.is_correction IS TRUE) OR (pl.payslip_id = p.id AND pl.is_correction IS NOT TRUE))
|
|
GROUP BY pl.is_code, pl.code
|
|
""", (tuple(self.ids),))
|
|
|
|
request_rows = self.env.cr.dictfetchall()
|
|
result = defaultdict(lambda: defaultdict(float))
|
|
for row in request_rows:
|
|
is_code = row['is_code']
|
|
result[is_code].update({
|
|
row['code']: row['total']
|
|
})
|
|
return result
|
|
|
|
def _log_is_correction(self, is_code, code, amount, corrected_payslip, is_correction=True):
|
|
self.ensure_one()
|
|
self.env['hr.payslip.is.log.line'].create({
|
|
'is_code': is_code,
|
|
'code': code,
|
|
'amount': amount,
|
|
'payslip_id': self.id,
|
|
'is_correction': is_correction,
|
|
'corrected_slip_id': corrected_payslip.id if corrected_payslip else False,
|
|
'date': corrected_payslip.date_from if corrected_payslip else self.date_from
|
|
})
|
|
|
|
def _find_rate(self, is_code, x):
|
|
self.ensure_one()
|
|
if not self.contract_id.l10n_ch_has_withholding_tax:
|
|
return 0, 0
|
|
if self.contract_id.l10n_ch_is_predefined_category:
|
|
canton, tax_code = is_code.split("-")
|
|
category_code = tax_code[0:2]
|
|
church_tax = tax_code[2]
|
|
parameter_code = f"l10n_ch_withholding_tax_rates_{canton}_{category_code}_{church_tax}"
|
|
else:
|
|
canton, (tax_scale, child_count, church_tax) = is_code.split("-")
|
|
parameter_code = f"l10n_ch_withholding_tax_rates_{canton}_{church_tax}_{tax_scale}_{child_count}"
|
|
rates = self._rule_parameter(parameter_code)
|
|
|
|
x = float_round(x, precision_rounding=1, rounding_method='DOWN')
|
|
for low, high, min_amount, rate in rates:
|
|
if low <= x <= high:
|
|
return min_amount, rate
|
|
return 0, 0
|
|
|
|
def _get_data_files_to_update(self):
|
|
# Note: file order should be maintained
|
|
return super()._get_data_files_to_update() + [(
|
|
'l10n_ch_hr_payroll_elm', [
|
|
'data/hr_payslip_input_type_data.xml',
|
|
'data/hr_salary_attachment_type_data.xml',
|
|
'data/hr_salary_rule_category_data.xml',
|
|
'data/hr_salary_rule_data.xml',
|
|
])]
|