weaver-e9-assistant-cli 1.1.3 → 1.1.4
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/cli.js +81 -11
- package/dist/hrm.js +30 -0
- package/dist/subordinate.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ import { collectQueryableBusiness } from './report.js';
|
|
|
31
31
|
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
32
|
import { getCurrentUser, getCurrentUserIsAdmin } from './wfuser.js';
|
|
33
33
|
import { getSubordinates, assertUserIdsInScope, assertNamesInScope, isScopeEnforced } from './subordinate.js';
|
|
34
|
-
import { resolveHrmTab, getHrmTabs, getHrmConditions, resolveHrmCondition, hrmList, buildHrmCardUrl, findHrmOrgNode, applyResolvedPersonIds } from './hrm.js';
|
|
34
|
+
import { resolveHrmTab, getHrmTabs, getHrmConditions, resolveHrmCondition, hrmList, buildHrmCardUrl, findHrmOrgNode, applyResolvedPersonIds, expandHrmOrgTree } from './hrm.js';
|
|
35
35
|
import { parseArgs, flagStr, flagBool } from './args.js';
|
|
36
36
|
import { todayStr } from './util.js';
|
|
37
37
|
import { BOX_TOP, DIVIDER } from './render.js';
|
|
@@ -146,6 +146,7 @@ function usage() {
|
|
|
146
146
|
' e9 cube find <名称> 按名模糊匹配台账(列候选消歧,返回 customid+mode+app,供 list/create 等用)',
|
|
147
147
|
' e9 cube find <名称> --tree-detail 展开 tree 测试类台账的全部树节点路径(默认只显首条菜单路径)',
|
|
148
148
|
' e9 subordinate list [--name <姓名>] [--all-level] [--page <n>] [--json] [--raw] [--refresh] 查询我的下属(默认直接下属;--all-level 全部层级)',
|
|
149
|
+
' e9 hrm tree [--id <组织id>] [--type <1分部|2部门>] [--depth <n>] [--dept <部门名|id>] [--subcompany <分部名|id>] [--json] 组织架构树',
|
|
149
150
|
' e9 hrm tabs 列出人事搜索范围(全部人员/同部门/我的下属)',
|
|
150
151
|
' e9 hrm conditions [--tab <全部人员|同部门|我的下属>] 列出该范围可用搜索条件(label→domkey)',
|
|
151
152
|
' e9 hrm search [--tab <全部人员|同部门|我的下属>] [--name <姓名>] [--condition "岗位=经理;性别=男"]',
|
|
@@ -1747,15 +1748,16 @@ async function runBlog(args) {
|
|
|
1747
1748
|
return { ...it, matchType, _sort: matchType === 'exact' ? 0 : matchType === 'prefix' ? 1 : 2 };
|
|
1748
1749
|
});
|
|
1749
1750
|
annotated.sort((a, b) => a._sort - b._sort);
|
|
1751
|
+
const atBase = endpointFor(cfg).baseUrl.replace(/\/+$/, '');
|
|
1750
1752
|
if (args.flags.json) {
|
|
1751
|
-
console.log(JSON.stringify({ pageIndex: r.pageIndex, pageSize: r.pageSize, noMore: r.noMore, query: q || undefined, items: annotated.map(({ _sort, ...rest }) => rest) }, null, 2));
|
|
1753
|
+
console.log(JSON.stringify({ pageIndex: r.pageIndex, pageSize: r.pageSize, noMore: r.noMore, query: q || undefined, items: annotated.map(({ _sort, ...rest }) => withLink('hrm', { id: rest.uid, name: rest.name }, atBase, rest)) }, null, 2));
|
|
1752
1754
|
return;
|
|
1753
1755
|
}
|
|
1754
1756
|
console.log(`E9 微博 @ 提及用户列表 [第 ${r.pageIndex} 页]${r.noMore ? '' : '(还有更多)'} 共 ${annotated.length} 条${q ? ` · 搜索「${flagStr(args, 'name')}」` : ''}`);
|
|
1755
1757
|
for (let i = 0; i < annotated.length; i++) {
|
|
1756
1758
|
const it = annotated[i];
|
|
1757
1759
|
const tag = it.matchType === 'exact' ? ' ★精确匹配' : it.matchType === 'prefix' ? ' ·前缀' : '';
|
|
1758
|
-
console.log(`${i + 1}. ${it.name} (uid=${it.uid})${tag}`);
|
|
1760
|
+
console.log(`${i + 1}. ${hyperlinkFor('hrm', { id: it.uid }, atBase, it.name)} (uid=${it.uid})${tag}`);
|
|
1759
1761
|
}
|
|
1760
1762
|
return;
|
|
1761
1763
|
}
|
|
@@ -3781,8 +3783,9 @@ async function runSubordinate(args) {
|
|
|
3781
3783
|
console.error(JSON.stringify(r.raw.counts, null, 2));
|
|
3782
3784
|
console.error('===== 解析结果 =====');
|
|
3783
3785
|
}
|
|
3786
|
+
const subBase = endpointFor(cfg).baseUrl.replace(/\/+$/, '');
|
|
3784
3787
|
if (args.flags.json) {
|
|
3785
|
-
console.log(JSON.stringify({ lastname, allLevel, page, pageSize: r.pageSize, total: r.total, count: r.count, items: r.items.map((it) => ({ id: it.id, name: it.name, fields: it.fields })) }, null, 2));
|
|
3788
|
+
console.log(JSON.stringify({ lastname, allLevel, page, pageSize: r.pageSize, total: r.total, count: r.count, items: r.items.map((it) => withLink('hrm', { id: it.id, name: it.name, fields: it.fields }, subBase)) }, null, 2));
|
|
3786
3789
|
return;
|
|
3787
3790
|
}
|
|
3788
3791
|
console.log(`我的下属${allLevel ? '(全部层级)' : '(直接下属)'}${name ? ` · 姓名过滤「${name}」` : ''} [第 ${page} 页${r.pageSize != null ? ` · 每页 ${r.pageSize} 条` : ''}${r.total != null ? ` / 共 ${r.total} 人` : ''}] 当前 ${r.count} 人`);
|
|
@@ -3790,13 +3793,16 @@ async function runSubordinate(args) {
|
|
|
3790
3793
|
console.log('(无下属)');
|
|
3791
3794
|
return;
|
|
3792
3795
|
}
|
|
3793
|
-
|
|
3796
|
+
// 表头同样排除 id 列(服务端把 id 列的标题误标为「是否在线」,列出来会误导)与 randomFieldId
|
|
3797
|
+
const hdrIdCol = r.items[0]?.idCol;
|
|
3798
|
+
const cols = (r.columns || []).filter((c) => c.dataIndex && c.dataIndex !== 'randomFieldId' && c.dataIndex !== hdrIdCol);
|
|
3794
3799
|
if (cols.length)
|
|
3795
3800
|
console.log(`字段(${cols.length}): ${cols.map((c) => c.title ?? c.dataIndex).join(' | ')}`);
|
|
3796
3801
|
for (let i = 0; i < r.items.length; i++) {
|
|
3797
3802
|
const it = r.items[i];
|
|
3798
|
-
console.log(`${i + 1}. ${it.name || '(无姓名)'} [id=${it.id || ''}]`);
|
|
3799
|
-
|
|
3803
|
+
console.log(`${i + 1}. ${hyperlinkFor('hrm', { id: it.id }, subBase, it.name || '(无姓名)')} [id=${it.id || ''}]`);
|
|
3804
|
+
// 排除内部列:id 列(其标题被服务端误标为「是否在线」)、姓名列(已作可点标题)、randomFieldId(id 的副本)
|
|
3805
|
+
const meta = it.fields.filter((f) => f.value && f.title && f.dataIndex !== it.idCol && f.dataIndex !== it.nameCol && f.dataIndex !== 'randomFieldId').slice(0, 6).map((f) => `${f.title}=${f.value}`).join(' | ');
|
|
3800
3806
|
if (meta)
|
|
3801
3807
|
console.log(` ${meta}`);
|
|
3802
3808
|
}
|
|
@@ -3804,6 +3810,18 @@ async function runSubordinate(args) {
|
|
|
3804
3810
|
|
|
3805
3811
|
// ─── 组织人事(HRM)搜索 ─────────────────────────────────
|
|
3806
3812
|
|
|
3813
|
+
/**
|
|
3814
|
+
* hrm search 结果里值为「字典编码」的列(dataIndex)。
|
|
3815
|
+
* E9 在人事搜索接口(hrm/search/getHrmSearchResult)里**不解析**这些字段——给的是 id/code,
|
|
3816
|
+
* 而同为人员列表的 subordinateinfo/getSearchResult 会解析(部门/分部/直接上级都给中文名)。
|
|
3817
|
+
* 实测:岗位 jobtitlespan 给的就是 "1830"、性别 sexspan 给 "1"。OA 的「查询人员」页同样只显示编码。
|
|
3818
|
+
* 故渲染时标注「(编码)」,避免用户把 1830 误读成名称。
|
|
3819
|
+
*/
|
|
3820
|
+
const HRM_CODE_FIELDS = new Set([
|
|
3821
|
+
'sex', 'accounttype', 'belongto', 'jobtitle', 'jobGroupId',
|
|
3822
|
+
'jobactivity', 'jobcall', 'joblevel', 'status', 'systemlanguage', 'locationid',
|
|
3823
|
+
]);
|
|
3824
|
+
|
|
3807
3825
|
/** 组织节点名称 → id(用于 --dept/--subcompany 名称定位;纯数字原样直传)。 */
|
|
3808
3826
|
async function resolveHrmOrgValue(cfg, keyword, { matchType, flag }) {
|
|
3809
3827
|
const node = await findHrmOrgNode(cfg, keyword, { matchType });
|
|
@@ -3859,8 +3877,56 @@ async function runHrm(args) {
|
|
|
3859
3877
|
}
|
|
3860
3878
|
return;
|
|
3861
3879
|
}
|
|
3880
|
+
if (sub === 'tree') {
|
|
3881
|
+
// 组织架构树:无参列顶层分部;--dept/--subcompany 定位到该组织后展开;--id(+可选 --type)直接指定起点;--depth 展示层数
|
|
3882
|
+
const depth = Math.min(Math.max(Number(flagStr(args, 'depth') ?? '1') || 1, 1), 5);
|
|
3883
|
+
const deptSpec = flagStr(args, 'dept');
|
|
3884
|
+
const subSpec = flagStr(args, 'subcompany');
|
|
3885
|
+
let start = { type: '0', id: '0' };
|
|
3886
|
+
let located = null;
|
|
3887
|
+
if (deptSpec || subSpec) {
|
|
3888
|
+
const kw = deptSpec ?? subSpec;
|
|
3889
|
+
const wantType = deptSpec ? '2' : '1';
|
|
3890
|
+
const label = deptSpec ? '部门' : '分部';
|
|
3891
|
+
if (/^\d+$/.test(kw)) {
|
|
3892
|
+
located = { id: kw, type: wantType, name: kw };
|
|
3893
|
+
}
|
|
3894
|
+
else {
|
|
3895
|
+
located = await findHrmOrgNode(cfg, kw, { matchType: wantType });
|
|
3896
|
+
if (!located)
|
|
3897
|
+
throw new Error(`未找到${label}「${kw}」。可先跑 e9 hrm tree 查看顶层分部,或改用数字 id。`);
|
|
3898
|
+
}
|
|
3899
|
+
start = { type: located.type, id: located.id };
|
|
3900
|
+
}
|
|
3901
|
+
else if (flagStr(args, 'id')) {
|
|
3902
|
+
start = { type: flagStr(args, 'type') ?? '1', id: flagStr(args, 'id') };
|
|
3903
|
+
}
|
|
3904
|
+
const r = await expandHrmOrgTree(cfg, { ...start, depth });
|
|
3905
|
+
if (args.flags.json) {
|
|
3906
|
+
console.log(JSON.stringify({ start, located, depth, nodes: r.nodes, total: r.total, truncated: r.truncated }, null, 2));
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
console.log(located
|
|
3910
|
+
? `E9 组织架构 · ${located.name || located.id}(type=${start.type} id=${start.id})`
|
|
3911
|
+
: 'E9 组织架构 · 顶层分部');
|
|
3912
|
+
if (!r.nodes.length) {
|
|
3913
|
+
console.log('(无下级组织)');
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3916
|
+
const render = (nodes, indent) => {
|
|
3917
|
+
for (const n of nodes) {
|
|
3918
|
+
const kind = n.type === '1' ? '分部' : n.type === '2' ? '部门' : `type=${n.type}`;
|
|
3919
|
+
console.log(`${indent}${n.name} [${kind} id=${n.id}]`);
|
|
3920
|
+
if (n.children.length)
|
|
3921
|
+
render(n.children, indent + ' ');
|
|
3922
|
+
}
|
|
3923
|
+
};
|
|
3924
|
+
render(r.nodes, ' ');
|
|
3925
|
+
console.log(`\n共 ${r.total} 个组织节点${r.truncated ? '(已达显示上限,结果被截断)' : ''}。`);
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3862
3928
|
if (sub !== 'search') {
|
|
3863
|
-
console.error(`hrm 子命令: tabs/conditions/search,收到: ${sub}`);
|
|
3929
|
+
console.error(`hrm 子命令: tabs/conditions/search/tree,收到: ${sub}`);
|
|
3864
3930
|
process.exit(1);
|
|
3865
3931
|
}
|
|
3866
3932
|
const tab = flagStr(args, 'tab') ?? '全部人员';
|
|
@@ -3940,8 +4006,12 @@ async function runHrm(args) {
|
|
|
3940
4006
|
const it = r.items[i];
|
|
3941
4007
|
const head = hyperlinkFor('hrm', it, base, it.name || '(无姓名)');
|
|
3942
4008
|
console.log(`${i + 1}. ${head} [id=${it.id || ''}]`);
|
|
3943
|
-
// 行 meta 去掉姓名(head 已显示)与 id
|
|
3944
|
-
const meta = it.fields
|
|
4009
|
+
// 行 meta 去掉姓名(head 已显示)与 id 类列,避免重复;字典编码字段标注「(编码)」
|
|
4010
|
+
const meta = it.fields
|
|
4011
|
+
.filter((f) => !['lastname', 'id', 'randomField0', 'randomFieldId'].includes(f.dataIndex))
|
|
4012
|
+
.slice(0, 8)
|
|
4013
|
+
.map((f) => `${f.title}=${f.value}${f.value && HRM_CODE_FIELDS.has(f.dataIndex) ? '(编码)' : ''}`)
|
|
4014
|
+
.join(' | ');
|
|
3945
4015
|
if (meta)
|
|
3946
4016
|
console.log(` ${meta}`);
|
|
3947
4017
|
}
|
|
@@ -4046,7 +4116,7 @@ async function main() {
|
|
|
4046
4116
|
const cmd = args._[0] ?? 'todo';
|
|
4047
4117
|
if (cmd === 'version') {
|
|
4048
4118
|
const buildHash = createHash('sha256').update(fs.readFileSync(new URL(import.meta.url))).digest('hex').slice(0, 12);
|
|
4049
|
-
console.log(JSON.stringify({ name: 'weaver-e9-assistant-cli', version: '1.1.
|
|
4119
|
+
console.log(JSON.stringify({ name: 'weaver-e9-assistant-cli', version: '1.1.4', buildHash }, null, 2));
|
|
4050
4120
|
return;
|
|
4051
4121
|
}
|
|
4052
4122
|
if (cmd === 'whoami') {
|
package/dist/hrm.js
CHANGED
|
@@ -411,6 +411,36 @@ export async function findHrmOrgNode(cfg, keyword, { matchType = null, maxDepth
|
|
|
411
411
|
return null;
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
/**
|
|
415
|
+
* 递归展开组织树(供 `e9 hrm tree` 用)。
|
|
416
|
+
* 起点 (type,id):'0'/'0' = 顶层分部;'1'/<分部id> = 子分部+部门;'2'/<部门id> = 子部门。
|
|
417
|
+
* depth = 展示层数(1 = 只列起点下一层);maxNodes 防失控(超限截断,调用方须提示)。
|
|
418
|
+
*/
|
|
419
|
+
export async function expandHrmOrgTree(cfg, { type = '0', id = '0', depth = 1, maxNodes = 500 } = {}) {
|
|
420
|
+
let count = 0;
|
|
421
|
+
let truncated = false;
|
|
422
|
+
async function walk(t, i, levels) {
|
|
423
|
+
if (levels < 1)
|
|
424
|
+
return [];
|
|
425
|
+
const r = await getHrmSearchTree(cfg, { type: t, id: i });
|
|
426
|
+
const out = [];
|
|
427
|
+
for (const n of r.datas) {
|
|
428
|
+
if (count >= maxNodes) {
|
|
429
|
+
truncated = true;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
count++;
|
|
433
|
+
const node = { id: n.id, name: n.name, type: n.type, isParent: n.isParent, children: [] };
|
|
434
|
+
if (n.isParent && levels > 1)
|
|
435
|
+
node.children = await walk(n.type, n.id, levels - 1);
|
|
436
|
+
out.push(node);
|
|
437
|
+
}
|
|
438
|
+
return out;
|
|
439
|
+
}
|
|
440
|
+
const nodes = await walk(type, id, depth);
|
|
441
|
+
return { nodes, total: count, truncated };
|
|
442
|
+
}
|
|
443
|
+
|
|
414
444
|
// ─── 编排:完整搜索 ──────────────────────────────────────
|
|
415
445
|
|
|
416
446
|
/**
|
package/dist/subordinate.js
CHANGED
|
@@ -116,7 +116,7 @@ export function normalizeSubordinateRow(row, columns) {
|
|
|
116
116
|
title: c.title,
|
|
117
117
|
value: stripHtml(get(`${c.dataIndex}span`) ?? get(c.dataIndex)),
|
|
118
118
|
}));
|
|
119
|
-
return { id, name, fields, raw: row };
|
|
119
|
+
return { id, name, fields, idCol: idCol?.dataIndex, nameCol: nameCol?.dataIndex, raw: row };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|