@steedos-labs/plugin-workflow 3.0.0-beta.9 → 3.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main/default/client/instance.client.js +6 -6
- package/main/default/client/object_workflows.client.js +8 -7
- package/main/default/client/socket.client.js +46 -3
- package/main/default/manager/import.js +17 -1
- package/main/default/manager/instance_manager.js +20 -6
- package/main/default/manager/push_manager.js +20 -12
- package/main/default/manager/uuflowManagerForInitApproval.js +794 -0
- package/main/default/manager/uuflow_manager.js +53 -4
- package/main/default/manager/workflow_manager.js +1 -1
- package/main/default/methods/instance_approve.js +258 -0
- package/main/default/methods/trace_approve_cc.js +571 -0
- package/main/default/objectTranslations/flows.en/flows.en.objectTranslation.yml +19 -0
- package/main/default/objectTranslations/flows.zh-CN/flows.zh-CN.objectTranslation.yml +19 -0
- package/main/default/objectTranslations/forms.en/forms.en.objectTranslation.yml +191 -0
- package/main/default/objectTranslations/forms.zh-CN/forms.zh-CN.objectTranslation.yml +246 -0
- package/main/default/objectTranslations/instance_tasks.en/instance_tasks.en.objectTranslation.yml +213 -0
- package/main/default/objectTranslations/instance_tasks.zh-CN/instance_tasks.zh-CN.objectTranslation.yml +213 -0
- package/main/default/objectTranslations/instances.en/instances.en.objectTranslation.yml +212 -0
- package/main/default/objectTranslations/instances.zh-CN/instances.zh-CN.objectTranslation.yml +209 -0
- package/main/default/objects/categories.object.yml +1 -0
- package/main/default/objects/flows/buttons/del.button.yml +7 -10
- package/main/default/objects/flows/buttons/design_form_layout.button.js +5 -2
- package/main/default/objects/flows/buttons/distributeAdmin.button.yml +5 -5
- package/main/default/objects/flows/buttons/newexport.button.yml +1 -1
- package/main/default/objects/flows/buttons/newimport.button.yml +2 -1
- package/main/default/objects/flows/flows.object.yml +12 -4
- package/main/default/objects/forms/forms.object.yml +85 -0
- package/main/default/objects/instance_tasks/buttons/instance_new.button.yml +3 -5
- package/main/default/objects/instances/buttons/instance_cc.button.yml +7 -7
- package/main/default/objects/instances/buttons/instance_delete.button.yml +2 -2
- package/main/default/objects/instances/buttons/instance_delete_many.button.yml +6 -6
- package/main/default/objects/instances/buttons/instance_distribute.button.yml +14 -13
- package/main/default/objects/instances/buttons/instance_flow_chart.button.yml +1 -1
- package/main/default/objects/instances/buttons/instance_forward.button.yml +8 -8
- package/main/default/objects/instances/buttons/instance_new.button.yml +3 -5
- package/main/default/objects/instances/buttons/instance_reassign.button.yml +1 -16
- package/main/default/objects/instances/buttons/instance_related.button.yml +4 -4
- package/main/default/objects/instances/buttons/instance_relocate.button.yml +9 -12
- package/main/default/objects/instances/buttons/instance_retrieve.button.yml +106 -2
- package/main/default/objects/instances/buttons/instance_save.button.yml +3 -9
- package/main/default/objects/instances/buttons/instance_submit.button.yml +1 -1
- package/main/default/objects/instances/buttons/instance_terminate.button.yml +7 -10
- package/main/default/objects/instances/listviews/monitor.listview.yml +0 -1
- package/main/default/pages/flowdetail.page.amis.json +11 -11
- package/main/default/pages/instance_detail.page.amis.json +25 -37
- package/main/default/pages/instance_tasks_detail.page.amis.json +21 -5
- package/main/default/pages/instance_tasks_list.page.amis.json +147 -110
- package/main/default/pages/instances_list.page.amis.json +146 -110
- package/main/default/routes/afterHook.js +34 -0
- package/main/default/routes/am.router.js +49 -2
- package/main/default/routes/api_cc.router.js +5 -12
- package/main/default/routes/api_flow_permission.router.js +7 -2
- package/main/default/routes/api_get_object_workflows.router.js +79 -22
- package/main/default/routes/api_have_read.router.js +73 -0
- package/main/default/routes/api_object_workflow_drafts.router.js +18 -19
- package/main/default/routes/api_workflow_approve_save.router.js +2 -1
- package/main/default/routes/api_workflow_chart.router.js +682 -0
- package/main/default/routes/api_workflow_engine.router.js +4 -4
- package/main/default/routes/api_workflow_flow_version.router.js +61 -0
- package/main/default/routes/api_workflow_form_version.router.js +61 -0
- package/main/default/routes/api_workflow_instance_return.router.js +164 -167
- package/main/default/routes/api_workflow_next_step_users.router.js +13 -8
- package/main/default/routes/api_workflow_reassign.router.js +200 -196
- package/main/default/routes/api_workflow_relocate.router.js +4 -3
- package/main/default/routes/api_workflow_retrieve.router.js +246 -237
- package/main/default/routes/export.router.js +5 -4
- package/main/default/routes/flow_form_design.ejs +33 -8
- package/main/default/routes/flow_form_design.router.js +4 -3
- package/main/default/routes/import.router.js +6 -7
- package/main/default/services/flows.service.js +1 -1
- package/main/default/translations/en.translation.yml +5 -0
- package/main/default/translations/zh-CN.translation.yml +2 -1
- package/main/default/triggers/amis_form_design.trigger.js +27 -5
- package/main/default/triggers/instances.trigger.js +9 -7
- package/main/default/utils/designerManager.js +12 -5
- package/package.json +4 -4
- package/package.service.js +21 -7
- package/public/workflow/index.css +4 -0
- package/src/instance_record_queue.js +1 -3
- package/src/rests/api_workflow_instance_batch_remove.js +4 -3
- package/src/webhook_queue.js +283 -0
- package/main/default/manager/index.js +0 -23
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// @ts-check
|
|
3
|
+
const express = require("express");
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
const { requireAuthentication } = require("@steedos/auth");
|
|
6
|
+
const _ = require('lodash');
|
|
7
|
+
const { getCollection } = require("../utils/collection");
|
|
8
|
+
const WorkflowManager = require('../manager/workflow_manager');
|
|
9
|
+
const getHandlersManager = require('../manager/handlers_manager');
|
|
10
|
+
const { t } = require('@steedos/i18n')
|
|
11
|
+
|
|
12
|
+
const FlowversionAPI = {
|
|
13
|
+
traceMaxApproveCount: 10,
|
|
14
|
+
traceSplitApprovesIndex: 5,
|
|
15
|
+
isExpandApprove: false,
|
|
16
|
+
|
|
17
|
+
getAbsoluteUrl: function (url) {
|
|
18
|
+
const rootUrl = (typeof __meteor_runtime_config__ !== "undefined" && __meteor_runtime_config__?.ROOT_URL_PATH_PREFIX) || "";
|
|
19
|
+
if (rootUrl) {
|
|
20
|
+
url = rootUrl + url;
|
|
21
|
+
}
|
|
22
|
+
return url;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
writeResponse: function (res, httpCode, body) {
|
|
26
|
+
res.statusCode = httpCode;
|
|
27
|
+
res.end(body);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
sendInvalidURLResponse: function (res) {
|
|
31
|
+
return this.writeResponse(res, 404, "url must has querys as instance_id.");
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
sendAuthTokenExpiredResponse: function (res) {
|
|
35
|
+
return this.writeResponse(res, 401, "the auth_token has expired.");
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
replaceErrorSymbol: function (str) {
|
|
39
|
+
return str.replace(/\"/g, """).replace(/\n/g, "<br/>");
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
getStepHandlerName: async function (step, insId) {
|
|
43
|
+
const db = {
|
|
44
|
+
users: await getCollection('users'),
|
|
45
|
+
// flow_roles: await getCollection('flow_roles')
|
|
46
|
+
};
|
|
47
|
+
// 获取当前用户userId,默认空字符串即可
|
|
48
|
+
let loginUserId = '';
|
|
49
|
+
let stepId = step._id;
|
|
50
|
+
let stepHandlerName = "";
|
|
51
|
+
try {
|
|
52
|
+
if (step.step_type === "condition") {
|
|
53
|
+
return stepHandlerName;
|
|
54
|
+
}
|
|
55
|
+
let userIds = await getHandlersManager.getHandlers(insId, stepId, loginUserId);
|
|
56
|
+
let usersCol = db.users;
|
|
57
|
+
let approverNames = [];
|
|
58
|
+
for (let userId of userIds) {
|
|
59
|
+
let user = await usersCol.findOne({ _id: userId }, { projection: { name: 1 } });
|
|
60
|
+
if (user) {
|
|
61
|
+
approverNames.push(user.name);
|
|
62
|
+
} else {
|
|
63
|
+
approverNames.push("");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (approverNames.length > 3) {
|
|
67
|
+
stepHandlerName = approverNames.slice(0, 3).join(",") + "...";
|
|
68
|
+
} else {
|
|
69
|
+
stepHandlerName = approverNames.join(",");
|
|
70
|
+
}
|
|
71
|
+
return stepHandlerName;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
/*
|
|
76
|
+
// switch step.deal_type
|
|
77
|
+
// when 'specifyUser'
|
|
78
|
+
// approverNames = step.approver_users.map (userId)->
|
|
79
|
+
// user = db.users.findOne(userId)
|
|
80
|
+
// if user
|
|
81
|
+
// return user.name
|
|
82
|
+
// else
|
|
83
|
+
// return ""
|
|
84
|
+
// stepHandlerName = approverNames.join(",")
|
|
85
|
+
// when 'applicantRole'
|
|
86
|
+
// approverNames = step.approver_roles.map (roleId)->
|
|
87
|
+
// role = db.flow_roles.findOne(roleId)
|
|
88
|
+
// if role
|
|
89
|
+
// return role.name
|
|
90
|
+
// else
|
|
91
|
+
// return ""
|
|
92
|
+
// stepHandlerName = approverNames.join(",")
|
|
93
|
+
// else
|
|
94
|
+
// stepHandlerName = ''
|
|
95
|
+
// break
|
|
96
|
+
// return stepHandlerName
|
|
97
|
+
*/
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
getStepLabel: function (stepName, stepHandlerName) {
|
|
101
|
+
// 返回sstepName与stepHandlerName结合的步骤显示名称
|
|
102
|
+
let nodeStr = "";
|
|
103
|
+
if (stepName) {
|
|
104
|
+
nodeStr = `<div class='graph-node'>
|
|
105
|
+
<div class='step-name'>${stepName}</div>
|
|
106
|
+
<div class='step-handler-name'>${stepHandlerName}</div>
|
|
107
|
+
</div>`;
|
|
108
|
+
// 把特殊字符清空或替换,以避免mermaidAPI出现异常
|
|
109
|
+
nodeStr = FlowversionAPI.replaceErrorSymbol(nodeStr);
|
|
110
|
+
}
|
|
111
|
+
return nodeStr;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
getStepName: async function (step, cachedStepNames, instance_id) {
|
|
115
|
+
// 返回step节点名称,优先从缓存cachedStepNames中取,否则调用getStepLabel生成
|
|
116
|
+
let cachedStepName = cachedStepNames[step._id];
|
|
117
|
+
if (cachedStepName) {
|
|
118
|
+
return cachedStepName;
|
|
119
|
+
}
|
|
120
|
+
let stepHandlerName = await FlowversionAPI.getStepHandlerName(step, instance_id);
|
|
121
|
+
let stepName = FlowversionAPI.getStepLabel(step.name, stepHandlerName);
|
|
122
|
+
cachedStepNames[step._id] = stepName;
|
|
123
|
+
return stepName;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
generateStepsGraphSyntax: async function (steps, currentStepId, isConvertToString, direction, instance_id) {
|
|
127
|
+
let nodes = [`graph ${direction}`];
|
|
128
|
+
let cachedStepNames = {};
|
|
129
|
+
for (let step of steps) {
|
|
130
|
+
let lines = step.lines;
|
|
131
|
+
if (lines?.length) {
|
|
132
|
+
for (let line of lines) {
|
|
133
|
+
let stepName = "";
|
|
134
|
+
// 标记条件节点
|
|
135
|
+
if (step.name) {
|
|
136
|
+
if (step.step_type === "condition") {
|
|
137
|
+
nodes.push(` class ${step._id} condition;`);
|
|
138
|
+
}
|
|
139
|
+
stepName = await FlowversionAPI.getStepName(step, cachedStepNames, instance_id);
|
|
140
|
+
} else {
|
|
141
|
+
stepName = "";
|
|
142
|
+
}
|
|
143
|
+
// 原findPropertyByPK("_id",line.to_step),转为find
|
|
144
|
+
let toStep = steps.find(s => s._id === line.to_step);
|
|
145
|
+
let toStepName = await FlowversionAPI.getStepName(toStep, cachedStepNames, instance_id);
|
|
146
|
+
nodes.push(` ${step._id}("${stepName}")-->${line.to_step}("${toStepName}")`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (currentStepId) {
|
|
151
|
+
nodes.push(` class ${currentStepId} current-step-node;`);
|
|
152
|
+
}
|
|
153
|
+
if (isConvertToString) {
|
|
154
|
+
let graphSyntax = nodes.join("\n");
|
|
155
|
+
return graphSyntax;
|
|
156
|
+
} else {
|
|
157
|
+
return nodes;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
getApproveJudgeText: function (judge) {
|
|
162
|
+
let locale = "zh-CN";
|
|
163
|
+
let judgeText = '';
|
|
164
|
+
switch (judge) {
|
|
165
|
+
case 'approved':
|
|
166
|
+
// 已核准
|
|
167
|
+
judgeText = t('Instance State approved', {}, locale); break;
|
|
168
|
+
case 'rejected':
|
|
169
|
+
// 已驳回
|
|
170
|
+
judgeText = t('Instance State rejected', {}, locale); break;
|
|
171
|
+
case 'terminated':
|
|
172
|
+
// 已取消
|
|
173
|
+
judgeText = t('Instance State terminated', {}, locale); break;
|
|
174
|
+
case 'reassigned':
|
|
175
|
+
// 转签核
|
|
176
|
+
judgeText = t('Instance State reassigned', {}, locale); break;
|
|
177
|
+
case 'relocated':
|
|
178
|
+
// 重定位
|
|
179
|
+
judgeText = t('Instance State relocated', {}, locale); break;
|
|
180
|
+
case 'retrieved':
|
|
181
|
+
// 已取回
|
|
182
|
+
judgeText = t('Instance State retrieved', {}, locale); break;
|
|
183
|
+
case 'returned':
|
|
184
|
+
// 已退回
|
|
185
|
+
judgeText = t('Instance State returned', {}, locale); break;
|
|
186
|
+
case 'readed':
|
|
187
|
+
// 已阅
|
|
188
|
+
judgeText = t('Instance State readed', {}, locale); break;
|
|
189
|
+
default:
|
|
190
|
+
judgeText = '';
|
|
191
|
+
}
|
|
192
|
+
return judgeText;
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
getTraceName: function (traceName, approveHandlerName) {
|
|
196
|
+
// 返回trace节点名称
|
|
197
|
+
let nodeStr = "";
|
|
198
|
+
if (traceName) {
|
|
199
|
+
nodeStr = `<div class='graph-node'>
|
|
200
|
+
<div class='trace-name'>${traceName}</div>
|
|
201
|
+
<div class='trace-handler-name'>${approveHandlerName}</div>
|
|
202
|
+
</div>`;
|
|
203
|
+
nodeStr = FlowversionAPI.replaceErrorSymbol(nodeStr);
|
|
204
|
+
}
|
|
205
|
+
return nodeStr;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
getTraceFromApproveCountersWithType: function (trace) {
|
|
209
|
+
// 该函数生成json结构,表现出所有传阅、分发、转发节点有有后续子节点的计数情况,其结构为:
|
|
210
|
+
// counters = {
|
|
211
|
+
// [fromApproveId(来源节点ID)]:{
|
|
212
|
+
// [toApproveType(目标结点类型)]:目标节点在指定类型下的后续节点个数
|
|
213
|
+
// }
|
|
214
|
+
// }
|
|
215
|
+
let counters = {};
|
|
216
|
+
let approves = trace.approves;
|
|
217
|
+
if (!approves) return null;
|
|
218
|
+
approves.forEach(function (approve) {
|
|
219
|
+
if (approve.from_approve_id) {
|
|
220
|
+
if (!counters[approve.from_approve_id]) counters[approve.from_approve_id] = {};
|
|
221
|
+
if (counters[approve.from_approve_id][approve.type]) {
|
|
222
|
+
counters[approve.from_approve_id][approve.type]++;
|
|
223
|
+
} else {
|
|
224
|
+
counters[approve.from_approve_id][approve.type] = 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return counters;
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
getTraceCountersWithType: function (trace, traceFromApproveCounters) {
|
|
232
|
+
// 该函数生成json结构,表现出所有传阅、分发、转发的节点流向,其结构为:
|
|
233
|
+
// counters = {
|
|
234
|
+
// [fromApproveId(来源节点ID)]:{
|
|
235
|
+
// [toApproveType(目标结点类型)]:[{
|
|
236
|
+
// from_type: 来源节点类型
|
|
237
|
+
// from_approve_handler_name: 来源节点处理人
|
|
238
|
+
// to_approve_id: 目标节点ID
|
|
239
|
+
// to_approve_handler_names: [多个目标节点汇总处理人集合]
|
|
240
|
+
// is_total: true/false,是否汇总节点
|
|
241
|
+
// },...]
|
|
242
|
+
// }
|
|
243
|
+
// }
|
|
244
|
+
// 上述目标结点内容中有一个属性is_total表示是否汇总节点,如果是,则把多个节点汇总合并成一个,
|
|
245
|
+
// 但是本身有后续子节点的节点不参与汇总及计数。
|
|
246
|
+
let counters = {};
|
|
247
|
+
let approves = trace.approves;
|
|
248
|
+
if (!approves) return null;
|
|
249
|
+
let traceMaxApproveCount = FlowversionAPI.traceMaxApproveCount;
|
|
250
|
+
let isExpandApprove = FlowversionAPI.isExpandApprove;
|
|
251
|
+
approves.forEach(function (toApprove) {
|
|
252
|
+
let toApproveType = toApprove.type;
|
|
253
|
+
let toApproveFromId = toApprove.from_approve_id;
|
|
254
|
+
let toApproveHandlerName = toApprove.handler_name;
|
|
255
|
+
if (!toApproveFromId) return;
|
|
256
|
+
approves.forEach(function (fromApprove) {
|
|
257
|
+
if (fromApprove._id == toApproveFromId) {
|
|
258
|
+
let counter = counters[toApproveFromId];
|
|
259
|
+
if (!counter) counter = counters[toApproveFromId] = {};
|
|
260
|
+
if (!counter[toApprove.type]) counter[toApprove.type] = [];
|
|
261
|
+
let counter2 = counter[toApprove.type];
|
|
262
|
+
if (traceFromApproveCounters[toApprove._id]?.[toApproveType]) {
|
|
263
|
+
// 有后续子节点,则不参与汇总及计数
|
|
264
|
+
counter2.push({
|
|
265
|
+
from_type: fromApprove.type,
|
|
266
|
+
from_approve_handler_name: fromApprove.handler_name,
|
|
267
|
+
to_approve_id: toApprove._id,
|
|
268
|
+
to_approve_handler_name: toApprove.handler_name
|
|
269
|
+
});
|
|
270
|
+
} else {
|
|
271
|
+
// 原findPropertyByPK("is_total", true),转为find
|
|
272
|
+
let counterContent = isExpandApprove ? null : counter2.find(item => item.is_total === true);
|
|
273
|
+
// 如果强制要求展开所有节点,则不做汇总处理
|
|
274
|
+
if (counterContent) {
|
|
275
|
+
counterContent.count++;
|
|
276
|
+
if (!(counterContent.count > traceMaxApproveCount)) {
|
|
277
|
+
counterContent.to_approve_handler_names.push(toApprove.handler_name);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
counter2.push({
|
|
281
|
+
from_type: fromApprove.type,
|
|
282
|
+
from_approve_handler_name: fromApprove.handler_name,
|
|
283
|
+
to_approve_id: toApprove._id,
|
|
284
|
+
count: 1,
|
|
285
|
+
to_approve_handler_names: [toApprove.handler_name],
|
|
286
|
+
is_total: true
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
return counters;
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
pushApprovesWithTypeGraphSyntax: function (nodes, trace) {
|
|
297
|
+
let traceFromApproveCounters = FlowversionAPI.getTraceFromApproveCountersWithType(trace);
|
|
298
|
+
let traceCounters = FlowversionAPI.getTraceCountersWithType(trace, traceFromApproveCounters);
|
|
299
|
+
if (!traceCounters) return;
|
|
300
|
+
let extraHandlerNamesCounter = {}; //记录需要额外生成所有处理人姓名的被传阅、分发、转发节点
|
|
301
|
+
let traceMaxApproveCount = FlowversionAPI.traceMaxApproveCount;
|
|
302
|
+
let splitIndex = FlowversionAPI.traceSplitApprovesIndex;
|
|
303
|
+
let currentTraceName = trace.name;
|
|
304
|
+
for (let fromApproveId in traceCounters) {
|
|
305
|
+
let fromApprove = traceCounters[fromApproveId];
|
|
306
|
+
for (let toApproveType in fromApprove) {
|
|
307
|
+
let toApproves = fromApprove[toApproveType];
|
|
308
|
+
toApproves.forEach(function (toApprove) {
|
|
309
|
+
let typeName = "";
|
|
310
|
+
switch (toApproveType) {
|
|
311
|
+
case 'cc': typeName = "传阅"; break;
|
|
312
|
+
case 'forward': typeName = "转发"; break;
|
|
313
|
+
case 'distribute': typeName = "分发"; break;
|
|
314
|
+
}
|
|
315
|
+
let isTypeNode = ["cc", "forward", "distribute"].indexOf(toApprove.from_type) >= 0;
|
|
316
|
+
let traceName;
|
|
317
|
+
if (isTypeNode) {
|
|
318
|
+
traceName = toApprove.from_approve_handler_name;
|
|
319
|
+
} else {
|
|
320
|
+
traceName = FlowversionAPI.getTraceName(currentTraceName, toApprove.from_approve_handler_name);
|
|
321
|
+
}
|
|
322
|
+
let strToHandlerNames = "";
|
|
323
|
+
if (toApprove.is_total) {
|
|
324
|
+
let toHandlerNames = toApprove.to_approve_handler_names;
|
|
325
|
+
if (splitIndex && toApprove.count > splitIndex) {
|
|
326
|
+
// 在姓名集合中插入回车符号换行
|
|
327
|
+
toHandlerNames.splice(splitIndex, 0, "<br/>,");
|
|
328
|
+
}
|
|
329
|
+
strToHandlerNames = toHandlerNames.join(",").replace(",,", "");
|
|
330
|
+
let extraCount = toApprove.count - traceMaxApproveCount;
|
|
331
|
+
if (extraCount > 0) {
|
|
332
|
+
strToHandlerNames += `等${toApprove.count}人`;
|
|
333
|
+
if (!extraHandlerNamesCounter[fromApproveId]) extraHandlerNamesCounter[fromApproveId] = {};
|
|
334
|
+
extraHandlerNamesCounter[fromApproveId][toApproveType] = toApprove.to_approve_id;
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
strToHandlerNames = toApprove.to_approve_handler_name;
|
|
338
|
+
}
|
|
339
|
+
if (isTypeNode) {
|
|
340
|
+
nodes.push(` ${fromApproveId}>\"${traceName}\"]--${typeName}-->${toApprove.to_approve_id}>\"${strToHandlerNames}\"]`);
|
|
341
|
+
} else {
|
|
342
|
+
nodes.push(` ${fromApproveId}(\"${traceName}\")--${typeName}-->${toApprove.to_approve_id}>\"${strToHandlerNames}\"]`);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// 为需要额外生成所有处理人姓名的被传阅、分发、转发节点,增加鼠标弹出详细层事件
|
|
348
|
+
// extraHandlerNamesCounter的结构为:
|
|
349
|
+
// counters = {
|
|
350
|
+
// [fromApproveId(来源节点ID)]:{
|
|
351
|
+
// [toApproveType(目标结点类型)]:目标结点ID
|
|
352
|
+
// }
|
|
353
|
+
// }
|
|
354
|
+
let approves = trace.approves;
|
|
355
|
+
if (!_.isEmpty(extraHandlerNamesCounter)) {
|
|
356
|
+
for (let fromApproveId in extraHandlerNamesCounter) {
|
|
357
|
+
let fromApprove = extraHandlerNamesCounter[fromApproveId];
|
|
358
|
+
for (let toApproveType in fromApprove) {
|
|
359
|
+
let toApproveId = fromApprove[toApproveType];
|
|
360
|
+
let tempHandlerNames = [];
|
|
361
|
+
approves.forEach(function (approve) {
|
|
362
|
+
if (fromApproveId == approve.from_approve_id) {
|
|
363
|
+
if (!(traceFromApproveCounters?.[approve._id]?.[toApproveType])) {
|
|
364
|
+
// 有后续子节点,则不参与汇总及计数
|
|
365
|
+
tempHandlerNames.push(approve.handler_name);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
nodes.push(` click ${toApproveId} callback \"${tempHandlerNames.join(",")}\"`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
generateTracesGraphSyntax: function (traces, isConvertToString, direction) {
|
|
376
|
+
let nodes = [`graph ${direction}`];
|
|
377
|
+
let lastTrace = null;
|
|
378
|
+
let lastApproves = [];
|
|
379
|
+
traces.forEach(function (trace) {
|
|
380
|
+
let lines = trace.previous_trace_ids;
|
|
381
|
+
let currentTraceName = trace.name;
|
|
382
|
+
if (lines?.length) {
|
|
383
|
+
for (let line of lines) {
|
|
384
|
+
// 原findPropertyByPK("_id",line),转为find
|
|
385
|
+
let fromTrace = traces.find(s => s._id == line);
|
|
386
|
+
let currentFromTraceName = fromTrace.name;
|
|
387
|
+
let fromApproves = fromTrace.approves;
|
|
388
|
+
let toApproves = trace.approves;
|
|
389
|
+
lastTrace = trace;
|
|
390
|
+
lastApproves = toApproves;
|
|
391
|
+
fromApproves.forEach(function (fromApprove) {
|
|
392
|
+
let fromApproveHandlerName = fromApprove.handler_name;
|
|
393
|
+
if (toApproves?.length) {
|
|
394
|
+
toApproves.forEach(function (toApprove) {
|
|
395
|
+
if (["cc", "forward", "distribute"].indexOf(toApprove.type) < 0) {
|
|
396
|
+
if (["cc", "forward", "distribute"].indexOf(fromApprove.type) < 0) {
|
|
397
|
+
let fromTraceName = FlowversionAPI.getTraceName(currentFromTraceName, fromApproveHandlerName);
|
|
398
|
+
let toTraceName = FlowversionAPI.getTraceName(currentTraceName, toApprove.handler_name);
|
|
399
|
+
// 不是传阅、分发、转发,则连接到下一个trace
|
|
400
|
+
let judgeText = FlowversionAPI.getApproveJudgeText(fromApprove.judge);
|
|
401
|
+
if (judgeText) {
|
|
402
|
+
nodes.push(` ${fromApprove._id}("${fromTraceName}")--${judgeText}-->${toApprove._id}("${toTraceName}")`);
|
|
403
|
+
} else {
|
|
404
|
+
nodes.push(` ${fromApprove._id}("${fromTraceName}")-->${toApprove._id}("${toTraceName}")`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
} else {
|
|
410
|
+
// 最后一个步骤的trace
|
|
411
|
+
if (["cc", "forward", "distribute"].indexOf(fromApprove.type) < 0) {
|
|
412
|
+
let fromTraceName = FlowversionAPI.getTraceName(currentFromTraceName, fromApproveHandlerName);
|
|
413
|
+
let toTraceName = FlowversionAPI.replaceErrorSymbol(currentTraceName);
|
|
414
|
+
// 不是传阅、分发、转发,则连接到下一个trace
|
|
415
|
+
let judgeText = FlowversionAPI.getApproveJudgeText(fromApprove.judge);
|
|
416
|
+
if (judgeText) {
|
|
417
|
+
nodes.push(` ${fromApprove._id}("${fromTraceName}")--${judgeText}-->${trace._id}("${toTraceName}")`);
|
|
418
|
+
} else {
|
|
419
|
+
nodes.push(` ${fromApprove._id}("${fromTraceName}")-->${trace._id}("${toTraceName}")`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
// 第一个trace,因traces可能只有一个,这时需要单独显示出来
|
|
427
|
+
trace.approves.forEach(function (approve) {
|
|
428
|
+
let traceName = FlowversionAPI.getTraceName(currentTraceName, approve.handler_name);
|
|
429
|
+
nodes.push(` ${approve._id}("${traceName}")`);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
FlowversionAPI.pushApprovesWithTypeGraphSyntax(nodes, trace);
|
|
433
|
+
});
|
|
434
|
+
// 签批历程中最后的approves高亮显示,结束步骤的trace中是没有approves的,所以结束步骤不高亮显示
|
|
435
|
+
lastApproves?.forEach(function (lastApprove) {
|
|
436
|
+
nodes.push(` class ${lastApprove._id} current-step-node;`);
|
|
437
|
+
});
|
|
438
|
+
if (isConvertToString) {
|
|
439
|
+
let graphSyntax = nodes.join("\n");
|
|
440
|
+
return graphSyntax;
|
|
441
|
+
} else {
|
|
442
|
+
return nodes;
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
sendHtmlResponse: async function (req, res, type) {
|
|
447
|
+
const db = {
|
|
448
|
+
instances: await getCollection('instances')
|
|
449
|
+
};
|
|
450
|
+
let query = req.query;
|
|
451
|
+
let instance_id = query.instance_id;
|
|
452
|
+
let direction = query.direction || 'TD';
|
|
453
|
+
const allowDirections = ['TB', 'BT', 'RL', 'LR', 'TD'];
|
|
454
|
+
if (!_.includes(allowDirections, direction)) {
|
|
455
|
+
return this.writeResponse(res, 500, "Invalid direction. The value of direction should be in ['TB', 'BT', 'RL', 'LR', 'TD']");
|
|
456
|
+
}
|
|
457
|
+
if (!instance_id) {
|
|
458
|
+
return FlowversionAPI.sendInvalidURLResponse(res);
|
|
459
|
+
}
|
|
460
|
+
let title = query.title;
|
|
461
|
+
if (title) {
|
|
462
|
+
title = decodeURIComponent(decodeURIComponent(title));
|
|
463
|
+
} else {
|
|
464
|
+
title = "Workflow Chart";
|
|
465
|
+
}
|
|
466
|
+
let error_msg = "";
|
|
467
|
+
let graphSyntax = "";
|
|
468
|
+
FlowversionAPI.isExpandApprove = false;
|
|
469
|
+
if (type === "traces_expand") {
|
|
470
|
+
type = "traces";
|
|
471
|
+
FlowversionAPI.isExpandApprove = true;
|
|
472
|
+
}
|
|
473
|
+
switch (type) {
|
|
474
|
+
case 'traces': {
|
|
475
|
+
let instance = await db.instances.findOne({ _id: instance_id }, { projection: { traces: 1 } });
|
|
476
|
+
if (instance) {
|
|
477
|
+
let traces = instance.traces;
|
|
478
|
+
if (traces?.length) {
|
|
479
|
+
graphSyntax = FlowversionAPI.generateTracesGraphSyntax(traces, false, direction);
|
|
480
|
+
} else {
|
|
481
|
+
error_msg = "没有找到当前申请单的流程步骤数据";
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
error_msg = "当前申请单不存在或已被删除";
|
|
485
|
+
}
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
default: {
|
|
489
|
+
let instance = await db.instances.findOne({ _id: instance_id }, { projection: { flow_version: 1, flow: 1, traces: { $slice: -1 } } });
|
|
490
|
+
if (instance) {
|
|
491
|
+
let currentStepId = instance.traces?.[0]?.step;
|
|
492
|
+
let flowversion = await WorkflowManager.getInstanceFlowVersion(instance);
|
|
493
|
+
let steps = flowversion?.steps;
|
|
494
|
+
if (steps?.length) {
|
|
495
|
+
graphSyntax = await FlowversionAPI.generateStepsGraphSyntax(steps, currentStepId, false, direction, instance_id);
|
|
496
|
+
} else {
|
|
497
|
+
error_msg = "没有找到当前申请单的流程步骤数据";
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
error_msg = "当前申请单不存在或已被删除";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
res.setHeader('Content-Type', 'text/html');
|
|
505
|
+
return this.writeResponse(res, 200, `
|
|
506
|
+
<!DOCTYPE html>
|
|
507
|
+
<html>
|
|
508
|
+
<head>
|
|
509
|
+
<meta charset="utf-8">
|
|
510
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes">
|
|
511
|
+
<title>${title}</title>
|
|
512
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
513
|
+
<meta name="theme-color" content="#000">
|
|
514
|
+
<meta name="application-name">
|
|
515
|
+
<script type="text/javascript" src="/unpkg.com/jquery@1.11.2/dist/jquery.min.js"></script>
|
|
516
|
+
<script type="text/javascript" src="/unpkg.com/mermaid@9.1.2/dist/mermaid.min.js"></script>
|
|
517
|
+
<style>
|
|
518
|
+
body {
|
|
519
|
+
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
520
|
+
text-align: center;
|
|
521
|
+
background-color: #fff;
|
|
522
|
+
}
|
|
523
|
+
.loading{
|
|
524
|
+
position: absolute;
|
|
525
|
+
left: 0px;
|
|
526
|
+
right: 0px;
|
|
527
|
+
top: 50%;
|
|
528
|
+
z-index: 1100;
|
|
529
|
+
text-align: center;
|
|
530
|
+
margin-top: -30px;
|
|
531
|
+
font-size: 36px;
|
|
532
|
+
color: #dfdfdf;
|
|
533
|
+
}
|
|
534
|
+
.error-msg{
|
|
535
|
+
position: absolute;
|
|
536
|
+
left: 0px;
|
|
537
|
+
right: 0px;
|
|
538
|
+
bottom: 20px;
|
|
539
|
+
z-index: 1100;
|
|
540
|
+
text-align: center;
|
|
541
|
+
font-size: 20px;
|
|
542
|
+
color: #a94442;
|
|
543
|
+
}
|
|
544
|
+
#flow-steps-svg .node rect{
|
|
545
|
+
fill: #ccccff;
|
|
546
|
+
stroke: rgb(144, 144, 255);
|
|
547
|
+
stroke-width: 2px;
|
|
548
|
+
}
|
|
549
|
+
#flow-steps-svg .node.current-step-node rect{
|
|
550
|
+
fill: #cde498;
|
|
551
|
+
stroke: #13540c;
|
|
552
|
+
stroke-width: 2px;
|
|
553
|
+
}
|
|
554
|
+
#flow-steps-svg .node.condition rect{
|
|
555
|
+
fill: #ececff;
|
|
556
|
+
stroke: rgb(204, 204, 255);
|
|
557
|
+
stroke-width: 1px;
|
|
558
|
+
}
|
|
559
|
+
#flow-steps-svg .node .trace-handler-name{
|
|
560
|
+
color: #777;
|
|
561
|
+
}
|
|
562
|
+
#flow-steps-svg .node .step-handler-name{
|
|
563
|
+
color: #777;
|
|
564
|
+
}
|
|
565
|
+
div.mermaidTooltip{
|
|
566
|
+
position: fixed!important;
|
|
567
|
+
text-align: left!important;
|
|
568
|
+
padding: 4px!important;
|
|
569
|
+
font-size: 14px!important;
|
|
570
|
+
max-width: 500px!important;
|
|
571
|
+
left: auto!important;
|
|
572
|
+
top: 15px!important;
|
|
573
|
+
right: 15px;
|
|
574
|
+
}
|
|
575
|
+
.btn-zoom{
|
|
576
|
+
background: rgba(0, 0, 0, 0.1);
|
|
577
|
+
border-color: transparent;
|
|
578
|
+
display: inline-block;
|
|
579
|
+
padding: 2px 10px;
|
|
580
|
+
font-size: 26px;
|
|
581
|
+
border-radius: 20px;
|
|
582
|
+
background: #eee;
|
|
583
|
+
color: #777;
|
|
584
|
+
position: fixed;
|
|
585
|
+
bottom: 15px;
|
|
586
|
+
outline: none;
|
|
587
|
+
cursor: pointer;
|
|
588
|
+
z-index: 99999;
|
|
589
|
+
-webkit-user-select: none;
|
|
590
|
+
-moz-user-select: none;
|
|
591
|
+
-ms-user-select: none;
|
|
592
|
+
user-select: none;
|
|
593
|
+
line-height: 1.2;
|
|
594
|
+
}
|
|
595
|
+
@media (max-width: 768px) {
|
|
596
|
+
.btn-zoom{
|
|
597
|
+
display:none;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
.btn-zoom:hover{
|
|
601
|
+
background: rgba(0, 0, 0, 0.2);
|
|
602
|
+
}
|
|
603
|
+
.btn-zoom-up{
|
|
604
|
+
left: 15px;
|
|
605
|
+
}
|
|
606
|
+
.btn-zoom-down{
|
|
607
|
+
left: 60px;
|
|
608
|
+
padding: 1px 13px 3px 13px;
|
|
609
|
+
}
|
|
610
|
+
</style>
|
|
611
|
+
</head>
|
|
612
|
+
<body>
|
|
613
|
+
<div class = "loading">Loading...</div>
|
|
614
|
+
<div class = "error-msg">${error_msg}</div>
|
|
615
|
+
<div class="mermaid"></div>
|
|
616
|
+
<script type="text/javascript">
|
|
617
|
+
mermaid.initialize({
|
|
618
|
+
startOnLoad:false
|
|
619
|
+
});
|
|
620
|
+
$(function(){
|
|
621
|
+
var graphNodes = ${JSON.stringify(graphSyntax)};
|
|
622
|
+
//方便前面可以通过调用mermaid.currentNodes调式,特意增加currentNodes属性。
|
|
623
|
+
mermaid.currentNodes = graphNodes;
|
|
624
|
+
var graphSyntax = graphNodes.join("\\n");
|
|
625
|
+
console.log(graphNodes);
|
|
626
|
+
console.log(graphSyntax);
|
|
627
|
+
console.log("You can access the graph nodes by 'mermaid.currentNodes' in the console of browser.");
|
|
628
|
+
$(".loading").remove();
|
|
629
|
+
var id = "flow-steps-svg";
|
|
630
|
+
var element = $('.mermaid');
|
|
631
|
+
var insertSvg = function(svgCode, bindFunctions) {
|
|
632
|
+
element.html(svgCode);
|
|
633
|
+
if(typeof callback !== 'undefined'){
|
|
634
|
+
callback(id);
|
|
635
|
+
}
|
|
636
|
+
bindFunctions(element[0]);
|
|
637
|
+
};
|
|
638
|
+
mermaid.render(id, graphSyntax, insertSvg, element[0]);
|
|
639
|
+
var zoomSVG = function(zoom){
|
|
640
|
+
var currentWidth = $("svg").width();
|
|
641
|
+
var newWidth = currentWidth * zoom;
|
|
642
|
+
$("svg").css("maxWidth",newWidth + "px").width(newWidth);
|
|
643
|
+
}
|
|
644
|
+
//支持鼠标滚轮缩放画布
|
|
645
|
+
$(window).on("mousewheel",function(event){
|
|
646
|
+
if(event.ctrlKey){
|
|
647
|
+
event.preventDefault();
|
|
648
|
+
var zoom = event.originalEvent.wheelDelta > 0 ? 1.1 : 0.9;
|
|
649
|
+
zoomSVG(zoom);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
$(".btn-zoom").on("click",function(){
|
|
653
|
+
zoomSVG($(this).attr("zoom"));
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
</script>
|
|
657
|
+
<a class="btn-zoom btn-zoom-up" zoom=1.1 title="点击放大">+</a>
|
|
658
|
+
<a class="btn-zoom btn-zoom-down" zoom=0.9 title="点击缩小">-</a>
|
|
659
|
+
</body>
|
|
660
|
+
</html>
|
|
661
|
+
`);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// /api/workflow/chart?instance_id=:instance_id
|
|
666
|
+
// 流程图
|
|
667
|
+
router.get('/api/workflow/chart', requireAuthentication, async function (req, res) {
|
|
668
|
+
await FlowversionAPI.sendHtmlResponse(req, res);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// /api/workflow/chart/traces?instance_id=:instance_id
|
|
672
|
+
// 汇总签批历程图
|
|
673
|
+
router.get('/api/workflow/chart/traces', requireAuthentication, async function (req, res) {
|
|
674
|
+
await FlowversionAPI.sendHtmlResponse(req, res, "traces");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// /api/workflow/chart/traces_expand?instance_id=:instance_id
|
|
678
|
+
// 展开所有节点的签批历程图
|
|
679
|
+
router.get('/api/workflow/chart/traces_expand', requireAuthentication, async function (req, res) {
|
|
680
|
+
await FlowversionAPI.sendHtmlResponse(req, res, "traces_expand");
|
|
681
|
+
});
|
|
682
|
+
exports.default = router;
|