1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_ch_hr_payroll_elm/models/hr_payslip.py
2024-12-10 09:04:09 +07:00

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',
])]