Files
2025-08-19 15:05:55 +07:00

298 lines
14 KiB
Python

# -*- coding: utf-8 -*-
from ast import literal_eval
import pandas as pd
from odoo import http, fields, models
from odoo.http import request
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
class QuickboardController(http.Controller):
def get_quickboard_item_values(self, quickboard_item, start_date=None, end_date=None, with_data=False):
vals = {
'id': quickboard_item.id,
'name': quickboard_item.name,
'model_name': quickboard_item.model_name,
'icon': quickboard_item.icon,
'type': quickboard_item.type,
'chart_type': quickboard_item.chart_type,
'height': quickboard_item.height,
'width': quickboard_item.width,
'x_pos': quickboard_item.x_pos,
'y_pos': quickboard_item.y_pos,
'value_field_name': ",".join([f"{o.display_name}" for o in quickboard_item.value_field_id]),
'value_field_type': ",".join([f"{o.ttype}" for o in quickboard_item.value_field_id]),
'dimension_field_name': quickboard_item.dimension_field_id.display_name,
'dimension_field_type': quickboard_item.dimension_field_id.ttype,
'datetime_granularity': quickboard_item.datetime_granularity,
'group_field_name': quickboard_item.group_field_id.display_name,
'group_field_type': quickboard_item.group_field_id.ttype,
'list_row_limit': quickboard_item.list_row_limit,
'aggregate_function': quickboard_item.aggregate_function,
'text_color': quickboard_item.text_color,
'background_color': quickboard_item.background_color
}
if with_data:
domain = []
date_filter_field = "create_date"
if date_filter_field not in request.env[quickboard_item.model_name]:
field_iterable = request.env[quickboard_item.model_name]._fields.items()
new_date_filter_field = next((v for k, v in field_iterable if v.type in ["date", "datetime"]), None)
date_filter_field = new_date_filter_field.name
if start_date and date_filter_field is not None:
sd = fields.Datetime.from_string(start_date)
domain.append((date_filter_field, ">", sd))
if end_date and date_filter_field is not None:
ed = fields.Datetime.from_string(end_date)
domain.append((date_filter_field, "<", ed))
if quickboard_item.domain_filter and quickboard_item.domain_filter != "":
the_filter = expression.AND([literal_eval(quickboard_item.domain_filter)])
domain = expression.AND([domain, the_filter])
if quickboard_item.type == "basic":
aggregate_value = 0
aggr_func = f"{quickboard_item.value_field_id.name}:{quickboard_item.aggregate_function}"
agg = request.env[quickboard_item.model_name].sudo()._read_group(
domain=domain,
groupby=[],
aggregates=[aggr_func]
)
aggregate_value = agg[0][0] if agg[0][0] else 0
vals.update({ 'aggregate_value': aggregate_value })
elif quickboard_item.type == 'list':
data = []
grouping = []
aggr_func = f"{quickboard_item.value_field_id.name}:{quickboard_item.aggregate_function}"
group_by = quickboard_item.dimension_field_id.name
if quickboard_item.dimension_field_id.ttype in ["date", "datetime"]:
group_by = f"{group_by}:{quickboard_item.datetime_granularity}"
grouping.append(group_by)
limit = quickboard_item.list_row_limit
order = f"{aggr_func} desc"
aggs = request.env[quickboard_item.model_name].sudo()._read_group(
domain=domain,
groupby=grouping,
aggregates=[aggr_func],
limit=limit,
order=order
)
# seq is to ease t-foreach on the javascript part because it needs t-key
for seq, agg in enumerate(aggs, start=1):
if isinstance(agg[0], models.Model):
if agg[0]:
x_data = agg[0].name
else:
x_data = "N/A"
else:
x_data = agg[0]
data.append({
"seq": seq,
"x": x_data,
"y": agg[1]
})
vals.update({'data': data})
else:
if quickboard_item.group_field_id:
data = []
grouping = []
aggr_func = f"{quickboard_item.value_field_id.name}:{quickboard_item.aggregate_function}"
group_by = quickboard_item.dimension_field_id.name
if quickboard_item.dimension_field_id.ttype in ["date", "datetime"]:
group_by = f"{group_by}:{quickboard_item.datetime_granularity}"
grouping.append(group_by)
order = f"{grouping[0]} desc, {aggr_func} asc"
sub_group = quickboard_item.group_field_id.name
if quickboard_item.group_field_id.ttype in "many2one":
sub_group = quickboard_item.group_field_id.name
if quickboard_item.group_field_id.ttype in ["date", "datetime"]:
sub_group = f"{sub_group}:{quickboard_item.datetime_granularity}"
grouping.append(sub_group)
order = f"{grouping[1]} desc, {grouping[0]} desc, {aggr_func} asc"
aggs = request.env[quickboard_item.model_name].sudo()._read_group(
domain=domain,
groupby=grouping,
aggregates=[aggr_func],
order=order
)
if len(aggs) > 0:
dimension_field_display_name = quickboard_item.dimension_field_id.display_name
group_field_display_name = quickboard_item.group_field_id.display_name
value_field_display_name = quickboard_item.value_field_id.display_name
df = pd.DataFrame(aggs,
columns=[
dimension_field_display_name,
group_field_display_name,
value_field_display_name
]
)
if (quickboard_item.dimension_field_id.ttype == "many2one"):
df[dimension_field_display_name] = df[dimension_field_display_name].map(lambda o: o.name)
filler = None
if quickboard_item.dimension_field_id.ttype in ["date", "datetime"]:
if quickboard_item.datetime_granularity == "year":
filler = pd.DataFrame(pd.date_range(
df[dimension_field_display_name].min(),
df[dimension_field_display_name].max(),
freq="YS"
),
columns=[dimension_field_display_name]
)
elif quickboard_item.datetime_granularity == "month":
filler = pd.DataFrame(pd.date_range(
df[dimension_field_display_name].min(),
df[dimension_field_display_name].max(),
freq="MS"
),
columns=[dimension_field_display_name]
)
else:
filler = pd.DataFrame(pd.date_range(
df[dimension_field_display_name].min(),
df[dimension_field_display_name].max(),
freq="D"
),
columns=[dimension_field_display_name]
)
else:
filler = pd.DataFrame(
df[dimension_field_display_name].unique(),
columns=[dimension_field_display_name]
)
keys = df[group_field_display_name].unique().tolist()
for key in keys:
dfd = df.loc[
df[group_field_display_name] == key
][
[
dimension_field_display_name,
value_field_display_name
]
]
dfd = filler.merge(right=dfd, how="left", on=dimension_field_display_name)
if (quickboard_item.value_field_id.ttype in ["integer", "float", "monetary"]):
dfd = dfd.fillna(0)
else:
dfd = dfd.fillna("")
if quickboard_item.dimension_field_id.ttype in ["date", "datetime"]:
dfd[dimension_field_display_name] = dfd[dimension_field_display_name].dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
dataset = dfd.values.tolist()
label = "N/A"
if isinstance(key, models.Model):
label = key.name
else:
label = key
data.append({
"label": label,
"dataset": dataset
})
vals.update({'data': data})
else:
data = []
group_by = quickboard_item.dimension_field_id.name
if quickboard_item.dimension_field_id.ttype in ["date", "datetime"]:
group_by = f"{group_by}:{quickboard_item.datetime_granularity}"
for value_field in quickboard_item.value_field_id:
aggr_func = f"{value_field.name}:{quickboard_item.aggregate_function}"
order = f"{group_by} desc, {aggr_func} asc"
aggs = request.env[quickboard_item.model_name].sudo()._read_group(
domain=domain,
groupby=[group_by],
aggregates=[aggr_func],
order=order
)
dataset = []
for agg in aggs:
if isinstance(agg[0], models.Model):
if agg[0]:
x_data = agg[0].name
else:
x_data = "N/A"
else:
x_data = agg[0]
dataset.append([x_data, agg[1]])
data.append({
"label": value_field.display_name,
"dataset": dataset
})
vals.update({'data': data})
return vals
@http.route('/quickboard/item', type='json', auth='user', website=True)
def get_quickboard_item(self, item_id, start_date=None, end_date=None):
quickboard_item = request.env['quickboard.item'].with_context({"hide_model": True}).search([("id", "=", item_id)])
vals = self.get_quickboard_item_values(quickboard_item, start_date, end_date, True)
return vals
@http.route('/quickboard/item_defs', type='json', auth='user', website=True)
def get_quickboard_items(self):
items = []
for quickboard_item in request.env['quickboard.item'].with_context({"hide_model": True}).search([], order="id"):
vals = self.get_quickboard_item_values(quickboard_item, None, None, False)
items.append(vals)
return items
@http.route('/quickboard/save_layout', type='json', auth='user', website=True)
def save_layout(self, layout):
for item in layout:
quickboard_item = request.env["quickboard.item"].with_context({"hide_model": True}).search([("id", "=", item["id"])], limit=1)
quickboard_item.update({
"x_pos": item["x"],
"y_pos": item["y"],
"height": item["h"] if "h" in item else 0,
"width": item["w"] if "w" in item else 0
})
return True
@http.route('/quickboard/save_theme', type='json', auth='user', website=True)
def save_theme(self, theme):
request.env.user.res_users_settings_id.quickboard_theme = theme
return True
@http.route('/quickboard/save_filter', type='json', auth='user', website=True)
def save_filter(self, start_date, end_date):
request.env.user.res_users_settings_id.update({
"quickboard_start_date": start_date,
"quickboard_end_date": end_date
})
return True