Skip to content
Snippets Groups Projects
Commit 9a5ba15c authored by Julien Van Roy's avatar Julien Van Roy
Browse files

[FIX] {l10n_}account_edi_ubl_cii{_tests}: handle price_include taxes


In Factur-X, there is no way to represent a tax "price_include" because
every amounts should be tax excluded.

Currently in Factur-X, a line with a tax price_include = True will be
incorrectly exported. Indeed, the Factur-X.xml is generated by setting the
GrossPriceProduct as the price_unit. In Factur-X, this amount (and the others
in the line details) should be tax excluded. Thus, it's wrong to set the
GrossPriceProduct as the price_unit.

To fix this, the GrossPriceProduct should be the price_unit if no tax
price_include = True is set, otherwise, the gross price = price_unit/(1+tax/100).

This way, the Factur-X file will be consistent with the norm.

Note that the import of a Factur-X xml will thus try to pick taxes with price_include = False,
and the price_unit will be tax excluded. If no matching tax with price_include = False is
retrieved, a tax with price_include = True is searched, if found, the price_unit is
recomputed accordingly. In both cases, the lines subtotals are the same.

opw-3032382

closes odoo/odoo#106465

X-original-commit: 649e0f2d
Signed-off-by: default avatarWilliam André (wan) <wan@odoo.com>
Signed-off-by: default avatarJulien Van Roy <juvr@odoo.com>
parent d4b91b6c
No related branches found
No related tags found
No related merge requests found
Showing
with 422 additions and 91 deletions
......@@ -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">
......
......@@ -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)"/>
......
......@@ -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 !
......
......@@ -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
......
......@@ -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
......
......@@ -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)
# -------------------------------------------------------------------------
......
......@@ -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>
......
<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&#232;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-&#201;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-&#201;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>
......@@ -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>
......
......@@ -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'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment