[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:
@@ -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>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
|
||||
@@ -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,5 +2,3 @@
|
||||
from . import template_nl
|
||||
from . import account_journal
|
||||
from . import account_chart_template
|
||||
from . import res_partner
|
||||
from . import res_company
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user