Initial commit
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
# Fusion Accounting - Multicurrency Revaluation Report Handler
|
||||
# Computes unrealised FX gains/losses and provides an adjustment wizard
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import float_is_zero, SQL
|
||||
|
||||
|
||||
class FusionMulticurrencyRevaluationHandler(models.AbstractModel):
|
||||
"""Manages unrealised gains and losses arising from fluctuating
|
||||
exchange rates. Presents balances at both historical and current
|
||||
rates and offers an adjustment-entry wizard."""
|
||||
|
||||
_name = 'account.multicurrency.revaluation.report.handler'
|
||||
_inherit = 'account.report.custom.handler'
|
||||
_description = 'Multicurrency Revaluation Report Custom Handler'
|
||||
|
||||
# ---- Display Configuration ----
|
||||
def _get_custom_display_config(self):
|
||||
return {
|
||||
'components': {
|
||||
'AccountReportFilters': 'fusion_accounting.MulticurrencyRevaluationReportFilters',
|
||||
},
|
||||
'templates': {
|
||||
'AccountReportLineName': 'fusion_accounting.MulticurrencyRevaluationReportLineName',
|
||||
},
|
||||
}
|
||||
|
||||
# ---- Options ----
|
||||
def _custom_options_initializer(self, report, options, previous_options):
|
||||
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
||||
|
||||
active_currencies = self.env['res.currency'].search([('active', '=', True)])
|
||||
if len(active_currencies) < 2:
|
||||
raise UserError(_("At least two active currencies are required for this report."))
|
||||
|
||||
fx_rates = active_currencies._get_rates(
|
||||
self.env.company, options.get('date', {}).get('date_to'),
|
||||
)
|
||||
base_rate = fx_rates[self.env.company.currency_id.id]
|
||||
for cid in fx_rates:
|
||||
fx_rates[cid] /= base_rate
|
||||
|
||||
options['currency_rates'] = {
|
||||
str(cur.id): {
|
||||
'currency_id': cur.id,
|
||||
'currency_name': cur.name,
|
||||
'currency_main': self.env.company.currency_id.name,
|
||||
'rate': (
|
||||
fx_rates[cur.id]
|
||||
if not previous_options.get('currency_rates', {}).get(str(cur.id), {}).get('rate')
|
||||
else float(previous_options['currency_rates'][str(cur.id)]['rate'])
|
||||
),
|
||||
}
|
||||
for cur in active_currencies
|
||||
}
|
||||
|
||||
for cr in options['currency_rates'].values():
|
||||
if cr['rate'] == 0:
|
||||
raise UserError(_("Currency rate cannot be zero."))
|
||||
|
||||
options['company_currency'] = options['currency_rates'].pop(
|
||||
str(self.env.company.currency_id.id),
|
||||
)
|
||||
options['custom_rate'] = any(
|
||||
not float_is_zero(cr['rate'] - fx_rates[cr['currency_id']], 20)
|
||||
for cr in options['currency_rates'].values()
|
||||
)
|
||||
options['multi_currency'] = True
|
||||
options['buttons'].append({
|
||||
'name': _('Adjustment Entry'),
|
||||
'sequence': 30,
|
||||
'action': 'action_multi_currency_revaluation_open_revaluation_wizard',
|
||||
'always_show': True,
|
||||
})
|
||||
|
||||
# ---- Warnings ----
|
||||
def _customize_warnings(self, report, options, all_column_groups_expression_totals, warnings):
|
||||
if len(self.env.companies) > 1:
|
||||
warnings['fusion_accounting.multi_currency_revaluation_report_warning_multicompany'] = {
|
||||
'alert_type': 'warning',
|
||||
}
|
||||
if options['custom_rate']:
|
||||
warnings['fusion_accounting.multi_currency_revaluation_report_warning_custom_rate'] = {
|
||||
'alert_type': 'warning',
|
||||
}
|
||||
|
||||
# ---- Post-Processing ----
|
||||
def _custom_line_postprocessor(self, report, options, lines):
|
||||
adj_line_id = self.env.ref('fusion_accounting.multicurrency_revaluation_to_adjust').id
|
||||
excl_line_id = self.env.ref('fusion_accounting.multicurrency_revaluation_excluded').id
|
||||
|
||||
processed = []
|
||||
for idx, ln in enumerate(lines):
|
||||
model_name, model_id = report._get_model_info_from_id(ln['id'])
|
||||
|
||||
if model_name == 'account.report.line' and (
|
||||
(model_id == adj_line_id
|
||||
and report._get_model_info_from_id(lines[idx + 1]['id']) == ('account.report.line', excl_line_id))
|
||||
or (model_id == excl_line_id and idx == len(lines) - 1)
|
||||
):
|
||||
continue
|
||||
|
||||
elif model_name == 'res.currency':
|
||||
rate_val = float(options['currency_rates'][str(model_id)]['rate'])
|
||||
ln['name'] = '{fc} (1 {mc} = {r:.6} {fc})'.format(
|
||||
fc=ln['name'],
|
||||
mc=self.env.company.currency_id.display_name,
|
||||
r=rate_val,
|
||||
)
|
||||
|
||||
elif model_name == 'account.account':
|
||||
ln['is_included_line'] = (
|
||||
report._get_res_id_from_line_id(ln['id'], 'account.account') == adj_line_id
|
||||
)
|
||||
|
||||
ln['cur_revaluation_line_model'] = model_name
|
||||
processed.append(ln)
|
||||
|
||||
return processed
|
||||
|
||||
def _custom_groupby_line_completer(self, report, options, line_dict):
|
||||
info = report._get_model_info_from_id(line_dict['id'])
|
||||
if info[0] == 'res.currency':
|
||||
line_dict['unfolded'] = True
|
||||
line_dict['unfoldable'] = False
|
||||
|
||||
# ---- Actions ----
|
||||
def action_multi_currency_revaluation_open_revaluation_wizard(self, options):
|
||||
wiz_view = self.env.ref(
|
||||
'fusion_accounting.view_account_multicurrency_revaluation_wizard', False,
|
||||
)
|
||||
return {
|
||||
'name': _("Make Adjustment Entry"),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'account.multicurrency.revaluation.wizard',
|
||||
'view_mode': 'form',
|
||||
'view_id': wiz_view.id,
|
||||
'views': [(wiz_view.id, 'form')],
|
||||
'multi': 'True',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
**self.env.context,
|
||||
'multicurrency_revaluation_report_options': options,
|
||||
},
|
||||
}
|
||||
|
||||
def action_multi_currency_revaluation_open_general_ledger(self, options, params):
|
||||
report = self.env['account.report'].browse(options['report_id'])
|
||||
acct_id = report._get_res_id_from_line_id(params['line_id'], 'account.account')
|
||||
acct_line_id = report._get_generic_line_id('account.account', acct_id)
|
||||
gl_options = self.env.ref('fusion_accounting.general_ledger_report').get_options(options)
|
||||
gl_options['unfolded_lines'] = [acct_line_id]
|
||||
|
||||
gl_action = self.env['ir.actions.actions']._for_xml_id(
|
||||
'fusion_accounting.action_account_report_general_ledger',
|
||||
)
|
||||
gl_action['params'] = {
|
||||
'options': gl_options,
|
||||
'ignore_session': True,
|
||||
}
|
||||
return gl_action
|
||||
|
||||
def action_multi_currency_revaluation_toggle_provision(self, options, params):
|
||||
"""Toggle inclusion/exclusion of an account from the provision."""
|
||||
id_map = self.env['account.report']._get_res_ids_from_line_id(
|
||||
params['line_id'], ['res.currency', 'account.account'],
|
||||
)
|
||||
acct = self.env['account.account'].browse(id_map['account.account'])
|
||||
cur = self.env['res.currency'].browse(id_map['res.currency'])
|
||||
if cur in acct.exclude_provision_currency_ids:
|
||||
acct.exclude_provision_currency_ids -= cur
|
||||
else:
|
||||
acct.exclude_provision_currency_ids += cur
|
||||
return {'type': 'ir.actions.client', 'tag': 'reload'}
|
||||
|
||||
def action_multi_currency_revaluation_open_currency_rates(self, options, params=None):
|
||||
cur_id = self.env['account.report']._get_res_id_from_line_id(
|
||||
params['line_id'], 'res.currency',
|
||||
)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Currency Rates (%s)', self.env['res.currency'].browse(cur_id).display_name),
|
||||
'views': [(False, 'list')],
|
||||
'res_model': 'res.currency.rate',
|
||||
'context': {**self.env.context, 'default_currency_id': cur_id, 'active_id': cur_id},
|
||||
'domain': [('currency_id', '=', cur_id)],
|
||||
}
|
||||
|
||||
# ---- Custom Engines ----
|
||||
def _report_custom_engine_multi_currency_revaluation_to_adjust(
|
||||
self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None,
|
||||
):
|
||||
return self._revaluation_custom_lines(
|
||||
options, 'to_adjust', current_groupby, next_groupby, offset=offset, limit=limit,
|
||||
)
|
||||
|
||||
def _report_custom_engine_multi_currency_revaluation_excluded(
|
||||
self, expressions, options, date_scope, current_groupby, next_groupby, offset=0, limit=None, warnings=None,
|
||||
):
|
||||
return self._revaluation_custom_lines(
|
||||
options, 'excluded', current_groupby, next_groupby, offset=offset, limit=limit,
|
||||
)
|
||||
|
||||
def _revaluation_custom_lines(self, options, line_code, current_groupby, next_groupby, offset=0, limit=None):
|
||||
def _build_result(report_obj, qr):
|
||||
return {
|
||||
'balance_currency': qr['balance_currency'] if len(qr['currency_id']) == 1 else None,
|
||||
'currency_id': qr['currency_id'][0] if len(qr['currency_id']) == 1 else None,
|
||||
'balance_operation': qr['balance_operation'],
|
||||
'balance_current': qr['balance_current'],
|
||||
'adjustment': qr['adjustment'],
|
||||
'has_sublines': qr['aml_count'] > 0,
|
||||
}
|
||||
|
||||
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 []),
|
||||
)
|
||||
|
||||
if not current_groupby:
|
||||
return {
|
||||
'balance_currency': None, 'currency_id': None,
|
||||
'balance_operation': None, 'balance_current': None,
|
||||
'adjustment': None, 'has_sublines': False,
|
||||
}
|
||||
|
||||
rate_values_sql = "(VALUES {})".format(
|
||||
', '.join("(%s, %s)" for _ in options['currency_rates']),
|
||||
)
|
||||
rate_params = list(chain.from_iterable(
|
||||
(cr['currency_id'], cr['rate']) for cr in options['currency_rates'].values()
|
||||
))
|
||||
custom_rate_table = SQL(rate_values_sql, *rate_params)
|
||||
report_date = options['date']['date_to']
|
||||
|
||||
no_exchange_clause = SQL(
|
||||
"""
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM account_partial_reconcile pr
|
||||
WHERE pr.exchange_move_id = account_move_line.move_id
|
||||
AND pr.max_date <= %s
|
||||
)
|
||||
""",
|
||||
report_date,
|
||||
)
|
||||
|
||||
qry = report._get_report_query(options, 'strict_range')
|
||||
tail = report._get_engine_query_tail(offset, limit)
|
||||
|
||||
provision_test = 'NOT EXISTS' if line_code == 'to_adjust' else 'EXISTS'
|
||||
|
||||
groupby_col = f"account_move_line.{current_groupby}" if current_groupby else ''
|
||||
groupby_select = f"{groupby_col} AS grouping_key," if current_groupby else ''
|
||||
|
||||
full_sql = SQL(
|
||||
"""
|
||||
WITH custom_currency_table(currency_id, rate) AS (%(rate_table)s)
|
||||
SELECT
|
||||
subquery.grouping_key,
|
||||
ARRAY_AGG(DISTINCT(subquery.currency_id)) AS currency_id,
|
||||
SUM(subquery.balance_currency) AS balance_currency,
|
||||
SUM(subquery.balance_operation) AS balance_operation,
|
||||
SUM(subquery.balance_current) AS balance_current,
|
||||
SUM(subquery.adjustment) AS adjustment,
|
||||
COUNT(subquery.aml_id) AS aml_count
|
||||
FROM (
|
||||
SELECT
|
||||
""" + groupby_select + """
|
||||
ROUND(account_move_line.balance - SUM(ara.amount_debit) + SUM(ara.amount_credit), aml_comp_currency.decimal_places) AS balance_operation,
|
||||
ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) AS balance_currency,
|
||||
ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) / custom_currency_table.rate AS balance_current,
|
||||
(
|
||||
ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) / custom_currency_table.rate
|
||||
- ROUND(account_move_line.balance - SUM(ara.amount_debit) + SUM(ara.amount_credit), aml_comp_currency.decimal_places)
|
||||
) AS adjustment,
|
||||
account_move_line.currency_id AS currency_id,
|
||||
account_move_line.id AS aml_id
|
||||
FROM %(from_refs)s,
|
||||
account_account AS account,
|
||||
res_currency AS aml_currency,
|
||||
res_currency AS aml_comp_currency,
|
||||
custom_currency_table,
|
||||
LATERAL (
|
||||
SELECT COALESCE(SUM(part.amount), 0.0) AS amount_debit,
|
||||
ROUND(SUM(part.debit_amount_currency), curr.decimal_places) AS amount_debit_currency,
|
||||
0.0 AS amount_credit, 0.0 AS amount_credit_currency,
|
||||
account_move_line.currency_id AS currency_id,
|
||||
account_move_line.id AS aml_id
|
||||
FROM account_partial_reconcile part
|
||||
JOIN res_currency curr ON curr.id = part.debit_currency_id
|
||||
WHERE account_move_line.id = part.debit_move_id AND part.max_date <= %(dt)s
|
||||
GROUP BY aml_id, curr.decimal_places
|
||||
UNION
|
||||
SELECT 0.0 AS amount_debit, 0.0 AS amount_debit_currency,
|
||||
COALESCE(SUM(part.amount), 0.0) AS amount_credit,
|
||||
ROUND(SUM(part.credit_amount_currency), curr.decimal_places) AS amount_credit_currency,
|
||||
account_move_line.currency_id AS currency_id,
|
||||
account_move_line.id AS aml_id
|
||||
FROM account_partial_reconcile part
|
||||
JOIN res_currency curr ON curr.id = part.credit_currency_id
|
||||
WHERE account_move_line.id = part.credit_move_id AND part.max_date <= %(dt)s
|
||||
GROUP BY aml_id, curr.decimal_places
|
||||
) AS ara
|
||||
WHERE %(where)s
|
||||
AND account_move_line.account_id = account.id
|
||||
AND account_move_line.currency_id = aml_currency.id
|
||||
AND account_move_line.company_currency_id = aml_comp_currency.id
|
||||
AND account_move_line.currency_id = custom_currency_table.currency_id
|
||||
AND account.account_type NOT IN ('income', 'income_other', 'expense', 'expense_depreciation', 'expense_direct_cost', 'off_balance')
|
||||
AND (
|
||||
account.currency_id != account_move_line.company_currency_id
|
||||
OR (account.account_type IN ('asset_receivable', 'liability_payable')
|
||||
AND account_move_line.currency_id != account_move_line.company_currency_id)
|
||||
)
|
||||
AND """ + provision_test + """ (
|
||||
SELECT 1 FROM account_account_exclude_res_currency_provision
|
||||
WHERE account_account_id = account_move_line.account_id
|
||||
AND res_currency_id = account_move_line.currency_id
|
||||
)
|
||||
AND (%(no_exch)s)
|
||||
GROUP BY account_move_line.id, aml_comp_currency.decimal_places, aml_currency.decimal_places, custom_currency_table.rate
|
||||
HAVING ROUND(account_move_line.balance - SUM(ara.amount_debit) + SUM(ara.amount_credit), aml_comp_currency.decimal_places) != 0
|
||||
OR ROUND(account_move_line.amount_currency - SUM(ara.amount_debit_currency) + SUM(ara.amount_credit_currency), aml_currency.decimal_places) != 0.0
|
||||
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
""" + groupby_select + """
|
||||
account_move_line.balance AS balance_operation,
|
||||
account_move_line.amount_currency AS balance_currency,
|
||||
account_move_line.amount_currency / custom_currency_table.rate AS balance_current,
|
||||
account_move_line.amount_currency / custom_currency_table.rate - account_move_line.balance AS adjustment,
|
||||
account_move_line.currency_id AS currency_id,
|
||||
account_move_line.id AS aml_id
|
||||
FROM %(from_refs)s
|
||||
JOIN account_account account ON account_move_line.account_id = account.id
|
||||
JOIN custom_currency_table ON custom_currency_table.currency_id = account_move_line.currency_id
|
||||
WHERE %(where)s
|
||||
AND account.account_type NOT IN ('income', 'income_other', 'expense', 'expense_depreciation', 'expense_direct_cost', 'off_balance')
|
||||
AND (
|
||||
account.currency_id != account_move_line.company_currency_id
|
||||
OR (account.account_type IN ('asset_receivable', 'liability_payable')
|
||||
AND account_move_line.currency_id != account_move_line.company_currency_id)
|
||||
)
|
||||
AND """ + provision_test + """ (
|
||||
SELECT 1 FROM account_account_exclude_res_currency_provision
|
||||
WHERE account_account_id = account_id
|
||||
AND res_currency_id = account_move_line.currency_id
|
||||
)
|
||||
AND (%(no_exch)s)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM account_partial_reconcile part
|
||||
WHERE (part.debit_move_id = account_move_line.id OR part.credit_move_id = account_move_line.id)
|
||||
AND part.max_date <= %(dt)s
|
||||
)
|
||||
AND (account_move_line.balance != 0.0 OR account_move_line.amount_currency != 0.0)
|
||||
) subquery
|
||||
GROUP BY grouping_key
|
||||
ORDER BY grouping_key
|
||||
%(tail)s
|
||||
""",
|
||||
rate_table=custom_rate_table,
|
||||
from_refs=qry.from_clause,
|
||||
dt=report_date,
|
||||
where=qry.where_clause,
|
||||
no_exch=no_exchange_clause,
|
||||
tail=tail,
|
||||
)
|
||||
self.env.cr.execute(full_sql)
|
||||
rows = self.env.cr.dictfetchall()
|
||||
|
||||
if not current_groupby:
|
||||
return _build_result(report, rows[0] if rows else {})
|
||||
return [(r['grouping_key'], _build_result(report, r)) for r in rows]
|
||||
Reference in New Issue
Block a user