264 lines
12 KiB
Python
264 lines
12 KiB
Python
# 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']
|