Files
gzth/yuthon_ai_agent/models/ai_report_template_config.py
T
2026-06-10 17:07:15 +08:00

278 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
import json
import logging
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class AiReportTemplateColumn(models.Model):
"""报表模板 - 列定义"""
_name = 'ai.report.template.column'
_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='选择模型字段,自动填充字段名和显示名称')
field_name = fields.Char('字段名', required=True) # 保持 required=True
label = fields.Char('显示名称',
help='不填则默认使用字段名')
width = fields.Integer('列宽', default=120)
color_operator = fields.Selection([
('eq', '等于'),
('neq', '不等于'),
('gt', '大于'),
('lt', '小于'),
('gte', '大于等于'),
('lte', '小于等于'),
('contains', '包含'),
], string='操作符')
color_value = fields.Char('匹配值',
help='匹配的值,如"已完成"或"100"')
cell_color = fields.Char('显示颜色', default='#5DCAA5',
help='匹配成功时显示的颜色,如 #E74C3C(红)、#5DCAA5(绿)')
sequence = fields.Integer('排序', default=10)
@api.onchange('field_id')
def _onchange_field_id(self):
if self.field_id:
self.field_name = self.field_id.name
if not self.label:
self.label = self.field_id.field_description
# 添加 create 和 write 方法确保 field_name 有值
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
# 如果有 field_id 但没有 field_name,自动填充
if vals.get('field_id') and not vals.get('field_name'):
field = self.env['ir.model.fields'].browse(vals['field_id'])
if field.exists():
vals['field_name'] = field.name
if not vals.get('label'):
vals['label'] = field.field_description
# 如果既没有 field_id 也没有 field_name,抛出友好错误
if not vals.get('field_name'):
raise ValidationError(_('列定义必须指定字段名(field_name)或选择字段(field_id)'))
return super().create(vals_list)
def write(self, vals):
# 处理 write 操作
if vals.get('field_id') and not vals.get('field_name'):
field = self.env['ir.model.fields'].browse(vals['field_id'])
if field.exists():
vals['field_name'] = field.name
if vals.get('label') is None and not self.label:
vals['label'] = field.field_description
return super().write(vals)
class AiReportTemplateCard(models.Model):
"""报表模板 - 汇总卡片"""
_name = 'ai.report.template.card'
_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), ('ttype', 'in', ('integer', 'float', 'monetary'))]",
help='选择数值型字段,自动填充字段名')
field_name = fields.Char('统计字段名', required=True) # 保持 required=True
label = fields.Char('卡片标签', required=True)
calc = fields.Selection([
('sum', '求和'),
('count', '计数'),
('distinct', '去重'),
], string='计算方式', default='sum')
prefix = fields.Char('前缀', help='如 ¥、$ 等')
unit = fields.Char('单位', help='如 万、% 等')
sequence = fields.Integer('排序', default=10)
@api.onchange('field_id')
def _onchange_field_id(self):
if self.field_id:
self.field_name = self.field_id.name
if not self.label:
self.label = self.field_id.field_description
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('field_id') and not vals.get('field_name'):
field = self.env['ir.model.fields'].browse(vals['field_id'])
if field.exists():
vals['field_name'] = field.name
if not vals.get('label'):
vals['label'] = field.field_description
if not vals.get('field_name'):
raise ValidationError(_('汇总卡片必须指定统计字段名(field_name)或选择统计字段(field_id)'))
return super().create(vals_list)
def write(self, vals):
if vals.get('field_id') and not vals.get('field_name'):
field = self.env['ir.model.fields'].browse(vals['field_id'])
if field.exists():
vals['field_name'] = field.name
if vals.get('label') is None and not self.label:
vals['label'] = field.field_description
return super().write(vals)
class AiReportTemplateColorRule(models.Model):
"""报表模板 - 颜色规则(隶属于列定义)"""
_name = 'ai.report.template.color.rule'
_description = '颜色规则'
_order = 'sequence, id'
column_id = fields.Many2one('ai.report.template.column', string='所属列',
required=True, ondelete='cascade')
template_id = fields.Many2one('ai.report.template', string='模板',
compute='_compute_template_id', store=True)
op = fields.Selection([
('eq', '等于'),
('>=', '大于等于'),
('<=', '小于等于'),
('>', '大于'),
('<', '小于'),
('contains', '包含'),
('neq', '不等于'),
], string='操作符', default='eq')
value = fields.Char('匹配值', required=True,
help='匹配的值,如"已完成"或"100"')
color = fields.Char('颜色', default='#5DCAA5',
help='十六进制颜色代码,如 #E74C3C(红)、#5DCAA5(绿)、#F39C12(橙)')
sequence = fields.Integer('排序', default=10)
@api.depends('column_id.template_id')
def _compute_template_id(self):
for r in self:
r.template_id = r.column_id.template_id if r.column_id else False
class AiReportTemplateParamDef(models.Model):
"""报表模板 - AI参数定义"""
_name = 'ai.report.template.param.def'
_description = 'AI参数定义'
_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)
name = fields.Char('参数名', required=True,
help='AI 识别参数用的键名,如 date_from')
label = fields.Char('显示名',
help='给 AI 看的说明,如"开始日期"')
param_type = fields.Selection([
('date', '日期'),
('char', '文本'),
('select', '选择'),
('float', '数字'),
('int', '整数'),
], string='参数类型', default='char')
field_id = fields.Many2one('ir.model.fields', string='映射字段',
domain="[('model_id', '=', model_id)]",
help='选择要映射的模型字段,自动填充映射字段名')
domain_field = fields.Char('映射字段名',
help='映射到模型字段名,如 date_order')
op = fields.Char('操作符', default='=',
help='Odoo domain 操作符,如 >=、<=、ilike、=')
description = fields.Text('说明',
help='详细说明 AI 如何提取此参数')
options = fields.Char('选项',
help='select 类型时,逗号分隔的选项')
required = fields.Boolean('必填')
sequence = fields.Integer('排序', default=10)
@api.onchange('field_id')
def _onchange_field_id(self):
if self.field_id:
self.domain_field = self.field_id.name
if not self.label:
self.label = self.field_id.field_description
class AiReportTemplateChart(models.Model):
"""报表模板 - 图表配置"""
_name = 'ai.report.template.chart'
_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)
chart_type = fields.Selection([
('bar', '柱状图'),
('line', '折线图'),
('pie', '饼图'),
('doughnut', '环形图'),
('polarArea', '玫瑰图'),
], string='图表类型', default='bar', required=True)
label_field_id = fields.Many2one('ir.model.fields', string='分组字段',
domain="[('model_id', '=', model_id)]", ondelete='set null',
help='作为图表分类轴的字段(必填)')
label_field_name = fields.Char('分组字段名',
compute='_compute_label_field_name', store=True)
value_field_id = fields.Many2one('ir.model.fields', string='数值字段',
domain="[('model_id', '=', model_id), ('ttype', 'in', ('integer', 'float', 'monetary'))]",
ondelete='set null', help='留空则使用计数')
value_field_name = fields.Char('数值字段名',
compute='_compute_value_field_name', store=True)
calc_method = fields.Selection([
('count', '计数'),
('sum', '求和'),
('avg', '平均值'),
], string='计算方式', default='count')
title = fields.Char('图表标题')
sequence = fields.Integer('排序', default=10)
@api.depends('label_field_id')
def _compute_label_field_name(self):
for r in self:
r.label_field_name = r.label_field_id.name if r.label_field_id else ''
@api.depends('value_field_id')
def _compute_value_field_name(self):
for r in self:
r.value_field_name = r.value_field_id.name if r.value_field_id else ''
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