diff --git a/yuthon_ai_agent/models/ai_report_template.py b/yuthon_ai_agent/models/ai_report_template.py index f8dd5a33a..6d42d9e2f 100644 --- a/yuthon_ai_agent/models/ai_report_template.py +++ b/yuthon_ai_agent/models/ai_report_template.py @@ -225,107 +225,90 @@ class AiReportTemplate(models.Model): if self.model_id: self.model_name = self.model_id.model + # ===== onchange: field_ids 变化时联动更新 ===== @api.onchange('field_ids') def _onchange_field_ids(self): + """显示字段变化 → 自动同步 字段列表 + 字段标签 + 列定义""" if self.field_ids: - self.field_list = json.dumps([f.name for f in self.field_ids], ensure_ascii=False) + 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 - ) - # 同步 column_ids:去重 + 删不在的 + 补缺失的 + 修复空字段 - if self.column_ids or self.field_ids: - new_field_ids = set(self.field_ids.ids) - cmds = [] - # 1) 收集 column_ids,按 field_id 分组,重复的只保留第一个 - kept = {} - for col in self.column_ids: - fid = col.field_id.id if col.field_id else None - if not fid: - cmds.append((2, col.id)) - elif fid not in kept: - kept[fid] = col - else: - cmds.append((2, col.id)) # 重复的删掉 - # 2) 修复 kept 列中 field_name 为空的列 - for fid, col in kept.items(): - if not col.field_name: - f = self.field_ids.filtered(lambda r: r.id == fid) - fname = f.name if f else col.field_id.name - cmds.append((1, col.id, {'field_name': fname})) - # 3) 删掉不在 field_ids 里的 - for fid, col in kept.items(): - if fid not in new_field_ids: - cmds.append((2, col.id)) - # 4) 补充 field_ids 里有但 column_ids 里没有的 - seq = max((col.sequence for col in self.column_ids), default=0) + 10 - for f in self.field_ids: - if f.id not in kept: - cmds.append((0, 0, { - 'field_id': f.id, - 'field_name': f.name, - 'label': f.field_description, - 'sequence': seq, - })) - seq += 10 - if cmds: - self.column_ids = cmds + ensure_ascii=False) + else: + self.field_list = '[]' + self.field_labels = '{}' + # 同步列定义(onchange 和 write 共用同一套逻辑) + self._sync_columns() - # ===== create / write 同步 ===== + # ===== create / write: 保存时联动同步 ===== @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'): + if 'field_ids' in vals: self._sync_field_list_from_ids(vals) records = super().create(vals_list) for record in records: - if record.field_ids: - record._sync_column_ids_from_field_ids() + record._sync_columns() return records 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'): + field_ids_changed = 'field_ids' in vals + if field_ids_changed: self._sync_field_list_from_ids(vals) result = super().write(vals) - # 任何 write 都触发 column 去重 + 同步(清理历史重复数据 + 确保一致性) - for record in self: - if record.field_ids: - record._sync_column_ids_from_field_ids() + if field_ids_changed: + for record in self: + record._sync_columns() return result - def _sync_column_ids_from_field_ids(self): - """根据 field_ids 同步 column_ids:去重 + 删不在的 + 补缺失的 + 修复空字段""" + # ===== 核心:列定义同步 ===== + def _sync_columns(self): + """根据 field_ids 同步 column_ids,保证: + ① 列定义 = 显示字段(一对一对应) + ② 不重复 → 同一 field_id 只保留一条 + ③ 不遗漏 → field_ids 有的必须建列 + ④ 不多余 → field_ids 没的必须删列 + ⑤ 可修复 → field_name 为空的自动补全""" self.ensure_one() - new_field_ids = set(self.field_ids.ids) + target_fids = set(self.field_ids.ids) cmds = [] - # 1) 收集 column_ids,按 field_id 分组,重复的只保留第一个 - kept = {} + kept = {} # fid → col(去重后唯一保留的列) + + # ---- 步骤 1:遍历现有 column_ids,去重 + 清理无 field_id 的脏数据 ---- for col in self.column_ids: fid = col.field_id.id if col.field_id else None if not fid: - cmds.append((2, col.id)) # 没有 field_id 的脏数据直接删 - elif fid not in kept: - kept[fid] = col + cmds.append((3, col.id)) # 脏数据:无关联 field → 删除 + elif fid in kept: + cmds.append((3, col.id)) # 重复:同一 field_id 第二条 → 删除 else: - cmds.append((2, col.id)) # 重复的删掉 - # 2) 修复 kept 列中 field_name 为空的列(required=True 约束) + kept[fid] = col # 首条 → 保留 + + # ---- 步骤 2:删除 field 已不在 field_ids 中的列 ---- + for fid, col in list(kept.items()): + if fid not in target_fids: + cmds.append((3, col.id)) + del kept[fid] # 从 kept 清除,避免步骤 4 误判 + + # ---- 步骤 3:修复保留列中 field_name 为空的 ---- for fid, col in kept.items(): if not col.field_name: - f = self.field_ids.filtered(lambda r: r.id == fid) - fname = f.name if f else col.field_id.name + field = self.field_ids.filtered(lambda r: r.id == fid) + fname = field.name if field else col.field_id.name cmds.append((1, col.id, {'field_name': fname})) - # 3) 删掉不在 field_ids 里的 - for fid, col in kept.items(): - if fid not in new_field_ids: - cmds.append((2, col.id)) - # 4) 补充 field_ids 里有但 column_ids 里没有的 - seq = max((col.sequence for col in self.column_ids), default=0) + 10 + + # ---- 步骤 4:field_ids 中有、但 column_ids 中没有的 → 新建 ---- + seq = max( + (c.sequence for c in self.column_ids if c.sequence), + default=0 + ) + 10 for f in self.field_ids: if f.id not in kept: cmds.append((0, 0, { @@ -335,28 +318,35 @@ class AiReportTemplate(models.Model): 'sequence': seq, })) seq += 10 + if cmds: self.column_ids = cmds + # ===== 辅助:从 vals 命令中提取 field_ids 并填充 field_list / field_labels ===== @api.model def _sync_field_list_from_ids(self, vals): - import json + """从 vals['field_ids'](Many2many 命令格式)提取 ID 集合, + 无条件覆盖 field_list 和 field_labels(因为 field_ids 变了)""" 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: - ids = cmd[2] + if cmd[0] == 6: # (6, 0, ids) → 全量替换 + ids = list(cmd[2]) if cmd[2] else [] break - elif cmd[0] == 4: + elif cmd[0] == 4: # (4, id) → 追加 ids.append(cmd[1]) + elif cmd[0] == 5: # (5,) → 清空所有 + ids = [] + break 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 - ) + vals['field_list'] = json.dumps( + [f.name for f in fields_records], ensure_ascii=False) + vals['field_labels'] = json.dumps( + {f.name: f.field_description for f in fields_records}, + ensure_ascii=False) + else: + vals['field_list'] = '[]' + vals['field_labels'] = '{}'