weaver-e10-builder 1.0.2-beta
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/README.md +200 -0
- package/dist/commands/app.d.ts +3 -0
- package/dist/commands/app.d.ts.map +1 -0
- package/dist/commands/app.js +305 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +386 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +106 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +134 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/docx.d.ts +3 -0
- package/dist/commands/docx.d.ts.map +1 -0
- package/dist/commands/docx.js +54 -0
- package/dist/commands/docx.js.map +1 -0
- package/dist/commands/form-button.d.ts +3 -0
- package/dist/commands/form-button.d.ts.map +1 -0
- package/dist/commands/form-button.js +441 -0
- package/dist/commands/form-button.js.map +1 -0
- package/dist/commands/form-data.d.ts +3 -0
- package/dist/commands/form-data.d.ts.map +1 -0
- package/dist/commands/form-data.js +197 -0
- package/dist/commands/form-data.js.map +1 -0
- package/dist/commands/form-ebuilder-columns.d.ts +3 -0
- package/dist/commands/form-ebuilder-columns.d.ts.map +1 -0
- package/dist/commands/form-ebuilder-columns.js +118 -0
- package/dist/commands/form-ebuilder-columns.js.map +1 -0
- package/dist/commands/form-ebuilder-filter.d.ts +3 -0
- package/dist/commands/form-ebuilder-filter.d.ts.map +1 -0
- package/dist/commands/form-ebuilder-filter.js +135 -0
- package/dist/commands/form-ebuilder-filter.js.map +1 -0
- package/dist/commands/form-ebuilder-scope.d.ts +3 -0
- package/dist/commands/form-ebuilder-scope.d.ts.map +1 -0
- package/dist/commands/form-ebuilder-scope.js +321 -0
- package/dist/commands/form-ebuilder-scope.js.map +1 -0
- package/dist/commands/form-ebuilder-search.d.ts +3 -0
- package/dist/commands/form-ebuilder-search.d.ts.map +1 -0
- package/dist/commands/form-ebuilder-search.js +252 -0
- package/dist/commands/form-ebuilder-search.js.map +1 -0
- package/dist/commands/form-formula.d.ts +12 -0
- package/dist/commands/form-formula.d.ts.map +1 -0
- package/dist/commands/form-formula.js +252 -0
- package/dist/commands/form-formula.js.map +1 -0
- package/dist/commands/form-layout-init.d.ts +15 -0
- package/dist/commands/form-layout-init.d.ts.map +1 -0
- package/dist/commands/form-layout-init.js +162 -0
- package/dist/commands/form-layout-init.js.map +1 -0
- package/dist/commands/form-layout.d.ts +3 -0
- package/dist/commands/form-layout.d.ts.map +1 -0
- package/dist/commands/form-layout.js +236 -0
- package/dist/commands/form-layout.js.map +1 -0
- package/dist/commands/form-list-click.d.ts +3 -0
- package/dist/commands/form-list-click.d.ts.map +1 -0
- package/dist/commands/form-list-click.js +154 -0
- package/dist/commands/form-list-click.js.map +1 -0
- package/dist/commands/form-list-event.d.ts +3 -0
- package/dist/commands/form-list-event.d.ts.map +1 -0
- package/dist/commands/form-list-event.js +203 -0
- package/dist/commands/form-list-event.js.map +1 -0
- package/dist/commands/form-list-filter.d.ts +9 -0
- package/dist/commands/form-list-filter.d.ts.map +1 -0
- package/dist/commands/form-list-filter.js +107 -0
- package/dist/commands/form-list-filter.js.map +1 -0
- package/dist/commands/form-list-group.d.ts +11 -0
- package/dist/commands/form-list-group.d.ts.map +1 -0
- package/dist/commands/form-list-group.js +100 -0
- package/dist/commands/form-list-group.js.map +1 -0
- package/dist/commands/form-list-permission.d.ts +3 -0
- package/dist/commands/form-list-permission.d.ts.map +1 -0
- package/dist/commands/form-list-permission.js +127 -0
- package/dist/commands/form-list-permission.js.map +1 -0
- package/dist/commands/form-list-search-group.d.ts +3 -0
- package/dist/commands/form-list-search-group.d.ts.map +1 -0
- package/dist/commands/form-list-search-group.js +132 -0
- package/dist/commands/form-list-search-group.js.map +1 -0
- package/dist/commands/form-list-stats.d.ts +11 -0
- package/dist/commands/form-list-stats.d.ts.map +1 -0
- package/dist/commands/form-list-stats.js +139 -0
- package/dist/commands/form-list-stats.js.map +1 -0
- package/dist/commands/form-list-unit.d.ts +3 -0
- package/dist/commands/form-list-unit.d.ts.map +1 -0
- package/dist/commands/form-list-unit.js +95 -0
- package/dist/commands/form-list-unit.js.map +1 -0
- package/dist/commands/form-list-utils.d.ts +32 -0
- package/dist/commands/form-list-utils.d.ts.map +1 -0
- package/dist/commands/form-list-utils.js +76 -0
- package/dist/commands/form-list-utils.js.map +1 -0
- package/dist/commands/form-list.d.ts +3 -0
- package/dist/commands/form-list.d.ts.map +1 -0
- package/dist/commands/form-list.js +624 -0
- package/dist/commands/form-list.js.map +1 -0
- package/dist/commands/form-permission.d.ts +3 -0
- package/dist/commands/form-permission.d.ts.map +1 -0
- package/dist/commands/form-permission.js +193 -0
- package/dist/commands/form-permission.js.map +1 -0
- package/dist/commands/form-relate.d.ts +14 -0
- package/dist/commands/form-relate.d.ts.map +1 -0
- package/dist/commands/form-relate.js +140 -0
- package/dist/commands/form-relate.js.map +1 -0
- package/dist/commands/form-serial-mark.d.ts +18 -0
- package/dist/commands/form-serial-mark.d.ts.map +1 -0
- package/dist/commands/form-serial-mark.js +167 -0
- package/dist/commands/form-serial-mark.js.map +1 -0
- package/dist/commands/form-table-view.d.ts +3 -0
- package/dist/commands/form-table-view.d.ts.map +1 -0
- package/dist/commands/form-table-view.js +217 -0
- package/dist/commands/form-table-view.js.map +1 -0
- package/dist/commands/form-validate.d.ts +3 -0
- package/dist/commands/form-validate.d.ts.map +1 -0
- package/dist/commands/form-validate.js +122 -0
- package/dist/commands/form-validate.js.map +1 -0
- package/dist/commands/form-view-permission.d.ts +3 -0
- package/dist/commands/form-view-permission.d.ts.map +1 -0
- package/dist/commands/form-view-permission.js +86 -0
- package/dist/commands/form-view-permission.js.map +1 -0
- package/dist/commands/form-view.d.ts +3 -0
- package/dist/commands/form-view.d.ts.map +1 -0
- package/dist/commands/form-view.js +145 -0
- package/dist/commands/form-view.js.map +1 -0
- package/dist/commands/form.d.ts +3 -0
- package/dist/commands/form.d.ts.map +1 -0
- package/dist/commands/form.js +2467 -0
- package/dist/commands/form.js.map +1 -0
- package/dist/commands/icon.d.ts +3 -0
- package/dist/commands/icon.d.ts.map +1 -0
- package/dist/commands/icon.js +82 -0
- package/dist/commands/icon.js.map +1 -0
- package/dist/commands/matrix.d.ts +3 -0
- package/dist/commands/matrix.d.ts.map +1 -0
- package/dist/commands/matrix.js +587 -0
- package/dist/commands/matrix.js.map +1 -0
- package/dist/commands/menu-permission.d.ts +3 -0
- package/dist/commands/menu-permission.d.ts.map +1 -0
- package/dist/commands/menu-permission.js +188 -0
- package/dist/commands/menu-permission.js.map +1 -0
- package/dist/commands/menu.d.ts +16 -0
- package/dist/commands/menu.d.ts.map +1 -0
- package/dist/commands/menu.js +269 -0
- package/dist/commands/menu.js.map +1 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +345 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/role.d.ts +3 -0
- package/dist/commands/role.d.ts.map +1 -0
- package/dist/commands/role.js +224 -0
- package/dist/commands/role.js.map +1 -0
- package/dist/commands/user.d.ts +3 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +49 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/commands/workflow-action.d.ts +3 -0
- package/dist/commands/workflow-action.d.ts.map +1 -0
- package/dist/commands/workflow-action.js +1821 -0
- package/dist/commands/workflow-action.js.map +1 -0
- package/dist/commands/workflow-advanced.d.ts +3 -0
- package/dist/commands/workflow-advanced.d.ts.map +1 -0
- package/dist/commands/workflow-advanced.js +290 -0
- package/dist/commands/workflow-advanced.js.map +1 -0
- package/dist/commands/workflow-condition.d.ts +7 -0
- package/dist/commands/workflow-condition.d.ts.map +1 -0
- package/dist/commands/workflow-condition.js +562 -0
- package/dist/commands/workflow-condition.js.map +1 -0
- package/dist/commands/workflow-message.d.ts +3 -0
- package/dist/commands/workflow-message.d.ts.map +1 -0
- package/dist/commands/workflow-message.js +399 -0
- package/dist/commands/workflow-message.js.map +1 -0
- package/dist/commands/workflow-node-overtime.d.ts +3 -0
- package/dist/commands/workflow-node-overtime.d.ts.map +1 -0
- package/dist/commands/workflow-node-overtime.js +150 -0
- package/dist/commands/workflow-node-overtime.js.map +1 -0
- package/dist/commands/workflow-node-reject.d.ts +3 -0
- package/dist/commands/workflow-node-reject.d.ts.map +1 -0
- package/dist/commands/workflow-node-reject.js +115 -0
- package/dist/commands/workflow-node-reject.js.map +1 -0
- package/dist/commands/workflow-node-sign.d.ts +15 -0
- package/dist/commands/workflow-node-sign.d.ts.map +1 -0
- package/dist/commands/workflow-node-sign.js +100 -0
- package/dist/commands/workflow-node-sign.js.map +1 -0
- package/dist/commands/workflow-node-validate.d.ts +16 -0
- package/dist/commands/workflow-node-validate.d.ts.map +1 -0
- package/dist/commands/workflow-node-validate.js +169 -0
- package/dist/commands/workflow-node-validate.js.map +1 -0
- package/dist/commands/workflow-node.d.ts +3 -0
- package/dist/commands/workflow-node.d.ts.map +1 -0
- package/dist/commands/workflow-node.js +347 -0
- package/dist/commands/workflow-node.js.map +1 -0
- package/dist/commands/workflow-numbering.d.ts +17 -0
- package/dist/commands/workflow-numbering.d.ts.map +1 -0
- package/dist/commands/workflow-numbering.js +191 -0
- package/dist/commands/workflow-numbering.js.map +1 -0
- package/dist/commands/workflow-plan-range.d.ts +3 -0
- package/dist/commands/workflow-plan-range.d.ts.map +1 -0
- package/dist/commands/workflow-plan-range.js +116 -0
- package/dist/commands/workflow-plan-range.js.map +1 -0
- package/dist/commands/workflow-setting.d.ts +3 -0
- package/dist/commands/workflow-setting.d.ts.map +1 -0
- package/dist/commands/workflow-setting.js +440 -0
- package/dist/commands/workflow-setting.js.map +1 -0
- package/dist/commands/workflow-title.d.ts +14 -0
- package/dist/commands/workflow-title.d.ts.map +1 -0
- package/dist/commands/workflow-title.js +106 -0
- package/dist/commands/workflow-title.js.map +1 -0
- package/dist/commands/workflow.d.ts +26 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +2359 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/core/auth.d.ts +36 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +351 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/client.d.ts +27 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +100 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/crypto.d.ts +11 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +102 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/edition.d.ts +4 -0
- package/dist/core/edition.d.ts.map +1 -0
- package/dist/core/edition.js +19 -0
- package/dist/core/edition.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/dist/services/action-detail.d.ts +112 -0
- package/dist/services/action-detail.d.ts.map +1 -0
- package/dist/services/action-detail.js +443 -0
- package/dist/services/action-detail.js.map +1 -0
- package/dist/services/condition-rules.d.ts +45 -0
- package/dist/services/condition-rules.d.ts.map +1 -0
- package/dist/services/condition-rules.js +207 -0
- package/dist/services/condition-rules.js.map +1 -0
- package/dist/services/form-create.d.ts +102 -0
- package/dist/services/form-create.d.ts.map +1 -0
- package/dist/services/form-create.js +835 -0
- package/dist/services/form-create.js.map +1 -0
- package/dist/services/form-ebuilder-filter.d.ts +60 -0
- package/dist/services/form-ebuilder-filter.d.ts.map +1 -0
- package/dist/services/form-ebuilder-filter.js +562 -0
- package/dist/services/form-ebuilder-filter.js.map +1 -0
- package/dist/services/form-table-view.d.ts +117 -0
- package/dist/services/form-table-view.d.ts.map +1 -0
- package/dist/services/form-table-view.js +882 -0
- package/dist/services/form-table-view.js.map +1 -0
- package/dist/services/form-view-permission.d.ts +30 -0
- package/dist/services/form-view-permission.d.ts.map +1 -0
- package/dist/services/form-view-permission.js +208 -0
- package/dist/services/form-view-permission.js.map +1 -0
- package/dist/services/form-view.d.ts +41 -0
- package/dist/services/form-view.d.ts.map +1 -0
- package/dist/services/form-view.js +334 -0
- package/dist/services/form-view.js.map +1 -0
- package/dist/services/formula-detail.d.ts +31 -0
- package/dist/services/formula-detail.d.ts.map +1 -0
- package/dist/services/formula-detail.js +66 -0
- package/dist/services/formula-detail.js.map +1 -0
- package/dist/services/rich-types.d.ts +35 -0
- package/dist/services/rich-types.d.ts.map +1 -0
- package/dist/services/rich-types.js +105 -0
- package/dist/services/rich-types.js.map +1 -0
- package/dist/services/workflow-layout.d.ts +118 -0
- package/dist/services/workflow-layout.d.ts.map +1 -0
- package/dist/services/workflow-layout.js +279 -0
- package/dist/services/workflow-layout.js.map +1 -0
- package/dist/services/workflow-operator.d.ts +36 -0
- package/dist/services/workflow-operator.d.ts.map +1 -0
- package/dist/services/workflow-operator.js +65 -0
- package/dist/services/workflow-operator.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/column-name.d.ts +13 -0
- package/dist/utils/column-name.d.ts.map +1 -0
- package/dist/utils/column-name.js +45 -0
- package/dist/utils/column-name.js.map +1 -0
- package/ecodecmd/package.json +27 -0
- package/ecodecmd/src/build.js +709 -0
- package/ecodecmd/src/compile.js +81 -0
- package/ecodecmd/src/index.js +48 -0
- package/ecodecmd/src/init.js +73 -0
- package/ecodecmd/src/lib/@babel/plugin-proposal-object-rest-spread@7.5.5/index.js +1795 -0
- package/ecodecmd/src/lib/@babel/plugin-proposal-optional-chaining@7.6.0/index.js +135 -0
- package/ecodecmd/src/lib/@babel/preset-env-standalone@7.5.5/babel-preset-env.js +76043 -0
- package/ecodecmd/src/lib/@babel/standalone@7.5.5/babel.js +90802 -0
- package/ecodecmd/src/lib/exec.js +77 -0
- package/ecodecmd/src/loaddep.js +154 -0
- package/ecodecmd/src/util/compress.js +58 -0
- package/ecodecmd/src/util/crypto-util.js +19 -0
- package/ecodecmd/src/util/index.js +153 -0
- package/ecodecmd/src/util/snowflake.js +144 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2359 @@
|
|
|
1
|
+
import { AuthSession } from '../core/auth.js';
|
|
2
|
+
import { ApiClient } from '../core/client.js';
|
|
3
|
+
import { resolveSourceForm, fetchEbField } from '../services/action-detail.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generate a negative temporary ID for saveAddActionList new items.
|
|
6
|
+
* The API requires negative IDs for new items that haven't been persisted yet.
|
|
7
|
+
*/
|
|
8
|
+
export function tempIdNeg() {
|
|
9
|
+
return String(-Date.now() * 1000 - Math.floor(Math.random() * 10000));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a row returned by getAddActionDataList into the write format
|
|
13
|
+
* expected by saveAddActionList.
|
|
14
|
+
*
|
|
15
|
+
* The read format differs from the write format and MUST NOT be re-sent verbatim:
|
|
16
|
+
* - actionType=2: read `actionData` lacks paramsSet/sysCustomAction/id and carries
|
|
17
|
+
* isSetLocale; `async` is boolean false (write needs 0); extra fields hasRule/
|
|
18
|
+
* pubAction/sync/syncTime/objId/actionSelect are present.
|
|
19
|
+
* - actionType=0: targetValue/firstValue/operatorValue/customValue are nested under
|
|
20
|
+
* `fieldMappingData`; flat versions, dateRule, isCreate, dateRuleStr are dropped.
|
|
21
|
+
*
|
|
22
|
+
* Re-sending the raw read format corrupts the node: the save itself returns 200 but
|
|
23
|
+
* every subsequent getAddActionDataList/saveAddActionList returns 500 (the server
|
|
24
|
+
* loads the broken rows on every later call). The node then becomes unrecoverable
|
|
25
|
+
* through the API. So every existing row must be normalized before being merged
|
|
26
|
+
* back into a save payload.
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeActionRow(row) {
|
|
29
|
+
if (!row || typeof row !== 'object')
|
|
30
|
+
return row;
|
|
31
|
+
if (row.actionType === 2) {
|
|
32
|
+
const actionSet = row.actionData?.actionSet || [];
|
|
33
|
+
const out = {
|
|
34
|
+
id: row.id,
|
|
35
|
+
actionName: row.actionName,
|
|
36
|
+
open: row.open,
|
|
37
|
+
actionId: row.actionId,
|
|
38
|
+
esbName: row.esbName || row.actionName,
|
|
39
|
+
customName: row.customName || '',
|
|
40
|
+
actionType: 2,
|
|
41
|
+
actionData: {
|
|
42
|
+
actionSelect: row.actionData?.actionSelect || '1',
|
|
43
|
+
sysCustomAction: [],
|
|
44
|
+
paramsSet: actionSet.map((a) => ({
|
|
45
|
+
id: a.id, name: a.name,
|
|
46
|
+
eventType: a.eventType || 'WorkflowTrigger',
|
|
47
|
+
interfaceCreate: false, isKeepDiary: 1,
|
|
48
|
+
})),
|
|
49
|
+
actionSet,
|
|
50
|
+
actionName: '',
|
|
51
|
+
id: row.id,
|
|
52
|
+
customDataId: '',
|
|
53
|
+
},
|
|
54
|
+
async: 0,
|
|
55
|
+
triggerType: row.triggerType,
|
|
56
|
+
order: row.order,
|
|
57
|
+
};
|
|
58
|
+
if (row.executeTime)
|
|
59
|
+
out.executeTime = row.executeTime;
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
if (row.actionType === 0) {
|
|
63
|
+
const fm = row.fieldMappingData || row;
|
|
64
|
+
return {
|
|
65
|
+
id: row.id,
|
|
66
|
+
actionName: row.actionName,
|
|
67
|
+
executeTime: row.executeTime || ['1'],
|
|
68
|
+
open: row.open,
|
|
69
|
+
async: 0,
|
|
70
|
+
actionType: 0,
|
|
71
|
+
dateRuleStr: row.dateRuleStr || '',
|
|
72
|
+
triggerType: row.triggerType,
|
|
73
|
+
order: row.order,
|
|
74
|
+
targetValue: fm.targetValue,
|
|
75
|
+
firstValue: fm.firstValue,
|
|
76
|
+
operatorValue: fm.operatorValue || '',
|
|
77
|
+
secondValue: fm.secondValue || '',
|
|
78
|
+
customValue: fm.customValue,
|
|
79
|
+
dateRule: row.dateRule || [],
|
|
80
|
+
isCreate: row.isCreate ?? false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return row;
|
|
84
|
+
}
|
|
85
|
+
export function registerWorkflowCommands(program) {
|
|
86
|
+
const wf = program.command('workflow').description('流程管理');
|
|
87
|
+
wf.command('create')
|
|
88
|
+
.description('创建流程(自动设置所有人可发起)')
|
|
89
|
+
.argument('<appId>', '应用 ID')
|
|
90
|
+
.argument('<formObjId>', '表单 objId')
|
|
91
|
+
.argument('<name>', '流程名称')
|
|
92
|
+
.argument('[desc]', '流程描述', '')
|
|
93
|
+
.option('--no-menu', '不自动添加发起菜单(默认建流程后自动加,指向表单新建页)')
|
|
94
|
+
.action(async (appId, formObjId, name, desc, cmdOpts) => {
|
|
95
|
+
const opts = program.opts();
|
|
96
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
97
|
+
if (!auth)
|
|
98
|
+
process.exit(1);
|
|
99
|
+
const client = new ApiClient(auth);
|
|
100
|
+
const baseUrl = auth.getBaseUrl();
|
|
101
|
+
// objId → formId
|
|
102
|
+
const formInfo = await client.get(`${baseUrl}/api/ebuilder/form/obj/getInfo/${formObjId}`);
|
|
103
|
+
const realFormId = formInfo?.data?.formId || '';
|
|
104
|
+
if (!realFormId) {
|
|
105
|
+
console.error(`错误: 无法获取 formId,传入的是 objId=${formObjId}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
console.error(` objId ${formObjId} -> formId ${realFormId}`);
|
|
109
|
+
// Create workflow
|
|
110
|
+
const ts = Date.now();
|
|
111
|
+
const payload = {
|
|
112
|
+
workflowDescMultiLang: { tablefiled: 'wfp_base.workflowdesc', targetId: String(ts + 1), needReplaceTargetId: true },
|
|
113
|
+
createClient: '0', ebFormId: realFormId, createPcShow: ['0'], wfSymbol: '',
|
|
114
|
+
orderId: '0.0', moduleCard: 0, createType: '0', workflowName: name,
|
|
115
|
+
workflowDesc: desc, relateForm: '4', isLock: '0', isFree: '0',
|
|
116
|
+
addDefaultHide: 0, createPcShowCustom: '', workflowType: '-1', id: '0',
|
|
117
|
+
workflowNameMultiLang: { tablefiled: 'wfp_base.workflowname', targetId: String(ts), needReplaceTargetId: true },
|
|
118
|
+
createMobileShowCustom: '', status: '1', createMobileShow: ['0'],
|
|
119
|
+
formMode: '0', formId: 0, relateFormMode: '0', workflowId: -1,
|
|
120
|
+
isCreate: 1, workflowNameCurrenLangData: name, source: 'ebuilderCreate',
|
|
121
|
+
messageParams: { appId, groupId: '-1', sourceType: 'BACK' },
|
|
122
|
+
copyForm: 0, pathManage: 0, subCompanyId: 0, helpDoc: 0,
|
|
123
|
+
relateType: '', externalForm: 0, belongType: 1,
|
|
124
|
+
};
|
|
125
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/baseSet/addForm`, payload);
|
|
126
|
+
// 成功哨兵:code=200, status=true, data.workflowId 存在且不为 "-1"
|
|
127
|
+
// 注意:不能用 data.id(可能为 "0"),必须取 data.workflowId
|
|
128
|
+
const workflowId = data?.data?.workflowId || '';
|
|
129
|
+
if (data?.code !== 200 || !data?.status || workflowId === '-1' || !workflowId) {
|
|
130
|
+
console.error(`创建失败: ${data?.data?.msg || data?.msg || '未知错误'}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
console.error(`流程已创建,ID: ${workflowId}`);
|
|
134
|
+
// 文档要求:创建后立即 getBaseInfoList 回读验证
|
|
135
|
+
try {
|
|
136
|
+
const verify = await client.post(`${baseUrl}/api/bs/workflow/pathdef/baseSet/getBaseInfoList`, {
|
|
137
|
+
belongType: 1, current: 1, pageSize: 1, isTemplate: 0,
|
|
138
|
+
searchParams: {
|
|
139
|
+
quickSearchDatas: { workflowName: name },
|
|
140
|
+
searchDatas: { workflowNameCondition: 'in', workflowTypeCondition: 'in', formType: 'all', formCondition: 'in', status: '0', subCompanyIdCondition: 'in', type: 'and' },
|
|
141
|
+
otherSearchDatas: { workflowType: '', workflowId: '', subCompanyId: '' },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
const vlist = verify?.data?.list || verify?.data?.data || [];
|
|
145
|
+
const found = vlist.find((w) => String(w.id || w.workflowId) === String(workflowId));
|
|
146
|
+
if (!found) {
|
|
147
|
+
console.error(`回读警告: getBaseInfoList 未找到 workflowId=${workflowId}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch { /* 回读失败不阻塞 */ }
|
|
151
|
+
// Auto-set initiator permission
|
|
152
|
+
try {
|
|
153
|
+
const nodes = await client.get(`${baseUrl}/api/bs/workflow/pathdef/nodeBase/getNodeAndLinkId?type=all&workflowId=${workflowId}`);
|
|
154
|
+
const nodeId = nodes?.data?.[0];
|
|
155
|
+
if (nodeId) {
|
|
156
|
+
const rg = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/getLayoutConfigInfo`, {
|
|
157
|
+
groupId: '', workflowId, nodeId, sourceType: '1', isOperatorRuleAuth: true,
|
|
158
|
+
});
|
|
159
|
+
const rgid = rg?.data?.reserveGroupId || '';
|
|
160
|
+
if (rgid) {
|
|
161
|
+
await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/saveOperatorGroup`, {
|
|
162
|
+
workflowId, nodeId, groupId: '', isCreate: '1', reserveGroupId: rgid,
|
|
163
|
+
itemDatas: [{
|
|
164
|
+
level: '0~100', isOut: false, isSaveHrmOperator: false,
|
|
165
|
+
id: '1', groupType: ' ', itemName: '', itemLevel: '0~100',
|
|
166
|
+
endApprovers: '', hrmOperators: '', levelObj: '',
|
|
167
|
+
workflowId, nodeId, conditionId: '', orders: '0.00',
|
|
168
|
+
groupTypeId: '110', isOpen: true, bigTypeId: 1,
|
|
169
|
+
condition: '', groupCondition: '', isSuportVirtualId: true,
|
|
170
|
+
openProjectOrganization: false, isExtendType: 0,
|
|
171
|
+
}],
|
|
172
|
+
oldGroupName: '', refWorkflowId: '', oldItemDatas: [],
|
|
173
|
+
groupName: '', deleteDatas: [], mustreview: 0,
|
|
174
|
+
});
|
|
175
|
+
console.error(' 已设置所有人可发起');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
console.error(' 警告: 无法自动设置发起权限,请手动设置');
|
|
181
|
+
}
|
|
182
|
+
const workflowUrl = `${baseUrl}/sp/workflow/flowpage/create/${workflowId}`;
|
|
183
|
+
// Auto-add 发起 menu (LAYOUT → form create page, triggers bound workflow)
|
|
184
|
+
let menuId = null;
|
|
185
|
+
if (cmdOpts.menu !== false) {
|
|
186
|
+
try {
|
|
187
|
+
const { addFormStartMenu } = await import('./menu.js');
|
|
188
|
+
menuId = await addFormStartMenu(client, baseUrl, appId, formObjId, name);
|
|
189
|
+
console.error(` 发起菜单已添加${menuId ? ` (id=${menuId})` : ''}`);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
console.error(` 发起菜单添加失败: ${e.message}(可用 \`e10-cli menu add-start ${appId} ${formObjId}\` 手动补加)`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (opts.json) {
|
|
196
|
+
console.log(JSON.stringify({ workflowId, name, url: workflowUrl, menuId }));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(`流程入口: ${workflowUrl}`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
wf.command('list')
|
|
203
|
+
.description('列出应用下的所有流程')
|
|
204
|
+
.argument('<appId>', '应用 ID')
|
|
205
|
+
.option('--out <file>', '将 JSON 输出写入文件(UTF-8)')
|
|
206
|
+
.action(async (appId, cmdOpts) => {
|
|
207
|
+
const opts = program.opts();
|
|
208
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
209
|
+
if (!auth)
|
|
210
|
+
process.exit(1);
|
|
211
|
+
const client = new ApiClient(auth);
|
|
212
|
+
const baseUrl = auth.getBaseUrl();
|
|
213
|
+
const data = await client.post(`${baseUrl}/api/bs/ebuilder/flow/approval/getApprovalListDatasByAppId?apid=${appId}&cid=${appId}`, { appId, viewType: '', sortParams: [], searchParamData: {} }, { ebbusinessid: '10000000000000000' });
|
|
214
|
+
if (data?.code !== 200 || !data?.data?.data) {
|
|
215
|
+
console.error(`查询失败: ${data?.msg || '未知错误'}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
const workflows = data.data.data.map((w) => ({
|
|
219
|
+
workflowId: w.workflowid || '',
|
|
220
|
+
name: w.workflowname || '',
|
|
221
|
+
formName: w.dataset?.text || '',
|
|
222
|
+
formId: w.formid || '',
|
|
223
|
+
objId: w.objId || '',
|
|
224
|
+
status: w.status === '1' ? '启用' : '停用',
|
|
225
|
+
createTime: w.createTime || '',
|
|
226
|
+
}));
|
|
227
|
+
if (cmdOpts?.out) {
|
|
228
|
+
const { writeFileSync } = await import('fs');
|
|
229
|
+
writeFileSync(cmdOpts.out, JSON.stringify({ workflows }, null, 2), 'utf-8');
|
|
230
|
+
console.error(`已写入: ${cmdOpts.out}`);
|
|
231
|
+
}
|
|
232
|
+
else if (opts.json) {
|
|
233
|
+
console.log(JSON.stringify({ workflows }, null, 2));
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.log(`共 ${workflows.length} 个流程:\n`);
|
|
237
|
+
for (const w of workflows) {
|
|
238
|
+
console.log(` ${w.workflowId} ${w.name.padEnd(20)} ${w.status} 表单: ${w.formName}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
wf.command('search')
|
|
243
|
+
.description('按名称/ID 搜索工作流(不依赖应用 ID,通用搜索)')
|
|
244
|
+
.option('--name <keyword>', '工作流名称(模糊匹配)')
|
|
245
|
+
.option('--id <workflowId>', '精确工作流 ID')
|
|
246
|
+
.option('--type <typeId>', '工作流类型 ID')
|
|
247
|
+
.option('--belong <n>', '所属类型: 0=个人, 1=团队', '1')
|
|
248
|
+
.option('--page-size <n>', '每页条数', '20')
|
|
249
|
+
.option('--page-no <n>', '页码', '1')
|
|
250
|
+
.action(async (cmdOpts) => {
|
|
251
|
+
const opts = program.opts();
|
|
252
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
253
|
+
if (!auth)
|
|
254
|
+
process.exit(1);
|
|
255
|
+
const client = new ApiClient(auth);
|
|
256
|
+
const baseUrl = auth.getBaseUrl();
|
|
257
|
+
const body = {
|
|
258
|
+
isTemplate: 0,
|
|
259
|
+
belongType: parseInt(cmdOpts.belong) || 1,
|
|
260
|
+
current: parseInt(cmdOpts.pageNo) || 1,
|
|
261
|
+
pageSize: parseInt(cmdOpts.pageSize) || 20,
|
|
262
|
+
searchParams: {
|
|
263
|
+
searchDatas: { type: 'and', status: '0', formType: 'all' },
|
|
264
|
+
quickSearchDatas: {},
|
|
265
|
+
otherSearchDatas: {},
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
if (cmdOpts.name)
|
|
269
|
+
body.searchParams.quickSearchDatas.workflowName = cmdOpts.name;
|
|
270
|
+
if (cmdOpts.id)
|
|
271
|
+
body.searchParams.otherSearchDatas.workflowId = cmdOpts.id;
|
|
272
|
+
if (cmdOpts.type)
|
|
273
|
+
body.searchParams.otherSearchDatas.workflowType = cmdOpts.type;
|
|
274
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/baseSet/getBaseInfoList`, body);
|
|
275
|
+
if (data?.code !== 200 || !data?.status) {
|
|
276
|
+
console.error(`查询失败: ${data?.msg || '未知错误'}`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
const records = data?.data?.data || [];
|
|
280
|
+
const total = data?.data?.total || 0;
|
|
281
|
+
if (opts.json) {
|
|
282
|
+
console.log(JSON.stringify({ total, current: data?.data?.current, pageSize: data?.data?.pageSize, records }, null, 2));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.log(`共 ${total} 个工作流 (第 ${data?.data?.current || 1} 页):\n`);
|
|
286
|
+
for (const r of records) {
|
|
287
|
+
const statusLabel = r.status === '1' ? '启用' : r.status === '5' ? '停用' : r.status || '-';
|
|
288
|
+
console.log(` ${r.id} ${(r.workflowName || '').padEnd(24)} ${statusLabel} ${r.typeName || ''} ${r.subCompanyName || ''}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
wf.command('nodes')
|
|
293
|
+
.description('查看流程节点详情')
|
|
294
|
+
.argument('<workflowId>', '流程 ID')
|
|
295
|
+
.option('--out <file>', '将 JSON 输出写入文件(UTF-8)')
|
|
296
|
+
.option('--mode <mode>', '展示模式: chart(默认, 流程图XML) / list(节点编辑列表, 含分流/合流/属性)', 'chart')
|
|
297
|
+
.action(async (workflowId, cmdOpts) => {
|
|
298
|
+
const opts = program.opts();
|
|
299
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
300
|
+
if (!auth)
|
|
301
|
+
process.exit(1);
|
|
302
|
+
const client = new ApiClient(auth);
|
|
303
|
+
const baseUrl = auth.getBaseUrl();
|
|
304
|
+
// --mode list: use getNodeEditTable (list-based, richer info)
|
|
305
|
+
if (cmdOpts.mode === 'list') {
|
|
306
|
+
const nodeData = await client.get(`${baseUrl}/api/bs/workflow/pathdef/nodeBase/getNodeEditTable?workflowId=${workflowId}`);
|
|
307
|
+
if (nodeData?.code !== 200 || !nodeData?.status) {
|
|
308
|
+
console.error(`查询失败: ${nodeData?.msg || '未知错误'}`);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
const nodes = nodeData?.data?.data || [];
|
|
312
|
+
const nodeTypeLabels = {
|
|
313
|
+
'0': '发起', '1': '审批', '2': '确认', '3': '结束',
|
|
314
|
+
'5': '投票', '7': '自动处理', '8': '等待',
|
|
315
|
+
'forkstart': '分流点', 'forkend': '合流点',
|
|
316
|
+
};
|
|
317
|
+
const mergeLabels = { '0': '全部分支', '1': '指定分支', '2': '分支数', '3': '比例' };
|
|
318
|
+
if (cmdOpts?.out) {
|
|
319
|
+
const { writeFileSync } = await import('fs');
|
|
320
|
+
writeFileSync(cmdOpts.out, JSON.stringify({ nodes, formItemProps: nodeData?.data?.formItemProps }, null, 2), 'utf-8');
|
|
321
|
+
console.error(`已写入: ${cmdOpts.out}`);
|
|
322
|
+
}
|
|
323
|
+
else if (opts.json) {
|
|
324
|
+
console.log(JSON.stringify({ nodes }, null, 2));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
console.log(`共 ${nodes.length} 个节点:\n`);
|
|
328
|
+
for (const n of nodes) {
|
|
329
|
+
const typeLabel = nodeTypeLabels[n.nodeType] || n.nodeType;
|
|
330
|
+
const attr = n.nodeAttr && n.nodeAttr !== '0' ? ` attr=${n.nodeAttr}` : '';
|
|
331
|
+
const merge = n.mergeType != null ? ` 合流:${mergeLabels[n.mergeType] || n.mergeType}` : '';
|
|
332
|
+
console.log(` ${n.id} ${(n.nodeName || '').padEnd(20)} ${typeLabel}${attr}${merge}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const data = await client.postForm(`${baseUrl}/api/bs/workflow/pathdef/chart/getXml`, `workflowId=${workflowId}&keepTitle=undefined&showType=0`);
|
|
338
|
+
if (data?.code !== 200) {
|
|
339
|
+
console.error(`查询失败: ${data?.msg || '未知错误'}`);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
const xml = data?.data?.xml || '';
|
|
343
|
+
if (!xml) {
|
|
344
|
+
console.error('该流程没有 XML 数据');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
// Parse nodes and links from mxGraph XML
|
|
348
|
+
const nodeMap = new Map();
|
|
349
|
+
const links = [];
|
|
350
|
+
const nodeIdToRef = new Map(); // nodeId → node ref (node_xxx)
|
|
351
|
+
// Extract node cells: vertex="1" with nodeId (root cells have no nodeId)
|
|
352
|
+
const nodeRe = /<mxCell\s[^>]*nodeId="([^"]*)"[^>]*>/g;
|
|
353
|
+
for (const match of xml.matchAll(nodeRe)) {
|
|
354
|
+
const tag = match[0];
|
|
355
|
+
const nodeId = match[1]; // captured from regex
|
|
356
|
+
const value = tag.match(/value="([^"]*)"/)?.[1] || '';
|
|
357
|
+
const nodeType = tag.match(/nodeType="([^"]*)"/)?.[1] || '';
|
|
358
|
+
const operator = tag.match(/operatorNameStr="([^"]*)"/)?.[1] || '';
|
|
359
|
+
const id = tag.match(/id="([^"]*)"/)?.[1] || ''; // node_xxx ref
|
|
360
|
+
const nodeName = value || '(未命名)';
|
|
361
|
+
nodeMap.set(nodeId, { name: nodeName, type: nodeType, operator });
|
|
362
|
+
if (id)
|
|
363
|
+
nodeIdToRef.set(nodeId, id);
|
|
364
|
+
}
|
|
365
|
+
// Extract link cells: edge="1"
|
|
366
|
+
const linkRe = /<mxCell\s[^>]*edge="1"[^>]*>/g;
|
|
367
|
+
for (const match of xml.matchAll(linkRe)) {
|
|
368
|
+
const tag = match[0];
|
|
369
|
+
const linkId = tag.match(/linkId="([^"]*)"/)?.[1] || '';
|
|
370
|
+
const source = tag.match(/source="([^"]*)"/)?.[1] || '';
|
|
371
|
+
const target = tag.match(/target="([^"]*)"/)?.[1] || '';
|
|
372
|
+
const label = tag.match(/linkNameInputValue="([^"]*)"/)?.[1] || '';
|
|
373
|
+
const hasCondition = tag.match(/hasCondition="([^"]*)"/)?.[1] || '0';
|
|
374
|
+
// Resolve source/target from node ref (node_xxx) to nodeId
|
|
375
|
+
const sourceNodeId = [...nodeIdToRef.entries()].find(([, ref]) => ref === source)?.[0] || source;
|
|
376
|
+
const targetNodeId = [...nodeIdToRef.entries()].find(([, ref]) => ref === target)?.[0] || target;
|
|
377
|
+
links.push({ linkId, source: sourceNodeId, target: targetNodeId, label, condition: hasCondition });
|
|
378
|
+
}
|
|
379
|
+
// Build ordered node list following links
|
|
380
|
+
const visited = new Set();
|
|
381
|
+
const ordered = [];
|
|
382
|
+
// Find start node (source not present as target of any link, or nodeType=0)
|
|
383
|
+
const targetIds = new Set(links.map(l => l.target));
|
|
384
|
+
const startNodeId = [...nodeMap.keys()].find(id => {
|
|
385
|
+
const n = nodeMap.get(id);
|
|
386
|
+
return n.type === '0' || !targetIds.has(id);
|
|
387
|
+
});
|
|
388
|
+
if (startNodeId) {
|
|
389
|
+
const queue = [startNodeId];
|
|
390
|
+
while (queue.length > 0) {
|
|
391
|
+
const cur = queue.shift();
|
|
392
|
+
if (visited.has(cur))
|
|
393
|
+
continue;
|
|
394
|
+
visited.add(cur);
|
|
395
|
+
ordered.push(cur);
|
|
396
|
+
for (const l of links.filter(l => l.source === cur)) {
|
|
397
|
+
if (!visited.has(l.target))
|
|
398
|
+
queue.push(l.target);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Remaining unvisited nodes
|
|
403
|
+
for (const nodeId of nodeMap.keys()) {
|
|
404
|
+
if (!visited.has(nodeId))
|
|
405
|
+
ordered.push(nodeId);
|
|
406
|
+
}
|
|
407
|
+
const TYPE_LABELS = {
|
|
408
|
+
'0': '发起',
|
|
409
|
+
'1': '审批',
|
|
410
|
+
'2': '分支',
|
|
411
|
+
'3': '归档',
|
|
412
|
+
'4': '抄送',
|
|
413
|
+
};
|
|
414
|
+
const nodesJson = ordered.map(nodeId => {
|
|
415
|
+
const n = nodeMap.get(nodeId);
|
|
416
|
+
return { nodeId, name: n.name, type: n.type, typeLabel: TYPE_LABELS[n.type] || n.type, operator: n.operator };
|
|
417
|
+
});
|
|
418
|
+
const jsonOutput = JSON.stringify({ nodes: nodesJson, links }, null, 2);
|
|
419
|
+
if (cmdOpts?.out) {
|
|
420
|
+
const { writeFileSync } = await import('fs');
|
|
421
|
+
writeFileSync(cmdOpts.out, jsonOutput, 'utf-8');
|
|
422
|
+
console.error(`已写入: ${cmdOpts.out}`);
|
|
423
|
+
}
|
|
424
|
+
else if (opts.json) {
|
|
425
|
+
console.log(jsonOutput);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const wfName = data?.data?.workflowDatas?.workflowName || workflowId;
|
|
429
|
+
console.log(`流程: ${wfName}`);
|
|
430
|
+
console.log(`共 ${nodeMap.size} 个节点, ${links.length} 条连线:\n`);
|
|
431
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
432
|
+
const nodeId = ordered[i];
|
|
433
|
+
const n = nodeMap.get(nodeId);
|
|
434
|
+
const typeLabel = TYPE_LABELS[n.type] || n.type;
|
|
435
|
+
const op = n.operator ? ` [${n.operator}]` : '';
|
|
436
|
+
console.log(` ${i + 1}. ${n.name}`);
|
|
437
|
+
console.log(` ${nodeId} ${typeLabel}${op}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
wf.command('set-operator')
|
|
442
|
+
.description('设置审批节点的操作者')
|
|
443
|
+
.argument('<workflowId>', '流程 ID')
|
|
444
|
+
.argument('<nodeId>', '节点 ID')
|
|
445
|
+
.argument('[keyword]', '人员/角色关键词(--type form 时不需要)', '')
|
|
446
|
+
.option('--type <type>', '操作者类型: user(指定人) / role(指定角色) / dept(指定部门) / position(指定岗位) / all(所有人) / free(自由选择) / superior(发起人上级) / prev-node(上一节点) / form(表单相关人员) / matrix(矩阵对应)', 'user')
|
|
447
|
+
.option('--source <formObjId>', '表单 ID(--type form 时必填)')
|
|
448
|
+
.option('--target <fieldId>', '人员字段 ID(--type form 时必填)')
|
|
449
|
+
.option('--condition <json>', '筛选条件 JSON / @文件(--type form 时可选)')
|
|
450
|
+
.option('--sign-order <order>', '签署顺序: 会签(默认) / 非会签 / 竞签 / 依次处理 / 抄送 / 抄送需批注', '会签')
|
|
451
|
+
.option('--orders <orders>', '批次号(两位小数,如 0.00 / 1.00)', '0.00')
|
|
452
|
+
.option('--workflow-form <objId>', '流程绑定的表单 objId(valueFieldId 引用字段时在此表单查找)')
|
|
453
|
+
.option('--matrix-id <id>', '矩阵 ID(--type matrix 必填)')
|
|
454
|
+
.option('--value-config-id <id>', '值字段 configId,即矩阵列的字段 ID(--type matrix 必填)')
|
|
455
|
+
.option('--matrix-rule <rules...>', '矩阵条件映射规则。格式: "条件configId:数据来源type:匹配operate:维度virtualId"。type: 0=指定部门/1=发起人部门/5=当前操作人部门。operate: 1=本部门/2=本及下级/3=本及上级/49=逐级向上/67=本及下级逐级向上')
|
|
456
|
+
.option('--batch-condition <expr>', '批次条件表达式(--type matrix 可选)')
|
|
457
|
+
.option('--append', '追加操作者组(不清空节点已有操作者组;默认是清空后全量重建)')
|
|
458
|
+
.action(async (workflowId, nodeId, keyword, cmdOpts) => {
|
|
459
|
+
const opts = program.opts();
|
|
460
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
461
|
+
if (!auth)
|
|
462
|
+
process.exit(1);
|
|
463
|
+
const client = new ApiClient(auth);
|
|
464
|
+
const baseUrl = auth.getBaseUrl();
|
|
465
|
+
// 追加模式(--append)下提示语用「追加」,否则「设置」
|
|
466
|
+
const verb = cmdOpts.append ? '已追加操作者组' : '已设置操作者';
|
|
467
|
+
// 0. 清理节点已有的操作者组(防止重复设置堆积多组,全量重建)。
|
|
468
|
+
// --append 时跳过清理:新组追加到已有组之后(如 部门领导矩阵 + 门店经理矩阵 双组会签)。
|
|
469
|
+
if (!cmdOpts.append) {
|
|
470
|
+
const { clearNodeOperatorGroups } = await import('../services/workflow-operator.js');
|
|
471
|
+
try {
|
|
472
|
+
const cleared = await clearNodeOperatorGroups(client, baseUrl, workflowId, nodeId);
|
|
473
|
+
if (cleared > 0)
|
|
474
|
+
console.error(` 已清理 ${cleared} 个旧操作者组`);
|
|
475
|
+
}
|
|
476
|
+
catch { /* 无旧组或读取失败则跳过 */ }
|
|
477
|
+
}
|
|
478
|
+
const isRole = cmdOpts.type === 'role';
|
|
479
|
+
// 1. Search for user or role
|
|
480
|
+
let targetId = '';
|
|
481
|
+
let targetName = '';
|
|
482
|
+
let browserData;
|
|
483
|
+
let browserDatas = [];
|
|
484
|
+
let groupType;
|
|
485
|
+
let groupTypeId;
|
|
486
|
+
let itemName;
|
|
487
|
+
let itemLevel = '';
|
|
488
|
+
let includeType = '';
|
|
489
|
+
let includeTypeCN = '';
|
|
490
|
+
const isSuperior = cmdOpts.type === 'superior';
|
|
491
|
+
if (isSuperior) {
|
|
492
|
+
// Superior type: no keyword needed, fixed fields
|
|
493
|
+
targetId = '';
|
|
494
|
+
targetName = '直接上级';
|
|
495
|
+
groupType = '流程发起人';
|
|
496
|
+
groupTypeId = '114';
|
|
497
|
+
itemName = '直接上级';
|
|
498
|
+
// Build payload directly — superior has no browserData
|
|
499
|
+
console.error(` 操作者: 发起人上级 (直接上级) 类型: ${groupType}`);
|
|
500
|
+
const rg = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/getLayoutConfigInfo`, {
|
|
501
|
+
groupId: '', workflowId, nodeId, sourceType: '1', isOperatorRuleAuth: true,
|
|
502
|
+
});
|
|
503
|
+
const reserveGroupId = rg?.data?.reserveGroupId || '';
|
|
504
|
+
const payload = {
|
|
505
|
+
workflowId, nodeId, groupId: '', isCreate: '', reserveGroupId,
|
|
506
|
+
itemDatas: [{
|
|
507
|
+
includeType: '13',
|
|
508
|
+
includeTypeCN: '上级',
|
|
509
|
+
objLevelType: '1',
|
|
510
|
+
objLevelTypeCN: '全部机构',
|
|
511
|
+
objectId: '1',
|
|
512
|
+
objectIdCN: '直接上级',
|
|
513
|
+
signOrder: '1',
|
|
514
|
+
signOrderCN: '会签',
|
|
515
|
+
isOut: false,
|
|
516
|
+
isSaveHrmOperator: false,
|
|
517
|
+
id: '1',
|
|
518
|
+
groupType,
|
|
519
|
+
itemName,
|
|
520
|
+
itemLevel: '',
|
|
521
|
+
endApprovers: '',
|
|
522
|
+
hrmOperators: '',
|
|
523
|
+
levelObj: '',
|
|
524
|
+
workflowId, nodeId,
|
|
525
|
+
conditionId: '',
|
|
526
|
+
orders: '0.00',
|
|
527
|
+
groupTypeId,
|
|
528
|
+
isOpen: true,
|
|
529
|
+
bigTypeId: 1,
|
|
530
|
+
condition: '',
|
|
531
|
+
groupCondition: '',
|
|
532
|
+
level: '',
|
|
533
|
+
isSuportVirtualId: false,
|
|
534
|
+
openProjectOrganization: false,
|
|
535
|
+
isExtendType: 0,
|
|
536
|
+
}],
|
|
537
|
+
oldGroupName: '',
|
|
538
|
+
refWorkflowId: '',
|
|
539
|
+
oldItemDatas: [],
|
|
540
|
+
groupName: '',
|
|
541
|
+
deleteDatas: [],
|
|
542
|
+
mustreview: 0,
|
|
543
|
+
};
|
|
544
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/saveOperatorGroup`, payload);
|
|
545
|
+
if (data?.code === 200 && data?.status) {
|
|
546
|
+
const msg = opts.json
|
|
547
|
+
? JSON.stringify({ success: true, workflowId, nodeId, operator: '直接上级', type: groupType, append: !!cmdOpts.append })
|
|
548
|
+
: `${verb}: 发起人上级(直接上级) → 节点 ${nodeId}`;
|
|
549
|
+
console.log(msg);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
console.error(`设置失败: ${data?.msg || '未知错误'}`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const isForm = cmdOpts.type === 'form';
|
|
558
|
+
if (isForm) {
|
|
559
|
+
// --type form: 从表单数据中动态获取相关人员
|
|
560
|
+
if (!cmdOpts.source || !cmdOpts.target) {
|
|
561
|
+
console.error('--type form 需要 --source <表单ID> 和 --target <人员字段ID>');
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
// Parse optional condition
|
|
565
|
+
let ebFormCondition = {};
|
|
566
|
+
let ebFormConditionCn = '';
|
|
567
|
+
if (cmdOpts.condition) {
|
|
568
|
+
let condStr = cmdOpts.condition;
|
|
569
|
+
if (condStr.startsWith('@')) {
|
|
570
|
+
const { readFileSync: rfs, existsSync: exs } = await import('fs');
|
|
571
|
+
const fp = condStr.slice(1);
|
|
572
|
+
if (!exs(fp)) {
|
|
573
|
+
console.error(`文件不存在: ${fp}`);
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
condStr = rfs(fp, 'utf-8');
|
|
577
|
+
}
|
|
578
|
+
const cond = JSON.parse(condStr);
|
|
579
|
+
const isFormFieldValue = !!cond.valueFieldId;
|
|
580
|
+
const funcMap = { gt: '大于', lt: '小于', eq: '等于', gte: '大于等于', lte: '小于等于', like: '包含' };
|
|
581
|
+
// Need field list to resolve valueFieldId
|
|
582
|
+
// Determine which form to look up valueFieldId in:
|
|
583
|
+
// workflowForm (流程绑定的表单) takes priority, fallback to source (矩阵表单)
|
|
584
|
+
const lookupFormObjId = cmdOpts.workflowForm || cmdOpts.source;
|
|
585
|
+
let allSrcFields = [];
|
|
586
|
+
if (isFormFieldValue) {
|
|
587
|
+
try {
|
|
588
|
+
const srcFields = await client.post(`${baseUrl}/api/ebuilder/form/dataset/v1/getFields`, {
|
|
589
|
+
objId: lookupFormObjId, userid: auth.userId, tenantKey: auth.tenantKey,
|
|
590
|
+
});
|
|
591
|
+
allSrcFields = [...(srcFields?.data?.mainFields || []), ...(srcFields?.data?.subFields || [])];
|
|
592
|
+
}
|
|
593
|
+
catch { /* ignore */ }
|
|
594
|
+
}
|
|
595
|
+
// Build conditions object
|
|
596
|
+
let conditions;
|
|
597
|
+
let valueDesc;
|
|
598
|
+
if (isFormFieldValue) {
|
|
599
|
+
const refField = allSrcFields.find((f) => f.fieldId === cond.valueFieldId || f.id === cond.valueFieldId);
|
|
600
|
+
if (!refField) {
|
|
601
|
+
console.error(`未找到引用字段: ${cond.valueFieldId}(在表单 ${lookupFormObjId} 中)`);
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
const ffType = refField.fieldType || refField.type || 'Text';
|
|
605
|
+
const ffId = refField.fieldId || refField.id || '';
|
|
606
|
+
const fmk = {
|
|
607
|
+
id: ffId,
|
|
608
|
+
text: refField.fieldName || refField.name || '',
|
|
609
|
+
type: ffType,
|
|
610
|
+
compType: ffType,
|
|
611
|
+
formInfo: { fieldId: ffId },
|
|
612
|
+
objId: lookupFormObjId,
|
|
613
|
+
name: refField.dataKey || refField.columnName || '',
|
|
614
|
+
showName: refField.fieldName || refField.name || '',
|
|
615
|
+
multiSelect: refField.multiSelect || 'false',
|
|
616
|
+
mainField: true,
|
|
617
|
+
module: '',
|
|
618
|
+
parseMark: true,
|
|
619
|
+
usage: '',
|
|
620
|
+
subTableId: '',
|
|
621
|
+
lazyLoad: false,
|
|
622
|
+
fieldClassification: '',
|
|
623
|
+
browserType: '',
|
|
624
|
+
optionType: '',
|
|
625
|
+
disAllowedUsage: [],
|
|
626
|
+
mainPrimaryKey: false,
|
|
627
|
+
deleteType: false,
|
|
628
|
+
parentId: '',
|
|
629
|
+
physicalField: '',
|
|
630
|
+
placeholderMark: false,
|
|
631
|
+
dbFieldType: '',
|
|
632
|
+
richEditor: false,
|
|
633
|
+
describe: '',
|
|
634
|
+
changeTrigger: '1',
|
|
635
|
+
browserParams: {
|
|
636
|
+
formId: '', layoutId: '',
|
|
637
|
+
serviceModule: 'ebuilderform',
|
|
638
|
+
fieldId: ffId,
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
if (ffType === 'Employee') {
|
|
642
|
+
fmk.conditionType = [
|
|
643
|
+
{ id: 'in', content: '属于', disabled: false },
|
|
644
|
+
{ id: 'notIn', content: '不属于', disabled: false },
|
|
645
|
+
];
|
|
646
|
+
}
|
|
647
|
+
conditions = {
|
|
648
|
+
func: cond.func || 'eq',
|
|
649
|
+
value: { compareFormField: fmk },
|
|
650
|
+
type: 'formField',
|
|
651
|
+
filterValueType: ffType,
|
|
652
|
+
text: { compareFormField: { ...fmk } },
|
|
653
|
+
isEsb: false,
|
|
654
|
+
};
|
|
655
|
+
valueDesc = refField.fieldName || refField.name || cond.valueFieldId;
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
conditions = {
|
|
659
|
+
func: cond.func || 'gt',
|
|
660
|
+
value: String(cond.value || ''),
|
|
661
|
+
type: '',
|
|
662
|
+
text: '',
|
|
663
|
+
isEsb: false,
|
|
664
|
+
};
|
|
665
|
+
valueDesc = String(cond.value || '');
|
|
666
|
+
}
|
|
667
|
+
// Build ebFormCondition from simplified condition
|
|
668
|
+
const condKey = String(Date.now());
|
|
669
|
+
ebFormCondition = {
|
|
670
|
+
key: condKey,
|
|
671
|
+
relationship: '1',
|
|
672
|
+
isParent: true,
|
|
673
|
+
isNewFilter: true,
|
|
674
|
+
parentNode: '',
|
|
675
|
+
datas: [{
|
|
676
|
+
isParent: false,
|
|
677
|
+
parentNode: condKey,
|
|
678
|
+
id: cond.fieldId,
|
|
679
|
+
fieldName: cond.fieldId,
|
|
680
|
+
fieldType: cond.fieldType || 'Text',
|
|
681
|
+
optionType: '',
|
|
682
|
+
text: cond.fieldName || cond.fieldId,
|
|
683
|
+
objId: cmdOpts.source,
|
|
684
|
+
compType: (cond.fieldType === 'Number' || cond.fieldType === 'Money') ? 'NumberComponent' : 'TextComponent',
|
|
685
|
+
type: 'condition',
|
|
686
|
+
conditions,
|
|
687
|
+
multiSelect: 'false',
|
|
688
|
+
formInfo: { fieldId: cond.fieldId },
|
|
689
|
+
originBrowserType: '',
|
|
690
|
+
module: '',
|
|
691
|
+
mainField: true,
|
|
692
|
+
subTableId: '',
|
|
693
|
+
key: String(Date.now()),
|
|
694
|
+
config: { fieldId: cond.fieldId },
|
|
695
|
+
}],
|
|
696
|
+
version: 1,
|
|
697
|
+
filterIsNull: true,
|
|
698
|
+
};
|
|
699
|
+
ebFormConditionCn = `( ${cond.fieldName || cond.fieldId} ${funcMap[cond.func] || cond.func} '${valueDesc}' )`;
|
|
700
|
+
}
|
|
701
|
+
const signOrder = cmdOpts.signOrder === '或签' ? '2' : '1';
|
|
702
|
+
const signOrderCN = cmdOpts.signOrder === '或签' ? '或签' : '会签';
|
|
703
|
+
// Try to get form name and field name via API
|
|
704
|
+
let sourceIdCN = '';
|
|
705
|
+
let targetIdCN = '';
|
|
706
|
+
try {
|
|
707
|
+
const formFields = await client.post(`${baseUrl}/api/ebuilder/form/dataset/v1/getFields`, {
|
|
708
|
+
objId: cmdOpts.source, userid: auth.userId, tenantKey: auth.tenantKey,
|
|
709
|
+
});
|
|
710
|
+
const allFields = [...(formFields?.data?.mainFields || []), ...(formFields?.data?.subFields || [])];
|
|
711
|
+
const targetField = allFields.find((f) => f.fieldId === cmdOpts.target || f.id === cmdOpts.target);
|
|
712
|
+
if (targetField) {
|
|
713
|
+
targetIdCN = targetField.fieldName || targetField.name || '';
|
|
714
|
+
}
|
|
715
|
+
// Try to get form name from app list or form list — skip for now, not critical
|
|
716
|
+
}
|
|
717
|
+
catch { /* ignore */ }
|
|
718
|
+
console.error(` 操作者: 表单相关人员 source=${cmdOpts.source} target=${cmdOpts.target} ${ebFormConditionCn || '(无筛选)'}`);
|
|
719
|
+
const rg = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/getLayoutConfigInfo`, {
|
|
720
|
+
groupId: '', workflowId, nodeId, sourceType: '1', isOperatorRuleAuth: true,
|
|
721
|
+
});
|
|
722
|
+
const reserveGroupId = rg?.data?.reserveGroupId || '';
|
|
723
|
+
const itemData = {
|
|
724
|
+
signOrder,
|
|
725
|
+
signOrderCN,
|
|
726
|
+
isOut: false,
|
|
727
|
+
isSaveHrmOperator: false,
|
|
728
|
+
id: '1',
|
|
729
|
+
groupType: 'EB表单相关(人员基础数据)',
|
|
730
|
+
itemName: targetIdCN || cmdOpts.target,
|
|
731
|
+
itemLevel: '',
|
|
732
|
+
endApprovers: '',
|
|
733
|
+
hrmOperators: '',
|
|
734
|
+
levelObj: '',
|
|
735
|
+
workflowId, nodeId,
|
|
736
|
+
conditionId: '',
|
|
737
|
+
orders: '0.00',
|
|
738
|
+
groupTypeId: '1201',
|
|
739
|
+
isOpen: true,
|
|
740
|
+
bigTypeId: 12,
|
|
741
|
+
sourceId: cmdOpts.source,
|
|
742
|
+
sourceIdCN: sourceIdCN || cmdOpts.source,
|
|
743
|
+
targetId: cmdOpts.target,
|
|
744
|
+
targetIdCN: targetIdCN || cmdOpts.target,
|
|
745
|
+
ebFormCondition,
|
|
746
|
+
ebFormConditionCn,
|
|
747
|
+
condition: '',
|
|
748
|
+
groupCondition: '',
|
|
749
|
+
isSuportVirtualId: true,
|
|
750
|
+
openProjectOrganization: false,
|
|
751
|
+
isExtendType: 0,
|
|
752
|
+
};
|
|
753
|
+
const payload = {
|
|
754
|
+
workflowId, nodeId, groupId: '', isCreate: '', reserveGroupId,
|
|
755
|
+
itemDatas: [itemData],
|
|
756
|
+
oldGroupName: '',
|
|
757
|
+
refWorkflowId: '',
|
|
758
|
+
oldItemDatas: [],
|
|
759
|
+
groupName: '',
|
|
760
|
+
deleteDatas: [],
|
|
761
|
+
mustreview: 0,
|
|
762
|
+
};
|
|
763
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/saveOperatorGroup`, payload);
|
|
764
|
+
if (data?.code === 200 && data?.status) {
|
|
765
|
+
const msg = opts.json
|
|
766
|
+
? JSON.stringify({ success: true, workflowId, nodeId, type: 'form', source: cmdOpts.source, target: cmdOpts.target, append: !!cmdOpts.append })
|
|
767
|
+
: `${verb}: 表单相关人员 → 节点 ${nodeId} (${targetIdCN || cmdOpts.target})`;
|
|
768
|
+
console.log(msg);
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
console.error(`设置失败: ${data?.msg || '未知错误'}`);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
// New types from Phase 4: dept / position / all / free / prev-node
|
|
777
|
+
if (cmdOpts.type === 'dept' || cmdOpts.type === 'position' || cmdOpts.type === 'all' || cmdOpts.type === 'free' || cmdOpts.type === 'prev-node') {
|
|
778
|
+
let groupType = '';
|
|
779
|
+
let groupTypeId = '';
|
|
780
|
+
let itemName = '';
|
|
781
|
+
const itemData = {};
|
|
782
|
+
if (cmdOpts.type === 'dept') {
|
|
783
|
+
if (!keyword) {
|
|
784
|
+
console.error('--type dept 需要部门关键词');
|
|
785
|
+
process.exit(1);
|
|
786
|
+
}
|
|
787
|
+
const deptRes = await client.post(`${baseUrl}/api/workflow/pathdef/common/browser/complete/department`, {
|
|
788
|
+
browserMultiple: true, virtualType: 'All', showSanYuan: true, newly: 0, searchValue: keyword,
|
|
789
|
+
});
|
|
790
|
+
const dept = deptRes?.data?.data?.[0];
|
|
791
|
+
if (!dept) {
|
|
792
|
+
console.error(`未找到部门: ${keyword}`);
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
groupType = '指定部门';
|
|
796
|
+
groupTypeId = '102';
|
|
797
|
+
itemName = dept.name || keyword;
|
|
798
|
+
itemData.hrmOperators = dept.id || '';
|
|
799
|
+
itemData.hrmOperatorsCN = dept.name || '';
|
|
800
|
+
itemData.includeType = '2';
|
|
801
|
+
itemData.includeTypeCN = '本部门及下级部门';
|
|
802
|
+
itemData.level = '10~90';
|
|
803
|
+
itemData.itemLevel = '10~90';
|
|
804
|
+
itemData.hrmBrowserDataDtos = [{ id: dept.id, name: dept.name, _entityType: 'department' }];
|
|
805
|
+
console.error(` 操作者: ${dept.name} (${dept.id}) 类型: 指定部门(包含下级)`);
|
|
806
|
+
}
|
|
807
|
+
else if (cmdOpts.type === 'position') {
|
|
808
|
+
if (!keyword) {
|
|
809
|
+
console.error('--type position 需要岗位关键词');
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
const posRes = await client.post(`${baseUrl}/api/workflow/pathdef/common/browser/complete/position`, {
|
|
813
|
+
browserMultiple: true, newly: 0, searchValue: keyword,
|
|
814
|
+
});
|
|
815
|
+
const pos = posRes?.data?.data?.[0];
|
|
816
|
+
if (!pos) {
|
|
817
|
+
console.error(`未找到岗位: ${keyword}`);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
groupType = '指定岗位';
|
|
821
|
+
groupTypeId = '105';
|
|
822
|
+
itemName = pos.name || keyword;
|
|
823
|
+
itemData.hrmOperators = pos.id || '';
|
|
824
|
+
itemData.hrmOperatorsCN = pos.name || '';
|
|
825
|
+
itemData.objLevelType = '1';
|
|
826
|
+
itemData.objLevelTypeCN = '全部机构';
|
|
827
|
+
itemData.hrmBrowserDataDtos = [{ id: pos.id, name: pos.name, _entityType: 'position' }];
|
|
828
|
+
console.error(` 操作者: ${pos.name} (${pos.id}) 类型: 指定岗位(全部机构)`);
|
|
829
|
+
}
|
|
830
|
+
else if (cmdOpts.type === 'all') {
|
|
831
|
+
groupType = '所有人';
|
|
832
|
+
groupTypeId = '110';
|
|
833
|
+
itemName = '所有人';
|
|
834
|
+
itemData.level = '0~100';
|
|
835
|
+
itemData.itemLevel = '0~100';
|
|
836
|
+
itemData.hrmOperators = '';
|
|
837
|
+
console.error(` 操作者: 所有人 类型: ${groupType}`);
|
|
838
|
+
}
|
|
839
|
+
else if (cmdOpts.type === 'free') {
|
|
840
|
+
groupType = '自由选择';
|
|
841
|
+
groupTypeId = '112';
|
|
842
|
+
itemName = '发起时自由选择';
|
|
843
|
+
itemData.hrmOperators = '';
|
|
844
|
+
console.error(` 操作者: 发起时自由选择 类型: ${groupType}`);
|
|
845
|
+
}
|
|
846
|
+
else if (cmdOpts.type === 'prev-node') {
|
|
847
|
+
groupType = '节点操作者';
|
|
848
|
+
groupTypeId = '115';
|
|
849
|
+
itemName = '上一节点操作者';
|
|
850
|
+
itemData.includeType = '23';
|
|
851
|
+
itemData.includeTypeCN = '[上一节点]';
|
|
852
|
+
itemData.objLevelType = '13';
|
|
853
|
+
itemData.objLevelTypeCN = '本人';
|
|
854
|
+
itemData.isLastCycle = '1';
|
|
855
|
+
itemData.isLastCycleCN = '仅最后一轮操作者';
|
|
856
|
+
itemData.hrmOperators = '';
|
|
857
|
+
console.error(` 操作者: 上一节点操作者(本人) 类型: ${groupType}`);
|
|
858
|
+
}
|
|
859
|
+
// Common fields
|
|
860
|
+
const signOrderMap = {
|
|
861
|
+
'会签': { code: '1', cn: '会签' },
|
|
862
|
+
'非会签': { code: '0', cn: '非会签' },
|
|
863
|
+
'竞签': { code: '0', cn: '非会签', competeSignOrder: '1', competeSignOrderCN: '竞签' },
|
|
864
|
+
'依次处理': { code: '2', cn: '依次处理', executeOrders: '1-1,2-2', executeOrdersCN: '安全级别-正序,人员显示顺序-倒序' },
|
|
865
|
+
'抄送': { code: '3', cn: '抄送' },
|
|
866
|
+
'抄送需批注': { code: '4', cn: '抄送' },
|
|
867
|
+
};
|
|
868
|
+
const so = signOrderMap[cmdOpts.signOrder || '会签'] || signOrderMap['会签'];
|
|
869
|
+
// Get reserveGroupId
|
|
870
|
+
const rg = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/getLayoutConfigInfo`, {
|
|
871
|
+
groupId: '', workflowId, nodeId, sourceType: '1', isOperatorRuleAuth: true,
|
|
872
|
+
});
|
|
873
|
+
const reserveGroupId = rg?.data?.reserveGroupId || '';
|
|
874
|
+
const payload = {
|
|
875
|
+
workflowId, nodeId, groupId: '', isCreate: '', reserveGroupId,
|
|
876
|
+
itemDatas: [{
|
|
877
|
+
...itemData,
|
|
878
|
+
signOrder: so.code,
|
|
879
|
+
signOrderCN: so.cn,
|
|
880
|
+
orders: cmdOpts.orders || '0.00',
|
|
881
|
+
isOut: false, isSaveHrmOperator: false, id: '1',
|
|
882
|
+
groupType, itemName,
|
|
883
|
+
endApprovers: '', levelObj: '',
|
|
884
|
+
workflowId, nodeId, conditionId: '',
|
|
885
|
+
groupTypeId, isOpen: true, bigTypeId: 1,
|
|
886
|
+
condition: '', groupCondition: '',
|
|
887
|
+
isSuportVirtualId: false, openProjectOrganization: false, isExtendType: 0,
|
|
888
|
+
...(so.competeSignOrder ? { competeSignOrder: so.competeSignOrder, competeSignOrderCN: so.competeSignOrderCN } : {}),
|
|
889
|
+
...(so.executeOrders ? { executeOrders: so.executeOrders, executeOrdersCN: so.executeOrdersCN } : {}),
|
|
890
|
+
}],
|
|
891
|
+
oldGroupName: '', refWorkflowId: '', oldItemDatas: [],
|
|
892
|
+
groupName: '', deleteDatas: [], mustreview: 0,
|
|
893
|
+
};
|
|
894
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/saveOperatorGroup`, payload);
|
|
895
|
+
if (data?.code === 200 && data?.status) {
|
|
896
|
+
const msg = opts.json
|
|
897
|
+
? JSON.stringify({ success: true, workflowId, nodeId, operator: itemName, type: groupType, append: !!cmdOpts.append })
|
|
898
|
+
: `${verb}: ${itemName} [${groupType}] → 节点 ${nodeId}`;
|
|
899
|
+
console.log(msg);
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
console.error(`设置失败: ${data?.msg || '未知错误'}`);
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
// --type matrix: 矩阵对应操作者
|
|
908
|
+
if (cmdOpts.type === 'matrix') {
|
|
909
|
+
if (!cmdOpts.matrixId) {
|
|
910
|
+
console.error('--type matrix 需要 --matrix-id <矩阵ID>');
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
if (!cmdOpts.valueConfigId) {
|
|
914
|
+
console.error('--type matrix 需要 --value-config-id <值字段configId>');
|
|
915
|
+
process.exit(1);
|
|
916
|
+
}
|
|
917
|
+
// Parse matrix rules: "条件configId:数据来源type:匹配operate:维度virtualId"
|
|
918
|
+
const matrixRules = [];
|
|
919
|
+
const ruleErrors = [];
|
|
920
|
+
let matrixRuleCondition = '';
|
|
921
|
+
const condParts = [];
|
|
922
|
+
if (cmdOpts.matrixRule && cmdOpts.matrixRule.length > 0) {
|
|
923
|
+
for (const ruleStr of cmdOpts.matrixRule) {
|
|
924
|
+
const parts = ruleStr.split(':');
|
|
925
|
+
const sourceField = parts[0];
|
|
926
|
+
const type = parts[1] || '0'; // 0=指定部门 1=发起人部门
|
|
927
|
+
const operate = parts[2] || '1'; // 1=本部门
|
|
928
|
+
const virtualId = parts[3] || '1'; // 1=行政组织
|
|
929
|
+
const typeLabels = { '0': '指定部门', '1': '发起人部门(系统)', '5': '当前操作人部门(系统)' };
|
|
930
|
+
const operateLabels = { '1': '本部门', '2': '本部门及下级部门', '3': '本部门及上级部门', '49': '本部门(逐级向上)', '67': '本部门及下级部门(逐级向上)' };
|
|
931
|
+
matrixRules.push({
|
|
932
|
+
sourceField, sourceFieldCN: '', sourceType: '', type, typeCN: typeLabels[type] || '',
|
|
933
|
+
virtualId, virtualIdCN: virtualId === '1' ? '行政组织' : virtualId,
|
|
934
|
+
operate: parseInt(operate) || 1, operateCN: operateLabels[operate] || '',
|
|
935
|
+
isFieldEmpty: 0,
|
|
936
|
+
});
|
|
937
|
+
condParts.push(`config_${sourceField} ${operateLabels[operate] || operate}`);
|
|
938
|
+
}
|
|
939
|
+
if (ruleErrors.length > 0) {
|
|
940
|
+
for (const e of ruleErrors)
|
|
941
|
+
console.error(` ❌ ${e}`);
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
matrixRuleCondition = condParts.join('; ');
|
|
945
|
+
}
|
|
946
|
+
// Get reserveGroupId
|
|
947
|
+
const rg = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/getLayoutConfigInfo`, {
|
|
948
|
+
groupId: '', workflowId, nodeId, sourceType: '1', isOperatorRuleAuth: true,
|
|
949
|
+
});
|
|
950
|
+
const reserveGroupId = rg?.data?.reserveGroupId || '';
|
|
951
|
+
const signOrderMap = {
|
|
952
|
+
'会签': { code: '1', cn: '会签' }, '非会签': { code: '0', cn: '非会签' },
|
|
953
|
+
};
|
|
954
|
+
const so = signOrderMap[cmdOpts.signOrder || '会签'] || signOrderMap['会签'];
|
|
955
|
+
const batchCond = cmdOpts.batchCondition || '';
|
|
956
|
+
const conditionDesc = batchCond ? `矩阵条件:${matrixRuleCondition}\\n批次条件:${batchCond}` : `矩阵条件:${matrixRuleCondition}`;
|
|
957
|
+
const itemData = {
|
|
958
|
+
signOrder: so.code,
|
|
959
|
+
signOrderCN: so.cn,
|
|
960
|
+
isOut: false, isSaveHrmOperator: false,
|
|
961
|
+
sourceId: cmdOpts.matrixId,
|
|
962
|
+
sourceIdCN: '', sourceIdType: '2',
|
|
963
|
+
targetId: cmdOpts.valueConfigId,
|
|
964
|
+
targetIdCN: '',
|
|
965
|
+
id: '1',
|
|
966
|
+
groupType: `矩阵对应`,
|
|
967
|
+
itemName: '',
|
|
968
|
+
itemLevel: '',
|
|
969
|
+
matrixRules,
|
|
970
|
+
endApprovers: '', hrmOperators: '', levelObj: '',
|
|
971
|
+
workflowId, nodeId,
|
|
972
|
+
conditionId: '',
|
|
973
|
+
orders: cmdOpts.orders || '0.00',
|
|
974
|
+
groupTypeId: '401',
|
|
975
|
+
isOpen: true, bigTypeId: 4,
|
|
976
|
+
conditionSourceId: '-1',
|
|
977
|
+
condition: conditionDesc,
|
|
978
|
+
groupCondition: batchCond,
|
|
979
|
+
matrixRuleCondition,
|
|
980
|
+
isSuportVirtualId: true, openProjectOrganization: false, isExtendType: 0,
|
|
981
|
+
};
|
|
982
|
+
const payload = {
|
|
983
|
+
workflowId, nodeId, groupId: '', isCreate: '', reserveGroupId,
|
|
984
|
+
itemDatas: [itemData],
|
|
985
|
+
oldGroupName: '', refWorkflowId: '', oldItemDatas: [],
|
|
986
|
+
groupName: '', deleteDatas: [], mustreview: 0,
|
|
987
|
+
};
|
|
988
|
+
console.error(` 矩阵对应: matrixId=${cmdOpts.matrixId} valueConfig=${cmdOpts.valueConfigId} 规则: ${matrixRuleCondition || '(无)'}`);
|
|
989
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/saveOperatorGroup`, payload);
|
|
990
|
+
if (data?.code === 200 && data?.status) {
|
|
991
|
+
const msg = opts.json
|
|
992
|
+
? JSON.stringify({ success: true, workflowId, nodeId, type: 'matrix', matrixId: cmdOpts.matrixId, append: !!cmdOpts.append })
|
|
993
|
+
: `${verb}(矩阵): 矩阵 ${cmdOpts.matrixId} → 节点 ${nodeId}`;
|
|
994
|
+
console.log(msg);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
console.error(`设置失败: ${data?.msg || '未知错误'}`);
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (isRole) {
|
|
1003
|
+
// Search roles — keyword 支持逗号分隔多角色(组内多操作者,如竞签多候选)
|
|
1004
|
+
const { findRolesByKeywords } = await import('../services/workflow-operator.js');
|
|
1005
|
+
const treeData = await client.get(`${baseUrl}/api/bs/auth/role/tree?menuCode=roleSettings`);
|
|
1006
|
+
if (treeData?.code !== 200) {
|
|
1007
|
+
console.error('查询角色列表失败');
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
const roles = treeData?.data?.data || [];
|
|
1011
|
+
if (!keyword.split(',').map(s => s.trim()).filter(Boolean).length) {
|
|
1012
|
+
console.error('--type role 需要角色关键词');
|
|
1013
|
+
process.exit(1);
|
|
1014
|
+
}
|
|
1015
|
+
const { found: foundList, missing } = findRolesByKeywords(roles, keyword);
|
|
1016
|
+
if (missing) {
|
|
1017
|
+
console.error(`未找到角色: ${missing}`);
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1020
|
+
targetId = foundList.map(f => f.id).join(',');
|
|
1021
|
+
targetName = foundList.map(f => f.content).join(',');
|
|
1022
|
+
browserDatas = foundList.map(f => ({
|
|
1023
|
+
id: f.id,
|
|
1024
|
+
name: f.content,
|
|
1025
|
+
content: f.content,
|
|
1026
|
+
contentDesc: '',
|
|
1027
|
+
isLeaf: true,
|
|
1028
|
+
icon: f.icon || 'Icon-role',
|
|
1029
|
+
stopDepartment: false,
|
|
1030
|
+
leaf: true,
|
|
1031
|
+
}));
|
|
1032
|
+
groupType = '指定角色';
|
|
1033
|
+
groupTypeId = '104';
|
|
1034
|
+
itemName = `${targetName}(所有级别)`;
|
|
1035
|
+
itemLevel = '所有级别';
|
|
1036
|
+
includeType = '51';
|
|
1037
|
+
includeTypeCN = '所有级别';
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
// Search users
|
|
1041
|
+
const userData = await client.post(`${baseUrl}/api/hrm/common/getEmployeeByKeyWords`, {
|
|
1042
|
+
keyword,
|
|
1043
|
+
searchType: 1,
|
|
1044
|
+
searchCount: 1,
|
|
1045
|
+
appDetachModule: 'EM',
|
|
1046
|
+
disableAppDetach: 0,
|
|
1047
|
+
pageNo: 1,
|
|
1048
|
+
pageSize: 1,
|
|
1049
|
+
});
|
|
1050
|
+
if (userData?.code !== 200 || !userData?.data?.userList?.length) {
|
|
1051
|
+
console.error(`未找到人员: ${keyword}`);
|
|
1052
|
+
process.exit(1);
|
|
1053
|
+
}
|
|
1054
|
+
const emp = userData.data.userList[0];
|
|
1055
|
+
targetId = emp.id || '';
|
|
1056
|
+
targetName = emp.name || '';
|
|
1057
|
+
browserData = {
|
|
1058
|
+
id: emp.id || '',
|
|
1059
|
+
cid: emp.cid || emp.companyId || '',
|
|
1060
|
+
userid: emp.userid || emp.userId || emp.id || '',
|
|
1061
|
+
name: emp.name || '',
|
|
1062
|
+
statusCode: emp.statusCode || 'normal',
|
|
1063
|
+
content: emp.name || '',
|
|
1064
|
+
title: `${emp.name || ''}|${emp.department || ''}`,
|
|
1065
|
+
contentDesc: emp.name || '',
|
|
1066
|
+
department: emp.department || '',
|
|
1067
|
+
avatar: emp.avatar || { previewPath: '', appPreviewPath: '', name: emp.name || '', userId: emp.id || '' },
|
|
1068
|
+
indexType: 'R',
|
|
1069
|
+
isLeaf: true,
|
|
1070
|
+
templateType: 'default',
|
|
1071
|
+
originTargetType: 'user',
|
|
1072
|
+
pid: '',
|
|
1073
|
+
richContent: emp.name || '',
|
|
1074
|
+
departmentId: emp.departmentId || emp.departmentid || '',
|
|
1075
|
+
mainId: emp.mainId || emp.id || '',
|
|
1076
|
+
stopDepartment: false,
|
|
1077
|
+
leaf: true,
|
|
1078
|
+
};
|
|
1079
|
+
groupType = '指定人';
|
|
1080
|
+
groupTypeId = '101';
|
|
1081
|
+
itemName = emp.name || '';
|
|
1082
|
+
browserDatas = [browserData];
|
|
1083
|
+
}
|
|
1084
|
+
console.error(` 操作者: ${targetName} (${targetId}) 类型: ${groupType}`);
|
|
1085
|
+
// 2. Get reserveGroupId from layout config
|
|
1086
|
+
const rg = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/getLayoutConfigInfo`, {
|
|
1087
|
+
groupId: '', workflowId, nodeId, sourceType: '1', isOperatorRuleAuth: true,
|
|
1088
|
+
});
|
|
1089
|
+
const reserveGroupId = rg?.data?.reserveGroupId || '';
|
|
1090
|
+
// 3. Build payload
|
|
1091
|
+
const soMap = {
|
|
1092
|
+
'会签': { code: '1', cn: '会签' },
|
|
1093
|
+
'非会签': { code: '0', cn: '非会签' },
|
|
1094
|
+
'竞签': { code: '0', cn: '非会签', competeSignOrder: '1', competeSignOrderCN: '竞签' },
|
|
1095
|
+
'依次处理': { code: '2', cn: '依次处理', executeOrders: '1-1,2-2', executeOrdersCN: '安全级别-正序,人员显示顺序-倒序' },
|
|
1096
|
+
'抄送': { code: '3', cn: '抄送' },
|
|
1097
|
+
'抄送需批注': { code: '4', cn: '抄送' },
|
|
1098
|
+
};
|
|
1099
|
+
const so = soMap[cmdOpts.signOrder || '会签'] || soMap['会签'];
|
|
1100
|
+
const itemData = {
|
|
1101
|
+
hrmOperators: targetId,
|
|
1102
|
+
hrmOperatorsCN: targetName,
|
|
1103
|
+
signOrder: so.code,
|
|
1104
|
+
signOrderCN: so.cn,
|
|
1105
|
+
detailSignOrder: '0',
|
|
1106
|
+
detailSignOrderCN: '',
|
|
1107
|
+
organizeSignOrder: '0',
|
|
1108
|
+
organizeSignOrderCN: '',
|
|
1109
|
+
competeOtherView: '0',
|
|
1110
|
+
competeOtherViewCN: '',
|
|
1111
|
+
...(so.competeSignOrder ? { competeSignOrder: so.competeSignOrder, competeSignOrderCN: so.competeSignOrderCN } : {}),
|
|
1112
|
+
...(so.executeOrders ? { executeOrders: so.executeOrders, executeOrdersCN: so.executeOrdersCN } : {}),
|
|
1113
|
+
isOut: false,
|
|
1114
|
+
isSaveHrmOperator: false,
|
|
1115
|
+
id: '2',
|
|
1116
|
+
groupType,
|
|
1117
|
+
itemName,
|
|
1118
|
+
itemLevel,
|
|
1119
|
+
endApprovers: '',
|
|
1120
|
+
levelObj: '',
|
|
1121
|
+
workflowId, nodeId,
|
|
1122
|
+
conditionId: '',
|
|
1123
|
+
orders: '0.00',
|
|
1124
|
+
groupTypeId,
|
|
1125
|
+
isOpen: true,
|
|
1126
|
+
bigTypeId: 1,
|
|
1127
|
+
condition: '',
|
|
1128
|
+
groupCondition: '',
|
|
1129
|
+
isSuportVirtualId: false,
|
|
1130
|
+
openProjectOrganization: false,
|
|
1131
|
+
isExtendType: 0,
|
|
1132
|
+
hrmBrowserDataDtos: browserDatas,
|
|
1133
|
+
};
|
|
1134
|
+
// Role-specific fields
|
|
1135
|
+
if (isRole) {
|
|
1136
|
+
itemData.includeType = includeType;
|
|
1137
|
+
itemData.includeTypeCN = includeTypeCN;
|
|
1138
|
+
}
|
|
1139
|
+
const payload = {
|
|
1140
|
+
workflowId, nodeId, groupId: '', isCreate: '', reserveGroupId,
|
|
1141
|
+
itemDatas: [itemData],
|
|
1142
|
+
oldGroupName: '',
|
|
1143
|
+
refWorkflowId: '',
|
|
1144
|
+
oldItemDatas: [],
|
|
1145
|
+
groupName: '',
|
|
1146
|
+
deleteDatas: [],
|
|
1147
|
+
mustreview: 0,
|
|
1148
|
+
};
|
|
1149
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeOperator/saveOperatorGroup`, payload);
|
|
1150
|
+
if (data?.code === 200 && data?.status) {
|
|
1151
|
+
const msg = opts.json
|
|
1152
|
+
? JSON.stringify({ success: true, workflowId, nodeId, operator: targetName, type: groupType, append: !!cmdOpts.append })
|
|
1153
|
+
: `${verb}: ${targetName} [${groupType}] → 节点 ${nodeId}`;
|
|
1154
|
+
console.log(msg);
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
console.error(`设置失败: ${data?.msg || '未知错误'}`);
|
|
1158
|
+
process.exit(1);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
wf.command('link-list')
|
|
1162
|
+
.description('查看流程出口列表(含默认/退回/条件状态)')
|
|
1163
|
+
.argument('<workflowId>', '流程 ID')
|
|
1164
|
+
.option('--node-id <nodeId>', '限定单个源节点(不传则展示全部出口)')
|
|
1165
|
+
.option('--out <file>', '将 JSON 输出写入文件(UTF-8)')
|
|
1166
|
+
.action(async (workflowId, cmdOpts) => {
|
|
1167
|
+
const opts = program.opts();
|
|
1168
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1169
|
+
if (!auth)
|
|
1170
|
+
process.exit(1);
|
|
1171
|
+
const client = new ApiClient(auth);
|
|
1172
|
+
const baseUrl = auth.getBaseUrl();
|
|
1173
|
+
const params = new URLSearchParams();
|
|
1174
|
+
params.set('workflowId', workflowId);
|
|
1175
|
+
if (cmdOpts.nodeId)
|
|
1176
|
+
params.set('nodeId', cmdOpts.nodeId);
|
|
1177
|
+
const data = await client.get(`${baseUrl}/api/bs/workflow/pathdef/nodeLink/getWfpNodeLinkTable?${params.toString()}`);
|
|
1178
|
+
if (data?.code !== 200 || !data?.status) {
|
|
1179
|
+
console.error(`查询失败: ${data?.msg || '未知错误'}`);
|
|
1180
|
+
process.exit(1);
|
|
1181
|
+
}
|
|
1182
|
+
const links = data?.data?.data || [];
|
|
1183
|
+
const nodeTypeMap = data?.data?.otherParams?.nodeTypeMap || {};
|
|
1184
|
+
const nodeAttrMap = data?.data?.otherParams?.nodeAttrMap || {};
|
|
1185
|
+
if (cmdOpts?.out) {
|
|
1186
|
+
const { writeFileSync } = await import('fs');
|
|
1187
|
+
writeFileSync(cmdOpts.out, JSON.stringify({ links, nodeTypeMap, nodeAttrMap }, null, 2), 'utf-8');
|
|
1188
|
+
console.error(`已写入: ${cmdOpts.out}`);
|
|
1189
|
+
}
|
|
1190
|
+
else if (opts.json) {
|
|
1191
|
+
console.log(JSON.stringify({ links }, null, 2));
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
console.log(`共 ${links.length} 条出口:\n`);
|
|
1195
|
+
for (const l of links) {
|
|
1196
|
+
const flags = [];
|
|
1197
|
+
if (l.isDefault === '1')
|
|
1198
|
+
flags.push('默认');
|
|
1199
|
+
if (l.isReject === '1')
|
|
1200
|
+
flags.push('退回');
|
|
1201
|
+
if (l.hasCondition)
|
|
1202
|
+
flags.push('有条件');
|
|
1203
|
+
const flagStr = flags.length > 0 ? ` [${flags.join(',')}]` : '';
|
|
1204
|
+
console.log(` ${l.id} ${l.srcNodeId} → ${l.destNodeId} ${l.linkName || '(未命名)'}${flagStr}`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
wf.command('generate-link-id')
|
|
1209
|
+
.description('生成一个新的连线 ID')
|
|
1210
|
+
.argument('<workflowId>', '流程 ID')
|
|
1211
|
+
.action(async (workflowId) => {
|
|
1212
|
+
const opts = program.opts();
|
|
1213
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1214
|
+
if (!auth)
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
const client = new ApiClient(auth);
|
|
1217
|
+
const baseUrl = auth.getBaseUrl();
|
|
1218
|
+
const data = await client.get(`${baseUrl}/api/bs/workflow/pathdef/nodeLink/generateId?v=${Date.now()}`);
|
|
1219
|
+
if (data?.code === 200 && data?.status) {
|
|
1220
|
+
const linkId = data?.data || '';
|
|
1221
|
+
if (opts.json) {
|
|
1222
|
+
console.log(JSON.stringify({ linkId }));
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
console.log(linkId);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
console.error(`生成失败: ${data?.msg || '未知错误'}`);
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
wf.command('add-link')
|
|
1234
|
+
.description('给流程添加新连线(列表 API:生成ID + 获取出口列表 + 添加 + 保存)')
|
|
1235
|
+
.argument('<workflowId>', '流程 ID')
|
|
1236
|
+
.argument('<fromNodeId>', '来源节点 ID')
|
|
1237
|
+
.argument('<toNodeId>', '目标节点 ID')
|
|
1238
|
+
.argument('<label>', '连线标签')
|
|
1239
|
+
.option('--reject', '标记为退回线')
|
|
1240
|
+
.option('--default', '标记为默认出口(同源节点只一条)')
|
|
1241
|
+
.action(async (workflowId, fromNodeId, toNodeId, label, cmdOpts) => {
|
|
1242
|
+
const opts = program.opts();
|
|
1243
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1244
|
+
if (!auth)
|
|
1245
|
+
process.exit(1);
|
|
1246
|
+
const client = new ApiClient(auth);
|
|
1247
|
+
const baseUrl = auth.getBaseUrl();
|
|
1248
|
+
const BASE = `${baseUrl}/api/bs/workflow/pathdef`;
|
|
1249
|
+
// 1. Generate link ID
|
|
1250
|
+
const genData = await client.get(`${BASE}/nodeLink/generateId?_=${Date.now()}`);
|
|
1251
|
+
const linkId = genData?.data || '';
|
|
1252
|
+
if (!linkId) {
|
|
1253
|
+
console.error('生成连线 ID 失败');
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
// 2. Read current link table
|
|
1257
|
+
const linkRes = await client.get(`${BASE}/nodeLink/getWfpNodeLinkTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1258
|
+
if (linkRes?.code !== 200 || !linkRes?.status) {
|
|
1259
|
+
console.error(`读取出口列表失败: ${linkRes?.msg || '未知错误'}`);
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
const links = linkRes?.data?.data || [];
|
|
1263
|
+
// If --default, clear other defaults from the same source node
|
|
1264
|
+
if (cmdOpts.default) {
|
|
1265
|
+
for (const l of links) {
|
|
1266
|
+
if (String(l.srcNodeId) === String(fromNodeId) && l.isDefault === '1') {
|
|
1267
|
+
l.isDefault = '0';
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
// 3. Build new link row and append
|
|
1272
|
+
const newLink = {
|
|
1273
|
+
flag: 'add',
|
|
1274
|
+
id: linkId,
|
|
1275
|
+
conditionKey: { conditionKey: '', source: '2' },
|
|
1276
|
+
isReject: cmdOpts.reject ? '1' : '0',
|
|
1277
|
+
isDefault: cmdOpts.default ? '1' : '0',
|
|
1278
|
+
srcNodeId: fromNodeId,
|
|
1279
|
+
destNodeId: toNodeId,
|
|
1280
|
+
publicSegmentId: '',
|
|
1281
|
+
linkName: label,
|
|
1282
|
+
orderId: String(links.length),
|
|
1283
|
+
};
|
|
1284
|
+
links.push(newLink);
|
|
1285
|
+
// 4. Save
|
|
1286
|
+
const saveRes = await client.post(`${BASE}/nodeLink/saveNodeLinkEditTable`, {
|
|
1287
|
+
workflowId,
|
|
1288
|
+
datas: JSON.stringify(links),
|
|
1289
|
+
deleteRows: '',
|
|
1290
|
+
changedLinkIds: '',
|
|
1291
|
+
updateRowIds: [],
|
|
1292
|
+
});
|
|
1293
|
+
if (saveRes?.code !== 200 || !saveRes?.status || saveRes?.fail !== false || saveRes?.data?.status !== true) {
|
|
1294
|
+
console.error(`添加连线失败: ${saveRes?.data?.msg || saveRes?.msg || '未知错误'}`);
|
|
1295
|
+
process.exit(1);
|
|
1296
|
+
}
|
|
1297
|
+
const flags = [];
|
|
1298
|
+
if (cmdOpts.reject)
|
|
1299
|
+
flags.push('退回');
|
|
1300
|
+
if (cmdOpts.default)
|
|
1301
|
+
flags.push('默认出口');
|
|
1302
|
+
const flagStr = flags.length ? ` (${flags.join(', ')})` : '';
|
|
1303
|
+
if (opts.json) {
|
|
1304
|
+
console.log(JSON.stringify({ success: true, workflowId, linkId, fromNodeId, toNodeId, label, reject: !!cmdOpts.reject, default: !!cmdOpts.default }));
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
console.log(`连线已添加: ${label} id=${linkId} ${fromNodeId} → ${toNodeId}${flagStr}`);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
wf.command('add-links')
|
|
1311
|
+
.description('批量添加连线(列表 API,一次保存完成所有连线)')
|
|
1312
|
+
.argument('<workflowId>', '流程 ID')
|
|
1313
|
+
.argument('<links>', '连线 JSON 数组或 @文件路径。格式:[{"from":"nodeId","to":"nodeId","label":"标签"}]')
|
|
1314
|
+
.option('--reject', '全部标记为退回线')
|
|
1315
|
+
.option('--default', '全部标记为默认出口')
|
|
1316
|
+
.action(async (workflowId, linksArg, cmdOpts) => {
|
|
1317
|
+
const opts = program.opts();
|
|
1318
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1319
|
+
if (!auth)
|
|
1320
|
+
process.exit(1);
|
|
1321
|
+
const client = new ApiClient(auth);
|
|
1322
|
+
const baseUrl = auth.getBaseUrl();
|
|
1323
|
+
const BASE = `${baseUrl}/api/bs/workflow/pathdef`;
|
|
1324
|
+
let linksStr = linksArg;
|
|
1325
|
+
if (linksArg.startsWith('@')) {
|
|
1326
|
+
const { readFileSync: rfl, existsSync: exl } = await import('fs');
|
|
1327
|
+
const fp = linksArg.slice(1);
|
|
1328
|
+
if (!exl(fp)) {
|
|
1329
|
+
console.error(`文件不存在: ${fp}`);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
linksStr = rfl(fp, 'utf-8');
|
|
1333
|
+
}
|
|
1334
|
+
let linkDefs;
|
|
1335
|
+
try {
|
|
1336
|
+
linkDefs = JSON.parse(linksStr);
|
|
1337
|
+
}
|
|
1338
|
+
catch {
|
|
1339
|
+
console.error('JSON 解析失败');
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1342
|
+
if (!Array.isArray(linkDefs) || linkDefs.length === 0) {
|
|
1343
|
+
console.error('links 必须是非空数组');
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
// 1. Generate IDs for all new links
|
|
1347
|
+
const newLinkIds = [];
|
|
1348
|
+
for (let i = 0; i < linkDefs.length; i++) {
|
|
1349
|
+
const genData = await client.get(`${BASE}/nodeLink/generateId?_=${Date.now()}`);
|
|
1350
|
+
const lid = genData?.data || '';
|
|
1351
|
+
if (!lid) {
|
|
1352
|
+
console.error(`生成第 ${i + 1} 条连线 ID 失败`);
|
|
1353
|
+
process.exit(1);
|
|
1354
|
+
}
|
|
1355
|
+
newLinkIds.push(lid);
|
|
1356
|
+
}
|
|
1357
|
+
// 2. Read current link table
|
|
1358
|
+
const linkRes = await client.get(`${BASE}/nodeLink/getWfpNodeLinkTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1359
|
+
if (linkRes?.code !== 200 || !linkRes?.status) {
|
|
1360
|
+
console.error(`读取出口列表失败: ${linkRes?.msg || '未知错误'}`);
|
|
1361
|
+
process.exit(1);
|
|
1362
|
+
}
|
|
1363
|
+
const links = linkRes?.data?.data || [];
|
|
1364
|
+
// 3. Append all new links
|
|
1365
|
+
for (let i = 0; i < linkDefs.length; i++) {
|
|
1366
|
+
const l = linkDefs[i];
|
|
1367
|
+
const reject = l.reject ?? cmdOpts.reject ?? false;
|
|
1368
|
+
const isDefault = l.default ?? cmdOpts.default ?? false;
|
|
1369
|
+
// Clear other defaults from same source if this one is default
|
|
1370
|
+
if (isDefault) {
|
|
1371
|
+
for (const existing of links) {
|
|
1372
|
+
if (String(existing.srcNodeId) === String(l.from) && existing.isDefault === '1') {
|
|
1373
|
+
existing.isDefault = '0';
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
links.push({
|
|
1378
|
+
flag: 'add',
|
|
1379
|
+
id: newLinkIds[i],
|
|
1380
|
+
conditionKey: { conditionKey: '', source: '2' },
|
|
1381
|
+
isReject: reject ? '1' : '0',
|
|
1382
|
+
isDefault: isDefault ? '1' : '0',
|
|
1383
|
+
srcNodeId: l.from,
|
|
1384
|
+
destNodeId: l.to,
|
|
1385
|
+
publicSegmentId: '',
|
|
1386
|
+
linkName: l.label,
|
|
1387
|
+
orderId: String(links.length),
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
// 4. Save all at once
|
|
1391
|
+
const saveRes = await client.post(`${BASE}/nodeLink/saveNodeLinkEditTable`, {
|
|
1392
|
+
workflowId,
|
|
1393
|
+
datas: JSON.stringify(links),
|
|
1394
|
+
deleteRows: '',
|
|
1395
|
+
changedLinkIds: '',
|
|
1396
|
+
updateRowIds: [],
|
|
1397
|
+
});
|
|
1398
|
+
if (saveRes?.code !== 200 || !saveRes?.status || saveRes?.fail !== false || saveRes?.data?.status !== true) {
|
|
1399
|
+
console.error(`批量添加连线失败: ${saveRes?.data?.msg || saveRes?.msg || '未知错误'}`);
|
|
1400
|
+
process.exit(1);
|
|
1401
|
+
}
|
|
1402
|
+
if (opts.json) {
|
|
1403
|
+
console.log(JSON.stringify({ success: true, workflowId, added: linkDefs.length, linkIds: newLinkIds }));
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
console.log(`已添加 ${linkDefs.length} 条连线`);
|
|
1407
|
+
for (let i = 0; i < linkDefs.length; i++) {
|
|
1408
|
+
console.log(` ${linkDefs[i].label} id=${newLinkIds[i]} ${linkDefs[i].from} → ${linkDefs[i].to}`);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
const NODE_TYPES = {
|
|
1413
|
+
'0': '发起',
|
|
1414
|
+
'1': '审批',
|
|
1415
|
+
'2': '确认/会签',
|
|
1416
|
+
'3': '归档',
|
|
1417
|
+
'5': '投递',
|
|
1418
|
+
'7': '自动处理',
|
|
1419
|
+
'8': '等待',
|
|
1420
|
+
'forkstart': '分流点',
|
|
1421
|
+
'forkend': '合流点',
|
|
1422
|
+
'forkmiddle': '分流中间点',
|
|
1423
|
+
};
|
|
1424
|
+
wf.command('add-node')
|
|
1425
|
+
.description('添加流程节点(列表 API,自动同步到流程图)')
|
|
1426
|
+
.argument('<workflowId>', '流程 ID')
|
|
1427
|
+
.argument('<type>', `节点类型: ${Object.entries(NODE_TYPES).map(([k, v]) => `${k}=${v}`).join(', ')}`)
|
|
1428
|
+
.argument('<name>', '节点名称')
|
|
1429
|
+
.option('--node-attr <attr>', '节点属性: 0=一般(默认), 1=分流点, 2=分流中间点, 3=合流点', '0')
|
|
1430
|
+
.option('--merge-type <type>', '合流方式 (合流点必填): 0=全部, 1=指定, 2=数量, 3=比例')
|
|
1431
|
+
.option('--merge-num <num>', '合流分支数或百分比 (mergeType=2/3 时必填)')
|
|
1432
|
+
.option('--branch-nodes <ids>', '指定合流分支节点 ID,逗号分隔 (mergeType=1 时必填)')
|
|
1433
|
+
.action(async (workflowId, type, name, cmdOpts) => {
|
|
1434
|
+
if (!NODE_TYPES[type]) {
|
|
1435
|
+
console.error(`不支持的节点类型: ${type},可选: ${Object.keys(NODE_TYPES).join(', ')}`);
|
|
1436
|
+
process.exit(1);
|
|
1437
|
+
}
|
|
1438
|
+
// Resolve actual nodeType and nodeAttr
|
|
1439
|
+
let actualType = type;
|
|
1440
|
+
let nodeAttr = cmdOpts.nodeAttr || '0';
|
|
1441
|
+
if (type === 'forkmiddle') {
|
|
1442
|
+
actualType = '1';
|
|
1443
|
+
nodeAttr = '2';
|
|
1444
|
+
}
|
|
1445
|
+
else if (type === 'forkstart') {
|
|
1446
|
+
nodeAttr = '1';
|
|
1447
|
+
}
|
|
1448
|
+
else if (type === 'forkend') {
|
|
1449
|
+
nodeAttr = '3';
|
|
1450
|
+
}
|
|
1451
|
+
const opts = program.opts();
|
|
1452
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1453
|
+
if (!auth)
|
|
1454
|
+
process.exit(1);
|
|
1455
|
+
const client = new ApiClient(auth);
|
|
1456
|
+
const baseUrl = auth.getBaseUrl();
|
|
1457
|
+
const BASE = `${baseUrl}/api/bs/workflow/pathdef`;
|
|
1458
|
+
// 1. Generate new node ID via multilang
|
|
1459
|
+
const mlRes = await client.post(`${BASE}/multilang/getTargetId`, { targetId: '-1' });
|
|
1460
|
+
const nodeId = mlRes?.data || '';
|
|
1461
|
+
if (!nodeId) {
|
|
1462
|
+
console.error('生成节点 ID 失败');
|
|
1463
|
+
process.exit(1);
|
|
1464
|
+
}
|
|
1465
|
+
// 2. Read current node edit table
|
|
1466
|
+
const nodeRes = await client.get(`${BASE}/nodeBase/getNodeEditTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1467
|
+
if (nodeRes?.code !== 200 || !nodeRes?.status) {
|
|
1468
|
+
console.error(`读取节点列表失败: ${nodeRes?.msg || '未知错误'}`);
|
|
1469
|
+
process.exit(1);
|
|
1470
|
+
}
|
|
1471
|
+
const nodeRows = nodeRes?.data?.data || [];
|
|
1472
|
+
// 3. Build new node row
|
|
1473
|
+
const tempRowId = `rowId_${Date.now()}`;
|
|
1474
|
+
const newRow = {
|
|
1475
|
+
id: tempRowId,
|
|
1476
|
+
targetId: nodeId,
|
|
1477
|
+
needReplaceTargetId: true,
|
|
1478
|
+
nodeName: name,
|
|
1479
|
+
nodeType: actualType,
|
|
1480
|
+
nodeAttr,
|
|
1481
|
+
orderId: String(nodeRows.length),
|
|
1482
|
+
};
|
|
1483
|
+
// Forkend merge settings
|
|
1484
|
+
if (actualType === 'forkend') {
|
|
1485
|
+
const mergeType = cmdOpts.mergeType || '0';
|
|
1486
|
+
newRow.mergeType = mergeType;
|
|
1487
|
+
if (mergeType === '1') {
|
|
1488
|
+
newRow.branchNodes = cmdOpts.branchNodes || '';
|
|
1489
|
+
newRow.flowToForkend = true;
|
|
1490
|
+
}
|
|
1491
|
+
else if (mergeType === '2' || mergeType === '3') {
|
|
1492
|
+
newRow.mergeNum = cmdOpts.mergeNum || (mergeType === '2' ? '1' : '50');
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
nodeRows.forEach((n, i) => { n.orderId = String(i); });
|
|
1496
|
+
nodeRows.push(newRow);
|
|
1497
|
+
// 4. Save
|
|
1498
|
+
const saveRes = await client.post(`${BASE}/nodeBase/saveNodeEditTable`, {
|
|
1499
|
+
workflowId,
|
|
1500
|
+
datas: JSON.stringify(nodeRows),
|
|
1501
|
+
deleteRows: '',
|
|
1502
|
+
changeRows: '',
|
|
1503
|
+
changeControlledRows: '',
|
|
1504
|
+
});
|
|
1505
|
+
if (saveRes?.code !== 200 || !saveRes?.status || saveRes?.fail !== false || saveRes?.data?.status !== true) {
|
|
1506
|
+
console.error(`添加节点失败: ${saveRes?.data?.msg || saveRes?.msg || '未知错误'}`);
|
|
1507
|
+
process.exit(1);
|
|
1508
|
+
}
|
|
1509
|
+
const mergeInfo = actualType === 'forkend'
|
|
1510
|
+
? ` mergeType=${newRow.mergeType}${newRow.mergeNum ? ' mergeNum=' + newRow.mergeNum : ''}`
|
|
1511
|
+
: '';
|
|
1512
|
+
if (opts.json) {
|
|
1513
|
+
console.log(JSON.stringify({ success: true, workflowId, nodeId, type, name, nodeAttr, ...(actualType === 'forkend' ? { mergeType: newRow.mergeType, mergeNum: newRow.mergeNum } : {}) }));
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
console.log(`节点已添加: ${name} (${NODE_TYPES[type]}) id=${nodeId}${mergeInfo}`);
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
wf.command('rename-node')
|
|
1520
|
+
.description('重命名节点(通过 saveNodeSetForm API)')
|
|
1521
|
+
.argument('<workflowId>', '流程 ID')
|
|
1522
|
+
.argument('<nodeId>', '节点 ID')
|
|
1523
|
+
.argument('<name>', '新名称')
|
|
1524
|
+
.action(async (workflowId, nodeId, name) => {
|
|
1525
|
+
const opts = program.opts();
|
|
1526
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1527
|
+
if (!auth)
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
const client = new ApiClient(auth);
|
|
1530
|
+
const baseUrl = auth.getBaseUrl();
|
|
1531
|
+
// 1. Get current node settings
|
|
1532
|
+
const getRes = await client.get(`${baseUrl}/api/bs/workflow/pathdef/nodeBase/getNodeSetForm?workflowId=${workflowId}&nodeId=${nodeId}`);
|
|
1533
|
+
if (getRes?.code !== 200 || !getRes?.data?.data) {
|
|
1534
|
+
console.error(`获取节点设置失败: ${getRes?.msg || '节点不存在'}`);
|
|
1535
|
+
process.exit(1);
|
|
1536
|
+
}
|
|
1537
|
+
const nodeData = getRes.data.data;
|
|
1538
|
+
// 节点不存在时 getNodeSetForm 只回一个空的 {nodeName:""},直接回传会让 saveNodeSetForm 报「系统错误」。
|
|
1539
|
+
// 用字段数判断(真实节点有 nodeType/operator/voteSet 等完整配置,空节点只有 1 个 key)。
|
|
1540
|
+
if (Object.keys(nodeData).length <= 1) {
|
|
1541
|
+
console.error(`节点不存在或 nodeId 有误: ${nodeId}`);
|
|
1542
|
+
console.error(' 可用 `e10-cli workflow nodes <workflowId>` 查看该流程下真实节点 ID');
|
|
1543
|
+
process.exit(1);
|
|
1544
|
+
}
|
|
1545
|
+
const oldName = nodeData.nodeName || '';
|
|
1546
|
+
// 2. Update name
|
|
1547
|
+
nodeData.nodeName = name;
|
|
1548
|
+
nodeData.nodeNameMultilang = name;
|
|
1549
|
+
// 3. Save
|
|
1550
|
+
const payload = { workflowId, nodeId };
|
|
1551
|
+
for (const [k, v] of Object.entries(nodeData)) {
|
|
1552
|
+
payload[k] = v;
|
|
1553
|
+
}
|
|
1554
|
+
const saveRes = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeBase/saveNodeSetForm`, payload);
|
|
1555
|
+
if (saveRes?.code === 200 && saveRes?.status) {
|
|
1556
|
+
console.log(opts.json
|
|
1557
|
+
? JSON.stringify({ success: true, workflowId, nodeId, oldName, name })
|
|
1558
|
+
: `节点已改名: ${oldName} → ${name}`);
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
console.error(`保存失败: ${saveRes?.msg || '未知错误'}`);
|
|
1562
|
+
process.exit(1);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
wf.command('save-layout')
|
|
1566
|
+
.description('保存流程图布局(浏览器 mxGraph 格式 XML,非 getXml 输出)')
|
|
1567
|
+
.argument('<workflowId>', '流程 ID')
|
|
1568
|
+
.argument('<xml>', '浏览器格式的 XML 或 @文件路径')
|
|
1569
|
+
.option('--delete-links <ids>', '要删除的连线 ID,逗号分隔')
|
|
1570
|
+
.option('--delete-nodes <ids>', '要删除的节点 ID,逗号分隔')
|
|
1571
|
+
.action(async (workflowId, xmlArg, cmdOpts) => {
|
|
1572
|
+
const opts = program.opts();
|
|
1573
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1574
|
+
if (!auth)
|
|
1575
|
+
process.exit(1);
|
|
1576
|
+
const client = new ApiClient(auth);
|
|
1577
|
+
const baseUrl = auth.getBaseUrl();
|
|
1578
|
+
let xml = xmlArg;
|
|
1579
|
+
if (xmlArg.startsWith('@')) {
|
|
1580
|
+
const { readFileSync, existsSync } = await import('fs');
|
|
1581
|
+
const filePath = xmlArg.slice(1);
|
|
1582
|
+
if (!existsSync(filePath)) {
|
|
1583
|
+
console.error(`文件不存在: ${filePath}`);
|
|
1584
|
+
process.exit(1);
|
|
1585
|
+
}
|
|
1586
|
+
xml = readFileSync(filePath, 'utf-8').trim();
|
|
1587
|
+
}
|
|
1588
|
+
// If XML starts with <?xml, strip declaration and set grid=0 for browser format
|
|
1589
|
+
xml = xml.replace(/^<\?xml[^>]*\?>\s*/, '');
|
|
1590
|
+
xml = xml.replace(/grid="1"/, 'grid="0"');
|
|
1591
|
+
// 服务器 saveLayout 只认浏览器 mxGraph 格式(纯数字 cell id + source/target 数字引用)。
|
|
1592
|
+
// 不要转成 node_X/link_X——服务器解析不了会静默失败。直接回传浏览器格式 XML,
|
|
1593
|
+
// 仅对服务器不存在的 nodeId/linkId(新增节点/连线)注入 flag="add"。
|
|
1594
|
+
try {
|
|
1595
|
+
const chartData = await client.postForm(`${baseUrl}/api/bs/workflow/pathdef/chart/getXml`, `workflowId=${workflowId}&keepTitle=undefined&showType=0`);
|
|
1596
|
+
const serverXml = chartData?.data?.xml || '';
|
|
1597
|
+
if (serverXml) {
|
|
1598
|
+
const knownNodes = new Set([...serverXml.matchAll(/nodeId="(\d+)"/g)].map(m => m[1]));
|
|
1599
|
+
const knownLinks = new Set([...serverXml.matchAll(/linkId="(\d+)"/g)].map(m => m[1]));
|
|
1600
|
+
// 新增节点 cell:nodeId 不在服务器已知集 → 注入 flag="add"
|
|
1601
|
+
for (const m of xml.matchAll(/<mxCell\b[^>]*nodeId="(\d+)"[^>]*>/g)) {
|
|
1602
|
+
if (!knownNodes.has(m[1]) && !/flag="add"/.test(m[0])) {
|
|
1603
|
+
xml = xml.replace(m[0], m[0].replace('nodeId=', 'flag="add" nodeId='));
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
// 新增连线 cell:linkId 不在服务器已知集 → 注入 flag="add"
|
|
1607
|
+
for (const m of xml.matchAll(/<mxCell\b[^>]*linkId="(\d+)"[^>]*>/g)) {
|
|
1608
|
+
if (!knownLinks.has(m[1]) && !/flag="add"/.test(m[0])) {
|
|
1609
|
+
xml = xml.replace(m[0], m[0].replace('linkId=', 'flag="add" linkId='));
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
catch { /* best effort */ }
|
|
1615
|
+
// Lock the chart for editing (browser does this before save)
|
|
1616
|
+
try {
|
|
1617
|
+
await client.postForm(`${baseUrl}/api/bs/workflow/pathdef/chart/checkoutHeart`, `workflowId=${workflowId}&chartSrc=0`);
|
|
1618
|
+
}
|
|
1619
|
+
catch { /* best effort */ }
|
|
1620
|
+
// Base64-encode: standard base64 then replace + with $ (avoids form-decoding + as space)
|
|
1621
|
+
const xmlB64 = Buffer.from(xml).toString('base64').replace(/\+/g, '$');
|
|
1622
|
+
// Build form body — server expects unencoded JSON braces
|
|
1623
|
+
const body = `workflowId=${workflowId}&deleteLinks=${cmdOpts.deleteLinks || ''}&deleteNodes=${cmdOpts.deleteNodes || ''}&groups={"col":[],"row":[]}&deleteGroups=&templateKey=0&chartSrc=0&showConditionName=false&showLinkName=false&isSaveBySkipLinkValidate=false&xml=${xmlB64}`;
|
|
1624
|
+
const data = await client.postForm(`${baseUrl}/api/bs/workflow/pathdef/chart/saveLayout`, body);
|
|
1625
|
+
if (data?.code === 200 && data?.status) {
|
|
1626
|
+
const msg = opts.json
|
|
1627
|
+
? JSON.stringify({ success: true, workflowId })
|
|
1628
|
+
: `流程图已保存: ${workflowId}`;
|
|
1629
|
+
console.log(msg);
|
|
1630
|
+
}
|
|
1631
|
+
else {
|
|
1632
|
+
console.error(`保存失败: ${data?.msg || '未知错误'}`);
|
|
1633
|
+
process.exit(1);
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1636
|
+
wf.command('auto-layout')
|
|
1637
|
+
.description('流程节点自动排版(分层布局,全量重排节点坐标)')
|
|
1638
|
+
.argument('<workflowId>', '流程 ID')
|
|
1639
|
+
.option('--direction <dir>', '布局方向: ltr=左右(默认), ttb=上下', 'ltr')
|
|
1640
|
+
.option('--gap-x <n>', '相邻层间距(ltr=列距, ttb=行距)', '90')
|
|
1641
|
+
.option('--gap-y <n>', '同层节点间距(ltr=行距, ttb=列距)', '40')
|
|
1642
|
+
.option('--dry-run', '只计算坐标,不回写')
|
|
1643
|
+
.option('--out <file>', '配合 --dry-run 输出新 XML 到文件')
|
|
1644
|
+
.action(async (workflowId, cmdOpts) => {
|
|
1645
|
+
const opts = program.opts();
|
|
1646
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1647
|
+
if (!auth)
|
|
1648
|
+
process.exit(1);
|
|
1649
|
+
const client = new ApiClient(auth);
|
|
1650
|
+
const baseUrl = auth.getBaseUrl();
|
|
1651
|
+
const { autoLayoutWorkflow } = await import('../services/workflow-layout.js');
|
|
1652
|
+
const res = await autoLayoutWorkflow({
|
|
1653
|
+
client: client,
|
|
1654
|
+
baseUrl,
|
|
1655
|
+
workflowId,
|
|
1656
|
+
options: {
|
|
1657
|
+
direction: (cmdOpts.direction === 'ttb' ? 'ttb' : 'ltr'),
|
|
1658
|
+
gapX: cmdOpts.gapX ? Number(cmdOpts.gapX) : undefined,
|
|
1659
|
+
gapY: cmdOpts.gapY ? Number(cmdOpts.gapY) : undefined,
|
|
1660
|
+
dryRun: cmdOpts.dryRun,
|
|
1661
|
+
},
|
|
1662
|
+
});
|
|
1663
|
+
if (res.error) {
|
|
1664
|
+
console.error(`排版失败: ${res.error}`);
|
|
1665
|
+
process.exit(1);
|
|
1666
|
+
}
|
|
1667
|
+
if (cmdOpts.dryRun) {
|
|
1668
|
+
console.error(`[dry-run] 方向=${res.direction} 节点=${res.nodeCount} 连线=${res.linkCount} 层=${res.layerCount}`);
|
|
1669
|
+
for (const n of res.nodes) {
|
|
1670
|
+
console.log(` 层${n.layer} (${n.x},${n.y}) ${n.name} ${n.nodeId}`);
|
|
1671
|
+
}
|
|
1672
|
+
if (cmdOpts.out) {
|
|
1673
|
+
const { writeFileSync } = await import('fs');
|
|
1674
|
+
writeFileSync(cmdOpts.out, res.xml ?? '', 'utf-8');
|
|
1675
|
+
console.error(`新 XML 已写入: ${cmdOpts.out}`);
|
|
1676
|
+
}
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
if (res.saved) {
|
|
1680
|
+
console.error(`布局已保存,回读验证: ${res.verified ? '坐标全部生效 ✓' : '未通过(请检查)'}`);
|
|
1681
|
+
}
|
|
1682
|
+
else {
|
|
1683
|
+
console.error(`保存失败: 服务器未确认(可用 --dry-run 排查)`);
|
|
1684
|
+
process.exit(1);
|
|
1685
|
+
}
|
|
1686
|
+
if (opts.json) {
|
|
1687
|
+
console.log(JSON.stringify({ ...res, xml: undefined }, null, 2));
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
console.log(`共 ${res.nodeCount} 个节点, ${res.linkCount} 条连线, ${res.layerCount} 层:`);
|
|
1691
|
+
for (const n of res.nodes) {
|
|
1692
|
+
console.log(` 层${n.layer} (${n.x},${n.y}) ${n.name} ${n.nodeId}`);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
wf.command('link-save')
|
|
1697
|
+
.description('修改/删除出口(列表方式,传完整 datas JSON 字符串 或 @文件)')
|
|
1698
|
+
.argument('<workflowId>', '流程 ID')
|
|
1699
|
+
.argument('<datas>', '完整出口数组的 JSON 字符串,或 @文件路径(JSON 文件)')
|
|
1700
|
+
.option('--delete-rows <ids>', '要删除的出口 ID,逗号分隔')
|
|
1701
|
+
.option('--changed-link-ids <ids>', '源/目标发生变化的出口 ID,逗号分隔')
|
|
1702
|
+
.option('--update-row-ids <json>', '明确要更新的出口 ID 数组,JSON 格式,如 \'["id1","id2"]\'')
|
|
1703
|
+
.action(async (workflowId, datasArg, cmdOpts) => {
|
|
1704
|
+
const opts = program.opts();
|
|
1705
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1706
|
+
if (!auth)
|
|
1707
|
+
process.exit(1);
|
|
1708
|
+
const client = new ApiClient(auth);
|
|
1709
|
+
const baseUrl = auth.getBaseUrl();
|
|
1710
|
+
let datasStr = datasArg;
|
|
1711
|
+
if (datasArg.startsWith('@')) {
|
|
1712
|
+
const { readFileSync, existsSync } = await import('fs');
|
|
1713
|
+
const filePath = datasArg.slice(1);
|
|
1714
|
+
if (!existsSync(filePath)) {
|
|
1715
|
+
console.error(`文件不存在: ${filePath}`);
|
|
1716
|
+
process.exit(1);
|
|
1717
|
+
}
|
|
1718
|
+
datasStr = readFileSync(filePath, 'utf-8');
|
|
1719
|
+
}
|
|
1720
|
+
// datas must be a JSON string (not array) per the API contract
|
|
1721
|
+
let datas;
|
|
1722
|
+
try {
|
|
1723
|
+
let parsed = JSON.parse(datasStr);
|
|
1724
|
+
// If parsed is a string, it's already a JSON string; use as-is
|
|
1725
|
+
if (typeof parsed === 'string') {
|
|
1726
|
+
datas = parsed;
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
// Strip read-only fields to avoid "系统错误" from unknown fields
|
|
1730
|
+
const ALLOWED_KEYS = new Set([
|
|
1731
|
+
'flag', 'id', 'srcNodeId', 'destNodeId', 'linkName',
|
|
1732
|
+
'isReject', 'isDefault', 'orderId', 'publicSegmentId',
|
|
1733
|
+
]);
|
|
1734
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
1735
|
+
const cleaned = rows.map((row) => {
|
|
1736
|
+
const out = {};
|
|
1737
|
+
for (const k of ALLOWED_KEYS) {
|
|
1738
|
+
if (row[k] !== undefined)
|
|
1739
|
+
out[k] = row[k];
|
|
1740
|
+
}
|
|
1741
|
+
// Always preserve conditionKey (nested object)
|
|
1742
|
+
out.conditionKey = {
|
|
1743
|
+
conditionKey: row?.conditionKey?.conditionKey || '',
|
|
1744
|
+
source: row?.conditionKey?.source || '2',
|
|
1745
|
+
};
|
|
1746
|
+
return out;
|
|
1747
|
+
});
|
|
1748
|
+
datas = JSON.stringify(cleaned);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
catch {
|
|
1752
|
+
datas = datasStr; // assume it's already a JSON string
|
|
1753
|
+
}
|
|
1754
|
+
let updateRowIds = [];
|
|
1755
|
+
if (cmdOpts.updateRowIds) {
|
|
1756
|
+
try {
|
|
1757
|
+
updateRowIds = JSON.parse(cmdOpts.updateRowIds);
|
|
1758
|
+
}
|
|
1759
|
+
catch {
|
|
1760
|
+
console.error('--update-row-ids 必须是 JSON 数组,如 \'["id1","id2"]\'');
|
|
1761
|
+
process.exit(1);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
const payload = {
|
|
1765
|
+
workflowId,
|
|
1766
|
+
datas,
|
|
1767
|
+
deleteRows: cmdOpts.deleteRows || '',
|
|
1768
|
+
changedLinkIds: cmdOpts.changedLinkIds || '',
|
|
1769
|
+
updateRowIds,
|
|
1770
|
+
};
|
|
1771
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeLink/saveNodeLinkEditTable`, payload);
|
|
1772
|
+
// 成功哨兵:code=200, status=true, data.status=true
|
|
1773
|
+
if (data?.code === 200 && data?.status && data?.data?.status !== false) {
|
|
1774
|
+
const deletedNodes = data?.data?.deletedNodes;
|
|
1775
|
+
const warn = deletedNodes?.length ? ` (警告: ${deletedNodes.length} 个节点已被删除)` : '';
|
|
1776
|
+
const msg = opts.json
|
|
1777
|
+
? JSON.stringify({ success: true, workflowId, ...(deletedNodes ? { deletedNodes } : {}) })
|
|
1778
|
+
: `出口已保存${warn}`;
|
|
1779
|
+
console.log(msg);
|
|
1780
|
+
}
|
|
1781
|
+
else {
|
|
1782
|
+
console.error(`保存失败: ${data?.data?.msg || data?.msg || '未知错误'}`);
|
|
1783
|
+
process.exit(1);
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
wf.command('link-rename')
|
|
1787
|
+
.description('重命名出口(连线名称):读出口列表 → 改 linkName → 保存')
|
|
1788
|
+
.argument('<workflowId>', '流程 ID')
|
|
1789
|
+
.argument('<linkId>', '连线 ID(用 link-list 查看)')
|
|
1790
|
+
.argument('<name>', '新的出口名称')
|
|
1791
|
+
.action(async (workflowId, linkId, name) => {
|
|
1792
|
+
const opts = program.opts();
|
|
1793
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1794
|
+
if (!auth)
|
|
1795
|
+
process.exit(1);
|
|
1796
|
+
const client = new ApiClient(auth);
|
|
1797
|
+
const baseUrl = auth.getBaseUrl();
|
|
1798
|
+
const BASE = `${baseUrl}/api/bs/workflow/pathdef`;
|
|
1799
|
+
// 1. 读出口列表
|
|
1800
|
+
const linkRes = await client.get(`${BASE}/nodeLink/getWfpNodeLinkTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1801
|
+
if (linkRes?.code !== 200 || !linkRes?.status) {
|
|
1802
|
+
console.error(`读取出口列表失败: ${linkRes?.msg || '未知错误'}`);
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
const links = linkRes?.data?.data || [];
|
|
1806
|
+
// 2. 找目标连线
|
|
1807
|
+
const target = links.find((l) => String(l.id) === String(linkId));
|
|
1808
|
+
if (!target) {
|
|
1809
|
+
console.error(`未找到连线: ${linkId}。可用: ${links.map((l) => `${l.id}(${l.linkName || '未命名'})`).join(', ')}`);
|
|
1810
|
+
process.exit(1);
|
|
1811
|
+
}
|
|
1812
|
+
const oldName = target.linkName || '';
|
|
1813
|
+
target.linkName = name;
|
|
1814
|
+
// 3. 清洗后保存(去掉只读字段,保留 conditionKey,避免"系统错误")
|
|
1815
|
+
const ALLOWED_KEYS = ['flag', 'id', 'srcNodeId', 'destNodeId', 'linkName',
|
|
1816
|
+
'isReject', 'isDefault', 'orderId', 'publicSegmentId'];
|
|
1817
|
+
const cleaned = links.map((row) => {
|
|
1818
|
+
const out = {};
|
|
1819
|
+
for (const k of ALLOWED_KEYS)
|
|
1820
|
+
if (row[k] !== undefined)
|
|
1821
|
+
out[k] = row[k];
|
|
1822
|
+
out.conditionKey = {
|
|
1823
|
+
conditionKey: row?.conditionKey?.conditionKey || '',
|
|
1824
|
+
source: row?.conditionKey?.source || '2',
|
|
1825
|
+
};
|
|
1826
|
+
return out;
|
|
1827
|
+
});
|
|
1828
|
+
const saveRes = await client.post(`${BASE}/nodeLink/saveNodeLinkEditTable`, {
|
|
1829
|
+
workflowId,
|
|
1830
|
+
datas: JSON.stringify(cleaned),
|
|
1831
|
+
deleteRows: '',
|
|
1832
|
+
changedLinkIds: '',
|
|
1833
|
+
updateRowIds: [String(linkId)],
|
|
1834
|
+
});
|
|
1835
|
+
if (saveRes?.code !== 200 || !saveRes?.status || saveRes?.fail !== false || saveRes?.data?.status !== true) {
|
|
1836
|
+
console.error(`重命名失败: ${saveRes?.data?.msg || saveRes?.msg || '未知错误'}`);
|
|
1837
|
+
process.exit(1);
|
|
1838
|
+
}
|
|
1839
|
+
const msg = opts.json
|
|
1840
|
+
? JSON.stringify({ success: true, workflowId, linkId, name })
|
|
1841
|
+
: `出口已重命名: ${oldName || '(未命名)'} → ${name}`;
|
|
1842
|
+
console.log(msg);
|
|
1843
|
+
});
|
|
1844
|
+
wf.command('delete-link')
|
|
1845
|
+
.description('删除连线(列表 API:读取出口列表 → 过滤 → 保存)')
|
|
1846
|
+
.argument('<workflowId>', '流程 ID')
|
|
1847
|
+
.argument('<linkIds...>', '要删除的连线 ID(可多个)')
|
|
1848
|
+
.action(async (workflowId, linkIds) => {
|
|
1849
|
+
const opts = program.opts();
|
|
1850
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1851
|
+
if (!auth)
|
|
1852
|
+
process.exit(1);
|
|
1853
|
+
const client = new ApiClient(auth);
|
|
1854
|
+
const baseUrl = auth.getBaseUrl();
|
|
1855
|
+
const BASE = `${baseUrl}/api/bs/workflow/pathdef`;
|
|
1856
|
+
// 1. Read current link table
|
|
1857
|
+
const linkRes = await client.get(`${BASE}/nodeLink/getWfpNodeLinkTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1858
|
+
if (linkRes?.code !== 200 || !linkRes?.status) {
|
|
1859
|
+
console.error(`读取出口列表失败: ${linkRes?.msg || '未知错误'}`);
|
|
1860
|
+
process.exit(1);
|
|
1861
|
+
}
|
|
1862
|
+
const links = linkRes?.data?.data || [];
|
|
1863
|
+
// 2. Filter out deleted links
|
|
1864
|
+
const linkIdSet = new Set(linkIds.map(String));
|
|
1865
|
+
const remaining = links.filter((l) => !linkIdSet.has(String(l.id)));
|
|
1866
|
+
const deletedCount = links.length - remaining.length;
|
|
1867
|
+
if (deletedCount === 0) {
|
|
1868
|
+
console.error(`未找到要删除的连线: ${linkIds.join(', ')}`);
|
|
1869
|
+
process.exit(1);
|
|
1870
|
+
}
|
|
1871
|
+
remaining.forEach((l, i) => { l.orderId = String(i); });
|
|
1872
|
+
// 3. Save with deleteRows
|
|
1873
|
+
const saveRes = await client.post(`${BASE}/nodeLink/saveNodeLinkEditTable`, {
|
|
1874
|
+
workflowId,
|
|
1875
|
+
datas: JSON.stringify(remaining),
|
|
1876
|
+
deleteRows: linkIds.join(','),
|
|
1877
|
+
changedLinkIds: '',
|
|
1878
|
+
updateRowIds: [],
|
|
1879
|
+
});
|
|
1880
|
+
if (saveRes?.code !== 200 || !saveRes?.status || saveRes?.fail !== false || saveRes?.data?.status !== true) {
|
|
1881
|
+
console.error(`删除连线失败: ${saveRes?.data?.msg || saveRes?.msg || '未知错误'}`);
|
|
1882
|
+
process.exit(1);
|
|
1883
|
+
}
|
|
1884
|
+
if (saveRes?.data?.deletedNodes?.length) {
|
|
1885
|
+
console.error(`警告: ${saveRes.data.deletedNodes.length} 个关联节点已被自动删除`);
|
|
1886
|
+
}
|
|
1887
|
+
if (opts.json) {
|
|
1888
|
+
console.log(JSON.stringify({ success: true, workflowId, deleted: linkIds.slice(0, deletedCount) }));
|
|
1889
|
+
}
|
|
1890
|
+
else {
|
|
1891
|
+
console.log(`已删除 ${deletedCount} 条连线: ${linkIds.slice(0, deletedCount).join(', ')}`);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
wf.command('delete-node')
|
|
1895
|
+
.description('删除节点(列表 API,自动清理关联出口并可选桥接)')
|
|
1896
|
+
.argument('<workflowId>', '流程 ID')
|
|
1897
|
+
.argument('<nodeId>', '要删除的节点 ID')
|
|
1898
|
+
.option('--bridge', '自动桥接上下游(上游→下游直连)')
|
|
1899
|
+
.action(async (workflowId, nodeId, cmdOpts) => {
|
|
1900
|
+
const opts = program.opts();
|
|
1901
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1902
|
+
if (!auth)
|
|
1903
|
+
process.exit(1);
|
|
1904
|
+
const client = new ApiClient(auth);
|
|
1905
|
+
const baseUrl = auth.getBaseUrl();
|
|
1906
|
+
const BASE = `${baseUrl}/api/bs/workflow/pathdef`;
|
|
1907
|
+
// 1. Read node table
|
|
1908
|
+
const nodeRes = await client.get(`${BASE}/nodeBase/getNodeEditTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1909
|
+
if (nodeRes?.code !== 200 || !nodeRes?.status) {
|
|
1910
|
+
console.error(`读取节点列表失败: ${nodeRes?.msg || '未知错误'}`);
|
|
1911
|
+
process.exit(1);
|
|
1912
|
+
}
|
|
1913
|
+
const nodeRows = nodeRes?.data?.data || [];
|
|
1914
|
+
const targetNode = nodeRows.find((n) => String(n.id) === String(nodeId));
|
|
1915
|
+
if (!targetNode) {
|
|
1916
|
+
console.error(`节点 ${nodeId} 在列表中不存在`);
|
|
1917
|
+
process.exit(1);
|
|
1918
|
+
}
|
|
1919
|
+
// 2. Read link table BEFORE deleting node (to find upstream/downstream for bridge)
|
|
1920
|
+
let upstreamId = '';
|
|
1921
|
+
let downstreamId = '';
|
|
1922
|
+
if (cmdOpts.bridge) {
|
|
1923
|
+
const linkRes = await client.get(`${BASE}/nodeLink/getWfpNodeLinkTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1924
|
+
if (linkRes?.code === 200 && linkRes?.status) {
|
|
1925
|
+
const links = linkRes?.data?.data || [];
|
|
1926
|
+
const incoming = links.find((l) => String(l.destNodeId) === String(nodeId));
|
|
1927
|
+
const outgoing = links.find((l) => String(l.srcNodeId) === String(nodeId));
|
|
1928
|
+
upstreamId = incoming?.srcNodeId || '';
|
|
1929
|
+
downstreamId = outgoing?.destNodeId || '';
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
// 3. Delete node (platform auto-cleans associated links)
|
|
1933
|
+
const remaining = nodeRows.filter((n) => String(n.id) !== String(nodeId));
|
|
1934
|
+
remaining.forEach((n, i) => { n.orderId = String(i); });
|
|
1935
|
+
const saveNodeRes = await client.post(`${BASE}/nodeBase/saveNodeEditTable`, {
|
|
1936
|
+
workflowId,
|
|
1937
|
+
datas: JSON.stringify(remaining),
|
|
1938
|
+
deleteRows: nodeId,
|
|
1939
|
+
changeRows: '',
|
|
1940
|
+
changeControlledRows: '',
|
|
1941
|
+
});
|
|
1942
|
+
if (saveNodeRes?.code !== 200 || !saveNodeRes?.status || saveNodeRes?.data?.status === false) {
|
|
1943
|
+
console.error(`删除节点失败: ${saveNodeRes?.data?.msg || saveNodeRes?.msg || '未知错误'}`);
|
|
1944
|
+
process.exit(1);
|
|
1945
|
+
}
|
|
1946
|
+
// 4. Add bridge link if needed
|
|
1947
|
+
let bridgeResult = undefined;
|
|
1948
|
+
if (cmdOpts.bridge && upstreamId && downstreamId) {
|
|
1949
|
+
const gidRes = await client.get(`${BASE}/nodeLink/generateId?_=${Date.now()}`);
|
|
1950
|
+
const newLinkId = String(gidRes?.data || `link_${Date.now()}`);
|
|
1951
|
+
// Read current links (after auto-clean)
|
|
1952
|
+
const linkRes2 = await client.get(`${BASE}/nodeLink/getWfpNodeLinkTable?workflowId=${workflowId}&_=${Date.now()}`);
|
|
1953
|
+
const currentLinks = (linkRes2?.data?.data || []).map((l, i) => ({ ...l, orderId: String(i) }));
|
|
1954
|
+
currentLinks.push({
|
|
1955
|
+
flag: 'add',
|
|
1956
|
+
id: newLinkId,
|
|
1957
|
+
conditionKey: { conditionKey: '', source: '2' },
|
|
1958
|
+
isReject: '0',
|
|
1959
|
+
isDefault: '0',
|
|
1960
|
+
srcNodeId: upstreamId,
|
|
1961
|
+
destNodeId: downstreamId,
|
|
1962
|
+
publicSegmentId: '',
|
|
1963
|
+
linkName: '桥接',
|
|
1964
|
+
orderId: String(currentLinks.length),
|
|
1965
|
+
});
|
|
1966
|
+
const saveLinkRes = await client.post(`${BASE}/nodeLink/saveNodeLinkEditTable`, {
|
|
1967
|
+
workflowId,
|
|
1968
|
+
datas: JSON.stringify(currentLinks),
|
|
1969
|
+
deleteRows: '',
|
|
1970
|
+
changedLinkIds: '',
|
|
1971
|
+
updateRowIds: [],
|
|
1972
|
+
});
|
|
1973
|
+
if (saveLinkRes?.code === 200 && saveLinkRes?.status && saveLinkRes?.data?.status !== false) {
|
|
1974
|
+
bridgeResult = { from: upstreamId, to: downstreamId, linkId: newLinkId };
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
const result = {
|
|
1978
|
+
success: true,
|
|
1979
|
+
workflowId,
|
|
1980
|
+
deletedNode: { id: nodeId, name: targetNode.nodeName },
|
|
1981
|
+
bridged: bridgeResult,
|
|
1982
|
+
};
|
|
1983
|
+
if (opts.json) {
|
|
1984
|
+
console.log(JSON.stringify(result));
|
|
1985
|
+
}
|
|
1986
|
+
else {
|
|
1987
|
+
console.log(`已删除节点: ${targetNode.nodeName} (${nodeId})`);
|
|
1988
|
+
if (bridgeResult)
|
|
1989
|
+
console.log(` 桥接: ${upstreamId} → ${downstreamId} (${bridgeResult.linkId})`);
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
wf.command('delete')
|
|
1993
|
+
.description('删除流程')
|
|
1994
|
+
.argument('<workflowId>', '流程 ID')
|
|
1995
|
+
.option('--with-form', '同时删除关联表单')
|
|
1996
|
+
.action(async (workflowId, cmdOpts) => {
|
|
1997
|
+
const opts = program.opts();
|
|
1998
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
1999
|
+
if (!auth)
|
|
2000
|
+
process.exit(1);
|
|
2001
|
+
const client = new ApiClient(auth);
|
|
2002
|
+
const baseUrl = auth.getBaseUrl();
|
|
2003
|
+
const deleteForm = cmdOpts.withForm ? 'true' : 'false';
|
|
2004
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/baseSet/deletePath?workflowId=${workflowId}&deleteForm=${deleteForm}`);
|
|
2005
|
+
if (data?.code === 200 && data?.status) {
|
|
2006
|
+
const msg = opts.json
|
|
2007
|
+
? JSON.stringify({ success: true, workflowId })
|
|
2008
|
+
: `流程已删除: ${workflowId}`;
|
|
2009
|
+
console.log(msg);
|
|
2010
|
+
}
|
|
2011
|
+
else {
|
|
2012
|
+
console.error(`删除失败: ${data?.msg || '未知错误'}`);
|
|
2013
|
+
process.exit(1);
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
wf.command('set-link-condition-key')
|
|
2017
|
+
.description('将条件 Key 绑定到连线')
|
|
2018
|
+
.argument('<workflowId>', '流程 ID')
|
|
2019
|
+
.argument('<linkId>', '连线 ID')
|
|
2020
|
+
.argument('<conditionKey>', '条件 Key(由 saveCondition 返回)')
|
|
2021
|
+
.argument('<nodeId>', '来源节点 ID')
|
|
2022
|
+
.action(async (workflowId, linkId, conditionKey, nodeId) => {
|
|
2023
|
+
const opts = program.opts();
|
|
2024
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
2025
|
+
if (!auth)
|
|
2026
|
+
process.exit(1);
|
|
2027
|
+
const client = new ApiClient(auth);
|
|
2028
|
+
const baseUrl = auth.getBaseUrl();
|
|
2029
|
+
const payload = { workflowId, linkId, conditionKey, nodeId };
|
|
2030
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeLink/saveLinkConditionKey`, payload);
|
|
2031
|
+
if (data?.code === 200 && data?.status) {
|
|
2032
|
+
const msg = opts.json
|
|
2033
|
+
? JSON.stringify({ success: true, linkId, conditionKey })
|
|
2034
|
+
: `条件已绑定: ${conditionKey} → 连线 ${linkId}`;
|
|
2035
|
+
console.log(msg);
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
console.error(`绑定失败: ${data?.msg || '未知错误'}`);
|
|
2039
|
+
process.exit(1);
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
wf.command('save-condition')
|
|
2043
|
+
.description('保存连线条件规则(自动生成节点 ID,简化 JSON 只需写条件逻辑)')
|
|
2044
|
+
.argument('<json>', '条件定义 JSON,以 @ 开头表示文件路径')
|
|
2045
|
+
.option('--app-id <appId>', '应用 ID(用于生成唯一 ID)')
|
|
2046
|
+
.action(async (jsonArg, cmdOpts) => {
|
|
2047
|
+
const opts = program.opts();
|
|
2048
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
2049
|
+
if (!auth)
|
|
2050
|
+
process.exit(1);
|
|
2051
|
+
let jsonStr = jsonArg;
|
|
2052
|
+
if (jsonArg.startsWith('@')) {
|
|
2053
|
+
const { readFileSync, existsSync } = await import('fs');
|
|
2054
|
+
const filePath = jsonArg.slice(1);
|
|
2055
|
+
if (!existsSync(filePath)) {
|
|
2056
|
+
console.error(`文件不存在: ${filePath}`);
|
|
2057
|
+
process.exit(1);
|
|
2058
|
+
}
|
|
2059
|
+
jsonStr = readFileSync(filePath, 'utf-8');
|
|
2060
|
+
}
|
|
2061
|
+
let payload;
|
|
2062
|
+
try {
|
|
2063
|
+
payload = JSON.parse(jsonStr);
|
|
2064
|
+
}
|
|
2065
|
+
catch {
|
|
2066
|
+
console.error('JSON 解析失败');
|
|
2067
|
+
process.exit(1);
|
|
2068
|
+
}
|
|
2069
|
+
const client = new ApiClient(auth);
|
|
2070
|
+
const baseUrl = auth.getBaseUrl();
|
|
2071
|
+
// Auto-fetch mapBaseId from getConditionSet (platform requires it for linking condition to link)
|
|
2072
|
+
if (!payload.mapBaseId) {
|
|
2073
|
+
try {
|
|
2074
|
+
const csRes = await client.post(`${baseUrl}/api/workflow/pathdef/rule/condition/getConditionSet`, {
|
|
2075
|
+
source: payload.source ?? 2,
|
|
2076
|
+
sourceId: payload.sourceId,
|
|
2077
|
+
workflowId: payload.workflowId,
|
|
2078
|
+
conditionName: payload.conditionName || '',
|
|
2079
|
+
linkName: payload.linkName || '',
|
|
2080
|
+
needFormfield: true,
|
|
2081
|
+
readOnly: false,
|
|
2082
|
+
templateTokenId: String(Date.now()),
|
|
2083
|
+
});
|
|
2084
|
+
if (csRes?.code === 200 && csRes?.data?.mapBaseId) {
|
|
2085
|
+
payload.mapBaseId = csRes.data.mapBaseId;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
catch { /* best effort */ }
|
|
2089
|
+
}
|
|
2090
|
+
// Always enrich formField + add leaf helper fields required by platform
|
|
2091
|
+
const enrichChildrens = (childrens, fieldList) => {
|
|
2092
|
+
for (const c of childrens) {
|
|
2093
|
+
if (c.childrens && c.relation !== undefined) {
|
|
2094
|
+
enrichChildrens(c.childrens, fieldList);
|
|
2095
|
+
}
|
|
2096
|
+
else {
|
|
2097
|
+
// Leaf: fill required helper fields (platform requires them)
|
|
2098
|
+
c.workflowId = c.workflowId || payload.workflowId;
|
|
2099
|
+
c.elementValue1Id = c.elementValue1Id ?? '';
|
|
2100
|
+
c.elementValue2Id = c.elementValue2Id ?? '';
|
|
2101
|
+
c.expressionBaseId = c.expressionBaseId ?? '';
|
|
2102
|
+
c.dataValueId = c.dataValueId ?? '';
|
|
2103
|
+
// Stringify selectType (platform expects string)
|
|
2104
|
+
if (c.selectType !== undefined && typeof c.selectType !== 'string') {
|
|
2105
|
+
c.selectType = String(c.selectType);
|
|
2106
|
+
}
|
|
2107
|
+
if (c.formField && fieldList.length > 0) {
|
|
2108
|
+
for (let i = 0; i < c.formField.length; i++) {
|
|
2109
|
+
const ff = c.formField[i];
|
|
2110
|
+
const fid = String(ff.id || ff.fieldId || '');
|
|
2111
|
+
const fname = ff.name || ff.fieldName || '';
|
|
2112
|
+
const full = fieldList.find((f) => String(f.id) === fid || f.name === fname);
|
|
2113
|
+
if (full) {
|
|
2114
|
+
c.formField[i] = {
|
|
2115
|
+
id: full.id,
|
|
2116
|
+
name: full.name,
|
|
2117
|
+
fieldType: full.fieldType,
|
|
2118
|
+
detail: full.detail ?? false,
|
|
2119
|
+
detailName: full.detailName ?? '主表',
|
|
2120
|
+
total: full.total ?? false,
|
|
2121
|
+
formId: full.formId,
|
|
2122
|
+
sourceModule: full.sourceModule ?? 'ebuilder',
|
|
2123
|
+
dataTemplateId: full.dataTemplateId ?? '-1',
|
|
2124
|
+
detailTableId: full.detailTableId ?? '-1',
|
|
2125
|
+
layoutDetail: full.layoutDetail || '',
|
|
2126
|
+
selectDataSource: full.selectDataSource ?? false,
|
|
2127
|
+
groupKey: full.groupKey || full.fieldType,
|
|
2128
|
+
supportContainType: full.supportContainType ?? false,
|
|
2129
|
+
newFormDesigner: full.newFormDesigner ?? false,
|
|
2130
|
+
variable: full.variable ?? false,
|
|
2131
|
+
valueCount: full.valueCount ?? false,
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
let fieldList = [];
|
|
2140
|
+
try {
|
|
2141
|
+
const browserRes = await client.post(`${baseUrl}/api/workflow/pathdef/common/browser/complete/conditionFieldBrowser`, {
|
|
2142
|
+
browserMultiple: false,
|
|
2143
|
+
sourceId: payload.sourceId,
|
|
2144
|
+
tab: 'formField',
|
|
2145
|
+
source: 2,
|
|
2146
|
+
workflowId: payload.workflowId,
|
|
2147
|
+
newly: 1,
|
|
2148
|
+
searchValue: '',
|
|
2149
|
+
});
|
|
2150
|
+
fieldList = browserRes?.data?.data || [];
|
|
2151
|
+
}
|
|
2152
|
+
catch { /* best effort */ }
|
|
2153
|
+
if (payload.childrens)
|
|
2154
|
+
enrichChildrens(payload.childrens, fieldList);
|
|
2155
|
+
// Platform requires allRuleIsEmpty at top level
|
|
2156
|
+
if (payload.allRuleIsEmpty === undefined)
|
|
2157
|
+
payload.allRuleIsEmpty = false;
|
|
2158
|
+
const data = await client.post(`${baseUrl}/api/workflow/pathdef/rule/condition/saveCondition`, payload);
|
|
2159
|
+
if (data?.code === 200 && data?.status) {
|
|
2160
|
+
const conditionKey = data?.data?.conditionKey || data?.data?.conditionId || '';
|
|
2161
|
+
const conditionCn = data?.data?.conditionCn || '';
|
|
2162
|
+
// Auto-bind conditionKey to the link (without this, the link's hasCondition stays 0)
|
|
2163
|
+
let bindKey = conditionKey;
|
|
2164
|
+
if (!bindKey || bindKey === '-1') {
|
|
2165
|
+
try {
|
|
2166
|
+
const csRes = await client.post(`${baseUrl}/api/workflow/pathdef/rule/condition/getConditionSet`, {
|
|
2167
|
+
source: payload.source ?? 2,
|
|
2168
|
+
sourceId: payload.sourceId,
|
|
2169
|
+
workflowId: payload.workflowId,
|
|
2170
|
+
conditionName: payload.conditionName || '',
|
|
2171
|
+
linkName: payload.linkName || '',
|
|
2172
|
+
needFormfield: true, readOnly: false,
|
|
2173
|
+
templateTokenId: String(Date.now()),
|
|
2174
|
+
});
|
|
2175
|
+
if (csRes?.code === 200 && csRes?.data?.mapBaseId) {
|
|
2176
|
+
bindKey = csRes.data.mapBaseId;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
catch { /* best effort */ }
|
|
2180
|
+
}
|
|
2181
|
+
const sourceNodeId = payload.nodeId || '';
|
|
2182
|
+
if (bindKey && bindKey !== '-1' && sourceNodeId) {
|
|
2183
|
+
try {
|
|
2184
|
+
await client.post(`${baseUrl}/api/bs/workflow/pathdef/nodeLink/saveLinkConditionKey`, {
|
|
2185
|
+
workflowId: payload.workflowId,
|
|
2186
|
+
linkId: payload.sourceId,
|
|
2187
|
+
conditionKey: bindKey,
|
|
2188
|
+
nodeId: sourceNodeId,
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
catch { /* best effort — binding is optional */ }
|
|
2192
|
+
}
|
|
2193
|
+
if (opts.json) {
|
|
2194
|
+
console.log(JSON.stringify(data.data, null, 2));
|
|
2195
|
+
}
|
|
2196
|
+
else {
|
|
2197
|
+
console.log(`条件已保存: conditionKey=${conditionKey}${conditionCn ? ' (' + conditionCn + ')' : ''}${sourceNodeId && bindKey && bindKey !== '-1' ? ' [已绑定到连线]' : ''}`);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
else {
|
|
2201
|
+
console.error(`保存失败: ${data?.msg || '未知错误'}`);
|
|
2202
|
+
process.exit(1);
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
wf.command('save-action')
|
|
2206
|
+
.description('保存节点提交事件/动作。只接 JSON 一个参数(workflowId/nodeId 写在 JSON 内部,不是位置参数)。简化 JSON(常量赋值)或完整 data[] 结构(高级)')
|
|
2207
|
+
.argument('<json>', '动作定义 JSON,以 @ 开头表示文件路径;workflowId/nodeId 等均在 JSON 内')
|
|
2208
|
+
.action(async (jsonArg) => {
|
|
2209
|
+
const opts = program.opts();
|
|
2210
|
+
const auth = await AuthSession.fromOptions(opts);
|
|
2211
|
+
if (!auth)
|
|
2212
|
+
process.exit(1);
|
|
2213
|
+
let jsonStr = jsonArg;
|
|
2214
|
+
if (jsonArg.startsWith('@')) {
|
|
2215
|
+
const { readFileSync, existsSync } = await import('fs');
|
|
2216
|
+
const filePath = jsonArg.slice(1);
|
|
2217
|
+
if (!existsSync(filePath)) {
|
|
2218
|
+
console.error(`文件不存在: ${filePath}`);
|
|
2219
|
+
process.exit(1);
|
|
2220
|
+
}
|
|
2221
|
+
jsonStr = readFileSync(filePath, 'utf-8');
|
|
2222
|
+
}
|
|
2223
|
+
let parsed;
|
|
2224
|
+
try {
|
|
2225
|
+
parsed = JSON.parse(jsonStr);
|
|
2226
|
+
}
|
|
2227
|
+
catch {
|
|
2228
|
+
console.error('JSON 解析失败');
|
|
2229
|
+
process.exit(1);
|
|
2230
|
+
}
|
|
2231
|
+
// 简化 schema → 构造 saveAddActionList 正确 payload(actionType=0 字段常量赋值)
|
|
2232
|
+
// 真实浏览器 payload: data[0] = {id(负临时), actionName, executeTime:["1"], open:true, async:0,
|
|
2233
|
+
// actionType:0, triggerType:"pre", targetValue:<字段id>, firstValue:"custom", customValue:<值>, ...}
|
|
2234
|
+
let payload;
|
|
2235
|
+
if (Array.isArray(parsed?.data)) {
|
|
2236
|
+
// 完整结构(高级用法)原样透传
|
|
2237
|
+
payload = parsed;
|
|
2238
|
+
}
|
|
2239
|
+
else if (parsed?.fieldId !== undefined && parsed?.value !== undefined) {
|
|
2240
|
+
const wf = parsed.workflowId;
|
|
2241
|
+
const nodeId = parsed.nodeId;
|
|
2242
|
+
if (!wf || !nodeId) {
|
|
2243
|
+
console.error('简化 JSON 缺少 workflowId / nodeId');
|
|
2244
|
+
process.exit(1);
|
|
2245
|
+
}
|
|
2246
|
+
const fieldName = parsed.fieldName || '';
|
|
2247
|
+
const value = String(parsed.value);
|
|
2248
|
+
const nodeName = parsed.nodeName || '节点';
|
|
2249
|
+
const trigger = parsed.trigger || 'pre';
|
|
2250
|
+
payload = {
|
|
2251
|
+
workflowId: wf, objId: nodeId, triggerType: 'all',
|
|
2252
|
+
data: [{
|
|
2253
|
+
id: tempIdNeg(),
|
|
2254
|
+
actionName: `${fieldName} = ${value}`.trim(),
|
|
2255
|
+
executeTime: ['1'], open: true, async: 0, actionType: 0,
|
|
2256
|
+
dateRuleStr: '', triggerType: trigger, order: 0,
|
|
2257
|
+
targetValue: parsed.fieldId,
|
|
2258
|
+
firstValue: 'custom',
|
|
2259
|
+
operatorValue: '', secondValue: '',
|
|
2260
|
+
customValue: value,
|
|
2261
|
+
dateRule: [], isCreate: false,
|
|
2262
|
+
}],
|
|
2263
|
+
isNodeFlag: '1', objName: nodeName,
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
else {
|
|
2267
|
+
// 兜底:旧式裸 payload 直接透传(向后兼容)
|
|
2268
|
+
payload = parsed;
|
|
2269
|
+
}
|
|
2270
|
+
const client = new ApiClient(auth);
|
|
2271
|
+
const baseUrl = auth.getBaseUrl();
|
|
2272
|
+
// 归一化用户提供的 data[] 行——高级用法常直接回传 list-node 的读回格式
|
|
2273
|
+
// (fieldMappingData 嵌套 / async:false 布尔 / 无 paramsSet),原样写入会坏节点
|
|
2274
|
+
// (之后读该节点恒 500,API 不可恢复)。normalizeActionRow 对已是写格式的行幂等。
|
|
2275
|
+
payload.data = (payload.data || []).map(normalizeActionRow);
|
|
2276
|
+
// Fetch existing actions and merge (same pattern as action create / message create)
|
|
2277
|
+
const wfId = payload.workflowId;
|
|
2278
|
+
const nId = payload.objId;
|
|
2279
|
+
// 校验 actionType=0 行的 targetValue(=fieldId) 必须是流程表单里真实存在的字段。
|
|
2280
|
+
// 无效 fieldId 会被服务端存成空字段映射(targetValue/customValue 全空),之后任何
|
|
2281
|
+
// 合并写回都会把空映射行带回 saveAddActionList → 节点写坏、读恒 500。在源头拦住。
|
|
2282
|
+
const type0Rows = (payload.data || []).filter((r) => r.actionType === 0);
|
|
2283
|
+
if (type0Rows.length > 0) {
|
|
2284
|
+
const srcForm = await resolveSourceForm(client, baseUrl, wfId);
|
|
2285
|
+
if (srcForm) {
|
|
2286
|
+
const srcFields = await fetchEbField(client, baseUrl, srcForm.sourceObjId, srcForm.sourceAppId);
|
|
2287
|
+
const validIds = new Set([
|
|
2288
|
+
...srcFields.main,
|
|
2289
|
+
...srcFields.detail.flatMap((d) => d.fields || []),
|
|
2290
|
+
].map((f) => String(f.id)));
|
|
2291
|
+
for (const r of type0Rows) {
|
|
2292
|
+
if (!validIds.has(String(r.targetValue ?? ''))) {
|
|
2293
|
+
console.error(` ❌ 字段不存在: fieldId=${r.targetValue ?? '(空)'} 不在流程表单「${srcForm.sourceFormName}」中`);
|
|
2294
|
+
console.error(` 可用字段: ${srcFields.main.map((f) => `${f.id}(${f.showName})`).join(', ')}`);
|
|
2295
|
+
process.exit(1);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
let existingActions = [];
|
|
2301
|
+
let nodeCorrupted = false;
|
|
2302
|
+
try {
|
|
2303
|
+
const existing = await client.post(`${baseUrl}/api/bs/workflow/pathdef/addaction/getAddActionDataList`, {
|
|
2304
|
+
workflowId: wfId, objId: nId, triggerType: 'all', isNodeFlag: '1',
|
|
2305
|
+
});
|
|
2306
|
+
if (existing?.code === 200 && existing?.status) {
|
|
2307
|
+
existingActions = existing?.data?.data || [];
|
|
2308
|
+
}
|
|
2309
|
+
else if (existing?.code === 500 || /系统错误/.test(existing?.msg || '')) {
|
|
2310
|
+
nodeCorrupted = true;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch {
|
|
2314
|
+
nodeCorrupted = true;
|
|
2315
|
+
}
|
|
2316
|
+
if (nodeCorrupted) {
|
|
2317
|
+
console.error(` ⚠ 节点动作读取失败(500):该节点很可能已被写坏(曾以读回格式原样保存)。`);
|
|
2318
|
+
console.error(` API 无法修复,请用 \`workflow delete-node ${wfId} ${nId}\` 删除节点后重建。`);
|
|
2319
|
+
process.exit(1);
|
|
2320
|
+
}
|
|
2321
|
+
if (existingActions.length > 0) {
|
|
2322
|
+
// Merge: normalize existing read-format rows back to write format, then append.
|
|
2323
|
+
// Re-sending raw read rows (fieldMappingData nesting / async:false / no paramsSet)
|
|
2324
|
+
// corrupts the node — every later read returns 500.
|
|
2325
|
+
const oldCount = existingActions.length;
|
|
2326
|
+
// 归一化已有行;跳过 actionType=0 且 targetValue 为空的残留坏动作(无效 fieldId 造成),
|
|
2327
|
+
// 这类空映射行写回会坏节点。跳过后合并仍可安全进行。
|
|
2328
|
+
const normalized = existingActions.map(normalizeActionRow).filter((r) => {
|
|
2329
|
+
if (r.actionType === 0 && !r.targetValue) {
|
|
2330
|
+
console.error(` ⚠ 跳过空字段映射的已有动作「${r.actionName || ''}」(无效 fieldId 残留,写回会坏节点)`);
|
|
2331
|
+
return false;
|
|
2332
|
+
}
|
|
2333
|
+
return true;
|
|
2334
|
+
});
|
|
2335
|
+
for (const item of (payload.data || [])) {
|
|
2336
|
+
item.order = normalized.length;
|
|
2337
|
+
normalized.push(item);
|
|
2338
|
+
}
|
|
2339
|
+
payload.data = normalized;
|
|
2340
|
+
console.error(` 已有 ${oldCount} 个动作,归一化合并后 ${normalized.length} 个`);
|
|
2341
|
+
}
|
|
2342
|
+
const data = await client.post(`${baseUrl}/api/bs/workflow/pathdef/addaction/saveAddActionList`, payload);
|
|
2343
|
+
if (data?.code === 200 && data?.status) {
|
|
2344
|
+
if (opts.json) {
|
|
2345
|
+
console.log(JSON.stringify(data.data, null, 2));
|
|
2346
|
+
}
|
|
2347
|
+
else {
|
|
2348
|
+
const actions = payload?.data?.length || 0;
|
|
2349
|
+
console.log(`动作已保存: ${actions} 个动作 → 节点 ${payload?.objId}`);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
else {
|
|
2353
|
+
console.error(`保存失败: ${data?.msg || '未知错误'}`);
|
|
2354
|
+
process.exit(1);
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
return wf;
|
|
2358
|
+
}
|
|
2359
|
+
//# sourceMappingURL=workflow.js.map
|