bpmn.ts 3.67 KB
// ProcessModel → ELK-BPMN JSON(bpmn-elk-layout 的输入)+ 点击详情表。
// 节点 id 重编为合法 NCName(Node_N),序列流端点按映射改写,与详情表 id 完全一致。

import type { Ontology, ProcessModel, ProcessNode } from './types'

const EVENT_TYPES = new Set(['startEvent', 'endEvent', 'intermediateCatchEvent'])
const GATEWAY_TYPES = new Set(['exclusiveGateway', 'parallelGateway'])

function elkNode(n: ProcessNode, nid: string) {
  const bpmn: any = {}
  if (EVENT_TYPES.has(n.type)) { bpmn.type = n.type; bpmn.eventDefinitionType = 'none' }
  else if (GATEWAY_TYPES.has(n.type)) bpmn.type = n.type
  else bpmn.type = n.type === 'userTask' ? 'userTask' : 'task'
  if (n.name) bpmn.name = n.name
  return { id: nid, bpmn }
}

// 仅加大层间距,给分支条件标签留位置;其余布局用库默认。
const ELK_OPTS = { 'elk.layered.spacing.nodeNodeBetweenLayers': 90 }

/** ProcessModel → ELK-BPMN JSON。 */
export function toElkBpmn(pm: ProcessModel): any {
  const idmap = new Map<string, string>()
  pm.nodes.forEach((n, i) => idmap.set(n.id, `Node_${i + 1}`))

  const edges = pm.flows.flatMap((f, i) => {
    const s = idmap.get(f.source), t = idmap.get(f.target)
    if (!s || !t) return []
    const b: any = { type: 'sequenceFlow' }
    const label = f.name || f.condition
    if (label) b.name = label
    if (f.condition) b.conditionExpression = { body: f.condition }
    return [{ id: `Flow_${i + 1}`, sources: [s], targets: [t], bpmn: b }]
  })

  return {
    id: 'definitions',
    children: [{
      id: 'Process_1',
      bpmn: { type: 'process' },
      layoutOptions: { ...ELK_OPTS },
      children: pm.nodes.map((n) => elkNode(n, idmap.get(n.id)!)),
      edges,
    }],
  }
}

const KIND_TEXT: Record<string, string> = {
  startEvent: '开始事件', endEvent: '结束事件', task: '任务', userTask: '人工任务',
  exclusiveGateway: '排他网关(分支判断)', parallelGateway: '并行网关', intermediateCatchEvent: '中间事件',
}

/** bpmn 元素 id → 详情(点击查看:任务关联行为、网关分支、连线条件)。 */
export function detailMap(pm: ProcessModel, onto: Ontology): Record<string, any> {
  const idmap = new Map<string, string>()
  pm.nodes.forEach((n, i) => idmap.set(n.id, `Node_${i + 1}`))
  const nameOf = (id: string) => pm.nodes.find((n) => n.id === id)?.name || id
  const details: Record<string, any> = {}

  for (const n of pm.nodes) {
    const d: any = { kind: n.type, kindLabel: KIND_TEXT[n.type] || n.type, name: n.name || '', lane: n.lane || '' }
    if (n.behaviorId) {
      const b = onto.behaviors.find((x) => x.id === n.behaviorId)
      if (b) {
        const owner = onto.objects.find((o) => o.id === b.owner_object_id)
        d.behavior = {
          id: b.id, name: b.name, type: b.type, commandType: b.commandType,
          owner: owner ? owner.name : b.owner_object_id, description: b.description || '',
          preconditions: b.preconditions, effects: b.effects,
          produced_events: b.produced_events,
          applied_rules: b.applied_rules.map((rid) => onto.rules.find((r) => r.id === rid)?.name || rid),
        }
      }
    }
    if (GATEWAY_TYPES.has(n.type)) {
      d.branches = pm.flows.filter((f) => f.source === n.id).map((f) => ({
        to: nameOf(f.target), label: f.name || '', condition: f.condition || '',
      }))
    }
    details[idmap.get(n.id)!] = d
  }

  pm.flows.forEach((f, i) => {
    if (!idmap.get(f.source) || !idmap.get(f.target)) return
    details[`Flow_${i + 1}`] = {
      kind: 'sequenceFlow', kindLabel: '连线 / 分支', name: f.name || '', condition: f.condition || '',
      from: nameOf(f.source), to: nameOf(f.target),
    }
  })
  return details
}