From 27c69175ffe3751828fd824f8f47affaad3ed0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Fri, 5 Jun 2026 00:00:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_ai_agent/data/demo_report_template.xml | 2 - yuthon_ai_agent/models/ai_report_template.py | 74 +++++++++++++++---- .../models/ai_report_template_config.py | 63 +++++++++++----- yuthon_ai_agent/security/ir.model.access.csv | 2 + yuthon_ai_agent/views/ai_report_views.xml | 23 ++++-- 5 files changed, 126 insertions(+), 38 deletions(-) diff --git a/yuthon_ai_agent/data/demo_report_template.xml b/yuthon_ai_agent/data/demo_report_template.xml index da2f0d25f..14f3430da 100644 --- a/yuthon_ai_agent/data/demo_report_template.xml +++ b/yuthon_ai_agent/data/demo_report_template.xml @@ -10,7 +10,6 @@ sale.order ["name","partner_id","date_order","amount_total","state","user_id"] {"name":"订单号","partner_id":"客户","date_order":"下单日期","amount_total":"金额","state":"状态","user_id":"销售员"} - [["state","!=","cancel"]] date_order desc 5 @@ -25,7 +24,6 @@ crm.lead ["name","partner_name","expected_revenue","probability","stage_id","create_date","user_id"] {"name":"线索名称","partner_name":"客户","expected_revenue":"预期收入","probability":"赢单率","stage_id":"阶段","create_date":"创建时间","user_id":"负责人"} - [["active","=",True]] create_date desc 6 diff --git a/yuthon_ai_agent/models/ai_report_template.py b/yuthon_ai_agent/models/ai_report_template.py index 6b18d2b48..181399aac 100644 --- a/yuthon_ai_agent/models/ai_report_template.py +++ b/yuthon_ai_agent/models/ai_report_template.py @@ -28,8 +28,11 @@ class AiReportTemplate(models.Model): 'template_id', 'field_id', string='显示字段', domain="[('model_id','=',model_id)]", help='选择要显示的字段。选择后自动填充"字段列表"和"字段标签"') - default_domain = fields.Text('默认条件', default='[]', - help='JSON 格式的默认查询 domain') + default_domain = fields.Text('默认条件', compute='_compute_default_domain', + inverse='_inverse_default_domain', store=True, + help='自动从查询条件行生成,也可手动编辑 JSON') + domain_line_ids = fields.One2many('ai.report.template.domain.line', 'template_id', + string='查询条件行', copy=True) default_order = fields.Char('默认排序', default='id desc') description = fields.Text('使用说明', translate=True, help='告诉 AI 何时使用此模板') @@ -92,19 +95,13 @@ class AiReportTemplate(models.Model): cr = [] for col in t.column_ids: if col.color_value and col.cell_color: - # 解析操作符前缀: >= <= != > < = ~ - cv = col.color_value.strip() - m = re.match(r'^(>=|<=|!=|>|<|=|~)(.+)$', cv) - if m: - op_prefix, val = m.group(1), m.group(2) - op_map = {'=': 'eq', '~': 'contains', '!=': 'neq', - '>': '>', '<': '<', '>=': '>=', '<=': '<='} - op = op_map.get(op_prefix, 'eq') - else: - op, val = 'eq', cv + op_map = {'eq': 'eq', 'neq': 'neq', 'gt': '>', 'lt': '<', + 'gte': '>=', 'lte': '<=', 'contains': 'contains'} + op = op_map.get(col.color_operator or 'eq', 'eq') cr.append({ 'field': col.field_name, - 'rules': [{'op': op, 'value': val.strip(), 'color': col.cell_color}], + 'rules': [{'op': op, 'value': col.color_value.strip(), + 'color': col.cell_color}], }) if cr: # 合并相同 field 的规则 @@ -154,6 +151,57 @@ class AiReportTemplate(models.Model): def _inverse_param_definitions(self): pass + # ===== Computed: default_domain ===== + @api.depends('domain_line_ids', 'domain_line_ids.field_id', + 'domain_line_ids.operator', 'domain_line_ids.value') + def _compute_default_domain(self): + for t in self: + if not t.domain_line_ids: + t.default_domain = '[]' + continue + domain = [] + for line in t.domain_line_ids.sorted('sequence'): + if not line.field_id or not line.value: + continue + val = line.value.strip() + if line.operator in ('in', 'not in'): + val = [v.strip() for v in val.split(',')] + domain.append([line.field_id.name, line.operator, val]) + t.default_domain = json.dumps(domain, ensure_ascii=False) + + def _inverse_default_domain(self): + """手动编辑 JSON 时反向解析到 domain_line_ids(简单实现:清空后重建)""" + for t in self: + if not t.default_domain: + continue + try: + domain = json.loads(t.default_domain) + except (json.JSONDecodeError, TypeError): + continue + cmds = [(5, 0, 0)] + seq = 10 + for cond in domain: + if len(cond) >= 3: + op = cond[1] + val = cond[2] + if isinstance(val, list): + val = ', '.join(str(v) for v in val) + else: + val = str(val) + field = t.env['ir.model.fields'].search([ + ('model', '=', t.model_name), + ('name', '=', cond[0]), + ], limit=1) + if field: + cmds.append((0, 0, { + 'field_id': field.id, + 'operator': op, + 'value': val, + 'sequence': seq, + })) + seq += 10 + t.domain_line_ids = cmds + # ===== 预览动作 ===== def action_preview(self): """预览报表:使用模板配置生成一个临时报表并打开 URL""" diff --git a/yuthon_ai_agent/models/ai_report_template_config.py b/yuthon_ai_agent/models/ai_report_template_config.py index c34136b1f..1fa7ca014 100644 --- a/yuthon_ai_agent/models/ai_report_template_config.py +++ b/yuthon_ai_agent/models/ai_report_template_config.py @@ -24,15 +24,17 @@ class AiReportTemplateColumn(models.Model): label = fields.Char('显示名称', help='不填则默认使用字段名') width = fields.Integer('列宽', default=120) + color_operator = fields.Selection([ + ('eq', '等于'), + ('neq', '不等于'), + ('gt', '大于'), + ('lt', '小于'), + ('gte', '大于等于'), + ('lte', '小于等于'), + ('contains', '包含'), + ], string='操作符', default='eq') color_value = fields.Char('匹配值', - help='格式:[操作符]值。不写操作符默认等于。\n' - ' =已完成 / 已完成 → 等于"已完成"\n' - ' >100 → 大于 100\n' - ' <50 → 小于 50\n' - ' >=100 → 大于等于 100\n' - ' <=50 → 小于等于 50\n' - ' !=已完成 → 不等于"已完成"\n' - ' ~张三 → 包含"张三"') + help='匹配的值,如"已完成"或"100"') cell_color = fields.Char('显示颜色', default='#5DCAA5', help='匹配成功时显示的颜色,如 #E74C3C(红)、#5DCAA5(绿)') sequence = fields.Integer('排序', default=10) @@ -44,18 +46,11 @@ class AiReportTemplateColumn(models.Model): if not self.label: self.label = self.field_id.field_description - @api.constrains('color_value') + @api.constrains('color_value', 'cell_color') def _check_color_value(self): - import re for rec in self: - if not rec.color_value: - continue - # 合法格式: [>=|<=|!=|>|<|=|~]值 - if not re.match(r'^(>=|<=|!=|>|<|=|~)?\S', rec.color_value): - raise ValidationError( - '匹配值格式错误:%s\n' - '正确示例:已完成、>100、<50、>=100、<=50、!=已完成、~张三' % rec.color_value - ) + if rec.cell_color and not rec.color_value: + raise ValidationError('设置了显示颜色时,匹配值不能为空') class AiReportTemplateCard(models.Model): @@ -160,3 +155,35 @@ class AiReportTemplateParamDef(models.Model): self.domain_field = self.field_id.name if not self.label: self.label = self.field_id.field_description + + +class AiReportTemplateDomainLine(models.Model): + _name = 'ai.report.template.domain.line' + _description = '报表模板-查询条件行' + _order = 'sequence, id' + + template_id = fields.Many2one('ai.report.template', string='模板', + required=True, ondelete='cascade') + model_id = fields.Many2one('ir.model', related='template_id.model_id', store=True) + field_id = fields.Many2one('ir.model.fields', string='字段', + domain="[('model_id', '=', model_id)]", + help='选择模型字段') + operator = fields.Selection([ + ('=', '等于'), + ('!=', '不等于'), + ('>', '大于'), + ('<', '小于'), + ('>=', '大于等于'), + ('<=', '小于等于'), + ('ilike', '包含'), + ('not ilike', '不包含'), + ('in', '在列表中'), + ('not in', '不在列表中'), + ], string='操作符', default='=') + value = fields.Char('值', help='匹配的值,多个值用逗号分隔(用于in/not in)') + sequence = fields.Integer('排序', default=10) + + @api.onchange('field_id') + def _onchange_field_id(self): + if self.field_id and not self.value: + pass diff --git a/yuthon_ai_agent/security/ir.model.access.csv b/yuthon_ai_agent/security/ir.model.access.csv index 92580664a..dbdf8b9e7 100644 --- a/yuthon_ai_agent/security/ir.model.access.csv +++ b/yuthon_ai_agent/security/ir.model.access.csv @@ -33,3 +33,5 @@ access_ai_report_template_filter_user,筛选器-用户只读,model_ai_report_tem 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 +access_ai_report_template_domain_line_user,查询条件行-用户只读,model_ai_report_template_domain_line,yuthon_ai_agent.group_ai_agent_user,1,0,0,0 +access_ai_report_template_domain_line_admin,查询条件行-管理员,model_ai_report_template_domain_line,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 8306a00f9..907c53fc2 100644 --- a/yuthon_ai_agent/views/ai_report_views.xml +++ b/yuthon_ai_agent/views/ai_report_views.xml @@ -164,8 +164,6 @@ placeholder="选择要显示的字段(选择后自动生成下方配置)"/> - @@ -196,15 +194,30 @@ - - - + + + + + + + + + + + + + + + +