@@ -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': [
|
||||
|
||||
@@ -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,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
|
||||
)
|
||||
|
||||
@@ -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="描述此模板的用途,如:当用户查询销售订单列表时使用此模板"/>
|
||||
|
||||
Reference in New Issue
Block a user