[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>
This commit is contained in:
@@ -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)"/>
|
||||
|
||||
@@ -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('/', '_'))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from . import test_facturx
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user