diff options
| author | Laurent Smet <las@odoo.com> | 2020-07-01 08:53:00 +0000 |
|---|---|---|
| committer | Laurent Smet <las@odoo.com> | 2020-07-03 17:29:42 +0000 |
| commit | 187dc351bbe1734d58b596c4792b962ab256057b (patch) | |
| tree | 2b8746c94f687206a7d75aee3019386ee6b69e64 /addons | |
| parent | aa7dd77a6787447d9ca9f8c576bbaf02f6856fbc (diff) | |
[FIX] account_facturx: Fix facturx xml with discount, group of taxes and multi-currency
- Fix discount indicator with 100.0% discount.
- Fix reported tax amount with group of taxes.
- Fix reported tax_base_amount with foreign currency.
- Fix invoice summary according the facturx xsd.
closes odoo/odoo#54080
X-original-commit: e97333f6d80eaa5807467a863a9c8f5850e1d644
Related: odoo/enterprise#11657
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
Signed-off-by: Laurent Smet <smetl@users.noreply.github.com>
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/account_edi_facturx/data/facturx_templates.xml | 31 | ||||
| -rw-r--r-- | addons/account_edi_facturx/models/account_edi_format.py | 43 | ||||
| -rw-r--r-- | addons/account_edi_facturx/tests/__init__.py | 3 | ||||
| -rw-r--r-- | addons/account_edi_facturx/tests/test_facturx.py | 224 |
4 files changed, 282 insertions, 19 deletions
diff --git a/addons/account_edi_facturx/data/facturx_templates.xml b/addons/account_edi_facturx/data/facturx_templates.xml index 1ead8f38f46..719a1caae48 100644 --- a/addons/account_edi_facturx/data/facturx_templates.xml +++ b/addons/account_edi_facturx/data/facturx_templates.xml @@ -2,13 +2,14 @@ <odoo> <data> <template id="account_invoice_line_facturx_export"> + <t t-set="line" t-value="line_values['line']"/> <t xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"> <ram:IncludedSupplyChainTradeLineItem> <!-- Line number. --> <ram:AssociatedDocumentLineDocument> - <ram:LineID t-esc="line_counter"/> + <ram:LineID t-esc="line_values['index']"/> </ram:AssociatedDocumentLineDocument> <!-- Product. --> @@ -25,23 +26,15 @@ <!-- Amounts. --> <ram:SpecifiedLineTradeAgreement> - <t t-set="taxes" - t-value="line.tax_ids.compute_all( - line.price_unit, - currency=line.currency_id, - quantity=line.quantity, - product=line.product_id, - partner=record.partner_id, - is_refund=line.move_id.move_type in ('in_refund', 'out_refund'))"/> <ram:GrossPriceProductTradePrice> <ram:ChargeAmount t-att-currencyID="currency.name" - t-esc="format_monetary(taxes['total_excluded'], currency)"/> + t-esc="format_monetary(line.price_unit, currency)"/> <!-- Discount. --> <ram:AppliedTradeAllowanceCharge t-if="line.discount"> <ram:ChargeIndicator> - <udt:Indicator t-esc="'false' if line.discount == 1 else 'true'"/> + <udt:Indicator t-esc="'false' if line.discount == 100 else 'true'"/> </ram:ChargeIndicator> <ram:CalculationPercent t-esc="line.discount"/> </ram:AppliedTradeAllowanceCharge> @@ -55,7 +48,8 @@ </ram:SpecifiedLineTradeDelivery> <ram:SpecifiedLineTradeSettlement> - <t t-foreach="line.tax_ids" t-as="tax"> + <t t-foreach="line_values['tax_details']" t-as="tax_vals"> + <t t-set="tax" t-value="tax_vals['tax']"/> <ram:ApplicableTradeTax t-if="tax.amount_type == 'percent'"> <ram:RateApplicablePercent t-esc="tax.amount"/> </ram:ApplicableTradeTax> @@ -130,9 +124,7 @@ <rsm:SupplyChainTradeTransaction> <!-- Invoice lines. --> - <t t-set="line_counter" t-value="0"/> - <t t-foreach="record.invoice_line_ids.filtered(lambda l: not l.display_type)" t-as="line"> - <t t-set="line_counter" t-value="line_counter + 1"/> + <t t-foreach="invoice_line_values" t-as="line_values"> <t t-call="account_edi_facturx.account_invoice_line_facturx_export"/> </t> @@ -191,14 +183,15 @@ </ram:SpecifiedTradeSettlementPaymentMeans> <!-- Tax Summary. --> - <t t-foreach="record.line_ids.filtered(lambda line: line.tax_line_id)" t-as="tax_line"> + <t t-foreach="tax_details" t-as="tax_vals"> + <t t-set="tax_line" t-value="tax_vals['line']"/> <ram:ApplicableTradeTax> <ram:CalculatedAmount t-att-currencyID="currency.name" - t-esc="format_monetary(tax_line.price_unit, currency)"/> + t-esc="format_monetary(tax_vals['tax_amount'], currency)"/> <ram:BasisAmount t-att-currencyID="currency.name" - t-esc="format_monetary(tax_line.tax_base_amount, currency)"/> + t-esc="format_monetary(tax_vals['tax_base_amount'], currency)"/> <ram:RateApplicablePercent t-if="tax_line.tax_line_id.amount_type == 'percent'" t-esc="tax_line.tax_line_id.amount"/> @@ -220,7 +213,7 @@ t-esc="format_monetary(record.amount_untaxed, currency)"/> <ram:TaxBasisTotalAmount t-att-currencyID="currency.name" - t-esc="format_monetary(sum(record.line_ids.filtered(lambda line: line.tax_line_id).mapped('price_unit')), currency)"/> + t-esc="format_monetary(record.amount_untaxed, currency)"/> <ram:TaxTotalAmount t-att-currencyID="currency.name" t-esc="format_monetary(record.amount_tax, currency)"/> diff --git a/addons/account_edi_facturx/models/account_edi_format.py b/addons/account_edi_facturx/models/account_edi_format.py index 032675fc132..54751a5da4d 100644 --- a/addons/account_edi_facturx/models/account_edi_format.py +++ b/addons/account_edi_facturx/models/account_edi_format.py @@ -41,7 +41,50 @@ class AccountEdiFormat(models.Model): 'record': invoice, 'format_date': format_date, 'format_monetary': format_monetary, + 'invoice_line_values': [], } + + # Tax lines. + aggregated_taxes_details = {line.tax_line_id.id: { + 'line': line, + 'tax_amount': -line.amount_currency if line.currency_id else -line.balance, + 'tax_base_amount': 0.0, + } for line in invoice.line_ids.filtered('tax_line_id')} + + # Invoice lines. + for i, line in enumerate(invoice.invoice_line_ids.filtered(lambda l: not l.display_type)): + price_unit_with_discount = line.price_unit * (1 - (line.discount / 100.0)) + taxes_res = line.tax_ids.compute_all( + price_unit_with_discount, + currency=line.currency_id, + quantity=line.quantity, + product=line.product_id, + partner=invoice.partner_id, + is_refund=line.move_id.move_type in ('in_refund', 'out_refund'), + ) + + line_template_values = { + 'line': line, + 'index': i + 1, + 'tax_details': [], + 'net_price_subtotal': taxes_res['total_excluded'], + } + + for tax_res in taxes_res['taxes']: + tax = self.env['account.tax'].browse(tax_res['id']) + line_template_values['tax_details'].append({ + 'tax': tax, + 'tax_amount': tax_res['amount'], + 'tax_base_amount': tax_res['base'], + }) + + if tax.id in aggregated_taxes_details: + aggregated_taxes_details[tax.id]['tax_base_amount'] += tax_res['base'] + + template_values['invoice_line_values'].append(line_template_values) + + template_values['tax_details'] = list(aggregated_taxes_details.values()) + xml_content = b"<?xml version='1.0' encoding='UTF-8'?>" xml_content += self.env.ref('account_edi_facturx.account_invoice_facturx_export')._render(template_values) xml_name = '%s_facturx.xml' % (invoice.name.replace('/', '_')) diff --git a/addons/account_edi_facturx/tests/__init__.py b/addons/account_edi_facturx/tests/__init__.py new file mode 100644 index 00000000000..2f0994cfaa7 --- /dev/null +++ b/addons/account_edi_facturx/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- + +from . import test_facturx diff --git a/addons/account_edi_facturx/tests/test_facturx.py b/addons/account_edi_facturx/tests/test_facturx.py new file mode 100644 index 00000000000..98a0f056ef5 --- /dev/null +++ b/addons/account_edi_facturx/tests/test_facturx.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +from odoo.addons.account.tests.account_test_xml import AccountTestEdiCommon +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestAccountEdiFacturx(AccountTestEdiCommon): + + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + + # ==== Init ==== + + cls.tax_10_include = cls.env['account.tax'].create({ + 'name': 'tax_10_include', + 'amount_type': 'percent', + 'amount': 10, + 'type_tax_use': 'sale', + 'price_include': True, + 'include_base_amount': True, + 'sequence': 10, + }) + + cls.tax_20 = cls.env['account.tax'].create({ + 'name': 'tax_20', + 'amount_type': 'percent', + 'amount': 20, + 'type_tax_use': 'sale', + 'sequence': 20, + }) + + cls.tax_group = cls.env['account.tax'].create({ + 'name': 'tax_group', + 'amount_type': 'group', + 'amount': 0.0, + 'type_tax_use': 'sale', + 'children_tax_ids': [(6, 0, (cls.tax_10_include + cls.tax_20).ids)], + }) + + # ==== EDI ==== + + cls.facturx_edi_format = cls.env.ref('account_edi_facturx.edi_facturx_1_0_05') + cls.journal = cls.company_data['default_journal_sale'] + cls.journal.edi_format_ids = [(6, 0, cls.facturx_edi_format.ids)] + + # ==== Invoice ==== + + cls.invoice = cls.env['account.move'].create({ + 'move_type': 'out_invoice', + 'journal_id': cls.journal.id, + 'partner_id': cls.partner_b.id, + 'invoice_date': '2017-01-01', + 'date': '2017-01-01', + 'currency_id': cls.currency_data['currency'].id, + 'invoice_line_ids': [(0, 0, { + 'product_id': cls.product_a.id, + 'product_uom_id': cls.env.ref('uom.product_uom_dozen').id, + 'price_unit': 275.0, + 'quantity': 5, + 'discount': 20.0, + 'tax_ids': [(6, 0, cls.tax_20.ids)], + })], + }) + + cls.expected_invoice_facturx_values = ''' + <CrossIndustryInvoice> + <ExchangedDocumentContext> + <GuidelineSpecifiedDocumentContextParameter> + <ID>urn:cen.eu:en16931:2017</ID> + </GuidelineSpecifiedDocumentContextParameter> + </ExchangedDocumentContext> + <ExchangedDocument> + <TypeCode>380</TypeCode> + <IssueDateTime> + <DateTimeString format="102">20170101</DateTimeString> + </IssueDateTime> + </ExchangedDocument> + <SupplyChainTradeTransaction> + <IncludedSupplyChainTradeLineItem> + <AssociatedDocumentLineDocument> + <LineID>1</LineID> + </AssociatedDocumentLineDocument> + <SpecifiedTradeProduct> + <Name>product_a</Name> + </SpecifiedTradeProduct> + <SpecifiedLineTradeAgreement> + <GrossPriceProductTradePrice> + <ChargeAmount currencyID="Gol">275.000</ChargeAmount> + <AppliedTradeAllowanceCharge> + <ChargeIndicator> + <Indicator>true</Indicator> + </ChargeIndicator> + <CalculationPercent>20.0</CalculationPercent> + </AppliedTradeAllowanceCharge> + </GrossPriceProductTradePrice> + </SpecifiedLineTradeAgreement> + <SpecifiedLineTradeDelivery> + <BilledQuantity>5.0</BilledQuantity> + </SpecifiedLineTradeDelivery> + <SpecifiedLineTradeSettlement> + <ApplicableTradeTax> + <RateApplicablePercent>20.0</RateApplicablePercent> + </ApplicableTradeTax> + <SpecifiedTradeSettlementLineMonetarySummation> + <LineTotalAmount currencyID="Gol">1100.000</LineTotalAmount> + </SpecifiedTradeSettlementLineMonetarySummation> + </SpecifiedLineTradeSettlement> + </IncludedSupplyChainTradeLineItem> + <ApplicableHeaderTradeAgreement> + <SellerTradeParty> + <Name>company_1_data</Name> + <DefinedTradeContact> + <PersonName>company_1_data</PersonName> + </DefinedTradeContact> + <PostalTradeAddress/> + </SellerTradeParty> + <BuyerTradeParty> + <Name>partner_b</Name> + <DefinedTradeContact> + <PersonName>partner_b</PersonName> + </DefinedTradeContact> + <PostalTradeAddress/> + </BuyerTradeParty> + <BuyerOrderReferencedDocument> + <IssuerAssignedID>INV/2017/01/0001: INV/2017/01/0001</IssuerAssignedID> + </BuyerOrderReferencedDocument> + </ApplicableHeaderTradeAgreement> + <ApplicableHeaderTradeDelivery/> + <ApplicableHeaderTradeSettlement> + <ApplicableTradeTax> + <CalculatedAmount currencyID="Gol">220.000</CalculatedAmount> + <BasisAmount currencyID="Gol">1100.000</BasisAmount> + <RateApplicablePercent>20.0</RateApplicablePercent> + </ApplicableTradeTax> + <SpecifiedTradePaymentTerms> + <DueDateDateTime> + <DateTimeString>20170101</DateTimeString> + </DueDateDateTime> + </SpecifiedTradePaymentTerms> + <SpecifiedTradeSettlementHeaderMonetarySummation> + <LineTotalAmount currencyID="Gol">1100.000</LineTotalAmount> + <TaxBasisTotalAmount currencyID="Gol">1100.000</TaxBasisTotalAmount> + <TaxTotalAmount currencyID="Gol">220.000</TaxTotalAmount> + <GrandTotalAmount currencyID="Gol">1320.000</GrandTotalAmount> + <TotalPrepaidAmount currencyID="Gol">0.000</TotalPrepaidAmount> + <DuePayableAmount currencyID="Gol">1320.000</DuePayableAmount> + </SpecifiedTradeSettlementHeaderMonetarySummation> + </ApplicableHeaderTradeSettlement> + </SupplyChainTradeTransaction> + </CrossIndustryInvoice> + ''' + + def test_facturx(self): + ''' Test the generated Facturx Edi attachment without any modification of the invoice. ''' + + with self.mocked_today('2017-02-01'): + self.invoice.post() + + xml_content = self.facturx_edi_format._export_invoice_to_attachment(self.invoice)['datas'] + current_etree = self.get_xml_tree_from_string(xml_content) + expected_etree = self.get_xml_tree_from_string(self.expected_invoice_facturx_values) + self.assertXmlTreeEqual(current_etree, expected_etree) + + def test_facturx_group_of_taxes(self): + ''' Same as above with a group of taxes. ''' + self.invoice.write({ + 'invoice_line_ids': [(1, self.invoice.invoice_line_ids.id, {'tax_ids': [(6, 0, self.tax_group.ids)]})], + }) + + with self.mocked_today('2017-02-01'): + self.invoice.post() + + xml_content = self.facturx_edi_format._export_invoice_to_attachment(self.invoice)['datas'] + current_etree = self.get_xml_tree_from_string(xml_content) + expected_etree = self.with_applied_xpath( + self.get_xml_tree_from_string(self.expected_invoice_facturx_values), + ''' + <xpath expr="//GrossPriceProductTradePrice/ChargeAmount" position="replace"> + <ChargeAmount currencyID="Gol">275.000</ChargeAmount> + </xpath> + <xpath expr="//SpecifiedLineTradeSettlement" position="replace"> + <SpecifiedLineTradeSettlement> + <ApplicableTradeTax> + <RateApplicablePercent>10.0</RateApplicablePercent> + </ApplicableTradeTax> + <ApplicableTradeTax> + <RateApplicablePercent>20.0</RateApplicablePercent> + </ApplicableTradeTax> + <SpecifiedTradeSettlementLineMonetarySummation> + <LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount> + </SpecifiedTradeSettlementLineMonetarySummation> + </SpecifiedLineTradeSettlement> + </xpath> + <xpath expr="//ApplicableHeaderTradeSettlement" position="replace"> + <ApplicableHeaderTradeSettlement> + <ApplicableTradeTax> + <CalculatedAmount currencyID="Gol">220.000</CalculatedAmount> + <BasisAmount currencyID="Gol">1100.000</BasisAmount> + <RateApplicablePercent>20.0</RateApplicablePercent> + </ApplicableTradeTax> + <ApplicableTradeTax> + <CalculatedAmount currencyID="Gol">100.000</CalculatedAmount> + <BasisAmount currencyID="Gol">1000.000</BasisAmount> + <RateApplicablePercent>10.0</RateApplicablePercent> + </ApplicableTradeTax> + <SpecifiedTradePaymentTerms> + <DueDateDateTime> + <DateTimeString>20170101</DateTimeString> + </DueDateDateTime> + </SpecifiedTradePaymentTerms> + <SpecifiedTradeSettlementHeaderMonetarySummation> + <LineTotalAmount currencyID="Gol">1000.000</LineTotalAmount> + <TaxBasisTotalAmount currencyID="Gol">1000.000</TaxBasisTotalAmount> + <TaxTotalAmount currencyID="Gol">320.000</TaxTotalAmount> + <GrandTotalAmount currencyID="Gol">1320.000</GrandTotalAmount> + <TotalPrepaidAmount currencyID="Gol">0.000</TotalPrepaidAmount> + <DuePayableAmount currencyID="Gol">1320.000</DuePayableAmount> + </SpecifiedTradeSettlementHeaderMonetarySummation> + </ApplicableHeaderTradeSettlement> + </xpath> + ''', + ) + self.assertXmlTreeEqual(current_etree, expected_etree) |
