diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py index 9d0194be268..1db21d29058 100644 --- a/addons/account_edi/models/account_move.py +++ b/addons/account_edi/models/account_move.py @@ -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 diff --git a/addons/account_edi_facturx/data/facturx_templates.xml b/addons/account_edi_facturx/data/facturx_templates.xml index 719a1caae48..183ca443f05 100644 --- a/addons/account_edi_facturx/data/facturx_templates.xml +++ b/addons/account_edi_facturx/data/facturx_templates.xml @@ -2,14 +2,14 @@