[FIX] account_edi_ubl_cii: select the format based on the partner

Add a selection field on the partner to choose the EDI format.
Merge the `cii_` and `ubl_` fields.

Add an `endpoint_value` and `eas_code` on the partner. These values are
read when generating the Bis 3 (or one of its derivatives) to fill the
`EndpointID` and the `@schemeID` in the xml.

We can remove `l10n_nl_oin` and `l10n_nl_kvk` which are only used by
electronic invoicing, and replace every usage of them by the new fields
`endpoint_value` and `eas_code` (since for NL partners, they should
necessarily represent the KVK or OIN number: eas code '0106' or '0190').

We can also remove the `l10n_lu_peppol_id` module which was only there
to allow an arbitrary number to be set on the `EndpointID` (for
instance, for public administration that doesn't have a vat number).

task-3232845

closes odoo/odoo#115934

X-original-commit: 8bef3ac696d387a5b5867bc239d628477278fb1b
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
This commit is contained in:
Julien Van Roy
2023-03-21 08:31:06 +01:00
parent c9988a7812
commit 45ae6ad15c
31 changed files with 369 additions and 414 deletions
@@ -166,6 +166,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
composer_form = Form(
self.env['account.move.send'].with_context(default_ctx)
)
composer_form.checkbox_ubl_cii_xml = False
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False):
@@ -195,7 +196,6 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
{'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'},
{'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'},
{'name': _exp_report_name, 'type': 'application/pdf'},
{'name': 'factur-x.xml', 'type': 'text/plain'}, # 'test_enable'
],
'body_content': _exp_body_tip,
'email_from': self.user_account_other.email_formatted,
@@ -242,6 +242,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
composer_form = Form(
self.env['account.move.send'].with_context(default_ctx)
)
composer_form.checkbox_ubl_cii_xml = False
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False), \
@@ -261,7 +262,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
)
# print: template-based message
self.assertEqual(len(print_msg.attachment_ids), 4) # + factur-x.xml in tests
self.assertEqual(len(print_msg.attachment_ids), 3) # + factur-x.xml in tests
self.assertNotIn(self.attachments, print_msg.attachment_ids,
'Attachments should be duplicated, not just linked')
self.assertEqual(print_msg.author_id, self.user_account_other.partner_id,
@@ -285,7 +286,6 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
{'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'},
{'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'},
{'name': f'{test_move.name}.pdf', 'type': 'application/pdf'},
{'name': 'factur-x.xml', 'type': 'text/plain'},
],
'body_content': f'TemplateBody for {test_move.name}',
'email_from': self.user_account_other.email_formatted,
@@ -334,6 +334,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
composer_form = Form(
self.env['account.move.send'].with_context(default_ctx)
)
composer_form.checkbox_ubl_cii_xml = False
composer = composer_form.save()
with self.mock_mail_gateway(mail_unlink_sent=False), \
@@ -353,7 +354,7 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
)
# print: template-based message
self.assertEqual(len(print_msg.attachment_ids), 4) # + factur-x.xml in tests
self.assertEqual(len(print_msg.attachment_ids), 3) # + factur-x.xml in tests
self.assertNotIn(self.attachments, print_msg.attachment_ids,
'Attachments should be duplicated, not just linked')
self.assertEqual(print_msg.author_id, self.user_account_other.partner_id,
@@ -377,7 +378,6 @@ class TestAccountComposerPerformance(AccountMoveSendBase):
{'name': 'AttFileName_00.txt', 'raw': b'AttContent_00', 'type': 'text/plain'},
{'name': 'AttFileName_01.txt', 'raw': b'AttContent_01', 'type': 'text/plain'},
{'name': f'{test_move.name}.pdf', 'type': 'application/pdf'},
{'name': 'factur-x.xml', 'type': 'text/plain'},
],
'body_content': f'SpanishBody for {test_move.name}', # translated version
'email_from': self.user_account_other.email_formatted,
@@ -30,6 +30,7 @@ Pro rules and show the errors.
'views/account_move_send_views.xml',
'views/account_journal_views.xml',
'views/res_partner_views.xml',
],
'installable': True,
'auto_install': True,
@@ -12,4 +12,4 @@ from . import account_edi_xml_ubl_a_nz
from . import account_edi_xml_ubl_sg
from . import account_move_send
from . import account_move
from . import account_journal
from . import res_partner
@@ -38,49 +38,51 @@ UOM_TO_UNECE_CODE = {
# -------------------------------------------------------------------------
# ELECTRONIC ADDRESS SCHEME (EAS), see https://docs.peppol.eu/poacc/billing/3.0/codelist/eas/
# -------------------------------------------------------------------------
COUNTRY_EAS = {
'HU': 9910,
'AT': 9915,
'ES': 9920,
'AD': 9922,
'AL': 9923,
'BA': 9924,
'BE': 9925,
'BG': 9926,
'CH': 9927,
'CY': 9928,
'CZ': 9929,
'DE': 9930,
'EE': 9931,
'GB': 9932,
'GR': 9933,
'HR': 9934,
'IE': 9935,
'LI': 9936,
'LT': 9937,
'LU': 9938,
'LV': 9939,
'MC': 9940,
'ME': 9941,
'MK': 9942,
'MT': 9943,
'NL': 9944,
'PL': 9945,
'PT': 9946,
'RO': 9947,
'RS': 9948,
'SI': 9949,
'SK': 9950,
'SM': 9951,
'TR': 9952,
'VA': 9953,
'SE': 9955,
'FR': 9957,
'NO': '0192',
'SG': '0195',
'AU': '0151',
'NZ': '0088',
'FI': '0213',
EAS_MAPPING = {
'AD': {'9922': 'vat'},
'AL': {'9923': 'vat'},
'AT': {'9915': 'vat'},
'AU': {'0151': 'vat'},
'BA': {'9924': 'vat'},
'BE': {'9925': 'vat'},
'BG': {'9926': 'vat'},
'CH': {'9927': 'vat'},
'CY': {'9928': 'vat'},
'CZ': {'9929': 'vat'},
'DE': {'9930': 'vat'},
'EE': {'9931': 'vat'},
'ES': {'9920': 'vat'},
'FI': {'0213': 'vat'},
'FR': {'9957': 'vat', '0009': 'siret'},
'SG': {'0195': 'l10n_sg_unique_entity_number'},
'GB': {'9932': 'vat'},
'GR': {'9933': 'vat'},
'HR': {'9934': 'vat'},
'HU': {'9910': 'vat'},
'IE': {'9935': 'vat'},
'LI': {'9936': 'vat'},
'LT': {'9937': 'vat'},
'LU': {'9938': 'vat'},
'LV': {'9939': 'vat'},
'MC': {'9940': 'vat'},
'ME': {'9941': 'vat'},
'MK': {'9942': 'vat'},
'MT': {'9943': 'vat'},
# Do not add the vat for NL, since: "[NL-R-003] For suppliers in the Netherlands, the legal entity identifier
# MUST be either a KVK or OIN number (schemeID 0106 or 0190)" in the Bis 3 rules (in PartyLegalEntity/CompanyID).
'NL': {'0106': None, '0190': None},
'NO': {'0192': 'l10n_no_bronnoysund_number'},
'NZ': {'0088': 'company_registry'},
'PL': {'9945': 'vat'},
'PT': {'9946': 'vat'},
'RO': {'9947': 'vat'},
'RS': {'9948': 'vat'},
'SE': {'9955': 'vat'},
'SI': {'9949': 'vat'},
'SK': {'9950': 'vat'},
'SM': {'9951': 'vat'},
'TR': {'9952': 'vat'},
'VA': {'9953': 'vat'},
}
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from odoo import models, _
from odoo.addons.account_edi_ubl_cii.models.account_edi_common import COUNTRY_EAS
from stdnum.no import mva
@@ -79,12 +78,10 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
for vals in vals_list:
vals.pop('registration_address_vals', None)
if partner.country_code == 'NL' and 'l10n_nl_oin' in partner._fields:
endpoint = partner.l10n_nl_oin or partner.l10n_nl_kvk
scheme = '0190' if partner.l10n_nl_oin else '0106'
if partner.country_code == 'NL':
vals.update({
'company_id': endpoint,
'company_id_attrs': {'schemeID': scheme},
'company_id': partner.peppol_endpoint,
'company_id_attrs': {'schemeID': partner.peppol_eas},
})
if partner.country_id.code == "LU" and 'l10n_lu_peppol_identifier' in partner._fields and partner.l10n_lu_peppol_identifier:
vals['company_id'] = partner.l10n_lu_peppol_identifier
@@ -103,37 +100,10 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
# EXTENDS account.edi.xml.ubl_21
vals = super()._get_partner_party_vals(partner, role)
vals['endpoint_id'] = partner.vat
vals['endpoint_id_attrs'] = {'schemeID': COUNTRY_EAS.get(partner.country_id.code)}
if partner.country_code == 'NO' and 'l10n_no_bronnoysund_number' in partner._fields:
vals.update({
'endpoint_id': partner.l10n_no_bronnoysund_number,
'endpoint_id_attrs': {'schemeID': '0192'},
})
# [BR-NL-1] Dutch supplier registration number ( AccountingSupplierParty/Party/PartyLegalEntity/CompanyID );
# With a Dutch supplier (NL), SchemeID may only contain 106 (Chamber of Commerce number) or 190 (OIN number).
# [BR-NL-10] At a Dutch supplier, for a Dutch customer ( AccountingCustomerParty ) the customer registration
# number must be filled with Chamber of Commerce or OIN. SchemeID may only contain 106 (Chamber of
# Commerce number) or 190 (OIN number).
if partner.country_code == 'NL' and 'l10n_nl_oin' in partner._fields:
if partner.l10n_nl_oin:
vals.update({
'endpoint_id': partner.l10n_nl_oin,
'endpoint_id_attrs': {'schemeID': '0190'},
})
elif partner.l10n_nl_kvk:
vals.update({
'endpoint_id': partner.l10n_nl_kvk,
'endpoint_id_attrs': {'schemeID': '0106'},
})
if partner.country_id.code == 'SG' and 'l10n_sg_unique_entity_number' in partner._fields:
vals.update({
'endpoint_id': partner.l10n_sg_unique_entity_number,
'endpoint_id_attrs': {'schemeID': '0195'},
})
if partner.country_id.code == "LU" and 'l10n_lu_peppol_identifier' in partner._fields and partner.l10n_lu_peppol_identifier:
vals['endpoint_id'] = partner.l10n_lu_peppol_identifier
vals.update({
'endpoint_id': partner.peppol_endpoint,
'endpoint_id_attrs': {'schemeID': partner.peppol_eas},
})
return vals
@@ -141,10 +111,9 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
# EXTENDS account.edi.xml.ubl_21
vals = super()._get_partner_party_identification_vals_list(partner)
if partner.country_code == 'NL' and 'l10n_nl_oin' in partner._fields:
endpoint = partner.l10n_nl_oin or partner.l10n_nl_kvk
if partner.country_code == 'NL':
vals.append({
'id': endpoint,
'id': partner.peppol_endpoint,
})
return vals
@@ -276,6 +245,14 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
def _export_invoice_constraints(self, invoice, vals):
# EXTENDS account.edi.xml.ubl_21
constraints = super()._export_invoice_constraints(invoice, vals)
constraints.update({
'peppol_eas_is_set_supplier': self._check_required_fields(vals['supplier'], 'peppol_eas'),
'peppol_eas_is_set_customer': self._check_required_fields(vals['customer'], 'peppol_eas'),
'peppol_endpoint_is_set_supplier': self._check_required_fields(vals['supplier'], 'peppol_endpoint'),
'peppol_endpoint_is_set_customer': self._check_required_fields(vals['customer'], 'peppol_endpoint'),
})
constraints.update(
self._invoice_constraints_peppol_en16931_ubl(invoice, vals)
)
@@ -297,15 +274,6 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
and vals['customer'].country_id != vals['supplier'].country_id)
constraints = {
# [BR-S-02]-An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code
# (BT-151) is "Standard rated" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration
# identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63).
# ---
# [BR-CO-26]-In order for the buyer to automatically identify a supplier, the Seller identifier (BT-29),
# the Seller legal registration identifier (BT-30) and/or the Seller VAT identifier (BT-31) shall be present.
'cen_en16931_seller_vat_identifier': self._check_required_fields(
vals['supplier'], 'vat' # this check is larger than the rules above
),
# [BR-61]-If the Payment means type code (BT-81) means SEPA credit transfer, Local credit transfer or
# Non-SEPA international credit transfer, the Payment account identifier (BT-84) shall be present.
# note: Payment account identifier is <cac:PayeeFinancialAccount>
@@ -313,18 +281,6 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
'cen_en16931_payment_account_identifier': self._check_required_fields(
invoice, 'partner_bank_id'
) if vals['vals']['payment_means_vals_list'][0]['payment_means_code'] in (30, 58) else None,
# [BR-62]-The Seller electronic address (BT-34) shall have a Scheme identifier.
# if this fails, it might just be a missing country when mapping the country to the EAS code
'cen_en16931_seller_EAS': self._check_required_fields(
vals['vals']['accounting_supplier_party_vals']['party_vals']['endpoint_id_attrs'], 'schemeID',
_("No Electronic Address Scheme (EAS) could be found for %s.", vals['customer'].name)
),
# [BR-63]-The Buyer electronic address (BT-49) shall have a Scheme identifier.
# if this fails, it might just be a missing country when mapping the country to the EAS code
'cen_en16931_buyer_EAS': self._check_required_fields(
vals['vals']['accounting_customer_party_vals']['party_vals']['endpoint_id_attrs'], 'schemeID',
_("No Electronic Address Scheme (EAS) could be found for %s.", vals['customer'].name)
),
# [BR-IC-12]-In an Invoice with a VAT breakdown (BG-23) where the VAT category code (BT-118) is
# "Intra-community supply" the Deliver to country code (BT-80) shall not be blank.
'cen_en16931_delivery_country_code': self._check_required_fields(
@@ -361,14 +317,6 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
They always refer to the supplier's country.
"""
constraints = {
# PEPPOL-EN16931-R020: Seller electronic address MUST be provided
'peppol_en16931_ubl_seller_endpoint': self._check_required_fields(
vals['supplier'], 'vat'
),
# PEPPOL-EN16931-R010: Buyer electronic address MUST be provided
'peppol_en16931_ubl_buyer_endpoint': self._check_required_fields(
vals['customer'], 'vat'
),
# PEPPOL-EN16931-R003: A buyer reference or purchase order reference MUST be provided.
'peppol_en16931_ubl_buyer_ref_po_ref':
"A buyer reference or purchase order reference must be provided." if self._check_required_fields(
@@ -391,9 +339,9 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
# [NL-R-003] For suppliers in the Netherlands, the legal entity identifier MUST be either a
# KVK or OIN number (schemeID 0106 or 0190)
'nl_r_003': _(
"The supplier %s must have a KVK or OIN number.",
"%s should have a KVK or OIN number: the Peppol e-address (EAS) should be '0106' or '0190'.",
vals['supplier'].display_name
) if 'l10n_nl_oin' not in vals['supplier']._fields or 'l10n_nl_kvk' not in vals['supplier']._fields else '',
) if vals['supplier'].peppol_eas not in ('0106', '0190') else '',
# [NL-R-007] For suppliers in the Netherlands, the supplier MUST provide a means of payment
# (cac:PaymentMeans) if the payment is from customer to supplier
@@ -412,9 +360,9 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel):
# [NL-R-005] For suppliers in the Netherlands, if the customer is in the Netherlands,
# the customer’s legal entity identifier MUST be either a KVK or OIN number (schemeID 0106 or 0190)
'nl_r_005': _(
"The customer %s must have a KVK or OIN number.",
"%s should have a KVK or OIN number: the Peppol e-address (EAS) should be '0106' or '0190'.",
vals['customer'].display_name
) if 'l10n_nl_oin' not in vals['customer']._fields or 'l10n_nl_kvk' not in vals['customer']._fields else '',
) if vals['customer'].peppol_eas not in ('0106', '0190') else '',
})
if vals['supplier'].country_id.code == 'NO':
@@ -1,45 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, fields
from odoo.addons.account_edi_ubl_cii.models.account_edi_common import COUNTRY_EAS
class AccountJournal(models.Model):
_inherit = 'account.journal'
checkbox_cii_xml = fields.Boolean(string='CII Billing', default=False)
can_enable_cii_xml = fields.Boolean(compute='_compute_can_enable_ubl_cii_xml')
checkbox_ubl_xml = fields.Boolean(string='BIS Billing', default=False)
can_enable_ubl_xml = fields.Boolean(compute='_compute_can_enable_ubl_cii_xml')
@api.depends('type', 'company_id')
def _compute_can_enable_ubl_cii_xml(self):
for journal in self:
is_ubl_country = bool(journal._get_ubl_builder())
is_cii_country = bool(journal._get_cii_builder())
journal.can_enable_ubl_xml = journal.type == 'sale' and is_ubl_country
journal.can_enable_cii_xml = journal.type == 'sale' and is_cii_country
def _get_cii_builder(self):
self.ensure_one()
if self.country_code == 'FR':
return self.env['account.edi.xml.cii'], {'facturx_pdfa': True}
if self.country_code == 'DE':
return self.env['account.edi.xml.cii'], {'facturx_pdfa': True}
def _get_ubl_builder(self):
self.ensure_one()
if self.country_code == 'DE':
return self.env['account.edi.xml.ubl_de'], {}
if self.country_code in ('AU', 'NZ'):
return self.env['account.edi.xml.ubl_a_nz'], {}
if self.country_code == 'NL':
return self.env['account.edi.xml.ubl_nl'], {}
if self.country_code == 'SG':
return self.env['account.edi.xml.ubl_sg'], {}
if self.country_code in COUNTRY_EAS:
return self.env['account.edi.xml.ubl_bis3'], {}
@@ -5,27 +5,15 @@ from odoo import api, fields, models
class AccountMove(models.Model):
_inherit = 'account.move'
ubl_xml_id = fields.Many2one(
ubl_cii_xml_id = fields.Many2one(
comodel_name='ir.attachment',
string="UBL Attachment",
compute=lambda self: self._compute_linked_attachment_id('ubl_xml_id', 'ubl_xml_file'),
depends=['ubl_xml_file']
string="Attachment",
compute=lambda self: self._compute_linked_attachment_id('ubl_cii_xml_id', 'ubl_cii_xml_file'),
depends=['ubl_cii_xml_id']
)
ubl_xml_file = fields.Binary(
ubl_cii_xml_file = fields.Binary(
attachment=True,
string="UBL File",
copy=False,
)
cii_xml_id = fields.Many2one(
comodel_name='ir.attachment',
string="CII Attachment",
compute=lambda self: self._compute_linked_attachment_id('cii_xml_id', 'cii_xml_file'),
depends=['cii_xml_file']
)
cii_xml_file = fields.Binary(
comodel_name='ir.attachment',
string="CII File",
string="UBL/CII File",
copy=False,
)
@@ -16,59 +16,43 @@ _logger = logging.getLogger(__name__)
class AccountMoveSend(models.Model):
_inherit = 'account.move.send'
enable_ubl_xml = fields.Boolean(compute='_compute_send_mail_extra_fields')
checkbox_ubl_xml = fields.Boolean(
string="BIS Billing",
compute='_compute_checkbox_ubl_xml',
store=True,
readonly=False,
)
enable_cii_xml = fields.Boolean(compute='_compute_send_mail_extra_fields')
checkbox_cii_xml = fields.Boolean(
string="CII Billing",
compute='_compute_checkbox_cii_xml',
store=True,
readonly=False,
)
enable_ubl_cii_xml = fields.Boolean(compute='_compute_send_mail_extra_fields')
checkbox_ubl_cii_label = fields.Char(compute='_compute_checkbox_ubl_cii_label') # label for the checkbox_ubl_cii_xml field
checkbox_ubl_cii_xml = fields.Boolean(compute='_compute_checkbox_ubl_cii_xml', store=True, readonly=False)
@api.model
def _get_default_enable_cii_xml(self, move):
return move.journal_id._get_cii_builder() and\
not move.invoice_pdf_report_id and\
move.journal_id.checkbox_cii_xml
@api.model
def _get_default_enable_ubl_xml(self, move):
return move.journal_id._get_ubl_builder() and\
not move.invoice_pdf_report_id and\
move.journal_id.checkbox_ubl_xml
def _get_default_enable_ubl_cii_xml(self, move):
return not move.invoice_pdf_report_id and move.is_sale_document() and move.partner_id.ubl_cii_format
# -------------------------------------------------------------------------
# COMPUTE METHODS
# -------------------------------------------------------------------------
@api.depends('move_ids')
def _compute_checkbox_ubl_cii_label(self):
for wizard in self:
code_to_label = dict(wizard.move_ids.partner_id._fields['ubl_cii_format'].selection)
codes = wizard.move_ids.partner_id.mapped('ubl_cii_format')
if any(codes):
wizard.checkbox_ubl_cii_label = ", ".join(code_to_label[c] for c in codes)
else:
wizard.checkbox_ubl_cii_label = False
def _compute_send_mail_extra_fields(self):
# EXTENDS 'account'
super()._compute_send_mail_extra_fields()
for wizard in self:
wizard.enable_ubl_xml = any([self._get_default_enable_ubl_xml(m) for m in wizard.move_ids])
wizard.enable_cii_xml = any([self._get_default_enable_cii_xml(m) for m in wizard.move_ids])
wizard.enable_ubl_cii_xml = any(self._get_default_enable_ubl_cii_xml(m) for m in wizard.move_ids)
@api.depends('checkbox_ubl_xml', 'checkbox_cii_xml')
@api.depends('checkbox_ubl_cii_xml')
def _compute_mail_attachments_widget(self):
# EXTENDS 'account' - add depends
super()._compute_mail_attachments_widget()
@api.depends('enable_cii_xml')
def _compute_checkbox_cii_xml(self):
@api.depends('enable_ubl_cii_xml')
def _compute_checkbox_ubl_cii_xml(self):
for wizard in self:
wizard.checkbox_cii_xml = True
@api.depends('enable_ubl_xml')
def _compute_checkbox_ubl_xml(self):
for wizard in self:
wizard.checkbox_ubl_xml = True
wizard.checkbox_ubl_cii_xml = True
# -------------------------------------------------------------------------
# ATTACHMENTS
@@ -77,14 +61,14 @@ class AccountMoveSend(models.Model):
@api.model
def _get_linked_attachments(self, move):
# EXTENDS 'account'
return super()._get_linked_attachments(move) + move.cii_xml_id + move.ubl_xml_id
return super()._get_linked_attachments(move) + move.ubl_cii_xml_id
def _get_placeholder_mail_attachments_data(self, move):
# EXTENDS 'account'
results = super()._get_placeholder_mail_attachments_data(move)
if self.enable_ubl_xml and self.checkbox_ubl_xml:
builder, _options = move.journal_id._get_ubl_builder()
if self.enable_ubl_cii_xml and self.checkbox_ubl_cii_xml:
builder = move.partner_id._get_edi_builder()
filename = builder._export_invoice_filename(move)
results.append({
'id': f'placeholder_{filename}',
@@ -103,50 +87,34 @@ class AccountMoveSend(models.Model):
# EXTENDS 'account'
results = super()._prepare_document(invoice)
xmls_to_prepare = []
if self.checkbox_ubl_cii_xml and self._get_default_enable_ubl_cii_xml(invoice):
builder = invoice.partner_id._get_edi_builder()
# CII
cii_xml = self.checkbox_cii_xml and self._get_default_enable_cii_xml(invoice)
if cii_xml:
builder, options = invoice.journal_id._get_cii_builder()
else:
# Default XML acting as the default EDI for ODOO hidden from the users (facturx).
builder, options = self.env['account.edi.xml.cii'], {'skip_errors': True}
xmls_to_prepare.append(('cii', builder, options))
# UBL
ubl_xml = self.checkbox_ubl_xml and self._get_default_enable_ubl_xml(invoice)
if ubl_xml:
builder, options = invoice.journal_id._get_ubl_builder()
xmls_to_prepare.append(('ubl', builder, options))
for edi_type, builder, options in xmls_to_prepare:
xml_content, errors = builder._export_invoice(invoice)
filename = builder._export_invoice_filename(invoice)
# Failed.
if errors and not options.get('skip_errors'):
if errors:
return {
'error': "".join([
_("Errors occur while creating the EDI document (format: %s):", builder._description),
_("Errors occured while creating the EDI document (format: %s):", builder._description),
"\n",
"<p><li>" + "</li><li>".join(errors) + "</li></p>" if self.mode == 'invoice_multi' \
else "\n".join(errors)
]),
}
results[f'{edi_type}_xml_attachment_values'] = {
results['ubl_cii_xml_attachment_values'] = {
'name': filename,
'raw': xml_content,
'mimetype': 'application/xml',
'res_model': invoice._name,
'res_id': invoice.id,
'res_field': f'{edi_type}_xml_file', # Binary field
'res_field': 'ubl_cii_xml_file', # Binary field
}
results[f'{edi_type}_xml_options'] = {
results['ubl_cii_xml_options'] = {
'ubl_cii_format': invoice.partner_id.ubl_cii_format,
'builder': builder,
'options': options,
}
return results
@@ -155,15 +123,24 @@ class AccountMoveSend(models.Model):
# EXTENDS 'account'
super()._postprocess_document(invoice, prepared_data)
cii_xml = prepared_data.get('cii_xml_attachment_values')
ubl_xml = prepared_data.get('ubl_xml_attachment_values')
# Add PDF to XML
if ubl_xml:
if 'ubl_cii_xml_options' in prepared_data and prepared_data['ubl_cii_xml_options']['ubl_cii_format'] != 'facturx':
self._postprocess_invoice_ubl_xml(invoice, prepared_data)
# Add XML to PDF (for all -> inter-portability)
if tools.config['test_enable']: # during tests, no wkhtmltopdf:
# Always silently generate a Factur-X and embed it inside the PDF (inter-portability)
if 'ubl_cii_xml_options' in prepared_data and prepared_data['ubl_cii_xml_options']['ubl_cii_format'] == 'facturx':
xml_facturx = prepared_data['ubl_cii_xml_attachment_values']['raw']
else:
xml_facturx = self.env['account.edi.xml.cii']._export_invoice(invoice)[0]
# during tests, no wkhtmltopdf, create the attachment for test purposes
if tools.config['test_enable']:
self.env['ir.attachment'].create({
'name': 'factur-x.xml',
'raw': xml_facturx,
'res_id': invoice.id,
'res_model': 'account.move',
})
return
# Read pdf content.
@@ -174,11 +151,12 @@ class AccountMoveSend(models.Model):
writer = OdooPdfFileWriter()
writer.cloneReaderDocumentRoot(reader)
writer.addAttachment(cii_xml['name'], cii_xml['raw'], subtype='text/xml')
writer.addAttachment('factur-x.xml', xml_facturx, subtype='text/xml')
# PDF-A.
options = prepared_data['cii_xml_options']['options']
if options.get('facturx_pdfa') and not writer.is_pdfa:
if 'ubl_cii_xml_options' in prepared_data \
and prepared_data['ubl_cii_xml_options']['ubl_cii_format'] == 'facturx' \
and not writer.is_pdfa:
try:
writer.convert_to_pdfa()
except Exception as e:
@@ -203,7 +181,7 @@ class AccountMoveSend(models.Model):
def _postprocess_invoice_ubl_xml(self, invoice, prepared_data):
# Add PDF to XML
tree = etree.fromstring(prepared_data['ubl_xml_attachment_values']['raw'])
tree = etree.fromstring(prepared_data['ubl_cii_xml_attachment_values']['raw'])
anchor_elements = tree.xpath("//*[local-name()='AccountingSupplierParty']")
if not anchor_elements:
return
@@ -228,20 +206,14 @@ class AccountMoveSend(models.Model):
anchor_index = tree.index(anchor_elements[0])
tree.insert(anchor_index, etree.fromstring(to_inject))
prepared_data['ubl_xml_attachment_values']['raw'] = b"<?xml version='1.0' encoding='UTF-8'?>\n" \
prepared_data['ubl_cii_xml_attachment_values']['raw'] = b"<?xml version='1.0' encoding='UTF-8'?>\n" \
+ etree.tostring(cleanup_xml_node(tree))
def _link_document(self, invoice, prepared_data):
# EXTENDS 'account'
super()._link_document(invoice, prepared_data)
ubl_attachment_vals = prepared_data.get('ubl_xml_attachment_values')
if ubl_attachment_vals:
self.env['ir.attachment'].create(ubl_attachment_vals)
invoice.invalidate_model(fnames=['ubl_xml_id', 'ubl_xml_file'])
# during tests, no wkhtmltopdf, but we want to check CII xml
if tools.config['test_enable']:
cii_attachment_vals = prepared_data.get('cii_xml_attachment_values')
self.env['ir.attachment'].create(cii_attachment_vals)
invoice.invalidate_model(fnames=['cii_xml_id', 'cii_xml_file'])
attachment_vals = prepared_data.get('ubl_cii_xml_attachment_values')
if attachment_vals:
self.env['ir.attachment'].create(attachment_vals)
invoice.invalidate_model(fnames=['ubl_cii_xml_id', 'ubl_cii_xml_file'])
@@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api
from odoo.addons.account_edi_ubl_cii.models.account_edi_common import EAS_MAPPING
class ResPartner(models.Model):
_inherit = 'res.partner'
ubl_cii_format = fields.Selection(
string="Format",
selection=[
('facturx', "Factur-X (CII)"),
('xrechnung', "XRechnung (UBL)"),
('nlcius', "NLCIUS"),
('ubl_bis3', "Peppol Bis Billing 3.0"),
('ubl_a_nz', "A-NZ BIS Billing 3.0"),
('ubl_sg', "SG BIS Billing 3.0"),
],
compute='_compute_ubl_cii_format',
store=True,
readonly=False,
)
peppol_endpoint = fields.Char(
string="Peppol Endpoint",
help="Unique identifier used by the Peppol BIS Billing 3.0 and its derivatives, also known as 'Endpoint ID'.",
compute="_compute_peppol_endpoint",
store=True,
readonly=False,
)
peppol_eas = fields.Selection(
string="Peppol e-address (EAS)",
help="""Code used to identify the Endpoint for Peppol 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,
selection=[
('0002', "0002 - System Information et Repertoire des Entreprise et des Etablissements: SIRENE"),
('0007', "0007 - Organisationsnummer (Swedish legal entities)"),
('0009', "0009 - SIRET-CODE"),
('0037', "0037 - LY-tunnus"),
('0060', "0060 - Data Universal Numbering System (D-U-N-S Number)"),
('0088', "0088 - EAN Location Code"),
('0096', "0096 - DANISH CHAMBER OF COMMERCE Scheme (EDIRA compliant)"),
('0097', "0097 - FTI - Ediforum Italia, (EDIRA compliant)"),
('0106', "0106 - Association of Chambers of Commerce and Industry in the Netherlands, (EDIRA compliant)"),
('0130', "0130 - Directorates of the European Commission"),
('0135', "0135 - SIA Object Identifiers"),
('0142', "0142 - SECETI Object Identifiers"),
('0151', "0151 - Australian Business Number (ABN) Scheme"),
('0183', "0183 - Swiss Unique Business Identification Number (UIDB)"),
('0184', "0184 - DIGSTORG"),
('0188', "0188 - Corporate Number of The Social Security and Tax Number System"),
('0190', "0190 - Dutch Originator's Identification Number"),
('0191', "0191 - Centre of Registers and Information Systems of the Ministry of Justice"),
('0192', "0192 - Enhetsregisteret ved Bronnoysundregisterne"),
('0193', "0193 - UBL.BE party identifier"),
('0195', "0195 - Singapore UEN identifier"),
('0196', "0196 - Kennitala - Iceland legal id for individuals and legal entities"),
('0198', "0198 - ERSTORG"),
('0199', "0199 - Legal Entity Identifier (LEI)"),
('0200', "0200 - Legal entity code (Lithuania)"),
('0201', "0201 - Codice Univoco Unità Organizzativa iPA"),
('0202', "0202 - Indirizzo di Posta Elettronica Certificata"),
('0204', "0204 - Leitweg-ID"),
('0208', "0208 - Numero d'entreprise / ondernemingsnummer / Unternehmensnummer"),
('0209', "0209 - GS1 identification keys"),
('0210', "0210 - CODICE FISCALE"),
('0211', "0211 - PARTITA IVA"),
('0212', "0212 - Finnish Organization Identifier"),
('0213', "0213 - Finnish Organization Value Add Tax Identifier"),
('0215', "0215 - Net service ID"),
('0216', "0216 - OVTcode"),
('9901', "9901 - Danish Ministry of the Interior and Health"),
('9910', "9910 - Hungary VAT number"),
('9913', "9913 - Business Registers Network"),
('9914', "9914 - Österreichische Umsatzsteuer-Identifikationsnummer"),
('9915', "9915 - Österreichisches Verwaltungs bzw. Organisationskennzeichen"),
('9918', "9918 - SOCIETY FOR WORLDWIDE INTERBANK FINANCIAL, TELECOMMUNICATION S.W.I.F.T"),
('9919', "9919 - Kennziffer des Unternehmensregisters"),
('9920', "9920 - Agencia Española de Administración Tributaria"),
('9922', "9922 - Andorra VAT number"),
('9923', "9923 - Albania VAT number"),
('9924', "9924 - Bosnia and Herzegovina VAT number"),
('9925', "9925 - Belgium VAT number"),
('9926', "9926 - Bulgaria VAT number"),
('9927', "9927 - Switzerland VAT number"),
('9928', "9928 - Cyprus VAT number"),
('9929', "9929 - Czech Republic VAT number"),
('9930', "9930 - Germany VAT number"),
('9931', "9931 - Estonia VAT number"),
('9932', "9932 - United Kingdom VAT number"),
('9933', "9933 - Greece VAT number"),
('9934', "9934 - Croatia VAT number"),
('9935', "9935 - Ireland VAT number"),
('9936', "9936 - Liechtenstein VAT number"),
('9937', "9937 - Lithuania VAT number"),
('9938', "9938 - Luxemburg VAT number"),
('9939', "9939 - Latvia VAT number"),
('9940', "9940 - Monaco VAT number"),
('9941', "9941 - Montenegro VAT number"),
('9942', "9942 - Macedonia, the former Yugoslav Republic of VAT number"),
('9943', "9943 - Malta VAT number"),
('9944', "9944 - Netherlands VAT number"),
('9945', "9945 - Poland VAT number"),
('9946', "9946 - Portugal VAT number"),
('9947', "9947 - Romania VAT number"),
('9948', "9948 - Serbia VAT number"),
('9949', "9949 - Slovenia VAT number"),
('9950', "9950 - Slovakia VAT number"),
('9951', "9951 - San Marino VAT number"),
('9952', "9952 - Turkey VAT number"),
('9953', "9953 - Holy See (Vatican City State) VAT number"),
('9955', "9955 - Swedish VAT number"),
('9957', "9957 - French VAT number"),
('9959', "9959 - Employer Identification Number (EIN, USA)"),
]
)
@api.depends('country_code')
def _compute_ubl_cii_format(self):
for partner in self:
if partner.country_code == 'DE':
partner.ubl_cii_format = 'xrechnung'
elif partner.country_code in ('AU', 'NZ'):
partner.ubl_cii_format = 'ubl_a_nz'
elif partner.country_code == 'NL':
partner.ubl_cii_format = 'nlcius'
elif partner.country_code == 'FR':
partner.ubl_cii_format = 'facturx'
elif partner.country_code == 'SG':
partner.ubl_cii_format = 'ubl_sg'
elif partner.country_code in EAS_MAPPING:
partner.ubl_cii_format = 'ubl_bis3'
else:
partner.ubl_cii_format = partner.ubl_cii_format
@api.depends('country_code', 'vat')
def _compute_peppol_endpoint(self):
for partner in self:
if partner.ubl_cii_format != 'facturx' and partner.country_code in EAS_MAPPING:
eas_to_field = EAS_MAPPING[partner.country_code]
# Try to set both the peppol_eas and the peppol_endpoint
for field_name in eas_to_field.values():
if field_name and field_name in partner._fields and partner[field_name]:
partner.peppol_endpoint = partner[field_name]
# If it's not possible to set the peppol_endpoint, just set the peppol_eas
if not partner.peppol_endpoint:
partner.peppol_endpoint = partner.peppol_endpoint
else:
partner.peppol_endpoint = partner.peppol_endpoint
@api.depends('country_code', 'vat')
def _compute_peppol_eas(self):
for partner in self:
if partner.ubl_cii_format != 'facturx' and partner.country_code in EAS_MAPPING:
eas_to_field = EAS_MAPPING[partner.country_code]
# Try to set both the peppol_eas and the peppol_endpoint
for eas, field_name in eas_to_field.items():
if field_name in partner._fields and partner[field_name]:
partner.peppol_eas = eas
# If it's not possible to set the peppol_endpoint, just set the peppol_eas
if not partner.peppol_eas:
partner.peppol_eas = list(eas_to_field.keys())[0]
else:
partner.peppol_eas = partner.peppol_eas
def _get_edi_builder(self):
self.ensure_one()
if self.ubl_cii_format == 'xrechnung':
return self.env['account.edi.xml.ubl_de']
if self.ubl_cii_format == 'facturx':
return self.env['account.edi.xml.cii']
if self.ubl_cii_format == 'ubl_a_nz':
return self.env['account.edi.xml.ubl_a_nz']
if self.ubl_cii_format == 'nlcius':
return self.env['account.edi.xml.ubl_nl']
if self.ubl_cii_format == 'ubl_bis3':
return self.env['account.edi.xml.ubl_bis3']
if self.ubl_cii_format == 'ubl_sg':
return self.env['account.edi.xml.ubl_sg']
@@ -9,12 +9,6 @@
<xpath expr="//group[@name='group_edi_config']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<xpath expr="//group[@name='group_edi_config']" position="inside">
<field name="can_enable_ubl_xml" invisible="1"/>
<field name="can_enable_cii_xml" invisible="1"/>
<field name="checkbox_ubl_xml" attrs="{'invisible': [('can_enable_ubl_xml', '=', False)]}"/>
<field name="checkbox_cii_xml" attrs="{'invisible': [('can_enable_cii_xml', '=', False)]}"/>
</xpath>
</field>
</record>
</data>
@@ -7,17 +7,12 @@
<field name="inherit_id" ref="account.account_move_send_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='advanced_options']" position="inside">
<field name="enable_ubl_xml" invisible="1"/>
<field name="enable_cii_xml" invisible="1"/>
<div name="option_ubl_xml"
attrs="{'invisible': [('enable_ubl_xml', '=', False)]}">
<field name="checkbox_ubl_xml"/>
<b><label for="checkbox_ubl_xml"/></b>
</div>
<div name="option_cii_xml"
attrs="{'invisible': [('enable_cii_xml', '=', False)]}">
<field name="checkbox_cii_xml"/>
<b><label for="checkbox_cii_xml"/></b>
<field name="enable_ubl_cii_xml" invisible="1"/>
<div name="option_xml"
attrs="{'invisible': [('enable_ubl_cii_xml', '=', False)]}">
<!-- Use one field as the label for another -->
<field name="checkbox_ubl_cii_xml" nolabel="1" style="display: inline-block"/>
<field name="checkbox_ubl_cii_label" style="position: absolute"/>
</div>
</xpath>
</field>
@@ -0,0 +1,18 @@
<odoo>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">res.partner.property.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='banks']" position="after">
<group string="Electronic Invoicing" name="electronic_invoicing">
<field name="ubl_cii_format" widget="selection"/>
<field name="peppol_eas" widget="selection"
attrs="{'invisible': [('ubl_cii_format', 'in', ('facturx', False))]}"/>
<field name="peppol_endpoint"
attrs="{'invisible': [('ubl_cii_format', 'in', ('facturx', False))]}"/>
</group>
</xpath>
</field>
</record>
</odoo>
@@ -189,9 +189,6 @@ class TestUBLCommon(AccountTestInvoicingCommon):
for i, invoice_line_kwargs in enumerate(invoice_kwargs.get('invoice_line_ids', []))
],
})
# this is needed for formats not enabled by default on the journal
account_move.journal_id.checkbox_cii_xml = True
account_move.journal_id.checkbox_ubl_xml = True
account_move.action_post()
self.action_send_and_print(account_move)
@@ -135,7 +135,7 @@ class TestCIIFR(TestUBLCommon):
}],
)
pdf_attachment = invoice.cii_xml_id
pdf_attachment = invoice.ubl_cii_xml_id
self.assertEqual(pdf_attachment['name'], 'factur-x.xml')
def test_export_import_invoice(self):
@@ -169,7 +169,7 @@ class TestCIIFR(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
invoice.cii_xml_id,
invoice.ubl_cii_xml_id,
xpaths='''
<xpath expr="./*[local-name()='ExchangedDocument']/*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -217,7 +217,7 @@ class TestCIIFR(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
refund.cii_xml_id,
refund.ubl_cii_xml_id,
xpaths='''
<xpath expr="./*[local-name()='ExchangedDocument']/*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -270,7 +270,7 @@ class TestCIIFR(TestUBLCommon):
],
)
self._assert_invoice_attachment(
invoice.cii_xml_id,
invoice.ubl_cii_xml_id,
xpaths='''
<xpath expr="./*[local-name()='ExchangedDocument']/*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -289,7 +289,7 @@ class TestCIIFR(TestUBLCommon):
move_type='out_invoice',
invoice_line_ids=[{'product_id': self.product_a.id}],
)
self._test_encoding_in_attachment(invoice.cii_xml_id, 'factur-x.xml')
self._test_encoding_in_attachment(invoice.ubl_cii_xml_id, 'factur-x.xml')
####################################################
# Test import
@@ -302,7 +302,7 @@ class TestCIIFR(TestUBLCommon):
move_type='out_invoice',
invoice_line_ids=[{'product_id': self.product_a.id}],
)
self._test_import_partner(invoice.cii_xml_id, self.partner_1, self.partner_2)
self._test_import_partner(invoice.ubl_cii_xml_id, self.partner_1, self.partner_2)
def test_import_tax_included(self):
"""
@@ -53,8 +53,11 @@ class TestCIIUS(TestUBLCommon):
)
# Default XML acting as the default EDI
edi_attachment = invoice.cii_xml_id
self.assertTrue(edi_attachment)
edi_attachment = self.env['ir.attachment'].search([
('res_model', '=', 'account.move'),
('res_id', '=', invoice.id)
])
self.assertEqual(edi_attachment.name, "factur-x.xml")
def test_import_facturx_us_company(self):
""" Even for a US company, importing a PDF containing a Factur-X xml
@@ -87,7 +87,7 @@ class TestUBLAU(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
invoice.ubl_xml_id,
invoice.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -145,7 +145,7 @@ class TestUBLAU(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
refund.ubl_xml_id,
refund.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -112,7 +112,7 @@ class TestUBLBE(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
invoice.ubl_xml_id,
invoice.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -170,7 +170,7 @@ class TestUBLBE(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
refund.ubl_xml_id,
refund.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -204,7 +204,7 @@ class TestUBLBE(TestUBLCommon):
move_type='out_invoice',
invoice_line_ids=[{'product_id': self.product_a.id}],
)
self._test_encoding_in_attachment(invoice.ubl_xml_id, 'ubl_bis3.xml')
self._test_encoding_in_attachment(invoice.ubl_cii_xml_id, 'ubl_bis3.xml')
####################################################
# Test import
@@ -217,7 +217,7 @@ class TestUBLBE(TestUBLCommon):
move_type='out_invoice',
invoice_line_ids=[{'product_id': self.product_a.id}],
)
self._test_import_partner(invoice.cii_xml_id, self.partner_1, self.partner_2)
self._test_import_partner(invoice.ubl_cii_xml_id, self.partner_1, self.partner_2)
def test_import_export_invoice_xml(self):
"""
@@ -247,7 +247,7 @@ class TestUBLBE(TestUBLCommon):
}],
)
attachment = invoice.ubl_xml_id
attachment = invoice.ubl_cii_xml_id
self.assertTrue(attachment)
xml_content = base64.b64decode(attachment.with_context(bin_size=False).datas)
@@ -99,7 +99,7 @@ class TestUBLDE(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
invoice.ubl_xml_id,
invoice.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -157,7 +157,7 @@ class TestUBLDE(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
refund.ubl_xml_id,
refund.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -222,7 +222,7 @@ class TestUBLDE(TestUBLCommon):
)
partner = invoice.commercial_partner_id
attachment = invoice.ubl_xml_id
attachment = invoice.ubl_cii_xml_id
self.assertTrue(attachment)
@@ -20,7 +20,8 @@ class TestUBLNL(TestUBLCommon):
'email': 'info@outlook.nl',
'country_id': cls.env.ref('base.nl').id,
'bank_ids': [(0, 0, {'acc_number': 'NL000099998B57'})],
'l10n_nl_kvk': '77777677',
'peppol_eas': '0106',
'peppol_endpoint': '77777677',
'ref': 'ref_partner_1',
})
@@ -32,7 +33,8 @@ class TestUBLNL(TestUBLCommon):
'vat': 'NL41452B11',
'country_id': cls.env.ref('base.nl').id,
'bank_ids': [(0, 0, {'acc_number': 'NL93999574162167'})],
'l10n_nl_kvk': '1234567',
'peppol_eas': '0106',
'peppol_endpoint': '1234567',
'ref': 'ref_partner_2',
})
@@ -114,7 +116,7 @@ class TestUBLNL(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
invoice.ubl_xml_id,
invoice.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
@@ -172,7 +174,7 @@ class TestUBLNL(TestUBLCommon):
],
)
attachment = self._assert_invoice_attachment(
refund.ubl_xml_id,
refund.ubl_cii_xml_id,
xpaths=f'''
<xpath expr="./*[local-name()='ID']" position="replace">
<ID>___ignore___</ID>
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
-17
View File
@@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Luxembourg - Peppol Identifier",
'version': '1.0',
'description': """
Some Luxembourg public institutions do not have a VAT number but have been assigned an arbitrary number
(see: https://pch.gouvernement.lu/fr/peppol.html). Thus, this module adds the Peppol Identifier field on
the account.move form view. If this field is set, it is then read when exporting electronic invoicing formats.
""",
'depends': ['l10n_lu', 'account_edi_ubl_cii'],
'data': [
'views/partner_view.xml',
],
'license': 'LGPL-3',
}
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import res_partner
@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
country_code = fields.Char(related='country_id.code')
l10n_lu_peppol_identifier = fields.Char("Peppol Unique Identifier")
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_partner_form_inherit_l10n_lu_peppol_id" model="ir.ui.view">
<field name="name">res.partner.form.inherit.l10n_lu_peppol_id</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='misc']" position="inside">
<field name="country_code" invisible="1"/>
<field name="l10n_lu_peppol_identifier" attrs="{'invisible': [('country_code', '!=', 'LU')]}"/>
</xpath>
</field>
</record>
</odoo>
-2
View File
@@ -13,8 +13,6 @@
'data': [
'data/account_account_tag.xml',
'data/account_tax_report_data.xml',
'views/res_partner_views.xml',
'views/res_company_views.xml',
],
'demo': [
'demo/demo_company.xml',
-1
View File
@@ -11,7 +11,6 @@
<field name="phone">+31 6 12345678</field>
<field name="email">info@company.nlexample.com</field>
<field name="website">www.nlexample.com</field>
<field name="l10n_nl_kvk">12345678</field>
</record>
<record id="demo_company_nl" model="res.company">
-2
View File
@@ -2,5 +2,3 @@
from . import template_nl
from . import account_journal
from . import account_chart_template
from . import res_partner
from . import res_company
-9
View File
@@ -1,9 +0,0 @@
# coding: utf-8
from odoo import fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
l10n_nl_kvk = fields.Char(related='partner_id.l10n_nl_kvk', readonly=False)
l10n_nl_oin = fields.Char(related='partner_id.l10n_nl_oin', readonly=False)
-9
View File
@@ -1,9 +0,0 @@
# coding: utf-8
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
l10n_nl_kvk = fields.Char(string='KVK number')
l10n_nl_oin = fields.Char(string='Organisation Indentification Number')
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_company_form_inherit_nl" model="ir.ui.view">
<field name="name">res.company.form.inherit.l10n.nl</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='vat']" position="after">
<field name="l10n_nl_kvk" attrs="{'invisible': [('country_code', '!=', 'NL')]}"/>
<field name="l10n_nl_oin" attrs="{'invisible': [('country_code', '!=', 'NL')]}"/>
</xpath>
</field>
</record>
</data>
</odoo>
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_form_inherit_l10n_nl" model="ir.ui.view">
<field name="name">res.partner.form.inherit.l10n_nl</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='vat']" position="after">
<field name="l10n_nl_kvk" attrs="{'invisible': ['|', ('country_code', '!=', 'NL'), ('is_company', '=', False)]}"/>
<field name="l10n_nl_oin" attrs="{'invisible': ['|', ('country_code', '!=', 'NL'), ('is_company', '=', False)]}"/>
</xpath>
</field>
</record>
</odoo>