Check the invoice amounts against the source amounts

This commit is contained in:
Cédric Krier
2026-03-13 23:53:22 +01:00
parent df5dc23253
commit acfece868a
5 changed files with 126 additions and 1 deletions
+1
View File
@@ -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
+37
View File
@@ -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 (
+3
View File
@@ -42,6 +42,9 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.message" id="msg_invoice_payment_lines_greater_amount">
<field name="text">Payment lines amount on invoice "%(invoice)s" can not be greater than the invoice amount.</field>
</record>
<record model="ir.message" id="msg_invoice_source_mismatch">
<field name="text">The "%(field)s" value of the invoice "%(invoice)s", %(value)s, must equal the source value, %(source)s.</field>
</record>
<record model="ir.message" id="msg_invoice_supplier_payment_reference_invalid">
<field name="text">The %(type)s "%(reference)s" on invoice "%(invoice)s" is not valid.</field>
</record>
@@ -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'
@@ -38,13 +38,21 @@ this repository contains the full copyright notices and license terms. -->
<label name="state"/>
<field name="state"/>
</group>
<group col="2" colspan="2" id="amount" yfill="1" yalign="1">
<group col="4" colspan="2" id="amount" yfill="1" yalign="1">
<label name="untaxed_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="untaxed_amount" xalign="1.0" xexpand="0"/>
<label name="source_untaxed_amount" string="expected" xalign="1.0" xexpand="1" xfill="0"/>
<field name="source_untaxed_amount" xalign="1.0" xexpand="0"/>
<label name="tax_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="tax_amount" xalign="1.0" xexpand="0"/>
<label name="source_tax_amount" string="expected" xalign="1.0" xexpand="1" xfill="0"/>
<field name="source_tax_amount" xalign="1.0" xexpand="0"/>
<label name="total_amount" xalign="1.0" xexpand="1" xfill="0"/>
<field name="total_amount" xalign="1.0" xexpand="0"/>
<label name="source_total_amount" string="expected" xalign="1.0" xexpand="1" xfill="0"/>
<field name="source_total_amount" xalign="1.0" xexpand="0"/>
</group>
</group>
</group>