[IMP] account_edi[_facturx]: Add generic values to export invoice EDI

This commit is contained in:
Laurent Smet
2021-03-29 17:57:57 +00:00
committed by Josse Colpaert
parent 13bf64b376
commit 9b993f4af7
4 changed files with 157 additions and 78 deletions
+122
View File
@@ -135,6 +135,66 @@ class AccountMove(models.Model):
# Export Electronic Document
####################################################
def _prepare_edi_vals_to_export(self):
''' The purpose of this helper is to prepare values in order to export an invoice through the EDI system.
This includes the computation of the tax details for each invoice line that could be very difficult to
handle regarding the computation of the base amount.
:return: A python dict containing default pre-processed values.
'''
self.ensure_one()
res = {
'record': self,
'invoice_line_vals_list': [],
}
# Invoice lines details.
tax_detail_per_tax = {}
added_base_amount_keys = set()
for index, line in enumerate(self.invoice_line_ids.filtered(lambda line: not line.display_type), start=1):
line_vals = line._prepare_edi_vals_to_export()
line_vals['index'] = index
res['invoice_line_vals_list'].append(line_vals)
# Tax details.
for tax_vals in line_vals['tax_detail_vals_list']:
tax_detail_per_tax.setdefault(tax_vals['tax'], {
'tax': tax_vals['tax'],
'orig_tax': tax_vals['orig_tax'],
'tax_base_amount_currency': 0.0,
'tax_amount_currency': 0.0,
'tag_ids': set(),
})
vals = tax_detail_per_tax[tax_vals['tax']]
# Avoid adding multiple times the same base (e.g. with multiple taxes on the same line or multiple
# repartition lines).
base_amount_key = (line.id, tax_vals['tax']['id'])
if base_amount_key not in added_base_amount_keys:
vals['tax_base_amount_currency'] += tax_vals['tax_base_amount_currency']
added_base_amount_keys.add(base_amount_key)
vals['tax_amount_currency'] += tax_vals['tax_amount_currency']
for tag in tax_vals['tags']:
vals['tag_ids'].add(tag.id)
# Format the aggregated tax details as a list.
res['tax_detail_vals_list'] = []
for tax_detail_vals in tax_detail_per_tax.values():
res['tax_detail_vals_list'].append({
**tax_detail_vals,
'tags': self.env['account.account.tag'].browse(tax_detail_vals['tag_ids']),
})
# Totals.
res.update({
'total_price_subtotal_before_discount': sum(x['price_subtotal_before_discount'] for x in res['invoice_line_vals_list']),
'total_price_discount': sum(x['price_discount'] for x in res['invoice_line_vals_list']),
})
return res
def _update_payments_edi_documents(self):
''' Update the edi documents linked to the current journal entries. These journal entries must be linked to an
account.payment of an account.bank.statement.line. This additional method is needed because the payment flow is
@@ -307,6 +367,68 @@ class AccountMoveLine(models.Model):
# Export Electronic Document
####################################################
def _prepare_edi_vals_to_export(self):
''' The purpose of this helper is the same as '_prepare_edi_vals_to_export' but for a single invoice line.
This includes the computation of the tax details for each invoice line or the management of the discount.
Indeed, in some EDI, we need to provide extra values depending the discount such as:
- the discount as an amount instead of a percentage.
- the price_unit but after subtraction of the discount.
:return: A python dict containing default pre-processed values.
'''
self.ensure_one()
res = {
'line': self,
'price_unit_after_discount': self.price_unit * (1 - (self.discount / 100.0)),
'price_subtotal_before_discount': self.currency_id.round(self.price_unit * self.quantity),
'price_subtotal_unit': self.currency_id.round(self.price_subtotal / self.quantity) if self.quantity else 0.0,
'price_total_unit': self.currency_id.round(self.price_total / self.quantity) if self.quantity else 0.0,
}
res['price_discount'] = res['price_subtotal_before_discount'] - self.price_subtotal
# Tax details.
tax_detail_per_tax = {}
taxes_res = self.tax_ids.compute_all(
res['price_unit_after_discount'],
currency=self.currency_id,
quantity=self.quantity,
product=self.product_id,
partner=self.partner_id,
is_refund=self.move_id.move_type in ('in_refund', 'out_refund'),
)
taxes_added_to_base = set()
for tax_vals in taxes_res['taxes']:
tax_rep = self.env['account.tax.repartition.line'].browse(tax_vals['tax_repartition_line_id'])
tax = tax_rep.tax_id
tax_detail_per_tax.setdefault(tax, {
'tax': tax,
'orig_tax': tax_vals['group'].id if tax_vals['group'] else tax.id,
'tax_base_amount_currency': 0.0,
'tax_amount_currency': 0.0,
'tag_ids': set(),
})
vals = tax_detail_per_tax[tax]
# Avoid adding multiple times the same base (e.g. with multiple repartition lines).
if tax.id not in taxes_added_to_base:
vals['tax_base_amount_currency'] = tax_vals['base']
taxes_added_to_base.add(tax.id)
vals['tax_amount_currency'] += tax_vals['amount']
for tag_id in tax_rep.tag_ids:
vals['tag_ids'].add(tag_id)
res['tax_detail_vals_list'] = []
for tax_detail_vals in tax_detail_per_tax.values():
res['tax_detail_vals_list'].append({
**tax_detail_vals,
'tags': self.env['account.account.tag'].browse(tax_detail_vals['tag_ids']),
})
return res
def reconcile(self):
# OVERRIDE
# In some countries, the payments must be sent to the government under some condition. One of them could be
@@ -2,14 +2,14 @@
<odoo>
<data>
<template id="account_invoice_line_facturx_export">
<t t-set="line" t-value="line_values['line']"/>
<t t-set="line" t-value="line_vals['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_values['index']"/>
<ram:LineID t-esc="line_vals['index']"/>
</ram:AssociatedDocumentLineDocument>
<!-- Product. -->
@@ -28,8 +28,8 @@
<ram:SpecifiedLineTradeAgreement>
<ram:GrossPriceProductTradePrice>
<ram:ChargeAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(line.price_unit, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(line.price_unit, record.currency_id)"/>
<!-- Discount. -->
<ram:AppliedTradeAllowanceCharge t-if="line.discount">
@@ -48,8 +48,8 @@
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<t t-foreach="line_values['tax_details']" t-as="tax_vals">
<t t-set="tax" t-value="tax_vals['tax']"/>
<t t-foreach="line_vals['tax_detail_vals_list']" t-as="tax_detail_vals">
<t t-set="tax" t-value="tax_detail_vals['tax']"/>
<ram:ApplicableTradeTax t-if="tax.amount_type == 'percent'">
<ram:RateApplicablePercent t-esc="tax.amount"/>
</ram:ApplicableTradeTax>
@@ -57,8 +57,8 @@
<!-- Subtotal. -->
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(line.price_subtotal, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(line.price_subtotal, record.currency_id)"/>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
@@ -94,7 +94,6 @@
</template>
<template id="account_invoice_facturx_export">
<t t-set="currency" t-value="record.currency_id or record.company_currency_id"/>
<rsm:CrossIndustryInvoice
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
@@ -124,7 +123,7 @@
<rsm:SupplyChainTradeTransaction>
<!-- Invoice lines. -->
<t t-foreach="invoice_line_values" t-as="line_values">
<t t-foreach="invoice_line_vals_list" t-as="line_vals">
<t t-call="account_edi_facturx.account_invoice_line_facturx_export"/>
</t>
@@ -183,18 +182,18 @@
</ram:SpecifiedTradeSettlementPaymentMeans>
<!-- Tax Summary. -->
<t t-foreach="tax_details" t-as="tax_vals">
<t t-set="tax_line" t-value="tax_vals['line']"/>
<t t-foreach="tax_detail_vals_list" t-as="tax_detail_vals">
<t t-set="tax" t-value="tax_detail_vals['tax']"/>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(tax_vals['tax_amount'], currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(tax_detail_vals['tax_amount_currency'], record.currency_id)"/>
<ram:BasisAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(tax_vals['tax_base_amount'], currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(tax_detail_vals['tax_base_amount_currency'], record.currency_id)"/>
<ram:RateApplicablePercent
t-if="tax_line.tax_line_id.amount_type == 'percent'"
t-esc="tax_line.tax_line_id.amount"/>
t-if="tax.amount_type == 'percent'"
t-esc="tax.amount"/>
</ram:ApplicableTradeTax>
</t>
@@ -209,23 +208,23 @@
<!-- Summary. -->
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(record.amount_untaxed, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_untaxed, record.currency_id)"/>
<ram:TaxBasisTotalAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(record.amount_untaxed, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_untaxed, record.currency_id)"/>
<ram:TaxTotalAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(record.amount_tax, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_tax, record.currency_id)"/>
<ram:GrandTotalAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(record.amount_total, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_total, record.currency_id)"/>
<ram:TotalPrepaidAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(record.amount_total - record.amount_residual, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_total - record.amount_residual, record.currency_id)"/>
<ram:DuePayableAmount
t-att-currencyID="currency.name"
t-esc="format_monetary(record.amount_residual, currency)"/>
t-att-currencyID="record.currency_id.name"
t-esc="format_monetary(record.amount_residual, record.currency_id)"/>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
@@ -52,53 +52,11 @@ class AccountEdiFormat(models.Model):
self.ensure_one()
# Create file content.
template_values = {
'record': invoice,
**invoice._prepare_edi_vals_to_export(),
'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.with_context(force_sign=line.move_id._get_tax_force_sign()).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('/', '_'))
@@ -180,16 +180,16 @@ class TestAccountEdiFacturx(AccountEdiTestCommon):
</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>
<ApplicableTradeTax>
<CalculatedAmount currencyID="Gol">220.000</CalculatedAmount>
<BasisAmount currencyID="Gol">1100.000</BasisAmount>
<RateApplicablePercent>20.0</RateApplicablePercent>
</ApplicableTradeTax>
<SpecifiedTradePaymentTerms>
<DueDateDateTime>
<DateTimeString>20170101</DateTimeString>