报表
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
<field name="model_name">sale.order</field>
|
||||
<field name="field_list">["name","partner_id","date_order","amount_total","state","user_id"]</field>
|
||||
<field name="field_labels">{"name":"订单号","partner_id":"客户","date_order":"下单日期","amount_total":"金额","state":"状态","user_id":"销售员"}</field>
|
||||
<field name="default_domain">[["state","!=","cancel"]]</field>
|
||||
<field name="default_order">date_order desc</field>
|
||||
<field name="default_visible">5</field>
|
||||
<field name="report_config"><![CDATA[{"columns":[{"k":"name","l":"订单号","w":130},{"k":"partner_id","l":"客户","w":120},{"k":"date_order","l":"下单日期","w":110},{"k":"amount_total","l":"金额","w":110},{"k":"state","l":"状态","w":80}],"cards":[{"label":"总金额","field":"amount_total","calc":"sum","prefix":"¥"},{"label":"订单数","field":"id","calc":"count"}],"groupBy":[{"key":"state","label":"状态"}],"defaultVisible":5,"colorRules":[{"field":"state","rules":[{"op":"eq","value":"sale","color":"#5DCAA5"},{"op":"eq","value":"draft","color":"#EFC87E"},{"op":"eq","value":"done","color":"#85B7EB"},{"op":"eq","value":"cancel","color":"#ED93B1"}]}],"filters":[{"field":"state","type":"select","label":"状态","options":["draft","sale","done","cancel"]}]}]]></field>
|
||||
@@ -25,7 +24,6 @@
|
||||
<field name="model_name">crm.lead</field>
|
||||
<field name="field_list">["name","partner_name","expected_revenue","probability","stage_id","create_date","user_id"]</field>
|
||||
<field name="field_labels">{"name":"线索名称","partner_name":"客户","expected_revenue":"预期收入","probability":"赢单率","stage_id":"阶段","create_date":"创建时间","user_id":"负责人"}</field>
|
||||
<field name="default_domain">[["active","=",True]]</field>
|
||||
<field name="default_order">create_date desc</field>
|
||||
<field name="default_visible">6</field>
|
||||
<field name="report_config"><![CDATA[{"columns":[{"k":"name","l":"线索名称","w":140},{"k":"partner_name","l":"客户","w":120},{"k":"expected_revenue","l":"预期收入","w":110},{"k":"probability","l":"赢单率","w":80},{"k":"stage_id","l":"阶段","w":100},{"k":"create_date","l":"创建时间","w":120}],"cards":[{"label":"预期总收入","field":"expected_revenue","calc":"sum","prefix":"¥"},{"label":"线索数","field":"id","calc":"count"}],"groupBy":[{"key":"stage_id","label":"阶段"}],"defaultVisible":6,"colorRules":[],"filters":[{"field":"probability","type":"input","label":"最低赢单率"}]}]]></field>
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -164,8 +164,6 @@
|
||||
placeholder="选择要显示的字段(选择后自动生成下方配置)"/>
|
||||
<field name="field_list" invisible="1"/>
|
||||
<field name="field_labels" invisible="1"/>
|
||||
<field name="default_domain"
|
||||
placeholder="[['state','=','sale']]"/>
|
||||
<field name="default_order"
|
||||
placeholder="date_order desc"/>
|
||||
<field name="default_visible"/>
|
||||
@@ -196,15 +194,30 @@
|
||||
<field name="field_id"
|
||||
placeholder="选择字段..."
|
||||
options="{'no_create': True}"/>
|
||||
<field name="label" placeholder="显示名称(可选)"/>
|
||||
<field name="color_value" placeholder="匹配值"/>
|
||||
<field name="cell_color" widget="color"/>
|
||||
<field name="label" placeholder="显示名称(可选)"/>
|
||||
<field name="color_operator"/>
|
||||
<field name="color_value" placeholder="匹配值"/>
|
||||
<field name="cell_color" widget="color"/>
|
||||
<field name="width"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="查询条件" name="domain">
|
||||
<field name="domain_line_ids" nolabel="1">
|
||||
<tree string="查询条件" editable="bottom">
|
||||
<field name="model_id" column_invisible="1"/>
|
||||
<field name="field_id"
|
||||
placeholder="选择字段..."
|
||||
options="{'no_create': True}"/>
|
||||
<field name="operator"/>
|
||||
<field name="value" placeholder="值"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="AI 参数" name="params" invisible="1">
|
||||
<field name="param_def_ids" nolabel="1">
|
||||
<tree string="参数定义" editable="bottom">
|
||||
|
||||
Reference in New Issue
Block a user