Files
yuthon_base/project_md/models/project_mindmap_extend.py
2026-07-23 16:08:07 +08:00

107 lines
4.2 KiB
Python

from odoo import api, models
class ProjectProject(models.Model):
_inherit = 'project.project'
@api.model
def get_mindmap_tree(self, single_id=None):
"""返回 项目→阶段→任务 的树形结构(dict),供 mindmap 视图渲染。
不建中间表:直接利用已有的
project.project.type_ids(阶段)/ project.task.project_id / project.task.stage_id。
字段是否存在在服务端用 _fields 直接判断,杜绝 JS 侧写死字段导致的
Invalid field / 白屏。
"""
self = self.sudo()
# 1) 项目(单项目视角则只取当前项目)
proj_domain = [('id', '=', single_id)] if single_id else []
projects = self.search_read(proj_domain, ['name', 'type_ids'])
# 2) 阶段:经各项目 type_ids 收集、保序去重
stage_ids = []
for p in projects:
stage_ids += p['type_ids']
stage_ids = list(dict.fromkeys(stage_ids))
stages = self.env['project.task.type'].search_read(
[('id', 'in', stage_ids)], ['name', 'sequence']
) if stage_ids else []
stage_by_id = {s['id']: s for s in stages}
# 3) 任务:进度字段在部分版本/客制下不存在,服务端安全探测
task_fields = ['name', 'display_name', 'project_id', 'stage_id']
tk = self.env['project.task']
progress_field = None
if 'subtask_completion_percentage' in tk._fields:
progress_field = 'subtask_completion_percentage'
elif 'progress' in tk._fields:
progress_field = 'progress'
if progress_field:
task_fields.append(progress_field)
task_domain = [('project_id', '=', single_id)] if single_id else []
tasks = tk.search_read(task_domain, task_fields)
def _norm(v):
if v is None or v is False:
return 0
try:
v = float(v)
except (TypeError, ValueError):
return 0
# 部分字段存 0-1,有的存 0-100;统一归一到 0-100
return int(v) if v > 1 else int(v * 100)
project_nodes = {}
for p in projects:
project_nodes[p['id']] = {
'id': 'P%d' % p['id'],
'name': p['name'] or ('项目%s' % p['id']),
'kind': 'project',
'progress': 0,
'children': [],
}
for p in projects:
pn = project_nodes[p['id']]
proj_stages = [stage_by_id[i] for i in p['type_ids'] if i in stage_by_id]
proj_stages.sort(key=lambda s: s.get('sequence') or 0)
for st in proj_stages:
sn = {
'id': 'P%d_S%d' % (p['id'], st['id']),
'name': st['name'],
'kind': 'stage',
'progress': 0,
'children': [],
}
pn['children'].append(sn)
st_tasks = [
t for t in tasks
if t.get('project_id') and t['project_id'][0] == p['id']
and t.get('stage_id') and t['stage_id'][0] == st['id']
]
for t in st_tasks:
raw = t.get(progress_field) if progress_field else None
sn['children'].append({
'id': 'T%d' % t['id'],
'name': t.get('display_name') or t['name'],
'kind': 'task',
'resId': t['id'],
'progress': _norm(raw),
})
sn['progress'] = round(
sum(c['progress'] for c in sn['children']) / len(sn['children'])
) if sn['children'] else 0
pn['progress'] = round(
sum(c['progress'] for c in pn['children']) / len(pn['children'])
) if pn['children'] else 0
roots = list(project_nodes.values())
if single_id and len(roots) == 1:
return roots[0]
return {
'id': 0,
'name': '所有项目' if not single_id else '项目阶段与任务',
'children': roots,
}