158 lines
5.7 KiB
Python
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',
|
|
}
|