diff --git a/addons/account_edi_facturx/data/facturx_templates.xml b/addons/account_edi_facturx/data/facturx_templates.xml index c24ecdcd0ca7e865a2999456f27b50a4bc1dd740..0aefec9204783b7207119b3a86a577592a0acd38 100644 --- a/addons/account_edi_facturx/data/facturx_templates.xml +++ b/addons/account_edi_facturx/data/facturx_templates.xml @@ -33,7 +33,7 @@ <!-- Line information, with discount and unit price separate --> <ram:GrossPriceProductTradePrice> <ram:ChargeAmount - t-esc="format_monetary(line.price_unit, record.currency_id)"/> + t-esc="format_monetary(line_vals['gross_price_total_unit'], record.currency_id)"/> <!-- Discount. --> <ram:AppliedTradeAllowanceCharge t-if="line.discount"> 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 1292a45f48607b6a4e1bb099254599bce6f976a0..ae3b69a87cef71947989cad65ab2f601bc28fa92 100644 --- a/addons/account_edi_ubl_cii/data/cii_22_templates.xml +++ b/addons/account_edi_ubl_cii/data/cii_22_templates.xml @@ -27,9 +27,12 @@ <!-- Amounts. --> <ram:SpecifiedLineTradeAgreement> - <!-- Line information: unit_price (discount is below the taxes) --> + <!-- Line information: unit_price + NB: the gross_price_unit should be the price tax excluded ! + if price_unit = 100 and tax 10% price_include -> then the gross_price_unit is 91 + --> <ram:GrossPriceProductTradePrice> - <ram:ChargeAmount t-out="format_monetary(line.price_unit, 2)"/> + <ram:ChargeAmount t-out="format_monetary(line_vals['gross_price_total_unit'], 2)"/> <!-- Discount. --> <ram:AppliedTradeAllowanceCharge t-if="line.discount"> <ram:ChargeIndicator> @@ -62,17 +65,6 @@ </ram:ApplicableTradeTax> </t> - <!-- Discount. --> - <!-- A discount in percent can only be on the whole document (CalculationPercent), not on a line... --> - <ram:SpecifiedTradeAllowanceCharge t-if="line.discount"> - <ram:ChargeIndicator> - <udt:Indicator>false</udt:Indicator> - </ram:ChargeIndicator> - <ram:ActualAmount t-out="format_monetary(line.price_unit * line.discount/100, 2)"/> - <!-- https://unece.org/fileadmin/DAM/trade/untdid/d16b/tred/tred5189.htm --> - <ram:ReasonCode>95</ram:ReasonCode> - </ram:SpecifiedTradeAllowanceCharge> - <!-- Subtotal. --> <ram:SpecifiedTradeSettlementLineMonetarySummation> <ram:LineTotalAmount t-out="format_monetary(line.price_subtotal, 2)"/> 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 44e8442cc5a56ec687ab0825654c499a6da0826c..c9be340db0b90750a8ea1694c3613d763e4a87a1 100644 --- a/addons/account_edi_ubl_cii/models/account_edi_common.py +++ b/addons/account_edi_ubl_cii/models/account_edi_common.py @@ -405,7 +405,7 @@ class AccountEdiCommon(models.AbstractModel): with (UBL | CII): * net_unit_price = 'Price/PriceAmount' | 'NetPriceProductTradePrice' (mandatory) (BT-146) - * gross_unit_price = 'GrossPriceProductTradePrice' | 'GrossPriceProductTradePrice' (optional) (BT-148) + * gross_unit_price = 'Price/AllowanceCharge/BaseAmount' | 'GrossPriceProductTradePrice' (optional) (BT-148) * basis_qty = 'Price/BaseQuantity' | 'BasisQuantity' (optional, either below net_price node or gross_price node) (BT-149) * billed_qty = 'InvoicedQuantity' | 'BilledQuantity' (mandatory) (BT-129) @@ -447,6 +447,12 @@ class AccountEdiCommon(models.AbstractModel): } :params: invoice_line_form :params: qty_factor + :returns: { + 'quantity': float, + 'product_uom_id': (optional) uom.uom, + 'price_unit': float, + 'discount': float, + } """ # basis_qty (optional) basis_qty = 1 @@ -468,10 +474,9 @@ class AccountEdiCommon(models.AbstractModel): rebate_node = tree.find(xpath_dict['rebate']) net_price_unit_node = tree.find(xpath_dict['net_price_unit']) if rebate_node is not None: - if net_price_unit_node is not None and gross_price_unit_node is not None: - rebate = float(gross_price_unit_node.text) - float(net_price_unit_node.text) - else: - rebate = float(rebate_node.text) + rebate = float(rebate_node.text) + elif net_price_unit_node is not None and gross_price_unit_node is not None: + rebate = float(gross_price_unit_node.text) - float(net_price_unit_node.text) # net_price_unit (mandatory) net_price_unit = None @@ -516,9 +521,7 @@ class AccountEdiCommon(models.AbstractModel): #################################################### # quantity - invoice_line_form.quantity = billed_qty * qty_factor - if product_uom_id is not None: - invoice_line_form.product_uom_id = product_uom_id + quantity = billed_qty * qty_factor # price_unit if gross_price_unit is not None: @@ -527,17 +530,59 @@ class AccountEdiCommon(models.AbstractModel): price_unit = (net_price_unit + rebate) / basis_qty else: raise UserError(_("No gross price nor net price found for line in xml")) - invoice_line_form.price_unit = price_unit # discount + discount = 0 if billed_qty * price_unit != 0 and price_subtotal is not None: - invoice_line_form.discount = 100 * (1 - price_subtotal / (billed_qty * price_unit)) + discount = 100 * (1 - price_subtotal / (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 # respected, and the result will not be correct, so we just follow the simple rule below: if net_price_unit == 0 and price_subtotal != net_price_unit * (billed_qty / basis_qty) - allow_charge_amount: - invoice_line_form.price_unit = price_subtotal / billed_qty + price_unit = price_subtotal / billed_qty + + return { + 'quantity': quantity, + 'price_unit': price_unit, + 'discount': discount, + 'product_uom_id': product_uom_id, + } + + 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. + inv_line_vals['taxes'] = [] + for tax_node in tax_nodes: + amount = float(tax_node.text) + domain = [ + ('company_id', '=', journal.company_id.id), + ('amount_type', '=', 'percent'), + ('type_tax_use', '=', journal.type), + ('amount', '=', amount), + ] + tax_excl = self.env['account.tax'].search(domain + [('price_include', '=', False)], limit=1) + tax_incl = self.env['account.tax'].search(domain + [('price_include', '=', True)], limit=1) + if tax_excl: + inv_line_vals['taxes'].append(tax_excl) + elif tax_incl: + inv_line_vals['taxes'].append(tax_incl) + 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)) + # Set the values on the line_form + invoice_line_form.quantity = inv_line_vals['quantity'] + if inv_line_vals.get('product_uom_id'): + invoice_line_form.product_uom_id = inv_line_vals['product_uom_id'] + else: + logs.append( + _("Could not retrieve the unit of measure for line with label '%s'.", invoice_line_form.name)) + invoice_line_form.price_unit = inv_line_vals['price_unit'] + invoice_line_form.discount = inv_line_vals['discount'] + invoice_line_form.tax_ids.clear() + for tax in inv_line_vals['taxes']: + invoice_line_form.tax_ids.add(tax) + return logs # ------------------------------------------------------------------------- # Check xml using the free API from Ph. Helger, don't abuse it ! 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 557ba2aa37e9985ad57e36e967bcfaabe6e08349..f460e270dc42d2c87ab68108bf514abb641ff420 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 @@ -333,32 +333,10 @@ class AccountEdiXmlCII(models.AbstractModel): 'allowance_charge_amount': './{*}ActualAmount', # below allowance_charge node 'line_total_amount': './{*}SpecifiedLineTradeSettlement/{*}SpecifiedTradeSettlementLineMonetarySummation/{*}LineTotalAmount', } - self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line_form, qty_factor) - - if not invoice_line_form.product_uom_id: - logs.append( - _("Could not retrieve the unit of measure for line with label '%s'. Did you install the inventory " - "app and enabled the 'Units of Measure' option ?", invoice_line_form.name)) - - # Taxes - taxes = [] + inv_line_vals = self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line_form, qty_factor) + # retrieve tax nodes tax_nodes = tree.findall('.//{*}ApplicableTradeTax/{*}RateApplicablePercent') - for tax_node in tax_nodes: - tax = self.env['account.tax'].search([ - ('company_id', '=', journal.company_id.id), - ('amount', '=', float(tax_node.text)), - ('amount_type', '=', 'percent'), - ('type_tax_use', '=', journal.type), - ], limit=1) - if tax: - taxes.append(tax) - else: - logs.append(_("Could not retrieve the tax: %s %% for line '%s'.", float(tax_node.text), invoice_line_form.name)) - - invoice_line_form.tax_ids.clear() - for tax in taxes: - invoice_line_form.tax_ids.add(tax) - return logs + return self._import_fill_invoice_line_taxes(journal, tax_nodes, invoice_line_form, inv_line_vals, logs) # ------------------------------------------------------------------------- # IMPORT : helpers 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 17719fa11e02e7f8a6fe8cd011d38156b251df64..9f91397b78ec5e2d1213ba0d3d98b9d5788942cb 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 @@ -587,37 +587,13 @@ class AccountEdiXmlUBL20(models.AbstractModel): 'allowance_charge_amount': './{*}Amount', # below allowance_charge node 'line_total_amount': './{*}LineExtensionAmount', } - self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line_form, qty_factor) - - if not invoice_line_form.product_uom_id: - logs.append( - _("Could not retrieve the unit of measure for line with label '%s'. Did you install the inventory " - "app and enabled the 'Units of Measure' option ?", invoice_line_form.name)) - - # Taxes - taxes = [] - + inv_line_vals = self._import_fill_invoice_line_values(tree, xpath_dict, invoice_line_form, qty_factor) + # retrieve tax nodes tax_nodes = tree.findall('.//{*}Item/{*}ClassifiedTaxCategory/{*}Percent') if not tax_nodes: for elem in tree.findall('.//{*}TaxTotal'): tax_nodes += elem.findall('.//{*}TaxSubtotal/{*}Percent') - - for tax_node in tax_nodes: - tax = self.env['account.tax'].search([ - ('company_id', '=', journal.company_id.id), - ('amount', '=', float(tax_node.text)), - ('amount_type', '=', 'percent'), - ('type_tax_use', '=', journal.type), - ], limit=1) - if tax: - taxes.append(tax) - else: - logs.append(_("Could not retrieve the tax: %s %% for line '%s'.", float(tax_node.text), invoice_line_form.name)) - - invoice_line_form.tax_ids.clear() - for tax in taxes: - invoice_line_form.tax_ids.add(tax) - return logs + return self._import_fill_invoice_line_taxes(journal, tax_nodes, invoice_line_form, inv_line_vals, logs) # ------------------------------------------------------------------------- # IMPORT : helpers 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 0e48aee9b3fde6aa22500038aca24523a767140b..6de631042baeb32c611b23cb9e085a6e437114d5 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py @@ -81,6 +81,7 @@ class TestUBLCommon(AccountEdiTestCommon): self.assert_same_invoice(invoice, new_invoice) def _assert_imported_invoice_from_file(self, subfolder, filename, amount_total, amount_tax, list_line_subtotals, + list_line_price_unit=None, list_line_discount=None, list_line_taxes=None, move_type='in_invoice', currency_id=None): """ Create an empty account.move, update the file to fill its fields, asserts the currency, total and tax amounts @@ -109,6 +110,13 @@ class TestUBLCommon(AccountEdiTestCommon): 'amount_tax': amount_tax, 'currency_id': currency_id, }]) + if list_line_price_unit: + self.assertEqual(invoice.invoice_line_ids.mapped('price_unit'), list_line_price_unit) + if list_line_discount: + self.assertEqual(invoice.invoice_line_ids.mapped('discount'), list_line_discount) + if list_line_taxes: + for line, taxes in zip(invoice.invoice_line_ids, list_line_taxes): + self.assertEqual(line.tax_ids, taxes) self.assertEqual(invoice.invoice_line_ids.mapped('price_subtotal'), list_line_subtotals) # ------------------------------------------------------------------------- diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice.xml index 8c7f1a242ba6f1c94a9c1fb35569373db7c63907..7fa151c8f3e679250e28b7ed7fbbbe179997c5dd 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice.xml +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice.xml @@ -45,13 +45,6 @@ <ram:CategoryCode>S</ram:CategoryCode> <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> </ram:ApplicableTradeTax> - <ram:SpecifiedTradeAllowanceCharge> - <ram:ChargeIndicator> - <udt:Indicator>false</udt:Indicator> - </ram:ChargeIndicator> - <ram:ActualAmount>99.00</ram:ActualAmount> - <ram:ReasonCode>95</ram:ReasonCode> - </ram:SpecifiedTradeAllowanceCharge> <ram:SpecifiedTradeSettlementLineMonetarySummation> <ram:LineTotalAmount>1782.00</ram:LineTotalAmount> </ram:SpecifiedTradeSettlementLineMonetarySummation> diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice_tax_incl.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice_tax_incl.xml new file mode 100644 index 0000000000000000000000000000000000000000..ae2eef40989fd6c14f1f09cc97ca8aa5ece6053a --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_invoice_tax_incl.xml @@ -0,0 +1,239 @@ +<rsm:CrossIndustryInvoice xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType: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>INV/2017/01/0001</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>95.24</ram:ChargeAmount> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>95.24</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>5.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>95.24</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:IncludedSupplyChainTradeLineItem> + <ram:AssociatedDocumentLineDocument> + <ram:LineID>2</ram:LineID> + </ram:AssociatedDocumentLineDocument> + <ram:SpecifiedTradeProduct> + <ram:Name>product_a</ram:Name> + </ram:SpecifiedTradeProduct> + <ram:SpecifiedLineTradeAgreement> + <ram:GrossPriceProductTradePrice> + <ram:ChargeAmount>100.00</ram:ChargeAmount> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>100.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>5.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>100.00</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:IncludedSupplyChainTradeLineItem> + <ram:AssociatedDocumentLineDocument> + <ram:LineID>3</ram:LineID> + </ram:AssociatedDocumentLineDocument> + <ram:SpecifiedTradeProduct> + <ram:Name>product_a</ram:Name> + </ram:SpecifiedTradeProduct> + <ram:SpecifiedLineTradeAgreement> + <ram:GrossPriceProductTradePrice> + <ram:ChargeAmount>190.48</ram:ChargeAmount> + <ram:AppliedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator>false</udt:Indicator> + </ram:ChargeIndicator> + <ram:ActualAmount>19.05</ram:ActualAmount> + </ram:AppliedTradeAllowanceCharge> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>171.43</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>5.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>171.43</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:IncludedSupplyChainTradeLineItem> + <ram:AssociatedDocumentLineDocument> + <ram:LineID>4</ram:LineID> + </ram:AssociatedDocumentLineDocument> + <ram:SpecifiedTradeProduct> + <ram:Name>product_a</ram:Name> + </ram:SpecifiedTradeProduct> + <ram:SpecifiedLineTradeAgreement> + <ram:GrossPriceProductTradePrice> + <ram:ChargeAmount>200.00</ram:ChargeAmount> + <ram:AppliedTradeAllowanceCharge> + <ram:ChargeIndicator> + <udt:Indicator>false</udt:Indicator> + </ram:ChargeIndicator> + <ram:ActualAmount>20.00</ram:ActualAmount> + </ram:AppliedTradeAllowanceCharge> + </ram:GrossPriceProductTradePrice> + <ram:NetPriceProductTradePrice> + <ram:ChargeAmount>180.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>5.0</ram:RateApplicablePercent> + </ram:ApplicableTradeTax> + <ram:SpecifiedTradeSettlementLineMonetarySummation> + <ram:LineTotalAmount>180.00</ram:LineTotalAmount> + </ram:SpecifiedTradeSettlementLineMonetarySummation> + </ram:SpecifiedLineTradeSettlement> + </ram:IncludedSupplyChainTradeLineItem> + <ram:ApplicableHeaderTradeAgreement> + <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>INV/2017/01/0001</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>INV/2017/00001</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>27.33</ram:CalculatedAmount> + <ram:TypeCode>VAT</ram:TypeCode> + <ram:BasisAmount>546.67</ram:BasisAmount> + <ram:CategoryCode>S</ram:CategoryCode> + <ram:DueDateTypeCode>5</ram:DueDateTypeCode> + <ram:RateApplicablePercent>5.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>546.67</ram:LineTotalAmount> + <ram:TaxBasisTotalAmount>546.67</ram:TaxBasisTotalAmount> + <ram:TaxTotalAmount currencyID="USD">27.33</ram:TaxTotalAmount> + <ram:GrandTotalAmount>574.00</ram:GrandTotalAmount> + <ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount> + <ram:DuePayableAmount>574.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_out_refund.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_refund.xml index d1ff9a334700e00f5e13f8c594c3f47b23d3e037..1c4ed46bcbd0f4109d6f42764f365e90412b0e66 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_refund.xml +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/facturx_out_refund.xml @@ -45,13 +45,6 @@ <ram:CategoryCode>S</ram:CategoryCode> <ram:RateApplicablePercent>21.0</ram:RateApplicablePercent> </ram:ApplicableTradeTax> - <ram:SpecifiedTradeAllowanceCharge> - <ram:ChargeIndicator> - <udt:Indicator>false</udt:Indicator> - </ram:ChargeIndicator> - <ram:ActualAmount>99.00</ram:ActualAmount> - <ram:ReasonCode>95</ram:ReasonCode> - </ram:SpecifiedTradeAllowanceCharge> <ram:SpecifiedTradeSettlementLineMonetarySummation> <ram:LineTotalAmount>1782.00</ram:LineTotalAmount> </ram:SpecifiedTradeSettlementLineMonetarySummation> 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 505da29ce34fe6c395bc11fd0bacbc28bde7feb9..de3c8f065a11fe97fbf0d976a4558a0bdd3a66db 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 @@ -74,6 +74,28 @@ class TestCIIFR(TestUBLCommon): 'country_id': cls.env.ref('base.fr').id, }) + cls.tax_5_purchase = cls.env['account.tax'].create({ + 'name': 'tax_5', + 'amount_type': 'percent', + 'amount': 5, + 'type_tax_use': 'purchase', + }) + + cls.tax_5 = cls.env['account.tax'].create({ + 'name': 'tax_5', + 'amount_type': 'percent', + 'amount': 5, + 'type_tax_use': 'sale', + }) + + cls.tax_5_incl = cls.env['account.tax'].create({ + 'name': 'tax_5_incl', + 'amount_type': 'percent', + 'amount': 5, + 'type_tax_use': 'sale', + 'price_include': True, + }) + @classmethod def setup_company_data(cls, company_name, chart_template): # OVERRIDE @@ -210,10 +232,95 @@ class TestCIIFR(TestUBLCommon): self.assertEqual(xml_filename, "factur-x.xml") self._assert_imported_invoice_from_etree(refund, xml_etree, xml_filename) + def test_export_tax_included(self): + """ + Tests whether the tax included price_units are correctly converted to tax excluded + amounts in the exported xml + """ + 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': 100, + 'tax_ids': [(6, 0, self.tax_5_incl.ids)], + }, + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 100, + 'tax_ids': [(6, 0, self.tax_5.ids)], + }, + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 200, + 'discount': 10, + 'tax_ids': [(6, 0, self.tax_5_incl.ids)], + }, + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 200, + 'discount': 10, + 'tax_ids': [(6, 0, self.tax_5.ids)], + }, + ], + ) + self._assert_invoice_attachment( + invoice, + xpaths=''' + <xpath expr="./*[local-name()='ExchangedDocument']/*[local-name()='ID']" position="replace"> + <ID>___ignore___</ID> + </xpath> + <xpath expr=".//*[local-name()='IssuerAssignedID']" position="replace"> + <IssuerAssignedID>___ignore___</IssuerAssignedID> + </xpath> + ''', + expected_file='from_odoo/facturx_out_invoice_tax_incl.xml' + ) + #################################################### # Test import #################################################### + def test_import_tax_included(self): + """ + Tests whether the tax included / tax excluded are correctly decoded when + importing a document. The imported xml represents the following invoice: + + Description Quantity Unit Price Disc (%) Taxes Amount + -------------------------------------------------------------------------------- + Product A 1 100 0 5% (incl) 95.24 + Product A 1 100 0 5% (not incl) 100 + Product A 2 200 10 5% (incl) 171.43 + Product A 2 200 10 5% (not incl) 180 + ----------------------- + Untaxed Amount: 546.67 + Taxes: 27.334 + ----------------------- + Total: 574.004 + """ + self._assert_imported_invoice_from_file( + subfolder='tests/test_files/from_odoo', + filename='facturx_out_invoice_tax_incl.xml', + amount_total=574.004, + amount_tax=27.334, + list_line_subtotals=[95.24, 100, 171.43, 180], + # /!\ The price_unit are different for taxes with price_include, because all amounts in Factur-X should be + # tax excluded. At import, the tax included amounts are thus converted into tax excluded ones. + # Yet, the line subtotals and total will be the same (if an equivalent tax exist with price_include = False) + list_line_price_unit=[95.24, 100, 190.48, 200], + list_line_discount=[0, 0, 10, 10], + # Again, all taxes in the imported invoice are price_include = False + list_line_taxes=[self.tax_5_purchase]*4, + move_type='in_invoice', + currency_id=self.env['res.currency'].search([('name', '=', 'USD')], limit=1).id, + ) + def test_import_fnfe_examples(self): # Source: official documentation of the FNFE (subdirectory: "5. FACTUR-X 1.0.06 - Examples") subfolder = 'tests/test_files/from_factur-x_doc'