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"/>
-
+
+
+
+
+
+
-
-
-
+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+