@rytass/bpm-core-shared 0.5.0 → 0.5.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/CHANGELOG.md +10 -0
- package/package.json +3 -2
- package/src/index.js +13 -0
- package/src/index.js.map +1 -0
- package/src/lib/condition.d.ts +43 -0
- package/src/lib/condition.js +3 -0
- package/src/lib/condition.js.map +1 -0
- package/src/lib/form.d.ts +48 -0
- package/src/lib/form.js +3 -0
- package/src/lib/form.js.map +1 -0
- package/src/lib/identity.d.ts +24 -0
- package/src/lib/identity.js +3 -0
- package/src/lib/identity.js.map +1 -0
- package/src/lib/organization.d.ts +47 -0
- package/src/lib/organization.js +3 -0
- package/src/lib/organization.js.map +1 -0
- package/src/lib/status.d.ts +7 -0
- package/src/lib/status.js +3 -0
- package/src/lib/status.js.map +1 -0
- package/src/lib/workflow-command.d.ts +156 -0
- package/src/lib/workflow-command.js +292 -0
- package/src/lib/workflow-command.js.map +1 -0
- package/src/lib/workflow-graph.d.ts +96 -0
- package/src/lib/workflow-graph.js +605 -0
- package/src/lib/workflow-graph.js.map +1 -0
- package/src/lib/workflow-toolset.d.ts +105 -0
- package/src/lib/workflow-toolset.js +702 -0
- package/src/lib/workflow-toolset.js.map +1 -0
- package/src/lib/workflow.d.ts +160 -0
- package/src/lib/workflow.js +3 -0
- package/src/lib/workflow.js.map +1 -0
- package/eslint.config.mjs +0 -19
- package/jest.config.cts +0 -10
- package/project.json +0 -49
- package/src/lib/condition.ts +0 -55
- package/src/lib/form.ts +0 -77
- package/src/lib/identity.ts +0 -25
- package/src/lib/organization.ts +0 -52
- package/src/lib/shared.spec.ts +0 -13
- package/src/lib/status.ts +0 -29
- package/src/lib/workflow-command.spec.ts +0 -278
- package/src/lib/workflow-command.ts +0 -848
- package/src/lib/workflow-graph.ts +0 -974
- package/src/lib/workflow-toolset.spec.ts +0 -230
- package/src/lib/workflow-toolset.ts +0 -1083
- package/src/lib/workflow.ts +0 -216
- package/tsconfig.json +0 -23
- package/tsconfig.lib.json +0 -16
- package/tsconfig.spec.json +0 -16
- /package/src/{index.ts → index.d.ts} +0 -0
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readFallbackWorkflowDefinition,
|
|
3
|
-
readWorkflowDefinitionIssue,
|
|
4
|
-
} from './workflow-graph';
|
|
5
|
-
import { WorkflowDefinition } from './workflow';
|
|
6
|
-
import {
|
|
7
|
-
WorkflowCommandOptions,
|
|
8
|
-
WorkflowDesignerState,
|
|
9
|
-
applyWorkflowCommand,
|
|
10
|
-
applyWorkflowMacroCommand,
|
|
11
|
-
} from './workflow-command';
|
|
12
|
-
|
|
13
|
-
function deterministicEdgeIds(): WorkflowCommandOptions {
|
|
14
|
-
let counter = 0;
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
createEdgeId: (source, target): string =>
|
|
18
|
-
`edge_${source}_${target}_${(counter += 1)}`,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function initialState(): WorkflowDesignerState {
|
|
23
|
-
return {
|
|
24
|
-
definition: readFallbackWorkflowDefinition(),
|
|
25
|
-
editingEdgeId: null,
|
|
26
|
-
formDefinitionVersionId: null,
|
|
27
|
-
formSchema: null,
|
|
28
|
-
initiatorPolicyCel: null,
|
|
29
|
-
selectedEdgeIds: [],
|
|
30
|
-
selectedNodeId: 'start',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('applyWorkflowCommand', () => {
|
|
35
|
-
it('adds a user task between start and end, flagging layout', () => {
|
|
36
|
-
const result = applyWorkflowCommand(
|
|
37
|
-
initialState(),
|
|
38
|
-
{ nodeType: 'userTask', type: 'addNode' },
|
|
39
|
-
deterministicEdgeIds(),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(result.changed).toBe(true);
|
|
43
|
-
expect(result.effects.layout).toBe(true);
|
|
44
|
-
expect(
|
|
45
|
-
result.state.definition.nodes.some((node) => node.type === 'userTask'),
|
|
46
|
-
).toBe(true);
|
|
47
|
-
// start -> userTask -> end
|
|
48
|
-
expect(result.state.definition.edges).toHaveLength(2);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('only node inserts and autoLayout request a relayout', () => {
|
|
52
|
-
const base = applyWorkflowCommand(
|
|
53
|
-
initialState(),
|
|
54
|
-
{ nodeType: 'userTask', type: 'addNode' },
|
|
55
|
-
deterministicEdgeIds(),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// addNode positions the new node -> relayout.
|
|
59
|
-
expect(base.effects.layout).toBe(true);
|
|
60
|
-
|
|
61
|
-
// Connecting already-positioned nodes preserves manual layout.
|
|
62
|
-
expect(
|
|
63
|
-
applyWorkflowCommand(base.state, {
|
|
64
|
-
source: 'start',
|
|
65
|
-
target: 'end',
|
|
66
|
-
type: 'connectEdge',
|
|
67
|
-
}).effects.layout,
|
|
68
|
-
).toBe(false);
|
|
69
|
-
|
|
70
|
-
// autoLayout is the explicit tidy step.
|
|
71
|
-
expect(
|
|
72
|
-
applyWorkflowCommand(base.state, { type: 'autoLayout' }).effects.layout,
|
|
73
|
-
).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('rejects an invalid connection (end node cannot be a source)', () => {
|
|
77
|
-
const result = applyWorkflowCommand(initialState(), {
|
|
78
|
-
source: 'end',
|
|
79
|
-
target: 'start',
|
|
80
|
-
type: 'connectEdge',
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(result.changed).toBe(false);
|
|
84
|
-
expect(result.error).toContain('無法連線');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('refuses to delete the start node', () => {
|
|
88
|
-
const result = applyWorkflowCommand(initialState(), {
|
|
89
|
-
nodeId: 'start',
|
|
90
|
-
type: 'deleteNode',
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
expect(result.changed).toBe(false);
|
|
94
|
-
expect(result.error).toContain('無法刪除');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('sets a user task approver and resets the decision policy to SINGLE', () => {
|
|
98
|
-
const withNode = applyWorkflowCommand(
|
|
99
|
-
initialState(),
|
|
100
|
-
{ nodeType: 'userTask', type: 'addNode' },
|
|
101
|
-
deterministicEdgeIds(),
|
|
102
|
-
).state;
|
|
103
|
-
const nodeId = withNode.definition.nodes.find(
|
|
104
|
-
(node) => node.type === 'userTask',
|
|
105
|
-
)?.id;
|
|
106
|
-
|
|
107
|
-
const result = applyWorkflowCommand(withNode, {
|
|
108
|
-
approverResolver: { positionId: 'pos-1', type: 'POSITION' },
|
|
109
|
-
nodeId: nodeId ?? '',
|
|
110
|
-
type: 'setUserTaskApprover',
|
|
111
|
-
});
|
|
112
|
-
const updated = result.state.definition.nodes.find(
|
|
113
|
-
(node) => node.id === nodeId,
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
expect(updated?.type).toBe('userTask');
|
|
117
|
-
|
|
118
|
-
if (updated?.type === 'userTask') {
|
|
119
|
-
expect(updated.data.approverResolver).toEqual({
|
|
120
|
-
positionId: 'pos-1',
|
|
121
|
-
type: 'POSITION',
|
|
122
|
-
});
|
|
123
|
-
expect(updated.data.decisionPolicy).toEqual({ type: 'SINGLE' });
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('reports a validation issue for an unconfigured DIRECT approver', () => {
|
|
128
|
-
const withNode = applyWorkflowCommand(
|
|
129
|
-
initialState(),
|
|
130
|
-
{ nodeType: 'userTask', type: 'addNode' },
|
|
131
|
-
deterministicEdgeIds(),
|
|
132
|
-
).state;
|
|
133
|
-
const nodeId =
|
|
134
|
-
withNode.definition.nodes.find((node) => node.type === 'userTask')?.id ??
|
|
135
|
-
'';
|
|
136
|
-
|
|
137
|
-
const result = applyWorkflowCommand(withNode, {
|
|
138
|
-
approverResolver: { memberIds: [], type: 'DIRECT' },
|
|
139
|
-
nodeId,
|
|
140
|
-
type: 'setUserTaskApprover',
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
expect(result.issue).toBe('簽核節點需要指定簽核會員。');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('compiles an edge condition against the bound form schema', () => {
|
|
147
|
-
// Build: start -> gateway -> end, then condition the gateway output.
|
|
148
|
-
const base = applyWorkflowCommand(
|
|
149
|
-
{
|
|
150
|
-
...initialState(),
|
|
151
|
-
formSchema: {
|
|
152
|
-
fields: [
|
|
153
|
-
{
|
|
154
|
-
fieldKey: 'amount',
|
|
155
|
-
label: '金額',
|
|
156
|
-
required: true,
|
|
157
|
-
type: 'money',
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
schemaVersion: 1,
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
{ nodeType: 'exclusiveGateway', type: 'addNode' },
|
|
164
|
-
deterministicEdgeIds(),
|
|
165
|
-
).state;
|
|
166
|
-
const gatewayOutEdge = base.definition.edges.find((edge) =>
|
|
167
|
-
base.definition.nodes.some(
|
|
168
|
-
(node) =>
|
|
169
|
-
node.id === edge.source && node.type === 'exclusiveGateway',
|
|
170
|
-
),
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const result = applyWorkflowCommand(base, {
|
|
174
|
-
edgeId: gatewayOutEdge?.id ?? '',
|
|
175
|
-
fieldKey: 'amount',
|
|
176
|
-
operator: 'GREATER_THAN',
|
|
177
|
-
type: 'setEdgeCondition',
|
|
178
|
-
value: '1000',
|
|
179
|
-
});
|
|
180
|
-
const updatedEdge = result.state.definition.edges.find(
|
|
181
|
-
(edge) => edge.id === gatewayOutEdge?.id,
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
expect(updatedEdge?.data.condition).toBe('form.amount > 1000');
|
|
185
|
-
expect(updatedEdge?.data.isDefault).toBe(false);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('applyWorkflowMacroCommand', () => {
|
|
190
|
-
it('insertApprovalStep adds a user task and sets its approver in one call', () => {
|
|
191
|
-
const result = applyWorkflowMacroCommand(
|
|
192
|
-
initialState(),
|
|
193
|
-
{
|
|
194
|
-
approverResolver: { memberIds: ['m-1'], type: 'DIRECT' },
|
|
195
|
-
label: '部門簽核',
|
|
196
|
-
type: 'insertApprovalStep',
|
|
197
|
-
},
|
|
198
|
-
deterministicEdgeIds(),
|
|
199
|
-
);
|
|
200
|
-
const userTask = result.state.definition.nodes.find(
|
|
201
|
-
(node) => node.type === 'userTask',
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
expect(result.changed).toBe(true);
|
|
205
|
-
expect(userTask?.data.label).toBe('部門簽核');
|
|
206
|
-
|
|
207
|
-
if (userTask?.type === 'userTask') {
|
|
208
|
-
expect(userTask.data.approverResolver).toEqual({
|
|
209
|
-
memberIds: ['m-1'],
|
|
210
|
-
type: 'DIRECT',
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe('readWorkflowDefinitionIssue — exclusive gateway default path', () => {
|
|
217
|
-
function gatewayDefinition(
|
|
218
|
-
edges: WorkflowDefinition['edges'],
|
|
219
|
-
): WorkflowDefinition {
|
|
220
|
-
return {
|
|
221
|
-
edges,
|
|
222
|
-
meta: { schemaVersion: 1 },
|
|
223
|
-
nodes: [
|
|
224
|
-
{ data: { label: '開始' }, id: 'start', position: { x: 0, y: 0 }, type: 'startEvent' },
|
|
225
|
-
{
|
|
226
|
-
data: { direction: 'split', label: '分流', triggerMode: 'AND' },
|
|
227
|
-
id: 'gw',
|
|
228
|
-
position: { x: 0, y: 0 },
|
|
229
|
-
type: 'exclusiveGateway',
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
data: { endState: 'APPROVED', label: '完成', triggerMode: 'AND' },
|
|
233
|
-
id: 'end',
|
|
234
|
-
position: { x: 0, y: 0 },
|
|
235
|
-
type: 'endEvent',
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
it('flags a gateway whose outputs all have conditions but no "其他情況" default', () => {
|
|
242
|
-
const issue = readWorkflowDefinitionIssue(
|
|
243
|
-
gatewayDefinition([
|
|
244
|
-
{ data: {}, id: 'e0', source: 'start', target: 'gw' },
|
|
245
|
-
{
|
|
246
|
-
data: { condition: 'form.amount > 100000', label: '金額>10萬' },
|
|
247
|
-
id: 'e1',
|
|
248
|
-
source: 'gw',
|
|
249
|
-
target: 'end',
|
|
250
|
-
},
|
|
251
|
-
]),
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
expect(issue).toContain('其他情況');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('passes once one output edge is the default path', () => {
|
|
258
|
-
const issue = readWorkflowDefinitionIssue(
|
|
259
|
-
gatewayDefinition([
|
|
260
|
-
{ data: {}, id: 'e0', source: 'start', target: 'gw' },
|
|
261
|
-
{
|
|
262
|
-
data: { condition: 'form.amount > 100000', label: '金額>10萬' },
|
|
263
|
-
id: 'e1',
|
|
264
|
-
source: 'gw',
|
|
265
|
-
target: 'end',
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
data: { isDefault: true, label: '其他情況' },
|
|
269
|
-
id: 'e2',
|
|
270
|
-
source: 'gw',
|
|
271
|
-
target: 'end',
|
|
272
|
-
},
|
|
273
|
-
]),
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
expect(issue).toBeNull();
|
|
277
|
-
});
|
|
278
|
-
});
|