From 3db3dff24ddf0c36de33a61611f9780e22903193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 5 Sep 2020 23:37:58 +0200 Subject: [PATCH] Allow setting the tax date on taxable lines We add a taxing date to the invoice line which allow overriding the value of the invoice. When crediting an invoice, the original tax date of each line is stored on the credit line so the tax are computed the same way. issue9520 review323791002 --- CHANGELOG | 1 + invoice.py | 36 +++++++++++++++++++++--------------- tests/scenario_invoice.rst | 6 ++++++ view/invoice_line_form.xml | 4 ++++ 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c4d945f8e6..3838ecbd27 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add optional taxes date on invoice line * Convert Invoice.update_taxes into dualmethod * Rename invoice state from cancel to cancelled diff --git a/invoice.py b/invoice.py index 9e1721ce66..0158f14725 100644 --- a/invoice.py +++ b/invoice.py @@ -820,20 +820,9 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): @property def taxable_lines(self): taxable_lines = [] - # In case we're called from an on_change we have to use some sensible - # defaults for line in self.lines: - if getattr(line, 'type', None) != 'line': - continue - taxable_lines.append(tuple()) - for attribute, default_value in [ - ('taxes', []), - ('unit_price', Decimal(0)), - ('quantity', 0.), - ]: - value = getattr(line, attribute, None) - taxable_lines[-1] += ( - value if value is not None else default_value,) + if getattr(line, 'type', None) == 'line': + taxable_lines.extend(line.taxable_lines) return taxable_lines @property @@ -1744,6 +1733,15 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin): 'readonly': _states['readonly'] | ~Bool(Eval('account')), }, depends=['type', 'invoice_type', 'company', 'account'] + _depends) + taxes_date = fields.Date( + "Taxes Date", + states={ + 'invisible': Eval('type') != 'line', + 'readonly': _states['readonly'], + }, + depends=['type'] + _depends, + help="The date at which the taxes are computed.\n" + "Leave empty for the accounting date.") invoice_taxes = fields.Function(fields.Many2Many('account.invoice.tax', None, None, 'Invoice Taxes'), 'get_invoice_taxes') origin = fields.Reference('Origin', selection='get_origin', select=True, @@ -1920,11 +1918,18 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin): @property def taxable_lines(self): - return [(self.taxes, self.unit_price, self.quantity)] + # In case we're called from an on_change we have to use some sensible + # defaults + return [( + getattr(self, 'taxes', None) or [], + getattr(self, 'unit_price', None) or Decimal(0), + getattr(self, 'quantity', None) or 0, + getattr(self, 'tax_date', None), + )] @property def tax_date(self): - return self.invoice.tax_date + return self.taxes_date or self.invoice.tax_date def _get_tax_context(self): if self.invoice: @@ -2254,6 +2259,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin): for field in ('sequence', 'type', 'invoice_type', 'unit_price', 'description', 'unit', 'product', 'account'): setattr(line, field, getattr(self, field)) + line.taxes_date = self.tax_date line.taxes = self.taxes return line diff --git a/tests/scenario_invoice.rst b/tests/scenario_invoice.rst index 5dab753670..dcab12c817 100644 --- a/tests/scenario_invoice.rst +++ b/tests/scenario_invoice.rst @@ -220,6 +220,12 @@ Credit invoice with refund:: 'cancelled' >>> invoice.reconciled == today True + >>> credit_note, = Invoice.find([ + ... ('type', '=', 'out'), ('id', '!=', invoice.id)]) + >>> credit_note.state + 'paid' + >>> all(line.taxes_date == today for line in credit_note.lines) + True >>> receivable.reload() >>> receivable.debit Decimal('240.00') diff --git a/view/invoice_line_form.xml b/view/invoice_line_form.xml index 5b37712d19..4ecddb1025 100644 --- a/view/invoice_line_form.xml +++ b/view/invoice_line_form.xml @@ -23,6 +23,10 @@ this repository contains the full copyright notices and license terms. -->