Files
enterprise-addons/l10n_cl_reports/models/account_eightcolumns_report.py
T

237 lines
11 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import SQL
from collections import OrderedDict
from datetime import timedelta
class L10n_ClReportHandler(models.AbstractModel):
_name = 'l10n_cl.report.handler'
_inherit = ['account.balance.sheet.report.handler']
_description = 'Chilean Report Custom Handler'
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
# dict of the form {account_id: {column_group_key: {expression_label: value}}}
lines_dict = {}
# totals dictionaries: dicts of the form {column_group_key: {expression_label: total_value}}
subtotals_dict = {}
fiscalyear_result_dict = {}
previous_years_unallocated_earnings_dict = {}
totals_dict = {}
# Build query
query_list = []
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
query = self._prepare_query(report, column_group_options, column_group_key)
query_list.append(SQL("(%s)", query))
# Set defaults here since the results of the query for this column_group_key might be empty
subtotals_dict[column_group_key] = dict.fromkeys([col['expression_label'] for col in column_group_options['columns']], 0.0)
full_query = SQL(" UNION ALL ").join(query_list)
self.env.cr.execute(full_query)
# Fill lines and subtotals dictionaries
for result in self.env.cr.dictfetchall():
account_id = result['id']
column_group_key = result['column_group_key']
lines_dict.setdefault(account_id, {})
lines_dict[account_id]['full_name'] = f"{result['code']} {result['name']}" if result['code'] else result['name']
lines_dict[account_id][column_group_key] = result
for expression_label in subtotals_dict[column_group_key]:
subtotals_dict[column_group_key][expression_label] += result[expression_label]
# Compute other 'total' lines
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
subtotals = subtotals_dict[column_group_key]
fiscalyear_result = self._calculate_fiscalyear_result(column_group_options, subtotals)
fiscalyear_result_dict[column_group_key] = fiscalyear_result
previous_years_unallocated_earnings = self._calculate_previous_years_unallocated_earnings(report, column_group_options)
previous_years_unallocated_earnings_dict[column_group_key] = previous_years_unallocated_earnings
totals = self._calculate_totals(column_group_options, subtotals, fiscalyear_result, previous_years_unallocated_earnings)
totals_dict[column_group_key] = totals
lines = []
for account_id, line_vals in lines_dict.items():
line = self._create_report_line(report, options, line_vals, account_id)
lines.append((0, line))
# Subtotals line
lines.append((0, self._create_report_total_line(report, options, subtotals_dict, _("Subtotal"), 2, markup='subtotals')))
# Fiscal year results line
lines.append((0, self._create_report_total_line(report, options, fiscalyear_result_dict, _("Profit and Loss"), 3, markup='fiscalyear_result')))
# Previous year unallocated earnings line
unalloc_line = self._create_report_total_line(
report, options, previous_years_unallocated_earnings_dict, _("Previous years unallocated earnings"), 3, markup='previous_years_unallocated_earnings')
if any(not self.env.company.currency_id.is_zero(column['no_format']) for column in unalloc_line['columns']):
lines.append((0, unalloc_line))
# General total line
lines.append((0, self._create_report_total_line(report, options, totals_dict, _("Total"), 1, markup='general_total')))
return lines
@api.model
def _prepare_query(self, report, options, column_group_key) -> SQL:
domain = [
'|',
('date', '>=', options['date']['date_from']),
'&',
('account_id.include_initial_balance', '=', True),
('account_id.account_type', '!=', 'equity_unaffected'),
]
query = report._get_report_query(options, 'from_beginning', domain=domain)
query.add_join('JOIN', alias='aa', table='account_account', condition=SQL('account_move_line.account_id = aa.id'))
account_code = self.env['account.account']._field_to_sql('aa', 'code', query)
account_name = self.env['account.account']._field_to_sql('aa', 'name')
sql_query = SQL(
"""
SELECT %(column_group_key)s AS column_group_key,
aa.id, %(account_code)s AS code, %(account_name)s AS name,
SUM(account_move_line.debit) AS debit,
SUM(account_move_line.credit) AS credit,
GREATEST(SUM(account_move_line.balance), 0) AS debitor,
GREATEST(SUM(-account_move_line.balance), 0) AS creditor,
CASE WHEN %(bs)s THEN GREATEST(SUM(account_move_line.balance), 0) ELSE 0 END AS assets,
CASE WHEN %(bs)s THEN GREATEST(SUM(-account_move_line.balance), 0) ELSE 0 END AS liabilities,
CASE WHEN %(pnl)s THEN GREATEST(SUM(account_move_line.balance), 0) ELSE 0 END AS loss,
CASE WHEN %(pnl)s THEN GREATEST(SUM(-account_move_line.balance), 0) ELSE 0 END AS gain
FROM %(table_references)s
WHERE %(search_condition)s
GROUP BY aa.id, %(account_code)s, %(account_name)s
ORDER BY %(account_code)s
""",
column_group_key=column_group_key,
account_code=account_code,
account_name=account_name,
table_references=query.from_clause,
search_condition=query.where_clause,
bs=SQL("aa.account_type LIKE 'asset%%' OR aa.account_type LIKE 'liability%%' OR aa.account_type LIKE 'equity%%'"),
pnl=SQL("aa.account_type LIKE 'expense%%' OR aa.account_type LIKE 'income%%'"),
)
return sql_query
def _create_report_line(self, report, options, vals, vals_id):
""" Create a standard (non total) line for the report
:param options: report options
:param vals: values necessary for the line
:param vals_id: id of the account
"""
columns = []
for column in options['columns']:
col_value = vals.get(column['column_group_key'], {}).get(column['expression_label'], False)
columns.append(report._build_column_dict(col_value, column, options=options))
return {
'id': report._get_generic_line_id('account.account', vals_id),
'caret_options': 'account.account',
'name': vals['full_name'],
'columns': columns,
'level': 2,
}
def _create_report_total_line(self, report, options, total_vals, name, level=1, markup=''):
""" Create a total line for the report
:param options: report options
:param total_vals: values necessary for the line
:param name: name given to the total line
:param level: the level of the total line
"""
if not total_vals:
total_vals = {}
columns = []
for column in options['columns']:
col_value = total_vals.get(column['column_group_key'], {}).get(column['expression_label'], False)
columns.append(report._build_column_dict(col_value, column, options=options))
return {
'id': report._get_generic_line_id(None, None, markup=markup),
'name': name,
'level': level,
'columns': columns,
}
def _calculate_fiscalyear_result(self, options, subtotal_line):
exercise_result = OrderedDict.fromkeys([col['expression_label'] for col in options['columns']], 0)
if subtotal_line['gain'] >= subtotal_line['loss']:
exercise_result['loss'] = subtotal_line['gain'] - subtotal_line['loss']
exercise_result['liabilities'] = exercise_result['loss']
else:
exercise_result['gain'] = subtotal_line['loss'] - subtotal_line['gain']
exercise_result['assets'] = exercise_result['gain']
return exercise_result
def _calculate_unallocated_earnings_value(self, report, options):
"""
Get all the unallocated earnings value from the previous fiscal years.
The past moves that target Income and expense account (+ special type of expenses)
are summed, and the allocated earnings are removed by summing the balances
of the moves targeting the special account 'Undistributed Profits/Losses'.
"""
new_options = options.copy()
date_from_str = new_options.get('date', {}).get('date_from', '')
date_from = fields.Date.from_string(date_from_str) or fields.Date.today()
fiscal_dates = self.env.company.compute_fiscalyear_dates(date_from)
new_options['date'] = report._get_dates_period(
None,
fiscal_dates['date_from'] - timedelta(days=1),
'range',
period_type='custom')
query = report._get_report_query(new_options, 'strict_range')
account_types = (
'equity_unaffected',
'income',
'income_other',
'expense_direct_cost',
'expense',
'expense_depreciation'
)
sql_query = SQL(
"""
SELECT -SUM(account_move_line.balance) as unaffected_earnings
FROM account_account AS aa, %(table_references)s
WHERE %(search_condition)s
AND aa.id = account_move_line.account_id
AND aa.account_type IN %(account_types)s
""".strip('\n'),
table_references=query.from_clause,
search_condition=query.where_clause,
account_types=account_types,
)
self.env.cr.execute(sql_query)
value = self.env.cr.fetchone()[0] or 0.0
return self.env.company.currency_id.round(value)
def _calculate_previous_years_unallocated_earnings(self, report, options):
earning = self._calculate_unallocated_earnings_value(report, options)
if not earning:
return {}
sum_col, sold_col, liabilities_sign = {
True: ('debit', 'debitor', -1),
False: ('credit', 'creditor', 1),
}.get(earning < 0)
abs_earning = abs(earning)
row = OrderedDict.fromkeys([col['expression_label'] for col in options['columns']], 0)
row[sum_col] = abs_earning
row[sold_col] = abs_earning
row['liabilities'] = liabilities_sign * abs_earning
return row
def _calculate_totals(self, options, subtotal_line, exercise_result_line, previous_years_unallocated_earnings):
parts = [subtotal_line, exercise_result_line]
if previous_years_unallocated_earnings:
parts.append(previous_years_unallocated_earnings)
column_expression_labels = [col['expression_label'] for col in options['columns']]
return OrderedDict([(col, sum([part.get(col, 0) for part in parts])) for col in column_expression_labels])