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

232 lines
11 KiB
Python

# coding: utf-8
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from lxml import etree
from lxml.objectify import fromstring
from collections import defaultdict
from odoo import models, fields, _
from odoo.exceptions import UserError, RedirectWarning
CFDIBCE_XSLT_CADENA = 'l10n_mx_reports/data/xslt/1.3/BalanzaComprobacion_1_2.xslt'
CFDICATALOGO_XSLT_CADENA = 'l10n_mx_reports/data/xslt/1.3/CatalogoCuentas_1_2.xslt'
class TrialBalanceCustomHandler(models.AbstractModel):
_inherit = 'account.trial.balance.report.handler'
def _custom_options_initializer(self, report, options, previous_options=None):
super()._custom_options_initializer(report, options, previous_options)
if self.env.company.account_fiscal_country_id.code == 'MX':
options['buttons'] += [
{'name': _("SAT (XML)"), 'action': 'export_file', 'action_param': 'action_l10n_mx_generate_sat_xml', 'file_export_type': _("SAT (XML)"), 'sequence': 15},
{'name': _("COA SAT (XML)"), 'action': 'export_file', 'action_param': 'action_l10n_mx_generate_coa_sat_xml', 'file_export_type': _("COA SAT (XML)"), 'sequence': 16},
]
def action_l10n_mx_generate_sat_xml(self, options):
if self.env.company.account_fiscal_country_id.code != 'MX':
raise UserError(_("Only Mexican company can generate SAT report."))
sat_values = self._l10n_mx_get_sat_values(options)
file_name = f"{sat_values['vat']}{sat_values['year']}{sat_values['month']}BN"
cfdi = self.env['ir.qweb']._render('l10n_mx_reports.cfdibalance', sat_values)
sat_report = self._l10n_mx_edi_add_digital_stamp(CFDIBCE_XSLT_CADENA, cfdi)
self.env['ir.attachment'].l10n_mx_reports_validate_xml_from_attachment(sat_report, 'xsd_mx_cfdibalance_1_3.xsd')
return {
'file_name': f"{file_name}.xml",
'file_content': self.env['l10n_mx_edi.document']._convert_xml_to_attachment_data(sat_report),
'file_type': 'xml',
}
def _l10n_mx_edi_add_digital_stamp(self, path_xslt, cfdi):
"""Add digital stamp certificate attributes in XML report"""
tree = fromstring(cfdi)
certificate = self.env.company.sudo().l10n_mx_edi_certificate_ids._get_valid_certificate()
if not certificate:
return tree
cadena = certificate._get_cadena_chain(tree, path_xslt)
sello = certificate.sudo()._get_encrypted_cadena(cadena)
tree.attrib['Sello'] = sello
tree.attrib['noCertificado'] = certificate.serial_number
tree.attrib['Certificado'] = certificate.sudo()._get_data()[0]
return tree
def _l10n_mx_get_sat_values(self, options):
report = self.env['account.report'].browse(options['report_id'])
sat_options = self._l10n_mx_get_sat_options(options)
report_lines = report._get_lines(sat_options)
# The SAT code has to be of the form XXX.YY . Any additional suffixes are allowed, but if the line starts
# with anything else it should not be included in the SAT report.
sat_code = re.compile(r'((\d{3})\.\d{2})')
account_lines = []
parents = defaultdict(lambda: defaultdict(int))
for line in [line for line in report_lines if line.get('level') == 4]:
dummy, res_id = report._get_model_info_from_id(line['id'])
account = self.env['account.account'].browse(res_id)
is_credit_account = any([account.account_type.startswith(acc_type) for acc_type in ['liability', 'equity', 'income']])
balance_sign = -1 if is_credit_account else 1
cols = line.get('columns', [])
# Initial Debit - Initial Credit = Initial Balance
initial = balance_sign * (cols[0].get('no_format', 0.0) - cols[1].get('no_format', 0.0))
# Debit and Credit of the selected period
debit = cols[2].get('no_format', 0.0)
credit = cols[3].get('no_format', 0.0)
# End Debit - End Credit = End Balance
end = balance_sign * (cols[4].get('no_format', 0.0) - cols[5].get('no_format', 0.0))
pid_match = sat_code.match(line['name'])
if not pid_match:
raise UserError(_("Invalid SAT code: %s", line['name']))
for pid in pid_match.groups():
parents[pid]['initial'] += initial
parents[pid]['debit'] += debit
parents[pid]['credit'] += credit
parents[pid]['end'] += end
for pid in sorted(parents.keys()):
account_lines.append({
'number': pid,
'initial': '%.2f' % parents[pid]['initial'],
'debit': '%.2f' % parents[pid]['debit'],
'credit': '%.2f' % parents[pid]['credit'],
'end': '%.2f' % parents[pid]['end'],
})
report_date = fields.Date.to_date(sat_options['date']['date_from'])
return {
'vat': self.env.company.vat or '',
'month': str(report_date.month).zfill(2),
'year': report_date.year,
'type': 'N',
'accounts': account_lines,
}
def action_l10n_mx_generate_coa_sat_xml(self, options):
if self.env.company.account_fiscal_country_id.code != 'MX':
raise UserError(_("Only Mexican company can generate SAT report."))
coa_values = self._l10n_mx_get_coa_values(options)
file_name = f"{coa_values['vat']}{coa_values['year']}{coa_values['month']}CT"
cfdi = self.env['ir.qweb']._render('l10n_mx_reports.cfdicoa', coa_values)
coa_report = self._l10n_mx_edi_add_digital_stamp(CFDICATALOGO_XSLT_CADENA, cfdi)
self.env['ir.attachment'].l10n_mx_reports_validate_xml_from_attachment(coa_report, 'xsd_mx_cfdicoa_1_3.xsd')
return {
'file_name': f"{file_name}.xml",
'file_content': self.env['l10n_mx_edi.document']._convert_xml_to_attachment_data(coa_report),
'file_type': 'xml',
}
def _l10n_mx_get_coa_values(self, options):
# Checking if debit/credit tags are installed
debit_balance_account_tag = self.env.ref('l10n_mx.tag_debit_balance_account', raise_if_not_found=False)
credit_balance_account_tag = self.env.ref('l10n_mx.tag_credit_balance_account', raise_if_not_found=False)
if not debit_balance_account_tag or not credit_balance_account_tag:
raise UserError(_("Missing Debit or Credit balance account tag in database."))
coa_options = self._l10n_mx_get_sat_options(options)
accounts = self.env['account.account'].search([
*self.env['account.account']._check_company_domain(self.env.company),
('account_type', '!=', 'equity_unaffected'),
('group_id', '!=', False),
])
accounts_groups_by_parent = defaultdict(lambda: defaultdict(lambda: self.env['account.account']))
accounts_template_data = []
for account in accounts:
accounts_groups_by_parent[account.group_id.parent_id][account.group_id] |= account
no_tag_accounts = self.env['account.account']
multi_tag_accounts = self.env['account.account']
parent_nature = ''
for parent, accounts_by_group in accounts_groups_by_parent.items():
if not parent:
continue
parent_nature = set()
group_lines = []
for group, accounts in accounts_by_group.items():
group_nature = set()
for account in accounts:
nature = ''
if debit_balance_account_tag in account.tag_ids:
nature += 'D'
if credit_balance_account_tag in account.tag_ids:
nature += 'A'
if not nature:
no_tag_accounts |= account
elif len(nature) > 1:
multi_tag_accounts |= account
else:
group_nature.add(nature)
if len(group_nature) > 1:
multi_tag_accounts |= accounts
group_nature = len(group_nature) and group_nature.pop()
group_lines.append({
'code': group.code_prefix_start,
'number': group.code_prefix_start,
'name': group.name,
'level': 2,
'nature': group_nature,
})
if group_nature:
parent_nature.add(group_nature)
if len(parent_nature) > 1:
for accounts in accounts_by_group.values():
multi_tag_accounts |= accounts
parent_nature = len(parent_nature) and parent_nature.pop()
parent_line = [{
'code': parent.code_prefix_start,
'number': parent.code_prefix_start,
'name': parent.name,
'level': 1,
'nature': parent_nature,
}]
accounts_template_data += parent_line + group_lines
if no_tag_accounts:
raise RedirectWarning(
_("Some accounts present in your trial balance don't have a Debit or a Credit balance account tag."),
{
'name': _("Accounts without tag"),
'type': 'ir.actions.act_window',
'views': [(False, 'list'), (False, 'form')],
'res_model': 'account.account',
'target': 'current',
'domain': [('id', 'in', no_tag_accounts.ids)],
},
_('Show list')
)
if multi_tag_accounts:
raise RedirectWarning(
_("Some account prefixes used in your trial balance use both Debit and Credit balance account tags. This is not allowed."),
{
'name': _("Accounts with too much tags"),
'type': 'ir.actions.act_window',
'views': [(False, 'list'), (False, 'form')],
'res_model': 'account.account',
'target': 'current',
'domain': [('id', 'in', multi_tag_accounts.ids)],
},
_('Show list')
)
report_date = fields.Date.to_date(coa_options['date']['date_from'])
return {
'vat': self.env.company.vat or '',
'month': str(report_date.month).zfill(2),
'year': report_date.year,
'accounts': accounts_template_data,
}
def _l10n_mx_get_sat_options(self, options):
sat_options = options.copy()
del sat_options['comparison']
return self.env['account.report'].browse(options['report_id']).get_options(
previous_options={
**sat_options,
'hierarchy': True, # We need the hierarchy activated to get group lines
}
)