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

195 lines
8.0 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from collections import defaultdict
from datetime import date
from lxml import etree
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import format_date
from odoo.tools.misc import file_path
class L10nBe273S(models.Model):
_name = 'l10n_be.273s'
_description = '273S Sheet'
_order = 'period'
@api.model
def default_get(self, field_list=None):
if self.env.company.country_id.code != "BE":
raise UserError(_('You must be logged in a Belgian company to use this feature'))
return super().default_get(field_list)
year = fields.Integer(required=True, default=lambda self: fields.Date.today().year)
month = fields.Selection([
('1', 'January'),
('2', 'February'),
('3', 'March'),
('4', 'April'),
('5', 'May'),
('6', 'June'),
('7', 'July'),
('8', 'August'),
('9', 'September'),
('10', 'October'),
('11', 'November'),
('12', 'December'),
], required=True, default=lambda self: str((fields.Date.today() + relativedelta(months=-1)).month))
period = fields.Date(
'Period', compute='_compute_period', store=True)
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', related='company_id.currency_id')
state = fields.Selection([
('draft', 'Draft'),
('waiting', 'Waiting'),
('done', 'Done')
], default='draft', compute="_compute_state", store=True)
pdf_file = fields.Binary(string="PDF File")
pdf_filename = fields.Char("PDF Filename")
xml_file = fields.Binary(string="XML File")
xml_filename = fields.Char("XML Filename")
xml_validation_state = fields.Selection([
('normal', 'N/A'),
('done', 'Valid'),
('invalid', 'Invalid'),
], default='normal', compute='_compute_validation_state', store=True)
error_message = fields.Char('Error Message', compute='_compute_validation_state', store=True)
@api.depends('period')
def _compute_display_name(self):
for record in self:
record.display_name = format_date(self.env, record.period, date_format="MMMM y", lang_code=self.env.user.lang)
@api.depends('year', 'month')
def _compute_period(self):
for record in self:
record.period = date(record.year, int(record.month), 1)
@api.depends('xml_file', 'pdf_file', 'xml_validation_state')
def _compute_state(self):
for record in self:
state = 'draft'
if record.xml_file and record.pdf_file and record.xml_validation_state:
state = 'done'
elif record.xml_file or record.pdf_file:
state = 'waiting'
record.state = state
@api.depends('xml_file')
def _compute_validation_state(self):
xsd_schema_file_path = file_path('l10n_be_hr_payroll/data/withholdingTaxDeclarationOriginal_202012.xsd')
xsd_root = etree.parse(xsd_schema_file_path)
schema = etree.XMLSchema(xsd_root)
no_xml_file_records = self.filtered(lambda record: not record.xml_file)
no_xml_file_records.update({
'xml_validation_state': 'normal',
'error_message': False})
for record in self - no_xml_file_records:
xml_root = etree.fromstring(base64.b64decode(record.xml_file))
try:
schema.assertValid(xml_root)
record.xml_validation_state = 'done'
except etree.DocumentInvalid as err:
record.xml_validation_state = 'invalid'
record.error_message = str(err)
def _get_rendering_data(self):
date_from = self.period + relativedelta(day=1)
date_to = self.period + relativedelta(day=31)
payslips = self.env['hr.payslip'].search([
('state', 'in', ['done', 'paid']),
('company_id', '=', self.company_id.id),
('date_from', '>=', date_from),
('date_to', '<=', date_to)])
employees = payslips.filtered(lambda p: p.contract_id.ip).employee_id.filtered(lambda e: not e._is_niss_valid())
if employees:
raise UserError(_('Invalid NISS number for those employees:\n %s', '\n'.join(employees.mapped('name'))))
# The first threshold is at 16320 € of gross IP, so we only consider the rate at 7.5 %.
line_values = payslips._get_line_values(['IP', 'IP.DED'], compute_sum=True)
gross_amount = line_values['IP']['sum']['total']
tax_amount = - line_values['IP.DED']['sum']['total']
mapped_ip = defaultdict(lambda: [0, 0])
for payslip in payslips.sudo():
ip_amount = line_values['IP'][payslip.id]['total']
if ip_amount:
mapped_ip[payslip.employee_id][0] += ip_amount
mapped_ip[payslip.employee_id][1] += line_values['IP.DED'][payslip.id]['total']
currency = self.env.company.currency_id
return {
'unique_reference': self.id,
'company_info': {
'identification': "BE%s" % (self.company_id.l10n_be_company_number),
'name': self.company_id.name,
'address': self.company_id.partner_id._display_address(),
'phone': self.company_id.phone,
'email': self.company_id.email,
},
'period': fields.Date.today(),
'declaration': {
'gross_amount': round(gross_amount / 2, 2) * 2, # To avoid 1 cent diff (eg: 286079.05)
'deductable_costs': {
'fixed': gross_amount / 2,
'actual': 0,
},
'taxable_amount': gross_amount / 2,
'rate': 15.0,
'tax_amount': tax_amount,
},
'beneficiaries': [
{
'identification': {
'nature': "Citizen",
'name': employee.name,
'street': employee.private_street,
'city': employee.private_city,
'zip': employee.private_zip,
'country': employee.private_country_id.code,
'nationality': employee.country_id.code,
'identification': employee.niss.replace('-', '').replace('.', ''),
},
'gross_amount': ip_values[0],
'deductable_costs': {
'fixed': ip_values[0] / 2,
'actual': 0,
},
'tax_amount': ip_values[1],
} for employee, ip_values in mapped_ip.items()],
'to_eurocent': lambda amount: '%s' % int(amount * 100),
'to_monetary': lambda amount: '%.2f %s' % (amount, currency.symbol),
}
def action_generate_pdf(self):
self.ensure_one()
export_273S_pdf, dummy = self.env["ir.actions.report"].sudo()._render_qweb_pdf(
self.env.ref('l10n_be_hr_payroll.action_report_ip_273S'),
res_ids=self.ids, data=self._get_rendering_data())
self.pdf_filename = '%s-273S_report.pdf' % (self.period.strftime('%B%Y'))
self.pdf_file = base64.encodebytes(export_273S_pdf)
def action_generate_xml(self):
self.ensure_one()
self.xml_filename = '%s-273S_report.xml' % (self.period.strftime('%B%Y'))
xml_str = self.env['ir.qweb']._render('l10n_be_hr_payroll.273S_xml_report', self._get_rendering_data())
# Prettify xml string
root = etree.fromstring(xml_str, parser=etree.XMLParser(remove_blank_text=True))
xml_formatted_str = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=True)
self.xml_file = base64.encodebytes(xml_formatted_str)
def action_validate(self):
self.ensure_one()