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. -->
-
+
+
+
+
+
+
+
+
+