Files
yuthon_base/yuthon_bpd/models/bpd_diagram.py
T
2026-09-22 15:09:11 +08:00

158 lines
5.7 KiB
Python

# -*- coding: utf-8 -*-
import json
import logging
from odoo import api, fields, models
try: # Odoo 各版本导出位置略有差异,双保险
from odoo.tools import file_open
except ImportError: # pragma: no cover
from odoo.tools.misc import file_open
_logger = logging.getLogger(__name__)
TEMPLATE_FILE = 'yuthon_bpd/static/src/bpd_default.json'
class BpdDiagram(models.Model):
"""业务流程图主模型。
一张图 = 若干泳道 + 若干节点 + 若干连线。坐标全部使用画布逻辑单位,
与前端 SVG 的 viewBox 一致,因此后端只存「逻辑坐标」,渲染缩放交给前端。
"""
_name = 'yuthon.bpd.diagram'
_description = '业务流程图'
_order = 'id'
name = fields.Char(string='名称', required=True)
key = fields.Char(string='关键字', index=True)
canvas_w = fields.Integer(string='画布宽', default=1560)
canvas_h = fields.Integer(string='画布高', default=980)
theme = fields.Text(
string='主题配色',
default='{}',
help='JSON 格式配色主题,前端读取后直接映射到节点/泳道/连线',
)
lane_ids = fields.One2many('yuthon.bpd.lane', 'diagram_id', string='泳道')
node_ids = fields.One2many('yuthon.bpd.node', 'diagram_id', string='节点')
edge_ids = fields.One2many('yuthon.bpd.edge', 'diagram_id', string='连线')
# ---------------- 模板装载 ----------------
@api.model
def _ensure_default_diagram(self):
"""安装后确保存在一张示例流程图(数据来自 bpd_default.json)。"""
if self.search_count([('key', '=', 'erp_mto')]):
return None
return self._load_template(TEMPLATE_FILE)
@api.model
def _load_template(self, path):
try:
with file_open(path, 'r') as f:
data = json.load(f)
except (IOError, OSError, ValueError) as exc:
_logger.warning('加载 BPD 模板失败: %s', exc)
return None
theme = data.get('theme') or {}
diagram = self.create({
'name': data.get('name') or '业务流程图',
'key': data.get('key'),
'canvas_w': (data.get('canvas') or {}).get('width', 1560),
'canvas_h': (data.get('canvas') or {}).get('height', 980),
'theme': json.dumps(theme, ensure_ascii=False),
})
lane_map = {}
for i, l in enumerate(data.get('lanes', [])):
lane = self.env['yuthon.bpd.lane'].create({
'diagram_id': diagram.id,
'name': l.get('name', '泳道'),
'kind': l.get('kind', 'column'),
'x': l.get('x', 0),
'y': l.get('y', 0),
'w': l.get('w', 300),
'h': l.get('h', 600),
'head_h': l.get('head_h', 34),
'label_pos': l.get('label_pos', 'top-center'),
'sequence': i,
})
lane_map[l.get('name')] = lane.id
node_map = {}
for i, n in enumerate(data.get('nodes', [])):
node = self.env['yuthon.bpd.node'].create({
'diagram_id': diagram.id,
'lane_id': lane_map.get(n.get('lane')) or False,
'label': n.get('label', ''),
'sub': n.get('sub', ''),
'x': n.get('x', 0),
'y': n.get('y', 0),
'w': n.get('w', 96),
'h': n.get('h', 34),
'shape': n.get('shape', 'box'),
'variant': n.get('variant', 'doc'),
'sequence': i,
})
node_map[n.get('id')] = node.id
for e in data.get('edges', []):
frm = node_map.get(e.get('from'))
to = node_map.get(e.get('to'))
if not frm or not to:
continue
self.env['yuthon.bpd.edge'].create({
'diagram_id': diagram.id,
'from_id': frm,
'to_id': to,
'label': e.get('label', ''),
'style': e.get('style', 'solid'),
})
_logger.info('已装载 BPD 示例流程图 key=%s', data.get('key'))
return diagram
# ---------------- 序列化 ----------------
def to_json(self):
self.ensure_one()
try:
theme = json.loads(self.theme or '{}') if self.theme else {}
except (ValueError, TypeError):
theme = {}
return {
'id': self.id,
'name': self.name,
'key': self.key or '',
'canvas': {'width': self.canvas_w, 'height': self.canvas_h},
'theme': theme,
'lanes': [{
'id': l.id,
'name': l.name,
'kind': l.kind,
'x': l.x, 'y': l.y, 'w': l.w, 'h': l.h,
'head_h': l.head_h,
'label_pos': l.label_pos,
} for l in self.lane_ids],
'nodes': [n.to_json() for n in self.node_ids],
'edges': [e.to_json() for e in self.edge_ids],
}
@api.model
def get_default(self):
"""前端拿图:优先 key=erp_mto,否则取第一张。"""
rec = self.search([('key', '=', 'erp_mto')], limit=1) \
or self.search([], limit=1, order='id')
return rec.to_json() if rec else None
# ---------------- 入口:后台跳前端 ----------------
def action_open_visual(self):
"""后台表单按钮:在新标签打开该图的可视化编辑页(带 diagram id)。"""
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'url': '/yuthon_bpd/static/src/bpd_view.html?id=%s' % self.id,
'target': 'new',
}