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 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
- const cols = (r.columns || []).filter((c) => c.dataIndex && c.dataIndex !== 'randomFieldId');
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
- const meta = it.fields.filter((f) => f.value && f.title && f.dataIndex !== it.idCol).slice(0, 6).map((f) => `${f.title}=${f.value}`).join(' | ');
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.filter((f) => !['lastname', 'id', 'randomField0', 'randomFieldId'].includes(f.dataIndex)).slice(0, 8).map((f) => `${f.title}=${f.value}`).join(' | ');
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.3', buildHash }, null, 2));
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
  /**
@@ -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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weaver-e9-assistant-cli",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "泛微 e-cology E9 HTTP 能力 CLI 封装(待办、微博、新建流程、邮件、公文、日程、考勤、建模台账)",
5
5
  "type": "module",
6
6
  "bin": {