mis-builder/mis_builder/models/mis_report_instance.py
2024-05-03 10:57:01 +02:00

923 lines
35 KiB
Python

# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
# Copyright 2020 CorporateHub (https://corporatehub.eu)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import ast
import datetime
import logging
from dateutil.relativedelta import relativedelta
from flectra import _, api, fields, models
from flectra.exceptions import UserError, ValidationError
from .aep import AccountingExpressionProcessor as AEP
from .expression_evaluator import ExpressionEvaluator
_logger = logging.getLogger(__name__)
SRC_ACTUALS = "actuals"
SRC_ACTUALS_ALT = "actuals_alt"
SRC_CMPCOL = "cmpcol"
SRC_SUMCOL = "sumcol"
MODE_NONE = "none"
MODE_FIX = "fix"
MODE_REL = "relative"
class DateFilterRequired(ValidationError):
pass
class DateFilterForbidden(ValidationError):
pass
class MisReportInstancePeriodSum(models.Model):
_name = "mis.report.instance.period.sum"
_description = "MIS Report Instance Period Sum"
period_id = fields.Many2one(
comodel_name="mis.report.instance.period",
string="Parent column",
ondelete="cascade",
required=True,
)
period_to_sum_id = fields.Many2one(
comodel_name="mis.report.instance.period",
string="Column",
ondelete="restrict",
required=True,
)
sign = fields.Selection([("+", "+"), ("-", "-")], required=True, default="+")
@api.constrains("period_id", "period_to_sum_id")
def _check_period_to_sum(self):
for rec in self:
if rec.period_id == rec.period_to_sum_id:
raise ValidationError(
_("You cannot sum period %s with itself.") % rec.period_id.name
)
class MisReportInstancePeriod(models.Model):
"""A MIS report instance has the logic to compute
a report template for a given date period.
Periods have a duration (day, week, fiscal period) and
are defined as an offset relative to a pivot date.
"""
@api.depends(
"report_instance_id.pivot_date",
"report_instance_id.comparison_mode",
"date_range_type_id",
"type",
"offset",
"duration",
"mode",
"manual_date_from",
"manual_date_to",
"is_ytd",
)
def _compute_dates(self):
for record in self:
record.date_from = False
record.date_to = False
record.valid = False
report = record.report_instance_id
d = fields.Date.from_string(report.pivot_date)
if not report.comparison_mode:
record.date_from = report.date_from
record.date_to = report.date_to
record.valid = record.date_from and record.date_to
elif record.mode == MODE_NONE:
record.date_from = False
record.date_to = False
record.valid = True
elif record.mode == MODE_FIX:
record.date_from = record.manual_date_from
record.date_to = record.manual_date_to
record.valid = record.date_from and record.date_to
elif record.mode == MODE_REL and record.type == "d":
date_from = d + datetime.timedelta(days=record.offset)
date_to = date_from + datetime.timedelta(days=record.duration - 1)
record.date_from = fields.Date.to_string(date_from)
record.date_to = fields.Date.to_string(date_to)
record.valid = True
elif record.mode == MODE_REL and record.type == "w":
date_from = d - datetime.timedelta(d.weekday())
date_from = date_from + datetime.timedelta(days=record.offset * 7)
date_to = date_from + datetime.timedelta(days=(7 * record.duration) - 1)
record.date_from = fields.Date.to_string(date_from)
record.date_to = fields.Date.to_string(date_to)
record.valid = True
elif record.mode == MODE_REL and record.type == "m":
date_from = d.replace(day=1)
date_from = date_from + relativedelta(months=record.offset)
date_to = (
date_from
+ relativedelta(months=record.duration - 1)
+ relativedelta(day=31)
)
record.date_from = fields.Date.to_string(date_from)
record.date_to = fields.Date.to_string(date_to)
record.valid = True
elif record.mode == MODE_REL and record.type == "y":
date_from = d.replace(month=1, day=1)
date_from = date_from + relativedelta(years=record.offset)
date_to = date_from + relativedelta(years=record.duration - 1)
date_to = date_to.replace(month=12, day=31)
record.date_from = fields.Date.to_string(date_from)
record.date_to = fields.Date.to_string(date_to)
record.valid = True
elif record.mode == MODE_REL and record.type == "date_range":
date_range_obj = record.env["date.range"]
current_periods = date_range_obj.search(
[
("type_id", "=", record.date_range_type_id.id),
("date_start", "<=", d),
("date_end", ">=", d),
"|",
("company_id", "=", False),
(
"company_id",
"in",
record.report_instance_id.query_company_ids.ids,
),
]
)
if current_periods:
# TODO we take the first date range we found as current
# this may be surprising if several companies
# have overlapping date ranges with different dates
current_period = current_periods[0]
all_periods = date_range_obj.search(
[
("type_id", "=", current_period.type_id.id),
("company_id", "=", current_period.company_id.id),
],
order="date_start",
)
p = all_periods.ids.index(current_period.id) + record.offset
if p >= 0 and p + record.duration <= len(all_periods):
periods = all_periods[p : p + record.duration]
record.date_from = periods[0].date_start
record.date_to = periods[-1].date_end
record.valid = True
if record.mode == MODE_REL and record.valid and record.is_ytd:
record.date_from = fields.Date.from_string(record.date_to).replace(
day=1, month=1
)
_name = "mis.report.instance.period"
_description = "MIS Report Instance Period"
name = fields.Char(required=True, string="Label", translate=True)
mode = fields.Selection(
[
(MODE_FIX, "Fixed dates"),
(MODE_REL, "Relative to report base date"),
(MODE_NONE, "No date filter"),
],
required=True,
default=MODE_FIX,
)
type = fields.Selection(
[
("d", _("Day")),
("w", _("Week")),
("m", _("Month")),
("y", _("Year")),
("date_range", _("Date Range")),
],
string="Period type",
)
is_ytd = fields.Boolean(
default=False,
string="Year to date",
help="Forces the start date to Jan 1st of the relevant year",
)
date_range_type_id = fields.Many2one(
comodel_name="date.range.type",
string="Date Range Type",
domain=[("allow_overlap", "=", False)],
)
offset = fields.Integer(help="Offset from current period", default=-1)
duration = fields.Integer(help="Number of periods", default=1)
date_from = fields.Date(compute="_compute_dates", string="From (computed)")
date_to = fields.Date(compute="_compute_dates", string="To (computed)")
manual_date_from = fields.Date(string="From")
manual_date_to = fields.Date(string="To")
date_range_id = fields.Many2one(comodel_name="date.range", string="Date Range")
valid = fields.Boolean(compute="_compute_dates", type="boolean")
sequence = fields.Integer(default=100)
report_instance_id = fields.Many2one(
comodel_name="mis.report.instance",
string="Report Instance",
required=True,
ondelete="cascade",
)
report_id = fields.Many2one(related="report_instance_id.report_id")
normalize_factor = fields.Integer(
string="Factor",
help="Factor to use to normalize the period (used in comparison",
default=1,
)
subkpi_ids = fields.Many2many("mis.report.subkpi", string="Sub KPI Filter")
source = fields.Selection(
[
(SRC_ACTUALS, "Actuals"),
(SRC_ACTUALS_ALT, "Actuals (alternative)"),
(SRC_SUMCOL, "Sum columns"),
(SRC_CMPCOL, "Compare columns"),
],
default=SRC_ACTUALS,
required=True,
help="Actuals: current data, from accounting and other queries.\n"
"Actuals (alternative): current data from an "
"alternative source (eg a database view providing look-alike "
"account move lines).\n"
"Sum columns: summation (+/-) of other columns.\n"
"Compare to column: compare to other column.\n",
)
source_aml_model_id = fields.Many2one(
comodel_name="ir.model",
string="Move lines source",
domain=[
("field_id.name", "=", "debit"),
("field_id.name", "=", "credit"),
("field_id.name", "=", "account_id"),
("field_id.name", "=", "date"),
("field_id.name", "=", "company_id"),
("field_id.model_id.model", "!=", "account.move.line"),
],
compute="_compute_source_aml_model_id",
store=True,
readonly=False,
help="A 'move line like' model, ie having at least debit, credit, "
"date, account_id and company_id fields.",
)
source_aml_model_name = fields.Char(
string="Move lines source model name", related="source_aml_model_id.model"
)
source_sumcol_ids = fields.One2many(
comodel_name="mis.report.instance.period.sum",
inverse_name="period_id",
string="Columns to sum",
)
source_sumcol_accdet = fields.Boolean(string="Sum account details")
source_cmpcol_from_id = fields.Many2one(
comodel_name="mis.report.instance.period", string="versus"
)
source_cmpcol_to_id = fields.Many2one(
comodel_name="mis.report.instance.period", string="Compare"
)
allowed_cmpcol_ids = fields.Many2many(
comodel_name="mis.report.instance.period", compute="_compute_allowed_cmpcol_ids"
)
analytic_domain = fields.Text(
default="[]",
help="A domain to additionally filter move lines considered in this column.",
)
_order = "sequence, id"
_sql_constraints = [
("duration", "CHECK (duration>0)", "Wrong duration, it must be positive!"),
(
"normalize_factor",
"CHECK (normalize_factor>0)",
"Wrong normalize factor, it must be positive!",
),
(
"name_unique",
"unique(name, report_instance_id)",
"Period name should be unique by report",
),
]
@api.depends("source", "report_instance_id.report_id.move_lines_source")
def _compute_source_aml_model_id(self):
for record in self:
if record.source == SRC_ACTUALS:
if not record.report_instance_id.report_id:
raise UserError(
_(
"Please select a report template and/or "
"save the report before adding columns."
)
)
# use the default model defined on the report template
record.source_aml_model_id = (
record.report_instance_id.report_id.move_lines_source
)
elif record.source in (SRC_SUMCOL, SRC_CMPCOL):
record.source_aml_model_id = False
elif record.source == SRC_ACTUALS_ALT:
pass # let the user choose
@api.depends("report_instance_id")
def _compute_allowed_cmpcol_ids(self):
"""Compute actual records while in NewId context"""
for record in self:
record.allowed_cmpcol_ids = record.report_instance_id.period_ids - record
@api.constrains("source_aml_model_id")
def _check_source_aml_model_id(self):
for record in self:
if record.source_aml_model_id:
record_model = record.source_aml_model_id.field_id.filtered(
lambda r: r.name == "account_id"
).relation
report_account_model = record.report_id.account_model
if record_model != report_account_model:
raise ValidationError(
_(
"Actual (alternative) models used in columns must "
"have the same account model in the Account field and must "
"be the same defined in the "
"report template: %s"
)
% report_account_model
)
@api.onchange("date_range_id")
def _onchange_date_range(self):
if self.date_range_id:
self.manual_date_from = self.date_range_id.date_start
self.manual_date_to = self.date_range_id.date_end
@api.onchange("manual_date_from", "manual_date_to")
def _onchange_dates(self):
if self.date_range_id:
if (
self.manual_date_from != self.date_range_id.date_start
or self.manual_date_to != self.date_range_id.date_end
):
self.date_range_id = False
@api.onchange("source")
def _onchange_source(self):
if self.source in (SRC_SUMCOL, SRC_CMPCOL):
self.mode = MODE_NONE
# Dirty hack to solve bug https://github.com/OCA/mis-builder/issues/393
if self.source and not self.report_instance_id.id:
self.report_instance_id = self.report_instance_id._origin.id
def _get_additional_move_line_filter(self):
"""Prepare a filter to apply on all move lines
This filter will be applied with a AND operator on all accounting expression
domains. This hook is intended to be inherited, and is useful to implement
filtering on analytic dimensions or operational units.
The default filter is obtained from the report-instance-level
``_get_filter_domain`` method, extended with a per column analytic domain.
Returns an Flectra domain expression (a python list) compatible with
account.move.line.
"""
self.ensure_one()
if not self.source_aml_model_name:
# This column does not have a move line source, so this additional move line
# filter does not apply
return []
# First get the report-level filter domain.
domain = self.report_instance_id._get_filter_domain(self.source_aml_model_name)
if self.analytic_domain:
# Then extend it with the column-level analytic domain.
domain.extend(ast.literal_eval(self.analytic_domain))
return domain
def _get_additional_query_filter(self, query):
"""Prepare an additional filter to apply on the query
This filter is combined to the query domain with a AND
operator. This hook is intended
to be inherited, and is useful to implement filtering
on analytic dimensions or operational units.
Returns an Flectra domain expression (a python list)
compatible with the model of the query."""
self.ensure_one()
return []
@api.constrains("mode", "source")
def _check_mode_source(self):
for rec in self:
if rec.source in (SRC_ACTUALS, SRC_ACTUALS_ALT):
if rec.mode == MODE_NONE:
raise DateFilterRequired(
_("A date filter is mandatory for this source " "in column %s.")
% rec.name
)
elif rec.source in (SRC_SUMCOL, SRC_CMPCOL):
if rec.mode != MODE_NONE:
raise DateFilterForbidden(
_("No date filter is allowed for this source " "in column %s.")
% rec.name
)
@api.constrains("source", "source_cmpcol_from_id", "source_cmpcol_to_id")
def _check_source_cmpcol(self):
for rec in self:
if rec.source == SRC_CMPCOL:
if not rec.source_cmpcol_from_id or not rec.source_cmpcol_to_id:
raise ValidationError(
_("Please provide both columns to compare in %s.") % rec.name
)
if rec.source_cmpcol_from_id == rec or rec.source_cmpcol_to_id == rec:
raise ValidationError(
_("Column %s cannot be compared to itrec.") % rec.name
)
if (
rec.source_cmpcol_from_id.report_instance_id
!= rec.report_instance_id
or rec.source_cmpcol_to_id.report_instance_id
!= rec.report_instance_id
):
raise ValidationError(
_("Columns to compare must belong to the same report " "in %s")
% rec.name
)
def copy_data(self, default=None):
if self.source == SRC_CMPCOL:
# While duplicating a MIS report instance, comparison columns are
# ignored because they would raise an error, as they keep the old
# `source_cmpcol_from_id` and `source_cmpcol_to_id` from the
# original record.
return [
False,
]
return super().copy_data(default=default)
class MisReportInstance(models.Model):
"""The MIS report instance combines everything to compute
a MIS report template for a set of periods."""
@api.depends("date")
def _compute_pivot_date(self):
for record in self:
if self.env.context.get("mis_pivot_date"):
record.pivot_date = self.env.context.get("mis_pivot_date")
elif record.date:
record.pivot_date = record.date
else:
record.pivot_date = fields.Date.context_today(record)
_name = "mis.report.instance"
_description = "MIS Report Instance"
name = fields.Char(required=True, translate=True)
description = fields.Char(related="report_id.description", readonly=True)
date = fields.Date(
string="Base date", help="Report base date " "(leave empty to use current date)"
)
pivot_date = fields.Date(compute="_compute_pivot_date")
report_id = fields.Many2one("mis.report", required=True, string="Report")
period_ids = fields.One2many(
comodel_name="mis.report.instance.period",
inverse_name="report_instance_id",
required=True,
string="Periods",
copy=True,
)
target_move = fields.Selection(
[("posted", "All Posted Entries"), ("all", "All Entries")],
string="Target Moves",
required=True,
default="posted",
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Allowed company",
default=lambda self: self.env.company,
required=False,
)
multi_company = fields.Boolean(
string="Multiple companies",
help="Check if you wish to specify several companies to be searched for data.",
default=False,
)
company_ids = fields.Many2many(
comodel_name="res.company",
string="Allowed companies",
help="Select companies for which data will be searched.",
)
query_company_ids = fields.Many2many(
string="Effective companies",
comodel_name="res.company",
compute="_compute_query_company_ids",
help="Companies for which data will be searched.",
)
currency_id = fields.Many2one(
comodel_name="res.currency",
string="Currency",
help="Select target currency for the report. "
"Required if companies have different currencies.",
required=False,
)
landscape_pdf = fields.Boolean(string="Landscape PDF")
no_auto_expand_accounts = fields.Boolean(string="Disable account details expansion")
display_columns_description = fields.Boolean(
help="Display the date range details in the column headers."
)
comparison_mode = fields.Boolean(
compute="_compute_comparison_mode", inverse="_inverse_comparison_mode"
)
date_range_id = fields.Many2one(comodel_name="date.range", string="Date Range")
date_from = fields.Date(string="From")
date_to = fields.Date(string="To")
temporary = fields.Boolean(default=False)
source_aml_model_id = fields.Many2one(
related="report_id.move_lines_source",
readonly=True,
)
source_aml_model_name = fields.Char(
related="source_aml_model_id.model",
related_sudo=True,
readonly=True,
)
analytic_domain = fields.Text(
default="[]",
help=(
"A domain to additionally filter move lines considered in this report. "
"Caution: when using different move line sources in different columns, "
"such as budgets by account, "
"make sure to use only fields that are available in "
"all move line sources."
),
)
widget_show_filters = fields.Boolean(
default=True,
string="Show filters box",
help="Show the filter bar in the report widget.",
)
widget_show_settings_button = fields.Boolean(
default=False,
string="Show settings button",
help="Show the settings button in the report widget.",
)
widget_show_pivot_date = fields.Boolean(
default=False,
string="Show Pivot Date",
help="Show the Pivot Date in the report widget filter bar.",
)
widget_search_view_id = fields.Many2one(
comodel_name="ir.ui.view",
domain='[("type", "=", "search"), ("model", "=", source_aml_model_name)]',
compute="_compute_widget_search_view_id",
store=True,
readonly=False,
string="Filter box search view",
help="Search view to customize the filter box in the report widget.",
)
@api.depends("report_id.move_lines_source")
def _compute_widget_search_view_id(self):
for rec in self:
rec.widget_search_view_id = (
self.env["ir.ui.view"]
.sudo()
.search(
[
("type", "=", "search"),
("model", "=", rec.report_id.move_lines_source.model),
],
limit=1,
)
)
@api.onchange("multi_company")
def _onchange_company(self):
if self.multi_company:
self.company_ids |= self.company_id
self.company_id = False
else:
prev = self.company_ids.ids
company = False
if self.env.company.id in prev or not prev:
company = self.env.company
else:
for c_id in prev:
if c_id in self.env.companies.ids:
company = self.env["res.company"].browse(c_id)
break
self.company_id = company
self.company_ids = False
@api.depends("multi_company", "company_id", "company_ids")
@api.depends_context("allowed_company_ids")
def _compute_query_company_ids(self):
for rec in self:
if rec.multi_company:
if not rec.company_ids:
rec.query_company_ids = self.env.companies
else:
rec.query_company_ids = rec.company_ids & self.env.companies
else:
rec.query_company_ids = rec.company_id or self.env.company
@api.model
def get_filter_descriptions(self):
return []
def save_report(self):
self.ensure_one()
self.write({"temporary": False})
xmlid = "mis_builder.mis_report_instance_view_action"
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
view = self.env.ref("mis_builder.mis_report_instance_view_form")
action.update({"views": [(view.id, "form")], "res_id": self.id})
return action
@api.model
def _vacuum_report(self, hours=24):
clear_date = fields.Datetime.to_string(
datetime.datetime.now() - datetime.timedelta(hours=hours)
)
reports = self.search(
[("write_date", "<", clear_date), ("temporary", "=", True)]
)
_logger.debug("Vacuum %s Temporary MIS Builder Report", len(reports))
return reports.unlink()
def copy(self, default=None):
self.ensure_one()
default = dict(default or {})
default["name"] = _("%s (copy)") % self.name
return super().copy(default)
def _format_date(self, date):
# format date following user language
lang_model = self.env["res.lang"]
lang = lang_model._lang_get(self.env.user.lang)
date_format = lang.date_format
return datetime.datetime.strftime(fields.Date.from_string(date), date_format)
@api.depends("date_from")
def _compute_comparison_mode(self):
for instance in self:
instance.comparison_mode = bool(instance.period_ids) and not bool(
instance.date_from
)
def _inverse_comparison_mode(self):
for record in self:
if not record.comparison_mode:
if not record.date_from:
record.date_from = fields.Date.context_today(self)
if not record.date_to:
record.date_to = fields.Date.context_today(self)
record.period_ids.unlink()
record.write({"period_ids": [(0, 0, {"name": "Default"})]})
else:
record.date_from = None
record.date_to = None
@api.onchange("date_range_id")
def _onchange_date_range(self):
if self.date_range_id:
self.date_from = self.date_range_id.date_start
self.date_to = self.date_range_id.date_end
@api.onchange("date_from", "date_to")
def _onchange_dates(self):
if self.date_range_id:
if (
self.date_from != self.date_range_id.date_start
or self.date_to != self.date_range_id.date_end
):
self.date_range_id = False
def _add_analytic_filters_to_context(self, context):
self.ensure_one()
context["mis_analytic_domain"] = ast.literal_eval(self.analytic_domain)
def _get_filter_domain(self, source_aml_model_name):
"""Return the domain to filter the source move lines.
It combines
- the draft/posted filter (if the move line source has a parent_state
field).
- the analytic domain field configured on this report instance
- a mis_analytic_domain obtained from the context (typically populated
by the mis builder widget)
"""
domain = []
# draft/posted filter
domain.extend(
self.report_id._get_target_move_domain(
self.target_move, source_aml_model_name
)
)
# report-level analytic domain filter
domain.extend(ast.literal_eval(self.analytic_domain))
# contextual analytic domain filter
domain.extend(self.env.context.get("mis_analytic_domain", []))
return domain
@api.model
def get_views(self, views, options=None):
"""
Override to get correct form view on dashboard
"""
context = self.env.context
if (
context.get("from_dashboard")
and context.get("active_model") == "mis.report.instance"
):
view_id = self.env.ref(
"mis_builder." "mis_report_instance_result_view_form"
)
mis_report_form_view = view_id and [view_id.id, "form"]
for view in views:
if view and view[1] == "form":
views.remove(view)
views.append(mis_report_form_view)
result = super().get_views(views, options)
return result
def preview(self):
self.ensure_one()
view_id = self.env.ref("mis_builder." "mis_report_instance_result_view_form")
return {
"type": "ir.actions.act_window",
"res_model": "mis.report.instance",
"res_id": self.id,
"view_mode": "form",
"view_id": view_id.id,
"target": "current",
"context": self.env.context,
}
def print_pdf(self):
self.ensure_one()
return (
self.env.ref("mis_builder.qweb_pdf_export")
.with_context(landscape=self.landscape_pdf)
.report_action(self, data=dict(dummy=True)) # required to propagate context
)
def export_xls(self):
self.ensure_one()
return self.env.ref("mis_builder.xls_export").report_action(
self, data=dict(dummy=True)
) # required to propagate context
def display_settings(self):
assert len(self.ids) <= 1
view_id = self.env.ref("mis_builder.mis_report_instance_view_form")
return {
"type": "ir.actions.act_window",
"res_model": "mis.report.instance",
"res_id": self.id if self.id else False,
"view_mode": "form",
"views": [(view_id.id, "form")],
"view_id": view_id.id,
"target": "current",
}
def _add_column_move_lines(self, aep, kpi_matrix, period, label, description):
if not period.date_from or not period.date_to:
raise UserError(
_("Column %s with move lines source must have from/to dates.")
% (period.name,)
)
expression_evaluator = ExpressionEvaluator(
aep,
period.date_from,
period.date_to,
period._get_additional_move_line_filter(),
period.source_aml_model_name,
)
self.report_id._declare_and_compute_period(
expression_evaluator,
kpi_matrix,
period.id,
label,
description,
period.subkpi_ids,
period._get_additional_query_filter,
no_auto_expand_accounts=self.no_auto_expand_accounts,
)
def _add_column_sumcol(self, aep, kpi_matrix, period, label, description):
kpi_matrix.declare_sum(
period.id,
[(c.sign, c.period_to_sum_id.id) for c in period.source_sumcol_ids],
label,
description,
period.source_sumcol_accdet,
)
def _add_column_cmpcol(self, aep, kpi_matrix, period, label, description):
kpi_matrix.declare_comparison(
period.id,
period.source_cmpcol_to_id.id,
period.source_cmpcol_from_id.id,
label,
description,
)
def _add_column(self, aep, kpi_matrix, period, label, description):
if period.source == SRC_ACTUALS:
return self._add_column_move_lines(
aep, kpi_matrix, period, label, description
)
elif period.source == SRC_ACTUALS_ALT:
return self._add_column_move_lines(
aep, kpi_matrix, period, label, description
)
elif period.source == SRC_SUMCOL:
return self._add_column_sumcol(aep, kpi_matrix, period, label, description)
elif period.source == SRC_CMPCOL:
return self._add_column_cmpcol(aep, kpi_matrix, period, label, description)
def _compute_matrix(self):
"""Compute a report and return a KpiMatrix.
The key attribute of the matrix columns (KpiMatrixCol)
is guaranteed to be the id of the mis.report.instance.period.
"""
self.ensure_one()
aep = self.report_id._prepare_aep(self.query_company_ids, self.currency_id)
kpi_matrix = self.report_id.prepare_kpi_matrix(self.multi_company)
for period in self.period_ids:
description = None
if period.mode == MODE_NONE:
pass
elif not self.display_columns_description:
pass
elif period.date_from == period.date_to and period.date_from:
description = self._format_date(period.date_from)
elif period.date_from and period.date_to:
date_from = self._format_date(period.date_from)
date_to = self._format_date(period.date_to)
description = _(
"from %(date_from)s to %(date_to)s",
date_from=date_from,
date_to=date_to,
)
self._add_column(aep, kpi_matrix, period, period.name, description)
kpi_matrix.compute_comparisons()
kpi_matrix.compute_sums()
return kpi_matrix
def compute(self):
self.ensure_one()
kpi_matrix = self._compute_matrix()
return kpi_matrix.as_dict()
def drilldown(self, arg):
self.ensure_one()
period_id = arg.get("period_id")
expr = arg.get("expr")
account_id = arg.get("account_id")
if period_id and expr and AEP.has_account_var(expr):
period = self.env["mis.report.instance.period"].browse(period_id)
aep = AEP(
self.query_company_ids, self.currency_id, self.report_id.account_model
)
aep.parse_expr(expr)
aep.done_parsing()
domain = aep.get_aml_domain_for_expr(
expr,
period.date_from,
period.date_to,
account_id,
)
domain.extend(period._get_additional_move_line_filter())
return {
"name": self._get_drilldown_action_name(arg),
"domain": domain,
"type": "ir.actions.act_window",
"res_model": period.source_aml_model_name,
"views": [[False, "list"], [False, "form"]],
"view_mode": "list",
"target": "current",
"context": {"active_test": False},
}
else:
return False
def _get_drilldown_action_name(self, arg):
kpi_id = arg.get("kpi_id")
kpi = self.env["mis.report.kpi"].browse(kpi_id)
period_id = arg.get("period_id")
period = self.env["mis.report.instance.period"].browse(period_id)
account_id = arg.get("account_id")
if account_id:
account = self.env[self.report_id.account_model].browse(account_id)
return f"{kpi.description} - {account.display_name} - {period.display_name}"
else:
return f"{kpi.description} - {period.display_name}"