Odoo18-Base/addons/account_edi_ubl_cii/models/res_partner.py

264 lines
12 KiB
Python
Raw Permalink Normal View History

2025-01-06 10:57:38 +07:00
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from stdnum.fr import siret
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.addons.account_edi_ubl_cii.models.account_edi_common import EAS_MAPPING
from odoo.addons.account.models.company import PEPPOL_DEFAULT_COUNTRIES
class ResPartner(models.Model):
_inherit = 'res.partner'
invoice_edi_format = fields.Selection(
selection_add=[
('facturx', "Factur-X (CII)"),
('ubl_bis3', "BIS Billing 3.0"),
('xrechnung', "XRechnung CIUS"),
('nlcius', "NLCIUS"),
('ubl_a_nz', "BIS Billing 3.0 A-NZ"),
('ubl_sg', "BIS Billing 3.0 SG"),
],
)
is_ubl_format = fields.Boolean(compute='_compute_is_ubl_format')
is_peppol_edi_format = fields.Boolean(compute='_compute_is_peppol_edi_format')
peppol_endpoint = fields.Char(
string="Peppol Endpoint",
help="Unique identifier used by the BIS Billing 3.0 and its derivatives, also known as 'Endpoint ID'.",
compute="_compute_peppol_endpoint",
store=True,
readonly=False,
tracking=True,
)
peppol_eas = fields.Selection(
string="Peppol e-address (EAS)",
help="""Code used to identify the Endpoint for BIS Billing 3.0 and its derivatives.
List available at https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/""",
compute="_compute_peppol_eas",
store=True,
readonly=False,
tracking=True,
selection=[
('9923', "Albania VAT - 9923"),
('9922', "Andorra VAT - 9922"),
('0151', "Australia ABN - 0151"),
('9914', "Austria UID - 9914"),
('9915', "Austria VOKZ - 9915"),
('0208', "Belgium CBE - 0208"),
('9925', "Belgium VAT - 9925"),
('9924', "Bosnia and Herzegovina VAT - 9924"),
('9926', "Bulgaria VAT - 9926"),
('9934', "Croatia VAT - 9934"),
('9928', "Cyprus VAT - 9928"),
('9929', "Czech Republic VAT - 9929"),
('0096', "Denmark CVR - 0096"),
('0184', "Denmark CVR - 0184"),
('0198', "Denmark SE - 0198"),
('0191', "Estonia Company code - 0191"),
('9931', "Estonia VAT - 9931"),
('0037', "Finland LY-tunnus - 0037"),
('0216', "Finland OVT code - 0216"),
('0213', "Finland VAT - 0213"),
('0002', "France SIRENE - 0002"),
('0009', "France SIRET - 0009"),
('9957', "France VAT - 9957"),
('0204', "Germany Leitweg-ID - 0204"),
('9930', "Germany VAT - 9930"),
('9933', "Greece VAT - 9933"),
('9910', "Hungary VAT - 9910"),
('0196', "Iceland Kennitala - 0196"),
('9935', "Ireland VAT - 9935"),
('0211', "Italia Partita IVA - 0211"),
('0097', "Italia FTI - 0097"),
('0188', "Japan SST - 0188"),
('0221', "Japan IIN - 0221"),
('9939', "Latvia VAT - 9939"),
('9936', "Liechtenstein VAT - 9936"),
('0200', "Lithuania JAK - 0200"),
('9937', "Lithuania VAT - 9937"),
('9938', "Luxemburg VAT - 9938"),
('9942', "Macedonia VAT - 9942"),
('0230', "Malaysia - 0230"),
('9943', "Malta VAT - 9943"),
('9940', "Monaco VAT - 9940"),
('9941', "Montenegro VAT - 9941"),
('0106', "Netherlands KvK - 0106"),
('0190', "Netherlands OIN - 0190"),
('9944', "Netherlands VAT - 9944"),
('0192', "Norway Org.nr. - 0192"),
('9945', "Poland VAT - 9945"),
('9946', "Portugal VAT - 9946"),
('9947', "Romania VAT - 9947"),
('9948', "Serbia VAT - 9948"),
('0195', "Singapore UEN - 0195"),
('9949', "Slovenia VAT - 9949"),
('9950', "Slovakia VAT - 9950"),
('9920', "Spain VAT - 9920"),
('0007', "Sweden Org.nr. - 0007"),
('9955', "Sweden VAT - 9955"),
('9927', "Swiss VAT - 9927"),
('0183', "Swiss UIDB - 0183"),
('9952', "Turkey VAT - 9952"),
('9932', "United Kingdom VAT - 9932"),
('9959', "USA EIN - 9959"),
('0060', "DUNS Number - 0060"),
('0088', "EAN Location Code - 0088"),
('0130', "Directorates of the European Commission - 0130"),
('0135', "SIA Object Identifiers - 0135"),
('0142', "SECETI Object Identifiers - 0142"),
('0193', "UBL.BE party identifier - 0193"),
('0199', "Legal Entity Identifier (LEI) - 0199"),
('0201', "Codice Univoco Unità Organizzativa iPA - 0201"),
('0202', "Indirizzo di Posta Elettronica Certificata - 0202"),
('0209', "GS1 identification keys - 0209"),
('0210', "Codice Fiscale - 0210"),
('9913', "Business Registers Network - 9913"),
('9918', "S.W.I.F.T - 9918"),
('9919', "Kennziffer des Unternehmensregisters - 9919"),
('9951', "San Marino VAT - 9951"),
('9953', "Vatican VAT - 9953"),
]
)
@api.constrains('peppol_endpoint')
def _check_peppol_fields(self):
for partner in self:
if partner.peppol_endpoint and partner.peppol_eas:
error = self._build_error_peppol_endpoint(partner.peppol_eas, partner.peppol_endpoint)
if error:
raise ValidationError(error)
@api.model
def _get_ubl_cii_formats(self):
return list(self._get_ubl_cii_formats_info().keys())
@api.model
def _get_ubl_cii_formats_info(self):
return {
'ubl_bis3': {'countries': list(PEPPOL_DEFAULT_COUNTRIES), 'on_peppol': True, 'sequence': 200},
'xrechnung': {'countries': ['DE'], 'on_peppol': True},
'ubl_a_nz': {'countries': ['NZ', 'AU'], 'on_peppol': True},
'nlcius': {'countries': ['NL'], 'on_peppol': True},
'ubl_sg': {'countries': ['SG'], 'on_peppol': True},
'facturx': {'countries': ['FR'], 'on_peppol': False},
}
@api.model
def _get_ubl_cii_formats_by_country(self):
formats_info = self._get_ubl_cii_formats_info()
countries = {country for format_val in formats_info.values() for country in (format_val.get('countries') or [])}
return {
country_code: [
format_key
for format_key, format_val in formats_info.items() if country_code in (format_val.get('countries') or [])
]
for country_code in countries
}
def _get_suggested_ubl_cii_edi_format(self):
self.ensure_one()
formats_info = self._get_ubl_cii_formats_info()
format_mapping = self._get_ubl_cii_formats_by_country()
country_code = self._deduce_country_code()
if country_code in format_mapping:
formats_by_country = format_mapping[country_code]
# return the format with the smallest sequence
if len(formats_by_country) == 1:
return formats_by_country[0]
else:
return min(formats_by_country, key=lambda e: formats_info[e].get('sequence', 100)) # we use a sequence of 100 by default
return False
def _get_suggested_invoice_edi_format(self):
# EXTENDS 'account'
return super()._get_suggested_invoice_edi_format() or self._get_suggested_ubl_cii_edi_format()
@api.model
def _get_peppol_formats(self):
formats_info = self._get_ubl_cii_formats_info()
return [format_key for format_key, format_vals in formats_info.items() if format_vals.get('on_peppol')]
def _peppol_eas_endpoint_depends(self):
# field dependencies of methods _compute_peppol_endpoint() and _compute_peppol_eas()
# because we need to extend depends in l10n modules
return ['country_code', 'vat', 'company_registry']
@api.depends(lambda self: self._peppol_eas_endpoint_depends())
def _compute_invoice_edi_format(self):
# EXTENDS 'account' - add depends
super()._compute_invoice_edi_format()
@api.depends_context('company')
@api.depends('invoice_edi_format')
def _compute_is_ubl_format(self):
for partner in self:
partner.is_ubl_format = partner.invoice_edi_format in self._get_ubl_cii_formats()
@api.depends_context('company')
@api.depends('invoice_edi_format')
def _compute_is_peppol_edi_format(self):
for partner in self:
partner.is_peppol_edi_format = partner.invoice_edi_format in self._get_peppol_formats()
@api.depends(lambda self: self._peppol_eas_endpoint_depends() + ['peppol_eas'])
def _compute_peppol_endpoint(self):
""" If the EAS changes and a valid endpoint is available, set it. Otherwise, keep the existing value."""
for partner in self:
partner.peppol_endpoint = partner.peppol_endpoint
country_code = partner._deduce_country_code()
if country_code in EAS_MAPPING:
field = EAS_MAPPING[country_code].get(partner.peppol_eas)
if field \
and field in partner._fields \
and partner[field] \
and not partner._build_error_peppol_endpoint(partner.peppol_eas, partner[field]):
partner.peppol_endpoint = partner[field]
@api.depends(lambda self: self._peppol_eas_endpoint_depends())
def _compute_peppol_eas(self):
"""
If the country_code changes, recompute the EAS only if there is a country_code, it exists in the
EAS_MAPPING, and the current EAS is not consistent with the new country_code.
"""
for partner in self:
partner.peppol_eas = partner.peppol_eas
country_code = partner._deduce_country_code()
if country_code in EAS_MAPPING:
eas_to_field = EAS_MAPPING[country_code]
if partner.peppol_eas not in eas_to_field.keys():
new_eas = next(iter(EAS_MAPPING[country_code].keys()))
# Iterate on the possible EAS until a valid one is found
for eas, field in eas_to_field.items():
if field and field in partner._fields and partner[field]:
if not partner._build_error_peppol_endpoint(eas, partner[field]):
new_eas = eas
break
partner.peppol_eas = new_eas
def _build_error_peppol_endpoint(self, eas, endpoint):
""" This function contains all the rules regarding the peppol_endpoint."""
if eas == '0208' and not re.match(r"^\d{10}$", endpoint):
return _("The Peppol endpoint is not valid. The expected format is: 0239843188")
if eas == '0009' and not siret.is_valid(endpoint):
return _("The Peppol endpoint is not valid. The expected format is: 73282932000074")
if eas == '0007' and not re.match(r"^\d{10}$", endpoint):
return _("The Peppol endpoint is not valid. "
"It should contain exactly 10 digits (Company Registry number)."
"The expected format is: 1234567890")
@api.model
def _get_edi_builder(self, invoice_edi_format):
if invoice_edi_format == 'xrechnung':
return self.env['account.edi.xml.ubl_de']
if invoice_edi_format == 'facturx':
return self.env['account.edi.xml.cii']
if invoice_edi_format == 'ubl_a_nz':
return self.env['account.edi.xml.ubl_a_nz']
if invoice_edi_format == 'nlcius':
return self.env['account.edi.xml.ubl_nl']
if invoice_edi_format == 'ubl_bis3':
return self.env['account.edi.xml.ubl_bis3']
if invoice_edi_format == 'ubl_sg':
return self.env['account.edi.xml.ubl_sg']