From 030963812fcba8d1431a4c9672a22f2cfca09b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Fri, 4 Feb 2022 15:39:41 +0100 Subject: [PATCH] Show debit / credit columns in ledgers when they contain lines issue11072 review372881002 --- CHANGELOG | 2 + account.py | 129 +++++++++++++++++++++++---- account.xml | 1 + fiscalyear.py | 1 + tests/scenario_reports.rst | 110 +++++++++++++++++++++-- view/general_ledger_account_list.xml | 1 + 6 files changed, 221 insertions(+), 23 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index abbd3b59f8..81a4ba59f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Show debit / credit columns in ledgers when they contain lines +* Count number of lines in Account and General Ledger * Handle errors in move template expression * Rename split lines into reschedule lines * Add optional credit type on account diff --git a/account.py b/account.py index b8b800a975..bd5c7a8756 100644 --- a/account.py +++ b/account.py @@ -9,9 +9,10 @@ from itertools import zip_longest from dateutil.relativedelta import relativedelta from sql import Column, Literal, Null, Window -from sql.aggregate import Max, Min, Sum +from sql.aggregate import Count, Max, Min, Sum from sql.conditionals import Case, Coalesce +from trytond import backend from trytond.i18n import gettext from trytond.model import ( Check, ModelSQL, ModelView, Unique, fields, sequence_ordered, tree) @@ -876,10 +877,18 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView): "Balance", currency='currency', digits='currency'), 'get_balance') credit = fields.Function(Monetary( - "Credit", currency='currency', digits='currency'), + "Credit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_credit_debit') debit = fields.Function(Monetary( - "Debit", currency='currency', digits='currency'), + "Debit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_credit_debit') amount_second_currency = fields.Function(Monetary( "Amount Second Currency", @@ -889,6 +898,8 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView): }, depends=['second_currency']), 'get_credit_debit') + line_count = fields.Function( + fields.Integer("Line Count"), 'get_credit_debit') note = fields.Text('Note') deferrals = fields.One2Many( 'account.account.deferral', 'account', "Deferrals", readonly=True, @@ -1026,16 +1037,21 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView): result = {} ids = [a.id for a in accounts] for name in names: - if name not in {'credit', 'debit', 'amount_second_currency'}: + if name not in { + 'credit', 'debit', 'amount_second_currency', 'line_count'}: raise ValueError('Unknown name: %s' % name) - result[name] = dict((i, Decimal(0)) for i in ids) + col_type = int if name == 'line_count' else Decimal + result[name] = defaultdict(col_type) table = cls.__table__() line = MoveLine.__table__() line_query, fiscalyear_ids = MoveLine.query_get(line) columns = [table.id] for name in names: - columns.append(Sum(Coalesce(Column(line, name), 0))) + if name == 'line_count': + columns.append(Count(Literal('*'))) + else: + columns.append(Sum(Coalesce(Column(line, name), 0))) for sub_ids in grouped_slice(ids): red_sql = reduce_ids(table.id, sub_ids) cursor.execute(*table.join(line, 'LEFT', @@ -1047,12 +1063,15 @@ class Account(AccountMixin(), ActivePeriodMixin, tree(), ModelSQL, ModelView): account_id = row[0] for i, name in enumerate(names, 1): # SQLite uses float for SUM - if not isinstance(row[i], Decimal): + if (name != 'line_count' + and not isinstance(row[i], Decimal)): result[name][account_id] = Decimal(str(row[i])) else: result[name][account_id] = row[i] for account in accounts: for name in names: + if name == 'line_count': + continue if (name == 'amount_second_currency' and account.second_currency): currency = account.second_currency @@ -1397,6 +1416,8 @@ class AccountParty(ActivePeriodMixin, ModelSQL): }, depends=['second_currency']), 'get_credit_debit') + line_count = fields.Function( + fields.Integer("Line Count"), 'get_credit_debit') second_currency = fields.Many2One( 'currency.currency', "Secondary Currency") @@ -1496,11 +1517,12 @@ class AccountParty(ActivePeriodMixin, ModelSQL): cursor = Transaction().connection.cursor() result = {} - ids = [a.id for a in records] for name in names: - if name not in {'credit', 'debit', 'amount_second_currency'}: + if name not in { + 'credit', 'debit', 'amount_second_currency', 'line_count'}: raise ValueError('Unknown name: %s' % name) - result[name] = dict((i, Decimal(0)) for i in ids) + column_type = int if name == 'line_count' else Decimal + result[name] = defaultdict(column_type) account_ids = {a.account.id for a in records} party_ids = {a.party.id for a in records} @@ -1510,7 +1532,10 @@ class AccountParty(ActivePeriodMixin, ModelSQL): line_query, fiscalyear_ids = MoveLine.query_get(line) columns = [table.id, line.party] for name in names: - columns.append(Sum(Coalesce(Column(line, name), 0))) + if name == 'line_count': + columns.append(Count(Literal('*'))) + else: + columns.append(Sum(Coalesce(Column(line, name), 0))) for sub_account_ids in grouped_slice(account_ids): account_sql = reduce_ids(table.id, sub_account_ids) for sub_party_ids in grouped_slice(party_ids): @@ -1529,12 +1554,15 @@ class AccountParty(ActivePeriodMixin, ModelSQL): continue for i, name in enumerate(names, 2): # SQLite uses float for SUM - if not isinstance(row[i], Decimal): + if (name != 'line_count' + and not isinstance(row[i], Decimal)): result[name][id_] = Decimal(str(row[i])) else: result[name][id_] = row[i] for record in records: for name in names: + if name == 'line_count': + continue if name == 'amount_second_currency' and record.second_currency: currency = record.second_currency else: @@ -1586,6 +1614,7 @@ class AccountDeferral(ModelSQL, ModelView): 'invisible': ~Eval('second_currency'), }, depends=['second_currency']) + line_count = fields.Integer("Line Count", required=True) second_currency = fields.Function(fields.Many2One( 'currency.currency', "Second Currency"), 'get_second_currency') @@ -1598,10 +1627,47 @@ class AccountDeferral(ModelSQL, ModelView): 'account.msg_deferral_unique'), ] + @classmethod + def __register__(cls, module_name): + pool = Pool() + MoveLine = pool.get('account.move.line') + Move = pool.get('account.move') + Period = pool.get('account.period') + + cursor = Transaction().connection.cursor() + deferral = cls.__table__() + move_line = MoveLine.__table__() + move = Move.__table__() + period = Period.__table__() + + exist = backend.TableHandler.table_exist(cls._table) + table_h = cls.__table_handler__(module_name) + created_line_count = exist and not table_h.column_exist('line_count') + + super().__register__(module_name) + + # Migration from 6.2: add line_count on deferrals + if created_line_count: + counting_query = (move_line + .join(move, condition=move_line.move == move.id) + .join(period, condition=move.period == period.id) + .select( + move_line.account, period.fiscalyear, Count(Literal('*')), + group_by=[move_line.account, period.fiscalyear])) + cursor.execute(*deferral.update( + [deferral.line_count], [counting_query.line_count], + from_=[counting_query], + where=((deferral.account == counting_query.account) + & (deferral.fiscalyear == counting_query.fiscalyear)))) + @classmethod def default_amount_second_currency(cls): return Decimal(0) + @classmethod + def default_line_count(cls): + return 0 + @classmethod def get_balance(cls, deferrals, name): pool = Pool() @@ -1715,23 +1781,50 @@ class _GeneralLedgerAccount(ActivePeriodMixin, ModelSQL, ModelView): account = fields.Many2One('account.account', "Account") company = fields.Many2One('company.company', 'Company') start_debit = fields.Function(Monetary( - "Start Debit", currency='currency', digits='currency'), + "Start Debit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_account', searcher='search_account') debit = fields.Function(Monetary( - "Debit", currency='currency', digits='currency'), + "Debit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_debit_credit', searcher='search_debit_credit') end_debit = fields.Function(Monetary( - "End Debit", currency='currency', digits='currency'), + "End Debit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_account', searcher='search_account') start_credit = fields.Function(Monetary( - "Start Credit", currency='currency', digits='currency'), + "Start Credit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_account', searcher='search_account') credit = fields.Function(Monetary( - "Credit", currency='currency', digits='currency'), + "Credit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_debit_credit', searcher='search_debit_credit') end_credit = fields.Function(Monetary( - "End Credit", currency='currency', digits='currency'), + "End Credit", currency='currency', digits='currency', + states={ + 'invisible': ~Eval('line_count', -1), + }, + depends=['line_count']), 'get_account', searcher='search_account') + line_count = fields.Function( + fields.Integer("Line Count"), + 'get_debit_credit', searcher='search_debit_credit') start_balance = fields.Function(Monetary( "Start Balance", currency='currency', digits='currency'), 'get_account', searcher='search_account') diff --git a/account.xml b/account.xml index 778f3c67e3..af1ce91e7e 100644 --- a/account.xml +++ b/account.xml @@ -550,6 +550,7 @@ this repository contains the full copyright notices and license terms. --> General Ledger - Accounts account.general_ledger.account account.general_ledger.account.context + diff --git a/fiscalyear.py b/fiscalyear.py index 89c34799d8..1ffc9dcff4 100644 --- a/fiscalyear.py +++ b/fiscalyear.py @@ -257,6 +257,7 @@ class FiscalYear(Workflow, ModelSQL, ModelView): deferral.fiscalyear = self deferral.debit = account.debit deferral.credit = account.credit + deferral.line_count = account.line_count deferral.amount_second_currency = account.amount_second_currency return deferral diff --git a/tests/scenario_reports.rst b/tests/scenario_reports.rst index 98baef7118..217d421049 100644 --- a/tests/scenario_reports.rst +++ b/tests/scenario_reports.rst @@ -40,6 +40,12 @@ Create chart of accounts:: >>> expense = accounts['expense'] >>> cash = accounts['cash'] +Create a child account:: + + >>> _ = revenue.childs.new() + >>> revenue.save() + >>> child_revenue, = revenue.childs + Create parties:: >>> Party = Model.get('party.party') @@ -61,7 +67,7 @@ Create a moves:: >>> move.journal = journal_revenue >>> move.date = period_3.start_date >>> line = move.lines.new() - >>> line.account = revenue + >>> line.account = child_revenue >>> line.credit = Decimal(10) >>> line = move.lines.new() >>> line.account = receivable @@ -102,6 +108,9 @@ Print some reports:: ... 'fiscalyear': fiscalyear.id, ... } >>> with config.set_context(context): + ... gl_child_revenue, = GeneralLedgerAccount.find([ + ... ('account', '=', child_revenue.id), + ... ]) ... gl_revenue, = GeneralLedgerAccount.find([ ... ('account', '=', revenue.id), ... ]) @@ -109,14 +118,26 @@ Print some reports:: ... ('account', '=', receivable.id), ... ('party', '=', party.id), ... ]) + >>> gl_child_revenue.start_balance + Decimal('0.00') + >>> gl_child_revenue.credit + Decimal('10.00') + >>> gl_child_revenue.debit + Decimal('0.00') + >>> gl_child_revenue.end_balance + Decimal('-10.00') + >>> gl_child_revenue.line_count + 1 >>> gl_revenue.start_balance Decimal('0.00') >>> gl_revenue.credit - Decimal('10.00') + Decimal('0.00') >>> gl_revenue.debit Decimal('0.00') >>> gl_revenue.end_balance Decimal('-10.00') + >>> gl_revenue.line_count + 0 >>> glp_receivable.start_balance Decimal('0.00') >>> glp_receivable.credit @@ -125,6 +146,8 @@ Print some reports:: Decimal('10.00') >>> glp_receivable.end_balance Decimal('0.00') + >>> glp_receivable.line_count + 2 >>> context = { ... 'company': company.id, @@ -133,6 +156,9 @@ Print some reports:: ... 'to_date': period_3.end_date, ... } >>> with config.set_context(context): + ... gl_child_revenue, = GeneralLedgerAccount.find([ + ... ('account', '=', child_revenue.id), + ... ]) ... gl_revenue, = GeneralLedgerAccount.find([ ... ('account', '=', revenue.id), ... ]) @@ -140,14 +166,26 @@ Print some reports:: ... ('account', '=', receivable.id), ... ('party', '=', party.id), ... ]) + >>> gl_child_revenue.start_balance + Decimal('0.00') + >>> gl_child_revenue.credit + Decimal('10.00') + >>> gl_child_revenue.debit + Decimal('0.00') + >>> gl_child_revenue.end_balance + Decimal('-10.00') + >>> gl_child_revenue.line_count + 1 >>> gl_revenue.start_balance Decimal('0.00') >>> gl_revenue.credit - Decimal('10.00') + Decimal('0.00') >>> gl_revenue.debit Decimal('0.00') >>> gl_revenue.end_balance Decimal('-10.00') + >>> gl_revenue.line_count + 0 >>> glp_receivable.start_balance Decimal('0.00') >>> glp_receivable.credit @@ -156,6 +194,8 @@ Print some reports:: Decimal('10.00') >>> glp_receivable.end_balance Decimal('10.00') + >>> glp_receivable.line_count + 1 >>> context = { ... 'company': company.id, @@ -163,17 +203,32 @@ Print some reports:: ... 'start_period': period_3.id, ... } >>> with config.set_context(context): + ... gl_child_revenue, = GeneralLedgerAccount.find([ + ... ('account', '=', child_revenue.id), + ... ]) ... gl_revenue, = GeneralLedgerAccount.find([ ... ('account', '=', revenue.id), ... ]) + >>> gl_child_revenue.start_balance + Decimal('0.00') + >>> gl_child_revenue.credit + Decimal('10.00') + >>> gl_child_revenue.debit + Decimal('0.00') + >>> gl_child_revenue.end_balance + Decimal('-10.00') + >>> gl_child_revenue.line_count + 1 >>> gl_revenue.start_balance Decimal('0.00') >>> gl_revenue.credit - Decimal('10.00') + Decimal('0.00') >>> gl_revenue.debit Decimal('0.00') >>> gl_revenue.end_balance Decimal('-10.00') + >>> gl_revenue.line_count + 0 >>> context = { ... 'company': company.id, @@ -181,9 +236,22 @@ Print some reports:: ... 'start_period': period_5.id, ... } >>> with config.set_context(context): + ... gl_child_revenue, = GeneralLedgerAccount.find([ + ... ('account', '=', child_revenue.id), + ... ]) ... gl_revenue, = GeneralLedgerAccount.find([ ... ('account', '=', revenue.id), ... ]) + >>> gl_child_revenue.start_balance + Decimal('-10.00') + >>> gl_child_revenue.credit + Decimal('0.00') + >>> gl_child_revenue.debit + Decimal('0.00') + >>> gl_child_revenue.end_balance + Decimal('-10.00') + >>> gl_child_revenue.line_count + 0 >>> gl_revenue.start_balance Decimal('-10.00') >>> gl_revenue.credit @@ -192,6 +260,8 @@ Print some reports:: Decimal('0.00') >>> gl_revenue.end_balance Decimal('-10.00') + >>> gl_revenue.line_count + 0 >>> context = { ... 'company': company.id, @@ -199,17 +269,32 @@ Print some reports:: ... 'from_date': period_3.start_date, ... } >>> with config.set_context(context): + ... gl_child_revenue, = GeneralLedgerAccount.find([ + ... ('account', '=', child_revenue.id), + ... ]) ... gl_revenue, = GeneralLedgerAccount.find([ ... ('account', '=', revenue.id), ... ]) + >>> gl_child_revenue.start_balance + Decimal('0.00') + >>> gl_child_revenue.credit + Decimal('10.00') + >>> gl_child_revenue.debit + Decimal('0.00') + >>> gl_child_revenue.end_balance + Decimal('-10.00') + >>> gl_child_revenue.line_count + 1 >>> gl_revenue.start_balance Decimal('0.00') >>> gl_revenue.credit - Decimal('10.00') + Decimal('0.00') >>> gl_revenue.debit Decimal('0.00') >>> gl_revenue.end_balance Decimal('-10.00') + >>> gl_revenue.line_count + 0 >>> context = { ... 'company': company.id, @@ -217,9 +302,22 @@ Print some reports:: ... 'from_date': period_5.start_date, ... } >>> with config.set_context(context): + ... gl_child_revenue, = GeneralLedgerAccount.find([ + ... ('account', '=', child_revenue.id), + ... ]) ... gl_revenue, = GeneralLedgerAccount.find([ ... ('account', '=', revenue.id), ... ]) + >>> gl_child_revenue.start_balance + Decimal('-10.00') + >>> gl_child_revenue.credit + Decimal('0.00') + >>> gl_child_revenue.debit + Decimal('0.00') + >>> gl_child_revenue.end_balance + Decimal('-10.00') + >>> gl_child_revenue.line_count + 0 >>> gl_revenue.start_balance Decimal('-10.00') >>> gl_revenue.credit @@ -228,6 +326,8 @@ Print some reports:: Decimal('0.00') >>> gl_revenue.end_balance Decimal('-10.00') + >>> gl_revenue.line_count + 0 >>> trial_balance = Report('account.trial_balance', context={ ... 'company': company.id, diff --git a/view/general_ledger_account_list.xml b/view/general_ledger_account_list.xml index 4b4b18904a..79ce6b37ea 100644 --- a/view/general_ledger_account_list.xml +++ b/view/general_ledger_account_list.xml @@ -4,6 +4,7 @@ this repository contains the full copyright notices and license terms. --> +