diff --git a/modules/account_invoice/CHANGELOG b/modules/account_invoice/CHANGELOG index bc41f4171a..afe8b5e3fc 100644 --- a/modules/account_invoice/CHANGELOG +++ b/modules/account_invoice/CHANGELOG @@ -1,3 +1,4 @@ +* Add cache on invoice's amounts Version 6.8.0 - 2023-05-01 -------------------------- diff --git a/modules/account_invoice/invoice.py b/modules/account_invoice/invoice.py index 6ef68e74d5..6fcd0d38be 100644 --- a/modules/account_invoice/invoice.py +++ b/modules/account_invoice/invoice.py @@ -6,7 +6,7 @@ from decimal import Decimal from itertools import chain, combinations, groupby from sql.aggregate import Sum -from sql.conditionals import Case, Coalesce +from sql.conditionals import Coalesce from sql.functions import CharLength, Round from trytond import backend @@ -218,12 +218,18 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): untaxed_amount = fields.Function(Monetary( "Untaxed", currency='currency', digits='currency'), 'get_amount', searcher='search_untaxed_amount') + untaxed_amount_cache = fields.Numeric( + "Untaxed Cache", digits='currency', readonly=True) tax_amount = fields.Function(Monetary( "Tax", currency='currency', digits='currency'), 'get_amount', searcher='search_tax_amount') + tax_amount_cache = fields.Numeric( + "Tax Cache", digits='currency', readonly=True) total_amount = fields.Function(Monetary( "Total", currency='currency', digits='currency'), 'get_amount', searcher='search_total_amount') + total_amount_cache = fields.Numeric( + "Total Cache", digits='currency', readonly=True) reconciled = fields.Function(fields.Date('Reconciled', states={ 'invisible': ~Eval('reconciled'), @@ -302,6 +308,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): 'state', 'alternative_payees', 'payment_lines', 'move', 'cancel_move', 'additional_moves', 'invoice_report_cache', 'invoice_report_format', + 'total_amount_cache', 'tax_amount_cache', 'untaxed_amount_cache', 'lines'} cls._order = [ ('number', 'DESC NULLS FIRST'), @@ -597,18 +604,28 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): def get_amount(cls, invoices, names): pool = Pool() InvoiceTax = pool.get('account.invoice.tax') - Move = pool.get('account.move') - MoveLine = pool.get('account.move.line') cursor = Transaction().connection.cursor() - untaxed_amount = dict((i.id, Decimal(0)) for i in invoices) - tax_amount = dict((i.id, Decimal(0)) for i in invoices) - total_amount = dict((i.id, Decimal(0)) for i in invoices) + untaxed_amount = {i.id: i.currency.round(Decimal(0)) for i in invoices} + tax_amount = untaxed_amount.copy() + total_amount = untaxed_amount.copy() + + invoices_no_cache = [] + for invoice in invoices: + if (invoice.total_amount_cache is not None + and invoice.untaxed_amount_cache is not None + and invoice.tax_amount_cache is not None): + total_amount[invoice.id] = invoice.total_amount_cache + untaxed_amount[invoice.id] = invoice.untaxed_amount_cache + tax_amount[invoice.id] = invoice.tax_amount_cache + else: + invoices_no_cache.append(invoice.id) + invoices_no_cache = cls.browse(invoices_no_cache) type_name = cls.tax_amount._field.sql_type().base tax = InvoiceTax.__table__() to_round = False - for sub_ids in grouped_slice(invoices): + for sub_ids in grouped_slice(invoices_no_cache): red_sql = reduce_ids(tax.invoice, sub_ids) cursor.execute(*tax.select(tax.invoice, Coalesce(Sum(tax.amount), 0).as_(type_name), @@ -626,56 +643,11 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): tax_amount[invoice.id] = invoice.currency.round( tax_amount[invoice.id]) - invoices_move = set() - invoices_no_move = set() - for invoice in invoices: - if invoice.move: - invoices_move.add(invoice.id) - else: - invoices_no_move.add(invoice.id) - invoices_move = cls.browse(invoices_move) - invoices_no_move = cls.browse(invoices_no_move) - - type_name = cls.total_amount._field.sql_type().base - invoice = cls.__table__() - move = Move.__table__() - line = MoveLine.__table__() - to_round = False - for sub_ids in grouped_slice(invoices_move): - red_sql = reduce_ids(invoice.id, sub_ids) - cursor.execute(*invoice.join(move, - condition=invoice.move == move.id - ).join(line, condition=move.id == line.move - ).select(invoice.id, - Coalesce(Sum( - Case(( - line.second_currency == invoice.currency, - line.amount_second_currency), - else_=line.debit - line.credit)), - 0).cast(type_name), - where=(invoice.account == line.account) & red_sql, - group_by=invoice.id)) - for invoice_id, sum_ in cursor: - # SQLite uses float for SUM - if not isinstance(sum_, Decimal): - sum_ = Decimal(str(sum_)) - to_round = True - total_amount[invoice_id] = sum_ - - for invoice in invoices_move: - if invoice.type == 'in': - total_amount[invoice.id] *= -1 - # Float amount must be rounded to get the right precision - if to_round: - total_amount[invoice.id] = invoice.currency.round( - total_amount[invoice.id]) - untaxed_amount[invoice.id] = ( - total_amount[invoice.id] - tax_amount[invoice.id]) - - for invoice in invoices_no_move: + for invoice in invoices_no_cache: + zero = invoice.currency.round(Decimal(0)) untaxed_amount[invoice.id] = sum( (line.amount for line in invoice.lines - if line.type == 'line'), Decimal(0)) + if line.type == 'line'), zero) total_amount[invoice.id] = ( untaxed_amount[invoice.id] + tax_amount[invoice.id]) @@ -1310,6 +1282,9 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): default.setdefault('invoice_date', None) default.setdefault('accounting_date', None) default.setdefault('payment_term_date', None) + default.setdefault('total_amount_cache', None) + default.setdefault('untaxed_amount_cache', None) + default.setdefault('tax_amount_cache', None) return super(Invoice, cls).copy(invoices, default=default) @classmethod @@ -1561,12 +1536,29 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): cls.cancel(invoices) return new_invoices + @classmethod + def _store_cache(cls, invoices): + for invoice in invoices: + if (invoice.untaxed_amount == invoice.untaxed_amount_cache + and invoice.tax_amount == invoice.tax_amount_cache + and invoice.total_amount == invoice.total_amount_cache): + continue + invoice.untaxed_amount_cache = invoice.untaxed_amount + invoice.tax_amount_cache = invoice.tax_amount + invoice.total_amount_cache = invoice.total_amount + cls.save(invoices) + @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, invoices): Move = Pool().get('account.move') + cls.write(invoices, { + 'tax_amount_cache': None, + 'untaxed_amount_cache': None, + 'total_amount_cache': None, + }) moves = [] for invoice in invoices: if invoice.move: @@ -1588,6 +1580,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): invoices_in = cls.browse([i for i in invoices if i.type == 'in']) cls.set_number(invoices_in) + cls._store_cache(invoices) cls._check_similar(invoices) moves = [] for invoice in invoices_in: @@ -1656,6 +1649,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): context = transaction.context cls.set_number(invoices) + cls._store_cache(invoices) moves = [] for invoice in invoices: move = invoice.get_move() @@ -1806,6 +1800,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin): cancel_moves.extend(additional_cancel_moves) if cancel_moves: Move.save(cancel_moves) + cls._store_cache(invoices) cls.save(to_save) if delete_moves: Move.delete(delete_moves) diff --git a/modules/account_invoice/tests/scenario_invoice.rst b/modules/account_invoice/tests/scenario_invoice.rst index 949934745e..b55d9b5888 100644 --- a/modules/account_invoice/tests/scenario_invoice.rst +++ b/modules/account_invoice/tests/scenario_invoice.rst @@ -346,10 +346,12 @@ Create some complex invoice and test its taxes base rounding:: Decimal('0.00') >>> invoice.taxes[0].base == invoice.untaxed_amount True - >>> found_invoice, = Invoice.find([('untaxed_amount', '=', Decimal(0))]) + >>> empty_invoice, found_invoice = Invoice.find( + ... [('untaxed_amount', '=', Decimal(0))], order=[('id', 'ASC')]) >>> found_invoice.id == invoice.id True - >>> found_invoice, = Invoice.find([('total_amount', '=', Decimal(0))]) + >>> empty_invoice, found_invoice = Invoice.find( + ... [('total_amount', '=', Decimal(0))], order=[('id', 'ASC')]) >>> found_invoice.id == invoice.id True diff --git a/modules/account_invoice/tests/scenario_invoice_tax_deductible.rst b/modules/account_invoice/tests/scenario_invoice_tax_deductible.rst index 06ebe6a746..232f7524fa 100644 --- a/modules/account_invoice/tests/scenario_invoice_tax_deductible.rst +++ b/modules/account_invoice/tests/scenario_invoice_tax_deductible.rst @@ -91,7 +91,7 @@ Post a supplier invoice with 0% deductible:: (Decimal('550.00'), Decimal('0.00'), Decimal('550.00')) >>> invoice.click('post') >>> invoice.untaxed_amount, invoice.tax_amount, invoice.total_amount - (Decimal('550.00'), Decimal('0'), Decimal('550.00')) + (Decimal('550.00'), Decimal('0.00'), Decimal('550.00')) >>> len(invoice.taxes) 0