This commit is contained in:
李鹏宇
2026-06-06 22:19:25 +08:00
parent 7dc0d11cbd
commit 46d2ad6c03
3 changed files with 157 additions and 0 deletions
+1
View File
@@ -39,6 +39,7 @@
'yuthon_ai_agent/static/src/xml/ai_chat_template.xml',
'yuthon_ai_agent/static/src/css/ai_chat.css',
'yuthon_ai_agent/static/src/css/ai_floating.css',
'yuthon_ai_agent/static/src/css/ai_report_backend.css',
],
},
'installable': True,
@@ -6,6 +6,7 @@ import re
from odoo import models, fields, api
from datetime import timedelta
from lxml import etree
_logger = logging.getLogger(__name__)
@@ -308,6 +309,156 @@ class AiReportTemplate(models.Model):
else:
t.fixed_report_url = False
# ===== 初始化列:从模型 tree 视图批量导入 =====
def action_init_columns(self):
"""从模型的默认 tree 视图中提取可见字段,批量生成列定义
- 查找模型优先级最高的 tree 视图
- 解析 arch,跳过 invisible / column_invisible / optional=hide 的字段
- 跳过系统技术字段(id, create_uid, create_date, write_uid, write_date, display_name)
- 仅添加当前 column_ids 中不存在的字段
"""
self.ensure_one()
if not self.model_name:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '无法初始化',
'message': '请先选择数据模型',
'type': 'warning',
}
}
# 1. 查找默认 tree 视图
View = self.env['ir.ui.view']
views = View.sudo().search([
('model', '=', self.model_name),
('type', '=', 'tree'),
('mode', 'in', ('primary', 'extension')),
], order='priority desc, id asc')
# 优先取 primary 视图;若无则取 extension 中第一个
tree_view = views.filtered(lambda v: v.mode == 'primary')[:1] or views[:1]
if not tree_view:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '未找到视图',
'message': f'模型 {self.model_name} 没有 tree 视图,无法自动初始化列',
'type': 'warning',
}
}
# 2. 解析 arch(处理 view inheritance)
try:
arch_et = tree_view.sudo()._get_combined_arch()
except Exception as e:
_logger.warning('解析 tree 视图 arch 失败: view=%s, error=%s', tree_view.xml_id or tree_view.id, e)
arch_et = etree.fromstring(tree_view.arch.encode('utf-8')) if tree_view.arch else None
if arch_et is None:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '解析失败',
'message': '无法解析 tree 视图的 XML 结构',
'type': 'danger',
}
}
# 3. 提取可见字段
tech_fields = {'id', 'create_uid', 'create_date', 'write_uid', 'write_date', 'display_name'}
existing_names = set(self.column_ids.mapped('field_name'))
new_columns = []
seq_base = max(self.column_ids.mapped('sequence') or [0]) + 10
# tree 中所有 <field> 节点
for idx, field_node in enumerate(arch_et.iter('field')):
fname = field_node.get('name')
if not fname:
continue
if fname in tech_fields:
continue
if fname in existing_names:
continue
# 判断是否为隐藏字段(静态属性 + modifiers JSON)
invisible = field_node.get('invisible') or field_node.get('column_invisible')
if invisible and invisible.strip() not in ('0', 'false', 'False', ''):
continue
modifiers = field_node.get('modifiers')
if modifiers:
try:
mods = json.loads(modifiers)
if mods.get('column_invisible') or mods.get('invisible'):
continue
except (json.JSONDecodeError, TypeError):
pass
if field_node.get('optional') == 'hide':
continue
# 获取显示名称:优先 arch 中的 string 属性,其次字段元数据
label = field_node.get('string')
field_rec = self.env['ir.model.fields'].sudo().search([
('model_id', '=', self.model_id.id),
('name', '=', fname),
], limit=1)
if not label:
label = field_rec.field_description if field_rec else fname
col_data = {
'field_name': fname,
'label': label,
'width': 120,
'sequence': seq_base + idx * 10,
}
if field_rec:
col_data['field_id'] = field_rec.id
new_columns.append(col_data)
if not new_columns:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '无需初始化',
'message': '所有可见字段已存在于列定义中,或该模型 tree 视图无可用字段',
'type': 'info',
}
}
# 4. 批量创建列定义
Column = self.env['ai.report.template.column']
created = 0
for col_vals in new_columns:
try:
Column.create(dict(col_vals, template_id=self.id))
created += 1
except Exception as e:
_logger.warning('创建列定义失败: template=%s, field=%s, error=%s', self.name, col_vals['field_name'], e)
# 5. 同步更新 field_list / field_labels(兼容性)
all_fields = self.column_ids.mapped('field_name')
self.field_list = json.dumps(all_fields, ensure_ascii=False)
field_labels = {}
for c in self.column_ids:
if c.field_name and c.label:
field_labels[c.field_name] = c.label
self.field_labels = json.dumps(field_labels, ensure_ascii=False)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '初始化完成',
'message': f'已从 tree 视图「{tree_view.name or tree_view.xml_id or tree_view.id}」导入 {created} 个字段到列定义',
'type': 'success',
}
}
# ===== 固定报表:生成/刷新动作 =====
def action_generate_fixed_report(self):
"""生成固定报表链接(按钮动作)
@@ -140,6 +140,11 @@
<button name="action_preview" type="object"
string="预览报表" class="oe_highlight"
invisible="not model_name"/>
<button name="action_init_columns" type="object"
string="初始化列" class="btn-secondary"
icon="fa-magic"
invisible="not model_name"
help="从模型的 tree 视图中提取可见字段,批量生成列定义"/>
</td>
</tr>
</table>