bdip-office-assistant-cli 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +37 -2
  2. package/dist/accounts.js +30 -0
  3. package/dist/args.js +36 -0
  4. package/dist/auth-token.js +783 -0
  5. package/dist/auth.js +331 -0
  6. package/dist/blog.js +562 -0
  7. package/dist/browser-links.js +224 -0
  8. package/dist/captcha-display.js +30 -0
  9. package/dist/cli.js +5533 -0
  10. package/dist/client.js +254 -0
  11. package/dist/config.js +170 -0
  12. package/dist/createcontext.js +41 -0
  13. package/dist/crypto.js +139 -0
  14. package/dist/cube.js +1771 -0
  15. package/dist/documents.js +98 -0
  16. package/dist/email.js +841 -0
  17. package/dist/field-preparation.js +237 -0
  18. package/dist/fileutil.js +20 -0
  19. package/dist/formula-runtime.js +55 -0
  20. package/dist/hrm.js +478 -0
  21. package/dist/keepalive.js +99 -0
  22. package/dist/kq.js +280 -0
  23. package/dist/linkage-calculations.js +68 -0
  24. package/dist/links.js +34 -0
  25. package/dist/meetings.js +172 -0
  26. package/dist/nodemap.js +242 -0
  27. package/dist/odoc.js +199 -0
  28. package/dist/office-tools.js +155 -0
  29. package/dist/pageopen.js +29 -0
  30. package/dist/parse.js +177 -0
  31. package/dist/password-input.js +84 -0
  32. package/dist/password-login.js +208 -0
  33. package/dist/person-selection.js +81 -0
  34. package/dist/querystate.js +38 -0
  35. package/dist/render.js +21 -0
  36. package/dist/report.js +50 -0
  37. package/dist/session-worker.js +54 -0
  38. package/dist/session.js +280 -0
  39. package/dist/submissionplan.js +45 -0
  40. package/dist/submissionstate.js +81 -0
  41. package/dist/subordinate.js +458 -0
  42. package/dist/supervise.js +152 -0
  43. package/dist/todoaskidea.js +54 -0
  44. package/dist/todobatch.js +172 -0
  45. package/dist/todocc.js +128 -0
  46. package/dist/todoforward.js +66 -0
  47. package/dist/todoreject.js +215 -0
  48. package/dist/todosubmit.js +325 -0
  49. package/dist/todotransfer.js +54 -0
  50. package/dist/util.js +25 -0
  51. package/dist/wfagent.js +538 -0
  52. package/dist/wfcreate.js +102 -0
  53. package/dist/wfform.js +169 -0
  54. package/dist/wfops.js +40 -0
  55. package/dist/wfreceiver.js +43 -0
  56. package/dist/wfstart.js +558 -0
  57. package/dist/wfsummary.js +89 -0
  58. package/dist/wfupload.js +83 -0
  59. package/dist/wfuser.js +96 -0
  60. package/dist/workflow-runtime.js +177 -0
  61. package/dist/workflow.js +570 -0
  62. package/dist/workplan.js +431 -0
  63. package/package.json +29 -6
@@ -0,0 +1,325 @@
1
+ import { createApiFromProfile } from './client.js';
2
+ import { getWfList } from './workflow.js';
3
+ import { todayStr, nowTimeStr } from './util.js';
4
+ import { findNonEditableFields } from './wfstart.js';
5
+
6
+ /** viewattr 中文标签(报错文案用)。 */
7
+ const VIEWATTR_LABEL_LOCAL = { 1: '只读', 2: '可编辑', 3: '必填', 4: '隐藏' };
8
+
9
+ /**
10
+ * 只做「当前节点字段属性」校验,不提交——供 CLI dry-run 提前拦截,避免"预览通过、提交才报错"。
11
+ * 返回被违规填值的只读/隐藏字段清单(空数组 = 通过)。
12
+ */
13
+ export async function validateFieldValuesAtNode(cfg, requestId, fieldValues) {
14
+ if (!fieldValues || !Object.keys(fieldValues).length)
15
+ return [];
16
+ const api = await createApiFromProfile(cfg);
17
+ const r = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
18
+ return findNonEditableFields({ tables: nodeFieldTables(r.json ?? {}) }, fieldValues);
19
+ }
20
+
21
+ /**
22
+ * 从 loadForm 响应的 tableInfo 抽出字段定义,供 findNonEditableFields 判定。
23
+ * ⚠️ 这里用的是 **iscreate=0(处理态)** 的属性——即**当前节点**的字段权限,
24
+ * 而非 create 路径的表单设计态(iscreate=1)。
25
+ */
26
+ function nodeFieldTables(json) {
27
+ const tables = [];
28
+ for (const key of Object.keys(json?.tableInfo ?? {})) {
29
+ const fmap = json.tableInfo[key]?.fieldinfomap;
30
+ if (!fmap || typeof fmap !== 'object')
31
+ continue;
32
+ tables.push({
33
+ key,
34
+ fields: Object.values(fmap).map((f) => ({
35
+ id: String(f?.fieldid ?? ''),
36
+ label: String(f?.fieldlabel ?? ''),
37
+ viewattr: (f?.viewattr === undefined || f?.viewattr === null || f?.viewattr === '') ? null : Number(f.viewattr),
38
+ })),
39
+ });
40
+ }
41
+ return tables;
42
+ }
43
+ /**
44
+ * 提交待办到下一节点。POST requestOperation(iscreate=0)。
45
+ * 两步 loadForm 拿签名(先取 wf+node,再带 wf+node 取 per-request 签名)。
46
+ *
47
+ * 修复(P0-5):submit_token key 旧硬编码 `1_<rid>_...`(前缀=1=sysadmin),非 sysadmin 取不到 token;
48
+ * 改用 `p.f_weaver_belongto_userid` 动态拼 key,并兜底 `1_` 旧前缀,与 wfstart.js create 路径一致。
49
+ */
50
+ export async function submitTodo(cfg, opts) {
51
+ const { requestId } = opts;
52
+ const remarkText = opts.remark ?? 'OK';
53
+ const remarkHtml = remarkText.split(/\r?\n/).map((l) => `<p>${l}</p>`).join('');
54
+ const api = await createApiFromProfile(cfg);
55
+ // 第一步 loadForm(不带 wf+node)→ 拿 workflowid/nodeid
56
+ const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
57
+ const j0 = r0.json ?? {};
58
+ const p0 = j0.params ?? {};
59
+ const sp0 = j0.submitParams ?? {};
60
+ const wfId = String(p0.workflowid ?? '');
61
+ const nodeId = String(p0.nodeid ?? '');
62
+ if(String(p0.currentnodetype??p0.nodetype)==='0')throw Error('创建节点必须使用 bdip office draft-update-preview 和 draft-submit --confirm 完成统一联动预览后提交');
63
+ // ★字段属性硬规则(当前节点):--field-values 只允许改该节点「可编辑/必填」(viewattr=2/3) 的字段。
64
+ // 依据本次 iscreate=0 的 loadForm(**处理态=当前节点字段权限**),与 create 路径的设计态判定不同。
65
+ // 只读(1)/隐藏(4) 一律拒绝——不得绕过节点字段权限自行填值。
66
+ if (opts.fieldValues && Object.keys(opts.fieldValues).length) {
67
+ const bad = findNonEditableFields({ tables: nodeFieldTables(j0) }, opts.fieldValues);
68
+ if (bad.length)
69
+ throw new Error(`拒绝填写当前节点的只读/隐藏字段:${bad.map((b) => `${b.label || b.id}(${VIEWATTR_LABEL_LOCAL[b.viewattr] ?? 'va=' + b.viewattr})`).join('、')}。本节点只允许修改「可编辑」字段,不得绕过节点字段权限自行填值。`);
70
+ }
71
+ // 第二步 loadForm(带 wf+node)→ 拿 per-request 签名
72
+ let p = p0;
73
+ let sp = sp0;
74
+ if (wfId && nodeId) {
75
+ const r1 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId, workflowid: wfId, nodeid: nodeId });
76
+ const j1 = r1.json ?? {};
77
+ p = j1.params ?? p0;
78
+ sp = j1.submitParams ?? sp0;
79
+ }
80
+ const userId = String(p.f_weaver_belongto_userid ?? '1');
81
+ // submit_token:loadForm 的 submitParams 对部分待办不返回 token(实跑发现 node 5967 待办没有),
82
+ // BDIP5 校验的是"客户端现生成的较新时间戳"防重复提交;故用 Date.now() 现生成,兜底 loadForm 值。
83
+ const submitTokenKey = `${userId}_${requestId}_request_submit_token`;
84
+ const submitToken = String(sp[submitTokenKey] ?? sp[`1_${requestId}_request_submit_token`] ?? Date.now());
85
+ // 构造提交体
86
+ // 从 signatureAttributesStr 解码提取 lastOperateDate 等运行时字段
87
+ const sigDecoded = Buffer.from(String(p.signatureAttributesStr ?? ''), 'base64').toString('utf8');
88
+ const lastOpDate = (sigDecoded.match(/lastOperateDate=([^|]*)/)?.[1] ?? '').trim();
89
+ const lastOpTime = (sigDecoded.match(/lastOperateTime=([^|]*)/)?.[1] ?? '').trim();
90
+ const todayStrVal = todayStr();
91
+ const timeStr = nowTimeStr();
92
+ const body = {
93
+ formid: String(sp.formid ?? p.formid ?? ''),
94
+ isSubmitDirectNode: '',
95
+ openByDefaultBrowser: '',
96
+ iscreate: '0',
97
+ creatertype: '0',
98
+ isdialog: '1',
99
+ lastOperateDate: lastOpDate,
100
+ createdoc: '',
101
+ nodeid: nodeId,
102
+ workflowid: wfId,
103
+ isbill: String(sp.isbill ?? p.isbill ?? '1'),
104
+ authStr: String(p.authStr ?? ''),
105
+ 'f_weaver_belongto_userid': userId,
106
+ currenttime: timeStr,
107
+ 'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
108
+ agentorByAgentId: '-1',
109
+ isMultiDoc: '',
110
+ inputcheck: '',
111
+ comemessage: '',
112
+ lastOperateTime: lastOpTime,
113
+ temphasUseTempletSucceed: '',
114
+ workflowRequestLogId: '',
115
+ edesign_layout: '',
116
+ [submitTokenKey]: submitToken,
117
+ requestid: requestId,
118
+ isremark: '0',
119
+ creater: String(p.creater ?? userId),
120
+ htmlfieldids: '',
121
+ SubmitToNodeid: '',
122
+ isCptwf: 'false',
123
+ isovertime: '',
124
+ agentType: '0',
125
+ authSignatureStr: String(p.authSignatureStr ?? ''),
126
+ nodetype: String(p.nodetype ?? sp.nodetype ?? '0'),
127
+ needoutprint: '',
128
+ lastOperator: String(sp.lastOperator ?? p.lastOperator ?? userId),
129
+ topage: '',
130
+ isFormSignature: '0',
131
+ remindTypes: '',
132
+ fromFlowDoc: '',
133
+ RejectNodes: '',
134
+ linkageUUID: String(sp.linkageUUID ?? ''),
135
+ billid: String(sp.billid ?? ''),
136
+ lastnodeid: '',
137
+ uploadType: '',
138
+ isSignMustInput: '',
139
+ RejectToNodeid: '',
140
+ isWorkflowDoc: 'false',
141
+ src: 'submit',
142
+ annexmaxUploadImageSize: '',
143
+ takisremark: '0',
144
+ workflowtype: String(sp.workflowtype ?? p.workflowtype ?? ''),
145
+ remarkLocation: '',
146
+ needcheck: String(sp.needcheck ?? ''),
147
+ needcheckLock: String(sp.needcheckLock ?? 'false'),
148
+ selectfieldvalue: String(sp.selectfieldvalue ?? ''),
149
+ RejectToType: '',
150
+ currentdate: todayStrVal,
151
+ // ★ 0.5.1 修复:submit 必须带 needwfback。BDIP5 bundle 实证:
152
+ // doSubmitBack()→needwfback:"1"(提交后流程退回当前节点)、doSubmitNoBack()→needwfback:"0"(正常流转)。
153
+ // 旧 body 缺此字段时服务端把"提交"当不流转处理(停在原节点)。
154
+ // 取 loadForm 回传值兜底 '0'(默认不回退=正常流转到下一节点)。
155
+ needwfback: String(sp.needwfback === undefined || sp.needwfback === null ? '0' : sp.needwfback),
156
+ remark: remarkHtml,
157
+ 'field-annexupload': '',
158
+ signdocids: '',
159
+ signworkflowids: '',
160
+ annexdocids: '',
161
+ annexdocinfos: '',
162
+ handWrittenSign: '',
163
+ isOdocRequest: '0',
164
+ enableIntervenor: '',
165
+ verifyRequiredRange: 'field-9999,',
166
+ linkageUnFinishedKey: '',
167
+ remarkquote: remarkHtml,
168
+ actiontype: 'requestOperation',
169
+ isFirstSubmit: '0',
170
+ existChangeRange: '',
171
+ requestname: String(p.requestname ?? ''),
172
+ requestlevel: '0',
173
+ chatsType: '-1',
174
+ messageType: '-1',
175
+ 'field-9': '0',
176
+ 'field-10': '4',
177
+ mainFieldUnEmptyCount: '0',
178
+ detailFieldUnEmptyCount: '0',
179
+ signatureAttributesStr: String(p.signatureAttributesStr ?? ''),
180
+ signatureSecretKey: String(p.signatureSecretKey ?? ''),
181
+ selectNextFlow: '',
182
+ openDataVerify: '0',
183
+ wfTestStr: '',
184
+ };
185
+ // 修改字段值(可选)
186
+ if (opts.fieldValues) {
187
+ let count = 0;
188
+ for (const [k, v] of Object.entries(opts.fieldValues)) {
189
+ const id = k.startsWith('field') ? k.replace(/^field/, '') : k;
190
+ body[`field${id}`] = v;
191
+ count++;
192
+ }
193
+ body.mainFieldUnEmptyCount = String(count);
194
+ }
195
+ return api.post('/api/workflow/reqform/requestOperation', body);
196
+ }
197
+ async function readMineListEvidence(cfg, requestId, before = {}) {
198
+ const query = {
199
+ category: 'mine', page: 1,
200
+ workflow: String(before.workflowid ?? ''),
201
+ keyword: String(before.requestname ?? ''),
202
+ };
203
+ const first = await getWfList(cfg, query);
204
+ let found = first.items.find((item) => String(item.requestId) === String(requestId));
205
+ const pageSize = Number(first.pageSize) || first.items.length || 50;
206
+ const pages = Math.ceil((Number(first.total) || 0) / pageSize);
207
+ if (pages > 50)
208
+ return { complete: false, found: Boolean(found), pagesChecked: 1, reason: `我的请求筛选结果仍有 ${pages} 页,超过 50 页核验上限` };
209
+ for (let page = 2; !found && page <= pages; page++) {
210
+ const next = await getWfList(cfg, { ...query, page });
211
+ found = next.items.find((item) => String(item.requestId) === String(requestId));
212
+ }
213
+ return { complete: true, found: Boolean(found), item: found, pagesChecked: Math.max(1, pages) };
214
+ }
215
+ /**
216
+ * 撤回流程(0.5.1)。实证(2026-09-08 .27):
217
+ * BDIP5 前端 OPER_WITHDRAW 走 POST /api/workflow/reqform/requestWithdraw(actionType=getSettings→submit),
218
+ * 返回 {"result":"success","msg":"撤回成功"},流程撤回到 recallNodeId 节点。
219
+ * - 审批人"撤回批准":3469387 批准后 getSettings 得 recallNodeId=5968(审批2)、allowWithdrawalType=1,
220
+ * submit 后流程回到 5968、重进待办、流转日志记"撤回1"。
221
+ * - 创建节点单据同样可用(3469383 getSettings 也返回 allowWithdrawalType=1)。
222
+ * ⚠ 旧实现(delRequestVerify+src=delete / functionLink flag=cancel)与前端不符已弃用——
223
+ * 已批准单据 currentnodeid 可能回到发起节点值,旧逻辑会误走删除路径。
224
+ * ⚠ flag=cancel(doCancel=撤销当前流程)与 flag=rb(doRetract=强制收回)实测都不是"撤回批准"。
225
+ */
226
+ export async function retractRequest(cfg, requestId, opts = {}) {
227
+ const remark = opts.remark ?? '撤回';
228
+ const api = await createApiFromProfile(cfg);
229
+ let p = {};
230
+ let sp = {};
231
+ let userId = '';
232
+ try {
233
+ const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
234
+ p = r0.json?.params ?? {};
235
+ sp = r0.json?.submitParams ?? {};
236
+ userId = String(p.f_weaver_belongto_userid ?? '');
237
+ }
238
+ catch (error) {
239
+ return { ok: false, verified: false, mode: 'unknown', message: `无法读取流程当前状态:${error.message}` };
240
+ }
241
+ if (!userId)
242
+ return { ok: false, verified: false, mode: 'unknown', message: '流程表单未返回当前用户 ID,已拒绝使用硬编码账号执行撤回。' };
243
+ const sigDecoded = Buffer.from(String(p.signatureAttributesStr ?? ''), 'base64').toString('utf8');
244
+ const lastOpDate = (sigDecoded.match(/lastOperateDate=([^|]*)/)?.[1] ?? '').trim();
245
+ const lastOpTime = (sigDecoded.match(/lastOperateTime=([^|]*)/)?.[1] ?? '').trim();
246
+ const submitTokenKey = `${userId}_${requestId}_request_submit_token`;
247
+ // requestWithdraw 的 baseParam(对齐前端 baseParamRange + 签名 + submit_token)
248
+ const base = {
249
+ requestid: String(requestId),
250
+ workflowid: String(p.workflowid ?? ''),
251
+ nodeid: String(p.nodeid ?? ''),
252
+ formid: String(sp.formid ?? p.formid ?? ''),
253
+ isbill: String(sp.isbill ?? p.isbill ?? '1'),
254
+ 'f_weaver_belongto_userid': userId,
255
+ 'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
256
+ languageid: '7',
257
+ authStr: String(p.authStr ?? ''),
258
+ authSignatureStr: String(p.authSignatureStr ?? ''),
259
+ signatureSecretKey: String(p.signatureSecretKey ?? ''),
260
+ signatureAttributesStr: String(p.signatureAttributesStr ?? ''),
261
+ [submitTokenKey]: String(sp[submitTokenKey] ?? Date.now()),
262
+ };
263
+ // ① getSettings:取撤回目标节点与权限
264
+ let settings = {};
265
+ let getResp;
266
+ try {
267
+ getResp = await api.post('/api/workflow/reqform/requestWithdraw', { ...base, actionType: 'getSettings' });
268
+ settings = getResp.json ?? {};
269
+ }
270
+ catch (error) {
271
+ return { ok: false, verified: false, mode: 'withdraw', message: `撤回前检查失败(requestWithdraw/getSettings):${error.message}` };
272
+ }
273
+ if (String(settings.allowWithdrawalType ?? '') !== '1') {
274
+ return { ok: false, verified: false, mode: 'withdraw', response: getResp, settings,
275
+ message: `OA 不允许撤回当前流程(allowWithdrawalType=${settings.allowWithdrawalType})。${settings.msg ?? ''}` };
276
+ }
277
+ // ② submit:执行撤回(remark 必填;recallNodeId/doPreOperate 来自 getSettings)
278
+ const body = {
279
+ ...base,
280
+ actionType: 'submit',
281
+ isRemind: '1',
282
+ remark,
283
+ recallNodeId: String(settings.recallNodeId ?? ''),
284
+ doPreOperate: String(settings.doPreOperate ?? ''),
285
+ lastOperateDate: String(sp.lastOperateDate ?? lastOpDate ?? ''),
286
+ lastOperateTime: String(sp.lastOperateTime ?? lastOpTime ?? ''),
287
+ currentdate: todayStr(),
288
+ currenttime: nowTimeStr(),
289
+ };
290
+ let response;
291
+ try {
292
+ response = await api.post('/api/workflow/reqform/requestWithdraw', body);
293
+ }
294
+ catch (error) {
295
+ return { ok: false, verified: false, mode: 'withdraw', settings, message: `撤回请求失败:${error.message}` };
296
+ }
297
+ const wj = response.json ?? {};
298
+ const bizSuccess = wj.result === 'success' || wj.result === '1' || wj.result === true || wj.success === true;
299
+ const recallNodeId = String(settings.recallNodeId ?? '');
300
+ // ③ 回读核验:流程应回到 recallNodeId(loadForm currentnodeid),且"我的请求"回读可见
301
+ try {
302
+ const afterResponse = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
303
+ const afterParams = afterResponse.json?.params ?? {};
304
+ const afterNodeId = String(afterParams.currentnodeid ?? afterParams.nodeid ?? '');
305
+ let mineList;
306
+ try { mineList = await readMineListEvidence(cfg, requestId, p); }
307
+ catch (error) { mineList = { complete: false, found: null, reason: error.message }; }
308
+ const returnedToRecall = !!recallNodeId && (afterNodeId === recallNodeId || afterNodeId === String(p.nodeid ?? ''));
309
+ const listVerified = mineList.complete && (bizSuccess ? mineList.found === true : mineList.found === false);
310
+ const verified = bizSuccess && (returnedToRecall || listVerified);
311
+ return {
312
+ ok: verified, verified, mode: 'withdraw', response, settings,
313
+ recallNodeId, afterNodeId,
314
+ mineList,
315
+ message: verified
316
+ ? `撤回已核验:流程已撤回到 ${settings.recallNodeName ?? recallNodeId} 节点。`
317
+ : bizSuccess
318
+ ? 'OA 已返回撤回成功,但回读未确认流程回到撤回节点,结果待人工核对。'
319
+ : `OA 未确认撤回成功:${wj.msg ?? response.text?.slice(0, 160) ?? ''}`,
320
+ };
321
+ }
322
+ catch (error) {
323
+ return { ok: false, verified: false, mode: 'withdraw', response, settings, message: `OA 已受理撤回,但回读核验失败:${error.message}` };
324
+ }
325
+ }
@@ -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
+ }