From 47c47bf27045da58aca21051f1de5a5fc9e1c87b Mon Sep 17 00:00:00 2001 From: Julien Van Roy <juvr@odoo.com> Date: Mon, 15 May 2023 09:55:56 +0000 Subject: [PATCH] [FIX] account_edi_ubl_cii: handle fixed taxes in UBL/CII Use case: the eco-taxes (recupel, auvibel, etc) are fixed taxes in Odoo, which apply before the "regular" (percentage) tax on an invoice line. We can have one or more fixed taxes and 1 regular tax. In EN16931, there can only be 1 tax per invoice line. Thus, the fixed taxes are encoded as charge on the invoice lines (with reason code `AEO`: "Collection and recycling"). The tags `AllowanceCharge` (in UBL) and `SpecifiedTradeAllowanceCharge` (in CII) are used. Then a serie of tax related infos need to be changed: the taxes in UBL/CII should not contain the fixed ones and the total untaxed amount needs to be adapted, as well as the total tax amount (since the fixed taxes were removed). To be able to import the fixed taxes back in Odoo, the charges on the invoice lines are read and their names and amounts are used to search on the existing taxes. task-3274208 closes odoo/odoo#122955 X-original-commit: 49ba394ab5637ecc67ae4720af4cc7e3b0ffd372 Signed-off-by: Laurent Smet <las@odoo.com> --- .../data/cii_22_templates.xml | 20 ++- .../models/account_edi_common.py | 51 +++++- .../models/account_edi_xml_cii_facturx.py | 43 ++++- .../models/account_edi_xml_ubl_20.py | 102 ++++++----- .../models/account_edi_xml_ubl_bis3.py | 6 +- .../models/account_edi_xml_ubl_nlcius.py | 4 +- .../tests/common.py | 21 +++ .../from_odoo/bis3_ecotaxes_case1.xml | 132 +++++++++++++++ .../from_odoo/bis3_ecotaxes_case2.xml | 138 +++++++++++++++ .../from_odoo/bis3_ecotaxes_case3.xml | 132 +++++++++++++++ .../from_odoo/facturx_ecotaxes_case1.xml | 152 +++++++++++++++++ .../from_odoo/facturx_ecotaxes_case2.xml | 160 ++++++++++++++++++ .../from_odoo/facturx_ecotaxes_case3.xml | 152 +++++++++++++++++ .../tests/test_xml_cii_fr.py | 81 +++++++++ .../tests/test_xml_ubl_be.py | 85 +++++++++- 15 files changed, 1225 insertions(+), 54 deletions(-) create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case1.xml create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case2.xml create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case3.xml create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case1.xml create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case2.xml create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case3.xml diff --git a/addons/account_edi_ubl_cii/data/cii_22_templates.xml b/addons/account_edi_ubl_cii/data/cii_22_templates.xml index 2bc3c38a11cc..6cdad2074593 100644 --- a/addons/account_edi_ubl_cii/data/cii_22_templates.xml +++ b/addons/account_edi_ubl_cii/data/cii_22_templates.xml @@ -69,9 +69,21 @@ </ram:ApplicableTradeTax> </t> + <!-- Allowance/Charge on the line --> + <t t-foreach="line_vals.get('allowance_charge_vals_list')" t-as="allowance_charge_vals"> + <ram:SpecifiedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator t-esc="allowance_charge_vals['indicator']"/> + </ram:ChargeIndicator> + <ram:ActualAmount t-esc="format_monetary(allowance_charge_vals['amount'], 2)"/> + <ram:ReasonCode t-esc="allowance_charge_vals['reason_code']"/> + <ram:Reason t-esc="allowance_charge_vals['reason']"/> + </ram:SpecifiedTradeAllowanceCharge> + </t> + <!-- Subtotal. --> <ram:SpecifiedTradeSettlementLineMonetarySummation> - <ram:LineTotalAmount t-out="format_monetary(line.price_subtotal, 2)"/> + <ram:LineTotalAmount t-esc="format_monetary(line_vals['line_total_amount'], 2)"/> </ram:SpecifiedTradeSettlementLineMonetarySummation> </ram:SpecifiedLineTradeSettlement> @@ -275,12 +287,12 @@ <!-- Summary. --> <ram:SpecifiedTradeSettlementHeaderMonetarySummation> <ram:LineTotalAmount - t-out="format_monetary(record.amount_untaxed, 2)"/> + t-esc="format_monetary(tax_basis_total_amount, 2)"/> <ram:TaxBasisTotalAmount - t-out="format_monetary(record.amount_untaxed, 2)"/> + t-esc="format_monetary(tax_basis_total_amount, 2)"/> <ram:TaxTotalAmount t-att-currencyID="currency.name" - t-out="format_monetary(record.amount_tax, 2)"/> + t-esc="format_monetary(tax_total_amount, 2)"/> <ram:GrandTotalAmount t-out="format_monetary(record.amount_total, 2)"/> <ram:TotalPrepaidAmount diff --git a/addons/account_edi_ubl_cii/models/account_edi_common.py b/addons/account_edi_ubl_cii/models/account_edi_common.py index e0f057ca8a66..6e6a501158c0 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_common.py +++ b/addons/account_edi_ubl_cii/models/account_edi_common.py @@ -572,6 +572,7 @@ class AccountEdiCommon(models.AbstractModel): product_uom_id = self.env.ref(uom_infered_xmlid[0], raise_if_not_found=False) # allow_charge_amount + fixed_taxes_list = [] allow_charge_amount = 0 # if positive: it's a discount, if negative: it's a charge allow_charge_nodes = tree.findall(xpath_dict['allowance_charge']) for allow_charge_el in allow_charge_nodes: @@ -581,8 +582,17 @@ class AccountEdiCommon(models.AbstractModel): else: discount_factor = -1 # it's a charge amount = allow_charge_el.find(xpath_dict['allowance_charge_amount']) + reason_code = allow_charge_el.find(xpath_dict['allowance_charge_reason_code']) + reason = allow_charge_el.find(xpath_dict['allowance_charge_reason']) if amount is not None: - allow_charge_amount += float(amount.text) * discount_factor + if reason_code is not None and reason_code.text == 'AEO' and reason is not None: + # Handle Fixed Taxes: when exporting from Odoo, we use the allowance_charge node + fixed_taxes_list.append({ + 'tax_name': reason.text, + 'tax_amount': float(amount.text), + }) + else: + allow_charge_amount += float(amount.text) * discount_factor # line_net_subtotal (mandatory) price_subtotal = None @@ -607,8 +617,9 @@ class AccountEdiCommon(models.AbstractModel): # discount discount = 0 + amount_fixed_taxes = sum(d['tax_amount'] for d in fixed_taxes_list) if billed_qty * price_unit != 0 and price_subtotal is not None: - discount = 100 * (1 - price_subtotal / (billed_qty * price_unit)) + discount = 100 * (1 - (price_subtotal - amount_fixed_taxes) / (billed_qty * price_unit)) # Sometimes, the xml received is very bad: unit price = 0, qty = 1, but price_subtotal = -200 # for instance, when filling a down payment as an invoice line. The equation in the docstring is not @@ -621,8 +632,31 @@ class AccountEdiCommon(models.AbstractModel): 'price_unit': price_unit, 'discount': discount, 'product_uom_id': product_uom_id, + 'fixed_taxes_list': fixed_taxes_list, } + def _import_retrieve_fixed_tax(self, invoice_line_form, fixed_tax_vals): + """ Retrieve the fixed tax at import, iteratively search for a tax: + 1. not price_include matching the name and the amount + 2. not price_include matching the amount + 3. price_include matching the name and the amount + 4. price_include matching the amount + """ + base_domain = [ + ('company_id', '=', invoice_line_form.company_id.id), + ('amount_type', '=', 'fixed'), + ('amount', '=', fixed_tax_vals['tax_amount']), + ] + for price_include in (False, True): + for name in (fixed_tax_vals['tax_name'], False): + domain = base_domain + [('price_include', '=', price_include)] + if name: + domain.append(('name', '=', name)) + tax = self.env['account.tax'].search(domain, limit=1) + if tax: + return tax + return self.env['account.tax'] + def _import_fill_invoice_line_taxes(self, journal, tax_nodes, invoice_line_form, inv_line_vals, logs): # Taxes: all amounts are tax excluded, so first try to fetch price_include=False taxes, # if no results, try to fetch the price_include=True taxes. If results, need to adapt the price_unit. @@ -644,6 +678,19 @@ class AccountEdiCommon(models.AbstractModel): inv_line_vals['price_unit'] *= (1 + tax_incl.amount / 100) else: logs.append(_("Could not retrieve the tax: %s %% for line '%s'.", amount, invoice_line_form.name)) + + # Handle Fixed Taxes + for fixed_tax_vals in inv_line_vals['fixed_taxes_list']: + tax = self._import_retrieve_fixed_tax(invoice_line_form, fixed_tax_vals) + if not tax: + # Nothing found: fix the price_unit s.t. line subtotal is matching the original invoice + inv_line_vals['price_unit'] += fixed_tax_vals['tax_amount'] + elif tax.price_include: + inv_line_vals['taxes'].append(tax.id) + inv_line_vals['price_unit'] += tax.amount + else: + inv_line_vals['taxes'].append(tax.id) + # Set the values on the line_form invoice_line_form.quantity = inv_line_vals['quantity'] if inv_line_vals.get('product_uom_id'): diff --git a/addons/account_edi_ubl_cii/models/account_edi_xml_cii_facturx.py b/addons/account_edi_ubl_cii/models/account_edi_xml_cii_facturx.py index 5e451139fbe5..8a5eb4dfb4a9 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_xml_cii_facturx.py +++ b/addons/account_edi_ubl_cii/models/account_edi_xml_cii_facturx.py @@ -125,11 +125,16 @@ class AccountEdiXmlCII(models.AbstractModel): def grouping_key_generator(base_line, tax_values): tax = tax_values['tax_repartition_line'].tax_id - return { + grouping_key = { **self._get_tax_unece_codes(invoice, tax), 'amount': tax.amount, 'amount_type': tax.amount_type, } + # If the tax is fixed, we want to have one group per tax + # s.t. when the invoice is imported, we can try to guess the fixed taxes + if tax.amount_type == 'fixed': + grouping_key['tax_name'] = tax.name + return grouping_key # Validate the structure of the taxes self._validate_taxes(invoice) @@ -137,6 +142,18 @@ class AccountEdiXmlCII(models.AbstractModel): # Create file content. tax_details = invoice._prepare_edi_tax_details(grouping_key_generator=grouping_key_generator) + # Fixed Taxes: filter them on the document level, and adapt the totals + # Fixed taxes are not supposed to be taxes in real live. However, this is the way in Odoo to manage recupel + # taxes in Belgium. Since only one tax is allowed, the fixed tax is removed from totals of lines but added + # as an extra charge/allowance. + fixed_taxes_keys = [k for k in tax_details['tax_details'] if k['amount_type'] == 'fixed'] + for key in fixed_taxes_keys: + fixed_tax_details = tax_details['tax_details'].pop(key) + tax_details['tax_amount_currency'] -= fixed_tax_details['tax_amount_currency'] + tax_details['tax_amount'] -= fixed_tax_details['tax_amount'] + tax_details['base_amount_currency'] += fixed_tax_details['tax_amount_currency'] + tax_details['base_amount'] += fixed_tax_details['tax_amount'] + if 'siret' in invoice.company_id._fields and invoice.company_id.siret: seller_siret = invoice.company_id.siret else: @@ -195,6 +212,24 @@ class AccountEdiXmlCII(models.AbstractModel): else: template_values['document_context_id'] = "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended" + # Fixed taxes: add them as charges on the invoice lines + for line_vals in template_values['invoice_line_vals_list']: + line_vals['allowance_charge_vals_list'] = [] + for grouping_key, tax_detail in tax_details['tax_details_per_record'][line_vals['line']]['tax_details'].items(): + if grouping_key['amount_type'] == 'fixed': + line_vals['allowance_charge_vals_list'].append({ + 'indicator': 'true', + 'reason': tax_detail['tax_name'], + 'reason_code': 'AEO', + 'amount': tax_detail['tax_amount_currency'], + }) + sum_fixed_taxes = sum(x['amount'] for x in line_vals['allowance_charge_vals_list']) + line_vals['line_total_amount'] = line_vals['line'].price_subtotal + sum_fixed_taxes + + # Fixed taxes: set the total adjusted amounts on the document level + template_values['tax_basis_total_amount'] = tax_details['base_amount_currency'] + template_values['tax_total_amount'] = tax_details['tax_amount_currency'] + return template_values def _export_invoice(self, invoice): @@ -337,8 +372,10 @@ class AccountEdiXmlCII(models.AbstractModel): 'net_price_unit': './{*}SpecifiedLineTradeAgreement/{*}NetPriceProductTradePrice/{*}ChargeAmount', 'billed_qty': './{*}SpecifiedLineTradeDelivery/{*}BilledQuantity', 'allowance_charge': './/{*}SpecifiedLineTradeSettlement/{*}SpecifiedTradeAllowanceCharge', - 'allowance_charge_indicator': './{*}ChargeIndicator/{*}Indicator', # below allowance_charge node - 'allowance_charge_amount': './{*}ActualAmount', # below allowance_charge node + 'allowance_charge_indicator': './{*}ChargeIndicator/{*}Indicator', + 'allowance_charge_amount': './{*}ActualAmount', + 'allowance_charge_reason': './{*}Reason', + 'allowance_charge_reason_code': './{*}ReasonCode', 'line_total_amount': './{*}SpecifiedLineTradeSettlement/{*}SpecifiedTradeSettlementLineMonetarySummation/{*}LineTotalAmount', } inv_line_vals = self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line, qty_factor) diff --git a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py index 2fcdff0dc7b5..b0d6742a4b9c 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py +++ b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_20.py @@ -170,19 +170,23 @@ class AccountEdiXmlUBL20(models.AbstractModel): return [] def _get_invoice_tax_totals_vals_list(self, invoice, taxes_vals): - return [{ + tax_totals_vals = { 'currency': invoice.currency_id, 'currency_dp': invoice.currency_id.decimal_places, 'tax_amount': taxes_vals['tax_amount_currency'], - 'tax_subtotal_vals': [{ - 'currency': invoice.currency_id, - 'currency_dp': invoice.currency_id.decimal_places, - 'taxable_amount': vals['base_amount_currency'], - 'tax_amount': vals['tax_amount_currency'], - 'percent': vals['_tax_category_vals_']['percent'], - 'tax_category_vals': vals['_tax_category_vals_'], - } for vals in taxes_vals['tax_details'].values()], - }] + 'tax_subtotal_vals': [], + } + for grouping_key, vals in taxes_vals['tax_details'].items(): + if grouping_key['tax_amount_type'] != 'fixed': + tax_totals_vals['tax_subtotal_vals'].append({ + 'currency': invoice.currency_id, + 'currency_dp': invoice.currency_id.decimal_places, + 'taxable_amount': vals['base_amount_currency'], + 'tax_amount': vals['tax_amount_currency'], + 'percent': vals['_tax_category_vals_']['percent'], + 'tax_category_vals': vals['_tax_category_vals_'], + }) + return [tax_totals_vals] def _get_invoice_line_item_vals(self, line, taxes_vals): """ Method used to fill the cac:InvoiceLine/cac:Item node. @@ -194,7 +198,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): """ product = line.product_id - taxes = line.tax_ids.flatten_taxes_hierarchy() + taxes = line.tax_ids.flatten_taxes_hierarchy().filtered(lambda t: t.amount_type != 'fixed') tax_category_vals_list = self._get_tax_category_list(line.move_id, taxes) description = line.name and line.name.replace('\n', ', ') @@ -215,26 +219,10 @@ class AccountEdiXmlUBL20(models.AbstractModel): def _get_document_allowance_charge_vals_list(self, invoice): """ https://docs.peppol.eu/poacc/billing/3.0/bis/#_document_level_allowance_or_charge - The aim is to transform the ecotax/récupel into a charge at the document level. - Warning, as the charge is transformed into an allowance, we have to make sure no tax is created on the line - level, otherwise, the TaxInclusiveAmount, will be wrong. """ - vals_list = [] - #for line in invoice.line_ids: - # for tax in line.tax_ids: - # if tax.amount_type == 'fixed': - # total_amount += tax.amount - # vals_list.append({ - # 'charge_indicator': 'true', - # 'allowance_charge_reason_code': 'AEO', # "Collection and recycling" - # 'allowance_charge_reason': 'Collection and recycling', - # 'amount': float(tax.amount), - # 'currency_name': line.currency_id.name, - # 'currency_dp': line.currency_id.decimal_places, - # }) - return vals_list - - def _get_invoice_line_allowance_vals_list(self, line): + return [] + + def _get_invoice_line_allowance_vals_list(self, line, tax_values_list=None): """ Method used to fill the cac:InvoiceLine>cac:AllowanceCharge node. Allowances are distinguished from charges using the ChargeIndicator node with 'false' as value. @@ -245,8 +233,20 @@ class AccountEdiXmlUBL20(models.AbstractModel): :param line: An invoice line. :return: A list of python dictionaries. """ + fixed_tax_charge_vals_list = [] + for grouping_key, tax_details in tax_values_list['tax_details'].items(): + if grouping_key['tax_amount_type'] == 'fixed': + fixed_tax_charge_vals_list.append({ + 'currency_name': line.currency_id.name, + 'currency_dp': line.currency_id.decimal_places, + 'charge_indicator': 'true', + 'allowance_charge_reason_code': 'AEO', + 'allowance_charge_reason': tax_details['tax_name'], + 'amount': tax_details['tax_amount_currency'], + }) + if not line.discount: - return [] + return fixed_tax_charge_vals_list # Price subtotal without discount: net_price_subtotal = line.price_subtotal @@ -272,7 +272,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): 'amount': gross_price_subtotal - net_price_subtotal, } - return [allowance_vals] + return [allowance_vals] + fixed_tax_charge_vals_list def _get_invoice_line_price_vals(self, line): """ Method used to fill the cac:InvoiceLine/cac:Price node. @@ -314,10 +314,14 @@ class AccountEdiXmlUBL20(models.AbstractModel): :param line: An invoice line. :return: A python dictionary. """ - allowance_charge_vals_list = self._get_invoice_line_allowance_vals_list(line) + allowance_charge_vals_list = self._get_invoice_line_allowance_vals_list(line, tax_values_list=taxes_vals) uom = super()._get_uom_unece_code(line) - + total_fixed_tax_amount = sum( + vals['amount'] + for vals in allowance_charge_vals_list + if vals['allowance_charge_reason_code'] == 'AEO' + ) return { 'currency': line.currency_id, 'currency_dp': line.currency_id.decimal_places, @@ -328,7 +332,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): 'invoiced_quantity': line.quantity, 'invoiced_quantity_attrs': {'unitCode': uom}, - 'line_extension_amount': line.price_subtotal, + 'line_extension_amount': line.price_subtotal + total_fixed_tax_amount, 'allowance_charge_vals': allowance_charge_vals_list, 'tax_total_vals': self._get_invoice_tax_totals_vals_list(line.move_id, taxes_vals), @@ -340,11 +344,17 @@ class AccountEdiXmlUBL20(models.AbstractModel): def grouping_key_generator(base_line, tax_values): tax = tax_values['tax_repartition_line'].tax_id tax_category_vals = self._get_tax_category_list(invoice, tax)[0] - return { + grouping_key = { 'tax_category_id': tax_category_vals['id'], 'tax_category_percent': tax_category_vals['percent'], '_tax_category_vals_': tax_category_vals, + 'tax_amount_type': tax.amount_type, } + # If the tax is fixed, we want to have one group per tax + # s.t. when the invoice is imported, we can try to guess the fixed taxes + if tax.amount_type == 'fixed': + grouping_key['tax_name'] = tax.name + return grouping_key # Validate the structure of the taxes self._validate_taxes(invoice) @@ -352,6 +362,18 @@ class AccountEdiXmlUBL20(models.AbstractModel): # Compute the tax details for the whole invoice and each invoice line separately. taxes_vals = invoice._prepare_edi_tax_details(grouping_key_generator=grouping_key_generator) + # Fixed Taxes: filter them on the document level, and adapt the totals + # Fixed taxes are not supposed to be taxes in real live. However, this is the way in Odoo to manage recupel + # taxes in Belgium. Since only one tax is allowed, the fixed tax is removed from totals of lines but added + # as an extra charge/allowance. + fixed_taxes_keys = [k for k in taxes_vals['tax_details'] if k['tax_amount_type'] == 'fixed'] + for key in fixed_taxes_keys: + fixed_tax_details = taxes_vals['tax_details'].pop(key) + taxes_vals['tax_amount_currency'] -= fixed_tax_details['tax_amount_currency'] + taxes_vals['tax_amount'] -= fixed_tax_details['tax_amount'] + taxes_vals['base_amount_currency'] += fixed_tax_details['tax_amount_currency'] + taxes_vals['base_amount'] += fixed_tax_details['tax_amount'] + # Compute values for invoice lines. line_extension_amount = 0.0 @@ -424,7 +446,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): 'currency': invoice.currency_id, 'currency_dp': invoice.currency_id.decimal_places, 'line_extension_amount': line_extension_amount, - 'tax_exclusive_amount': invoice.amount_untaxed, + 'tax_exclusive_amount': taxes_vals['base_amount_currency'], 'tax_inclusive_amount': invoice.amount_total, 'allowance_total_amount': allowance_total_amount or None, 'prepaid_amount': invoice.amount_total - invoice.amount_residual, @@ -606,8 +628,10 @@ class AccountEdiXmlUBL20(models.AbstractModel): 'net_price_unit': './{*}Price/{*}PriceAmount', 'billed_qty': './{*}InvoicedQuantity' if invoice.move_type in ('in_invoice', 'out_invoice') or qty_factor == -1 else './{*}CreditedQuantity', 'allowance_charge': './/{*}AllowanceCharge', - 'allowance_charge_indicator': './{*}ChargeIndicator', # below allowance_charge node - 'allowance_charge_amount': './{*}Amount', # below allowance_charge node + 'allowance_charge_indicator': './{*}ChargeIndicator', + 'allowance_charge_amount': './{*}Amount', + 'allowance_charge_reason': './{*}AllowanceChargeReason', + 'allowance_charge_reason_code': './{*}AllowanceChargeReasonCode', 'line_total_amount': './{*}LineExtensionAmount', } self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line, qty_factor) diff --git a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py index f1155b0d3e9a..4ef1aeceb3f8 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py +++ b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_bis3.py @@ -236,9 +236,9 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel): return vals_list - def _get_invoice_line_allowance_vals_list(self, line): + def _get_invoice_line_allowance_vals_list(self, line, tax_values_list=None): # EXTENDS account.edi.xml.ubl_21 - vals_list = super()._get_invoice_line_allowance_vals_list(line) + vals_list = super()._get_invoice_line_allowance_vals_list(line, tax_values_list=tax_values_list) for vals in vals_list: vals['currency_dp'] = 2 @@ -352,7 +352,7 @@ class AccountEdiXmlUBLBIS3(models.AbstractModel): } for line in invoice.invoice_line_ids: - if len(line.tax_ids.filtered(lambda t: t.amount_type != 'fixed')) != 1: + if len(line.tax_ids.flatten_taxes_hierarchy().filtered(lambda t: t.amount_type != 'fixed')) != 1: # [UBL-SR-48]-Invoice lines shall have one and only one classified tax category. # /!\ exception: possible to have any number of ecotaxes (fixed tax) with a regular percentage tax constraints.update({'cen_en16931_tax_line': _("Each invoice line shall have one and only one tax.")}) diff --git a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_nlcius.py b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_nlcius.py index 005049c96ad9..9ddbc8d38feb 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_nlcius.py +++ b/addons/account_edi_ubl_cii/models/account_edi_xml_ubl_nlcius.py @@ -55,9 +55,9 @@ class AccountEdiXmlUBLNL(models.AbstractModel): vals.pop('country_subentity') return vals - def _get_invoice_line_allowance_vals_list(self, line): + def _get_invoice_line_allowance_vals_list(self, line, tax_values_list=None): # EXTENDS account.edi.xml.ubl_bis3 - vals_list = super()._get_invoice_line_allowance_vals_list(line) + vals_list = super()._get_invoice_line_allowance_vals_list(line, tax_values_list=tax_values_list) # [BR-NL-32] Use of Discount reason code ( AllowanceChargeReasonCode ) is not recommended. # [BR-EN-34] Use of Charge reason code ( AllowanceChargeReasonCode ) is not recommended. # Careful ! [BR-42]-Each Invoice line allowance (BG-27) shall have an Invoice line allowance reason (BT-139) diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py index f913cfdfae85..c473f1dbf5c7 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py @@ -30,6 +30,22 @@ class TestUBLCommon(AccountEdiTestCommon): cls.tax_armageddon.children_tax_ids.unlink() cls.tax_armageddon.unlink() + # Fixed Taxes + cls.recupel = cls.env['account.tax'].create({ + 'name': "RECUPEL", + 'amount_type': 'fixed', + 'amount': 1, + 'include_base_amount': True, + 'sequence': 1, + }) + cls.auvibel = cls.env['account.tax'].create({ + 'name': "AUVIBEL", + 'amount_type': 'fixed', + 'amount': 1, + 'include_base_amount': True, + 'sequence': 2, + }) + @classmethod def setup_company_data(cls, company_name, chart_template=None, **kwargs): # OVERRIDE to force the company with EUR currency. @@ -95,6 +111,11 @@ class TestUBLCommon(AccountEdiTestCommon): # Create empty account.move, then update a file if move_type == 'in_invoice': invoice = self._create_empty_vendor_bill() + elif move_type == 'out_invoice': + invoice = self.env['account.move'].create({ + 'move_type': move_type, + 'journal_id': self.company_data['default_journal_sale'].id, + }) else: invoice = self.env['account.move'].create({ 'move_type': move_type, diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case1.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case1.xml new file mode 100644 index 000000000000..ae8a5781ef4e --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case1.xml @@ -0,0 +1,132 @@ +<?xml version='1.0' encoding='UTF-8'?> +<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" + xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" + xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> + <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + </cbc:CustomizationID> + <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID> + <cbc:ID>___ignore___</cbc:ID> + <cbc:IssueDate>2017-01-01</cbc:IssueDate> + <cbc:DueDate>2017-02-28</cbc:DueDate> + <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode> + <cbc:Note>test narration</cbc:Note> + <cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode> + <cbc:BuyerReference>ref_partner_2</cbc:BuyerReference> + <cac:OrderReference> + <cbc:ID>___ignore___</cbc:ID> + </cac:OrderReference> + <cac:AccountingSupplierParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0202239951</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_1</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Chaussée de Namur 40</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_1</cbc:RegistrationName> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_1</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingSupplierParty> + <cac:AccountingCustomerParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0477472701</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_2</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Rue des Bourlottes 9</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_2</cbc:RegistrationName> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_2</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingCustomerParty> + <cac:PaymentMeans> + <cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode> + <cbc:PaymentID>___ignore___</cbc:PaymentID> + <cac:PayeeFinancialAccount> + <cbc:ID>BE15001559627230</cbc:ID> + </cac:PayeeFinancialAccount> + </cac:PaymentMeans> + <cac:PaymentTerms> + <cbc:Note>30% Advance End of Following Month</cbc:Note> + </cac:PaymentTerms> + <cac:TaxTotal> + <cbc:TaxAmount currencyID="USD">21.00</cbc:TaxAmount> + <cac:TaxSubtotal> + <cbc:TaxableAmount currencyID="USD">100.00</cbc:TaxableAmount> + <cbc:TaxAmount currencyID="USD">21.00</cbc:TaxAmount> + <cac:TaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:TaxCategory> + </cac:TaxSubtotal> + </cac:TaxTotal> + <cac:LegalMonetaryTotal> + <cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount> + <cbc:TaxExclusiveAmount currencyID="USD">100.00</cbc:TaxExclusiveAmount> + <cbc:TaxInclusiveAmount currencyID="USD">121.00</cbc:TaxInclusiveAmount> + <cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount> + <cbc:PayableAmount currencyID="USD">121.00</cbc:PayableAmount> + </cac:LegalMonetaryTotal> + <cac:InvoiceLine> + <cbc:ID>___ignore___</cbc:ID> + <cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity> + <cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount> + <cac:AllowanceCharge> + <cbc:ChargeIndicator>true</cbc:ChargeIndicator> + <cbc:AllowanceChargeReasonCode>AEO</cbc:AllowanceChargeReasonCode> + <cbc:AllowanceChargeReason>RECUPEL</cbc:AllowanceChargeReason> + <cbc:Amount currencyID="USD">1.00</cbc:Amount> + </cac:AllowanceCharge> + <cac:Item> + <cbc:Description>product_a</cbc:Description> + <cbc:Name>product_a</cbc:Name> + <cac:ClassifiedTaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:ClassifiedTaxCategory> + </cac:Item> + <cac:Price> + <cbc:PriceAmount currencyID="USD">99.00</cbc:PriceAmount> + </cac:Price> + </cac:InvoiceLine> +</Invoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case2.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case2.xml new file mode 100644 index 000000000000..578fd806a65c --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case2.xml @@ -0,0 +1,138 @@ +<?xml version='1.0' encoding='UTF-8'?> +<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" + xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" + xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"> + <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + </cbc:CustomizationID> + <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID> + <cbc:ID>___ignore___</cbc:ID> + <cbc:IssueDate>2017-01-01</cbc:IssueDate> + <cbc:DueDate>2017-02-28</cbc:DueDate> + <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode> + <cbc:Note>test narration</cbc:Note> + <cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode> + <cbc:BuyerReference>ref_partner_2</cbc:BuyerReference> + <cac:OrderReference> + <cbc:ID>___ignore___</cbc:ID> + </cac:OrderReference> + <cac:AccountingSupplierParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0202239951</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_1</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Chaussée de Namur 40</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_1</cbc:RegistrationName> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_1</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingSupplierParty> + <cac:AccountingCustomerParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0477472701</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_2</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Rue des Bourlottes 9</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_2</cbc:RegistrationName> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_2</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingCustomerParty> + <cac:PaymentMeans> + <cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode> + <cbc:PaymentID>___ignore___</cbc:PaymentID> + <cac:PayeeFinancialAccount> + <cbc:ID>BE15001559627230</cbc:ID> + </cac:PayeeFinancialAccount> + </cac:PaymentMeans> + <cac:PaymentTerms> + <cbc:Note>30% Advance End of Following Month</cbc:Note> + </cac:PaymentTerms> + <cac:TaxTotal> + <cbc:TaxAmount currencyID="USD">21.00</cbc:TaxAmount> + <cac:TaxSubtotal> + <cbc:TaxableAmount currencyID="USD">100.00</cbc:TaxableAmount> + <cbc:TaxAmount currencyID="USD">21.00</cbc:TaxAmount> + <cac:TaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:TaxCategory> + </cac:TaxSubtotal> + </cac:TaxTotal> + <cac:LegalMonetaryTotal> + <cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount> + <cbc:TaxExclusiveAmount currencyID="USD">100.00</cbc:TaxExclusiveAmount> + <cbc:TaxInclusiveAmount currencyID="USD">121.00</cbc:TaxInclusiveAmount> + <cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount> + <cbc:PayableAmount currencyID="USD">121.00</cbc:PayableAmount> + </cac:LegalMonetaryTotal> + <cac:InvoiceLine> + <cbc:ID>___ignore___</cbc:ID> + <cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity> + <cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount> + <cac:AllowanceCharge> + <cbc:ChargeIndicator>true</cbc:ChargeIndicator> + <cbc:AllowanceChargeReasonCode>AEO</cbc:AllowanceChargeReasonCode> + <cbc:AllowanceChargeReason>RECUPEL</cbc:AllowanceChargeReason> + <cbc:Amount currencyID="USD">1.00</cbc:Amount> + </cac:AllowanceCharge> + <cac:AllowanceCharge> + <cbc:ChargeIndicator>true</cbc:ChargeIndicator> + <cbc:AllowanceChargeReasonCode>AEO</cbc:AllowanceChargeReasonCode> + <cbc:AllowanceChargeReason>AUVIBEL</cbc:AllowanceChargeReason> + <cbc:Amount currencyID="USD">1.00</cbc:Amount> + </cac:AllowanceCharge> + <cac:Item> + <cbc:Description>product_a</cbc:Description> + <cbc:Name>product_a</cbc:Name> + <cac:ClassifiedTaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:ClassifiedTaxCategory> + </cac:Item> + <cac:Price> + <cbc:PriceAmount currencyID="USD">98.00</cbc:PriceAmount> + </cac:Price> + </cac:InvoiceLine> +</Invoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case3.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case3.xml new file mode 100644 index 000000000000..3d13eec98279 --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_ecotaxes_case3.xml @@ -0,0 +1,132 @@ +<?xml version='1.0' encoding='UTF-8'?> +<Invoice xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" + xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" + xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> + <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + </cbc:CustomizationID> + <cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID> + <cbc:ID>___ignore___</cbc:ID> + <cbc:IssueDate>2017-01-01</cbc:IssueDate> + <cbc:DueDate>2017-02-28</cbc:DueDate> + <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode> + <cbc:Note>test narration</cbc:Note> + <cbc:DocumentCurrencyCode>USD</cbc:DocumentCurrencyCode> + <cbc:BuyerReference>ref_partner_2</cbc:BuyerReference> + <cac:OrderReference> + <cbc:ID>___ignore___</cbc:ID> + </cac:OrderReference> + <cac:AccountingSupplierParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0202239951</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_1</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Chaussée de Namur 40</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_1</cbc:RegistrationName> + <cbc:CompanyID>BE0202239951</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_1</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingSupplierParty> + <cac:AccountingCustomerParty> + <cac:Party> + <cbc:EndpointID schemeID="9925">BE0477472701</cbc:EndpointID> + <cac:PartyName> + <cbc:Name>partner_2</cbc:Name> + </cac:PartyName> + <cac:PostalAddress> + <cbc:StreetName>Rue des Bourlottes 9</cbc:StreetName> + <cbc:CityName>Ramillies</cbc:CityName> + <cbc:PostalZone>1367</cbc:PostalZone> + <cac:Country> + <cbc:IdentificationCode>BE</cbc:IdentificationCode> + </cac:Country> + </cac:PostalAddress> + <cac:PartyTaxScheme> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:PartyTaxScheme> + <cac:PartyLegalEntity> + <cbc:RegistrationName>partner_2</cbc:RegistrationName> + <cbc:CompanyID>BE0477472701</cbc:CompanyID> + </cac:PartyLegalEntity> + <cac:Contact> + <cbc:Name>partner_2</cbc:Name> + </cac:Contact> + </cac:Party> + </cac:AccountingCustomerParty> + <cac:PaymentMeans> + <cbc:PaymentMeansCode name="credit transfer">30</cbc:PaymentMeansCode> + <cbc:PaymentID>___ignore___</cbc:PaymentID> + <cac:PayeeFinancialAccount> + <cbc:ID>BE15001559627230</cbc:ID> + </cac:PayeeFinancialAccount> + </cac:PaymentMeans> + <cac:PaymentTerms> + <cbc:Note>30% Advance End of Following Month</cbc:Note> + </cac:PaymentTerms> + <cac:TaxTotal> + <cbc:TaxAmount currencyID="USD">21.00</cbc:TaxAmount> + <cac:TaxSubtotal> + <cbc:TaxableAmount currencyID="USD">100.00</cbc:TaxableAmount> + <cbc:TaxAmount currencyID="USD">21.00</cbc:TaxAmount> + <cac:TaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:TaxCategory> + </cac:TaxSubtotal> + </cac:TaxTotal> + <cac:LegalMonetaryTotal> + <cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount> + <cbc:TaxExclusiveAmount currencyID="USD">100.00</cbc:TaxExclusiveAmount> + <cbc:TaxInclusiveAmount currencyID="USD">121.00</cbc:TaxInclusiveAmount> + <cbc:PrepaidAmount currencyID="USD">0.00</cbc:PrepaidAmount> + <cbc:PayableAmount currencyID="USD">121.00</cbc:PayableAmount> + </cac:LegalMonetaryTotal> + <cac:InvoiceLine> + <cbc:ID>___ignore___</cbc:ID> + <cbc:InvoicedQuantity unitCode="C62">1.0</cbc:InvoicedQuantity> + <cbc:LineExtensionAmount currencyID="USD">100.00</cbc:LineExtensionAmount> + <cac:AllowanceCharge> + <cbc:ChargeIndicator>true</cbc:ChargeIndicator> + <cbc:AllowanceChargeReasonCode>AEO</cbc:AllowanceChargeReasonCode> + <cbc:AllowanceChargeReason>RECUPEL</cbc:AllowanceChargeReason> + <cbc:Amount currencyID="USD">1.00</cbc:Amount> + </cac:AllowanceCharge> + <cac:Item> + <cbc:Description>product_a</cbc:Description> + <cbc:Name>product_a</cbc:Name> + <cac:ClassifiedTaxCategory> + <cbc:ID>S</cbc:ID> + <cbc:Percent>21.0</cbc:Percent> + <cac:TaxScheme> + <cbc:ID>VAT</cbc:ID> + </cac:TaxScheme> + </cac:ClassifiedTaxCategory> + </cac:Item> + <cac:Price> + <cbc:PriceAmount currencyID="USD">99.00</cbc:PriceAmount> + </cac:Price> + </cac:InvoiceLine> +</Invoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case1.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case1.xml new file mode 100644 index 000000000000..7e21005f3881 --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case1.xml @@ -0,0 +1,152 @@ +<?xml version='1.0' encoding='UTF-8'?> +<rsm:CrossIndustryInvoice xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" + xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" + xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"> + <rsm:ExchangedDocumentContext> + <ram:GuidelineSpecifiedDocumentContextParameter> + <ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID> + </ram:GuidelineSpecifiedDocumentContextParameter> + </rsm:ExchangedDocumentContext> + <rsm:ExchangedDocument> + <ram:ID>___ignore___</ram:ID> + <ram:TypeCode>380</ram:TypeCode> + <ram:IssueDateTime> + <udt:DateTimeString format="102">20170101</udt:DateTimeString> + </ram:IssueDateTime> + <ram:IncludedNote> + <ram:Content>test narration</ram:Content> + </ram:IncludedNote> + </rsm:ExchangedDocument> + <rsm:SupplyChainTradeTransaction> + <ram:IncludedSupplyChainTradeLineItem> + <ram:AssociatedDocumentLineDocument> + <ram:LineID>1</ram:LineID> + </ram:AssociatedDocumentLineDocument> + <ram:SpecifiedTradeProduct> + <ram:Name>product_a</ram:Name> + </ram:SpecifiedTradeProduct> + <ram:SpecifiedLineTradeAgreement> + <ram:GrossPriceProductTradePrice> + <ram:ChargeAmount>99.00</ram:ChargeAmount> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>99.00</ram:ChargeAmount> + </ram:NetPriceProductTradePrice> + </ram:SpecifiedLineTradeAgreement> + <ram:SpecifiedLineTradeDelivery> + <ram:BilledQuantity unitCode="C62">1.0</ram:BilledQuantity> + </ram:SpecifiedLineTradeDelivery> + <ram:SpecifiedLineTradeSettlement> + <ram:ApplicableTradeTax> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator>true</udt:Indicator> + </ram:ChargeIndicator> + <ram:ActualAmount>1.00</ram:ActualAmount> + <ram:ReasonCode>AEO</ram:ReasonCode> + <ram:Reason>RECUPEL</ram:Reason> + </ram:SpecifiedTradeAllowanceCharge> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:ApplicableHeaderTradeAgreement> + <ram:BuyerReference>ref_partner_2</ram:BuyerReference> + <ram:SellerTradeParty> + <ram:Name>partner_1</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_1</ram:PersonName> + <ram:TelephoneUniversalCommunication> + <ram:CompleteNumber>+1 (650) 555-0111</ram:CompleteNumber> + </ram:TelephoneUniversalCommunication> + <ram:EmailURIUniversalCommunication> + <ram:URIID schemeID="SMTP">partner1@yourcompany.com</ram:URIID> + </ram:EmailURIUniversalCommunication> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>75000</ram:PostcodeCode> + <ram:LineOne>Rue Jean Jaurès, 42</ram:LineOne> + <ram:CityName>Paris</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + <ram:SpecifiedTaxRegistration> + <ram:ID schemeID="VA">FR05677404089</ram:ID> + </ram:SpecifiedTaxRegistration> + </ram:SellerTradeParty> + <ram:BuyerTradeParty> + <ram:Name>partner_2</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_2</ram:PersonName> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>52330</ram:PostcodeCode> + <ram:LineOne>Rue Charles de Gaulle</ram:LineOne> + <ram:CityName>Colombey-les-Deux-Églises</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + <ram:SpecifiedTaxRegistration> + <ram:ID schemeID="VA">FR35562153452</ram:ID> + </ram:SpecifiedTaxRegistration> + </ram:BuyerTradeParty> + <ram:BuyerOrderReferencedDocument> + <ram:IssuerAssignedID>___ignore___</ram:IssuerAssignedID> + </ram:BuyerOrderReferencedDocument> + </ram:ApplicableHeaderTradeAgreement> + <ram:ApplicableHeaderTradeDelivery> + <ram:ShipToTradeParty> + <ram:Name>partner_2</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_2</ram:PersonName> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>52330</ram:PostcodeCode> + <ram:LineOne>Rue Charles de Gaulle</ram:LineOne> + <ram:CityName>Colombey-les-Deux-Églises</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + </ram:ShipToTradeParty> + <ram:ActualDeliverySupplyChainEvent> + <ram:OccurrenceDateTime> + <udt:DateTimeString format="102">20170101</udt:DateTimeString> + </ram:OccurrenceDateTime> + </ram:ActualDeliverySupplyChainEvent> + </ram:ApplicableHeaderTradeDelivery> + <ram:ApplicableHeaderTradeSettlement> + <ram:PaymentReference>___ignore___</ram:PaymentReference> + <ram:InvoiceCurrencyCode>USD</ram:InvoiceCurrencyCode> + <ram:SpecifiedTradeSettlementPaymentMeans> + <ram:TypeCode>42</ram:TypeCode> + <ram:PayeePartyCreditorFinancialAccount> + <ram:ProprietaryID>FR15001559627230</ram:ProprietaryID> + </ram:PayeePartyCreditorFinancialAccount> + </ram:SpecifiedTradeSettlementPaymentMeans> + <ram:ApplicableTradeTax> + <ram:CalculatedAmount>21.00</ram:CalculatedAmount> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:BasisAmount>100.00</ram:BasisAmount> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:DueDateTypeCode>5</ram:DueDateTypeCode> + <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradePaymentTerms> + <ram:Description>30% Advance End of Following Month</ram:Description> + <ram:DueDateDateTime> + <udt:DateTimeString format="102">20170228</udt:DateTimeString> + </ram:DueDateDateTime> + </ram:SpecifiedTradePaymentTerms> + <ram:SpecifiedTradeSettlementHeaderMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + <ram:TaxBasisTotalAmount>100.00</ram:TaxBasisTotalAmount> + <ram:TaxTotalAmount currencyID="USD">21.00</ram:TaxTotalAmount> + <ram:GrandTotalAmount>121.00</ram:GrandTotalAmount> + <ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount> + <ram:DuePayableAmount>121.00</ram:DuePayableAmount> + </ram:SpecifiedTradeSettlementHeaderMonetarySummation> + </ram:ApplicableHeaderTradeSettlement> + </rsm:SupplyChainTradeTransaction> +</rsm:CrossIndustryInvoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case2.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case2.xml new file mode 100644 index 000000000000..aa6224056dcf --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case2.xml @@ -0,0 +1,160 @@ +<?xml version='1.0' encoding='UTF-8'?> +<rsm:CrossIndustryInvoice xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" + xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" + xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"> + <rsm:ExchangedDocumentContext> + <ram:GuidelineSpecifiedDocumentContextParameter> + <ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID> + </ram:GuidelineSpecifiedDocumentContextParameter> + </rsm:ExchangedDocumentContext> + <rsm:ExchangedDocument> + <ram:ID>___ignore___</ram:ID> + <ram:TypeCode>380</ram:TypeCode> + <ram:IssueDateTime> + <udt:DateTimeString format="102">20170101</udt:DateTimeString> + </ram:IssueDateTime> + <ram:IncludedNote> + <ram:Content>test narration</ram:Content> + </ram:IncludedNote> + </rsm:ExchangedDocument> + <rsm:SupplyChainTradeTransaction> + <ram:IncludedSupplyChainTradeLineItem> + <ram:AssociatedDocumentLineDocument> + <ram:LineID>1</ram:LineID> + </ram:AssociatedDocumentLineDocument> + <ram:SpecifiedTradeProduct> + <ram:Name>product_a</ram:Name> + </ram:SpecifiedTradeProduct> + <ram:SpecifiedLineTradeAgreement> + <ram:GrossPriceProductTradePrice> + <ram:ChargeAmount>98.00</ram:ChargeAmount> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>98.00</ram:ChargeAmount> + </ram:NetPriceProductTradePrice> + </ram:SpecifiedLineTradeAgreement> + <ram:SpecifiedLineTradeDelivery> + <ram:BilledQuantity unitCode="C62">1.0</ram:BilledQuantity> + </ram:SpecifiedLineTradeDelivery> + <ram:SpecifiedLineTradeSettlement> + <ram:ApplicableTradeTax> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator>true</udt:Indicator> + </ram:ChargeIndicator> + <ram:ActualAmount>1.00</ram:ActualAmount> + <ram:ReasonCode>AEO</ram:ReasonCode> + <ram:Reason>RECUPEL</ram:Reason> + </ram:SpecifiedTradeAllowanceCharge> + <ram:SpecifiedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator>true</udt:Indicator> + </ram:ChargeIndicator> + <ram:ActualAmount>1.00</ram:ActualAmount> + <ram:ReasonCode>AEO</ram:ReasonCode> + <ram:Reason>AUVIBEL</ram:Reason> + </ram:SpecifiedTradeAllowanceCharge> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:ApplicableHeaderTradeAgreement> + <ram:BuyerReference>ref_partner_2</ram:BuyerReference> + <ram:SellerTradeParty> + <ram:Name>partner_1</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_1</ram:PersonName> + <ram:TelephoneUniversalCommunication> + <ram:CompleteNumber>+1 (650) 555-0111</ram:CompleteNumber> + </ram:TelephoneUniversalCommunication> + <ram:EmailURIUniversalCommunication> + <ram:URIID schemeID="SMTP">partner1@yourcompany.com</ram:URIID> + </ram:EmailURIUniversalCommunication> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>75000</ram:PostcodeCode> + <ram:LineOne>Rue Jean Jaurès, 42</ram:LineOne> + <ram:CityName>Paris</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + <ram:SpecifiedTaxRegistration> + <ram:ID schemeID="VA">FR05677404089</ram:ID> + </ram:SpecifiedTaxRegistration> + </ram:SellerTradeParty> + <ram:BuyerTradeParty> + <ram:Name>partner_2</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_2</ram:PersonName> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>52330</ram:PostcodeCode> + <ram:LineOne>Rue Charles de Gaulle</ram:LineOne> + <ram:CityName>Colombey-les-Deux-Églises</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + <ram:SpecifiedTaxRegistration> + <ram:ID schemeID="VA">FR35562153452</ram:ID> + </ram:SpecifiedTaxRegistration> + </ram:BuyerTradeParty> + <ram:BuyerOrderReferencedDocument> + <ram:IssuerAssignedID>___ignore___</ram:IssuerAssignedID> + </ram:BuyerOrderReferencedDocument> + </ram:ApplicableHeaderTradeAgreement> + <ram:ApplicableHeaderTradeDelivery> + <ram:ShipToTradeParty> + <ram:Name>partner_2</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_2</ram:PersonName> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>52330</ram:PostcodeCode> + <ram:LineOne>Rue Charles de Gaulle</ram:LineOne> + <ram:CityName>Colombey-les-Deux-Églises</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + </ram:ShipToTradeParty> + <ram:ActualDeliverySupplyChainEvent> + <ram:OccurrenceDateTime> + <udt:DateTimeString format="102">20170101</udt:DateTimeString> + </ram:OccurrenceDateTime> + </ram:ActualDeliverySupplyChainEvent> + </ram:ApplicableHeaderTradeDelivery> + <ram:ApplicableHeaderTradeSettlement> + <ram:PaymentReference>___ignore___</ram:PaymentReference> + <ram:InvoiceCurrencyCode>USD</ram:InvoiceCurrencyCode> + <ram:SpecifiedTradeSettlementPaymentMeans> + <ram:TypeCode>42</ram:TypeCode> + <ram:PayeePartyCreditorFinancialAccount> + <ram:ProprietaryID>FR15001559627230</ram:ProprietaryID> + </ram:PayeePartyCreditorFinancialAccount> + </ram:SpecifiedTradeSettlementPaymentMeans> + <ram:ApplicableTradeTax> + <ram:CalculatedAmount>21.00</ram:CalculatedAmount> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:BasisAmount>100.00</ram:BasisAmount> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:DueDateTypeCode>5</ram:DueDateTypeCode> + <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradePaymentTerms> + <ram:Description>30% Advance End of Following Month</ram:Description> + <ram:DueDateDateTime> + <udt:DateTimeString format="102">20170228</udt:DateTimeString> + </ram:DueDateDateTime> + </ram:SpecifiedTradePaymentTerms> + <ram:SpecifiedTradeSettlementHeaderMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + <ram:TaxBasisTotalAmount>100.00</ram:TaxBasisTotalAmount> + <ram:TaxTotalAmount currencyID="USD">21.00</ram:TaxTotalAmount> + <ram:GrandTotalAmount>121.00</ram:GrandTotalAmount> + <ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount> + <ram:DuePayableAmount>121.00</ram:DuePayableAmount> + </ram:SpecifiedTradeSettlementHeaderMonetarySummation> + </ram:ApplicableHeaderTradeSettlement> + </rsm:SupplyChainTradeTransaction> +</rsm:CrossIndustryInvoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case3.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case3.xml new file mode 100644 index 000000000000..6c6a41a46be6 --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_ecotaxes_case3.xml @@ -0,0 +1,152 @@ +<?xml version='1.0' encoding='UTF-8'?> +<rsm:CrossIndustryInvoice xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" + xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" + xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"> + <rsm:ExchangedDocumentContext> + <ram:GuidelineSpecifiedDocumentContextParameter> + <ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID> + </ram:GuidelineSpecifiedDocumentContextParameter> + </rsm:ExchangedDocumentContext> + <rsm:ExchangedDocument> + <ram:ID>___ignore___</ram:ID> + <ram:TypeCode>380</ram:TypeCode> + <ram:IssueDateTime> + <udt:DateTimeString format="102">20170101</udt:DateTimeString> + </ram:IssueDateTime> + <ram:IncludedNote> + <ram:Content>test narration</ram:Content> + </ram:IncludedNote> + </rsm:ExchangedDocument> + <rsm:SupplyChainTradeTransaction> + <ram:IncludedSupplyChainTradeLineItem> + <ram:AssociatedDocumentLineDocument> + <ram:LineID>1</ram:LineID> + </ram:AssociatedDocumentLineDocument> + <ram:SpecifiedTradeProduct> + <ram:Name>product_a</ram:Name> + </ram:SpecifiedTradeProduct> + <ram:SpecifiedLineTradeAgreement> + <ram:GrossPriceProductTradePrice> + <ram:ChargeAmount>99.00</ram:ChargeAmount> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>99.00</ram:ChargeAmount> + </ram:NetPriceProductTradePrice> + </ram:SpecifiedLineTradeAgreement> + <ram:SpecifiedLineTradeDelivery> + <ram:BilledQuantity unitCode="C62">1.0</ram:BilledQuantity> + </ram:SpecifiedLineTradeDelivery> + <ram:SpecifiedLineTradeSettlement> + <ram:ApplicableTradeTax> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator>true</udt:Indicator> + </ram:ChargeIndicator> + <ram:ActualAmount>1.00</ram:ActualAmount> + <ram:ReasonCode>AEO</ram:ReasonCode> + <ram:Reason>RECUPEL</ram:Reason> + </ram:SpecifiedTradeAllowanceCharge> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:ApplicableHeaderTradeAgreement> + <ram:BuyerReference>ref_partner_2</ram:BuyerReference> + <ram:SellerTradeParty> + <ram:Name>partner_1</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_1</ram:PersonName> + <ram:TelephoneUniversalCommunication> + <ram:CompleteNumber>+1 (650) 555-0111</ram:CompleteNumber> + </ram:TelephoneUniversalCommunication> + <ram:EmailURIUniversalCommunication> + <ram:URIID schemeID="SMTP">partner1@yourcompany.com</ram:URIID> + </ram:EmailURIUniversalCommunication> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>75000</ram:PostcodeCode> + <ram:LineOne>Rue Jean Jaurès, 42</ram:LineOne> + <ram:CityName>Paris</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + <ram:SpecifiedTaxRegistration> + <ram:ID schemeID="VA">FR05677404089</ram:ID> + </ram:SpecifiedTaxRegistration> + </ram:SellerTradeParty> + <ram:BuyerTradeParty> + <ram:Name>partner_2</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_2</ram:PersonName> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>52330</ram:PostcodeCode> + <ram:LineOne>Rue Charles de Gaulle</ram:LineOne> + <ram:CityName>Colombey-les-Deux-Églises</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + <ram:SpecifiedTaxRegistration> + <ram:ID schemeID="VA">FR35562153452</ram:ID> + </ram:SpecifiedTaxRegistration> + </ram:BuyerTradeParty> + <ram:BuyerOrderReferencedDocument> + <ram:IssuerAssignedID>___ignore___</ram:IssuerAssignedID> + </ram:BuyerOrderReferencedDocument> + </ram:ApplicableHeaderTradeAgreement> + <ram:ApplicableHeaderTradeDelivery> + <ram:ShipToTradeParty> + <ram:Name>partner_2</ram:Name> + <ram:DefinedTradeContact> + <ram:PersonName>partner_2</ram:PersonName> + </ram:DefinedTradeContact> + <ram:PostalTradeAddress> + <ram:PostcodeCode>52330</ram:PostcodeCode> + <ram:LineOne>Rue Charles de Gaulle</ram:LineOne> + <ram:CityName>Colombey-les-Deux-Églises</ram:CityName> + <ram:CountryID>FR</ram:CountryID> + </ram:PostalTradeAddress> + </ram:ShipToTradeParty> + <ram:ActualDeliverySupplyChainEvent> + <ram:OccurrenceDateTime> + <udt:DateTimeString format="102">20170101</udt:DateTimeString> + </ram:OccurrenceDateTime> + </ram:ActualDeliverySupplyChainEvent> + </ram:ApplicableHeaderTradeDelivery> + <ram:ApplicableHeaderTradeSettlement> + <ram:PaymentReference>___ignore___</ram:PaymentReference> + <ram:InvoiceCurrencyCode>USD</ram:InvoiceCurrencyCode> + <ram:SpecifiedTradeSettlementPaymentMeans> + <ram:TypeCode>42</ram:TypeCode> + <ram:PayeePartyCreditorFinancialAccount> + <ram:ProprietaryID>FR15001559627230</ram:ProprietaryID> + </ram:PayeePartyCreditorFinancialAccount> + </ram:SpecifiedTradeSettlementPaymentMeans> + <ram:ApplicableTradeTax> + <ram:CalculatedAmount>21.00</ram:CalculatedAmount> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:BasisAmount>100.00</ram:BasisAmount> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:DueDateTypeCode>5</ram:DueDateTypeCode> + <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradePaymentTerms> + <ram:Description>30% Advance End of Following Month</ram:Description> + <ram:DueDateDateTime> + <udt:DateTimeString format="102">20170228</udt:DateTimeString> + </ram:DueDateDateTime> + </ram:SpecifiedTradePaymentTerms> + <ram:SpecifiedTradeSettlementHeaderMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + <ram:TaxBasisTotalAmount>100.00</ram:TaxBasisTotalAmount> + <ram:TaxTotalAmount currencyID="USD">21.00</ram:TaxTotalAmount> + <ram:GrandTotalAmount>121.00</ram:GrandTotalAmount> + <ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount> + <ram:DuePayableAmount>121.00</ram:DuePayableAmount> + </ram:SpecifiedTradeSettlementHeaderMonetarySummation> + </ram:ApplicableHeaderTradeSettlement> + </rsm:SupplyChainTradeTransaction> +</rsm:CrossIndustryInvoice> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_cii_fr.py b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_cii_fr.py index d0cbee2a0a2d..14ef99c69b72 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_cii_fr.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_cii_fr.py @@ -43,6 +43,7 @@ class TestCIIFR(TestUBLCommon): 'amount': 21, 'type_tax_use': 'sale', 'country_id': cls.env.ref('base.fr').id, + 'sequence': 10, }) cls.tax_12 = cls.env['account.tax'].create({ @@ -288,6 +289,64 @@ class TestCIIFR(TestUBLCommon): def test_encoding_in_attachment_facturx(self): self._test_encoding_in_attachment('facturx_1_0_05', 'factur-x.xml') + def test_export_with_fixed_taxes_case1(self): + # CASE 1: simple invoice with a recupel tax + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 99, + 'tax_ids': [(6, 0, [self.recupel.id, self.tax_21.id])], + } + ], + ) + self.assertEqual(invoice.amount_total, 121) + self._assert_invoice_attachment(invoice, None, 'from_odoo/facturx_ecotaxes_case1.xml') + + def test_export_with_fixed_taxes_case2(self): + # CASE 2: Same but with several ecotaxes + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 98, + 'tax_ids': [(6, 0, [self.recupel.id, self.auvibel.id, self.tax_21.id])], + } + ], + ) + self.assertEqual(invoice.amount_total, 121) + self._assert_invoice_attachment(invoice, None, 'from_odoo/facturx_ecotaxes_case2.xml') + + def test_export_with_fixed_taxes_case3(self): + # CASE 3: same as Case 1 but taxes are Price Included + self.recupel.price_include = True + self.tax_21.price_include = True + + # Price TTC = 121 = (99 + 1 ) * 1.21 + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 121, + 'tax_ids': [(6, 0, [self.recupel.id, self.tax_21.id])], + } + ], + ) + self.assertEqual(invoice.amount_total, 121) + self._assert_invoice_attachment(invoice, None, 'from_odoo/facturx_ecotaxes_case3.xml') + #################################################### # Test import #################################################### @@ -413,3 +472,25 @@ class TestCIIFR(TestUBLCommon): # source: Facture_F20220029_EN_16931_K.pdf, credit note labelled as an invoice with negative amounts self._assert_imported_invoice_from_file(subfolder=subfolder, filename='facturx_invoice_negative_amounts.xml', amount_total=100, amount_tax=0, list_line_subtotals=[-5, 10, 60, 30, 5], move_type='in_refund') + + def test_import_fixed_taxes(self): + """ Tests whether we correctly decode the xml attachments created using fixed taxes. + See the tests above to create these xml attachments ('test_export_with_fixed_taxes_case_[X]'). + NB: use move_type = 'out_invoice' s.t. we can retrieve the taxes used to create the invoices. + """ + subfolder = "tests/test_files/from_odoo" + self._assert_imported_invoice_from_file( + subfolder=subfolder, filename='facturx_ecotaxes_case1.xml', amount_total=121, amount_tax=22, + list_line_subtotals=[99], currency_id=self.currency_data['currency'].id, list_line_price_unit=[99], + list_line_discount=[0], list_line_taxes=[self.tax_21+self.recupel], move_type='out_invoice', + ) + self._assert_imported_invoice_from_file( + subfolder=subfolder, filename='facturx_ecotaxes_case2.xml', amount_total=121, amount_tax=23, + list_line_subtotals=[98], currency_id=self.currency_data['currency'].id, list_line_price_unit=[98], + list_line_discount=[0], list_line_taxes=[self.tax_21+self.recupel+self.auvibel], move_type='out_invoice', + ) + self._assert_imported_invoice_from_file( + subfolder=subfolder, filename='facturx_ecotaxes_case3.xml', amount_total=121, amount_tax=22, + list_line_subtotals=[99], currency_id=self.currency_data['currency'].id, list_line_price_unit=[99], + list_line_discount=[0], list_line_taxes=[self.tax_21+self.recupel], move_type='out_invoice', + ) diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py index 56cdd1bf96e5..b452f9283320 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py @@ -51,6 +51,7 @@ class TestUBLBE(TestUBLCommon): 'amount': 21, 'type_tax_use': 'sale', 'country_id': cls.env.ref('base.be').id, + 'sequence': 10, }) cls.tax_15 = cls.env['account.tax'].create({ @@ -282,7 +283,65 @@ class TestUBLBE(TestUBLCommon): } ], ) - self._assert_invoice_attachment(invoice, xpaths=None, expected_file='from_odoo/bis3_out_invoice_rounding.xml') + self._assert_invoice_attachment(invoice, None, 'from_odoo/bis3_out_invoice_rounding.xml') + + def test_export_with_fixed_taxes_case1(self): + # CASE 1: simple invoice with a recupel tax + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 99, + 'tax_ids': [(6, 0, [self.recupel.id, self.tax_21.id])], + } + ], + ) + self.assertEqual(invoice.amount_total, 121) + self._assert_invoice_attachment(invoice, None, 'from_odoo/bis3_ecotaxes_case1.xml') + + def test_export_with_fixed_taxes_case2(self): + # CASE 2: Same but with several ecotaxes + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 98, + 'tax_ids': [(6, 0, [self.recupel.id, self.auvibel.id, self.tax_21.id])], + } + ], + ) + self.assertEqual(invoice.amount_total, 121) + self._assert_invoice_attachment(invoice, None, 'from_odoo/bis3_ecotaxes_case2.xml') + + def test_export_with_fixed_taxes_case3(self): + # CASE 3: same as Case 1 but taxes are Price Included + self.recupel.price_include = True + self.tax_21.price_include = True + + # Price TTC = 121 = (99 + 1 ) * 1.21 + invoice = self._generate_move( + self.partner_1, + self.partner_2, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 121, + 'tax_ids': [(6, 0, [self.recupel.id, self.tax_21.id])], + } + ], + ) + self.assertEqual(invoice.amount_total, 121) + self._assert_invoice_attachment(invoice, None, 'from_odoo/bis3_ecotaxes_case3.xml') #################################################### # Test import @@ -417,3 +476,27 @@ class TestUBLBE(TestUBLCommon): invoice, ) self.assertRecordValues(invoice, [{'move_type': 'out_refund', 'amount_total': 3164.22}]) + + def test_import_fixed_taxes(self): + """ Tests whether we correctly decode the xml attachments created using fixed taxes. + See the tests above to create these xml attachments ('test_export_with_fixed_taxes_case_[X]'). + NB: use move_type = 'out_invoice' s.t. we can retrieve the taxes used to create the invoices. + """ + subfolder = "tests/test_files/from_odoo" + # The tax 21% from l10n_be is retrieved since it's a duplicate of self.tax_21 + tax_21 = self.env.ref(f'l10n_be.{self.env.company.id}_attn_VAT-OUT-21-L') + self._assert_imported_invoice_from_file( + subfolder=subfolder, filename='bis3_ecotaxes_case1.xml', amount_total=121, amount_tax=22, + list_line_subtotals=[99], currency_id=self.currency_data['currency'].id, list_line_price_unit=[99], + list_line_discount=[0], list_line_taxes=[tax_21+self.recupel], move_type='out_invoice', + ) + self._assert_imported_invoice_from_file( + subfolder=subfolder, filename='bis3_ecotaxes_case2.xml', amount_total=121, amount_tax=23, + list_line_subtotals=[98], currency_id=self.currency_data['currency'].id, list_line_price_unit=[98], + list_line_discount=[0], list_line_taxes=[tax_21+self.recupel+self.auvibel], move_type='out_invoice', + ) + self._assert_imported_invoice_from_file( + subfolder=subfolder, filename='bis3_ecotaxes_case3.xml', amount_total=121, amount_tax=22, + list_line_subtotals=[99], currency_id=self.currency_data['currency'].id, list_line_price_unit=[99], + list_line_discount=[0], list_line_taxes=[tax_21+self.recupel], move_type='out_invoice', + ) -- GitLab