Initial commit
This commit is contained in:
621
Fusion Accounting/models/account_deferred_reports.py
Normal file
621
Fusion Accounting/models/account_deferred_reports.py
Normal file
@@ -0,0 +1,621 @@
|
||||
# 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'
|
||||
Reference in New Issue
Block a user