forked from Mapan/odoo17e
731 lines
37 KiB
Python
731 lines
37 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import json
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.tools.misc import format_date
|
|
from odoo.tools import get_lang
|
|
from odoo.exceptions import UserError
|
|
|
|
from datetime import timedelta
|
|
from collections import defaultdict
|
|
|
|
|
|
class GeneralLedgerCustomHandler(models.AbstractModel):
|
|
_name = 'account.general.ledger.report.handler'
|
|
_inherit = 'account.report.custom.handler'
|
|
_description = 'General Ledger Custom Handler'
|
|
|
|
def _get_custom_display_config(self):
|
|
return {
|
|
'templates': {
|
|
'AccountReportLineName': 'account_reports.GeneralLedgerLineName',
|
|
},
|
|
}
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options=None):
|
|
# Remove multi-currency columns if needed
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
if self.user_has_groups('base.group_multi_currency'):
|
|
options['multi_currency'] = True
|
|
else:
|
|
options['columns'] = [
|
|
column for column in options['columns']
|
|
if column['expression_label'] != 'amount_currency'
|
|
]
|
|
|
|
# Automatically unfold the report when printing it, unless some specific lines have been unfolded
|
|
options['unfold_all'] = (options['export_mode'] == 'print' and not options.get('unfolded_lines')) or options['unfold_all']
|
|
|
|
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
|
|
lines = []
|
|
date_from = fields.Date.from_string(options['date']['date_from'])
|
|
company_currency = self.env.company.currency_id
|
|
|
|
totals_by_column_group = defaultdict(lambda: {'debit': 0, 'credit': 0, 'balance': 0})
|
|
for account, column_group_results in self._query_values(report, options):
|
|
eval_dict = {}
|
|
has_lines = False
|
|
for column_group_key, results in column_group_results.items():
|
|
account_sum = results.get('sum', {})
|
|
account_un_earn = results.get('unaffected_earnings', {})
|
|
|
|
account_debit = account_sum.get('debit', 0.0) + account_un_earn.get('debit', 0.0)
|
|
account_credit = account_sum.get('credit', 0.0) + account_un_earn.get('credit', 0.0)
|
|
account_balance = account_sum.get('balance', 0.0) + account_un_earn.get('balance', 0.0)
|
|
|
|
eval_dict[column_group_key] = {
|
|
'amount_currency': account_sum.get('amount_currency', 0.0) + account_un_earn.get('amount_currency', 0.0),
|
|
'debit': account_debit,
|
|
'credit': account_credit,
|
|
'balance': account_balance,
|
|
}
|
|
|
|
max_date = account_sum.get('max_date')
|
|
has_lines = has_lines or (max_date and max_date >= date_from)
|
|
|
|
totals_by_column_group[column_group_key]['debit'] += account_debit
|
|
totals_by_column_group[column_group_key]['credit'] += account_credit
|
|
totals_by_column_group[column_group_key]['balance'] += account_balance
|
|
|
|
lines.append(self._get_account_title_line(report, options, account, has_lines, eval_dict))
|
|
|
|
# Report total line.
|
|
for totals in totals_by_column_group.values():
|
|
totals['balance'] = company_currency.round(totals['balance'])
|
|
|
|
# Tax Declaration lines.
|
|
journal_options = report._get_options_journals(options)
|
|
if len(options['column_groups']) == 1 and len(journal_options) == 1 and journal_options[0]['type'] in ('sale', 'purchase'):
|
|
lines += self._tax_declaration_lines(report, options, journal_options[0]['type'])
|
|
|
|
# Total line
|
|
lines.append(self._get_total_line(report, options, totals_by_column_group))
|
|
|
|
return [(0, line) for line in lines]
|
|
|
|
def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function):
|
|
account_ids_to_expand = []
|
|
for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_general_ledger', []):
|
|
model, model_id = report._get_model_info_from_id(line_dict['id'])
|
|
if model == 'account.account':
|
|
account_ids_to_expand.append(model_id)
|
|
|
|
limit_to_load = report.load_more_limit if report.load_more_limit and not options.get('export_mode') else None
|
|
has_more_per_account_id = {}
|
|
|
|
unlimited_aml_results_per_account_id = self._get_aml_values(report, options, account_ids_to_expand)[0]
|
|
if limit_to_load:
|
|
# Apply the load_more_limit.
|
|
# load_more_limit cannot be passed to the call to _get_aml_values, otherwise it won't be applied per account but on the whole result.
|
|
# We gain perf from batching, but load every result ; then we need to filter them.
|
|
|
|
aml_results_per_account_id = {}
|
|
for account_id, account_aml_results in unlimited_aml_results_per_account_id.items():
|
|
account_values = {}
|
|
for key, value in account_aml_results.items():
|
|
if len(account_values) == limit_to_load:
|
|
has_more_per_account_id[account_id] = True
|
|
break
|
|
account_values[key] = value
|
|
aml_results_per_account_id[account_id] = account_values
|
|
else:
|
|
aml_results_per_account_id = unlimited_aml_results_per_account_id
|
|
|
|
return {
|
|
'initial_balances': self._get_initial_balance_values(report, account_ids_to_expand, options),
|
|
'aml_results': aml_results_per_account_id,
|
|
'has_more': has_more_per_account_id,
|
|
}
|
|
|
|
def _tax_declaration_lines(self, report, options, tax_type):
|
|
labels_replacement = {
|
|
'debit': _("Base Amount"),
|
|
'credit': _("Tax Amount"),
|
|
}
|
|
|
|
rslt = [{
|
|
'id': report._get_generic_line_id(None, None, markup='tax_decl_header_1'),
|
|
'name': _('Tax Declaration'),
|
|
'columns': [{} for column in options['columns']],
|
|
'level': 1,
|
|
'unfoldable': False,
|
|
'unfolded': False,
|
|
}, {
|
|
'id': report._get_generic_line_id(None, None, markup='tax_decl_header_2'),
|
|
'name': _('Name'),
|
|
'columns': [{'name': labels_replacement.get(col['expression_label'], '')} for col in options['columns']],
|
|
'level': 3,
|
|
'unfoldable': False,
|
|
'unfolded': False,
|
|
}]
|
|
|
|
# Call the generic tax report
|
|
generic_tax_report = self.env.ref('account.generic_tax_report')
|
|
tax_report_options = generic_tax_report.get_options({**options, 'selected_variant_id': generic_tax_report.id, 'forced_domain': [('tax_line_id.type_tax_use', '=', tax_type)]})
|
|
tax_report_lines = generic_tax_report._get_lines(tax_report_options)
|
|
tax_type_parent_line_id = generic_tax_report._get_generic_line_id(None, None, markup=tax_type)
|
|
|
|
for tax_report_line in tax_report_lines:
|
|
if tax_report_line.get('parent_id') == tax_type_parent_line_id:
|
|
original_columns = tax_report_line['columns']
|
|
row_column_map = {
|
|
'debit': original_columns[0],
|
|
'credit': original_columns[1],
|
|
}
|
|
|
|
tax_report_line['columns'] = [row_column_map.get(col['expression_label'], {}) for col in options['columns']]
|
|
rslt.append(tax_report_line)
|
|
|
|
return rslt
|
|
|
|
def _query_values(self, report, options):
|
|
""" Executes the queries, and performs all the computations.
|
|
|
|
:return: [(record, values_by_column_group), ...], where
|
|
- record is an account.account record.
|
|
- values_by_column_group is a dict in the form {column_group_key: values, ...}
|
|
- column_group_key is a string identifying a column group, as in options['column_groups']
|
|
- values is a list of dictionaries, one per period containing:
|
|
- sum: {'debit': float, 'credit': float, 'balance': float}
|
|
- (optional) initial_balance: {'debit': float, 'credit': float, 'balance': float}
|
|
- (optional) unaffected_earnings: {'debit': float, 'credit': float, 'balance': float}
|
|
"""
|
|
# Execute the queries and dispatch the results.
|
|
query, params = self._get_query_sums(report, options)
|
|
|
|
if not query:
|
|
return []
|
|
|
|
groupby_accounts = {}
|
|
groupby_companies = {}
|
|
|
|
self._cr.execute(query, params)
|
|
for res in self._cr.dictfetchall():
|
|
# No result to aggregate.
|
|
if res['groupby'] is None:
|
|
continue
|
|
|
|
column_group_key = res['column_group_key']
|
|
key = res['key']
|
|
if key == 'sum':
|
|
groupby_accounts.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']})
|
|
groupby_accounts[res['groupby']][column_group_key][key] = res
|
|
|
|
elif key == 'initial_balance':
|
|
groupby_accounts.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']})
|
|
groupby_accounts[res['groupby']][column_group_key][key] = res
|
|
|
|
elif key == 'unaffected_earnings':
|
|
groupby_companies.setdefault(res['groupby'], {col_group_key: {} for col_group_key in options['column_groups']})
|
|
groupby_companies[res['groupby']][column_group_key] = res
|
|
|
|
# Affect the unaffected earnings to the first fetched account of type 'account.data_unaffected_earnings'.
|
|
# There is an unaffected earnings for each company but it's less costly to fetch all candidate accounts in
|
|
# a single search and then iterate it.
|
|
if groupby_companies:
|
|
equity_unaffected_account_ids_by_company = self.env['account.account'].browse(
|
|
self.env['account.account']._name_search(options.get('filter_search_bar'), [
|
|
*self.env['account.account']._check_company_domain(list(groupby_companies.keys())),
|
|
('account_type', '=', 'equity_unaffected'),
|
|
])
|
|
).grouped('company_id')
|
|
|
|
for company_id, groupby_company in groupby_companies.items():
|
|
if equity_unaffected_account := equity_unaffected_account_ids_by_company.get(self.env['res.company'].browse(company_id).root_id):
|
|
for column_group_key in options['column_groups']:
|
|
groupby_accounts.setdefault(equity_unaffected_account.id, {col_group_key: {'unaffected_earnings': {}} for col_group_key in options['column_groups']})
|
|
|
|
if unaffected_earnings := groupby_company.get(column_group_key):
|
|
if groupby_accounts[equity_unaffected_account.id][column_group_key].get('unaffected_earnings'):
|
|
for key in ['amount_currency', 'debit', 'credit', 'balance']:
|
|
groupby_accounts[equity_unaffected_account.id][column_group_key]['unaffected_earnings'][key] += unaffected_earnings[key]
|
|
else:
|
|
groupby_accounts[equity_unaffected_account.id][column_group_key]['unaffected_earnings'] = unaffected_earnings
|
|
|
|
# Retrieve the accounts to browse.
|
|
# groupby_accounts.keys() contains all account ids affected by:
|
|
# - the amls in the current period.
|
|
# - the amls affecting the initial balance.
|
|
# - the unaffected earnings allocation.
|
|
# Note a search is done instead of a browse to preserve the table ordering.
|
|
if groupby_accounts:
|
|
accounts = self.env['account.account'].search([('id', 'in', list(groupby_accounts.keys()))])
|
|
else:
|
|
accounts = []
|
|
|
|
return [(account, groupby_accounts[account.id]) for account in accounts]
|
|
|
|
def _get_query_sums(self, report, options):
|
|
""" Construct a query retrieving all the aggregated sums to build the report. It includes:
|
|
- sums for all accounts.
|
|
- sums for the initial balances.
|
|
- sums for the unaffected earnings.
|
|
- sums for the tax declaration.
|
|
:return: (query, params)
|
|
"""
|
|
options_by_column_group = report._split_options_per_column_group(options)
|
|
|
|
params = []
|
|
queries = []
|
|
|
|
# Create the currency table.
|
|
# As the currency table is the same whatever the comparisons, create it only once.
|
|
ct_query = report._get_query_currency_table(options)
|
|
|
|
# ============================================
|
|
# 1) Get sums for all accounts.
|
|
# ============================================
|
|
for column_group_key, options_group in options_by_column_group.items():
|
|
if not options.get('general_ledger_strict_range'):
|
|
options_group = self._get_options_sum_balance(options_group)
|
|
|
|
# Sum is computed including the initial balance of the accounts configured to do so, unless a special option key is used
|
|
# (this is required for trial balance, which is based on general ledger)
|
|
sum_date_scope = 'strict_range' if options_group.get('general_ledger_strict_range') else 'normal'
|
|
|
|
query_domain = []
|
|
|
|
if options.get('export_mode') == 'print' and options.get('filter_search_bar'):
|
|
query_domain.append(('account_id', 'ilike', options['filter_search_bar']))
|
|
|
|
if options_group.get('include_current_year_in_unaff_earnings'):
|
|
query_domain += [('account_id.include_initial_balance', '=', True)]
|
|
|
|
tables, where_clause, where_params = report._query_get(options_group, sum_date_scope, domain=query_domain)
|
|
params.append(column_group_key)
|
|
params += where_params
|
|
queries.append(f"""
|
|
SELECT
|
|
account_move_line.account_id AS groupby,
|
|
'sum' AS key,
|
|
MAX(account_move_line.date) AS max_date,
|
|
%s AS column_group_key,
|
|
COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency,
|
|
SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) AS debit,
|
|
SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) AS credit,
|
|
SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) AS balance
|
|
FROM {tables}
|
|
LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id
|
|
WHERE {where_clause}
|
|
GROUP BY account_move_line.account_id
|
|
""")
|
|
|
|
# ============================================
|
|
# 2) Get sums for the unaffected earnings.
|
|
# ============================================
|
|
if not options_group.get('general_ledger_strict_range'):
|
|
unaff_earnings_domain = [('account_id.include_initial_balance', '=', False)]
|
|
|
|
# The period domain is expressed as:
|
|
# [
|
|
# ('date' <= fiscalyear['date_from'] - 1),
|
|
# ('account_id.include_initial_balance', '=', False),
|
|
# ]
|
|
|
|
new_options = self._get_options_unaffected_earnings(options_group)
|
|
tables, where_clause, where_params = report._query_get(new_options, 'strict_range', domain=unaff_earnings_domain)
|
|
params.append(column_group_key)
|
|
params += where_params
|
|
queries.append(f"""
|
|
SELECT
|
|
account_move_line.company_id AS groupby,
|
|
'unaffected_earnings' AS key,
|
|
NULL AS max_date,
|
|
%s AS column_group_key,
|
|
COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency,
|
|
SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) AS debit,
|
|
SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) AS credit,
|
|
SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) AS balance
|
|
FROM {tables}
|
|
LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id
|
|
WHERE {where_clause}
|
|
GROUP BY account_move_line.company_id
|
|
""")
|
|
|
|
return ' UNION ALL '.join(queries), params
|
|
|
|
def _get_options_unaffected_earnings(self, options):
|
|
''' Create options used to compute the unaffected earnings.
|
|
The unaffected earnings are the amount of benefits/loss that have not been allocated to
|
|
another account in the previous fiscal years.
|
|
The resulting dates domain will be:
|
|
[
|
|
('date' <= fiscalyear['date_from'] - 1),
|
|
('account_id.include_initial_balance', '=', False),
|
|
]
|
|
:param options: The report options.
|
|
:return: A copy of the options.
|
|
'''
|
|
new_options = options.copy()
|
|
new_options.pop('filter_search_bar', None)
|
|
fiscalyear_dates = self.env.company.compute_fiscalyear_dates(fields.Date.from_string(options['date']['date_from']))
|
|
|
|
# Trial balance uses the options key, general ledger does not
|
|
new_date_to = fields.Date.from_string(new_options['date']['date_to']) if options.get('include_current_year_in_unaff_earnings') else fiscalyear_dates['date_from'] - timedelta(days=1)
|
|
|
|
new_options['date'] = {
|
|
'mode': 'single',
|
|
'date_to': fields.Date.to_string(new_date_to),
|
|
}
|
|
|
|
return new_options
|
|
|
|
def _get_aml_values(self, report, options, expanded_account_ids, offset=0, limit=None):
|
|
rslt = {account_id: {} for account_id in expanded_account_ids}
|
|
aml_query, aml_params = self._get_query_amls(report, options, expanded_account_ids, offset=offset, limit=limit)
|
|
self._cr.execute(aml_query, aml_params)
|
|
aml_results_number = 0
|
|
has_more = False
|
|
for aml_result in self._cr.dictfetchall():
|
|
aml_results_number += 1
|
|
if aml_results_number == limit:
|
|
has_more = True
|
|
break
|
|
|
|
if aml_result['ref']:
|
|
aml_result['communication'] = f"{aml_result['ref']} - {aml_result['name']}"
|
|
else:
|
|
aml_result['communication'] = aml_result['name']
|
|
|
|
# The same aml can return multiple results when using account_report_cash_basis module, if the receivable/payable
|
|
# is reconciled with multiple payments. In this case, the date shown for the move lines actually corresponds to the
|
|
# reconciliation date. In order to keep distinct lines in this case, we include date in the grouping key.
|
|
aml_key = (aml_result['id'], aml_result['date'])
|
|
|
|
account_result = rslt[aml_result['account_id']]
|
|
if not aml_key in account_result:
|
|
account_result[aml_key] = {col_group_key: {} for col_group_key in options['column_groups']}
|
|
|
|
already_present_result = account_result[aml_key][aml_result['column_group_key']]
|
|
if already_present_result:
|
|
# In case the same move line gives multiple results at the same date, add them.
|
|
# This does not happen in standard GL report, but could because of custom shadowing of account.move.line,
|
|
# such as the one done in account_report_cash_basis (if the payable/receivable line is reconciled twice at the same date).
|
|
already_present_result['debit'] += aml_result['debit']
|
|
already_present_result['credit'] += aml_result['credit']
|
|
already_present_result['balance'] += aml_result['balance']
|
|
already_present_result['amount_currency'] += aml_result['amount_currency']
|
|
else:
|
|
account_result[aml_key][aml_result['column_group_key']] = aml_result
|
|
|
|
return rslt, has_more
|
|
|
|
def _get_query_amls(self, report, options, expanded_account_ids, offset=0, limit=None):
|
|
""" Construct a query retrieving the account.move.lines when expanding a report line with or without the load
|
|
more.
|
|
:param options: The report options.
|
|
:param expanded_account_ids: The account.account ids corresponding to consider. If None, match every account.
|
|
:param offset: The offset of the query (used by the load more).
|
|
:param limit: The limit of the query (used by the load more).
|
|
:return: (query, params)
|
|
"""
|
|
additional_domain = [('account_id', 'in', expanded_account_ids)] if expanded_account_ids is not None else None
|
|
queries = []
|
|
all_params = []
|
|
lang = self.env.user.lang or get_lang(self.env).code
|
|
journal_name = f"COALESCE(journal.name->>'{lang}', journal.name->>'en_US')" if \
|
|
self.pool['account.journal'].name.translate else 'journal.name'
|
|
account_name = f"COALESCE(account.name->>'{lang}', account.name->>'en_US')" if \
|
|
self.pool['account.account'].name.translate else 'account.name'
|
|
for column_group_key, group_options in report._split_options_per_column_group(options).items():
|
|
# Get sums for the account move lines.
|
|
# period: [('date' <= options['date_to']), ('date', '>=', options['date_from'])]
|
|
tables, where_clause, where_params = report._query_get(group_options, domain=additional_domain, date_scope='strict_range')
|
|
ct_query = report._get_query_currency_table(group_options)
|
|
query = f'''
|
|
(SELECT
|
|
account_move_line.id,
|
|
account_move_line.date,
|
|
account_move_line.date_maturity,
|
|
account_move_line.name,
|
|
account_move_line.ref,
|
|
account_move_line.company_id,
|
|
account_move_line.account_id,
|
|
account_move_line.payment_id,
|
|
account_move_line.partner_id,
|
|
account_move_line.currency_id,
|
|
account_move_line.amount_currency,
|
|
COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date,
|
|
account_move_line.date AS date,
|
|
ROUND(account_move_line.debit * currency_table.rate, currency_table.precision) AS debit,
|
|
ROUND(account_move_line.credit * currency_table.rate, currency_table.precision) AS credit,
|
|
ROUND(account_move_line.balance * currency_table.rate, currency_table.precision) AS balance,
|
|
move.name AS move_name,
|
|
company.currency_id AS company_currency_id,
|
|
partner.name AS partner_name,
|
|
move.move_type AS move_type,
|
|
account.code AS account_code,
|
|
{account_name} AS account_name,
|
|
journal.code AS journal_code,
|
|
{journal_name} AS journal_name,
|
|
full_rec.id AS full_rec_name,
|
|
%s AS column_group_key
|
|
FROM {tables}
|
|
JOIN account_move move ON move.id = account_move_line.move_id
|
|
LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id
|
|
LEFT JOIN res_company company ON company.id = account_move_line.company_id
|
|
LEFT JOIN res_partner partner ON partner.id = account_move_line.partner_id
|
|
LEFT JOIN account_account account ON account.id = account_move_line.account_id
|
|
LEFT JOIN account_journal journal ON journal.id = account_move_line.journal_id
|
|
LEFT JOIN account_full_reconcile full_rec ON full_rec.id = account_move_line.full_reconcile_id
|
|
WHERE {where_clause}
|
|
ORDER BY account_move_line.date, account_move_line.move_name, account_move_line.id)
|
|
'''
|
|
|
|
queries.append(query)
|
|
all_params.append(column_group_key)
|
|
all_params += where_params
|
|
|
|
full_query = " UNION ALL ".join(queries)
|
|
|
|
if offset:
|
|
full_query += ' OFFSET %s '
|
|
all_params.append(offset)
|
|
if limit:
|
|
full_query += ' LIMIT %s '
|
|
all_params.append(limit)
|
|
|
|
return (full_query, all_params)
|
|
|
|
def _get_initial_balance_values(self, report, account_ids, options):
|
|
"""
|
|
Get sums for the initial balance.
|
|
"""
|
|
queries = []
|
|
params = []
|
|
for column_group_key, options_group in report._split_options_per_column_group(options).items():
|
|
new_options = self._get_options_initial_balance(options_group)
|
|
ct_query = report._get_query_currency_table(new_options)
|
|
domain = [('account_id', 'in', account_ids)]
|
|
if new_options.get('include_current_year_in_unaff_earnings'):
|
|
domain += [('account_id.include_initial_balance', '=', True)]
|
|
tables, where_clause, where_params = report._query_get(new_options, 'normal', domain=domain)
|
|
params.append(column_group_key)
|
|
params += where_params
|
|
queries.append(f"""
|
|
SELECT
|
|
account_move_line.account_id AS groupby,
|
|
'initial_balance' AS key,
|
|
NULL AS max_date,
|
|
%s AS column_group_key,
|
|
COALESCE(SUM(account_move_line.amount_currency), 0.0) AS amount_currency,
|
|
SUM(ROUND(account_move_line.debit * currency_table.rate, currency_table.precision)) AS debit,
|
|
SUM(ROUND(account_move_line.credit * currency_table.rate, currency_table.precision)) AS credit,
|
|
SUM(ROUND(account_move_line.balance * currency_table.rate, currency_table.precision)) AS balance
|
|
FROM {tables}
|
|
LEFT JOIN {ct_query} ON currency_table.company_id = account_move_line.company_id
|
|
WHERE {where_clause}
|
|
GROUP BY account_move_line.account_id
|
|
""")
|
|
|
|
self._cr.execute(" UNION ALL ".join(queries), params)
|
|
|
|
init_balance_by_col_group = {
|
|
account_id: {column_group_key: {} for column_group_key in options['column_groups']}
|
|
for account_id in account_ids
|
|
}
|
|
for result in self._cr.dictfetchall():
|
|
init_balance_by_col_group[result['groupby']][result['column_group_key']] = result
|
|
|
|
accounts = self.env['account.account'].browse(account_ids)
|
|
return {
|
|
account.id: (account, init_balance_by_col_group[account.id])
|
|
for account in accounts
|
|
}
|
|
|
|
def _get_options_initial_balance(self, options):
|
|
""" Create options used to compute the initial balances.
|
|
The initial balances depict the current balance of the accounts at the beginning of
|
|
the selected period in the report.
|
|
The resulting dates domain will be:
|
|
[
|
|
('date' <= options['date_from'] - 1),
|
|
'|',
|
|
('date' >= fiscalyear['date_from']),
|
|
('account_id.include_initial_balance', '=', True)
|
|
]
|
|
:param options: The report options.
|
|
:return: A copy of the options.
|
|
"""
|
|
#pylint: disable=sql-injection
|
|
new_options = options.copy()
|
|
date_to = new_options['comparison']['periods'][-1]['date_from'] if new_options.get('comparison', {}).get('periods') else new_options['date']['date_from']
|
|
new_date_to = fields.Date.from_string(date_to) - timedelta(days=1)
|
|
|
|
# Date from computation
|
|
# We have two case:
|
|
# 1) We are choosing a date that starts at the beginning of a fiscal year and we want the initial period to be
|
|
# the previous fiscal year
|
|
# 2) We are choosing a date that starts in the middle of a fiscal year and in that case we want the initial period
|
|
# to be the beginning of the fiscal year
|
|
date_from = fields.Date.from_string(new_options['date']['date_from'])
|
|
current_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from)
|
|
|
|
if date_from == current_fiscalyear_dates['date_from']:
|
|
# We want the previous fiscal year
|
|
previous_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from - timedelta(days=1))
|
|
new_date_from = previous_fiscalyear_dates['date_from']
|
|
include_current_year_in_unaff_earnings = True
|
|
else:
|
|
# We want the current fiscal year
|
|
new_date_from = current_fiscalyear_dates['date_from']
|
|
include_current_year_in_unaff_earnings = False
|
|
|
|
new_options['date'] = {
|
|
'mode': 'range',
|
|
'date_from': fields.Date.to_string(new_date_from),
|
|
'date_to': fields.Date.to_string(new_date_to),
|
|
}
|
|
new_options['include_current_year_in_unaff_earnings'] = include_current_year_in_unaff_earnings
|
|
|
|
return new_options
|
|
|
|
def _get_options_sum_balance(self, options):
|
|
new_options = options.copy()
|
|
|
|
if not options.get('general_ledger_strict_range'):
|
|
# Date from
|
|
date_from = fields.Date.from_string(new_options['date']['date_from'])
|
|
current_fiscalyear_dates = self.env.company.compute_fiscalyear_dates(date_from)
|
|
new_date_from = current_fiscalyear_dates['date_from']
|
|
|
|
new_date_to = new_options['date']['date_to']
|
|
|
|
new_options['date'] = {
|
|
'mode': 'range',
|
|
'date_from': fields.Date.to_string(new_date_from),
|
|
'date_to': new_date_to,
|
|
}
|
|
|
|
return new_options
|
|
|
|
####################################################
|
|
# COLUMN/LINE HELPERS
|
|
####################################################
|
|
def _get_account_title_line(self, report, options, account, has_lines, eval_dict):
|
|
line_columns = []
|
|
for column in options['columns']:
|
|
col_value = eval_dict.get(column['column_group_key'], {}).get(column['expression_label'])
|
|
col_expr_label = column['expression_label']
|
|
|
|
value = None if col_value is None or (col_expr_label == 'amount_currency' and not account.currency_id) else col_value
|
|
|
|
line_columns.append(report._build_column_dict(
|
|
value,
|
|
column,
|
|
options=options,
|
|
currency=account.currency_id if col_expr_label == 'amount_currency' else None,
|
|
))
|
|
|
|
line_id = report._get_generic_line_id('account.account', account.id)
|
|
is_in_unfolded_lines = any(
|
|
report._get_res_id_from_line_id(line_id, 'account.account') == account.id
|
|
for line_id in options.get('unfolded_lines')
|
|
)
|
|
return {
|
|
'id': line_id,
|
|
'name': f'{account.code} {account.name}',
|
|
'columns': line_columns,
|
|
'level': 1,
|
|
'unfoldable': has_lines,
|
|
'unfolded': has_lines and (is_in_unfolded_lines or options.get('unfold_all')),
|
|
'expand_function': '_report_expand_unfoldable_line_general_ledger',
|
|
}
|
|
|
|
def _get_aml_line(self, report, parent_line_id, options, eval_dict, init_bal_by_col_group):
|
|
line_columns = []
|
|
for column in options['columns']:
|
|
col_expr_label = column['expression_label']
|
|
col_value = eval_dict[column['column_group_key']].get(col_expr_label)
|
|
col_currency = None
|
|
|
|
if col_value is not None:
|
|
if col_expr_label == 'amount_currency':
|
|
col_currency = self.env['res.currency'].browse(eval_dict[column['column_group_key']]['currency_id'])
|
|
col_value = None if col_currency == self.env.company.currency_id else col_value
|
|
elif col_expr_label == 'balance':
|
|
col_value += (init_bal_by_col_group[column['column_group_key']] or 0)
|
|
|
|
line_columns.append(report._build_column_dict(
|
|
col_value,
|
|
column,
|
|
options=options,
|
|
currency=col_currency,
|
|
))
|
|
|
|
aml_id = None
|
|
move_name = None
|
|
caret_type = None
|
|
for column_group_dict in eval_dict.values():
|
|
aml_id = column_group_dict.get('id', '')
|
|
if aml_id:
|
|
if column_group_dict.get('payment_id'):
|
|
caret_type = 'account.payment'
|
|
else:
|
|
caret_type = 'account.move.line'
|
|
move_name = column_group_dict['move_name']
|
|
date = str(column_group_dict.get('date', ''))
|
|
break
|
|
|
|
return {
|
|
'id': report._get_generic_line_id('account.move.line', aml_id, parent_line_id=parent_line_id, markup=date),
|
|
'caret_options': caret_type,
|
|
'parent_id': parent_line_id,
|
|
'name': move_name,
|
|
'columns': line_columns,
|
|
'level': 3,
|
|
}
|
|
|
|
@api.model
|
|
def _get_total_line(self, report, options, eval_dict):
|
|
line_columns = []
|
|
for column in options['columns']:
|
|
col_value = eval_dict[column['column_group_key']].get(column['expression_label'])
|
|
col_value = None if col_value is None else col_value
|
|
|
|
line_columns.append(report._build_column_dict(col_value, column, options=options))
|
|
|
|
return {
|
|
'id': report._get_generic_line_id(None, None, markup='total'),
|
|
'name': _('Total'),
|
|
'level': 1,
|
|
'columns': line_columns,
|
|
}
|
|
|
|
def caret_option_audit_tax(self, options, params):
|
|
return self.env['account.generic.tax.report.handler'].caret_option_audit_tax(options, params)
|
|
|
|
def _report_expand_unfoldable_line_general_ledger(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
|
|
def init_load_more_progress(line_dict):
|
|
return {
|
|
column['column_group_key']: line_col.get('no_format', 0)
|
|
for column, line_col in zip(options['columns'], line_dict['columns'])
|
|
if column['expression_label'] == 'balance'
|
|
}
|
|
|
|
report = self.env.ref('account_reports.general_ledger_report')
|
|
model, model_id = report._get_model_info_from_id(line_dict_id)
|
|
|
|
if model != 'account.account':
|
|
raise UserError(_("Wrong ID for general ledger line to expand: %s", line_dict_id))
|
|
|
|
lines = []
|
|
|
|
# Get initial balance
|
|
if offset == 0:
|
|
if unfold_all_batch_data:
|
|
account, init_balance_by_col_group = unfold_all_batch_data['initial_balances'][model_id]
|
|
else:
|
|
account, init_balance_by_col_group = self._get_initial_balance_values(report, [model_id], options)[model_id]
|
|
|
|
initial_balance_line = report._get_partner_and_general_ledger_initial_balance_line(options, line_dict_id, init_balance_by_col_group, account.currency_id)
|
|
|
|
if initial_balance_line:
|
|
lines.append(initial_balance_line)
|
|
|
|
# For the first expansion of the line, the initial balance line gives the progress
|
|
progress = init_load_more_progress(initial_balance_line)
|
|
|
|
# Get move lines
|
|
limit_to_load = report.load_more_limit + 1 if report.load_more_limit and options['export_mode'] != 'print' else None
|
|
if unfold_all_batch_data:
|
|
aml_results = unfold_all_batch_data['aml_results'][model_id]
|
|
has_more = unfold_all_batch_data['has_more'].get(model_id, False)
|
|
else:
|
|
aml_results, has_more = self._get_aml_values(report, options, [model_id], offset=offset, limit=limit_to_load)
|
|
aml_results = aml_results[model_id]
|
|
|
|
next_progress = progress
|
|
for aml_result in aml_results.values():
|
|
new_line = self._get_aml_line(report, line_dict_id, options, aml_result, next_progress)
|
|
lines.append(new_line)
|
|
next_progress = init_load_more_progress(new_line)
|
|
|
|
return {
|
|
'lines': lines,
|
|
'offset_increment': report.load_more_limit,
|
|
'has_more': has_more,
|
|
'progress': next_progress,
|
|
}
|