!11753 ai动态报表

Merge pull request !11753 from 李鹏宇/dev
This commit is contained in:
李鹏宇
2026-05-29 01:27:58 +00:00
committed by Gitee
6 changed files with 211 additions and 6 deletions
+2
View File
@@ -28,6 +28,8 @@
'views/ai_conversation_views.xml',
'data/demo_provider.xml',
'data/demo_rule.xml',
'data/demo_manual.xml',
'data/demo_report_template.xml',
],
'assets': {
'web.assets_backend': [
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- ============================================ -->
<!-- 操作手册 Demo 数据 -->
<!-- ============================================ -->
<record id="demo_manual_sale_order" model="ai.manual">
<field name="name">创建销售订单流程</field>
<field name="module_name">销售管理</field>
<field name="sequence">10</field>
<field name="content">
操作步骤:
1. 进入「销售」模块 → 点击「订单」→「创建」
2. 选择客户(必填),系统自动带入客户价格表
3. 添加产品行:点击「添加一行」,选择产品,输入数量
4. 确认订单:点击「确认」按钮
5. 交货:订单确认后点击「交付」
6. 开票:交货完成后点击「创建发票」
注意事项:
- 订单确认后不可直接删除,需先取消
- 价格默认取客户价格表,可手动调整
- 数量支持小数,精度在产品设置中配置
</field>
<field name="active" eval="True"/>
</record>
<record id="demo_manual_purchase_order" model="ai.manual">
<field name="name">采购订单创建流程</field>
<field name="module_name">采购管理</field>
<field name="sequence">20</field>
<field name="content">
操作步骤:
1. 进入「采购」模块 → 点击「采购订单」→「创建」
2. 选择供应商(必填)
3. 添加产品行:选择产品、输入数量和价格
4. 确认订单:点击「确认订单」
5. 收货:供应商发货后点击「收货」
6. 开票:收货后创建供应商账单
注意事项:
- 采购价格默认取供应商价格表
- 可设置最低采购数量提醒
- 收货支持分批收货
</field>
<field name="active" eval="True"/>
</record>
</odoo>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- ============================================ -->
<!-- 报表模板 Demo 数据 -->
<!-- ============================================ -->
<record id="demo_template_sale_order" model="ai.report.template">
<field name="name">销售订单报表</field>
<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="report_config">{"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>
<field name="param_definitions">[{"name":"date_from","label":"开始日期","type":"date","domain_field":"date_order","op":">=","description":"查询起始日期"},{"name":"date_to","label":"结束日期","type":"date","domain_field":"date_order","op":"<=","description":"查询截止日期"}]</field>
<field name="description">当用户查询销售订单列表或销售业绩时使用此模板。支持按日期范围筛选。</field>
<field name="sequence">10</field>
<field name="active" eval="True"/>
</record>
<record id="demo_template_crm_lead" model="ai.report.template">
<field name="name">销售线索报表</field>
<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="report_config">{"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>
<field name="description">当用户查询销售线索列表或销售管道时使用此模板。</field>
<field name="sequence">20</field>
<field name="active" eval="True"/>
</record>
</odoo>
+17
View File
@@ -17,6 +17,8 @@ class AiReport(models.Model):
name = fields.Char('报表名称', required=True)
model_name = fields.Char('数据模型', required=True,
help='Odoo 模型技术名称,如 sale.order')
model_id = fields.Many2one('ir.model', string='数据模型', ondelete='cascade',
help='选择数据模型,选择后自动填充"数据模型"字段')
domain = fields.Text('查询条件', default='[]',
help='JSON 格式的 Odoo domain')
field_list = fields.Text('显示字段', required=True,
@@ -40,6 +42,10 @@ class AiReport(models.Model):
if not vals.get('expire_date'):
# 默认 7 天后过期
vals['expire_date'] = fields.Datetime.now() + timedelta(days=7)
# 新增:Auto-sync model_name from model_id
if vals.get('model_id') and not vals.get('model_name'):
model = self.env['ir.model'].browse(vals['model_id'])
vals['model_name'] = model.model
return super().create(vals_list)
@staticmethod
@@ -53,6 +59,17 @@ class AiReport(models.Model):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
return f"{base_url}/ai/report/{self.access_token}"
@api.onchange('model_id')
def _onchange_model_id(self):
if self.model_id:
self.model_name = self.model_id.model
def write(self, vals):
if vals.get('model_id') and 'model_name' not in vals:
model = self.env['ir.model'].browse(vals['model_id'])
vals['model_name'] = model.model
return super().write(vals)
def fetch_data(self):
"""执行查询并返回数据(增强版:支持模板参数合并、字段回退)"""
self.ensure_one()
@@ -3,6 +3,7 @@ import json
import logging
from odoo import models, fields, api
from datetime import timedelta
_logger = logging.getLogger(__name__)
@@ -15,10 +16,16 @@ class AiReportTemplate(models.Model):
name = fields.Char('模板名称', required=True)
model_name = fields.Char('数据模型', required=True,
help='Odoo 模型技术名称,如 sale.order')
model_id = fields.Many2one('ir.model', string='数据模型', ondelete='cascade',
help='选择数据模型,选择后自动填充"数据模型"字段')
field_list = fields.Text('字段列表',
help='JSON 数组,如 ["name","date_order","partner_id","amount_total"]')
field_labels = fields.Text('字段标签',
help='JSON 对象,如 {"name":"订单号","amount_total":"金额"}')
field_ids = fields.Many2many('ir.model.fields', 'ai_report_template_field_rel',
'template_id', 'field_id', string='显示字段',
domain="[('model_id','=',model_id)]",
help='选择要显示的字段。选择后自动填充"字段列表"和"字段标签"')
default_domain = fields.Text('默认条件', default='[]',
help='JSON 格式的默认查询 domain')
default_order = fields.Char('默认排序', default='id desc')
@@ -31,6 +38,27 @@ class AiReportTemplate(models.Model):
help='JSON数组,定义模板可接受的参数。每项包含name(参数名)、label(显示名)、type(参数类型:date/char/select/float)、domain_field(映射模型字段)、op(操作符)、description(说明)、options(select选项)')
active = fields.Boolean(default=True)
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,
'field_list': self.field_list or '[]',
'field_labels': self.field_labels or '{}',
'domain': self.default_domain or '[]',
'order': self.default_order or 'id desc',
'template_id': self.id,
'expire_date': fields.Datetime.now() + timedelta(hours=1),
})
url = report.get_report_url()
return {
'type': 'ir.actions.act_url',
'url': url,
'target': 'new',
}
@api.model
def get_templates_for_ai(self):
"""获取所有启用模板的摘要,供 AI system_msg 注入(增强版:包含参数信息)"""
@@ -58,3 +86,60 @@ class AiReportTemplate(models.Model):
pass
lines.append(base_info)
return '\n'.join(lines)
@api.onchange('model_id')
def _onchange_model_id(self):
if self.model_id:
self.model_name = self.model_id.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
)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('model_id') and not vals.get('model_name'):
model = self.env['ir.model'].browse(vals['model_id'])
vals['model_name'] = model.model
if vals.get('field_ids'):
self._sync_field_list_from_ids(vals)
return super().create(vals_list)
def write(self, vals):
if vals.get('model_id') and 'model_name' not in vals:
model = self.env['ir.model'].browse(vals['model_id'])
vals['model_name'] = model.model
if vals.get('field_ids'):
self._sync_field_list_from_ids(vals)
return super().write(vals)
@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
ids = cmd[2]
break
elif cmd[0] == 4: # Add
ids.append(cmd[1])
if ids:
fields_records = self.env['ir.model.fields'].browse(ids)
if not vals.get('field_list'):
vals['field_list'] = json.dumps([f.name for f in fields_records], ensure_ascii=False)
if not vals.get('field_labels'):
vals['field_labels'] = json.dumps(
{f.name: f.field_description for f in fields_records},
ensure_ascii=False
)
+22 -6
View File
@@ -39,7 +39,11 @@
<group>
<group string="查询参数">
<field name="model_name"/>
<field name="model_id"
options="{'no_create': True}"/>
<field name="model_name"
attrs="{'readonly': [('model_id','!=',False)], 'required': [('model_id','=',False)]}"
placeholder="如 model_id 不含所需模型时可手动输入"/>
<field name="domain"/>
<field name="field_list"/>
<field name="field_labels"/>
@@ -131,12 +135,18 @@
<group>
<group string="模板配置">
<field name="model_id"
placeholder="选择数据模型,如 sale.order"
options="{'no_create': True}"/>
<field name="model_name"
placeholder="如:sale.order"/>
<field name="field_list"
placeholder='["name","date_order","partner_id","amount_total","state"]'/>
<field name="field_labels"
placeholder='{"name":"订单号","date_order":"下单日期","partner_id":"客户","amount_total":"金额","state":"状态"}'/>
attrs="{'readonly': [('model_id','!=',False)], 'required': [('model_id','=',False)]}"
placeholder="如 model_id 不含所需模型时可手动输入"/>
<field name="field_ids" widget="many2many_tags"
domain="[('model_id','=',model_id)]"
options="{'no_create': True}"
placeholder="选择要显示的字段"/>
<field name="field_list" invisible="1"/>
<field name="field_labels" invisible="1"/>
<field name="default_domain"
placeholder="[['state','=','sale']]"/>
<field name="default_order"
@@ -149,6 +159,12 @@
</group>
</group>
<group>
<button name="action_preview" type="object"
string="📊 预览报表" class="oe_highlight"
attrs="{'invisible': [('model_name','=',False)]}"/>
</group>
<group string="使用说明">
<field name="description" nolabel="1"
placeholder="描述此模板的用途,如:当用户查询销售订单列表时使用此模板"/>