changeset 985fe4c47073 in modules/account_payment:default
details: 
https://hg.tryton.org/modules/account_payment?cmd=changeset;node=985fe4c47073
description:
        Add aggregated information on Payment groups

        issue7890
        review305581002
diffstat:

 CHANGELOG                          |    2 +
 payment.py                         |  121 +++++++++++++++++++++++++++++++++++++
 tests/scenario_account_payment.rst |   34 ++++++++++
 view/payment_group_form.xml        |    9 ++
 view/payment_group_list.xml        |    4 +
 5 files changed, 170 insertions(+), 0 deletions(-)

diffs (240 lines):

diff -r 5efe109ffa3f -r 985fe4c47073 CHANGELOG
--- a/CHANGELOG Mon Jun 01 10:08:02 2020 +0100
+++ b/CHANGELOG Mon Jun 15 09:01:47 2020 +0200
@@ -1,3 +1,5 @@
+* Add aggregated information on Payment groups
+
 Version 5.6.0 - 2020-05-04
 * Bug fixes (see mercurial logs for details)
 * Allow scheduling payments on their line maturity dates
diff -r 5efe109ffa3f -r 985fe4c47073 payment.py
--- a/payment.py        Mon Jun 01 10:08:02 2020 +0100
+++ b/payment.py        Mon Jun 15 09:01:47 2020 +0200
@@ -1,12 +1,17 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+from collections import defaultdict
+from decimal import Decimal
 from itertools import groupby
 
+from sql.aggregate import Sum, Count
+
 from trytond.i18n import gettext
 from trytond.model import Workflow, ModelView, ModelSQL, fields
 from trytond.model.exceptions import AccessError
 from trytond.pyson import Eval, If
 from trytond.rpc import RPC
+from trytond.tools import reduce_ids, grouped_slice, cursor_dict
 from trytond.transaction import Transaction
 from trytond.wizard import Wizard, StateView, StateAction, Button
 from trytond.pool import Pool
@@ -65,6 +70,28 @@
     kind = fields.Selection(KINDS, 'Kind', required=True, readonly=True)
     payments = fields.One2Many('account.payment', 'group', 'Payments',
         readonly=True)
+    currency_digits = fields.Function(fields.Integer("Currency Digits"),
+        'on_change_with_currency_digits')
+    payment_count = fields.Function(fields.Integer(
+            "Payment Count",
+            help="The number of payments in the group."),
+        'get_payment_aggregated')
+    payment_amount = fields.Function(fields.Numeric(
+            "Payment Total Amount",
+            digits=(16, Eval('currency_digits', 2)),
+            depends=['currency_digits'],
+            help="The sum of all payment amounts."),
+        'get_payment_aggregated')
+    payment_amount_succeeded = fields.Function(fields.Numeric(
+            "Payment Succeeded",
+            digits=(16, Eval('currency_digits', 2)),
+            depends=['currency_digits'],
+            help="The sum of the amounts of the successful payments."),
+        'get_payment_aggregated')
+    payment_complete = fields.Function(fields.Boolean(
+            "Payment Complete",
+            help="All the payments in the group are complete."),
+        'get_payment_aggregated', searcher='search_complete')
 
     @classmethod
     def __register__(cls, module_name):
@@ -103,6 +130,100 @@
         default.setdefault('payments', None)
         return super(Group, cls).copy(groups, default=default)
 
+    @classmethod
+    def _get_complete_states(cls):
+        return ['succeeded', 'failed']
+
+    @classmethod
+    def get_payment_aggregated(cls, groups, names):
+        pool = Pool()
+        Payment = pool.get('account.payment')
+        cursor = Transaction().connection.cursor()
+
+        payment = Payment.__table__()
+
+        # initialize result and columns
+        result = defaultdict(defaultdict)
+        columns = [
+            payment.group.as_('group_id'),
+            Count(payment.group).as_('payment_count'),
+            Sum(payment.amount).as_('payment_amount'),
+            Sum(payment.amount,
+                filter_=(payment.state == 'succeeded'),
+                ).as_('payment_amount_succeeded'),
+            Count(payment.group,
+                filter_=(~payment.state.in_(cls._get_complete_states())),
+                ).as_('payment_not_complete'),
+            ]
+
+        for sub_ids in grouped_slice(groups):
+            cursor.execute(*payment.select(*columns,
+                where=reduce_ids(payment.group, sub_ids),
+                group_by=payment.group),
+                )
+
+            for row in cursor_dict(cursor):
+                group_id = row['group_id']
+
+                result['payment_count'][group_id] = row['payment_count']
+                result['payment_complete'][group_id] = \
+                    not row['payment_not_complete']
+
+                amount = row['payment_amount']
+                succeeded = row['payment_amount_succeeded']
+
+                if amount is not None:
+                    # SQLite uses float for SUM
+                    if not isinstance(amount, Decimal):
+                        amount = Decimal(str(amount))
+                    amount = cls(group_id).company.currency.round(amount)
+                result['payment_amount'][group_id] = amount
+
+                if succeeded is not None:
+                    # SQLite uses float for SUM
+                    if not isinstance(succeeded, Decimal):
+                        succeeded = Decimal(str(succeeded))
+                    succeeded = cls(group_id).company.currency.round(succeeded)
+                result['payment_amount_succeeded'][group_id] = succeeded
+
+        for key in list(result.keys()):
+            if key not in names:
+                del result[key]
+
+        return result
+
+    @classmethod
+    def search_complete(cls, name, clause):
+        pool = Pool()
+        Payment = pool.get('account.payment')
+        payment = Payment.__table__()
+
+        query_not_completed = payment.select(payment.group,
+            where=~payment.state.in_(cls._get_complete_states()),
+            group_by=payment.group)
+
+        operators = {
+            '=': 'not in',
+            '!=': 'in',
+            }
+        reverse = {
+            '=': 'in',
+            '!=': 'not in',
+            }
+
+        if clause[1] in operators:
+            if clause[2]:
+                return [('id', operators[clause[1]], query_not_completed)]
+            else:
+                return [('id', reverse[clause[1]], query_not_completed)]
+        else:
+            return []
+
+    @fields.depends('journal')
+    def on_change_with_currency_digits(self, name=None):
+        if self.journal:
+            return self.journal.currency.digits
+
 
 _STATES = {
     'readonly': Eval('state') != 'draft',
diff -r 5efe109ffa3f -r 985fe4c47073 tests/scenario_account_payment.rst
--- a/tests/scenario_account_payment.rst        Mon Jun 01 10:08:02 2020 +0100
+++ b/tests/scenario_account_payment.rst        Mon Jun 15 09:01:47 2020 +0200
@@ -91,6 +91,37 @@
     >>> line.payment_amount
     Decimal('30.00')
 
+Check the properties of the payment group::
+
+    >>> group = payment.group
+    >>> group.payment_count
+    1
+    >>> group.payment_amount
+    Decimal('20.00')
+    >>> group.payment_amount_succeeded
+    >>> group.payment_complete
+    False
+
+Success the payment and recheck the payment group::
+
+    >>> payment.click('succeed')
+    >>> payment.state
+    'succeeded'
+    >>> group.reload()
+    >>> group.payment_amount_succeeded
+    Decimal('20.00')
+    >>> group.payment_complete
+    True
+
+Search for the completed payment::
+
+    >>> PaymentGroup = Model.get('account.payment.group')
+    >>> group, = PaymentGroup.find([('payment_complete', '=', 'True')])
+    >>> group.payment_complete
+    True
+    >>> group.id == payment.group.id
+    True
+
 Partially fail to pay the remaining::
 
     >>> pay_line = Wizard('account.move.line.pay', [line])
@@ -109,6 +140,9 @@
     >>> payment.click('fail')
     >>> payment.state
     'failed'
+    >>> payment.group.payment_complete
+    True
+    >>> payment.group.payment_amount_succeeded
     >>> line.reload()
     >>> line.payment_amount
     Decimal('30.00')
diff -r 5efe109ffa3f -r 985fe4c47073 view/payment_group_form.xml
--- a/view/payment_group_form.xml       Mon Jun 01 10:08:02 2020 +0100
+++ b/view/payment_group_form.xml       Mon Jun 15 09:01:47 2020 +0200
@@ -10,4 +10,13 @@
     <field name="journal"/>
     <label name="kind"/>
     <field name="kind"/>
+    <separator id="payments" string="Payments Info" colspan="4"/>
+    <label name="payment_count" str+    <label name="payment_count" 
string="Count"/>
+    <field name="payment_count"/>
+    <label name="payment_amount" string="Total Amount"/>
+    <field name="payment_amount"/>
+    <label name="payment_amount_succeeded" string="Succeeded"/>
+    <field name="payment_amount_succeeded"/>
+    <label name="payment_complete" string="Complete"/>
+    <field name="payment_complete"/>
 </form>
diff -r 5efe109ffa3f -r 985fe4c47073 view/payment_group_list.xml
--- a/view/payment_group_list.xml       Mon Jun 01 10:08:02 2020 +0100
+++ b/view/payment_group_list.xml       Mon Jun 15 09:01:47 2020 +0200
@@ -5,4 +5,8 @@
     <field name="number"/>
     <field name="journal"/>
     <field name="kind"/>
+    <field name="payment_count"/>
+    <field name="payment_amount"/>
+    <field name="payment_amount_succeeded"/>
+    <field name="payment_complete"/>
 </tree>

Reply via email to