diff --git a/modules/account_invoice/CHANGELOG b/modules/account_invoice/CHANGELOG index 3edb0454be..aca205f8b2 100644 --- a/modules/account_invoice/CHANGELOG +++ b/modules/account_invoice/CHANGELOG @@ -1,3 +1,4 @@ +* Check the invoice amounts against the source amounts * Add support for Python 3.14 * Remove support for Python 3.9 * Add payment means to invoices diff --git a/modules/account_invoice/invoice.py b/modules/account_invoice/invoice.py index 2f3e432569..c2c8a9894a 100644 --- a/modules/account_invoice/invoice.py +++ b/modules/account_invoice/invoice.py @@ -310,16 +310,31 @@ class Invoice( origin_invoices = fields.Function(fields.Many2Many( 'account.invoice', None, None, "Origin Invoices"), 'get_origin_invoices', searcher='search_origin_invoices') + source_untaxed_amount = Monetary( + "Source Untaxed", digits='currency', readonly=True, + states={ + 'invisible': Eval('source_untaxed_amount', None) == Null, + }) 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) + source_tax_amount = Monetary( + "Source Tax", digits='currency', readonly=True, + states={ + 'invisible': Eval('source_tax_amount', None) == Null, + }) 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) + source_total_amount = Monetary( + "Source Total", digits='currency', readonly=True, + states={ + 'invisible': Eval('source_total_amount', None) == Null, + }) total_amount = fields.Function(Monetary( "Total", currency='currency', digits='currency'), 'get_amount', searcher='search_total_amount') @@ -1719,8 +1734,30 @@ class Invoice( @classmethod def validate_fields(cls, invoices, field_names): super().validate_fields(invoices, field_names) + cls.check_source(invoices, field_names) cls.check_supplier_payment_reference(invoices, field_names) + @classmethod + def check_source(cls, invoices, field_names): + pool = Pool() + Lang = pool.get('ir.lang') + if field_names and 'state' not in field_names: + return + for invoice in invoices: + if invoice.state not in {'draft', 'cancelled'}: + for field in ['untaxed_amount', 'tax_amount', 'total_amount']: + source = getattr(invoice, f'source_{field}') + value = getattr(invoice, field) + if source not in {None, value}: + lang = Lang.get() + raise InvoiceValidationError( + gettext('account_invoice' + '.msg_invoice_source_mismatch', + invoice=invoice.rec_name, + source=lang.currency(source, invoice.currency), + value=lang.currency(value, invoice.currency), + **cls.__names__(field=field))) + @classmethod def check_supplier_payment_reference(cls, invoices, field_names): if field_names and not ( diff --git a/modules/account_invoice/message.xml b/modules/account_invoice/message.xml index b74cdc8cf9..acedc305fd 100644 --- a/modules/account_invoice/message.xml +++ b/modules/account_invoice/message.xml @@ -42,6 +42,9 @@ this repository contains the full copyright notices and license terms. --> Payment lines amount on invoice "%(invoice)s" can not be greater than the invoice amount. + + The "%(field)s" value of the invoice "%(invoice)s", %(value)s, must equal the source value, %(source)s. + The %(type)s "%(reference)s" on invoice "%(invoice)s" is not valid. diff --git a/modules/account_invoice/tests/scenario_invoice_check_source.rst b/modules/account_invoice/tests/scenario_invoice_check_source.rst new file mode 100644 index 0000000000..f09b67b3e3 --- /dev/null +++ b/modules/account_invoice/tests/scenario_invoice_check_source.rst @@ -0,0 +1,76 @@ +============================= +Invoice Check Source Scenario +============================= + +Imports:: + + >>> import datetime as dt + >>> from decimal import Decimal + + >>> from proteus import Model + >>> from trytond.modules.account.tests.tools import ( + ... create_chart, create_fiscalyear, create_tax, get_accounts) + >>> from trytond.modules.account_invoice.tests.tools import ( + ... set_fiscalyear_invoice_sequences) + >>> from trytond.modules.company.tests.tools import create_company + >>> from trytond.tests.tools import activate_modules + + >>> today = dt.date.today() + +Activate modules:: + + >>> config = activate_modules('account_invoice', create_company, create_chart) + + >>> Invoice = Model.get('account.invoice') + >>> Party = Model.get('party.party') + +Create fiscal year:: + + >>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear()) + >>> fiscalyear.click('create_period') + +Get accounts:: + + >>> accounts = get_accounts() + +Create tax:: + + >>> tax = create_tax(Decimal('.10')) + >>> tax.save() + +Create party:: + + >>> supplier = Party(name="Supplier") + >>> supplier.save() + +Create invoice:: + + >>> invoice = Invoice(type='in', party=supplier) + >>> invoice.invoice_date = today + >>> line = invoice.lines.new() + >>> line.quantity = 1 + >>> line.unit_price = Decimal(10) + >>> line.account = accounts['expense'] + >>> line.taxes.append(tax) + >>> invoice.save() + + >>> Invoice.write([invoice.id], { + ... 'source_untaxed_amount': Decimal('100.00'), + ... 'source_tax_amount': Decimal('10.00'), + ... 'source_total_amount': Decimal('110.00'), + ... }, invoice._context) + +Try to validate:: + + >>> invoice.click('validate_invoice') + Traceback (most recent call last): + ... + InvoiceValidationError: ... + +Correct quantity:: + + >>> line, = invoice.lines + >>> line.quantity = 10 + >>> invoice.click('validate_invoice') + >>> invoice.state + 'validated' diff --git a/modules/account_invoice/view/invoice_form.xml b/modules/account_invoice/view/invoice_form.xml index d01547e2f9..956eb2e8be 100644 --- a/modules/account_invoice/view/invoice_form.xml +++ b/modules/account_invoice/view/invoice_form.xml @@ -38,13 +38,21 @@ this repository contains the full copyright notices and license terms. -->