weaver-e9-assistant-cli 1.1.4 → 1.1.5
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 +2 -0
- package/dist/cli.js +807 -25
- package/dist/kq.js +65 -0
- package/dist/nodemap.js +242 -0
- package/dist/odoc.js +7 -2
- package/dist/parse.js +4 -0
- package/dist/subordinate.js +25 -0
- package/dist/supervise.js +152 -0
- package/dist/todobatch.js +20 -9
- package/dist/todosubmit.js +47 -0
- package/dist/wfagent.js +538 -0
- package/dist/wfstart.js +5 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import { loadConfig, resolveViewcondition, endpointFor, setConfigField } from './config.js';
|
|
6
|
-
import { getWfList, getOaTabGroups, resolveWorkflowType, CATEGORY_SCOPE } from './workflow.js';
|
|
6
|
+
import { getWfList, getOaTabGroups, resolveWorkflowType, CATEGORY_SCOPE, parseTimeRange } from './workflow.js';
|
|
7
|
+
import { annotateNodeNames, isNodeNameDisabled, resolveNodeByName } from './nodemap.js';
|
|
7
8
|
import { parseIdList, batchSubmit, batchForward, batchAttention, getAttentionTypes } from './todobatch.js';
|
|
8
9
|
import { pageUrl, availablePages } from './pageopen.js';
|
|
9
10
|
import { buildBlogMyUrl, buildCubeCardUrl, buildCubeSearchUrl, normalizeE9Url } from './links.js';
|
|
@@ -12,7 +13,7 @@ import { loadSubmission, updateSubmission } from './submissionstate.js';
|
|
|
12
13
|
import { saveSubmissionPlan, loadSubmissionPlan } from './submissionplan.js';
|
|
13
14
|
import { saveCandidateQuery, loadCandidateQuery, persistSearchResult } from './querystate.js';
|
|
14
15
|
import { getFlowSummary } from './wfsummary.js';
|
|
15
|
-
import { submitTodo, retractRequest } from './todosubmit.js';
|
|
16
|
+
import { submitTodo, retractRequest, validateFieldValuesAtNode } from './todosubmit.js';
|
|
16
17
|
import { rejectTodo } from './todoreject.js';
|
|
17
18
|
import { transferTodo } from './todotransfer.js';
|
|
18
19
|
import { forwardTodo, circulateTodo } from './todoforward.js';
|
|
@@ -20,17 +21,19 @@ import { ccTodo } from './todocc.js';
|
|
|
20
21
|
import { askIdeaTodo } from './todoaskidea.js';
|
|
21
22
|
import { getMyBlogList, getMyBlogAll, isBlogRecalled, createBlog, getBlogWorkIndex, getBlogMenuCountdata, getVisitorList, getBlogNote, getCurrentUserInfo, getMsgRemindList, getDefaultTemplate, getBlogAppList, getAtAllList, getBlogPageSearchCondition, getBlogHomepageList, searchMyBlog, getBlogPersonalInfo, sendRemindUnsubmit, addBlogReply, addOrDeleteZan, saveBlogNote, recallBlog, deleteBlogReply, attentionOpt, dealMsgRemind, getShareToMeUserList } from './blog.js';
|
|
22
23
|
import { getCreateWfList, buildCreateWfUrl, buildRequestFormUrl, findCreateWfByName } from './wfcreate.js';
|
|
23
|
-
import { describeFormFields } from './wfform.js';
|
|
24
|
+
import { describeFormFields, VIEWATTR_LABEL } from './wfform.js';
|
|
24
25
|
import { getStartableFields, prepareCreateSubmission, submitCreateRequest, resolveWorkflowId, getOdocNextNodes, odocFlowNext } from './wfstart.js';
|
|
25
26
|
import { cmdTokenLogin, cmdCookieLogin, cmdAuthStatus } from './auth.js';
|
|
26
27
|
import { getEmailList, getEmailView, setMailReadStatus, resolveDateRange, sendEmail, setEmailStar, deleteEmails, moveEmails, getEmailLabels, addEmailLabel, applyEmailLabel, setEmailWaitdeal, recallEmail, uploadEmailAttachment, getEmailAttachmentList, getEmailSize, getEmailReciveAccounts } from './email.js';
|
|
27
28
|
import { searchWorkplan, createWorkplan, getMyCalendar, getAttentionData, getBaseData, getCalendarData, getSelectType, getWorkplanSearchCondition, viewWorkplan, deleteWorkplan, finishWorkplan, downloadWorkplanImportTemplate, importWorkplanExcel, exportWorkplanExcel } from './workplan.js';
|
|
28
29
|
import { listOdocTodo, getOdocTodoCount, listOdocCreate } from './odoc.js';
|
|
29
|
-
import {
|
|
30
|
+
import { getSuperviseTree, getSuperviseCount, getSuperviseConditions, listSupervise, doSupervise, resolveSuperviseTab, SUPERVISE_TABS } from './supervise.js';
|
|
31
|
+
import { getAgentList, getAgentConditions, listAgentTemplates, getAddAgentInfo, verifyOverlap, saveAgentTemplate, addAgent, takeBackAgent, takeBackAllAgent, getAgentDetail, editAgent, editAgentBatch, getBatchEditInfo, buildAgentBody, resolveView as resolveWfagentView, resolveStatus as resolveWfagentStatus, WFAGENT_VIEWS, WFAGENT_STATUS } from './wfagent.js';
|
|
32
|
+
import { punch, getButtons, resolvePunchItem, signSecret, buildEncryptObj, getKqReport, getKqMonthReport } from './kq.js';
|
|
30
33
|
import { collectQueryableBusiness } from './report.js';
|
|
31
34
|
import { listCube, getCubeCondition, getCubeInit, exportCubeExcel, getSearchColumnInfo, listCubeExcel, getCubeCountData, getCubeGroupData, getExpCardConfig, getModeBrowserOptions, getCardFields, submitCubeCard, deleteCubeData, printCubePdf, listCubeMenus, findCubeByName, fetchMenuMounts, previewCubeCard, fetchCubeRowsByIds, resolveCubeCondition, downloadCubeImportTemplate, importCubeExcel, assertEditableCardValues } from './cube.js';
|
|
32
35
|
import { getCurrentUser, getCurrentUserIsAdmin } from './wfuser.js';
|
|
33
|
-
import { getSubordinates, assertUserIdsInScope, assertNamesInScope, isScopeEnforced } from './subordinate.js';
|
|
36
|
+
import { getSubordinates, assertUserIdsInScope, assertNamesInScope, resolveNameToUid, isScopeEnforced } from './subordinate.js';
|
|
34
37
|
import { resolveHrmTab, getHrmTabs, getHrmConditions, resolveHrmCondition, hrmList, buildHrmCardUrl, findHrmOrgNode, applyResolvedPersonIds, expandHrmOrgTree } from './hrm.js';
|
|
35
38
|
import { parseArgs, flagStr, flagBool } from './args.js';
|
|
36
39
|
import { todayStr } from './util.js';
|
|
@@ -46,8 +49,8 @@ function usage() {
|
|
|
46
49
|
' [--workflow <id>] [--creator <id>] [--creator-dept <id>] [--creator-subcom <id>]',
|
|
47
50
|
' [--create-time <range>] [--receive-time <range>] [--operate-time <range>]',
|
|
48
51
|
' [--request-level <level>] [--request-mark <code>] [--workflow-type <type>]',
|
|
49
|
-
' [--node-id <id>] [--flow-status <status>] [--sort <spec>] [--sub-category <tab>]',
|
|
50
|
-
' [--total-only] [--ungrouped] [--creator-name <姓名>] [--resume-query <id> --candidate <n>]',
|
|
52
|
+
' [--node-id <id> | --node-name <节点名>] [--flow-status <status>] [--sort <spec>] [--sub-category <tab>]',
|
|
53
|
+
' [--total-only] [--ungrouped] [--no-node-name] [--creator-name <姓名>] [--resume-query <id> --candidate <n>]',
|
|
51
54
|
' e9 todo summary --requestid <id> 查看流程摘要(表单字段+值+流转历史)',
|
|
52
55
|
' e9 todo submit --requestid <id> [--remark "同意"] [--yes] 提交待办到下一节点(默认签字 OK)',
|
|
53
56
|
' e9 todo reject --requestid <id> [--remark "退回"] [--reject-to-node <nodeid>] [--reject-to-type <t>] [--reject-node <nodes>] [--yes] 退回/驳回待办',
|
|
@@ -58,7 +61,7 @@ function usage() {
|
|
|
58
61
|
' e9 todo ask-idea --requestid <id> --receiver <uid> | --receiver-name <姓名> [--remark "意见"] [--yes] 意见征询(向某人征询意见,不改审批人)',
|
|
59
62
|
' e9 todo batch-submit --request-ids "1,2" --user-ids "u1,u2" [--remark "同意"] [--yes] 批量提交(先预览)',
|
|
60
63
|
' e9 todo batch-forward --request-ids "1,2" --receiver <uid> [--remark "转发"] [--yes] 批量转发(先预览)',
|
|
61
|
-
' e9 todo batch-attention --request-ids "1,2" [--attention-id <类型id>] [--yes]
|
|
64
|
+
' e9 todo batch-attention --request-ids "1,2" [--category <todo|done|mine>] [--attention-id <类型id>] [--yes] 批量关注(先预览;--category 选在哪类列表定位,默认待办)',
|
|
62
65
|
' e9 todo done [--page <n>] [--keyword <text>] [--workflow <id>] [--creator <id>] [--sort <spec>] [--sub-category <tab>] [--json] [--raw] 已办列表',
|
|
63
66
|
' e9 todo mine [--page <n>] [--keyword <text>] [--workflow <id>] [--creator <id>] [--sort <spec>] [--sub-category <tab>] [--json] [--raw] 我发起的流程',
|
|
64
67
|
' e9 blog list [--date YYYY-MM-DD] [--scope <全部|关注|下级|未读|共享|分享给我|特别关注>] [--page <n>] [--json] [--raw]',
|
|
@@ -106,7 +109,7 @@ function usage() {
|
|
|
106
109
|
' e9 email recall --id <邮件id> [--yes] 撤回已发邮件',
|
|
107
110
|
' e9 email attachment upload --file <路径> 上传附件拿 fileid;attachment list 附件列表',
|
|
108
111
|
' e9 email size 邮箱容量/未读统计;email accounts 接收账户列表',
|
|
109
|
-
' e9 workplan list [--date <YYYY-MM-DD>] [--view-type <1
|
|
112
|
+
' e9 workplan list [--date <YYYY-MM-DD>] [--view-type <1当天|2周|3月>] [--group <部门|分部|下级|...>] 日程列表(默认我的日程;--group 走组织视图)',
|
|
110
113
|
' e9 workplan my-calendar [--date <YYYY-MM-DD>] [--view-type <1|3>] 日程月视图(MyCalendar,浏览器主用)',
|
|
111
114
|
' e9 workplan view --id <id> 查看日程详情',
|
|
112
115
|
' e9 workplan finish --id <id> [--yes] 完成日程',
|
|
@@ -120,11 +123,28 @@ function usage() {
|
|
|
120
123
|
' e9 workplan import-template [--out-dir <目录>] 下载原生日程导入模板',
|
|
121
124
|
' e9 workplan import --file <模板.xls[x]> [--yes] 批量导入日程(默认仅预览)',
|
|
122
125
|
' e9 workplan export [--type <list|html>] [--group <部门|分部|下级|...>] [--date <YYYY-MM-DD>] [--workplan-type <id>] [--out-dir <目录>] [--yes] 导出日程(list=日程清单 / html=日程一览表,默认 dry-run)',
|
|
126
|
+
' e9 supervise list [--tab <全部|未督办|已督办|我的关注>] [--keyword <text>] [--page <n>] [--json] 流程督办列表',
|
|
127
|
+
' e9 supervise count [--json] 流程督办计数(总数/未督办)',
|
|
128
|
+
' e9 supervise conditions [--json] 流程督办可用搜索条件',
|
|
129
|
+
' e9 supervise send --requestids "1,2" [--remark "请尽快处理"] [--yes] 发起督办(默认 dry-run)',
|
|
130
|
+
' e9 wfagent list [--view <mine|all|records>] [--status <全部|未结束|未开始|代理中|收回中|已结束>]',
|
|
131
|
+
' [--agent <代理人>] [--be-agent <被代理人>] [--begin <日期|"起 止">] [--end <日期|"起 止">]',
|
|
132
|
+
' [--page <n>] [--json] 流程代理列表(mine=我设置的 / all=全部 / records=代理办理记录)',
|
|
133
|
+
' e9 wfagent conditions [--view <mine|all|records>] [--json] 流程代理可用搜索条件',
|
|
134
|
+
' e9 wfagent templates [--name <名>] [--workflow <id>] [--page <n>] [--json] 代理模板列表',
|
|
135
|
+
' e9 wfagent create --agent <代理人> --be-agent <被代理人> --begin <YYYY-MM-DD> --end <YYYY-MM-DD>',
|
|
136
|
+
' [--range 全部 | --workflow <流程id,id>] [--save-template <模板名>] [--yes] 新建代理(默认 dry-run)',
|
|
137
|
+
' e9 wfagent edit --agent-ids "537,538" [--begin <日期>] [--end <日期>] [--time <HH:MM-HH:MM>] [--yes] 批量编辑(只改起止日期/时间)',
|
|
138
|
+
' e9 wfagent edit --agent-id <代理id> [--end <YYYY-MM-DD>] [--begin <YYYY-MM-DD>] [--time <HH:MM-HH:MM>] [--yes] 编辑代理(默认 dry-run,显示改前→改后)',
|
|
139
|
+
' e9 wfagent takeback --agent-ids "69,70" [--back-running <0|1>] [--yes] 收回代理(默认 dry-run;id 取列表的「代理id」)',
|
|
140
|
+
' e9 wfagent takeback --all [--be-agent <被代理人>] [--yes] 收回该被代理人名下**全部**代理(默认 dry-run,批量)',
|
|
123
141
|
' e9 odoc todo [--page <n>] [--subject <标题>] [--dispatch-num <发文字号>] [--type <公文种类id>] [--topic <主题分类id>] 公文待办列表(getCustomPageList table,每页 10 条)',
|
|
124
142
|
' e9 odoc count [--view] 公文待办/待阅计数(发文/收文/签报)',
|
|
125
143
|
' e9 odoc create-list [--type 1|2] 发文/收文流程列表(1=发文拟稿 2=收文登记,复用 create start 发起)',
|
|
126
144
|
' e9 kq punch on|off [--group-id <id>] [--userid <id>] [--date <YYYY-MM-DD>] [--yes] 打卡(上班/下班卡,默认 dry-run 展示 encryptObj,--yes 真打)',
|
|
127
145
|
' e9 kq status 查看今日打卡班次(getButtons timeline)',
|
|
146
|
+
' e9 kq report [--user <id>|--user-name <姓名>] [--month YYYY-MM|--year YYYY] [--json] 考勤统计 + 假期余额(默认本人·本月)',
|
|
147
|
+
' e9 kq detail [--user <id>|--user-name <姓名>] [--month YYYY-MM] [--json] 月度考勤明细(签到签退时间/IP)',
|
|
128
148
|
' e9 cube list --customid <id> [--page <n>] [--keyword <text>] [--condition <k=v;...>] [--json] 建模台账列表(--condition 支持中文字段名自动映射 domkey)',
|
|
129
149
|
' e9 cube fields --customid <id> 建模查询条件探查(数据状态/筛选字段)',
|
|
130
150
|
' e9 cube export --customid <id> [--filename <名>] [--condition <k=v;...>] [--keyword <text>] [--out-dir <目录>] [--plain] [--yes] 导出建模台账为 Excel(默认 dry-run;--plain 明文 modeCustomid 逃生口,默认 base64)',
|
|
@@ -265,7 +285,7 @@ function usage() {
|
|
|
265
285
|
' email size 邮箱容量统计;email accounts 接收账户列表',
|
|
266
286
|
' workplan list 日程列表(POST /api/workplan/calendar/getCalendarData,支持组织视图)',
|
|
267
287
|
' --date 参考日期 YYYY-MM-DD,默认今天',
|
|
268
|
-
' --view-type 视图:1=当天 / 3
|
|
288
|
+
' --view-type 视图:1=当天 / 2=周 / 3=月(默认)',
|
|
269
289
|
' --group 组织视图:部门/分部/上级/下级/常用组/关注;不传=默认当前用户',
|
|
270
290
|
' --type 日程类型 id 筛选,留空=全部',
|
|
271
291
|
' --json/--raw 同 todo',
|
|
@@ -346,7 +366,7 @@ function escapeLinkText(text) {
|
|
|
346
366
|
return String(text).replace(/([\[\]()\\])/g, '\\$1');
|
|
347
367
|
}
|
|
348
368
|
/** 统一拼各域 list/search 项的 markdown 超链接;无 id/无可链目标时回退纯文本。
|
|
349
|
-
* domain: 'todo'|'odoc'|'create'|'blog'|'email'|'cube'|'workplan'|'hrm'
|
|
369
|
+
* domain: 'todo'|'odoc'|'create'|'blog'|'email'|'cube'|'workplan'|'hrm'|'wfagent'
|
|
350
370
|
* text: 展示文本(调用方已计算好回退值);ctx: cube 需 {modeId,formId,customid} */
|
|
351
371
|
function hyperlinkFor(domain, item, base, text, ctx = {}) {
|
|
352
372
|
const meta = linkMetaFor(domain, item, base, ctx);
|
|
@@ -361,6 +381,7 @@ function linkMetaFor(domain, item, base, ctx = {}) {
|
|
|
361
381
|
switch (domain) {
|
|
362
382
|
case 'todo':
|
|
363
383
|
case 'odoc':
|
|
384
|
+
case 'wfagent': // 仅 records 视图的行有 requestId(代理产生的流程);mine/all 是代理配置、无流程详情页
|
|
364
385
|
if (item?.requestId)
|
|
365
386
|
url = buildRequestFormUrl(b, item.requestId);
|
|
366
387
|
urlType = url ? 'detail' : '';
|
|
@@ -1861,6 +1882,13 @@ async function runWorkplan(args) {
|
|
|
1861
1882
|
const group = flagStr(args, 'group');
|
|
1862
1883
|
const date = flagStr(args, 'date');
|
|
1863
1884
|
const type = flagStr(args, 'type');
|
|
1885
|
+
// --view-type:1=当天 / 2=周 / 3=月(默认)。
|
|
1886
|
+
// 此前 list 分支把 viewType 写死为 '3',导致 --view-type 静默失效(传 1/2 都还是月视图,且不报错)。
|
|
1887
|
+
const viewType = flagStr(args, 'view-type') ?? '3';
|
|
1888
|
+
if (!['1', '2', '3'].includes(viewType)) {
|
|
1889
|
+
console.error(`workplan list 的 --view-type 只支持 1(当天)/ 2(周)/ 3(月),收到: ${viewType}`);
|
|
1890
|
+
process.exit(1);
|
|
1891
|
+
}
|
|
1864
1892
|
// 默认(无 --group)=「我的日程」:查当前用户自己的日程,走 MyCalendar(selectUser 空=当前用户)。
|
|
1865
1893
|
// 只有显式传 --group 才走组织视图 getCalendarData(部门/分部/下级等)。
|
|
1866
1894
|
if (!group) {
|
|
@@ -1868,7 +1896,7 @@ async function runWorkplan(args) {
|
|
|
1868
1896
|
// ★越权防护:--select-user 指定他人日程时目标必须 ∈ {本人 + 全部层级下属}
|
|
1869
1897
|
if (selectUser)
|
|
1870
1898
|
await assertUserIdsInScope(cfg, [selectUser], { domain: 'workplan' });
|
|
1871
|
-
const r = await getMyCalendar(cfg, { selectDate: date, viewType
|
|
1899
|
+
const r = await getMyCalendar(cfg, { selectDate: date, viewType, selectUser: selectUser ?? '', workPlanType: type });
|
|
1872
1900
|
if (args.flags.raw) {
|
|
1873
1901
|
console.error('===== RAW MyCalendar =====');
|
|
1874
1902
|
console.error(JSON.stringify(r.raw, null, 2));
|
|
@@ -1885,7 +1913,10 @@ async function runWorkplan(args) {
|
|
|
1885
1913
|
}, null, 2));
|
|
1886
1914
|
return;
|
|
1887
1915
|
}
|
|
1888
|
-
|
|
1916
|
+
// 标明视图与覆盖区间——周/月视图下只看 selectDate 无法判断统计了哪几天
|
|
1917
|
+
const viewLabel = viewType === '1' ? '当天' : viewType === '2' ? '本周' : '本月';
|
|
1918
|
+
const range = r.range?.start && r.range?.end ? ` ${r.range.start} ~ ${r.range.end}` : '';
|
|
1919
|
+
console.log(`E9 日程 [${r.selectDate} · ${viewLabel}]${range} 共 ${r.items.length} 条`);
|
|
1889
1920
|
if (r.items.length === 0) {
|
|
1890
1921
|
console.log('(该区间无日程)');
|
|
1891
1922
|
return;
|
|
@@ -1924,7 +1955,8 @@ async function runWorkplan(args) {
|
|
|
1924
1955
|
objIds = bd.dept[0]?.id ?? '';
|
|
1925
1956
|
}
|
|
1926
1957
|
}
|
|
1927
|
-
|
|
1958
|
+
// 组织视图同样跟随 --view-type(getCalendarData 的 bywhat:1=日 / 2=周 / 3=月)
|
|
1959
|
+
const r = await getCalendarData(cfg, { selectDate: date, objType, objIds, workPlanType: type, bywhat: viewType });
|
|
1928
1960
|
if (args.flags.raw) {
|
|
1929
1961
|
console.error('===== RAW getCalendarData =====');
|
|
1930
1962
|
console.error(JSON.stringify(r.raw, null, 2));
|
|
@@ -1947,7 +1979,11 @@ async function runWorkplan(args) {
|
|
|
1947
1979
|
return;
|
|
1948
1980
|
}
|
|
1949
1981
|
const groupLabel = ` · ${group}`;
|
|
1950
|
-
|
|
1982
|
+
// 组织视图无 start/end 字段,区间从 days.value 推导(首日~末日)
|
|
1983
|
+
const gDays = r.days?.value ?? [];
|
|
1984
|
+
const gRange = gDays.length ? ` ${gDays[0]} ~ ${gDays[gDays.length - 1]}` : '';
|
|
1985
|
+
const gViewLabel = viewType === '1' ? '当天' : viewType === '2' ? '本周' : '本月';
|
|
1986
|
+
console.log(`E9 日程 [${r.selectDate} · ${gViewLabel}${groupLabel}]${gRange} 共 ${r.count} 条`);
|
|
1951
1987
|
if (r.count === 0) {
|
|
1952
1988
|
console.log('(该区间无日程)');
|
|
1953
1989
|
return;
|
|
@@ -2299,6 +2335,560 @@ async function runWorkplan(args) {
|
|
|
2299
2335
|
console.error(`workplan 暂只支持 list / view / finish / delete / types / search-conditions / search / create / my-calendar / attention / base-data / export / import-template / import,收到: ${sub}`);
|
|
2300
2336
|
process.exit(1);
|
|
2301
2337
|
}
|
|
2338
|
+
async function runSupervise(args) {
|
|
2339
|
+
const sub = args._[1] ?? 'list';
|
|
2340
|
+
const cfg = loadConfig(flagStr(args, 'config'));
|
|
2341
|
+
const base = endpointFor(cfg).baseUrl.replace(/\/+$/, '');
|
|
2342
|
+
if (sub === 'count') {
|
|
2343
|
+
const r = await getSuperviseCount(cfg);
|
|
2344
|
+
if (args.flags.json) {
|
|
2345
|
+
console.log(JSON.stringify({ total: r.total, pending: r.pending }, null, 2));
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
console.log(`E9 流程督办计数:共 ${r.total ?? '?'} 条,其中未督办 ${r.pending ?? '?'} 条。`);
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
if (sub === 'conditions') {
|
|
2352
|
+
const r = await getSuperviseConditions(cfg);
|
|
2353
|
+
if (args.flags.json) {
|
|
2354
|
+
console.log(JSON.stringify(r.groups, null, 2));
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
console.log('E9 流程督办 搜索条件:');
|
|
2358
|
+
for (const g of r.groups) {
|
|
2359
|
+
console.log(` ${g.title}:`);
|
|
2360
|
+
for (const it of g.items)
|
|
2361
|
+
console.log(` - ${it.label} (${it.domkey}${it.type ? `, ${it.type}` : ''})`);
|
|
2362
|
+
}
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (sub === 'send') {
|
|
2366
|
+
const requestIds = flagStr(args, 'requestids') ?? '';
|
|
2367
|
+
if (!requestIds) {
|
|
2368
|
+
console.error('supervise send 需要 --requestids "id1,id2"');
|
|
2369
|
+
process.exit(1);
|
|
2370
|
+
}
|
|
2371
|
+
const remark = flagStr(args, 'remark') ?? '';
|
|
2372
|
+
const r = await doSupervise(cfg, { requestIds, remark, send: !!args.flags.yes });
|
|
2373
|
+
if (!r.sent) {
|
|
2374
|
+
console.log(BOX_TOP);
|
|
2375
|
+
console.log(` [dry-run] 将发起督办(${r.requestIds.length} 条)`);
|
|
2376
|
+
console.log(DIVIDER);
|
|
2377
|
+
console.log(` requestids = ${r.requestIds.join(', ')}`);
|
|
2378
|
+
console.log(` 督办意见 = ${r.remark || '(空)'}`);
|
|
2379
|
+
console.log(' 确认无误后加 --yes 真正发起督办。');
|
|
2380
|
+
console.log(BOX_TOP);
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
const j = r.response?.json;
|
|
2384
|
+
const ok = j?.status === true || j?.status === '1' || j?.status === 1 || j?.success === true;
|
|
2385
|
+
console.log(`${ok ? '✅' : '⚠'} 督办已请求处理(${r.requestIds.length} 条) HTTP ${r.response?.status}`);
|
|
2386
|
+
if (j)
|
|
2387
|
+
console.log('响应:', JSON.stringify(j).slice(0, 300));
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
if (sub !== 'list') {
|
|
2391
|
+
console.error(`supervise 暂只支持 list / count / conditions / send,收到: ${sub}`);
|
|
2392
|
+
process.exit(1);
|
|
2393
|
+
}
|
|
2394
|
+
const tabSpec = flagStr(args, 'tab');
|
|
2395
|
+
const tabKey = resolveSuperviseTab(tabSpec);
|
|
2396
|
+
if (tabKey === undefined) {
|
|
2397
|
+
console.error(`未知督办页签「${tabSpec}」,可选:${Object.keys(SUPERVISE_TABS).join(' / ')}`);
|
|
2398
|
+
process.exit(1);
|
|
2399
|
+
}
|
|
2400
|
+
const r = await listSupervise(cfg, {
|
|
2401
|
+
tabKey,
|
|
2402
|
+
keyword: flagStr(args, 'keyword'),
|
|
2403
|
+
page: Number(flagStr(args, 'page') ?? '1') || 1,
|
|
2404
|
+
pageSize: flagStr(args, 'page-size'),
|
|
2405
|
+
creator: flagStr(args, 'creator'),
|
|
2406
|
+
creatorDept: flagStr(args, 'creator-dept'),
|
|
2407
|
+
creatorSubcom: flagStr(args, 'creator-subcom'),
|
|
2408
|
+
});
|
|
2409
|
+
const tabName = Object.keys(SUPERVISE_TABS).find((k) => SUPERVISE_TABS[k] === tabKey) ?? tabKey;
|
|
2410
|
+
if (args.flags.raw) {
|
|
2411
|
+
console.error('===== RAW getSessionkey =====');
|
|
2412
|
+
console.error(JSON.stringify(r.raw.getSessionkey, null, 2));
|
|
2413
|
+
console.error('===== RAW datas =====');
|
|
2414
|
+
console.error(JSON.stringify(r.raw.datas, null, 2));
|
|
2415
|
+
}
|
|
2416
|
+
if (args.flags.json) {
|
|
2417
|
+
console.log(JSON.stringify({
|
|
2418
|
+
tab: tabName, tabKey, page: r.page, pageSize: r.pageSize, total: r.total, count: r.items.length,
|
|
2419
|
+
items: r.items.map((it) => withLink('todo', it, base)),
|
|
2420
|
+
}, null, 2));
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
console.log(`E9 流程督办 [${tabName}] 第 ${r.page} 页 共 ${r.total ?? '?'} 条`);
|
|
2424
|
+
if (!r.items.length) {
|
|
2425
|
+
console.log('(无数据)');
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
for (let i = 0; i < r.items.length; i++) {
|
|
2429
|
+
const it = r.items[i];
|
|
2430
|
+
console.log(`${i + 1}. ${hyperlinkFor('todo', it, base, it.title || '(无标题)')}`);
|
|
2431
|
+
const meta = [it.creator, it.node, it.createTime, it.unoperators ? `未操作者=${it.unoperators}` : null].filter(Boolean).join(' | ');
|
|
2432
|
+
if (meta)
|
|
2433
|
+
console.log(` ${meta}`);
|
|
2434
|
+
}
|
|
2435
|
+
if (r.total != null && r.items.length < r.total)
|
|
2436
|
+
console.log(`还有 ${r.total - r.items.length} 条,用 --page <页码> 继续。`);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
/**
|
|
2440
|
+
* 流程代理(wfagent):list / conditions / templates / create。
|
|
2441
|
+
* ⚠️ 编辑、收回、批量编辑、模板删除在 E9 里存在但**未抓到包**,本域未实现(见 references/wfagent.md)。
|
|
2442
|
+
* ⚠️ create 的 4 个开关(isCreateAgenter/isProxyDeal/isSysCreateAgenter/isPendThing)语义未校准,
|
|
2443
|
+
* 默认值 1 照抄抓包样本,dry-run 预览里会显式打印,可用对应命令行开关覆盖。
|
|
2444
|
+
*/
|
|
2445
|
+
async function runWfagent(args) {
|
|
2446
|
+
const sub = args._[1] ?? 'list';
|
|
2447
|
+
const cfg = loadConfig(flagStr(args, 'config'));
|
|
2448
|
+
const base = endpointFor(cfg).baseUrl.replace(/\/+$/, '');
|
|
2449
|
+
const viewSpec = flagStr(args, 'view');
|
|
2450
|
+
const view = resolveWfagentView(viewSpec);
|
|
2451
|
+
if (view === undefined) {
|
|
2452
|
+
console.error(`未知代理视图「${viewSpec}」,可选:${Object.keys(WFAGENT_VIEWS).join(' / ')}(mine=我设置的 / all=全部 / records=代理办理记录)`);
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
if (sub === 'conditions') {
|
|
2457
|
+
const r = await getAgentConditions(cfg, { view });
|
|
2458
|
+
if (args.flags.json) {
|
|
2459
|
+
console.log(JSON.stringify(r.groups, null, 2));
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
console.log(`E9 流程代理 搜索条件(视图:${WFAGENT_VIEWS[view].title}):`);
|
|
2463
|
+
for (const g of r.groups) {
|
|
2464
|
+
console.log(` ${g.title}:`);
|
|
2465
|
+
for (const it of g.items)
|
|
2466
|
+
console.log(` - ${it.label} (${it.domkey}${it.type ? `, ${it.type}` : ''})${it.options.length ? ` 可选:${it.options.map((o) => `${o.name}=${o.key}`).join(' / ')}` : ''}`);
|
|
2467
|
+
}
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
if (sub === 'templates') {
|
|
2472
|
+
const r = await listAgentTemplates(cfg, {
|
|
2473
|
+
name: flagStr(args, 'name'),
|
|
2474
|
+
workflowId: flagStr(args, 'workflow'),
|
|
2475
|
+
agentUid: flagStr(args, 'agent'),
|
|
2476
|
+
page: Number(flagStr(args, 'page') ?? '1') || 1,
|
|
2477
|
+
});
|
|
2478
|
+
if (args.flags.json) {
|
|
2479
|
+
console.log(JSON.stringify({ page: r.page, total: r.total, count: r.items.length, items: r.items.map(({ raw, ...rest }) => rest) }, null, 2));
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
console.log(`E9 代理模板 第 ${r.page} 页 共 ${r.total ?? '?'} 条`);
|
|
2483
|
+
if (!r.items.length) {
|
|
2484
|
+
console.log('(无数据)');
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
for (let i = 0; i < r.items.length; i++) {
|
|
2488
|
+
const it = r.items[i];
|
|
2489
|
+
// 模板没有可跳转的流程详情页,故不出链接(列表超链接规则只针对流程列表)
|
|
2490
|
+
console.log(`${i + 1}. ${it.name || '(未命名)'} [模板id=${it.templateId}]`);
|
|
2491
|
+
console.log(` 代理人=${it.agent || '?'} | 流程范围=${it.workflowRange || '?'}`);
|
|
2492
|
+
}
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (sub === 'create') {
|
|
2497
|
+
const agentSpec = flagStr(args, 'agent');
|
|
2498
|
+
if (!agentSpec) {
|
|
2499
|
+
console.error('wfagent create 需要 --agent <代理人>(姓名或用户id)');
|
|
2500
|
+
process.exit(1);
|
|
2501
|
+
}
|
|
2502
|
+
// 代理人=委托对象,不限于下属 → 只走 at-list 姓名解析,不做授权范围过滤
|
|
2503
|
+
const agentUid = await resolveNameToUid(cfg, agentSpec);
|
|
2504
|
+
// 被代理人默认=当前登录人(getAddAgentInfo 的 formValue);显式指定他人时按越权策略校验
|
|
2505
|
+
const beAgentSpec = flagStr(args, 'be-agent');
|
|
2506
|
+
let beAgentUid = '';
|
|
2507
|
+
let beAgentLabel = '';
|
|
2508
|
+
if (beAgentSpec) {
|
|
2509
|
+
beAgentUid = await assertNamesInScope(cfg, [beAgentSpec], { domain: 'wfagent' });
|
|
2510
|
+
beAgentLabel = beAgentSpec;
|
|
2511
|
+
}
|
|
2512
|
+
else {
|
|
2513
|
+
const info = await getAddAgentInfo(cfg);
|
|
2514
|
+
beAgentUid = info.defaultBeAgentUid;
|
|
2515
|
+
beAgentLabel = info.defaultBeAgent || beAgentUid;
|
|
2516
|
+
if (!beAgentUid) {
|
|
2517
|
+
console.error('未能从 getAddAgentInfo 取到当前登录人作为被代理人,请用 --be-agent 显式指定。');
|
|
2518
|
+
process.exit(1);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
const { beginTime, endTime } = parseTimeSpec(flagStr(args, 'time'));
|
|
2522
|
+
const body = buildAgentBody({
|
|
2523
|
+
agentUid,
|
|
2524
|
+
beAgentUid,
|
|
2525
|
+
beginDate: flagStr(args, 'begin'),
|
|
2526
|
+
endDate: flagStr(args, 'end'),
|
|
2527
|
+
beginTime,
|
|
2528
|
+
endTime,
|
|
2529
|
+
workflowIds: flagStr(args, 'workflow'),
|
|
2530
|
+
range: flagStr(args, 'range'),
|
|
2531
|
+
isCreateAgenter: flagStr(args, 'agent-create-flow'),
|
|
2532
|
+
isProxyDeal: flagStr(args, 'proxy-deal'),
|
|
2533
|
+
isSysCreateAgenter: flagStr(args, 'sys-create'),
|
|
2534
|
+
isPendThing: flagStr(args, 'pend-thing'),
|
|
2535
|
+
});
|
|
2536
|
+
const overlap = await verifyOverlap(cfg, body);
|
|
2537
|
+
const templateName = flagStr(args, 'save-template');
|
|
2538
|
+
if (!args.flags.yes) {
|
|
2539
|
+
console.log(BOX_TOP);
|
|
2540
|
+
console.log(' [dry-run] 新建流程代理');
|
|
2541
|
+
console.log(DIVIDER);
|
|
2542
|
+
console.log(` 被代理人 = ${beAgentLabel}(${body.bagentuid})`);
|
|
2543
|
+
console.log(` 代理人 = ${agentSpec}(${body.agentuid})`);
|
|
2544
|
+
console.log(` 代理期间 = ${body.beginDate} ~ ${body.endDate}${beginTime || endTime ? ` ${beginTime || '00:00'}~${endTime || '23:59'}` : '(全天)'}`);
|
|
2545
|
+
console.log(` 流程范围 = ${body.agentrange === '2' ? '全部流程' : `指定流程 ${body.rangetype}`}`);
|
|
2546
|
+
if (body.agentrange === '2')
|
|
2547
|
+
console.log(' ⚠️ 「全部流程」= 会为**你的每一个流程各建一条**代理记录(实测本环境 466 条),**不是 1 条**!\n 只想试一条请改用 --workflow <单个流程id>。建议保存后到 OA「流程代理」页核对。');
|
|
2548
|
+
console.log(` 开关 = isCreateAgenter=${body.isCreateAgenter} isProxyDeal=${body.isProxyDeal} isSysCreateAgenter=${body.isSysCreateAgenter} isPendThing=${body.isPendThing}`);
|
|
2549
|
+
console.log(' 开关含义(据 workflow_agent 表结构校准):isCreateAgenter=代理创建权限 / isProxyDeal=流转中的待办 /');
|
|
2550
|
+
console.log(' isPendThing=是否把待办事宜转给代理人 / isSysCreateAgenter=系统创建代理标记。默认 1 来自抓包样本;');
|
|
2551
|
+
console.log(' 如需调整:--agent-create-flow / --proxy-deal / --sys-create / --pend-thing');
|
|
2552
|
+
console.log(` 重叠校验 = ${overlap.overlapCount ? `⚠️ 与 ${overlap.overlapCount} 条已有代理重叠(overlapIds=${overlap.overlapIds})` : '✅ 无重叠'}`);
|
|
2553
|
+
if (templateName)
|
|
2554
|
+
console.log(` 存为模板 = ${templateName}`);
|
|
2555
|
+
console.log(DIVIDER);
|
|
2556
|
+
console.log(` 完整请求体:${JSON.stringify(body)}`);
|
|
2557
|
+
console.log(BOX_TOP);
|
|
2558
|
+
console.log(' 确认无误后加 --yes 真正保存。');
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
let tplResult = null;
|
|
2562
|
+
if (templateName)
|
|
2563
|
+
tplResult = await saveAgentTemplate(cfg, body, templateName);
|
|
2564
|
+
const r = await addAgent(cfg, { body, send: true });
|
|
2565
|
+
const ok = /success/i.test(String(r.result ?? ''));
|
|
2566
|
+
console.log(`${ok ? '✅' : '⚠'} 代理保存已请求(agentresult=${r.result || '无'})`);
|
|
2567
|
+
if (tplResult)
|
|
2568
|
+
console.log(` 模板「${templateName}」:${tplResult.ok ? '✅ 已保存' : '⚠️ 未确认'}`);
|
|
2569
|
+
console.log(` 响应:${JSON.stringify(r.raw ?? {})}`);
|
|
2570
|
+
// 回读核验:到列表里找这条(找不回报「未确认」,不谎报成功)
|
|
2571
|
+
// ⚠️ 必须带 status=1(未结束)并**翻页**:按人筛选在 all 视图可能命中数百条已结束的历史,
|
|
2572
|
+
// 只翻第 1 页会漏掉刚建的那条,误报「回读未确认」。
|
|
2573
|
+
try {
|
|
2574
|
+
const want = (it) => it.agentUid === body.agentuid && it.beginDate === body.beginDate && it.endDate === body.endDate;
|
|
2575
|
+
let hit = null;
|
|
2576
|
+
for (let page = 1; page <= 5 && !hit; page++) {
|
|
2577
|
+
const check = await getAgentList(cfg, { view: 'all', agent: body.agentuid, beAgent: body.bagentuid, status: '1', page, pageSize: '100' });
|
|
2578
|
+
hit = (check.items || []).find(want) ?? null;
|
|
2579
|
+
if ((check.items || []).length < 100)
|
|
2580
|
+
break;
|
|
2581
|
+
}
|
|
2582
|
+
console.log(hit
|
|
2583
|
+
? ` ✅ 已核验:列表中出现该条代理(状态 ${hit.status || '?'})`
|
|
2584
|
+
: ' ⚠️ 回读未确认:列表中暂未查到该条,请到 OA「流程代理」页核对。');
|
|
2585
|
+
}
|
|
2586
|
+
catch (e) {
|
|
2587
|
+
console.log(` ⚠️ 回读核验失败:${e.message}`);
|
|
2588
|
+
}
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
if (sub === 'edit') {
|
|
2593
|
+
// --agent-ids(≥2 条)走**批量**编辑(只改起止日期/时间);--agent-id 走单条编辑
|
|
2594
|
+
const batchSpec = flagStr(args, 'agent-ids');
|
|
2595
|
+
if (batchSpec) {
|
|
2596
|
+
const time = parseTimeSpec(flagStr(args, 'time'));
|
|
2597
|
+
const opts = {
|
|
2598
|
+
agentIds: batchSpec,
|
|
2599
|
+
beginDate: flagStr(args, 'begin'),
|
|
2600
|
+
endDate: flagStr(args, 'end'),
|
|
2601
|
+
...(time.beginTime || time.endTime ? { beginTime: time.beginTime, endTime: time.endTime } : {}),
|
|
2602
|
+
};
|
|
2603
|
+
const r = await editAgentBatch(cfg, { ...opts, send: !!args.flags.yes });
|
|
2604
|
+
if (!r.sent) {
|
|
2605
|
+
console.log(BOX_TOP);
|
|
2606
|
+
console.log(` [dry-run] 批量编辑代理(${r.body.agentids.split(',').length} 条)`);
|
|
2607
|
+
console.log(DIVIDER);
|
|
2608
|
+
console.log(` 代理id = ${r.body.agentids}`);
|
|
2609
|
+
console.log(` 起止日期 = ${r.body.beginDate || '(空)'} ~ ${r.body.endDate || '(空)'}`);
|
|
2610
|
+
console.log(` 起止时间 = ${r.body.beginTime || '(空)'} ~ ${r.body.endTime || '(空)'}`);
|
|
2611
|
+
console.log(' ⚠️ 批量编辑是**整体覆盖**这 4 个字段——只改一部分请用单条编辑(--agent-id)。');
|
|
2612
|
+
console.log(` 重叠校验 = ${r.overlap.overlapCount ? `⚠️ 与 ${r.overlap.overlapCount} 条已有代理重叠(overlapIds=${r.overlap.overlapIds})` : '✅ 无重叠'}`);
|
|
2613
|
+
console.log(DIVIDER);
|
|
2614
|
+
console.log(` 完整请求体:${JSON.stringify(r.body)}`);
|
|
2615
|
+
console.log(BOX_TOP);
|
|
2616
|
+
console.log(' 确认无误后加 --yes 真正保存。');
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
console.log(`${/success/i.test(r.result) ? '✅' : '⚠'} 批量编辑已保存(agentresult=${r.result || '无'})`);
|
|
2620
|
+
try {
|
|
2621
|
+
const ids = r.body.agentids.split(',');
|
|
2622
|
+
const rows = await getAgentList(cfg, { view: 'mine', status: '1', page: 1, pageSize: '100' });
|
|
2623
|
+
for (const id of ids) {
|
|
2624
|
+
const hit = (rows.items || []).find((it) => String(it.agentId) === String(id));
|
|
2625
|
+
console.log(hit
|
|
2626
|
+
? ` 代理 ${id}:起止 = ${hit.beginDate} ~ ${hit.endDate} 状态 = ${hit.status}`
|
|
2627
|
+
: ` 代理 ${id}:⚠️ 生效中的列表里没找到(可能被改到「已结束」)`);
|
|
2628
|
+
}
|
|
2629
|
+
const allHit = ids.every((id) => (rows.items || []).some((it) => String(it.agentId) === String(id)));
|
|
2630
|
+
console.log(allHit ? ' ✅ 已核验:所选代理均已出现并生效。' : ' ⚠️ 回读未确认,请核查。');
|
|
2631
|
+
}
|
|
2632
|
+
catch (e) {
|
|
2633
|
+
console.log(` ⚠️ 回读核验失败:${e.message}`);
|
|
2634
|
+
}
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
const agentId = flagStr(args, 'agent-id');
|
|
2638
|
+
if (!agentId) {
|
|
2639
|
+
console.error('wfagent edit 需要 --agent-id <单条> 或 --agent-ids "537,538" <批量>');
|
|
2640
|
+
process.exit(1);
|
|
2641
|
+
}
|
|
2642
|
+
// 编辑 = 取详情 → 改字段 → 原样回吐,所以先拿详情(它同时也是 dry-run 的"改前")
|
|
2643
|
+
const detail = await getAgentDetail(cfg, agentId);
|
|
2644
|
+
const time = parseTimeSpec(flagStr(args, 'time'));
|
|
2645
|
+
const overrides = {
|
|
2646
|
+
beginDate: flagStr(args, 'begin'),
|
|
2647
|
+
endDate: flagStr(args, 'end'),
|
|
2648
|
+
...(time.beginTime || time.endTime ? { beginTime: time.beginTime, endTime: time.endTime } : {}),
|
|
2649
|
+
isCreateAgenter: flagStr(args, 'agent-create-flow'),
|
|
2650
|
+
isProxyDeal: flagStr(args, 'proxy-deal'),
|
|
2651
|
+
isSysCreateAgenter: flagStr(args, 'sys-create'),
|
|
2652
|
+
isPendThing: flagStr(args, 'pend-thing'),
|
|
2653
|
+
};
|
|
2654
|
+
const fmtSpan = (d, t) => `${d}${t ? ` ${t}` : ''}`;
|
|
2655
|
+
const beforeSpan = `${fmtSpan(detail.current.beginDate, detail.current.beginTime)} ~ ${fmtSpan(detail.current.endDate, detail.current.endTime)}`;
|
|
2656
|
+
if (!Object.values(overrides).some((v) => v !== undefined)) {
|
|
2657
|
+
console.log(`代理 ${agentId}(${detail.current.workflowName})当前值:`);
|
|
2658
|
+
console.log(` 代理期间 = ${beforeSpan}`);
|
|
2659
|
+
console.log(' 没给任何要改的字段。可改:--begin / --end / --time / --agent-create-flow / --proxy-deal / --sys-create / --pend-thing');
|
|
2660
|
+
console.log(' ⚠️ 代理人、流程范围不可改(要换请先 takeback 再 create)。');
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
const r = await editAgent(cfg, { agentId, detail, ...overrides, send: !!args.flags.yes });
|
|
2664
|
+
if (!r.sent) {
|
|
2665
|
+
console.log(BOX_TOP);
|
|
2666
|
+
console.log(` [dry-run] 编辑代理 ${agentId}(${detail.current.workflowName})`);
|
|
2667
|
+
console.log(DIVIDER);
|
|
2668
|
+
console.log(` 代理期间 = ${beforeSpan} ← 改前`);
|
|
2669
|
+
console.log(` = ${fmtSpan(r.body.beginDate, r.body.beginTime)} ~ ${fmtSpan(r.body.endDate, r.body.endTime)} ← 改后`);
|
|
2670
|
+
console.log(` 开关 = isCreateAgenter=${r.body.row_0_isCreateAgenter} isProxyDeal=${r.body.row_0_isProxyDeal} isSysCreateAgenter=${r.body.row_0_isSysCreateAgenter} isPendThing=${r.body.row_0_isPendThing}`);
|
|
2671
|
+
console.log(` 重叠校验 = ${r.overlap.overlapCount ? `⚠️ 与 ${r.overlap.overlapCount} 条已有代理重叠(overlapIds=${r.overlap.overlapIds})` : '✅ 无重叠'}`);
|
|
2672
|
+
console.log(DIVIDER);
|
|
2673
|
+
console.log(` 完整请求体:${JSON.stringify(r.body)}`);
|
|
2674
|
+
console.log(BOX_TOP);
|
|
2675
|
+
console.log(' 确认无误后加 --yes 真正保存。');
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
console.log(`${/success/i.test(r.result) ? '✅' : '⚠'} 编辑已保存(agentresult=${r.result || '无'})`);
|
|
2679
|
+
try {
|
|
2680
|
+
const rows = await getAgentList(cfg, { view: 'mine', status: '1', page: 1, pageSize: '100' });
|
|
2681
|
+
const hit = (rows.items || []).find((it) => String(it.agentId) === String(agentId));
|
|
2682
|
+
if (!hit) {
|
|
2683
|
+
console.log(' ⚠️ 回读未确认:生效中的列表里没找到该条(可能已被改到「已结束」)。');
|
|
2684
|
+
}
|
|
2685
|
+
else {
|
|
2686
|
+
const beginOk = !overrides.beginDate || String(hit.beginDate).startsWith(String(overrides.beginDate));
|
|
2687
|
+
const endOk = !overrides.endDate || String(hit.endDate) === String(overrides.endDate);
|
|
2688
|
+
console.log(` 回读:起止 = ${hit.beginDate} ~ ${hit.endDate} 状态 = ${hit.status}`);
|
|
2689
|
+
console.log(beginOk && endOk ? ' ✅ 已核验:改后的日期与提交一致。' : ' ⚠️ 回读与提交不一致,请核查。');
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
catch (e) {
|
|
2693
|
+
console.log(` ⚠️ 回读核验失败:${e.message}`);
|
|
2694
|
+
}
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
if (sub === 'takeback') {
|
|
2699
|
+
// --all:收回该被代理人名下的全部代理(界面「收回全部代理」,symbol=pt)
|
|
2700
|
+
if (args.flags.all === true) {
|
|
2701
|
+
const beAgentSpec = flagStr(args, 'be-agent');
|
|
2702
|
+
let beAgentUid = '';
|
|
2703
|
+
if (beAgentSpec)
|
|
2704
|
+
beAgentUid = await resolveNameToUid(cfg, beAgentSpec);
|
|
2705
|
+
else
|
|
2706
|
+
beAgentUid = (await getAddAgentInfo(cfg)).defaultBeAgentUid;
|
|
2707
|
+
if (!beAgentUid) {
|
|
2708
|
+
console.error('收回全部需要被代理人(--be-agent <姓名|id>),且未能从 getAddAgentInfo 取到当前登录人。');
|
|
2709
|
+
process.exit(1);
|
|
2710
|
+
}
|
|
2711
|
+
const backRunning = flagStr(args, 'back-running');
|
|
2712
|
+
const r = await takeBackAllAgent(cfg, { beAgentUid, backRunning, send: !!args.flags.yes });
|
|
2713
|
+
if (!r.sent) {
|
|
2714
|
+
console.log(BOX_TOP);
|
|
2715
|
+
console.log(' [dry-run] 将收回该被代理人名下的**全部**代理');
|
|
2716
|
+
console.log(DIVIDER);
|
|
2717
|
+
console.log(` 被代理人 = ${beAgentSpec || beAgentUid}(${r.beAgentUid})`);
|
|
2718
|
+
console.log(` backRunning= ${r.backRunning}(1=连流转中的一并收回)`);
|
|
2719
|
+
console.log(` 请求体 = symbol=pt & agentid=<自动取该被代理人名下第一条生效中的代理> & bagentuid=${r.beAgentUid} & backRunning=${r.backRunning}`);
|
|
2720
|
+
console.log(' ⚠️ 这是**批量**动作,会把这个被代理人名下所有代理一并标记「已结束」。');
|
|
2721
|
+
console.log(' 确认无误后加 --yes 真正收回。');
|
|
2722
|
+
console.log(BOX_TOP);
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
if (r.noop) {
|
|
2726
|
+
console.log(`该被代理人(${r.beAgentUid})名下没有生效中的代理,无需收回。`);
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
console.log(`${r.flag ? '✅' : '⚠'} 已发起收回全部代理(锚点 agentid=${r.agentId},flag=${r.flag})`);
|
|
2730
|
+
try {
|
|
2731
|
+
const left = await getAgentList(cfg, { view: 'all', beAgent: r.beAgentUid, status: '1', page: 1 });
|
|
2732
|
+
console.log(Number(left.total) === 0
|
|
2733
|
+
? ` ✅ 回读核验:该被代理人名下生效中的代理已为 0 条。`
|
|
2734
|
+
: ` ⚠️ 回读核验:仍有 ${left.total} 条生效中的代理,请核查。`);
|
|
2735
|
+
}
|
|
2736
|
+
catch (e) {
|
|
2737
|
+
console.log(` ⚠️ 回读核验失败:${e.message}`);
|
|
2738
|
+
}
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
const idSpec = flagStr(args, 'agent-ids');
|
|
2742
|
+
if (!idSpec) {
|
|
2743
|
+
console.error('wfagent takeback 需要 --agent-ids "69,70"(列表里的「代理id」,逗号分隔多个),或 --all 收回全部');
|
|
2744
|
+
process.exit(1);
|
|
2745
|
+
}
|
|
2746
|
+
const ids = idSpec.split(',').map((s) => s.trim()).filter(Boolean);
|
|
2747
|
+
const backRunning = flagStr(args, 'back-running');
|
|
2748
|
+
const r = await takeBackAgent(cfg, {
|
|
2749
|
+
agentIds: ids.join(','),
|
|
2750
|
+
backRunning,
|
|
2751
|
+
send: !!args.flags.yes,
|
|
2752
|
+
// 逐条发;大批量时每 25 条报一次进度(走 stderr,不污染 --json)
|
|
2753
|
+
onProgress: (i, total, id) => {
|
|
2754
|
+
if (total > 25 && (i === 1 || i % 25 === 0))
|
|
2755
|
+
console.error(` 正在收回 ${i}/${total}(agentid=${id})…`);
|
|
2756
|
+
},
|
|
2757
|
+
});
|
|
2758
|
+
if (!r.sent) {
|
|
2759
|
+
console.log(BOX_TOP);
|
|
2760
|
+
console.log(` [dry-run] 将收回 ${ids.length} 条代理`);
|
|
2761
|
+
console.log(DIVIDER);
|
|
2762
|
+
console.log(` 代理id = ${ids.slice(0, 20).join(', ')}${ids.length > 20 ? ` …(共 ${ids.length} 个)` : ''}`);
|
|
2763
|
+
console.log(` backRunning= ${r.backRunning}(1=连流转中的一并收回)`);
|
|
2764
|
+
console.log(` 请求体 = symbol=it & agentid=<每个id> & backRunning=${r.backRunning}`);
|
|
2765
|
+
console.log(' 确认无误后加 --yes 真正收回。');
|
|
2766
|
+
console.log(BOX_TOP);
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
const failed = r.results.filter((x) => !x.flag);
|
|
2770
|
+
console.log(`${failed.length ? '⚠' : '✅'} 已发起收回 ${ids.length} 条:接口回 flag=true 的 ${r.okCount} 条${failed.length ? `,失败/未确认 ${failed.length} 条(${failed.slice(0, 5).map((x) => x.agentId).join(',')}${failed.length > 5 ? ' …' : ''})` : ''}`);
|
|
2771
|
+
// ⚠️ 收回是「把 agenttype 置 0(已结束)」,行**不会消失**;且少传 symbol 时接口回 true 却毫无效果
|
|
2772
|
+
// ——所以必须按**状态码**回读核验,不能看"行在不在"。
|
|
2773
|
+
try {
|
|
2774
|
+
const after = new Map();
|
|
2775
|
+
let page = 1;
|
|
2776
|
+
while (true) {
|
|
2777
|
+
const p = await getAgentList(cfg, { view: 'mine', page, pageSize: '100' });
|
|
2778
|
+
for (const it of p.items)
|
|
2779
|
+
after.set(String(it.agentId), it.statusCode);
|
|
2780
|
+
if (p.items.length < 100 || page > 20)
|
|
2781
|
+
break;
|
|
2782
|
+
page++;
|
|
2783
|
+
}
|
|
2784
|
+
const notDone = ids.filter((id) => after.get(String(id)) !== '0');
|
|
2785
|
+
console.log(notDone.length
|
|
2786
|
+
? ` ⚠️ 回读核验:${ids.length - notDone.length}/${ids.length} 条已为「已结束」,仍有 ${notDone.length} 条没变(${notDone.slice(0, 8).join(',')}${notDone.length > 8 ? ' …' : ''})——请核查。`
|
|
2787
|
+
: ` ✅ 回读核验:${ids.length} 条均已标记「已结束」。`);
|
|
2788
|
+
}
|
|
2789
|
+
catch (e) {
|
|
2790
|
+
console.log(` ⚠️ 回读核验失败:${e.message}`);
|
|
2791
|
+
}
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
if (sub !== 'list') {
|
|
2796
|
+
console.error(`wfagent 暂只支持 list / conditions / templates / create / edit / takeback,收到: ${sub}`);
|
|
2797
|
+
process.exit(1);
|
|
2798
|
+
}
|
|
2799
|
+
const statusSpec = flagStr(args, 'status');
|
|
2800
|
+
const status = resolveWfagentStatus(statusSpec);
|
|
2801
|
+
if (status === undefined) {
|
|
2802
|
+
console.error(`未知代理状态「${statusSpec}」,可选:${Object.keys(WFAGENT_STATUS).join(' / ')}`);
|
|
2803
|
+
process.exit(1);
|
|
2804
|
+
}
|
|
2805
|
+
// 列表的按人筛选是「查他人数据」→ 走范围校验(姓名经 blog at-list 解析,硬规则)
|
|
2806
|
+
const listAgentSpec = flagStr(args, 'agent');
|
|
2807
|
+
const listBeAgentSpec = flagStr(args, 'be-agent');
|
|
2808
|
+
const agentFilter = listAgentSpec ? await assertNamesInScope(cfg, [listAgentSpec], { domain: 'wfagent' }) : undefined;
|
|
2809
|
+
const beAgentFilter = listBeAgentSpec ? await assertNamesInScope(cfg, [listBeAgentSpec], { domain: 'wfagent' }) : undefined;
|
|
2810
|
+
const r = await getAgentList(cfg, {
|
|
2811
|
+
view,
|
|
2812
|
+
status,
|
|
2813
|
+
agent: agentFilter,
|
|
2814
|
+
beAgent: beAgentFilter,
|
|
2815
|
+
wftype: flagStr(args, 'wftype'),
|
|
2816
|
+
workflowPath: flagStr(args, 'workflow-path'),
|
|
2817
|
+
// 日期沿用仓库惯例:单值=当天,双值="起 止"(同 todo --create-time)
|
|
2818
|
+
beginFrom: parseTimeRange(flagStr(args, 'begin') ?? '').from,
|
|
2819
|
+
beginTo: parseTimeRange(flagStr(args, 'begin') ?? '').to,
|
|
2820
|
+
endFrom: parseTimeRange(flagStr(args, 'end') ?? '').from,
|
|
2821
|
+
endTo: parseTimeRange(flagStr(args, 'end') ?? '').to,
|
|
2822
|
+
dept: flagStr(args, 'dept'),
|
|
2823
|
+
subcom: flagStr(args, 'subcom'),
|
|
2824
|
+
page: Number(flagStr(args, 'page') ?? '1') || 1,
|
|
2825
|
+
pageSize: flagStr(args, 'page-size'),
|
|
2826
|
+
});
|
|
2827
|
+
// 代理办理记录是「流程列表」→ 复用节点名解析,把 currentnodeid(id)显示成节点名
|
|
2828
|
+
if (view === 'records')
|
|
2829
|
+
await annotateNodeNamesForCli(cfg, r.items, args);
|
|
2830
|
+
if (args.flags.raw) {
|
|
2831
|
+
console.error('===== RAW getList =====');
|
|
2832
|
+
console.error(JSON.stringify(r.raw.getList, null, 2));
|
|
2833
|
+
console.error('===== RAW datas =====');
|
|
2834
|
+
console.error(JSON.stringify(r.raw.datas, null, 2));
|
|
2835
|
+
console.error('===== RAW counts =====');
|
|
2836
|
+
console.error(JSON.stringify(r.raw.counts, null, 2));
|
|
2837
|
+
console.error('===== 解析结果 =====');
|
|
2838
|
+
}
|
|
2839
|
+
if (args.flags.json) {
|
|
2840
|
+
console.log(JSON.stringify({
|
|
2841
|
+
view,
|
|
2842
|
+
viewTitle: r.viewTitle,
|
|
2843
|
+
page: r.page,
|
|
2844
|
+
pageSize: r.pageSize,
|
|
2845
|
+
total: r.total,
|
|
2846
|
+
count: r.items.length,
|
|
2847
|
+
items: r.items.map((it) => withLink('wfagent', it, base)),
|
|
2848
|
+
}, null, 2));
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
console.log(`E9 流程代理 · ${r.viewTitle} 第 ${r.page} 页(每页 ${r.pageSize ?? 10} 条) 共 ${r.total ?? '?'} 条`);
|
|
2852
|
+
if (!r.items.length) {
|
|
2853
|
+
console.log('(无数据)');
|
|
2854
|
+
if (args.flags.raw)
|
|
2855
|
+
console.error('提示:用 --raw 查看真实响应校准解析。');
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
for (let i = 0; i < r.items.length; i++) {
|
|
2859
|
+
const it = r.items[i];
|
|
2860
|
+
if (view === 'records') {
|
|
2861
|
+
// 代理产生的流程:标题必须渲染成流程详情超链接(列表超链接硬规则)
|
|
2862
|
+
console.log(`${i + 1}. ${hyperlinkFor('wfagent', it, base, it.title || '(无标题)')}`);
|
|
2863
|
+
const meta = [
|
|
2864
|
+
it.principal ? `被代理人=${it.principal}` : null,
|
|
2865
|
+
it.handler ? `代理人=${it.handler}` : null,
|
|
2866
|
+
nodeLabel(it),
|
|
2867
|
+
it.creator ? `流程发起人=${it.creator}` : null,
|
|
2868
|
+
it.createTime,
|
|
2869
|
+
].filter(Boolean).join(' | ');
|
|
2870
|
+
if (meta)
|
|
2871
|
+
console.log(` ${meta}`);
|
|
2872
|
+
}
|
|
2873
|
+
else {
|
|
2874
|
+
console.log(`${i + 1}. ${it.agent || '(未知代理人)'} ← 被代理人 ${it.beAgent || it.operator || '?'}`);
|
|
2875
|
+
const meta = [
|
|
2876
|
+
it.workflowName,
|
|
2877
|
+
it.workflowType,
|
|
2878
|
+
`${it.beginDate ?? '?'} ~ ${it.endDate ?? '?'}`,
|
|
2879
|
+
it.status,
|
|
2880
|
+
it.operator ? `创建人=${it.operator}` : null,
|
|
2881
|
+
it.agentId ? `代理id=${it.agentId}` : null,
|
|
2882
|
+
].filter(Boolean).join(' | ');
|
|
2883
|
+
if (meta)
|
|
2884
|
+
console.log(` ${meta}`);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
const pageSize = r.pageSize ?? 10;
|
|
2888
|
+
if (r.total != null && r.page * pageSize < r.total)
|
|
2889
|
+
console.log(`还有更多,用 --page <页码> 继续(每页 ${pageSize} 条)。`);
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2302
2892
|
async function runOdoc(args) {
|
|
2303
2893
|
const sub = args._[1] ?? 'todo';
|
|
2304
2894
|
const cfg = loadConfig(flagStr(args, 'config'));
|
|
@@ -2397,6 +2987,7 @@ async function runOdoc(args) {
|
|
|
2397
2987
|
customTabId: flagStr(args, 'custom-tab-id'),
|
|
2398
2988
|
page: Number(flagStr(args, 'page') ?? '1') || 1,
|
|
2399
2989
|
});
|
|
2990
|
+
await annotateNodeNamesForCli(cfg, r.items, args);
|
|
2400
2991
|
if (args.flags.raw) {
|
|
2401
2992
|
console.error('===== RAW getCustomPageList =====');
|
|
2402
2993
|
console.error(JSON.stringify(r.raw.getCustomPageList, null, 2));
|
|
@@ -2421,8 +3012,9 @@ async function runOdoc(args) {
|
|
|
2421
3012
|
odocTypeName: it.odocTypeName,
|
|
2422
3013
|
issuedNum: it.issuedNum,
|
|
2423
3014
|
receiveNum: it.receiveNum,
|
|
2424
|
-
|
|
2425
|
-
|
|
3015
|
+
stageName: it.stageName,
|
|
3016
|
+
node: it.node,
|
|
3017
|
+
nodeName: it.nodeName,
|
|
2426
3018
|
createrId: it.createrId,
|
|
2427
3019
|
createDate: it.createDate,
|
|
2428
3020
|
lastOperateDate: it.lastOperateDate,
|
|
@@ -2444,10 +3036,12 @@ async function runOdoc(args) {
|
|
|
2444
3036
|
const it = r.items[i];
|
|
2445
3037
|
const nameLinked = hyperlinkFor('odoc', it, odocBase, it.requestName ?? '(无标题)');
|
|
2446
3038
|
console.log(`${i + 1}. ${nameLinked}`);
|
|
3039
|
+
// 环节名(如「拟稿」)+ 当前节点(解析出名字时用「名字(id)」,否则退回 id)
|
|
3040
|
+
const nodeText = [it.stageName, nodeLabel(it)].filter(Boolean).join(' · ');
|
|
2447
3041
|
const meta = [
|
|
2448
3042
|
it.odocTypeName,
|
|
2449
3043
|
it.issuedNum ? `发文字号=${it.issuedNum}` : null,
|
|
2450
|
-
|
|
3044
|
+
nodeText || null,
|
|
2451
3045
|
it.createrId ? `拟稿人=${it.createrId}` : null,
|
|
2452
3046
|
it.createDate,
|
|
2453
3047
|
it.lastOperateDate ? `最后修改=${it.lastOperateDate}` : null,
|
|
@@ -2568,7 +3162,88 @@ async function runKq(args) {
|
|
|
2568
3162
|
console.log(BOX_TOP);
|
|
2569
3163
|
return;
|
|
2570
3164
|
}
|
|
2571
|
-
|
|
3165
|
+
if (sub === 'report' || sub === 'detail') {
|
|
3166
|
+
const month = flagStr(args, 'month');
|
|
3167
|
+
const year = flagStr(args, 'year');
|
|
3168
|
+
if (month && year) {
|
|
3169
|
+
console.error('--month 与 --year 只能传一个。');
|
|
3170
|
+
process.exit(1);
|
|
3171
|
+
}
|
|
3172
|
+
// 目标人员:--user <id> / --user-name <姓名>;都不传 = 当前登录人(cookie loginuuids)
|
|
3173
|
+
let targetId = flagStr(args, 'user') ?? '';
|
|
3174
|
+
const userName = flagStr(args, 'user-name');
|
|
3175
|
+
if (!targetId && userName)
|
|
3176
|
+
targetId = await assertNamesInScope(cfg, [userName], { domain: 'kq' });
|
|
3177
|
+
if (!targetId)
|
|
3178
|
+
targetId = parseUserIdFromCookie(cfg.cookie);
|
|
3179
|
+
if (!targetId) {
|
|
3180
|
+
console.error('未能确定目标人员:请传 --user <id> 或 --user-name <姓名>,或确认 cookie 含 loginuuids。');
|
|
3181
|
+
process.exit(1);
|
|
3182
|
+
}
|
|
3183
|
+
// 越权边界(默认停用;E9_SCOPE_ENFORCE=1 时启用):目标须 ∈ {本人 + 全部层级下属}
|
|
3184
|
+
await assertUserIdsInScope(cfg, [targetId], { domain: 'kq' });
|
|
3185
|
+
const type = year && !month ? '1' : '2';
|
|
3186
|
+
const typevalue = month || year || undefined;
|
|
3187
|
+
if (sub === 'detail') {
|
|
3188
|
+
const r = await getKqMonthReport(cfg, { typevalue, resourceId: targetId });
|
|
3189
|
+
if (args.flags.raw) {
|
|
3190
|
+
console.error('===== RAW getHrmKQMonthReportInfo =====');
|
|
3191
|
+
console.error(JSON.stringify(r.raw, null, 2));
|
|
3192
|
+
}
|
|
3193
|
+
if (args.flags.json) {
|
|
3194
|
+
console.log(JSON.stringify({ typevalue: r.typevalue, resourceId: r.resourceId, days: r.days }, null, 2));
|
|
3195
|
+
return;
|
|
3196
|
+
}
|
|
3197
|
+
console.log(`E9 考勤明细 ${r.typevalue} 人员 id=${targetId} 共 ${r.days.length} 天`);
|
|
3198
|
+
const withSign = r.days.filter((d) => d.signIn || d.signOut || d.types.length);
|
|
3199
|
+
if (!withSign.length) {
|
|
3200
|
+
console.log('(本月无打卡记录)');
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
for (const d of withSign) {
|
|
3204
|
+
const flags = d.types.length ? ` [${d.types.join(',')}]` : '';
|
|
3205
|
+
console.log(` ${d.date} 签到 ${d.signIn || '—'} → 签退 ${d.signOut || '—'}${flags}`);
|
|
3206
|
+
}
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
const r = await getKqReport(cfg, { type, typevalue, resourceId: targetId });
|
|
3210
|
+
if (args.flags.raw) {
|
|
3211
|
+
console.error('===== RAW getHrmKQReportInfo =====');
|
|
3212
|
+
console.error(JSON.stringify(r.raw, null, 2));
|
|
3213
|
+
}
|
|
3214
|
+
if (args.flags.json) {
|
|
3215
|
+
console.log(JSON.stringify({
|
|
3216
|
+
typevalue: r.typevalue, view: type === '1' ? 'year' : 'month', resourceId: r.resourceId,
|
|
3217
|
+
attendance: r.attendance, durations: r.durations, exceptions: r.exceptions, leave: r.leave,
|
|
3218
|
+
}, null, 2));
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
console.log(BOX_TOP);
|
|
3222
|
+
console.log(`E9 考勤统计 ${r.typevalue}(${type === '1' ? '年' : '月'}视图) 人员 id=${targetId}`);
|
|
3223
|
+
console.log(DIVIDER);
|
|
3224
|
+
console.log(` 出勤统计:应出勤 ${r.attendance.workDays}`);
|
|
3225
|
+
for (const it of r.attendance.items)
|
|
3226
|
+
console.log(` ${it.title} = ${it.value}`);
|
|
3227
|
+
const line = (label, arr) => {
|
|
3228
|
+
if (!arr.length)
|
|
3229
|
+
return;
|
|
3230
|
+
console.log(` ${label}:${arr.map((x) => `${x.title} ${x.value}`).join(' | ')}`);
|
|
3231
|
+
};
|
|
3232
|
+
line('流程时长', r.durations);
|
|
3233
|
+
line('异常', r.exceptions);
|
|
3234
|
+
if (r.leave.length) {
|
|
3235
|
+
console.log(DIVIDER);
|
|
3236
|
+
console.log(' 假期余额(可请天数):');
|
|
3237
|
+
for (const g of r.leave) {
|
|
3238
|
+
const items = g.items.map((x) => `${x.title} ${x.value}`).join(' | ');
|
|
3239
|
+
if (items)
|
|
3240
|
+
console.log(` ${g.title}:${items}`);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
console.log(BOX_TOP);
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
console.error(`kq 暂只支持 status / punch / report / detail,收到: ${sub}`);
|
|
2572
3247
|
process.exit(1);
|
|
2573
3248
|
}
|
|
2574
3249
|
/** CLI 侧当前时刻 HH:mm:ss(与 kq.ts 的 nowHHmmss 同逻辑,CLI 输出用)。 */
|
|
@@ -4017,12 +4692,41 @@ async function runHrm(args) {
|
|
|
4017
4692
|
}
|
|
4018
4693
|
}
|
|
4019
4694
|
|
|
4695
|
+
/**
|
|
4696
|
+
* 节点名解析:默认开启,`--no-node-name` 或环境变量 E9_NO_NODE_NAME=1 停用。
|
|
4697
|
+
* 接口失败/接口不存在时静默降级(列表照常出,只是节点列回退显示 id)。
|
|
4698
|
+
*/
|
|
4699
|
+
function annotateNodeNamesForCli(cfg, items, args) {
|
|
4700
|
+
// --raw 是校准/排障出口,保持原样不额外请求;--no-node-name / E9_NO_NODE_NAME=1 显式关闭。
|
|
4701
|
+
const disabled = args.flags['no-node-name'] === true || args.flags.raw === true || isNodeNameDisabled();
|
|
4702
|
+
return annotateNodeNames(cfg, items, { disabled });
|
|
4703
|
+
}
|
|
4704
|
+
/** 解析 --time "HH:MM-HH:MM";不传返回空串(= 全天)。 */
|
|
4705
|
+
function parseTimeSpec(raw) {
|
|
4706
|
+
const s = String(raw ?? '').trim();
|
|
4707
|
+
if (!s)
|
|
4708
|
+
return { beginTime: '', endTime: '' };
|
|
4709
|
+
const m = s.split(/[-~—]/).map((x) => x.trim()).filter(Boolean);
|
|
4710
|
+
if (m.length !== 2) {
|
|
4711
|
+
console.error('--time 格式应为 "HH:MM-HH:MM"');
|
|
4712
|
+
process.exit(1);
|
|
4713
|
+
}
|
|
4714
|
+
return { beginTime: m[0], endTime: m[1] };
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
/** 节点显示:解析到名字时用「名字(id)」,否则退回 id。 */
|
|
4718
|
+
function nodeLabel(item) {
|
|
4719
|
+
const id = item?.node ? String(item.node) : '';
|
|
4720
|
+
if (!id)
|
|
4721
|
+
return undefined;
|
|
4722
|
+
return item.nodeName ? `${item.nodeName}(${id})` : id;
|
|
4723
|
+
}
|
|
4020
4724
|
function printTodoItems(items, base, { indent = '', max = 10 } = {}) {
|
|
4021
4725
|
const visible = items.slice(0, max);
|
|
4022
4726
|
for (let i = 0; i < visible.length; i++) {
|
|
4023
4727
|
const it = visible[i];
|
|
4024
4728
|
const head = it.title ?? '(无标题)';
|
|
4025
|
-
const meta = [it.creator, it.currentOperator ? `未操作者=${it.currentOperator}` : null, it
|
|
4729
|
+
const meta = [it.creator, it.currentOperator ? `未操作者=${it.currentOperator}` : null, nodeLabel(it), it.createdate].filter(Boolean).join(' | ');
|
|
4026
4730
|
const headLinked = hyperlinkFor('todo', it, base, head);
|
|
4027
4731
|
console.log(`${indent}${i + 1}. ${headLinked}${it.level ? ` (${it.level})` : ''}`);
|
|
4028
4732
|
if (meta)
|
|
@@ -4050,9 +4754,15 @@ async function runTodoBatch(args) {
|
|
|
4050
4754
|
}
|
|
4051
4755
|
else {
|
|
4052
4756
|
// 批量关注:dry-run 也先查关注类型(只读),供用户确认加入哪个关注类型
|
|
4757
|
+
// 关注在 待办/已办/我发起 三个分类下都有「我的关注」页签,故定位列表可用 --category 指定(默认待办)
|
|
4758
|
+
const category = flagStr(args, 'category') ?? 'todo';
|
|
4759
|
+
if (!['todo', 'done', 'mine'].includes(category)) {
|
|
4760
|
+
console.error(`batch-attention 的 --category 只支持 todo(待办)/ done(已办)/ mine(我发起),收到: ${category}`);
|
|
4761
|
+
process.exit(1);
|
|
4762
|
+
}
|
|
4053
4763
|
const attentionTypes = await getAttentionTypes(cfg, requestIds);
|
|
4054
4764
|
const attentionId = flagStr(args, 'attention-id') ?? attentionTypes[0]?.id;
|
|
4055
|
-
preview = { operation: '批量关注', requestIds, attentionId, attentionTypes: attentionTypes.map((t) => ({ id: t.id, showname: t.showname })) };
|
|
4765
|
+
preview = { operation: '批量关注', category, requestIds, attentionId, attentionTypes: attentionTypes.map((t) => ({ id: t.id, showname: t.showname })) };
|
|
4056
4766
|
}
|
|
4057
4767
|
if (!args.flags.yes) {
|
|
4058
4768
|
console.log(BOX_TOP);
|
|
@@ -4068,7 +4778,7 @@ async function runTodoBatch(args) {
|
|
|
4068
4778
|
else if (sub === 'batch-forward')
|
|
4069
4779
|
result = await batchForward(cfg, { requestIds, receiver: preview.receiver, remark: preview.remark });
|
|
4070
4780
|
else
|
|
4071
|
-
result = await batchAttention(cfg, { requestIds, attentionId: preview.attentionId });
|
|
4781
|
+
result = await batchAttention(cfg, { requestIds, attentionId: preview.attentionId, category: preview.category });
|
|
4072
4782
|
const body = result.result?.json ?? {};
|
|
4073
4783
|
const ok = result.result?.ok && body.success !== false && body.status !== false;
|
|
4074
4784
|
console.log(`${ok ? '✅' : '⚠'} ${preview.operation}已请求处理(${requestIds.length} 条)。`);
|
|
@@ -4116,7 +4826,7 @@ async function main() {
|
|
|
4116
4826
|
const cmd = args._[0] ?? 'todo';
|
|
4117
4827
|
if (cmd === 'version') {
|
|
4118
4828
|
const buildHash = createHash('sha256').update(fs.readFileSync(new URL(import.meta.url))).digest('hex').slice(0, 12);
|
|
4119
|
-
console.log(JSON.stringify({ name: 'weaver-e9-assistant-cli', version: '1.1.
|
|
4829
|
+
console.log(JSON.stringify({ name: 'weaver-e9-assistant-cli', version: '1.1.5', buildHash }, null, 2));
|
|
4120
4830
|
return;
|
|
4121
4831
|
}
|
|
4122
4832
|
if (cmd === 'whoami') {
|
|
@@ -4155,6 +4865,14 @@ async function main() {
|
|
|
4155
4865
|
await runWorkplan(args);
|
|
4156
4866
|
return;
|
|
4157
4867
|
}
|
|
4868
|
+
if (cmd === 'supervise') {
|
|
4869
|
+
await runSupervise(args);
|
|
4870
|
+
return;
|
|
4871
|
+
}
|
|
4872
|
+
if (cmd === 'wfagent') {
|
|
4873
|
+
await runWfagent(args);
|
|
4874
|
+
return;
|
|
4875
|
+
}
|
|
4158
4876
|
if (cmd === 'odoc') {
|
|
4159
4877
|
await runOdoc(args);
|
|
4160
4878
|
return;
|
|
@@ -4289,6 +5007,43 @@ async function main() {
|
|
|
4289
5007
|
return typeof saved === 'string' ? saved : undefined;
|
|
4290
5008
|
};
|
|
4291
5009
|
const queryBool = (key) => args.flags[key] === true || savedQueryFlags[key] === true;
|
|
5010
|
+
// ★按节点名筛选(--node-name):节点 id 是**流程内编号**,必须先定位流程才能解析成 id。
|
|
5011
|
+
// 与 --node-id 互斥;未给 --workflow 时只在**本地缓存**的节点表里找(缓存来自历次列表查询)。
|
|
5012
|
+
let nodeIdFilter = queryFlag('node-id');
|
|
5013
|
+
const nodeNameFilter = queryFlag('node-name');
|
|
5014
|
+
if (nodeNameFilter) {
|
|
5015
|
+
// --json 时信息一律走 stderr,保证 stdout 是纯 JSON
|
|
5016
|
+
const say = args.flags.json ? console.error : console.log;
|
|
5017
|
+
if (nodeIdFilter)
|
|
5018
|
+
throw new Error('--node-name 与 --node-id 不能同时用(两者都在指定当前节点)。');
|
|
5019
|
+
const wfScope = String(workflowFilter ?? '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
5020
|
+
if (wfScope.length > 1)
|
|
5021
|
+
throw new Error(`--node-name 需要唯一的流程范围,但 --workflow 传了 ${wfScope.length} 个流程;请只保留一个。`);
|
|
5022
|
+
const resolved = await resolveNodeByName(cfg, nodeNameFilter, { workflowId: wfScope[0] });
|
|
5023
|
+
const pick = resolved.exact.length === 1 ? resolved.exact[0] : null;
|
|
5024
|
+
if (!pick) {
|
|
5025
|
+
if (resolved.unavailable) {
|
|
5026
|
+
console.error(`取不到流程 ${wfScope[0]} 的节点表(/api/workflow/test/getNodeInfo 不可用),无法按节点名筛选;请改用 --node-id <id>。`);
|
|
5027
|
+
process.exit(1);
|
|
5028
|
+
}
|
|
5029
|
+
if (resolved.candidates.length === 0) {
|
|
5030
|
+
console.error(resolved.scoped
|
|
5031
|
+
? `流程 ${wfScope[0]} 的节点表里没有匹配「${nodeNameFilter}」的节点。`
|
|
5032
|
+
: `本地缓存的 ${resolved.poolSize} 个流程节点表里没有匹配「${nodeNameFilter}」的节点。可加 --workflow <流程id> 限定流程,或先在待办列表里翻到该流程任意一条以缓存其节点表。`);
|
|
5033
|
+
process.exit(1);
|
|
5034
|
+
}
|
|
5035
|
+
say(BOX_TOP);
|
|
5036
|
+
say(` 「${nodeNameFilter}」匹配到 ${resolved.candidates.length} 个节点,请选一个后再用 --node-id 指定:`);
|
|
5037
|
+
say(DIVIDER);
|
|
5038
|
+
for (const c of resolved.candidates)
|
|
5039
|
+
say(` ${String(c.nodeId).padStart(6, ' ')} ${c.nodeName}${c.matchType === 'exact' ? ' ★(精确)' : ''} ← ${c.workflowName ?? '(未命名流程)'}(流程 ${c.workflowId})`);
|
|
5040
|
+
say(BOX_TOP);
|
|
5041
|
+
say(` 例:e9 todo ${sub} --node-id ${resolved.candidates[0].nodeId} --workflow ${resolved.candidates[0].workflowId}`);
|
|
5042
|
+
process.exit(0);
|
|
5043
|
+
}
|
|
5044
|
+
nodeIdFilter = pick.nodeId;
|
|
5045
|
+
say(`按节点名筛选:「${pick.nodeName}」→ 节点 id=${pick.nodeId}(流程 ${pick.workflowName ?? pick.workflowId})`);
|
|
5046
|
+
}
|
|
4292
5047
|
let workflowTypeFilter = queryFlag('workflow-type');
|
|
4293
5048
|
if (workflowTypeFilter && !/^\d+$/.test(workflowTypeFilter)) {
|
|
4294
5049
|
const resolvedType = await resolveWorkflowType(cfg, workflowTypeFilter, { category, viewcondition });
|
|
@@ -4318,7 +5073,7 @@ async function main() {
|
|
|
4318
5073
|
requestLevel: queryFlag('request-level'),
|
|
4319
5074
|
requestMark: queryFlag('request-mark'),
|
|
4320
5075
|
workflowType: workflowTypeFilter,
|
|
4321
|
-
nodeId:
|
|
5076
|
+
nodeId: nodeIdFilter,
|
|
4322
5077
|
flowStatus: queryFlag('flow-status'),
|
|
4323
5078
|
totalOnly: queryBool('total-only'),
|
|
4324
5079
|
debug: queryBool('debug'),
|
|
@@ -4328,6 +5083,7 @@ async function main() {
|
|
|
4328
5083
|
const shouldGroup = !listOpts.subCategory && !tab && !listOpts.totalOnly && !queryBool('ungrouped') && !queryBool('raw');
|
|
4329
5084
|
if (shouldGroup) {
|
|
4330
5085
|
const grouped = await getOaTabGroups(cfg, { ...listOpts, category });
|
|
5086
|
+
await annotateNodeNamesForCli(cfg, grouped.groups.flatMap((g) => g.items), args);
|
|
4331
5087
|
const scope = CATEGORY_SCOPE[category] ?? CATEGORY_SCOPE.todo;
|
|
4332
5088
|
const groupedSnapshot = {
|
|
4333
5089
|
category,
|
|
@@ -4365,6 +5121,7 @@ async function main() {
|
|
|
4365
5121
|
return;
|
|
4366
5122
|
}
|
|
4367
5123
|
const result = await getWfList(cfg, listOpts);
|
|
5124
|
+
await annotateNodeNamesForCli(cfg, result.items, args);
|
|
4368
5125
|
const savedResult = persistSearchResult(result, `E9 ${name ?? key} 查询`);
|
|
4369
5126
|
if (args.flags.raw) {
|
|
4370
5127
|
console.error('===== RAW getWfListParams =====');
|
|
@@ -4395,6 +5152,7 @@ async function main() {
|
|
|
4395
5152
|
creatorId: it.creatorId,
|
|
4396
5153
|
currentOperator: it.currentOperator,
|
|
4397
5154
|
node: it.node,
|
|
5155
|
+
nodeName: it.nodeName,
|
|
4398
5156
|
createdate: it.createdate,
|
|
4399
5157
|
receivedate: it.receivedate,
|
|
4400
5158
|
status: it.status,
|
|
@@ -4476,15 +5234,39 @@ async function runTodoSubmit(args) {
|
|
|
4476
5234
|
}
|
|
4477
5235
|
const remark = flagStr(args, 'remark') ?? 'OK';
|
|
4478
5236
|
const send = !!args.flags.yes;
|
|
5237
|
+
// --field-values '<json>':提交时修改字段值。此前 CLI 声明了该参数却从未解析/传入(静默失效)。
|
|
5238
|
+
const fvRaw = flagStr(args, 'field-values');
|
|
5239
|
+
let fieldValues;
|
|
5240
|
+
if (fvRaw) {
|
|
5241
|
+
try {
|
|
5242
|
+
fieldValues = JSON.parse(fvRaw);
|
|
5243
|
+
}
|
|
5244
|
+
catch {
|
|
5245
|
+
console.error(`--field-values 不是合法 JSON:${fvRaw}`);
|
|
5246
|
+
process.exit(1);
|
|
5247
|
+
}
|
|
5248
|
+
if (!fieldValues || typeof fieldValues !== 'object' || Array.isArray(fieldValues)) {
|
|
5249
|
+
console.error('--field-values 需为对象,如 \'{"70702":"新值"}\'');
|
|
5250
|
+
process.exit(1);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
4479
5253
|
if (!send) {
|
|
4480
5254
|
console.log(BOX_TOP);
|
|
4481
5255
|
console.log(` [dry-run] 提交待办 requestid=${requestId}`);
|
|
4482
5256
|
console.log(` 签字意见 = ${remark}`);
|
|
5257
|
+
if (fieldValues && Object.keys(fieldValues).length)
|
|
5258
|
+
console.log(` 字段修改 = ${JSON.stringify(fieldValues)}`);
|
|
5259
|
+
// dry-run 即校验字段属性(当前节点):只读/隐藏字段禁止填值——避免"预览通过、提交才报错"
|
|
5260
|
+
if (fieldValues && Object.keys(fieldValues).length) {
|
|
5261
|
+
const bad = await validateFieldValuesAtNode(cfg, requestId, fieldValues);
|
|
5262
|
+
if (bad.length)
|
|
5263
|
+
throw new Error(`拒绝填写当前节点的只读/隐藏字段:${bad.map((b) => `${b.label || b.id}(${VIEWATTR_LABEL[b.viewattr] ?? 'va=' + b.viewattr})`).join('、')}。本节点只允许修改「可编辑」字段,不得绕过节点字段权限自行填值。`);
|
|
5264
|
+
}
|
|
4483
5265
|
console.log(' 确认无误后加 --yes 真正提交。');
|
|
4484
5266
|
console.log(BOX_TOP);
|
|
4485
5267
|
return;
|
|
4486
5268
|
}
|
|
4487
|
-
const res = await submitTodo(cfg, { requestId, remark });
|
|
5269
|
+
const res = await submitTodo(cfg, { requestId, remark, fieldValues });
|
|
4488
5270
|
console.log(BOX_TOP);
|
|
4489
5271
|
const j = res.json;
|
|
4490
5272
|
const ok = j?.data?.type === 'SUCCESS' || j?.type === 'SUCCESS';
|