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()

Reply via email to