From 49ba394ab5637ecc67ae4720af4cc7e3b0ffd372 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#122518 X-original-commit: 891f3a1acf9caf9ffd2529a25cf2d7abaa571ca4 Signed-off-by: Laurent Smet <las@odoo.com> --- addons/account_edi/models/account_move.py | 6 +- .../data/cii_22_templates.xml | 20 ++- .../models/account_edi_common.py | 51 +++++- .../models/account_edi_xml_cii_facturx.py | 58 ++++++- .../models/account_edi_xml_ubl_20.py | 105 +++++++----- .../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 +++++++++- 16 files changed, 1238 insertions(+), 65 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/models/account_move.py b/addons/account_edi/models/account_move.py index 17eb8735ed0e..2f0888fac1dc 100644 --- a/addons/account_edi/models/account_move.py +++ b/addons/account_edi/models/account_move.py @@ -5,6 +5,7 @@ from collections import defaultdict from odoo import api, fields, models, _ from odoo.exceptions import UserError +from odoo.tools import frozendict class AccountMove(models.Model): @@ -241,9 +242,6 @@ class AccountMove(models.Model): ''' self.ensure_one() - def _serialize_python_dictionary(vals): - return '-'.join(str(vals[k]) for k in sorted(vals.keys())) - def default_grouping_key_generator(tax_values): return {'tax': tax_values['tax_id']} @@ -402,7 +400,7 @@ class AccountMove(models.Model): for tax_values in tax_values_list: grouping_key = grouping_key_generator(tax_values) - serialized_grouping_key = _serialize_python_dictionary(grouping_key) + serialized_grouping_key = frozendict(grouping_key) key_by_tax[tax_values['tax_id']] = serialized_grouping_key # Add to invoice line global tax amounts. 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 ae3b69a87cef..da3afa043777 100644 --- a/addons/account_edi_ubl_cii/data/cii_22_templates.xml +++ b/addons/account_edi_ubl_cii/data/cii_22_templates.xml @@ -65,9 +65,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> @@ -271,12 +283,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 676499c787ee..3cf2b75f126b 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_common.py +++ b/addons/account_edi_ubl_cii/models/account_edi_common.py @@ -553,6 +553,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: @@ -562,8 +563,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 @@ -588,8 +598,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 @@ -602,8 +613,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. @@ -625,6 +659,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) + inv_line_vals['price_unit'] += tax.amount + else: + inv_line_vals['taxes'].append(tax) + # 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 7d9a35e10f2b..c6d166d747fb 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 @@ -123,17 +123,36 @@ class AccountEdiXmlCII(models.AbstractModel): # Facturx requires the monetary values to be rounded to 2 decimal values return float_repr(number, decimal_places) + def grouping_key_generator(tax_values): + tax = tax_values['tax_id'] + 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) # Create file content. - tax_details = invoice._prepare_edi_tax_details( - grouping_key_generator=lambda tax_values: { - **self._get_tax_unece_codes(invoice, tax_values['tax_id']), - 'amount': tax_values['tax_id'].amount, - 'amount_type': tax_values['tax_id'].amount_type, - } - ) + 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 @@ -195,6 +214,25 @@ 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 + balance_sign = -1 if invoice.is_inbound() else 1 + for line_vals in template_values['invoice_line_vals_list']: + line_vals['allowance_charge_vals_list'] = [] + for grouping_key, tax_detail in tax_details['invoice_line_tax_details'][line_vals['line']]['tax_details'].items(): + if grouping_key['amount_type'] == 'fixed': + line_vals['allowance_charge_vals_list'].append({ + 'indicator': 'true', + 'reason': tax_detail['group_tax_details'][0]['tax_id'].name, + 'reason_code': 'AEO', + 'amount': balance_sign * 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'] = balance_sign * tax_details['base_amount_currency'] + template_values['tax_total_amount'] = balance_sign * tax_details['tax_amount_currency'] + return template_values def _export_invoice(self, invoice): @@ -331,8 +369,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_form, 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 125717c331eb..c6e17ef898e2 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 @@ -171,20 +171,23 @@ class AccountEdiXmlUBL20(models.AbstractModel): def _get_invoice_tax_totals_vals_list(self, invoice, taxes_vals): balance_sign = -1 if invoice.is_inbound() else 1 - - return [{ + tax_totals_vals = { 'currency': invoice.currency_id, 'currency_dp': invoice.currency_id.decimal_places, 'tax_amount': balance_sign * taxes_vals['tax_amount_currency'], - 'tax_subtotal_vals': [{ - 'currency': invoice.currency_id, - 'currency_dp': invoice.currency_id.decimal_places, - 'taxable_amount': balance_sign * vals['base_amount_currency'], - 'tax_amount': balance_sign * 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': balance_sign * vals['base_amount_currency'], + 'tax_amount': balance_sign * 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. @@ -196,7 +199,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', ', ') @@ -217,26 +220,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. @@ -247,8 +234,21 @@ class AccountEdiXmlUBL20(models.AbstractModel): :param line: An invoice line. :return: A list of python dictionaries. """ + fixed_tax_charge_vals_list = [] + balance_sign = -1 if line.move_id.is_inbound() else 1 + 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['group_tax_details'][0]['tax_id'].name, + 'amount': balance_sign * 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 @@ -274,7 +274,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. @@ -316,10 +316,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, @@ -330,7 +334,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), @@ -342,11 +346,17 @@ class AccountEdiXmlUBL20(models.AbstractModel): def grouping_key_generator(tax_values): tax = tax_values['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) @@ -354,6 +364,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 @@ -382,6 +404,7 @@ class AccountEdiXmlUBL20(models.AbstractModel): # OrderReference/ID (order_reference) is mandatory inside the OrderReference node ! order_reference = invoice.ref or invoice.name if sales_order_id else invoice.ref + balance_sign = -1 if invoice.is_inbound() else 1 vals = { 'builder': self, 'invoice': invoice, @@ -426,7 +449,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': balance_sign * 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, @@ -600,8 +623,10 @@ class AccountEdiXmlUBL20(models.AbstractModel): 'net_price_unit': './{*}Price/{*}PriceAmount', 'billed_qty': './{*}InvoicedQuantity' if invoice_form.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', } inv_line_vals = self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line_form, 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 2e34bee98767..3fd8940f2785 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py @@ -27,6 +27,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. @@ -94,6 +110,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 814adba00374..6a32b57295cc 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 @@ -48,6 +48,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({ @@ -286,6 +287,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 #################################################### @@ -386,3 +445,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 c253218b5575..f2dc85b03cde 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 @@ -396,3 +455,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