mirror of
https://gitlab.com/flectra-community/mis-builder.git
synced 2024-12-23 21:01:47 +00:00
114 lines
4.0 KiB
Python
114 lines
4.0 KiB
Python
# Copyright 2017 ACSONE SA/NV
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from collections import defaultdict
|
|
|
|
from flectra import _, api, fields, models
|
|
from flectra.exceptions import UserError
|
|
from flectra.osv import expression
|
|
|
|
ACC_SUM = "sum"
|
|
ACC_AVG = "avg"
|
|
ACC_NONE = "none"
|
|
|
|
|
|
def intersect_days(item_dt_from, item_dt_to, dt_from, dt_to):
|
|
item_days = (item_dt_to - item_dt_from).days + 1.0
|
|
i_dt_from = max(dt_from, item_dt_from)
|
|
i_dt_to = min(dt_to, item_dt_to)
|
|
i_days = (i_dt_to - i_dt_from).days + 1.0
|
|
return i_days, item_days
|
|
|
|
|
|
class MisKpiData(models.AbstractModel):
|
|
"""Abstract class for manually entered KPI values."""
|
|
|
|
_name = "mis.kpi.data"
|
|
_description = "MIS Kpi Data Abtract class"
|
|
|
|
name = fields.Char(compute="_compute_name", required=False)
|
|
kpi_expression_id = fields.Many2one(
|
|
comodel_name="mis.report.kpi.expression",
|
|
required=True,
|
|
ondelete="restrict",
|
|
string="KPI",
|
|
)
|
|
date_from = fields.Date(required=True, string="From")
|
|
date_to = fields.Date(required=True, string="To")
|
|
amount = fields.Float()
|
|
seq1 = fields.Integer(
|
|
related="kpi_expression_id.kpi_id.sequence",
|
|
store=True,
|
|
string="KPI Sequence",
|
|
)
|
|
seq2 = fields.Integer(
|
|
related="kpi_expression_id.subkpi_id.sequence",
|
|
store=True,
|
|
string="Sub-KPI Sequence",
|
|
)
|
|
|
|
@api.depends(
|
|
"kpi_expression_id.subkpi_id.name",
|
|
"kpi_expression_id.kpi_id.name",
|
|
"date_from",
|
|
"date_to",
|
|
)
|
|
def _compute_name(self):
|
|
for rec in self:
|
|
subkpi_name = rec.kpi_expression_id.subkpi_id.name
|
|
if subkpi_name:
|
|
subkpi_name = "." + subkpi_name
|
|
else:
|
|
subkpi_name = ""
|
|
rec.name = "{}{}: {} - {}".format(
|
|
rec.kpi_expression_id.kpi_id.name,
|
|
subkpi_name,
|
|
rec.date_from,
|
|
rec.date_to,
|
|
)
|
|
|
|
@api.model
|
|
def _intersect_days(self, item_dt_from, item_dt_to, dt_from, dt_to):
|
|
return intersect_days(item_dt_from, item_dt_to, dt_from, dt_to)
|
|
|
|
@api.model
|
|
def _query_kpi_data(self, date_from, date_to, base_domain):
|
|
"""Query mis.kpi.data over a time period.
|
|
|
|
Returns {mis.report.kpi.expression: amount}
|
|
"""
|
|
dt_from = fields.Date.from_string(date_from)
|
|
dt_to = fields.Date.from_string(date_to)
|
|
# all data items within or overlapping [date_from, date_to]
|
|
date_domain = [("date_from", "<=", date_to), ("date_to", ">=", date_from)]
|
|
domain = expression.AND([date_domain, base_domain])
|
|
res = defaultdict(float)
|
|
res_avg = defaultdict(list)
|
|
for item in self.search(domain):
|
|
item_dt_from = fields.Date.from_string(item.date_from)
|
|
item_dt_to = fields.Date.from_string(item.date_to)
|
|
i_days, item_days = self._intersect_days(
|
|
item_dt_from, item_dt_to, dt_from, dt_to
|
|
)
|
|
if item.kpi_expression_id.kpi_id.accumulation_method == ACC_SUM:
|
|
# accumulate pro-rata overlap between item and reporting period
|
|
res[item.kpi_expression_id] += item.amount * i_days / item_days
|
|
elif item.kpi_expression_id.kpi_id.accumulation_method == ACC_AVG:
|
|
# memorize the amount and number of days overlapping
|
|
# the reporting period (used as weight in average)
|
|
res_avg[item.kpi_expression_id].append((i_days, item.amount))
|
|
else:
|
|
raise UserError(
|
|
_(
|
|
"Unexpected accumulation method %(method)s for %(name)s.",
|
|
method=item.kpi_expression_id.kpi_id.accumulation_method,
|
|
name=item.name,
|
|
)
|
|
)
|
|
# compute weighted average for ACC_AVG
|
|
for kpi_expression, amounts in res_avg.items():
|
|
res[kpi_expression] = sum(d * a for d, a in amounts) / sum(
|
|
d for d, a in amounts
|
|
)
|
|
return res
|