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
package/dist/wfcreate.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
const MENU_IDS = '1,12';
|
|
3
|
+
/**
|
|
4
|
+
* 取"新建流程"列表。GET createreq/wfinfo,解析 datas[].wfbeans[]。
|
|
5
|
+
*/
|
|
6
|
+
export async function getCreateWfList(cfg, opts = {}) {
|
|
7
|
+
const api = await createApiFromProfile(cfg);
|
|
8
|
+
const tabkey = opts.tab ?? '1';
|
|
9
|
+
const r = await api.get('/api/workflow/createreq/wfinfo', {
|
|
10
|
+
needall: '1',
|
|
11
|
+
menuIds: MENU_IDS,
|
|
12
|
+
menuPathIds: MENU_IDS,
|
|
13
|
+
tabkey,
|
|
14
|
+
__random__: String(Date.now()),
|
|
15
|
+
});
|
|
16
|
+
const j = r.json ?? {};
|
|
17
|
+
const tabs = Array.isArray(j.tabs) ? j.tabs.map((t) => ({ title: t?.title, key: String(t?.key ?? '') })) : [];
|
|
18
|
+
const found = tabs.find((t) => String(t.key) === String(tabkey));
|
|
19
|
+
const tabInfo = { key: tabkey, title: found?.title };
|
|
20
|
+
const kw = (opts.keyword ?? '').trim().toLowerCase();
|
|
21
|
+
const wantCollected = String(tabkey) === '2'; // 我的收藏:客户端按 wfColl=="1" 过滤(服务端不按 tabkey 过滤 datas)
|
|
22
|
+
const categories = [];
|
|
23
|
+
let totalWorkflows = 0;
|
|
24
|
+
for (const cat of Array.isArray(j.datas) ? j.datas : []) {
|
|
25
|
+
const beans = Array.isArray(cat?.wfbeans) ? cat.wfbeans : [];
|
|
26
|
+
const workflows = beans
|
|
27
|
+
.map((w) => ({
|
|
28
|
+
id: str(w?.id),
|
|
29
|
+
name: str(w?.name),
|
|
30
|
+
typeId: str(w?.typeId),
|
|
31
|
+
letter: str(w?.letter),
|
|
32
|
+
spell: str(w?.spell),
|
|
33
|
+
isFreeCustomeForm: w?.isFreeCustomeForm,
|
|
34
|
+
isImportWf: str(w?.isImportWf),
|
|
35
|
+
pcurl: str(w?.pcurl),
|
|
36
|
+
mobileurl: str(w?.mobileurl),
|
|
37
|
+
wfColl: str(w?.wfColl),
|
|
38
|
+
}))
|
|
39
|
+
.filter((w) => {
|
|
40
|
+
if (wantCollected && w.wfColl !== '1')
|
|
41
|
+
return false;
|
|
42
|
+
if (kw && !(w.name ?? '').toLowerCase().includes(kw))
|
|
43
|
+
return false;
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
if (workflows.length === 0)
|
|
47
|
+
continue; // 过滤后无流程的分类跳过
|
|
48
|
+
totalWorkflows += workflows.length;
|
|
49
|
+
categories.push({
|
|
50
|
+
id: str(cat?.id),
|
|
51
|
+
name: str(cat?.typeName),
|
|
52
|
+
color: str(cat?.color),
|
|
53
|
+
img: str(cat?.img),
|
|
54
|
+
order: typeof cat?.order === 'number' ? cat.order : undefined,
|
|
55
|
+
workflows,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
tab: tabInfo,
|
|
60
|
+
tabs,
|
|
61
|
+
categories,
|
|
62
|
+
totalCategories: categories.length,
|
|
63
|
+
totalWorkflows,
|
|
64
|
+
raw: j,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function str(v) {
|
|
68
|
+
if (v === undefined || null === v || v === '')
|
|
69
|
+
return undefined;
|
|
70
|
+
return String(v);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 构造"发起流程"页面 URL(SPA 表单)。
|
|
74
|
+
* 模板:<baseUrl>/spa/workflow/static4form/index.html?_rdm=<时间戳>#/main/workflow/req?iscreate=1&workflowid=<id>
|
|
75
|
+
* 用户抓包确认:换 workflowid 即对应不同流程,_rdm 为时间戳(缓存破坏)。
|
|
76
|
+
*/
|
|
77
|
+
export function buildCreateWfUrl(baseUrl, workflowId, now = Date.now()) {
|
|
78
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
79
|
+
return `${base}/spa/workflow/static4form/index.html?_rdm=${now}#/main/workflow/req?iscreate=1&workflowid=${workflowId}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 构造"操作已有请求"表单 URL(处理/提交待办用)。
|
|
83
|
+
* 模板:<baseUrl>/spa/workflow/static4form/index.html?_rdm=<ts>#/main/workflow/req?iscreate=0&requestid=<id>&workflowid=<wf>
|
|
84
|
+
* 用于打开一个已存在的请求表单(业务字段此时可编辑),浏览器里手动填+提交。
|
|
85
|
+
*/
|
|
86
|
+
export function buildRequestFormUrl(baseUrl, requestId, workflowId, now = Date.now()) {
|
|
87
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
88
|
+
const wf = workflowId ? `&workflowid=${workflowId}` : '';
|
|
89
|
+
return `${base}/spa/workflow/static4form/index.html?_rdm=${now}#/main/workflow/req?iscreate=0&requestid=${requestId}${wf}`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 按流程名在"新建流程"列表里查找 id(模糊匹配,返回全部命中项)。
|
|
93
|
+
* 用于"发起 XX 流程"时把名字解析成 workflowid。
|
|
94
|
+
*/
|
|
95
|
+
export async function findCreateWfByName(cfg, name) {
|
|
96
|
+
const r = await getCreateWfList(cfg, { keyword: name });
|
|
97
|
+
const hits = [];
|
|
98
|
+
for (const c of r.categories)
|
|
99
|
+
for (const w of c.workflows)
|
|
100
|
+
hits.push(w);
|
|
101
|
+
return hits;
|
|
102
|
+
}
|
package/dist/wfform.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
import { AuthTokenSession } from './auth-token.js';
|
|
3
|
+
import { fetchCubeTreeBrowserOptions } from './cube.js';
|
|
4
|
+
export const VIEWATTR_LABEL = { 1: '可编辑', 2: '只读', 3: '隐藏' };
|
|
5
|
+
export const HTMLTYPE_LABEL = {
|
|
6
|
+
1: '单行文本', 2: '多行文本', 3: '浏览选择', 4: '附件', 5: '选择框', 6: '复选框', 7: '特殊',
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 取流程发起表单的字段定义。POST loadForm(aiAgentToken 鉴权)→ 解析 tableInfo。
|
|
10
|
+
* 非交互:用 E9Api(token 优先,token 失效走 reactive 重登或 cookie 回退)。
|
|
11
|
+
*/
|
|
12
|
+
export async function describeFormFields(cfg, workflowId) {
|
|
13
|
+
const api = await createApiFromProfile(cfg);
|
|
14
|
+
const r = await api.post('/api/workflow/reqform/loadForm', { iscreate: '1', workflowid: workflowId });
|
|
15
|
+
const j = r.json ?? {};
|
|
16
|
+
const ti = j.tableInfo;
|
|
17
|
+
if (!ti) {
|
|
18
|
+
throw new Error(`loadForm 无 tableInfo(verifyRight=${j.params?.verifyRight ?? '?'})。可能无权限或 token 失效——跑 e9 auth token-login 后重试。`);
|
|
19
|
+
}
|
|
20
|
+
const tables = [];
|
|
21
|
+
let total = 0;
|
|
22
|
+
for (const key of Object.keys(ti)) {
|
|
23
|
+
const fmap = ti[key]?.fieldinfomap;
|
|
24
|
+
if (!fmap || typeof fmap !== 'object')
|
|
25
|
+
continue;
|
|
26
|
+
const fields = Object.values(fmap).map((f) => {
|
|
27
|
+
const va = num(f?.viewattr);
|
|
28
|
+
const ht = num(f?.htmltype);
|
|
29
|
+
return {
|
|
30
|
+
id: str(f?.fieldid) ?? key,
|
|
31
|
+
label: str(f?.fieldlabel),
|
|
32
|
+
name: str(f?.fieldname),
|
|
33
|
+
viewattr: va,
|
|
34
|
+
viewattrLabel: va != null ? VIEWATTR_LABEL[va] ?? `va=${va}` : undefined,
|
|
35
|
+
htmltype: ht,
|
|
36
|
+
htmltypeLabel: ht != null ? HTMLTYPE_LABEL[ht] ?? `ht=${ht}` : undefined,
|
|
37
|
+
isdetail: f?.isdetail,
|
|
38
|
+
isonlyshow: f?.isonlyshow,
|
|
39
|
+
table: key,
|
|
40
|
+
raw: f,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
total += fields.length;
|
|
44
|
+
tables.push({ key, fields });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
workflowId,
|
|
48
|
+
workflowName: str(j.cellInfo?.sysFieldInfo?.workflowname) ?? str(j.params?.workflowname),
|
|
49
|
+
tables,
|
|
50
|
+
totalFields: total,
|
|
51
|
+
verifyRight: j.params?.verifyRight === true,
|
|
52
|
+
maindata: j.maindata,
|
|
53
|
+
submitParams: j.submitParams,
|
|
54
|
+
params: j.params,
|
|
55
|
+
raw: j,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function str(v) {
|
|
59
|
+
if (v === undefined || v === null || v === '')
|
|
60
|
+
return undefined;
|
|
61
|
+
return String(v);
|
|
62
|
+
}
|
|
63
|
+
function num(v) {
|
|
64
|
+
if (v === undefined || v === null || v === '')
|
|
65
|
+
return undefined;
|
|
66
|
+
const n = Number(v);
|
|
67
|
+
return Number.isFinite(n) ? n : undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 取自定义浏览框(browser.xxx)的选项(前 N 条)。通用接口(用户抓包确认):
|
|
71
|
+
* GET /api/public/browser/data/<detailtype>?pageSize=<N>¤t=1&min=1&max=<N>&type=<fielddbtype>&
|
|
72
|
+
* fieldid=<fieldId>&workflowid=<wf>&wfid=<wf>&billid=<formid>&isbill=1&requestid=-1&...&__random__=<ts>
|
|
73
|
+
* aiAgentToken 鉴权。返回 {total, columns, datas}:每行 id=选项值,显示名=columns 里 showType:1 的列值。
|
|
74
|
+
*
|
|
75
|
+
* detailtype/fielddbtype/formid 等从 loadForm fieldinfomap + params 取。pageSize 默认 10(前 10)。
|
|
76
|
+
*/
|
|
77
|
+
export async function getBrowserOptions(cfg, workflowId, fieldId, pageSize = 10) {
|
|
78
|
+
const f = await describeFormFields(cfg, workflowId);
|
|
79
|
+
// 字段可能在主表(main)或明细表(detail_<n>)的 fieldinfomap,遍历所有表查找
|
|
80
|
+
let fm;
|
|
81
|
+
const ti = f.raw?.tableInfo ?? {};
|
|
82
|
+
for (const key of Object.keys(ti)) {
|
|
83
|
+
const m = ti[key]?.fieldinfomap;
|
|
84
|
+
if (m && m[fieldId]) {
|
|
85
|
+
fm = m[fieldId];
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!fm)
|
|
90
|
+
throw new Error(`字段 ${fieldId} 不在 fieldinfomap(可能不是浏览框)`);
|
|
91
|
+
const detailtype = String(fm.detailtype ?? '');
|
|
92
|
+
const fielddbtype = String(fm.fielddbtype ?? '');
|
|
93
|
+
if (!detailtype || !fielddbtype)
|
|
94
|
+
throw new Error(`字段 ${fieldId} 无 detailtype/fielddbtype,非浏览框`);
|
|
95
|
+
const p = f.params ?? {};
|
|
96
|
+
// ★ cube 树浏览框(detailtype=256):走递归取树,treeid = fielddbtype
|
|
97
|
+
if (detailtype === '256') {
|
|
98
|
+
const apiW = await createApiFromProfile(cfg);
|
|
99
|
+
const base = {
|
|
100
|
+
fieldid: String(fieldId), viewtype: '0', fromModule: 'workflow',
|
|
101
|
+
wfid: String(workflowId), billid: String(p.billid || p.formid || ''), isbill: String(p.isbill ?? '1'),
|
|
102
|
+
requestid: '-1',
|
|
103
|
+
'f_weaver_belongto_userid': String(p.f_weaver_belongto_userid ?? '1'),
|
|
104
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
105
|
+
wf_isagent: '0', wf_beagenter: '0', wfTestStr: '', wfCreater: String(p.wfCreater ?? '1'),
|
|
106
|
+
disabledConditionCache: 'true', secretLevel: '4', companyId: '1', checkStrictly: 'false',
|
|
107
|
+
treeid: fielddbtype, cube_treeid: fielddbtype, treetype: '256',
|
|
108
|
+
};
|
|
109
|
+
const items = await fetchCubeTreeBrowserOptions(apiW, base, { pageSize, maxNodes: 300 });
|
|
110
|
+
return items.map((o) => ({ id: o.id, name: o.name, path: o.path }));
|
|
111
|
+
}
|
|
112
|
+
const sess = AuthTokenSession.fromProfile(cfg);
|
|
113
|
+
const base = cfg.baseUrl.replace(/\/+$/, '');
|
|
114
|
+
const q = new URLSearchParams({
|
|
115
|
+
pageSize: String(pageSize), current: '1', min: '1', max: String(pageSize),
|
|
116
|
+
alllevel: '1', virtualtype: '1', status: '8',
|
|
117
|
+
wfid: String(workflowId),
|
|
118
|
+
billid: String(p.billid || p.formid || ''), isbill: String(p.isbill ?? '1'),
|
|
119
|
+
requestid: '-1',
|
|
120
|
+
'f_weaver_belongto_userid': String(p.f_weaver_belongto_userid ?? '1'),
|
|
121
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
122
|
+
wf_isagent: '0', wf_beagenter: '0', wfTestStr: '',
|
|
123
|
+
fieldid: String(fieldId), viewtype: '0', fromModule: 'workflow',
|
|
124
|
+
wfCreater: String(p.wfCreater ?? '1'), disabledConditionCache: 'true',
|
|
125
|
+
defaultSearch: 'false', secretLevel: '4',
|
|
126
|
+
cmd: 'newly',
|
|
127
|
+
__random__: String(Date.now()),
|
|
128
|
+
});
|
|
129
|
+
const r = await fetch(`${base}/api/public/browser/data/${detailtype}?${q.toString()}`, {
|
|
130
|
+
headers: { aiAgentToken: sess.token, 'X-Requested-With': 'XMLHttpRequest' },
|
|
131
|
+
signal: AbortSignal.timeout(15000),
|
|
132
|
+
});
|
|
133
|
+
const t = await r.text();
|
|
134
|
+
let j = null;
|
|
135
|
+
try {
|
|
136
|
+
j = JSON.parse(t);
|
|
137
|
+
}
|
|
138
|
+
catch { /* non-json */ }
|
|
139
|
+
// 响应可能是数组(HR 等)或树形对象(部门等),统一展平
|
|
140
|
+
let flatDatas = [];
|
|
141
|
+
if (Array.isArray(j?.datas)) {
|
|
142
|
+
flatDatas = j.datas;
|
|
143
|
+
}
|
|
144
|
+
else if (j?.datas && typeof j.datas === 'object') {
|
|
145
|
+
// 树形结构:递归展平 subs,只取可点击的叶子节点(canClick=true 或 type=2)
|
|
146
|
+
flatDatas = flattenTree(j.datas);
|
|
147
|
+
}
|
|
148
|
+
const cols = Array.isArray(j?.columns) ? j.columns : [];
|
|
149
|
+
// 有 columns 时按旧逻辑解析;无 columns 时(如 HR/部门浏览框)按 name/lastname 解析
|
|
150
|
+
let nameKey, pkKey;
|
|
151
|
+
if (cols.length > 0) {
|
|
152
|
+
const showCol = cols.find((c) => String(c.showType) === '1')
|
|
153
|
+
?? cols.find((c) => c.isInputCol === 'true' || c.isInputCol === true)
|
|
154
|
+
?? cols.find((c) => /名称|名字|name/i.test(String(c.title ?? '')))
|
|
155
|
+
?? cols.find((c) => c.display === 'true' && c.hide !== 'true');
|
|
156
|
+
nameKey = showCol?.dataIndex ?? 'mc';
|
|
157
|
+
const pkCol = cols.find((c) => c.isPrimarykey === 'true' || c.isPrimarykey === true);
|
|
158
|
+
pkKey = pkCol?.dataIndex;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// 无 columns:优先 lastname,回退 name
|
|
162
|
+
nameKey = flatDatas.length > 0 && flatDatas[0].lastname != null ? 'lastname' : 'name';
|
|
163
|
+
pkKey = null;
|
|
164
|
+
}
|
|
165
|
+
return flatDatas
|
|
166
|
+
.filter((d) => d && d.id != null)
|
|
167
|
+
.map((d) => {
|
|
168
|
+
const pk = pkKey && d[pkKey] != null && String(d[pkKey]) !== '' ? String(d[pkKey]) : String(d.id);
|
|
169
|
+
const name = stripHtml(String(d[nameKey] ?? d.mc ?? d.name ?? d.id));
|
|
170
|
+
return { id: pk, name: name || String(d.id) };
|
|
171
|
+
})
|
|
172
|
+
.filter((o) => o.id);
|
|
173
|
+
}
|
|
174
|
+
/** 递归展平树形浏览数据(部门组织架构等),只取可点击节点 */
|
|
175
|
+
function flattenTree(node) {
|
|
176
|
+
const result = [];
|
|
177
|
+
if (!node || typeof node !== 'object')
|
|
178
|
+
return result;
|
|
179
|
+
// 可点击的叶子节点(canClick=true 或 type=2 部门)
|
|
180
|
+
if (node.canClick === true || String(node.canClick) === 'true' || node.type === '2') {
|
|
181
|
+
if (node.id != null)
|
|
182
|
+
result.push(node);
|
|
183
|
+
}
|
|
184
|
+
// 递归子节点
|
|
185
|
+
if (Array.isArray(node.subs)) {
|
|
186
|
+
for (const sub of node.subs) {
|
|
187
|
+
result.push(...flattenTree(sub));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
function stripHtml(s) {
|
|
193
|
+
return s.replace(/<[^>]*>/g, '').trim();
|
|
194
|
+
}
|
package/dist/wfops.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createApiFromProfile } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* P1-3 操作门禁:POST /api/workflow/reqform/rightMenu(HAR 已抓包)。
|
|
4
|
+
* 取流程的操作按钮权限(同意/退回/转办/转发/抄送/加审 等是否可用),
|
|
5
|
+
* 对标 e10 的 canSubmitRejectTransfer。
|
|
6
|
+
*/
|
|
7
|
+
export async function getRightMenu(cfg, requestId) {
|
|
8
|
+
const api = await createApiFromProfile(cfg);
|
|
9
|
+
const r0 = await api.post('/api/workflow/reqform/loadForm', { iscreate: '0', requestid: requestId });
|
|
10
|
+
const p = r0.json?.params ?? {};
|
|
11
|
+
const wfId = String(p.workflowid ?? '');
|
|
12
|
+
const nodeId = String(p.nodeid ?? '');
|
|
13
|
+
const userId = String(p.f_weaver_belongto_userid ?? '1');
|
|
14
|
+
const res = await api.post('/api/workflow/reqform/rightMenu', {
|
|
15
|
+
requestid: requestId,
|
|
16
|
+
workflowid: wfId,
|
|
17
|
+
nodeid: nodeId,
|
|
18
|
+
formid: String(p.formid ?? ''),
|
|
19
|
+
isbill: String(p.isbill ?? '1'),
|
|
20
|
+
'f_weaver_belongto_userid': userId,
|
|
21
|
+
'f_weaver_belongto_usertype': String(p.f_weaver_belongto_usertype ?? '0'),
|
|
22
|
+
authStr: String(p.authStr ?? ''),
|
|
23
|
+
authSignatureStr: String(p.authSignatureStr ?? ''),
|
|
24
|
+
signatureSecretKey: String(p.signatureSecretKey ?? ''),
|
|
25
|
+
signatureAttributesStr: String(p.signatureAttributesStr ?? ''),
|
|
26
|
+
wfTestStr: '',
|
|
27
|
+
});
|
|
28
|
+
const txt = res.text ?? '';
|
|
29
|
+
const has = (kw) => new RegExp(`"${kw}"\\s*:\\s*(true|1|"1")`, 'i').test(txt) || new RegExp(`"${kw}"`, 'i').test(txt);
|
|
30
|
+
return {
|
|
31
|
+
requestId,
|
|
32
|
+
canSubmit: has('submit') || has('agree'),
|
|
33
|
+
canReject: has('reject') || has('rejecttype'),
|
|
34
|
+
canTransfer: has('transfer') || has('turntodo'),
|
|
35
|
+
canForward: has('forward'),
|
|
36
|
+
canCc: has('cc') || has('remark'),
|
|
37
|
+
raw: res.json ?? null,
|
|
38
|
+
rawText: txt.slice(0, 600),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getAtAllList } from './blog.js';
|
|
2
|
+
/**
|
|
3
|
+
* 人员姓名 → userId(P0-6,实跑确认可用)。
|
|
4
|
+
* 用 blog 模块的 getAtAllList:GET /api/blog/at/getAtAllList?name=<关键词>,
|
|
5
|
+
* 返回 operators[].{uid, data(姓名)}。实跑搜 "wy2" → uid=2310 成功。
|
|
6
|
+
* (旧版用 /api/public/browser/data/1 + searchValue 搜不到——cmd/搜索参数未抓到,已弃用。)
|
|
7
|
+
*
|
|
8
|
+
* @param name 人员姓名(或工号)
|
|
9
|
+
* @returns {id, name}
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveUserName(cfg, name) {
|
|
12
|
+
const r = await getAtAllList(cfg, { name: String(name), pageSize: 20 });
|
|
13
|
+
const items = r.items ?? [];
|
|
14
|
+
if (items.length === 0)
|
|
15
|
+
throw new Error(`未找到人员「${name}」。请改用 --receiver <userId> 直接传 ID。`);
|
|
16
|
+
// 优先精确匹配姓名
|
|
17
|
+
const exact = items.filter((it) => it.name === name);
|
|
18
|
+
const pool = exact.length ? exact : items;
|
|
19
|
+
if (pool.length > 1) {
|
|
20
|
+
const list = pool.slice(0, 5).map((it) => `${it.uid}=${it.name}`).join(', ');
|
|
21
|
+
throw new Error(`人员「${name}」匹配到多个:${list}。请改用 --receiver <userId> 指定确切 ID。`);
|
|
22
|
+
}
|
|
23
|
+
const it = pool[0];
|
|
24
|
+
return { id: String(it.uid), name: it.name };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 把 opts.receiver / opts.receiverName 解析成逗号分隔的 userId 串(forwardOperators 用)。
|
|
28
|
+
* 同时返回接收人 userId 列表(经办人字段填值用)。
|
|
29
|
+
*/
|
|
30
|
+
export async function resolveReceivers(cfg, opts) {
|
|
31
|
+
if (opts.receiver)
|
|
32
|
+
return String(opts.receiver);
|
|
33
|
+
if (opts.receiverName) {
|
|
34
|
+
const names = String(opts.receiverName).split(/[,,]/).map((s) => s.trim()).filter(Boolean);
|
|
35
|
+
const ids = [];
|
|
36
|
+
for (const n of names) {
|
|
37
|
+
const r = await resolveUserName(cfg, n);
|
|
38
|
+
ids.push(r.id);
|
|
39
|
+
}
|
|
40
|
+
return ids.join(',');
|
|
41
|
+
}
|
|
42
|
+
return '';
|
|
43
|
+
}
|