from odoo import models, fields from itertools import chain from dateutil.relativedelta import relativedelta class AgedPartnerBalanceCustomHandler(models.AbstractModel): _inherit = "account.aged.partner.balance.report.handler" def _report_custom_engine_aged_receivable_na( self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None ): return self._aged_partner_report_custom_engine_common_na( options, 'asset_receivable', current_groupby, next_groupby, offset=offset, limit=limit ) def _report_custom_engine_aged_payable_na( self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None ): return self._aged_partner_report_custom_engine_common_na( options, 'liability_payable', current_groupby, next_groupby, offset=offset, limit=limit ) def _aged_partner_report_custom_engine_common_na(self, options, internal_type, current_groupby, next_groupby, offset=0, limit=None): report = self.env['account.report'].browse(options['report_id']) report._check_groupby_fields((next_groupby.split(',') if next_groupby else []) + ([current_groupby] if current_groupby else [])) def plus_days(date_obj, days): return fields.Date.to_string(date_obj + relativedelta(days=days)) def minus_days(date_obj, days): return fields.Date.to_string(date_obj - relativedelta(days=days)) date_to = fields.Date.from_string(options['date']['date_to']) periods = [ (False, minus_days(date_to, 1)), (date_to, plus_days(date_to, 29)), (plus_days(date_to, 30), plus_days(date_to, 59)), (plus_days(date_to, 60), plus_days(date_to, 89)), (plus_days(date_to, 90), plus_days(date_to, 119)), (plus_days(date_to, 120), False), ] def build_result_dict(report, query_res_lines): rslt = {f'period{i}': 0 for i in range(len(periods))} for query_res in query_res_lines: for i in range(len(periods)): period_key = f'period{i}' rslt[period_key] += query_res[period_key] if current_groupby == 'id': query_res = query_res_lines[0] # We're grouping by id, so there is only 1 element in query_res_lines anyway currency = self.env['res.currency'].browse(query_res['currency_id'][0]) if len(query_res['currency_id']) == 1 else None expected_date = len(query_res['expected_date']) == 1 and query_res['expected_date'][0] or len(query_res['due_date']) == 1 and query_res['due_date'][0] rslt.update({ 'invoice_date': query_res['invoice_date'][0] if len(query_res['invoice_date']) == 1 else None, 'due_date': query_res['due_date'][0] if len(query_res['due_date']) == 1 else None, 'amount_currency': query_res['amount_currency'], 'currency_id': query_res['currency_id'][0] if len(query_res['currency_id']) == 1 else None, 'currency': currency.display_name if currency else None, 'account_name': query_res['account_name'][0] if len(query_res['account_name']) == 1 else None, 'expected_date': expected_date or None, 'total': None, 'has_sublines': query_res['aml_count'] > 0, # Needed by the custom_unfold_all_batch_data_generator, to speed-up unfold_all 'partner_id': query_res['partner_id'][0] if query_res['partner_id'] else None, }) else: rslt.update({ 'invoice_date': None, 'due_date': None, 'amount_currency': None, 'currency_id': None, 'currency': None, 'account_name': None, 'expected_date': None, 'total': sum(rslt[f'period{i}'] for i in range(len(periods))), 'has_sublines': False, }) return rslt # Build period table period_table_format = ('(VALUES %s)' % ','.join("(%s, %s, %s)" for period in periods)) params = list(chain.from_iterable( (period[0] or None, period[1] or None, i) for i, period in enumerate(periods) )) period_table = self.env.cr.mogrify(period_table_format, params).decode(self.env.cr.connection.encoding) # Build query tables, where_clause, where_params = report._query_get(options, 'strict_range', domain=[('account_id.account_type', '=', internal_type)]) currency_table = report._get_query_currency_table(options) always_present_groupby = "period_table.period_index, currency_table.rate, currency_table.precision" if current_groupby: select_from_groupby = f"account_move_line.{current_groupby} AS grouping_key," groupby_clause = f"account_move_line.{current_groupby}, {always_present_groupby}" else: select_from_groupby = '' groupby_clause = always_present_groupby select_period_query = ','.join( f""" CASE WHEN period_table.period_index = {i} THEN %s * ( SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) - COALESCE(SUM(ROUND(part_debit.amount * currency_table.rate, currency_table.precision)), 0) + COALESCE(SUM(ROUND(part_credit.amount * currency_table.rate, currency_table.precision)), 0) ) ELSE 0 END AS period{i} """ for i in range(len(periods)) ) tail_query, tail_params = report._get_engine_query_tail(offset, limit) query = f""" WITH period_table(date_start, date_stop, period_index) AS ({period_table}) SELECT {select_from_groupby} %s * ( SUM(account_move_line.amount_currency) - COALESCE(SUM(part_debit.debit_amount_currency), 0) + COALESCE(SUM(part_credit.credit_amount_currency), 0) ) AS amount_currency, ARRAY_AGG(DISTINCT account_move_line.partner_id) AS partner_id, ARRAY_AGG(account_move_line.payment_id) AS payment_id, ARRAY_AGG(DISTINCT move.invoice_date) AS invoice_date, ARRAY_AGG(DISTINCT COALESCE(account_move_line.date_maturity, account_move_line.date)) AS report_date, ARRAY_AGG(DISTINCT account_move_line.expected_pay_date) AS expected_date, ARRAY_AGG(DISTINCT account.code) AS account_name, ARRAY_AGG(DISTINCT COALESCE(account_move_line.date_maturity, account_move_line.date)) AS due_date, ARRAY_AGG(DISTINCT account_move_line.currency_id) AS currency_id, COUNT(account_move_line.id) AS aml_count, ARRAY_AGG(account.code) AS account_code, {select_period_query} FROM {tables} JOIN account_journal journal ON journal.id = account_move_line.journal_id JOIN account_account account ON account.id = account_move_line.account_id JOIN account_move move ON move.id = account_move_line.move_id JOIN {currency_table} ON currency_table.company_id = account_move_line.company_id LEFT JOIN LATERAL ( SELECT SUM(part.amount) AS amount, SUM(part.debit_amount_currency) AS debit_amount_currency, part.debit_move_id FROM account_partial_reconcile part WHERE part.max_date <= %s AND part.debit_move_id = account_move_line.id GROUP BY part.debit_move_id ) part_debit ON TRUE LEFT JOIN LATERAL ( SELECT SUM(part.amount) AS amount, SUM(part.credit_amount_currency) AS credit_amount_currency, part.credit_move_id FROM account_partial_reconcile part WHERE part.max_date <= %s AND part.credit_move_id = account_move_line.id GROUP BY part.credit_move_id ) part_credit ON TRUE JOIN period_table ON ( period_table.date_start IS NULL OR COALESCE(account_move_line.date_maturity, account_move_line.date) >= DATE(period_table.date_start) ) AND ( period_table.date_stop IS NULL OR COALESCE(account_move_line.date_maturity, account_move_line.date) <= DATE(period_table.date_stop) ) WHERE {where_clause} GROUP BY {groupby_clause} HAVING ( SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) - COALESCE(SUM(ROUND(part_debit.amount * currency_table.rate, currency_table.precision)), 0) ) != 0 OR ( SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) - COALESCE(SUM(ROUND(part_credit.amount * currency_table.rate, currency_table.precision)), 0) ) != 0 {tail_query} """ multiplicator = -1 if internal_type == 'liability_payable' else 1 params = [ multiplicator, *([multiplicator] * len(periods)), date_to, date_to, *where_params, *tail_params, ] self._cr.execute(query, params) query_res_lines = self._cr.dictfetchall() if not current_groupby: return build_result_dict(report, query_res_lines) else: rslt = [] all_res_per_grouping_key = {} for query_res in query_res_lines: grouping_key = query_res['grouping_key'] all_res_per_grouping_key.setdefault(grouping_key, []).append(query_res) for grouping_key, query_res_lines in all_res_per_grouping_key.items(): rslt.append((grouping_key, build_result_dict(report, query_res_lines))) return rslt