changeset 38d21c5755e2 in modules/account_invoice:default
details:
https://hg.tryton.org/modules/account_invoice?cmd=changeset&node=38d21c5755e2
description:
Raise a warning when posting supplier invoice with same reference
issue8244
review265201002
diffstat:
CHANGELOG | 1 +
exceptions.py | 4 ++
invoice.py | 59 +++++++++++++++++++++++++++++++++++-
message.xml | 3 +
tests/scenario_invoice_supplier.rst | 22 +++++++++++++
5 files changed, 87 insertions(+), 2 deletions(-)
diffs (174 lines):
diff -r 4f87d78f6c8e -r 38d21c5755e2 CHANGELOG
--- a/CHANGELOG Sat Feb 12 19:34:40 2022 +0100
+++ b/CHANGELOG Mon Feb 14 00:11:38 2022 +0100
@@ -1,3 +1,4 @@
+* Raise a warning when validating supplier invoice with same reference
* Add support for Python 3.10
* Remove support for Python 3.6
diff -r 4f87d78f6c8e -r 38d21c5755e2 exceptions.py
--- a/exceptions.py Sat Feb 12 19:34:40 2022 +0100
+++ b/exceptions.py Mon Feb 14 00:11:38 2022 +0100
@@ -39,3 +39,7 @@
class InvoiceFutureWarning(UserWarning):
pass
+
+
+class InvoiceSimilarWarning(UserWarning):
+ pass
diff -r 4f87d78f6c8e -r 38d21c5755e2 invoice.py
--- a/invoice.py Sat Feb 12 19:34:40 2022 +0100
+++ b/invoice.py Mon Feb 14 00:11:38 2022 +0100
@@ -1,5 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+import datetime as dt
from collections import defaultdict, namedtuple
from decimal import Decimal
from itertools import chain, combinations, groupby
@@ -30,8 +31,8 @@
from .exceptions import (
InvoiceFutureWarning, InvoiceLineValidationError, InvoiceNumberError,
- InvoicePaymentTermDateWarning, InvoiceTaxValidationError,
- InvoiceValidationError, PayInvoiceError)
+ InvoicePaymentTermDateWarning, InvoiceSimilarWarning,
+ InvoiceTaxValidationError, InvoiceValidationError, PayInvoiceError)
if config.getboolean('account_invoice', 'filestore', default=False):
file_id = 'invoice_report_cache_id'
@@ -1524,6 +1525,7 @@
invoices_in = cls.browse([i for i in invoices if i.type == 'in'])
cls.set_number(invoices_in)
+ cls._check_similar(invoices)
moves = []
for invoice in invoices_in:
move = invoice.get_move()
@@ -1576,6 +1578,7 @@
raise InvoiceFutureWarning(warning_key,
gettext('account_invoice.msg_invoice_date_future',
invoices=names))
+ cls._check_similar([i for i in invoices if i.state != 'validated'])
cls._post(invoices)
@classmethod
@@ -1606,6 +1609,58 @@
cls.__queue__.process(reconciled)
@classmethod
+ def _check_similar(cls, invoices, type='in'):
+ pool = Pool()
+ Warning = pool.get('res.user.warning')
+ for sub_invoices in grouped_slice(invoices):
+ sub_invoices = list(sub_invoices)
+ domain = list(filter(None,
+ (i._similar_domain() for i in sub_invoices
+ if i.type == type)))
+ if not domain:
+ continue
+ if cls.search(['OR'] + domain, order=[]):
+ for invoice in sub_invoices:
+ domain = invoice._similar_domain()
+ if not domain:
+ continue
+ try:
+ similar, = cls.search(domain, limit=1)
+ except ValueError:
+ continue
+ warning_key = Warning.format(
+ 'invoice_similar', [invoice])
+ if Warning.check(warning_key):
+ raise InvoiceSimilarWarning(warning_key,
+ gettext('account_invoice.msg_invoice_similar',
+ similar=similar.rec_name,
+ invoice=invoice.rec_name))
+
+ def _similar_domain(self, delay=None):
+ pool = Pool()
+ Date = pool.get('ir.date')
+ if not self.reference:
+ return
+ with Transaction().set_context(company=self.company.id):
+ invoice_date = self.invoice_date or Date.today()
+ if delay is None:
+ delay = dt.timedelta(days=60)
+ return [
+ ('company', '=', self.company.id),
+ ('type', '=', self.type),
+ ('party', '=', self.party.id),
+ ('reference', '=', self.reference),
+ ('id', '!=', self.id),
+ ['OR',
+ ('invoice_date', '=', None),
+ [
+ ('invoice_date', '>=', invoice_date - delay),
+ ('invoice_date', '<=', invoice_date + delay),
+ ],
+ ],
+ ]
+
+ @classmethod
@ModelView.button_action('account_invoice.wizard_pay')
def pay(cls, invoices):
pass
diff -r 4f87d78f6c8e -r 38d21c5755e2 message.xml
--- a/message.xml Sat Feb 12 19:34:40 2022 +0100
+++ b/message.xml Mon Feb 14 00:11:38 2022 +0100
@@ -81,6 +81,9 @@
<record model="ir.message" id="msg_invoice_date_future">
<field name="text">The invoices "%(invoices)s" have an invoice
date in the future.</field>
</record>
+ <record model="ir.message" id="msg_invoice_similar">
+ <field name="text">The invoice "%(invoice)s" is similar to invoice
"%(similar)s".</field>
+ </record>
</data>
</tryton>
diff -r 4f87d78f6c8e -r 38d21c5755e2 tests/scenario_invoice_supplier.rst
--- a/tests/scenario_invoice_supplier.rst Sat Feb 12 19:34:40 2022 +0100
+++ b/tests/scenario_invoice_supplier.rst Mon Feb 14 00:11:38 2022 +0100
@@ -3,6 +3,7 @@
=========================
Imports::
+
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
@@ -102,6 +103,7 @@
>>> invoice.party = party
>>> invoice.payment_term = payment_term
>>> invoice.invoice_date = today
+ >>> invoice.reference = 'FAC001'
>>> line = InvoiceLine()
>>> invoice.lines.append(line)
>>> line.product = product
@@ -192,6 +194,26 @@
>>> credit_note.total_amount == -invoice.total_amount
True
+A warning is raised when creating an invoice with same reference::
+
+ >>> invoice = Invoice()
+ >>> invoice.type = 'in'
+ >>> invoice.party = party
+ >>> invoice.invoice_date = today
+ >>> invoice.reference = 'FAC001'
+ >>> line = invoice.lines.new()
+ >>> line.product = product
+ >>> line.quantity = 1
+ >>> line.unit_price = Decimal('20')
+ >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ InvoiceSimilarWarning: ...
+ >>> invoice.reference = 'FAC002'
+ >>> invoice.click('post')
+ >>> invoice.state
+ 'posted'
+
Create a posted and a draft invoice to cancel::
>>> invoice = Invoice()