269 lines
12 KiB
Python
269 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') # TODO remove in master
|
|
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"),
|
|
('9922', "Andorra VAT"),
|
|
('0151', "Australia ABN"),
|
|
('9914', "Austria UID"),
|
|
('9915', "Austria VOKZ"),
|
|
('0208', "Belgian Company Registry"),
|
|
('9925', "Belgian VAT number"),
|
|
('9924', "Bosnia and Herzegovina VAT"),
|
|
('9926', "Bulgaria VAT"),
|
|
('9934', "Croatia VAT"),
|
|
('9928', "Cyprus VAT"),
|
|
('9929', "Czech Republic VAT"),
|
|
('0096', "Denmark P"),
|
|
('0184', "Denmark CVR"),
|
|
('0198', "Denmark SE"),
|
|
('0191', "Estonia Company code"),
|
|
('9931', "Estonia VAT"),
|
|
('0037', "Finland LY-tunnus"),
|
|
('0216', "Finland OVT code"),
|
|
('0213', "Finland VAT"),
|
|
('0002', "France SIRENE"),
|
|
('0009', "France SIRET"),
|
|
('9957', "France VAT"),
|
|
('0204', "Germany Leitweg-ID"),
|
|
('9930', "Germany VAT"),
|
|
('9933', "Greece VAT"),
|
|
('9910', "Hungary VAT"),
|
|
('0196', "Iceland Kennitala"),
|
|
('9935', "Ireland VAT"),
|
|
('0211', "Italia Partita IVA"),
|
|
('0097', "Italia FTI"),
|
|
('0188', "Japan SST"),
|
|
('0221', "Japan IIN"),
|
|
('9939', "Latvia VAT"),
|
|
('9936', "Liechtenstein VAT"),
|
|
('0200', "Lithuania JAK"),
|
|
('9937', "Lithuania VAT"),
|
|
('9938', "Luxembourg VAT"),
|
|
('9942', "Macedonia VAT"),
|
|
('0230', "Malaysia"),
|
|
('9943', "Malta VAT"),
|
|
('9940', "Monaco VAT"),
|
|
('9941', "Montenegro VAT"),
|
|
('0106', "Netherlands KvK"),
|
|
('0190', "Netherlands OIN"),
|
|
('9944', "Netherlands VAT"),
|
|
('0192', "Norway Org.nr."),
|
|
('9945', "Poland VAT"),
|
|
('9946', "Portugal VAT"),
|
|
('9947', "Romania VAT"),
|
|
('9948', "Serbia VAT"),
|
|
('0195', "Singapore UEN"),
|
|
('9949', "Slovenia VAT"),
|
|
('9950', "Slovakia VAT"),
|
|
('9920', "Spain VAT"),
|
|
('0007', "Sweden Org.nr."),
|
|
('9955', "Sweden VAT"),
|
|
('9927', "Swiss VAT"),
|
|
('0183', "Swiss UIDB"),
|
|
('9952', "Turkey VAT"),
|
|
('9932', "United Kingdom VAT"),
|
|
('9959', "USA EIN"),
|
|
('0060', "DUNS Number"),
|
|
('0088', "EAN Location Code"),
|
|
('0130', "Directorates of the European Commission"),
|
|
('0135', "SIA Object Identifiers"),
|
|
('0142', "SECETI Object Identifiers"),
|
|
('0193', "UBL.BE party identifier"),
|
|
('0199', "Legal Entity Identifier (LEI)"),
|
|
('0201', "Codice Univoco Unità Organizzativa iPA"),
|
|
('0202', "Indirizzo di Posta Elettronica Certificata"),
|
|
('0209', "GS1 identification keys"),
|
|
('0210', "Codice Fiscale"),
|
|
('9913', "Business Registers Network"),
|
|
('9918', "S.W.I.F.T"),
|
|
('9919', "Kennziffer des Unternehmensregisters"),
|
|
('9951', "San Marino VAT"),
|
|
('9953', "Vatican VAT"),
|
|
]
|
|
)
|
|
|
|
@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': False}, # Not yet available through Odoo's Access Point, although it's a Peppol valid format
|
|
'nlcius': {'countries': ['NL'], 'on_peppol': True},
|
|
'ubl_sg': {'countries': ['SG'], 'on_peppol': False}, # Same.
|
|
'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()
|
|
format_mapping = self._get_ubl_cii_formats_by_country()
|
|
country_code = self.commercial_partner_id._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:
|
|
formats_info = self._get_ubl_cii_formats_info()
|
|
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_peppol_edi_format(self):
|
|
self.ensure_one()
|
|
suggested_format = self.commercial_partner_id._get_suggested_ubl_cii_edi_format()
|
|
return suggested_format if suggested_format in self.env['res.partner']._get_peppol_formats() else 'ubl_bis3'
|
|
|
|
def _get_peppol_edi_format(self):
|
|
self.ensure_one()
|
|
return self.invoice_edi_format or self._get_suggested_peppol_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']
|