weaver-e9-assistant-cli 1.0.0
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/dist/args.js +36 -0
- package/dist/auth-token.js +690 -0
- package/dist/auth.js +187 -0
- package/dist/blog.js +469 -0
- package/dist/cli.js +4041 -0
- package/dist/client.js +199 -0
- package/dist/config.js +124 -0
- package/dist/createcontext.js +41 -0
- package/dist/crypto.js +139 -0
- package/dist/cube.js +1633 -0
- package/dist/email.js +838 -0
- package/dist/fileutil.js +20 -0
- package/dist/kq.js +204 -0
- package/dist/links.js +34 -0
- package/dist/odoc.js +194 -0
- package/dist/pageopen.js +29 -0
- package/dist/parse.js +159 -0
- package/dist/querystate.js +38 -0
- package/dist/render.js +21 -0
- package/dist/report.js +50 -0
- package/dist/submissionplan.js +43 -0
- package/dist/submissionstate.js +81 -0
- package/dist/todoaskidea.js +54 -0
- package/dist/todobatch.js +154 -0
- package/dist/todocc.js +128 -0
- package/dist/todoforward.js +66 -0
- package/dist/todoreject.js +215 -0
- package/dist/todosubmit.js +277 -0
- package/dist/todotransfer.js +54 -0
- package/dist/util.js +25 -0
- package/dist/wfcreate.js +102 -0
- package/dist/wfform.js +194 -0
- package/dist/wfops.js +40 -0
- package/dist/wfreceiver.js +43 -0
- package/dist/wfstart.js +717 -0
- package/dist/wfsummary.js +89 -0
- package/dist/wfupload.js +78 -0
- package/dist/wfuser.js +60 -0
- package/dist/workflow.js +461 -0
- package/dist/workplan.js +394 -0
- package/package.json +25 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
import { todayStr, nowTimeStr } from './util.js';
|
|
3
|
+
/**
|
|
4
|
+
* 退回/驳回流程。HAR 正确链路:
|
|
5
|
+
* loadForm(2步拿签名) → judgeRejectWay(判断退回方式) → getForwardDatas(取可退回节点) →
|
|
6
|
+
* doBack(selectRow=选定节点行id) → requestOperation(src=reject)
|
|
7
|
+
*
|
|
8
|
+
* 修复(P0-3):旧实现跳过 judgeRejectWay/getForwardDatas/doBack,直接 requestOperation(src=reject),
|
|
9
|
+
* 且 RejectNodes/RejectToNodeid/RejectToType 全空 → 报"流程数据已更改"。
|
|
10
|
+
*
|
|
11
|
+
* ⚠ getForwardDatas 响应结构未抓到(HAR 只抓请求),selectRow/RejectToNodeid 的取值路径属 best-effort,
|
|
12
|
+
* 需实跑校准;如失败可用 --reject-node/--reject-to-node/--reject-to-type 手工指定。
|
|
13
|
+
*/
|
|
14
|
+
export async function rejectTodo(cfg, opts) {
|
|
15
|
+
const { requestId } = opts;
|
|
16
|
+
const remarkText = opts.remark ?? '退回';
|
|
17
|
+
const remarkHtml = remarkText.split(/\r?\n/).map((l) => `<p>${l}</p>`).join('');
|
|
18
|
+
const api = await createApiFromProfile(cfg);
|
|
19
|
+
// 第一步 loadForm → 拿签名
|
|
20
|
+
const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
|
|
21
|
+
const j0 = r0.json ?? {};
|
|
22
|
+
const p0 = j0.params ?? {};
|
|
23
|
+
const sp0 = j0.submitParams ?? {};
|
|
24
|
+
const wfId = String(p0.workflowid ?? '');
|
|
25
|
+
const nodeId = String(p0.nodeid ?? '');
|
|
26
|
+
let p = p0;
|
|
27
|
+
let sp = sp0;
|
|
28
|
+
if (wfId && nodeId) {
|
|
29
|
+
const r1 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId, workflowid: wfId, nodeid: nodeId });
|
|
30
|
+
const j1 = r1.json ?? {};
|
|
31
|
+
p = j1.params ?? p0;
|
|
32
|
+
sp = j1.submitParams ?? sp0;
|
|
33
|
+
}
|
|
34
|
+
const userId = String(p.f_weaver_belongto_userid ?? '1');
|
|
35
|
+
// ② judgeRejectWay(HAR:requestid/workflowid/nodeid/f_weaver_belongto_userid/...)
|
|
36
|
+
try {
|
|
37
|
+
await api.post('/api/workflow/reqform/judgeRejectWay', {
|
|
38
|
+
requestid: requestId,
|
|
39
|
+
workflowid: wfId,
|
|
40
|
+
nodeid: nodeId,
|
|
41
|
+
'f_weaver_belongto_userid': userId,
|
|
42
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
43
|
+
wfTestStr: '',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch { /* 判断失败不阻断,继续走退回 */ }
|
|
47
|
+
// ③ getForwardDatas(GET,取可退回节点列表;响应未抓,best-effort 解析 selectRow + nodeid)
|
|
48
|
+
let rejectNodes = [];
|
|
49
|
+
let selectRow = '';
|
|
50
|
+
let rejectToNode = opts.rejectToNode ?? '';
|
|
51
|
+
let rejectToType = opts.rejectToType ?? '';
|
|
52
|
+
let rejectNodesField = opts.rejectNode ?? '';
|
|
53
|
+
try {
|
|
54
|
+
const rFd = await api.get('/api/workflow/reqform/getForwardDatas', {
|
|
55
|
+
requestid: requestId, workflowid: wfId, nodeid: nodeId,
|
|
56
|
+
'f_weaver_belongto_userid': userId, 'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
57
|
+
});
|
|
58
|
+
const fdText = rFd.text ?? '';
|
|
59
|
+
// best-effort:从响应里提节点行 id(selectRow)与 nodeid/nodename
|
|
60
|
+
rejectNodes = [...fdText.matchAll(/["']?selectRow["']?\s*[:=]\s*["']?(\d+)["']?[^}]*?["']?nodeid["']?\s*[:=]\s*["']?(\d+)["']?(?:[^}]*?["']?nodename["']?\s*[:=]\s*["']?([^"',}]+)["']?)?/gi)]
|
|
61
|
+
.map((m) => ({ selectRow: m[1], nodeid: m[2], nodename: m[3] ?? '' }));
|
|
62
|
+
if (rejectNodes.length === 0) {
|
|
63
|
+
// 兜底:分别提 selectRow 与 nodeid
|
|
64
|
+
const srs = [...fdText.matchAll(/selectRow["']?\s*[:=]\s*["']?(\d+)/gi)].map((m) => m[1]);
|
|
65
|
+
const nids = [...fdText.matchAll(/nodeid["']?\s*[:=]\s*["']?(\d+)/gi)].map((m) => m[1]);
|
|
66
|
+
rejectNodes = srs.map((sr, i) => ({ selectRow: sr, nodeid: nids[i] ?? '', nodename: '' }));
|
|
67
|
+
}
|
|
68
|
+
// 选定目标节点:用户传 --reject-to-node(=nodeid) 优先,否则取第一个
|
|
69
|
+
const picked = rejectToNode
|
|
70
|
+
? rejectNodes.find((n) => n.nodeid === rejectToNode) ?? rejectNodes[0]
|
|
71
|
+
: rejectNodes[0];
|
|
72
|
+
if (picked) {
|
|
73
|
+
selectRow = picked.selectRow;
|
|
74
|
+
if (!rejectToNode)
|
|
75
|
+
rejectToNode = picked.nodeid;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { /* getForwardDatas 失败不阻断 */ }
|
|
79
|
+
// ④ doBack(selectRow)(HAR:selectRow=<节点行id>)
|
|
80
|
+
if (selectRow) {
|
|
81
|
+
try {
|
|
82
|
+
await api.post('/api/workflow/reqform/doBack', {
|
|
83
|
+
selectRow,
|
|
84
|
+
requestid: requestId,
|
|
85
|
+
workflowid: wfId,
|
|
86
|
+
nodeid: nodeId,
|
|
87
|
+
'f_weaver_belongto_userid': userId,
|
|
88
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch { /* doBack 失败不阻断 */ }
|
|
92
|
+
}
|
|
93
|
+
const sigDecoded = Buffer.from(String(p.signatureAttributesStr ?? ''), 'base64').toString('utf8');
|
|
94
|
+
const lastOpDate = (sigDecoded.match(/lastOperateDate=([^|]*)/)?.[1] ?? '').trim();
|
|
95
|
+
const lastOpTime = (sigDecoded.match(/lastOperateTime=([^|]*)/)?.[1] ?? '').trim();
|
|
96
|
+
const todayStrVal = todayStr();
|
|
97
|
+
const timeStr = nowTimeStr();
|
|
98
|
+
const submitTokenKey = `${userId}_${requestId}_request_submit_token`;
|
|
99
|
+
const submitToken = String(sp[submitTokenKey] ?? Date.now());
|
|
100
|
+
// ⑤ requestOperation(src=reject),带上退回目标(HAR 实测旧实现三字段全空是失败根因)
|
|
101
|
+
const body = {
|
|
102
|
+
formid: String(sp.formid ?? p.formid ?? ''),
|
|
103
|
+
isSubmitDirectNode: '',
|
|
104
|
+
openByDefaultBrowser: '',
|
|
105
|
+
iscreate: '0',
|
|
106
|
+
creatertype: '0',
|
|
107
|
+
isdialog: '1',
|
|
108
|
+
lastOperateDate: lastOpDate,
|
|
109
|
+
createdoc: '',
|
|
110
|
+
nodeid: nodeId,
|
|
111
|
+
workflowid: wfId,
|
|
112
|
+
isbill: String(sp.isbill ?? p.isbill ?? '1'),
|
|
113
|
+
authStr: String(p.authStr ?? ''),
|
|
114
|
+
'f_weaver_belongto_userid': userId,
|
|
115
|
+
currenttime: timeStr,
|
|
116
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
117
|
+
agentorByAgentId: '-1',
|
|
118
|
+
isMultiDoc: '',
|
|
119
|
+
inputcheck: '',
|
|
120
|
+
comemessage: '',
|
|
121
|
+
lastOperateTime: lastOpTime,
|
|
122
|
+
temphasUseTempletSucceed: '',
|
|
123
|
+
workflowRequestLogId: '',
|
|
124
|
+
edesign_layout: '',
|
|
125
|
+
[submitTokenKey]: submitToken,
|
|
126
|
+
requestid: requestId,
|
|
127
|
+
isremark: '0',
|
|
128
|
+
creater: String(p.creater ?? userId),
|
|
129
|
+
htmlfieldids: '',
|
|
130
|
+
SubmitToNodeid: '',
|
|
131
|
+
isCptwf: 'false',
|
|
132
|
+
isovertime: '',
|
|
133
|
+
agentType: '0',
|
|
134
|
+
authSignatureStr: String(p.authSignatureStr ?? ''),
|
|
135
|
+
nodetype: String(p.nodetype ?? sp.nodetype ?? '0'),
|
|
136
|
+
needoutprint: '',
|
|
137
|
+
lastOperator: String(sp.lastOperator ?? p.lastOperator ?? userId),
|
|
138
|
+
topage: '',
|
|
139
|
+
isFormSignature: '0',
|
|
140
|
+
remindTypes: '',
|
|
141
|
+
fromFlowDoc: '',
|
|
142
|
+
RejectNodes: rejectNodesField,
|
|
143
|
+
linkageUUID: String(sp.linkageUUID ?? ''),
|
|
144
|
+
billid: String(sp.billid ?? ''),
|
|
145
|
+
lastnodeid: '',
|
|
146
|
+
uploadType: '',
|
|
147
|
+
isSignMustInput: '',
|
|
148
|
+
RejectToNodeid: rejectToNode,
|
|
149
|
+
isWorkflowDoc: 'false',
|
|
150
|
+
src: 'reject',
|
|
151
|
+
annexmaxUploadImageSize: '',
|
|
152
|
+
takisremark: '0',
|
|
153
|
+
workflowtype: String(sp.workflowtype ?? p.workflowtype ?? ''),
|
|
154
|
+
remarkLocation: '',
|
|
155
|
+
needcheck: '',
|
|
156
|
+
needcheckLock: 'false',
|
|
157
|
+
selectfieldvalue: '',
|
|
158
|
+
RejectToType: rejectToType,
|
|
159
|
+
currentdate: todayStrVal,
|
|
160
|
+
needwfback: '0',
|
|
161
|
+
remark: remarkHtml,
|
|
162
|
+
'field-annexupload': '',
|
|
163
|
+
signdocids: '',
|
|
164
|
+
signworkflowids: '',
|
|
165
|
+
annexdocids: '',
|
|
166
|
+
annexdocinfos: '',
|
|
167
|
+
handWrittenSign: '',
|
|
168
|
+
isOdocRequest: '0',
|
|
169
|
+
enableIntervenor: '',
|
|
170
|
+
verifyRequiredRange: 'field-9999,',
|
|
171
|
+
linkageUnFinishedKey: '',
|
|
172
|
+
remarkquote: remarkHtml,
|
|
173
|
+
actiontype: 'requestOperation',
|
|
174
|
+
isFirstSubmit: '0',
|
|
175
|
+
existChangeRange: '',
|
|
176
|
+
requestname: String(p.requestname ?? ''),
|
|
177
|
+
requestlevel: '0',
|
|
178
|
+
chatsType: '-1',
|
|
179
|
+
messageType: '-1',
|
|
180
|
+
'field-9': '0',
|
|
181
|
+
'field-10': '4',
|
|
182
|
+
mainFieldUnEmptyCount: '0',
|
|
183
|
+
detailFieldUnEmptyCount: '0',
|
|
184
|
+
signatureAttributesStr: String(p.signatureAttributesStr ?? ''),
|
|
185
|
+
signatureSecretKey: String(p.signatureSecretKey ?? ''),
|
|
186
|
+
selectNextFlow: '0',
|
|
187
|
+
openDataVerify: '0',
|
|
188
|
+
wfTestStr: '',
|
|
189
|
+
};
|
|
190
|
+
return api.post('/api/workflow/reqform/requestOperation', body);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 获取可退回的节点列表(judgeRejectWay → getForwardDatas)。
|
|
194
|
+
*/
|
|
195
|
+
export async function getRejectNodes(cfg, requestId) {
|
|
196
|
+
const api = await createApiFromProfile(cfg);
|
|
197
|
+
const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
|
|
198
|
+
const j0 = r0.json ?? {};
|
|
199
|
+
const p0 = j0.params ?? {};
|
|
200
|
+
const wfId = String(p0.workflowid ?? '');
|
|
201
|
+
const nodeId = String(p0.nodeid ?? '');
|
|
202
|
+
const userId = String(p0.f_weaver_belongto_userid ?? '1');
|
|
203
|
+
try {
|
|
204
|
+
await api.post('/api/workflow/reqform/judgeRejectWay', {
|
|
205
|
+
requestid: requestId, workflowid: wfId, nodeid: nodeId,
|
|
206
|
+
'f_weaver_belongto_userid': userId, 'f_weaver_belongto_usertype': String(p0.f_weaver_belongto_usertype ?? '0'),
|
|
207
|
+
wfTestStr: '',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch { /* 忽略 */ }
|
|
211
|
+
return api.get('/api/workflow/reqform/getForwardDatas', {
|
|
212
|
+
requestid: requestId, workflowid: wfId, nodeid: nodeId,
|
|
213
|
+
'f_weaver_belongto_userid': userId, 'f_weaver_belongto_usertype': String(p0.f_weaver_belongto_usertype ?? '0'),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
import { getWfList } from './workflow.js';
|
|
3
|
+
import { todayStr, nowTimeStr } from './util.js';
|
|
4
|
+
/**
|
|
5
|
+
* 提交待办到下一节点。POST requestOperation(iscreate=0)。
|
|
6
|
+
* 两步 loadForm 拿签名(先取 wf+node,再带 wf+node 取 per-request 签名)。
|
|
7
|
+
*
|
|
8
|
+
* 修复(P0-5):submit_token key 旧硬编码 `1_<rid>_...`(前缀=1=sysadmin),非 sysadmin 取不到 token;
|
|
9
|
+
* 改用 `p.f_weaver_belongto_userid` 动态拼 key,并兜底 `1_` 旧前缀,与 wfstart.js create 路径一致。
|
|
10
|
+
*/
|
|
11
|
+
export async function submitTodo(cfg, opts) {
|
|
12
|
+
const { requestId } = opts;
|
|
13
|
+
const remarkText = opts.remark ?? 'OK';
|
|
14
|
+
const remarkHtml = remarkText.split(/\r?\n/).map((l) => `<p>${l}</p>`).join('');
|
|
15
|
+
const api = await createApiFromProfile(cfg);
|
|
16
|
+
// 第一步 loadForm(不带 wf+node)→ 拿 workflowid/nodeid
|
|
17
|
+
const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
|
|
18
|
+
const j0 = r0.json ?? {};
|
|
19
|
+
const p0 = j0.params ?? {};
|
|
20
|
+
const sp0 = j0.submitParams ?? {};
|
|
21
|
+
const wfId = String(p0.workflowid ?? '');
|
|
22
|
+
const nodeId = String(p0.nodeid ?? '');
|
|
23
|
+
// 第二步 loadForm(带 wf+node)→ 拿 per-request 签名
|
|
24
|
+
let p = p0;
|
|
25
|
+
let sp = sp0;
|
|
26
|
+
if (wfId && nodeId) {
|
|
27
|
+
const r1 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId, workflowid: wfId, nodeid: nodeId });
|
|
28
|
+
const j1 = r1.json ?? {};
|
|
29
|
+
p = j1.params ?? p0;
|
|
30
|
+
sp = j1.submitParams ?? sp0;
|
|
31
|
+
}
|
|
32
|
+
const userId = String(p.f_weaver_belongto_userid ?? '1');
|
|
33
|
+
// submit_token:loadForm 的 submitParams 对部分待办不返回 token(实跑发现 node 5967 待办没有),
|
|
34
|
+
// E9 校验的是"客户端现生成的较新时间戳"防重复提交;故用 Date.now() 现生成,兜底 loadForm 值。
|
|
35
|
+
const submitTokenKey = `${userId}_${requestId}_request_submit_token`;
|
|
36
|
+
const submitToken = String(sp[submitTokenKey] ?? sp[`1_${requestId}_request_submit_token`] ?? Date.now());
|
|
37
|
+
// 构造提交体
|
|
38
|
+
// 从 signatureAttributesStr 解码提取 lastOperateDate 等运行时字段
|
|
39
|
+
const sigDecoded = Buffer.from(String(p.signatureAttributesStr ?? ''), 'base64').toString('utf8');
|
|
40
|
+
const lastOpDate = (sigDecoded.match(/lastOperateDate=([^|]*)/)?.[1] ?? '').trim();
|
|
41
|
+
const lastOpTime = (sigDecoded.match(/lastOperateTime=([^|]*)/)?.[1] ?? '').trim();
|
|
42
|
+
const todayStrVal = todayStr();
|
|
43
|
+
const timeStr = nowTimeStr();
|
|
44
|
+
const body = {
|
|
45
|
+
formid: String(sp.formid ?? p.formid ?? ''),
|
|
46
|
+
isSubmitDirectNode: '',
|
|
47
|
+
openByDefaultBrowser: '',
|
|
48
|
+
iscreate: '0',
|
|
49
|
+
creatertype: '0',
|
|
50
|
+
isdialog: '1',
|
|
51
|
+
lastOperateDate: lastOpDate,
|
|
52
|
+
createdoc: '',
|
|
53
|
+
nodeid: nodeId,
|
|
54
|
+
workflowid: wfId,
|
|
55
|
+
isbill: String(sp.isbill ?? p.isbill ?? '1'),
|
|
56
|
+
authStr: String(p.authStr ?? ''),
|
|
57
|
+
'f_weaver_belongto_userid': userId,
|
|
58
|
+
currenttime: timeStr,
|
|
59
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
60
|
+
agentorByAgentId: '-1',
|
|
61
|
+
isMultiDoc: '',
|
|
62
|
+
inputcheck: '',
|
|
63
|
+
comemessage: '',
|
|
64
|
+
lastOperateTime: lastOpTime,
|
|
65
|
+
temphasUseTempletSucceed: '',
|
|
66
|
+
workflowRequestLogId: '',
|
|
67
|
+
edesign_layout: '',
|
|
68
|
+
[submitTokenKey]: submitToken,
|
|
69
|
+
requestid: requestId,
|
|
70
|
+
isremark: '0',
|
|
71
|
+
creater: String(p.creater ?? userId),
|
|
72
|
+
htmlfieldids: '',
|
|
73
|
+
SubmitToNodeid: '',
|
|
74
|
+
isCptwf: 'false',
|
|
75
|
+
isovertime: '',
|
|
76
|
+
agentType: '0',
|
|
77
|
+
authSignatureStr: String(p.authSignatureStr ?? ''),
|
|
78
|
+
nodetype: String(p.nodetype ?? sp.nodetype ?? '0'),
|
|
79
|
+
needoutprint: '',
|
|
80
|
+
lastOperator: String(sp.lastOperator ?? p.lastOperator ?? userId),
|
|
81
|
+
topage: '',
|
|
82
|
+
isFormSignature: '0',
|
|
83
|
+
remindTypes: '',
|
|
84
|
+
fromFlowDoc: '',
|
|
85
|
+
RejectNodes: '',
|
|
86
|
+
linkageUUID: String(sp.linkageUUID ?? ''),
|
|
87
|
+
billid: String(sp.billid ?? ''),
|
|
88
|
+
lastnodeid: '',
|
|
89
|
+
uploadType: '',
|
|
90
|
+
isSignMustInput: '',
|
|
91
|
+
RejectToNodeid: '',
|
|
92
|
+
isWorkflowDoc: 'false',
|
|
93
|
+
src: 'submit',
|
|
94
|
+
annexmaxUploadImageSize: '',
|
|
95
|
+
takisremark: '0',
|
|
96
|
+
workflowtype: String(sp.workflowtype ?? p.workflowtype ?? ''),
|
|
97
|
+
remarkLocation: '',
|
|
98
|
+
needcheck: String(sp.needcheck ?? ''),
|
|
99
|
+
needcheckLock: String(sp.needcheckLock ?? 'false'),
|
|
100
|
+
selectfieldvalue: String(sp.selectfieldvalue ?? ''),
|
|
101
|
+
RejectToType: '',
|
|
102
|
+
currentdate: todayStrVal,
|
|
103
|
+
// ★ 0.5.1 修复:submit 必须带 needwfback。E9 bundle 实证:
|
|
104
|
+
// doSubmitBack()→needwfback:"1"(提交后流程退回当前节点)、doSubmitNoBack()→needwfback:"0"(正常流转)。
|
|
105
|
+
// 旧 body 缺此字段时服务端把"提交"当不流转处理(停在原节点)。
|
|
106
|
+
// 取 loadForm 回传值兜底 '0'(默认不回退=正常流转到下一节点)。
|
|
107
|
+
needwfback: String(sp.needwfback === undefined || sp.needwfback === null ? '0' : sp.needwfback),
|
|
108
|
+
remark: remarkHtml,
|
|
109
|
+
'field-annexupload': '',
|
|
110
|
+
signdocids: '',
|
|
111
|
+
signworkflowids: '',
|
|
112
|
+
annexdocids: '',
|
|
113
|
+
annexdocinfos: '',
|
|
114
|
+
handWrittenSign: '',
|
|
115
|
+
isOdocRequest: '0',
|
|
116
|
+
enableIntervenor: '',
|
|
117
|
+
verifyRequiredRange: 'field-9999,',
|
|
118
|
+
linkageUnFinishedKey: '',
|
|
119
|
+
remarkquote: remarkHtml,
|
|
120
|
+
actiontype: 'requestOperation',
|
|
121
|
+
isFirstSubmit: '0',
|
|
122
|
+
existChangeRange: '',
|
|
123
|
+
requestname: String(p.requestname ?? ''),
|
|
124
|
+
requestlevel: '0',
|
|
125
|
+
chatsType: '-1',
|
|
126
|
+
messageType: '-1',
|
|
127
|
+
'field-9': '0',
|
|
128
|
+
'field-10': '4',
|
|
129
|
+
mainFieldUnEmptyCount: '0',
|
|
130
|
+
detailFieldUnEmptyCount: '0',
|
|
131
|
+
signatureAttributesStr: String(p.signatureAttributesStr ?? ''),
|
|
132
|
+
signatureSecretKey: String(p.signatureSecretKey ?? ''),
|
|
133
|
+
selectNextFlow: '',
|
|
134
|
+
openDataVerify: '0',
|
|
135
|
+
wfTestStr: '',
|
|
136
|
+
};
|
|
137
|
+
// 修改字段值(可选)
|
|
138
|
+
if (opts.fieldValues) {
|
|
139
|
+
let count = 0;
|
|
140
|
+
for (const [k, v] of Object.entries(opts.fieldValues)) {
|
|
141
|
+
const id = k.startsWith('field') ? k.replace(/^field/, '') : k;
|
|
142
|
+
body[`field${id}`] = v;
|
|
143
|
+
count++;
|
|
144
|
+
}
|
|
145
|
+
body.mainFieldUnEmptyCount = String(count);
|
|
146
|
+
}
|
|
147
|
+
return api.post('/api/workflow/reqform/requestOperation', body);
|
|
148
|
+
}
|
|
149
|
+
async function readMineListEvidence(cfg, requestId, before = {}) {
|
|
150
|
+
const query = {
|
|
151
|
+
category: 'mine', page: 1,
|
|
152
|
+
workflow: String(before.workflowid ?? ''),
|
|
153
|
+
keyword: String(before.requestname ?? ''),
|
|
154
|
+
};
|
|
155
|
+
const first = await getWfList(cfg, query);
|
|
156
|
+
let found = first.items.find((item) => String(item.requestId) === String(requestId));
|
|
157
|
+
const pageSize = Number(first.pageSize) || first.items.length || 50;
|
|
158
|
+
const pages = Math.ceil((Number(first.total) || 0) / pageSize);
|
|
159
|
+
if (pages > 50)
|
|
160
|
+
return { complete: false, found: Boolean(found), pagesChecked: 1, reason: `我的请求筛选结果仍有 ${pages} 页,超过 50 页核验上限` };
|
|
161
|
+
for (let page = 2; !found && page <= pages; page++) {
|
|
162
|
+
const next = await getWfList(cfg, { ...query, page });
|
|
163
|
+
found = next.items.find((item) => String(item.requestId) === String(requestId));
|
|
164
|
+
}
|
|
165
|
+
return { complete: true, found: Boolean(found), item: found, pagesChecked: Math.max(1, pages) };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 撤回流程(0.5.1)。实证(2026-09-08 .27):
|
|
169
|
+
* E9 前端 OPER_WITHDRAW 走 POST /api/workflow/reqform/requestWithdraw(actionType=getSettings→submit),
|
|
170
|
+
* 返回 {"result":"success","msg":"撤回成功"},流程撤回到 recallNodeId 节点。
|
|
171
|
+
* - 审批人"撤回批准":3469387 批准后 getSettings 得 recallNodeId=5968(审批2)、allowWithdrawalType=1,
|
|
172
|
+
* submit 后流程回到 5968、重进待办、流转日志记"撤回1"。
|
|
173
|
+
* - 创建节点单据同样可用(3469383 getSettings 也返回 allowWithdrawalType=1)。
|
|
174
|
+
* ⚠ 旧实现(delRequestVerify+src=delete / functionLink flag=cancel)与前端不符已弃用——
|
|
175
|
+
* 已批准单据 currentnodeid 可能回到发起节点值,旧逻辑会误走删除路径。
|
|
176
|
+
* ⚠ flag=cancel(doCancel=撤销当前流程)与 flag=rb(doRetract=强制收回)实测都不是"撤回批准"。
|
|
177
|
+
*/
|
|
178
|
+
export async function retractRequest(cfg, requestId, opts = {}) {
|
|
179
|
+
const remark = opts.remark ?? '撤回';
|
|
180
|
+
const api = await createApiFromProfile(cfg);
|
|
181
|
+
let p = {};
|
|
182
|
+
let sp = {};
|
|
183
|
+
let userId = '';
|
|
184
|
+
try {
|
|
185
|
+
const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
|
|
186
|
+
p = r0.json?.params ?? {};
|
|
187
|
+
sp = r0.json?.submitParams ?? {};
|
|
188
|
+
userId = String(p.f_weaver_belongto_userid ?? '');
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return { ok: false, verified: false, mode: 'unknown', message: `无法读取流程当前状态:${error.message}` };
|
|
192
|
+
}
|
|
193
|
+
if (!userId)
|
|
194
|
+
return { ok: false, verified: false, mode: 'unknown', message: '流程表单未返回当前用户 ID,已拒绝使用硬编码账号执行撤回。' };
|
|
195
|
+
const sigDecoded = Buffer.from(String(p.signatureAttributesStr ?? ''), 'base64').toString('utf8');
|
|
196
|
+
const lastOpDate = (sigDecoded.match(/lastOperateDate=([^|]*)/)?.[1] ?? '').trim();
|
|
197
|
+
const lastOpTime = (sigDecoded.match(/lastOperateTime=([^|]*)/)?.[1] ?? '').trim();
|
|
198
|
+
const submitTokenKey = `${userId}_${requestId}_request_submit_token`;
|
|
199
|
+
// requestWithdraw 的 baseParam(对齐前端 baseParamRange + 签名 + submit_token)
|
|
200
|
+
const base = {
|
|
201
|
+
requestid: String(requestId),
|
|
202
|
+
workflowid: String(p.workflowid ?? ''),
|
|
203
|
+
nodeid: String(p.nodeid ?? ''),
|
|
204
|
+
formid: String(sp.formid ?? p.formid ?? ''),
|
|
205
|
+
isbill: String(sp.isbill ?? p.isbill ?? '1'),
|
|
206
|
+
'f_weaver_belongto_userid': userId,
|
|
207
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
208
|
+
languageid: '7',
|
|
209
|
+
authStr: String(p.authStr ?? ''),
|
|
210
|
+
authSignatureStr: String(p.authSignatureStr ?? ''),
|
|
211
|
+
signatureSecretKey: String(p.signatureSecretKey ?? ''),
|
|
212
|
+
signatureAttributesStr: String(p.signatureAttributesStr ?? ''),
|
|
213
|
+
[submitTokenKey]: String(sp[submitTokenKey] ?? Date.now()),
|
|
214
|
+
};
|
|
215
|
+
// ① getSettings:取撤回目标节点与权限
|
|
216
|
+
let settings = {};
|
|
217
|
+
let getResp;
|
|
218
|
+
try {
|
|
219
|
+
getResp = await api.post('/api/workflow/reqform/requestWithdraw', { ...base, actionType: 'getSettings' });
|
|
220
|
+
settings = getResp.json ?? {};
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
return { ok: false, verified: false, mode: 'withdraw', message: `撤回前检查失败(requestWithdraw/getSettings):${error.message}` };
|
|
224
|
+
}
|
|
225
|
+
if (String(settings.allowWithdrawalType ?? '') !== '1') {
|
|
226
|
+
return { ok: false, verified: false, mode: 'withdraw', response: getResp, settings,
|
|
227
|
+
message: `OA 不允许撤回当前流程(allowWithdrawalType=${settings.allowWithdrawalType})。${settings.msg ?? ''}` };
|
|
228
|
+
}
|
|
229
|
+
// ② submit:执行撤回(remark 必填;recallNodeId/doPreOperate 来自 getSettings)
|
|
230
|
+
const body = {
|
|
231
|
+
...base,
|
|
232
|
+
actionType: 'submit',
|
|
233
|
+
isRemind: '1',
|
|
234
|
+
remark,
|
|
235
|
+
recallNodeId: String(settings.recallNodeId ?? ''),
|
|
236
|
+
doPreOperate: String(settings.doPreOperate ?? ''),
|
|
237
|
+
lastOperateDate: String(sp.lastOperateDate ?? lastOpDate ?? ''),
|
|
238
|
+
lastOperateTime: String(sp.lastOperateTime ?? lastOpTime ?? ''),
|
|
239
|
+
currentdate: todayStr(),
|
|
240
|
+
currenttime: nowTimeStr(),
|
|
241
|
+
};
|
|
242
|
+
let response;
|
|
243
|
+
try {
|
|
244
|
+
response = await api.post('/api/workflow/reqform/requestWithdraw', body);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
return { ok: false, verified: false, mode: 'withdraw', settings, message: `撤回请求失败:${error.message}` };
|
|
248
|
+
}
|
|
249
|
+
const wj = response.json ?? {};
|
|
250
|
+
const bizSuccess = wj.result === 'success' || wj.result === '1' || wj.result === true || wj.success === true;
|
|
251
|
+
const recallNodeId = String(settings.recallNodeId ?? '');
|
|
252
|
+
// ③ 回读核验:流程应回到 recallNodeId(loadForm currentnodeid),且"我的请求"回读可见
|
|
253
|
+
try {
|
|
254
|
+
const afterResponse = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
|
|
255
|
+
const afterParams = afterResponse.json?.params ?? {};
|
|
256
|
+
const afterNodeId = String(afterParams.currentnodeid ?? afterParams.nodeid ?? '');
|
|
257
|
+
let mineList;
|
|
258
|
+
try { mineList = await readMineListEvidence(cfg, requestId, p); }
|
|
259
|
+
catch (error) { mineList = { complete: false, found: null, reason: error.message }; }
|
|
260
|
+
const returnedToRecall = !!recallNodeId && (afterNodeId === recallNodeId || afterNodeId === String(p.nodeid ?? ''));
|
|
261
|
+
const listVerified = mineList.complete && (bizSuccess ? mineList.found === true : mineList.found === false);
|
|
262
|
+
const verified = bizSuccess && (returnedToRecall || listVerified);
|
|
263
|
+
return {
|
|
264
|
+
ok: verified, verified, mode: 'withdraw', response, settings,
|
|
265
|
+
recallNodeId, afterNodeId,
|
|
266
|
+
mineList,
|
|
267
|
+
message: verified
|
|
268
|
+
? `撤回已核验:流程已撤回到 ${settings.recallNodeName ?? recallNodeId} 节点。`
|
|
269
|
+
: bizSuccess
|
|
270
|
+
? 'OA 已返回撤回成功,但回读未确认流程回到撤回节点,结果待人工核对。'
|
|
271
|
+
: `OA 未确认撤回成功:${wj.msg ?? response.text?.slice(0, 160) ?? ''}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
return { ok: false, verified: false, mode: 'withdraw', response, settings, message: `OA 已受理撤回,但回读核验失败:${error.message}` };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
import { resolveReceivers } from './wfreceiver.js';
|
|
3
|
+
/**
|
|
4
|
+
* 转办(处理权转移)。
|
|
5
|
+
*
|
|
6
|
+
* 链路(前端 bundle 实证):doReview3 → A({showForward:!0, forwardflag:"3"}),与转发/传阅/意见征询
|
|
7
|
+
* 打开同一个 ForwardDialog,提交统一走 signInput → remarkOperate:
|
|
8
|
+
* loadForm(取 workflowid/nodeid/操作人 userid) → signInput(forwardflag=3 预载) →
|
|
9
|
+
* remarkOperate(operate=save, forwardflag=3, field5=接收人) → {"success":true,"message":"流程转办成功"}。
|
|
10
|
+
*
|
|
11
|
+
* 修复(0.5.1):旧实现走 requestOperation(src=submit, needwfback=1, forwardOperators=接收人)
|
|
12
|
+
* 且不带 forwardflag,被服务端当成"批准"。前端 doReview3 是 forwardflag="3"(非 requestOperation),
|
|
13
|
+
* 成功提示即"流程转办成功"。转办不需要改表单经办人字段——field5 即新办理人,由服务端处理。
|
|
14
|
+
* 参考 ForwardDialog 实现:doReview3 无 needwfback(转办并退回 doReviewback3 才带 needwfback=1)。
|
|
15
|
+
*/
|
|
16
|
+
export async function transferTodo(cfg, opts) {
|
|
17
|
+
const { requestId } = opts;
|
|
18
|
+
const remarkText = opts.remark ?? '';
|
|
19
|
+
const remarkHtml = remarkText.split(/\r?\n/).map((l) => `<p>${l}</p>`).join('');
|
|
20
|
+
const api = await createApiFromProfile(cfg);
|
|
21
|
+
const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: String(requestId) });
|
|
22
|
+
const p = (r0.json ?? {}).params ?? {};
|
|
23
|
+
const userId = String(p.f_weaver_belongto_userid ?? '1');
|
|
24
|
+
const userType = String(p.f_weaver_belongto_usertype ?? '0');
|
|
25
|
+
const workflowid = String(p.workflowid ?? '');
|
|
26
|
+
const nodeid = String(p.nodeid ?? '');
|
|
27
|
+
const field5 = await resolveReceivers(cfg, opts);
|
|
28
|
+
if (!field5)
|
|
29
|
+
throw new Error('转办需要目标人:传 --receiver <userId> 或 --receiver-name <姓名>');
|
|
30
|
+
const tokenKey = `forward_token_${requestId}_${userId}`;
|
|
31
|
+
const token = String(Date.now());
|
|
32
|
+
const common = {
|
|
33
|
+
isShowReplyNotice: '0', takOverMins: '30', remark: remarkHtml,
|
|
34
|
+
workflowRequestLogId: '', forwardflag: '3', requestid: String(requestId),
|
|
35
|
+
nodeid, workflowid, 'f_weaver_belongto_userid': userId, 'f_weaver_belongto_usertype': userType,
|
|
36
|
+
agentType: '0', agentorByAgentId: '-1', secLevelInfo: '[object Object]',
|
|
37
|
+
[tokenKey]: token,
|
|
38
|
+
};
|
|
39
|
+
// ① signInput 预载(forwardflag=3,field5 空)
|
|
40
|
+
await api.post('/api/workflow/reqform/signInput', {
|
|
41
|
+
...common, field5: '', IsSubmitedOpinion: '1', IsBeForwardTodo: '1',
|
|
42
|
+
});
|
|
43
|
+
// ② remarkOperate 提交(operate=save,field5=接收人)
|
|
44
|
+
return api.post('/api/workflow/reqform/remarkOperate', {
|
|
45
|
+
...common, field5,
|
|
46
|
+
operate: 'save',
|
|
47
|
+
openByDefaultBrowser: '', IsBeForwardSubmitAlready: '', IsBeForwardAlready: '',
|
|
48
|
+
IsSubmitedOpinion: '', IsBeForwardTodo: '',
|
|
49
|
+
isSubmitSign: '0', isChuanyue: '0', isRemind: '0',
|
|
50
|
+
signworkflowids: '', signdocids: '', remarkLocation: '',
|
|
51
|
+
'field-annexupload': '', 'field-annexupload-request': String(requestId),
|
|
52
|
+
handWrittenSign: '0', remindTypes: '',
|
|
53
|
+
});
|
|
54
|
+
}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用工具:日期字符串、sleep、字符串化。
|
|
3
|
+
* 统一各模块里重复的 today()/todayStr()/stringify()/sleep()。
|
|
4
|
+
*/
|
|
5
|
+
/** 今天 YYYY-MM-DD(本地时区,避免 UTC 偏移)。 */
|
|
6
|
+
export function todayStr() {
|
|
7
|
+
const d = new Date();
|
|
8
|
+
const tz = d.getTimezoneOffset() * 60000;
|
|
9
|
+
return new Date(d.getTime() - tz).toISOString().slice(0, 10);
|
|
10
|
+
}
|
|
11
|
+
/** 当前本地时间 HH:MM:SS(提交体里的 currenttime 字段用)。 */
|
|
12
|
+
export function nowTimeStr() {
|
|
13
|
+
const d = new Date();
|
|
14
|
+
const tz = d.getTimezoneOffset() * 60000;
|
|
15
|
+
return new Date(d.getTime() - tz).toISOString().slice(11, 19);
|
|
16
|
+
}
|
|
17
|
+
export function sleep(ms) {
|
|
18
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
19
|
+
}
|
|
20
|
+
/** 非空字符串化:null/undefined/'' → undefined,否则 String(v)。 */
|
|
21
|
+
export function stringify(v) {
|
|
22
|
+
if (v === undefined || v === null || v === '')
|
|
23
|
+
return undefined;
|
|
24
|
+
return String(v);
|
|
25
|
+
}
|