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

163 lines
7.2 KiB
Python

# -*- coding: utf-8 -*-
from typing import Dict, List
from odoo import api, fields, models
from odoo.exceptions import ValidationError
class QuickboardItem(models.Model):
_name = "quickboard.item"
_description = "Quickboard Item"
name = fields.Char(string="Name")
model_id = fields.Many2one('ir.model', string='Model')
model_name = fields.Char(related='model_id.model', string="Model Name")
icon = fields.Char(string="Icon")
type = fields.Selection(
selection=[("basic", "Basic"), ("chart", "Chart"), ("list", "List")],
string="Item Type",
default="basic")
chart_type = fields.Selection(
selection=[("bar", "Bar"), ('horizontal-bar', 'Horizontal Bar'), ('doughnut', "Doughnut"), ("line", "Line"), ("pie", "Pie"), ("polar", "Polar Area")],
string="Chart Type")
value_field_id = fields.Many2many("ir.model.fields", string="Value Field")
aggregate_function = fields.Selection(
selection=[("avg","Average"), ("count", "Count"), ('max', "Max"), ('min', "Min"), ("sum","Sum")],
string="Aggregate Function",
default="count",
depends=['value_field_id'])
dimension_field_id = fields.Many2one("ir.model.fields", string="Dimension Field")
group_field_id = fields.Many2one("ir.model.fields", string="Group Field")
datetime_granularity = fields.Selection(
selection=[("year", "Year"), ("month", "Month"), ("day", "Day")],
string="Date/Time Granularity",
default="day",
depends=['dimension_field_id'])
list_row_limit = fields.Integer(string="Row limit", default=10)
domain_filter = fields.Char(string="Filter")
# basic item color
text_color = fields.Integer("Text Color")
background_color = fields.Integer("Background Color")
# layout
x_pos = fields.Integer(string="X Pos")
y_pos = fields.Integer(string="Y Pos")
height = fields.Integer(string="Height")
width = fields.Integer(string="Width")
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if not self.env.context.get("ai_generation", False):
if 'type' in val:
if val["type"] == "basic":
val["width"] = 2
val["height"] = 1
else:
val["width"] = 4
val["height"] = 2
sql = f"""WITH item_dim AS (
SELECT y_pos, CASE WHEN height = 0 THEN 1 ELSE height END AS height
FROM quickboard_item sdi WHERE create_uid = {self.env.uid}
)
SELECT max(y_pos + height) as max_y_pos FROM item_dim WHERE y_pos = (SELECT max(y_pos) FROM item_dim);
"""
self.env.cr.execute(sql)
res = self.env.cr.dictfetchall()
max_y_pos = res[0].get("max_y_pos")
val["y_pos"] = max_y_pos
val["x_pos"] = 0
res = super().create(vals_list)
return res
@api.onchange("type")
def clear_values(self):
for rec in self:
if rec.type:
if rec.type == 'basic':
rec.group_field_id = False
rec.dimension_field_id = False
elif rec.type == 'list':
rec.group_field_id = False
@api.constrains("aggregate_function", "value_field_id")
def _validate_aggregate_function(self):
for rec in self:
if rec.type != 'chart' and len(rec.value_field_id) > 1:
raise ValidationError(f"Basic and list items can only have one value field.")
if rec.type == 'chart':
for vf in rec.value_field_id:
if vf.ttype not in ['float', 'integer', 'monetary'] and rec.aggregate_function != "count":
raise ValidationError(f"Other fields than float, integer and monetary can only use count as aggregation.")
else:
if rec.value_field_id.ttype not in ['float', 'integer', 'monetary'] and rec.aggregate_function != "count":
raise ValidationError(f"Other fields than float, integer and monetary can only use count as aggregation.")
@api.constrains("value_field_id", "dimension_field_id")
def _validate_value_field_01(self):
for rec in self:
if rec.value_field_id and rec.dimension_field_id and rec.value_field_id == rec.dimension_field_id:
raise ValidationError("Value field must not be the same with dimension field.")
@api.constrains("dimension_field_id", "type")
def _validate_dimension_field_01(self):
for rec in self:
if rec.type in ["chart", "list"] and not rec.dimension_field_id:
raise ValidationError("Dimension field is required for charts.")
@api.constrains("list_row_limit", "type")
def _validate_dimension_field_02(self):
for rec in self:
if rec.type == "list" and not rec.list_row_limit:
raise ValidationError("Row limit is required for lists.")
@api.constrains("dimension_field_id", "datetime_granularity")
def _validate_dimension_field_03(self):
for rec in self:
if rec.dimension_field_id.ttype in ["date", "datetime"] and not rec.datetime_granularity:
raise ValidationError("Granularity for date or datetime field is required for charts.")
@api.constrains("dimension_field_id", "group_field_id")
def _validate_dimension_field_04(self):
for rec in self:
if not rec.dimension_field_id and rec.group_field_id:
raise ValidationError("Dimension field is required for grouping data.")
if rec.dimension_field_id and rec.group_field_id and rec.dimension_field_id == rec.group_field_id:
raise ValidationError("Dimension field must not be the same with grouping field.")
@api.constrains("group_field_id", "type")
def _validate_group_field_01(self):
for rec in self:
if rec.type != "chart" and rec.group_field_id:
raise ValidationError("Grouping only supported for charts.")
@api.constrains("group_field_id")
def _validate_group_field_02(self):
for rec in self:
if rec.group_field_id and rec.group_field_id.ttype in ["many2many", "one2many"]:
raise ValidationError("Grouping is not supported for x2many fields.")
@api.constrains("value_field_id", "group_field_id")
def _validate_group_field_03(self):
for rec in self:
if rec.group_field_id and len(rec.value_field_id) > 1:
raise ValidationError("Grouping is not supported when using multiple value fields.")
def web_save(self, vals, specification: Dict[str, Dict], next_id=None) -> List[Dict]:
res = super(QuickboardItem, self).web_save(vals, specification=specification, next_id=next_id)
if self.env.context.get("quick_edit", False):
self.env["bus.bus"]._sendone(
"quickboard",
"quickboard_item_updated",
{
"id": self.id,
}
)
return res