From 84d83120c57f6d44ea2bada8e4f5af69f297d72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 7 Apr 2023 18:24:09 +0200 Subject: [PATCH] Add receivable/payable to company --- modules/account/CHANGELOG | 1 + modules/account/company.py | 122 ++++++++++++++++++ modules/account/company.xml | 58 +++++++++ modules/account/tests/test_module.py | 46 ++++--- modules/account/tryton.cfg | 1 + modules/account/view/company_form.xml | 19 +++ modules/account/view/company_list.xml | 9 ++ modules/account_invoice/view/company_form.xml | 13 +- 8 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 modules/account/company.xml create mode 100644 modules/account/view/company_form.xml create mode 100644 modules/account/view/company_list.xml diff --git a/modules/account/CHANGELOG b/modules/account/CHANGELOG index 10acfa05dd..bbba5e2941 100644 --- a/modules/account/CHANGELOG +++ b/modules/account/CHANGELOG @@ -1,3 +1,4 @@ +* Add receivable/payable to company Version 6.8.0 - 2023-05-01 -------------------------- diff --git a/modules/account/company.py b/modules/account/company.py index 054ac8d252..27b090d4cc 100644 --- a/modules/account/company.py +++ b/modules/account/company.py @@ -1,14 +1,136 @@ # 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 decimal import Decimal + +from sql import Literal, Null +from sql.aggregate import Sum +from sql.conditionals import Coalesce + from trytond.i18n import gettext +from trytond.model import fields from trytond.model.exceptions import AccessError +from trytond.modules.currency.fields import Monetary from trytond.pool import Pool, PoolMeta +from trytond.tools import grouped_slice, reduce_ids from trytond.transaction import Transaction class Company(metaclass=PoolMeta): __name__ = 'company.company' + receivable = fields.Function(Monetary( + "Receivable", currency='currency', digits='currency'), + 'get_receivable_payable', searcher='search_receivable_payable') + payable = fields.Function(Monetary( + "Payable", currency='currency', digits='currency'), + 'get_receivable_payable', searcher='search_receivable_payable') + receivable_today = fields.Function(Monetary( + "Receivable Today", currency='currency', digits='currency'), + 'get_receivable_payable', searcher='search_receivable_payable') + payable_today = fields.Function(Monetary( + "Payable Today", currency='currency', digits='currency'), + 'get_receivable_payable', searcher='search_receivable_payable') + + @classmethod + def get_receivable_payable(cls, companies, names): + amounts = {} + pool = Pool() + MoveLine = pool.get('account.move.line') + Account = pool.get('account.account') + AccountType = pool.get('account.account.type') + Date = pool.get('ir.date') + cursor = Transaction().connection.cursor() + + line = MoveLine.__table__() + account = Account.__table__() + account_type = AccountType.__table__() + + for name in names: + assert name in { + 'receivable', 'payable', 'receivable_today', 'payable_today'} + amounts[name] = dict.fromkeys(map(int, companies), Decimal(0)) + + exp = { + c.id: Decimal(str(10.0 ** -c.currency.digits)) for c in companies} + today = Date.today() + + amount = Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)) + for name in names: + if name.endswith('_today'): + code = name[:-len('_today')] + today_where = ( + (line.maturity_date <= today) + | (line.maturity_date == Null)) + else: + code = name + today_where = Literal(True) + for sub_companies in grouped_slice(companies): + sub_ids = [p.id for p in sub_companies] + company_where = reduce_ids(account.company, sub_ids) + cursor.execute(*line + .join(account, + condition=account.id == line.account) + .join(account_type, + condition=account.type == account_type.id) + .select(account.company, amount, + where=(getattr(account_type, code) + & (line.reconciliation == Null) + & company_where + & today_where), + group_by=account.company)) + for company_id, value in cursor: + # SQLite uses float for SUM + if not isinstance(value, Decimal): + value = Decimal(str(value)) + amounts[name][company_id] = value.quantize(exp[company_id]) + return amounts + + @classmethod + def search_receivable_payable(cls, name, clause): + pool = Pool() + MoveLine = pool.get('account.move.line') + Account = pool.get('account.account') + AccountType = pool.get('account.account.type') + Date = pool.get('ir.date') + + line = MoveLine.__table__() + account = Account.__table__() + account_type = AccountType.__table__() + + assert name in { + 'receivable', 'payable', 'receivable_today', 'payable_today'} + _, operator, value = clause + + today = Date.today() + + if name.endswith('_today'): + code = name[:-len('_today')] + today_query = ((line.maturity_date <= today) + | (line.maturity_date == Null)) + else: + code = name + today_query = Literal(True) + + Operator = fields.SQL_OPERATORS[operator] + + # Need to cast numeric for sqlite + cast_ = MoveLine.debit.sql_cast + amount = cast_(Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0))) + if operator in {'in', 'not in'}: + value = [cast_(Literal(Decimal(v or 0))) for v in value] + else: + value = cast_(Literal(Decimal(value or 0))) + query = (line + .join(account, condition=account.id == line.account) + .join(account_type, condition=account.type == account_type.id) + .select(account.company, + where=(getattr(account_type, code) + & (line.reconciliation == Null) + & today_query), + group_by=account.company, + having=Operator(amount, value))) + return [('id', 'in', query)] + @classmethod def write(cls, *args): pool = Pool() diff --git a/modules/account/company.xml b/modules/account/company.xml new file mode 100644 index 0000000000..cd99ed7157 --- /dev/null +++ b/modules/account/company.xml @@ -0,0 +1,58 @@ + + + + + + company.company + + company_list + + + + company.company + + company_form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/account/tests/test_module.py b/modules/account/tests/test_module.py index 5ce6b9da79..f687e6d3d4 100644 --- a/modules/account/tests/test_module.py +++ b/modules/account/tests/test_module.py @@ -1330,6 +1330,7 @@ class AccountTestCase( def test_receivable_payable(self): 'Test party receivable payable' pool = Pool() + Company = pool.get('company.company') Party = pool.get('party.party') FiscalYear = pool.get('account.fiscalyear') Journal = pool.get('account.journal') @@ -1399,27 +1400,34 @@ class AccountTestCase( def check_fields(): party_test = Party(party.id) + company_test = Company(company.id) - for field, value in [('receivable', Decimal('300')), - ('receivable_today', Decimal('100')), - ('payable', Decimal('90')), - ('payable_today', Decimal('30')), + for record, Model in [ + (party_test, Party), + (company_test, Company), ]: - msg = 'field: %s, value: %s' % (field, value) - self.assertEqual( - getattr(party_test, field), value, msg=msg) - self.assertEqual( - Party.search([(field, '=', value)]), - [party_test], msg=msg) - self.assertEqual( - Party.search([(field, 'in', [value])]), - [party_test], msg=msg) - self.assertEqual( - Party.search([(field, '!=', value)]), - [], msg=msg) - self.assertEqual( - Party.search([(field, 'not in', [value])]), - [], msg=msg) + for field, value in [ + ('receivable', Decimal('300')), + ('receivable_today', Decimal('100')), + ('payable', Decimal('90')), + ('payable_today', Decimal('30')), + ]: + with self.subTest( + record=record, field=field, value=value): + self.assertEqual( + getattr(record, field), value) + self.assertEqual( + Model.search([(field, '=', value)]), + [record]) + self.assertEqual( + Model.search([(field, 'in', [value])]), + [record]) + self.assertEqual( + Model.search([(field, '!=', value)]), + []) + self.assertEqual( + Model.search([(field, 'not in', [value])]), + []) check_fields() close_fiscalyear(fiscalyear) diff --git a/modules/account/tryton.cfg b/modules/account/tryton.cfg index 44be940fce..fd43eeb8eb 100644 --- a/modules/account/tryton.cfg +++ b/modules/account/tryton.cfg @@ -9,6 +9,7 @@ depends: xml: account.xml configuration.xml + company.xml fiscalyear.xml period.xml journal.xml diff --git a/modules/account/view/company_form.xml b/modules/account/view/company_form.xml new file mode 100644 index 0000000000..086f7ff6c2 --- /dev/null +++ b/modules/account/view/company_form.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/modules/account/view/company_list.xml b/modules/account/view/company_list.xml new file mode 100644 index 0000000000..3fdf5b18cd --- /dev/null +++ b/modules/account/view/company_list.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/modules/account_invoice/view/company_form.xml b/modules/account_invoice/view/company_form.xml index 2afb47f355..3efca6c3d4 100644 --- a/modules/account_invoice/view/company_form.xml +++ b/modules/account_invoice/view/company_form.xml @@ -2,12 +2,11 @@ - - - + + +