Initial commit
This commit is contained in:
268
Fusion Accounting/models/account_trial_balance_report.py
Normal file
268
Fusion Accounting/models/account_trial_balance_report.py
Normal file
@@ -0,0 +1,268 @@
|
||||
# Fusion Accounting - Trial Balance Report Handler
|
||||
|
||||
from odoo import api, models, _, fields
|
||||
from odoo.tools import float_compare
|
||||
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
# Sentinel key used for end-balance columns which are computed client-side
|
||||
# and never generate their own SQL column group.
|
||||
_END_COL_GROUP_SENTINEL = '_trial_balance_end_column_group'
|
||||
|
||||
|
||||
class TrialBalanceCustomHandler(models.AbstractModel):
|
||||
"""Wraps the General Ledger handler to produce a Trial Balance.
|
||||
|
||||
The trial balance adds initial-balance and end-balance column groups
|
||||
around the regular period columns and collapses each account's detail
|
||||
into a single non-foldable row.
|
||||
"""
|
||||
|
||||
_name = 'account.trial.balance.report.handler'
|
||||
_inherit = 'account.report.custom.handler'
|
||||
_description = 'Trial Balance Custom Handler'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Dynamic lines
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
|
||||
"""Delegate to the GL handler and then post-process rows to
|
||||
collapse debit/credit, compute end-balance columns, and remove
|
||||
expand functions."""
|
||||
|
||||
def _set_cell(row, idx, amount):
|
||||
row['columns'][idx]['no_format'] = amount
|
||||
row['columns'][idx]['is_zero'] = self.env.company.currency_id.is_zero(amount)
|
||||
|
||||
def _collapse_debit_credit(row, dr_idx, cr_idx, bal_idx=None):
|
||||
"""Net debit and credit: whichever is larger keeps the difference;
|
||||
the other becomes zero. Optionally write balance too."""
|
||||
dr_val = row['columns'][dr_idx]['no_format'] if dr_idx is not None else False
|
||||
cr_val = row['columns'][cr_idx]['no_format'] if cr_idx is not None else False
|
||||
|
||||
if dr_val and cr_val:
|
||||
cmp = self.env.company.currency_id.compare_amounts(dr_val, cr_val)
|
||||
if cmp == 1:
|
||||
_set_cell(row, dr_idx, dr_val - cr_val)
|
||||
_set_cell(row, cr_idx, 0.0)
|
||||
else:
|
||||
_set_cell(row, dr_idx, 0.0)
|
||||
_set_cell(row, cr_idx, (dr_val - cr_val) * -1)
|
||||
|
||||
if bal_idx is not None:
|
||||
_set_cell(row, bal_idx, dr_val - cr_val)
|
||||
|
||||
# Obtain raw GL lines
|
||||
gl_handler = self.env['account.general.ledger.report.handler']
|
||||
raw = [
|
||||
row[1]
|
||||
for row in gl_handler._dynamic_lines_generator(
|
||||
report, options, all_column_groups_expression_totals, warnings=warnings,
|
||||
)
|
||||
]
|
||||
|
||||
# Locate column indices for initial / end balance
|
||||
col_defs = options['columns']
|
||||
init_dr = next((i for i, c in enumerate(col_defs) if c.get('expression_label') == 'debit'), None)
|
||||
init_cr = next((i for i, c in enumerate(col_defs) if c.get('expression_label') == 'credit'), None)
|
||||
|
||||
end_dr = next((i for i, c in enumerate(col_defs)
|
||||
if c.get('expression_label') == 'debit'
|
||||
and c.get('column_group_key') == _END_COL_GROUP_SENTINEL), None)
|
||||
end_cr = next((i for i, c in enumerate(col_defs)
|
||||
if c.get('expression_label') == 'credit'
|
||||
and c.get('column_group_key') == _END_COL_GROUP_SENTINEL), None)
|
||||
end_bal = next((i for i, c in enumerate(col_defs)
|
||||
if c.get('expression_label') == 'balance'
|
||||
and c.get('column_group_key') == _END_COL_GROUP_SENTINEL), None)
|
||||
|
||||
cur = self.env.company.currency_id
|
||||
|
||||
# Process every account row (all except the last = total line)
|
||||
for row in raw[:-1]:
|
||||
_collapse_debit_credit(row, init_dr, init_cr)
|
||||
|
||||
# End balance = sum of all debit columns except the end one itself
|
||||
if end_dr is not None:
|
||||
dr_sum = sum(
|
||||
cur.round(cell['no_format'])
|
||||
for idx, cell in enumerate(row['columns'])
|
||||
if cell.get('expression_label') == 'debit'
|
||||
and idx != end_dr
|
||||
and cell['no_format'] is not None
|
||||
)
|
||||
_set_cell(row, end_dr, dr_sum)
|
||||
|
||||
if end_cr is not None:
|
||||
cr_sum = sum(
|
||||
cur.round(cell['no_format'])
|
||||
for idx, cell in enumerate(row['columns'])
|
||||
if cell.get('expression_label') == 'credit'
|
||||
and idx != end_cr
|
||||
and cell['no_format'] is not None
|
||||
)
|
||||
_set_cell(row, end_cr, cr_sum)
|
||||
|
||||
_collapse_debit_credit(row, end_dr, end_cr, end_bal)
|
||||
|
||||
# Remove GL expand-related keys
|
||||
row.pop('expand_function', None)
|
||||
row.pop('groupby', None)
|
||||
row['unfoldable'] = False
|
||||
row['unfolded'] = False
|
||||
|
||||
mdl = report._get_model_info_from_id(row['id'])[0]
|
||||
if mdl == 'account.account':
|
||||
row['caret_options'] = 'trial_balance'
|
||||
|
||||
# Recompute totals on the total line
|
||||
if raw:
|
||||
total_row = raw[-1]
|
||||
for idx in (init_dr, init_cr, end_dr, end_cr):
|
||||
if idx is not None:
|
||||
total_row['columns'][idx]['no_format'] = sum(
|
||||
cur.round(r['columns'][idx]['no_format'])
|
||||
for r in raw[:-1]
|
||||
if report._get_model_info_from_id(r['id'])[0] == 'account.account'
|
||||
)
|
||||
|
||||
return [(0, row) for row in raw]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Caret options
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _caret_options_initializer(self):
|
||||
return {
|
||||
'trial_balance': [
|
||||
{'name': _("General Ledger"), 'action': 'caret_option_open_general_ledger'},
|
||||
{'name': _("Journal Items"), 'action': 'open_journal_items'},
|
||||
],
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Column group management
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _get_column_group_creation_data(self, report, options, previous_options=None):
|
||||
"""Declare which extra column groups to add and on which side of
|
||||
the report they appear."""
|
||||
return (
|
||||
(self._build_initial_balance_col_group, 'left'),
|
||||
(self._build_end_balance_col_group, 'right'),
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _create_and_append_column_group(
|
||||
self, report, options, header_label, forced_opts, side,
|
||||
group_vals, exclude_initial_balance=False, append_col_groups=True,
|
||||
):
|
||||
"""Helper: generate a new column group and append it to *side*."""
|
||||
header_elem = [{'name': header_label, 'forced_options': forced_opts}]
|
||||
full_headers = [header_elem, *options['column_headers'][1:]]
|
||||
cg_vals = report._generate_columns_group_vals_recursively(full_headers, group_vals)
|
||||
|
||||
if exclude_initial_balance:
|
||||
for cg in cg_vals:
|
||||
cg['forced_options']['general_ledger_strict_range'] = True
|
||||
|
||||
cols, col_groups = report._build_columns_from_column_group_vals(forced_opts, cg_vals)
|
||||
|
||||
side['column_headers'] += header_elem
|
||||
if append_col_groups:
|
||||
side['column_groups'] |= col_groups
|
||||
side['columns'] += cols
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Options
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _custom_options_initializer(self, report, options, previous_options):
|
||||
"""Insert initial-balance and end-balance column groups around the
|
||||
standard period columns."""
|
||||
default_gv = {'horizontal_groupby_element': {}, 'forced_options': {}}
|
||||
lhs = {'column_headers': [], 'column_groups': {}, 'columns': []}
|
||||
rhs = {'column_headers': [], 'column_groups': {}, 'columns': []}
|
||||
|
||||
# Mid-period columns should use strict range
|
||||
for cg in options['column_groups'].values():
|
||||
cg['forced_options']['general_ledger_strict_range'] = True
|
||||
|
||||
if options.get('comparison') and not options['comparison'].get('periods'):
|
||||
options['comparison']['period_order'] = 'ascending'
|
||||
|
||||
for factory_fn, side_label in self._get_column_group_creation_data(report, options, previous_options):
|
||||
target = lhs if side_label == 'left' else rhs
|
||||
factory_fn(report, options, previous_options, default_gv, target)
|
||||
|
||||
options['column_headers'][0] = lhs['column_headers'] + options['column_headers'][0] + rhs['column_headers']
|
||||
options['column_groups'].update(lhs['column_groups'])
|
||||
options['column_groups'].update(rhs['column_groups'])
|
||||
options['columns'] = lhs['columns'] + options['columns'] + rhs['columns']
|
||||
options['ignore_totals_below_sections'] = True
|
||||
|
||||
# Force a shared currency-table period for all middle columns
|
||||
shared_period_key = '_trial_balance_middle_periods'
|
||||
for cg in options['column_groups'].values():
|
||||
dt = cg['forced_options'].get('date')
|
||||
if dt:
|
||||
dt['currency_table_period_key'] = shared_period_key
|
||||
|
||||
report._init_options_order_column(options, previous_options)
|
||||
|
||||
def _custom_line_postprocessor(self, report, options, lines):
|
||||
"""Add contrast styling to hierarchy group lines when hierarchy is
|
||||
enabled."""
|
||||
if options.get('hierarchy'):
|
||||
for ln in lines:
|
||||
mdl, _ = report._get_model_info_from_id(ln['id'])
|
||||
if mdl == 'account.group':
|
||||
ln['class'] = ln.get('class', '') + ' o_account_coa_column_contrast_hierarchy'
|
||||
return lines
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Column group builders
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _build_initial_balance_col_group(self, report, options, previous_options, default_gv, side):
|
||||
"""Create the Initial Balance column group on the left."""
|
||||
gl_handler = self.env['account.general.ledger.report.handler']
|
||||
init_opts = gl_handler._get_options_initial_balance(options)
|
||||
forced = {
|
||||
'date': init_opts['date'],
|
||||
'include_current_year_in_unaff_earnings': init_opts['include_current_year_in_unaff_earnings'],
|
||||
'no_impact_on_currency_table': True,
|
||||
}
|
||||
self._create_and_append_column_group(
|
||||
report, options, _("Initial Balance"), forced, side, default_gv,
|
||||
)
|
||||
|
||||
def _build_end_balance_col_group(self, report, options, previous_options, default_gv, side):
|
||||
"""Create the End Balance column group on the right.
|
||||
|
||||
No actual SQL is run for this group; its values are computed by
|
||||
summing all other groups during line post-processing.
|
||||
"""
|
||||
to_dt = options['date']['date_to']
|
||||
from_dt = (
|
||||
options['comparison']['periods'][-1]['date_from']
|
||||
if options.get('comparison', {}).get('periods')
|
||||
else options['date']['date_from']
|
||||
)
|
||||
forced = {
|
||||
'date': report._get_dates_period(
|
||||
fields.Date.from_string(from_dt),
|
||||
fields.Date.from_string(to_dt),
|
||||
'range',
|
||||
),
|
||||
}
|
||||
self._create_and_append_column_group(
|
||||
report, options, _("End Balance"), forced, side, default_gv,
|
||||
append_col_groups=False,
|
||||
)
|
||||
|
||||
# Mark end-balance columns with the sentinel key
|
||||
num_report_cols = len(report.column_ids)
|
||||
for col in side['columns'][-num_report_cols:]:
|
||||
col['column_group_key'] = _END_COL_GROUP_SENTINEL
|
||||
Reference in New Issue
Block a user