622 lines
26 KiB
Python
622 lines
26 KiB
Python
# Fusion Accounting - Deferred Revenue / Expense Report Handlers
|
|
# Computes period-by-period deferral breakdowns, generates closing entries
|
|
|
|
import calendar
|
|
from collections import defaultdict
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import models, fields, _, api, Command
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools import groupby, SQL
|
|
from odoo.addons.fusion_accounting.models.account_move import DEFERRED_DATE_MIN, DEFERRED_DATE_MAX
|
|
|
|
|
|
class FusionDeferredReportHandler(models.AbstractModel):
|
|
"""Base handler for deferred expense / revenue reports. Provides
|
|
shared domain construction, SQL queries, grouping logic, and
|
|
deferral-entry generation. Concrete sub-handlers set the report
|
|
type via ``_get_deferred_report_type``."""
|
|
|
|
_name = 'account.deferred.report.handler'
|
|
_inherit = 'account.report.custom.handler'
|
|
_description = 'Deferred Expense Report Custom Handler'
|
|
|
|
def _get_deferred_report_type(self):
|
|
raise NotImplementedError(
|
|
"Subclasses must return either 'expense' or 'revenue'."
|
|
)
|
|
|
|
# =====================================================================
|
|
# DOMAIN & QUERY HELPERS
|
|
# =====================================================================
|
|
|
|
def _get_domain(self, report, options, filter_already_generated=False, filter_not_started=False):
|
|
"""Build the search domain for deferred journal items within
|
|
the selected report period."""
|
|
base_domain = report._get_options_domain(options, "from_beginning")
|
|
if self._get_deferred_report_type() == 'expense':
|
|
acct_types = ('expense', 'expense_depreciation', 'expense_direct_cost')
|
|
else:
|
|
acct_types = ('income', 'income_other')
|
|
|
|
base_domain += [
|
|
('account_id.account_type', 'in', acct_types),
|
|
('deferred_start_date', '!=', False),
|
|
('deferred_end_date', '!=', False),
|
|
('deferred_end_date', '>=', options['date']['date_from']),
|
|
('move_id.date', '<=', options['date']['date_to']),
|
|
]
|
|
# Exclude lines that fall entirely within the period
|
|
base_domain += [
|
|
'!', '&', '&', '&', '&', '&',
|
|
('deferred_start_date', '>=', options['date']['date_from']),
|
|
('deferred_start_date', '<=', options['date']['date_to']),
|
|
('deferred_end_date', '>=', options['date']['date_from']),
|
|
('deferred_end_date', '<=', options['date']['date_to']),
|
|
('move_id.date', '>=', options['date']['date_from']),
|
|
('move_id.date', '<=', options['date']['date_to']),
|
|
]
|
|
if filter_already_generated:
|
|
base_domain += [
|
|
('deferred_end_date', '>=', options['date']['date_from']),
|
|
'!',
|
|
'&',
|
|
('move_id.deferred_move_ids.date', '=', options['date']['date_to']),
|
|
('move_id.deferred_move_ids.state', '=', 'posted'),
|
|
]
|
|
if filter_not_started:
|
|
base_domain += [('deferred_start_date', '>', options['date']['date_to'])]
|
|
return base_domain
|
|
|
|
@api.model
|
|
def _get_select(self):
|
|
"""Column expressions for the deferred-lines query."""
|
|
acct_name_expr = self.env['account.account']._field_to_sql(
|
|
'account_move_line__account_id', 'name',
|
|
)
|
|
return [
|
|
SQL("account_move_line.id AS line_id"),
|
|
SQL("account_move_line.account_id AS account_id"),
|
|
SQL("account_move_line.partner_id AS partner_id"),
|
|
SQL("account_move_line.product_id AS product_id"),
|
|
SQL("account_move_line__product_template_id.categ_id AS product_category_id"),
|
|
SQL("account_move_line.name AS line_name"),
|
|
SQL("account_move_line.deferred_start_date AS deferred_start_date"),
|
|
SQL("account_move_line.deferred_end_date AS deferred_end_date"),
|
|
SQL("account_move_line.deferred_end_date - account_move_line.deferred_start_date AS diff_days"),
|
|
SQL("account_move_line.balance AS balance"),
|
|
SQL("account_move_line.analytic_distribution AS analytic_distribution"),
|
|
SQL("account_move_line__move_id.id as move_id"),
|
|
SQL("account_move_line__move_id.name AS move_name"),
|
|
SQL("%s AS account_name", acct_name_expr),
|
|
]
|
|
|
|
def _get_lines(self, report, options, filter_already_generated=False):
|
|
"""Execute the deferred-lines query and return raw dicts."""
|
|
search_domain = self._get_domain(report, options, filter_already_generated)
|
|
qry = report._get_report_query(options, domain=search_domain, date_scope='from_beginning')
|
|
cols = SQL(', ').join(self._get_select())
|
|
|
|
full_query = SQL(
|
|
"""
|
|
SELECT %(cols)s
|
|
FROM %(from_clause)s
|
|
LEFT JOIN product_product AS account_move_line__product_id
|
|
ON account_move_line.product_id = account_move_line__product_id.id
|
|
LEFT JOIN product_template AS account_move_line__product_template_id
|
|
ON account_move_line__product_id.product_tmpl_id = account_move_line__product_template_id.id
|
|
WHERE %(where_clause)s
|
|
ORDER BY account_move_line.deferred_start_date, account_move_line.id
|
|
""",
|
|
cols=cols,
|
|
from_clause=qry.from_clause,
|
|
where_clause=qry.where_clause,
|
|
)
|
|
self.env.cr.execute(full_query)
|
|
return self.env.cr.dictfetchall()
|
|
|
|
# =====================================================================
|
|
# GROUPING HELPERS
|
|
# =====================================================================
|
|
|
|
@api.model
|
|
def _get_grouping_fields_deferred_lines(self, filter_already_generated=False, grouping_field='account_id'):
|
|
return (grouping_field,)
|
|
|
|
@api.model
|
|
def _group_by_deferred_fields(self, line, filter_already_generated=False, grouping_field='account_id'):
|
|
return tuple(
|
|
line[k] for k in self._get_grouping_fields_deferred_lines(filter_already_generated, grouping_field)
|
|
)
|
|
|
|
@api.model
|
|
def _get_grouping_fields_deferral_lines(self):
|
|
return ()
|
|
|
|
@api.model
|
|
def _group_by_deferral_fields(self, line):
|
|
return tuple(line[k] for k in self._get_grouping_fields_deferral_lines())
|
|
|
|
@api.model
|
|
def _group_deferred_amounts_by_grouping_field(
|
|
self, deferred_amounts_by_line, periods, is_reverse,
|
|
filter_already_generated=False, grouping_field='account_id',
|
|
):
|
|
"""Group deferred amounts per grouping field and compute period
|
|
totals. Returns ``(per_key_totals, aggregate_totals)``."""
|
|
grouped_iter = groupby(
|
|
deferred_amounts_by_line,
|
|
key=lambda row: self._group_by_deferred_fields(row, filter_already_generated, grouping_field),
|
|
)
|
|
per_key = {}
|
|
aggregate = {p: 0 for p in periods + ['totals_aggregated']}
|
|
multiplier = 1 if is_reverse else -1
|
|
|
|
for key, key_lines in grouped_iter:
|
|
key_lines = list(key_lines)
|
|
key_totals = self._get_current_key_totals_dict(key_lines, multiplier)
|
|
aggregate['totals_aggregated'] += key_totals['amount_total']
|
|
for period in periods:
|
|
period_val = multiplier * sum(ln[period] for ln in key_lines)
|
|
key_totals[period] = period_val
|
|
aggregate[period] += self.env.company.currency_id.round(period_val)
|
|
per_key[key] = key_totals
|
|
|
|
return per_key, aggregate
|
|
|
|
@api.model
|
|
def _get_current_key_totals_dict(self, key_lines, multiplier):
|
|
return {
|
|
'account_id': key_lines[0]['account_id'],
|
|
'product_id': key_lines[0]['product_id'],
|
|
'product_category_id': key_lines[0]['product_category_id'],
|
|
'amount_total': multiplier * sum(ln['balance'] for ln in key_lines),
|
|
'move_ids': {ln['move_id'] for ln in key_lines},
|
|
}
|
|
|
|
# =====================================================================
|
|
# REPORT DISPLAY
|
|
# =====================================================================
|
|
|
|
def _get_custom_display_config(self):
|
|
return {
|
|
'templates': {
|
|
'AccountReportFilters': 'fusion_accounting.DeferredFilters',
|
|
},
|
|
}
|
|
|
|
def _custom_options_initializer(self, report, options, previous_options):
|
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
|
|
|
per_col_group = report._split_options_per_column_group(options)
|
|
for col_dict in options['columns']:
|
|
col_opts = per_col_group[col_dict['column_group_key']]
|
|
col_dict['name'] = col_opts['date']['string']
|
|
col_dict['date_from'] = col_opts['date']['date_from']
|
|
col_dict['date_to'] = col_opts['date']['date_to']
|
|
|
|
options['columns'] = list(reversed(options['columns']))
|
|
|
|
total_col = [{
|
|
**options['columns'][0],
|
|
'name': _('Total'),
|
|
'expression_label': 'total',
|
|
'date_from': DEFERRED_DATE_MIN,
|
|
'date_to': DEFERRED_DATE_MAX,
|
|
}]
|
|
not_started_col = [{
|
|
**options['columns'][0],
|
|
'name': _('Not Started'),
|
|
'expression_label': 'not_started',
|
|
'date_from': options['columns'][-1]['date_to'],
|
|
'date_to': DEFERRED_DATE_MAX,
|
|
}]
|
|
before_col = [{
|
|
**options['columns'][0],
|
|
'name': _('Before'),
|
|
'expression_label': 'before',
|
|
'date_from': DEFERRED_DATE_MIN,
|
|
'date_to': options['columns'][0]['date_from'],
|
|
}]
|
|
later_col = [{
|
|
**options['columns'][0],
|
|
'name': _('Later'),
|
|
'expression_label': 'later',
|
|
'date_from': options['columns'][-1]['date_to'],
|
|
'date_to': DEFERRED_DATE_MAX,
|
|
}]
|
|
|
|
options['columns'] = total_col + not_started_col + before_col + options['columns'] + later_col
|
|
options['column_headers'] = []
|
|
options['deferred_report_type'] = self._get_deferred_report_type()
|
|
options['deferred_grouping_field'] = previous_options.get('deferred_grouping_field') or 'account_id'
|
|
|
|
co = self.env.company
|
|
report_type = self._get_deferred_report_type()
|
|
is_manual = (
|
|
(report_type == 'expense' and co.generate_deferred_expense_entries_method == 'manual')
|
|
or (report_type == 'revenue' and co.generate_deferred_revenue_entries_method == 'manual')
|
|
)
|
|
if is_manual:
|
|
options['buttons'].append({
|
|
'name': _('Generate entry'),
|
|
'action': 'action_generate_entry',
|
|
'sequence': 80,
|
|
'always_show': True,
|
|
})
|
|
|
|
def action_audit_cell(self, options, params):
|
|
"""Open a list of the invoices/bills and deferral entries
|
|
that underlie the clicked cell in the deferred report."""
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
col_data = next(
|
|
(c for c in options['columns']
|
|
if c['column_group_key'] == params.get('column_group_key')
|
|
and c['expression_label'] == params.get('expression_label')),
|
|
None,
|
|
)
|
|
if not col_data:
|
|
return
|
|
|
|
col_from = fields.Date.to_date(col_data['date_from'])
|
|
col_to = fields.Date.to_date(col_data['date_to'])
|
|
rpt_from = fields.Date.to_date(options['date']['date_from'])
|
|
rpt_to = fields.Date.to_date(options['date']['date_to'])
|
|
|
|
if col_data['expression_label'] in ('not_started', 'later'):
|
|
col_from = rpt_to + relativedelta(days=1)
|
|
if col_data['expression_label'] == 'before':
|
|
col_to = rpt_from - relativedelta(days=1)
|
|
|
|
_grp_model, grp_record_id = report._get_model_info_from_id(
|
|
params.get('calling_line_dict_id'),
|
|
)
|
|
|
|
source_domain = self._get_domain(
|
|
report, options,
|
|
filter_not_started=(col_data['expression_label'] == 'not_started'),
|
|
)
|
|
if grp_record_id:
|
|
source_domain.append(
|
|
(options['deferred_grouping_field'], '=', grp_record_id)
|
|
)
|
|
|
|
source_moves = self.env['account.move.line'].search(source_domain).move_id
|
|
visible_line_ids = source_moves.line_ids.ids
|
|
if col_data['expression_label'] != 'total':
|
|
visible_line_ids += source_moves.deferred_move_ids.line_ids.ids
|
|
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('Deferred Entries'),
|
|
'res_model': 'account.move.line',
|
|
'domain': [('id', 'in', visible_line_ids)],
|
|
'views': [(self.env.ref('fusion_accounting.view_deferred_entries_tree').id, 'list')],
|
|
'context': {
|
|
'search_default_pl_accounts': True,
|
|
f'search_default_{options["deferred_grouping_field"]}': grp_record_id,
|
|
'date_from': col_from,
|
|
'date_to': col_to,
|
|
'search_default_date_between': True,
|
|
'expand': True,
|
|
},
|
|
}
|
|
|
|
def _caret_options_initializer(self):
|
|
return {
|
|
'deferred_caret': [
|
|
{'name': _("Journal Items"), 'action': 'open_journal_items'},
|
|
],
|
|
}
|
|
|
|
def _customize_warnings(self, report, options, all_column_groups_expression_totals, warnings):
|
|
rpt_type = self._get_deferred_report_type()
|
|
co = self.env.company
|
|
is_manual_and_generated = (
|
|
(rpt_type == 'expense' and co.generate_deferred_expense_entries_method == 'manual'
|
|
or rpt_type == 'revenue' and co.generate_deferred_revenue_entries_method == 'manual')
|
|
and self.env['account.move'].search_count(
|
|
report._get_generated_deferral_entries_domain(options),
|
|
)
|
|
)
|
|
if is_manual_and_generated:
|
|
warnings['fusion_accounting.deferred_report_warning_already_posted'] = {
|
|
'alert_type': 'warning',
|
|
}
|
|
|
|
def open_journal_items(self, options, params):
|
|
report = self.env['account.report'].browse(options['report_id'])
|
|
rec_model, rec_id = report._get_model_info_from_id(params.get('line_id'))
|
|
item_domain = self._get_domain(report, options)
|
|
if rec_model == 'account.account' and rec_id:
|
|
item_domain += [('account_id', '=', rec_id)]
|
|
elif rec_model == 'product.product' and rec_id:
|
|
item_domain += [('product_id', '=', rec_id)]
|
|
elif rec_model == 'product.category' and rec_id:
|
|
item_domain += [('product_category_id', '=', rec_id)]
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _("Deferred Entries"),
|
|
'res_model': 'account.move.line',
|
|
'domain': item_domain,
|
|
'views': [(self.env.ref('fusion_accounting.view_deferred_entries_tree').id, 'list')],
|
|
'context': {
|
|
'search_default_group_by_move': True,
|
|
'expand': True,
|
|
},
|
|
}
|
|
|
|
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
|
|
"""Build the report lines by computing deferred amounts per
|
|
period and grouping field."""
|
|
|
|
def _format_columns(row_totals):
|
|
return [
|
|
{
|
|
**report._build_column_dict(
|
|
row_totals[(
|
|
fields.Date.to_date(col['date_from']),
|
|
fields.Date.to_date(col['date_to']),
|
|
col['expression_label'],
|
|
)],
|
|
col,
|
|
options=options,
|
|
currency=self.env.company.currency_id,
|
|
),
|
|
'auditable': True,
|
|
}
|
|
for col in options['columns']
|
|
]
|
|
|
|
raw_lines = self._get_lines(report, options)
|
|
col_periods = [
|
|
(
|
|
fields.Date.from_string(c['date_from']),
|
|
fields.Date.from_string(c['date_to']),
|
|
c['expression_label'],
|
|
)
|
|
for c in options['columns']
|
|
]
|
|
|
|
per_line_amounts = self.env['account.move']._get_deferred_amounts_by_line(
|
|
raw_lines, col_periods, self._get_deferred_report_type(),
|
|
)
|
|
per_key, totals = self._group_deferred_amounts_by_grouping_field(
|
|
deferred_amounts_by_line=per_line_amounts,
|
|
periods=col_periods,
|
|
is_reverse=(self._get_deferred_report_type() == 'expense'),
|
|
filter_already_generated=False,
|
|
grouping_field=options['deferred_grouping_field'],
|
|
)
|
|
|
|
output_lines = []
|
|
grp_model_name = self.env['account.move.line'][options['deferred_grouping_field']]._name
|
|
for key_totals in per_key.values():
|
|
grp_record = self.env[grp_model_name].browse(
|
|
key_totals[options['deferred_grouping_field']]
|
|
)
|
|
field_desc = self.env['account.move.line'][options['deferred_grouping_field']]._description
|
|
if options['deferred_grouping_field'] == 'product_id':
|
|
field_desc = _("Product")
|
|
display_label = grp_record.display_name or _("(No %s)", field_desc)
|
|
output_lines.append((0, {
|
|
'id': report._get_generic_line_id(grp_model_name, grp_record.id),
|
|
'name': display_label,
|
|
'caret_options': 'deferred_caret',
|
|
'level': 1,
|
|
'columns': _format_columns(key_totals),
|
|
}))
|
|
|
|
if per_key:
|
|
output_lines.append((0, {
|
|
'id': report._get_generic_line_id(None, None, markup='total'),
|
|
'name': 'Total',
|
|
'level': 1,
|
|
'columns': _format_columns(totals),
|
|
}))
|
|
|
|
return output_lines
|
|
|
|
# =====================================================================
|
|
# ENTRY GENERATION
|
|
# =====================================================================
|
|
|
|
def action_generate_entry(self, options):
|
|
new_moves = self._generate_deferral_entry(options)
|
|
return {
|
|
'name': _('Deferred Entries'),
|
|
'type': 'ir.actions.act_window',
|
|
'views': [(False, "list"), (False, "form")],
|
|
'domain': [('id', 'in', new_moves.ids)],
|
|
'res_model': 'account.move',
|
|
'context': {
|
|
'search_default_group_by_move': True,
|
|
'expand': True,
|
|
},
|
|
'target': 'current',
|
|
}
|
|
|
|
def _generate_deferral_entry(self, options):
|
|
"""Create the deferral move and its reversal for the selected period."""
|
|
rpt_type = self._get_deferred_report_type()
|
|
co = self.env.company
|
|
target_journal = (
|
|
co.deferred_expense_journal_id if rpt_type == "expense"
|
|
else co.deferred_revenue_journal_id
|
|
)
|
|
if not target_journal:
|
|
raise UserError(_("Please configure the deferred journal in accounting settings."))
|
|
|
|
period_start = fields.Date.to_date(DEFERRED_DATE_MIN)
|
|
period_end = fields.Date.from_string(options['date']['date_to'])
|
|
last_day = calendar.monthrange(period_end.year, period_end.month)[1]
|
|
if period_end.day != last_day:
|
|
raise UserError(
|
|
_("Entries can only be generated for periods ending on the last day of a month.")
|
|
)
|
|
if co._get_violated_lock_dates(period_end, False, target_journal):
|
|
raise UserError(_("Entries cannot be generated for a locked period."))
|
|
|
|
options['all_entries'] = False
|
|
report = self.env["account.report"].browse(options["report_id"])
|
|
self.env['account.move.line'].flush_model()
|
|
|
|
raw_lines = self._get_lines(report, options, filter_already_generated=True)
|
|
period_info = self.env['account.report']._get_dates_period(
|
|
period_start, period_end, 'range', period_type='month',
|
|
)
|
|
entry_ref = _("Grouped Deferral Entry of %s", period_info['string'])
|
|
reversal_ref = _("Reversal of Grouped Deferral Entry of %s", period_info['string'])
|
|
|
|
deferral_account = (
|
|
co.deferred_expense_account_id if rpt_type == 'expense'
|
|
else co.deferred_revenue_account_id
|
|
)
|
|
move_cmds, orig_move_ids = self._get_deferred_lines(
|
|
raw_lines, deferral_account,
|
|
(period_start, period_end, 'current'),
|
|
rpt_type == 'expense', entry_ref,
|
|
)
|
|
if not move_cmds:
|
|
raise UserError(_("No entry to generate."))
|
|
|
|
deferral_move = self.env['account.move'].with_context(
|
|
skip_account_deprecation_check=True,
|
|
).create({
|
|
'move_type': 'entry',
|
|
'deferred_original_move_ids': [Command.set(orig_move_ids)],
|
|
'journal_id': target_journal.id,
|
|
'date': period_end,
|
|
'auto_post': 'at_date',
|
|
'ref': entry_ref,
|
|
})
|
|
deferral_move.write({'line_ids': move_cmds})
|
|
|
|
reversal = deferral_move._reverse_moves()
|
|
reversal.write({
|
|
'date': deferral_move.date + relativedelta(days=1),
|
|
'ref': reversal_ref,
|
|
})
|
|
reversal.line_ids.name = reversal_ref
|
|
|
|
combined = deferral_move + reversal
|
|
self.env.cr.execute_values("""
|
|
INSERT INTO account_move_deferred_rel(original_move_id, deferred_move_id)
|
|
VALUES %s
|
|
ON CONFLICT DO NOTHING
|
|
""", [
|
|
(orig_id, dm.id)
|
|
for orig_id in orig_move_ids
|
|
for dm in combined
|
|
])
|
|
combined._post(soft=True)
|
|
return combined
|
|
|
|
@api.model
|
|
def _get_deferred_lines(self, raw_lines, deferral_account, period, is_reverse, label):
|
|
"""Compute the journal-item commands for a deferral entry and
|
|
return ``(line_commands, original_move_ids)``."""
|
|
if not deferral_account:
|
|
raise UserError(_("Please configure the deferred accounts in accounting settings."))
|
|
|
|
per_line_amounts = self.env['account.move']._get_deferred_amounts_by_line(
|
|
raw_lines, [period], is_reverse,
|
|
)
|
|
per_key, agg_totals = self._group_deferred_amounts_by_grouping_field(
|
|
per_line_amounts, [period], is_reverse, filter_already_generated=True,
|
|
)
|
|
if agg_totals['totals_aggregated'] == agg_totals[period]:
|
|
return [], set()
|
|
|
|
# Build per-key analytic distributions
|
|
dist_per_key = defaultdict(lambda: defaultdict(float))
|
|
deferral_dist = defaultdict(lambda: defaultdict(float))
|
|
for ln in raw_lines:
|
|
if not ln['analytic_distribution']:
|
|
continue
|
|
total_ratio = (
|
|
(ln['balance'] / agg_totals['totals_aggregated'])
|
|
if agg_totals['totals_aggregated'] else 0
|
|
)
|
|
key_data = per_key.get(self._group_by_deferred_fields(ln, True))
|
|
key_ratio = (
|
|
(ln['balance'] / key_data['amount_total'])
|
|
if key_data and key_data['amount_total'] else 0
|
|
)
|
|
for analytic_id, pct in ln['analytic_distribution'].items():
|
|
dist_per_key[self._group_by_deferred_fields(ln, True)][analytic_id] += pct * key_ratio
|
|
deferral_dist[self._group_by_deferral_fields(ln)][analytic_id] += pct * total_ratio
|
|
|
|
currency = self.env.company.currency_id
|
|
balance_remainder = 0
|
|
entry_lines = []
|
|
source_move_ids = set()
|
|
sign = 1 if is_reverse else -1
|
|
|
|
for key, kv in per_key.items():
|
|
for amt in (-kv['amount_total'], kv[period]):
|
|
if amt != 0 and kv[period] != kv['amount_total']:
|
|
source_move_ids |= kv['move_ids']
|
|
adjusted_balance = currency.round(sign * amt)
|
|
entry_lines.append(Command.create(
|
|
self.env['account.move.line']._get_deferred_lines_values(
|
|
account_id=kv['account_id'],
|
|
balance=adjusted_balance,
|
|
ref=label,
|
|
analytic_distribution=dist_per_key[key] or False,
|
|
line=kv,
|
|
)
|
|
))
|
|
balance_remainder += adjusted_balance
|
|
|
|
# Group deferral-account lines
|
|
grouped_values = {
|
|
k: list(v)
|
|
for k, v in groupby(per_key.values(), key=self._group_by_deferral_fields)
|
|
}
|
|
deferral_lines = []
|
|
for key, key_items in grouped_values.items():
|
|
key_balance = 0
|
|
for item in key_items:
|
|
if item[period] != item['amount_total']:
|
|
key_balance += currency.round(
|
|
sign * (item['amount_total'] - item[period])
|
|
)
|
|
deferral_lines.append(Command.create(
|
|
self.env['account.move.line']._get_deferred_lines_values(
|
|
account_id=deferral_account.id,
|
|
balance=key_balance,
|
|
ref=label,
|
|
analytic_distribution=deferral_dist[key] or False,
|
|
line=key_items[0],
|
|
)
|
|
))
|
|
balance_remainder += key_balance
|
|
|
|
if not currency.is_zero(balance_remainder):
|
|
deferral_lines.append(Command.create({
|
|
'account_id': deferral_account.id,
|
|
'balance': -balance_remainder,
|
|
'name': label,
|
|
}))
|
|
|
|
return entry_lines + deferral_lines, source_move_ids
|
|
|
|
|
|
class FusionDeferredExpenseHandler(models.AbstractModel):
|
|
_name = 'account.deferred.expense.report.handler'
|
|
_inherit = 'account.deferred.report.handler'
|
|
_description = 'Deferred Expense Custom Handler'
|
|
|
|
def _get_deferred_report_type(self):
|
|
return 'expense'
|
|
|
|
|
|
class FusionDeferredRevenueHandler(models.AbstractModel):
|
|
_name = 'account.deferred.revenue.report.handler'
|
|
_inherit = 'account.deferred.report.handler'
|
|
_description = 'Deferred Revenue Custom Handler'
|
|
|
|
def _get_deferred_report_type(self):
|
|
return 'revenue'
|