diff --git a/yuthon_ai_agent/data/demo_report_template.xml b/yuthon_ai_agent/data/demo_report_template.xml index ac89effc2..da2f0d25f 100644 --- a/yuthon_ai_agent/data/demo_report_template.xml +++ b/yuthon_ai_agent/data/demo_report_template.xml @@ -12,6 +12,7 @@ {"name":"订单号","partner_id":"客户","date_order":"下单日期","amount_total":"金额","state":"状态","user_id":"销售员"} [["state","!=","cancel"]] date_order desc + 5 =","description":"查询起始日期"},{"name":"date_to","label":"结束日期","type":"date","domain_field":"date_order","op":"<=","description":"查询截止日期"}]]]> 当用户查询销售订单列表或销售业绩时使用此模板。支持按日期范围筛选。 @@ -26,6 +27,7 @@ {"name":"线索名称","partner_name":"客户","expected_revenue":"预期收入","probability":"赢单率","stage_id":"阶段","create_date":"创建时间","user_id":"负责人"} [["active","=",True]] create_date desc + 6 当用户查询销售线索列表或销售管道时使用此模板。 20 diff --git a/yuthon_ai_agent/models/__init__.py b/yuthon_ai_agent/models/__init__.py index 4026111d6..a6fa32b41 100644 --- a/yuthon_ai_agent/models/__init__.py +++ b/yuthon_ai_agent/models/__init__.py @@ -6,3 +6,4 @@ from . import ai_rule from . import ai_manual from . import ai_report from . import ai_report_template +from . import ai_report_template_config diff --git a/yuthon_ai_agent/models/ai_report_template.py b/yuthon_ai_agent/models/ai_report_template.py index 5660e9913..0ebb16d3e 100644 --- a/yuthon_ai_agent/models/ai_report_template.py +++ b/yuthon_ai_agent/models/ai_report_template.py @@ -13,6 +13,7 @@ class AiReportTemplate(models.Model): _description = '报表模板' _order = 'sequence, id' + # ===== 基础配置 ===== name = fields.Char('模板名称', required=True) model_name = fields.Char('数据模型', required=True, help='Odoo 模型技术名称,如 sale.order') @@ -29,19 +30,125 @@ class AiReportTemplate(models.Model): default_domain = fields.Text('默认条件', default='[]', help='JSON 格式的默认查询 domain') default_order = fields.Char('默认排序', default='id desc') - description = fields.Text('使用说明', - help='告诉 AI 何时使用此模板,如"当用户查询销售订单列表时使用此模板"') + description = fields.Text('使用说明', translate=True, + help='告诉 AI 何时使用此模板') sequence = fields.Integer('排序', default=10) - report_config = fields.Text('报表视觉配置', default='{}', - help='JSON格式的报表视觉配置,包含columns(列定义)、cards(汇总卡片)、groupBy(分组)、colorRules(颜色规则)、filters(筛选器)、defaultVisible(默认显示列数)') - param_definitions = fields.Text('模板参数定义', default='[]', - help='JSON数组,定义模板可接受的参数。每项包含name(参数名)、label(显示名)、type(参数类型:date/char/select/float)、domain_field(映射模型字段)、op(操作符)、description(说明)、options(select选项)') active = fields.Boolean(default=True) + # ===== 视觉配置(拆分为 One2many) ===== + column_ids = fields.One2many('ai.report.template.column', 'template_id', + string='列定义', copy=True) + card_ids = fields.One2many('ai.report.template.card', 'template_id', + string='汇总卡片', copy=True) + group_by_ids = fields.One2many('ai.report.template.group.by', 'template_id', + string='分组设置', copy=True) + color_rule_ids = fields.One2many('ai.report.template.color.rule', 'template_id', + string='颜色规则', copy=True) + filter_ids = fields.One2many('ai.report.template.filter', 'template_id', + string='筛选器', copy=True) + param_def_ids = fields.One2many('ai.report.template.param.def', 'template_id', + string='AI 参数定义', copy=True) + + default_visible = fields.Integer('默认可见列数', default=10) + + # ===== 计算字段(兼容旧版 JSON) ===== + report_config = fields.Text('报表视觉配置', default='{}', compute='_compute_report_config', + inverse='_inverse_report_config', store=True, + help='自动从列/卡片/分组/颜色/筛选器生成,也可手动编辑 JSON') + param_definitions = fields.Text('模板参数定义', default='[]', + compute='_compute_param_definitions', inverse='_inverse_param_definitions', store=True, + help='自动从参数定义行生成,也可手动编辑 JSON') + + # ===== Computed: report_config ===== + @api.depends('column_ids', 'card_ids', 'group_by_ids', + 'color_rule_ids', 'filter_ids', 'default_visible') + def _compute_report_config(self): + for t in self: + # 如果没有任何配置行,保留现有值(兼容旧数据) + if not any([t.column_ids, t.card_ids, t.group_by_ids, + t.color_rule_ids, t.filter_ids]): + continue + cfg = {} + cols = [{'k': c.field_name, 'l': c.label or c.field_name, 'w': c.width or 120} + for c in t.column_ids] + if cols: + cfg['columns'] = cols + cards = [] + for c in t.card_ids: + d = {'label': c.label, 'field': c.field_name, 'calc': c.calc} + if c.prefix: + d['prefix'] = c.prefix + if c.unit: + d['unit'] = c.unit + cards.append(d) + if cards: + cfg['cards'] = cards + gb = [{'key': g.field_name, 'label': g.label or g.field_name} + for g in t.group_by_ids] + if gb: + cfg['groupBy'] = gb + cr = [] + for r in t.color_rule_ids: + # 按 field_name 分组 + cr.append({ + 'field': r.field_name, + 'rules': [{'op': r.op, 'value': r.value, 'color': r.color}], + }) + if cr: + # 合并相同 field 的规则 + merged = {} + for r in cr: + f = r['field'] + if f not in merged: + merged[f] = {'field': f, 'rules': []} + merged[f]['rules'].append(r['rules'][0]) + cfg['colorRules'] = list(merged.values()) + flt = [{'field': f.field_name, 'type': f.filter_type, + 'label': f.label or f.field_name} + for f in t.filter_ids] + for f, fdef in zip(flt, t.filter_ids): + if fdef.options: + f['options'] = [o.strip() for o in fdef.options.split(',') if o.strip()] + if flt: + cfg['filters'] = flt + if t.default_visible: + cfg['defaultVisible'] = t.default_visible + t.report_config = json.dumps(cfg, ensure_ascii=False) if cfg else '{}' + + def _inverse_report_config(self): + """反向:手动编辑 JSON 时回写旧字段值(不自动拆分到行,保持兼容)""" + pass # report_config 仍可手动编辑,但不反向拆分到行 + + # ===== Computed: param_definitions ===== + @api.depends('param_def_ids') + def _compute_param_definitions(self): + for t in self: + if not t.param_def_ids: + continue + defs = [] + for p in t.param_def_ids: + d = { + 'name': p.name, + 'label': p.label or p.name, + 'type': p.param_type, + 'domain_field': p.domain_field or p.name, + 'op': p.op or '=', + 'description': p.description or '', + } + if p.options: + d['options'] = [o.strip() for o in p.options.split(',') if o.strip()] + if p.required: + d['required'] = True + defs.append(d) + t.param_definitions = json.dumps(defs, ensure_ascii=False) if defs else '[]' + + def _inverse_param_definitions(self): + pass + + # ===== 预览动作 ===== def action_preview(self): """预览报表:使用模板配置生成一个临时报表并打开 URL""" self.ensure_one() - import json as _json report = self.env['ai.report'].sudo().create({ 'name': f'[预览] {self.name}', 'model_name': self.model_name, @@ -59,9 +166,10 @@ class AiReportTemplate(models.Model): 'target': 'new', } + # ===== AI 模板注入 ===== @api.model def get_templates_for_ai(self): - """获取所有启用模板的摘要,供 AI system_msg 注入(增强版:包含参数信息)""" + """获取所有启用模板的摘要,供 AI system_msg 注入""" templates = self.sudo().search([('active', '=', True)]) if not templates: return '' @@ -72,7 +180,6 @@ class AiReportTemplate(models.Model): f'- 模板"{t.name}":ID={t.id},模型={t.model_name},' f'字段={fields_str},说明={t.description or "无"}' ) - # 注入参数定义信息 if t.param_definitions: try: param_defs = json.loads(t.param_definitions) @@ -87,6 +194,7 @@ class AiReportTemplate(models.Model): lines.append(base_info) return '\n'.join(lines) + # ===== onchange ===== @api.onchange('model_id') def _onchange_model_id(self): if self.model_id: @@ -95,13 +203,13 @@ class AiReportTemplate(models.Model): @api.onchange('field_ids') def _onchange_field_ids(self): if self.field_ids: - import json self.field_list = json.dumps([f.name for f in self.field_ids], ensure_ascii=False) self.field_labels = json.dumps( {f.name: f.field_description for f in self.field_ids}, ensure_ascii=False ) + # ===== create / write 同步 ===== @api.model_create_multi def create(self, vals_list): for vals in vals_list: @@ -122,17 +230,16 @@ class AiReportTemplate(models.Model): @api.model def _sync_field_list_from_ids(self, vals): - """从 field_ids (M2M command list) 中提取字段名并同步到 field_list 和 field_labels""" import json ids = [] field_cmds = vals['field_ids'] if isinstance(field_cmds, list): for cmd in field_cmds: if isinstance(cmd, (list, tuple)): - if cmd[0] == 6: # Replace all + if cmd[0] == 6: ids = cmd[2] break - elif cmd[0] == 4: # Add + elif cmd[0] == 4: ids.append(cmd[1]) if ids: fields_records = self.env['ir.model.fields'].browse(ids) diff --git a/yuthon_ai_agent/security/ir.model.access.csv b/yuthon_ai_agent/security/ir.model.access.csv index 266f3c693..92580664a 100644 --- a/yuthon_ai_agent/security/ir.model.access.csv +++ b/yuthon_ai_agent/security/ir.model.access.csv @@ -21,3 +21,15 @@ access_ai_report_user,动态报表-用户,model_ai_report,yuthon_ai_agent.group_ access_ai_report_admin,动态报表-管理员,model_ai_report,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 access_ai_report_template_user,报表模板-用户只读,model_ai_report_template,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 access_ai_report_template_admin,报表模板-管理员,model_ai_report_template,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 +access_ai_report_template_column_user,列定义-用户只读,model_ai_report_template_column,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_column_admin,列定义-管理员,model_ai_report_template_column,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 +access_ai_report_template_card_user,汇总卡片-用户只读,model_ai_report_template_card,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_card_admin,汇总卡片-管理员,model_ai_report_template_card,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 +access_ai_report_template_group_by_user,分组-用户只读,model_ai_report_template_group_by,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_group_by_admin,分组-管理员,model_ai_report_template_group_by,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 +access_ai_report_template_color_rule_user,颜色规则-用户只读,model_ai_report_template_color_rule,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_color_rule_admin,颜色规则-管理员,model_ai_report_template_color_rule,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 +access_ai_report_template_filter_user,筛选器-用户只读,model_ai_report_template_filter,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_filter_admin,筛选器-管理员,model_ai_report_template_filter,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 +access_ai_report_template_param_def_user,参数定义-用户只读,model_ai_report_template_param_def,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_param_def_admin,参数定义-管理员,model_ai_report_template_param_def,yuthon_ai_agent.group_ai_agent_admin,1,1,1,1 diff --git a/yuthon_ai_agent/views/ai_report_views.xml b/yuthon_ai_agent/views/ai_report_views.xml index 0daf6f7e1..08cac5fb8 100644 --- a/yuthon_ai_agent/views/ai_report_views.xml +++ b/yuthon_ai_agent/views/ai_report_views.xml @@ -128,51 +128,135 @@ bg_color="text-bg-danger" invisible="active"/>
-

- -

+ + + + + +
+

+
+
- + + + + + + - - - + - + - -