summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/account_edi_facturx/data/facturx_templates.xml31
-rw-r--r--addons/account_edi_facturx/models/account_edi_format.py43
-rw-r--r--addons/account_edi_facturx/tests/__init__.py3
-rw-r--r--addons/account_edi_facturx/tests/test_facturx.py224
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)