@steedos-labs/plugin-workflow 3.0.102 → 3.0.104

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.
@@ -5,7 +5,7 @@
5
5
  <link rel="shortcut icon" type="image/svg+xml" href="/images/logo.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>designer</title>
8
- <script type="module" crossorigin src="/api/workflow/designer-v2/assets/index-C7za9VGq.js"></script>
8
+ <script type="module" crossorigin src="/api/workflow/designer-v2/assets/index-CTmOPK1Y.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/api/workflow/designer-v2/assets/index-CWhD6d5C.css">
10
10
  </head>
11
11
  <body>
@@ -1,5 +1,5 @@
1
1
  waitForThing(window, 'antd').then(function(){
2
- var v = '3.0.102';
2
+ var v = '3.0.104';
3
3
  loadJs('/amis-renderer/amis-renderer.js?v=' + v);
4
4
  loadCss('/amis-renderer/amis-renderer.css?v=' + v)
5
5
  })
@@ -291,10 +291,12 @@ router.post('/api/workflow/v2/nextStepUsersValue', requireAuthentication, async
291
291
  for (let i = traces.length - 1; i >= 0; i--) {
292
292
  const trace = traces[i];
293
293
  if (trace.step === nextStepId && trace.approves && trace.approves.length > 0) {
294
- // 排除非实际处理人的 approve
294
+ // 排除非实际处理人的 approve;传阅/分发的 approve 不是本步骤处理人
295
295
  const excludedApproveJudges = ['returned', 'relocated', 'terminated', 'retrieved'];
296
+ const excludedApproveTypes = ['cc', 'distribute'];
296
297
  newNextUsers = trace.approves
297
- .filter(approve => !excludedApproveJudges.includes(approve.judge))
298
+ .filter(approve => !excludedApproveJudges.includes(approve.judge)
299
+ && !excludedApproveTypes.includes(approve.type))
298
300
  .map(approve => approve.handler);
299
301
  break;
300
302
  }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * 回归测试:/api/workflow/v2/nextStepUsersValue 的 traces 回退逻辑
3
+ * 不能把传阅(type='cc')/分发(type='distribute')的 approve 算作下一步处理人。
4
+ *
5
+ * 背景(线上现象):
6
+ * - 步骤A的审批人传阅给用户X → 步骤A当前 trace 里 push 一条
7
+ * {type:'cc', handler:X} 的 approve(未读时无 judge,提交意见后 judge='submitted')。
8
+ * - 审批人驳回到上一步 → 上一步待办点提交时,提交对话框通过本接口预填
9
+ * 下一步(=步骤A)处理人。
10
+ * - 回退逻辑只按 judge 排除(returned/relocated/terminated/retrieved),
11
+ * cc approve 的 judge 不在排除表 → 被传阅用户X被预填为步骤A处理人。
12
+ *
13
+ * 期望(修复后):
14
+ * - traces 回退取处理人时排除 type='cc' 与 type='distribute' 的 approve,
15
+ * 与引擎其他取实际处理人处(uuflow_manager L1139/L3086/L4862)口径一致。
16
+ * - 正常(无传阅)驳回场景行为不变。
17
+ *
18
+ * 用法:
19
+ * node main/default/test/test_next_step_users_value_cc.js
20
+ * 或 npm run test:next-step-users-cc
21
+ */
22
+
23
+ const assert = require('assert');
24
+ const Module = require('module');
25
+
26
+ // ---- 伪造无法在独立环境 resolve 的外部依赖(express / @steedos/*) ----
27
+ const capturedRoutes = {};
28
+ const fakeModules = {
29
+ 'express': {
30
+ Router: function () {
31
+ return {
32
+ post: function (path) {
33
+ capturedRoutes[path] = arguments[arguments.length - 1];
34
+ },
35
+ };
36
+ },
37
+ },
38
+ '@steedos/auth': {
39
+ requireAuthentication: function (req, res, next) { next(); },
40
+ },
41
+ '@steedos/objectql': {
42
+ getObject: function (name) {
43
+ return {
44
+ // space_users 校验:认为查询到的用户都在职,原样回显
45
+ find: async function (query) {
46
+ const inFilter = (query.filters || []).find(f => f[1] === 'in');
47
+ const userIds = inFilter ? inFilter[2] : [];
48
+ return userIds.map(u => ({ user: u, name: 'name_of_' + u }));
49
+ },
50
+ };
51
+ },
52
+ },
53
+ '@steedos/i18n': {
54
+ t: function (key) { return key; },
55
+ },
56
+ };
57
+ const origResolve = Module._resolveFilename;
58
+ Module._resolveFilename = function (request) {
59
+ if (fakeModules[request]) return 'fake:' + request;
60
+ return origResolve.apply(this, arguments);
61
+ };
62
+ Object.keys(fakeModules).forEach(function (name) {
63
+ require.cache['fake:' + name] = {
64
+ id: 'fake:' + name,
65
+ filename: 'fake:' + name,
66
+ loaded: true,
67
+ exports: fakeModules[name],
68
+ };
69
+ });
70
+
71
+ // ---- 伪造插件内部依赖 ----
72
+ function seedCache(relPath, exportsObj) {
73
+ const p = require.resolve(relPath);
74
+ require.cache[p] = { id: p, filename: p, loaded: true, exports: exportsObj };
75
+ }
76
+ seedCache('../utils/trigger.js', {
77
+ excuteTriggers: async function () { },
78
+ });
79
+ seedCache('../manager/workflow_manager.js', {});
80
+ let fakeInstance = null;
81
+ seedCache('../manager/uuflow_manager.js', {
82
+ getInstance: async function () { return fakeInstance; },
83
+ });
84
+
85
+ require('../routes/api_workflow_next_step_users.router.js');
86
+ const handler = capturedRoutes['/api/workflow/v2/nextStepUsersValue'];
87
+ assert.ok(handler, '未捕获到 nextStepUsersValue 路由 handler');
88
+
89
+ // ---- 构造实例:submit → 步骤P(已审) → 步骤A(传阅X后驳回) → 步骤P(当前待办) ----
90
+ function buildInstance(opts) {
91
+ opts = opts || {};
92
+ const stepATrace = {
93
+ _id: 't3', step: 'step_A', name: '步骤A', is_finished: true, judge: 'rejected',
94
+ approves: [
95
+ { _id: 'a3', user: 'user_a', handler: 'user_a', judge: 'rejected', is_finished: true },
96
+ ],
97
+ };
98
+ if (opts.ccUnread) {
99
+ stepATrace.approves.push({
100
+ _id: 'cc1', user: 'user_x', handler: 'user_x', type: 'cc', is_finished: false, is_read: false,
101
+ });
102
+ }
103
+ if (opts.ccSubmitted) {
104
+ stepATrace.approves.push({
105
+ _id: 'cc2', user: 'user_y', handler: 'user_y', type: 'cc', is_finished: true, judge: 'submitted',
106
+ });
107
+ }
108
+ if (opts.distribute) {
109
+ stepATrace.approves.push({
110
+ _id: 'd1', user: 'user_d', handler: 'user_d', type: 'distribute', is_finished: false,
111
+ });
112
+ }
113
+ if (opts.returnedApprove) {
114
+ stepATrace.approves.push({
115
+ _id: 'r1', user: 'user_r', handler: 'user_r', judge: 'returned', is_finished: true,
116
+ });
117
+ }
118
+ return {
119
+ _id: 'ins1', space: 'space1', state: 'pending', applicant: 'user_p', submitter: 'user_p',
120
+ flow: 'flow1', form: 'form1', form_version: 'fv1',
121
+ step_approve: {},
122
+ traces: [
123
+ {
124
+ _id: 't1', step: 'step_start', name: '填写申请单', is_finished: true, judge: 'submitted',
125
+ approves: [{ _id: 'a1', user: 'user_p', handler: 'user_p', type: 'draft', judge: 'submitted' }],
126
+ },
127
+ {
128
+ _id: 't2', step: 'step_P', name: '步骤P', is_finished: true, judge: 'approved',
129
+ approves: [{ _id: 'a2', user: 'user_p', handler: 'user_p', judge: 'approved' }],
130
+ },
131
+ stepATrace,
132
+ {
133
+ _id: 't4', step: 'step_P', name: '步骤P', is_finished: false,
134
+ approves: [{ _id: 'a4', user: 'user_p', handler: 'user_p', is_finished: false }],
135
+ },
136
+ ],
137
+ };
138
+ }
139
+
140
+ async function callValue(nextStepId) {
141
+ const req = {
142
+ user: { userId: 'user_p', spaceId: 'space1' },
143
+ body: { instanceId: 'ins1', nextStepId: nextStepId },
144
+ };
145
+ const res = {
146
+ statusCode: null, body: null,
147
+ status: function (c) { this.statusCode = c; return this; },
148
+ send: function (b) { this.body = b; return this; },
149
+ };
150
+ await handler(req, res);
151
+ return res;
152
+ }
153
+
154
+ let passed = 0;
155
+ let failed = 0;
156
+ async function run(name, fn) {
157
+ try {
158
+ await fn();
159
+ console.log(` ✓ ${name}`);
160
+ passed++;
161
+ } catch (e) {
162
+ console.log(` ✗ ${name}`);
163
+ console.log(` ${e.message}`);
164
+ failed++;
165
+ }
166
+ }
167
+
168
+ (async () => {
169
+ console.log('nextStepUsersValue 回退取人排除传阅/分发 approve');
170
+
171
+ await run('传阅(未读,无 judge)后驳回 → 预填处理人不含被传阅用户', async () => {
172
+ fakeInstance = buildInstance({ ccUnread: true });
173
+ const res = await callValue('step_A');
174
+ assert.strictEqual(res.statusCode, 200);
175
+ assert.deepStrictEqual(res.body.value, ['user_a'],
176
+ '应只含原审批人,实际:' + JSON.stringify(res.body.value));
177
+ });
178
+
179
+ await run('传阅(已提交意见,judge=submitted)后驳回 → 预填处理人不含被传阅用户', async () => {
180
+ fakeInstance = buildInstance({ ccSubmitted: true });
181
+ const res = await callValue('step_A');
182
+ assert.strictEqual(res.statusCode, 200);
183
+ assert.deepStrictEqual(res.body.value, ['user_a'],
184
+ '应只含原审批人,实际:' + JSON.stringify(res.body.value));
185
+ });
186
+
187
+ await run('分发(type=distribute)approve 同样被排除', async () => {
188
+ fakeInstance = buildInstance({ distribute: true });
189
+ const res = await callValue('step_A');
190
+ assert.strictEqual(res.statusCode, 200);
191
+ assert.deepStrictEqual(res.body.value, ['user_a'],
192
+ '应只含原审批人,实际:' + JSON.stringify(res.body.value));
193
+ });
194
+
195
+ await run('judge 排除表行为不变(returned 仍被排除)', async () => {
196
+ fakeInstance = buildInstance({ returnedApprove: true });
197
+ const res = await callValue('step_A');
198
+ assert.strictEqual(res.statusCode, 200);
199
+ assert.deepStrictEqual(res.body.value, ['user_a'],
200
+ '应只含原审批人,实际:' + JSON.stringify(res.body.value));
201
+ });
202
+
203
+ await run('正常(无传阅)驳回场景行为不变', async () => {
204
+ fakeInstance = buildInstance();
205
+ const res = await callValue('step_A');
206
+ assert.strictEqual(res.statusCode, 200);
207
+ assert.deepStrictEqual(res.body.value, ['user_a']);
208
+ });
209
+
210
+ console.log(`\n${passed} passed, ${failed} failed`);
211
+ process.exit(failed > 0 ? 1 : 0);
212
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steedos-labs/plugin-workflow",
3
- "version": "3.0.102",
3
+ "version": "3.0.104",
4
4
  "main": "package.service.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -27,6 +27,7 @@
27
27
  "test:approve-values": "node main/default/test/test_getApproveValues.js",
28
28
  "test:skip-handler-error": "node main/default/test/test_handleSkipProcessed_handlerError.js",
29
29
  "test:return-after-skip": "node main/default/test/test_return_after_skip.js",
30
+ "test:next-step-users-cc": "node main/default/test/test_next_step_users_value_cc.js",
30
31
  "test:flow-validator": "node main/default/test/test_flow_validator.js",
31
32
  "test:ajax-error-message": "node main/default/test/test_ajax_error_message.js",
32
33
  "test:webhook-error-message": "node main/default/test/test_webhook_error_message.js"