vps-work 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/packages/vps-work/src/bin/vps-work.js +271 -0
  2. package/dist/packages/vps-work/src/commands/agent/index.js +264 -0
  3. package/dist/packages/vps-work/src/commands/approval/index.js +7 -0
  4. package/dist/packages/vps-work/src/commands/approval/shortcuts.js +259 -0
  5. package/dist/packages/vps-work/src/commands/attendance/applications.js +21 -0
  6. package/dist/packages/vps-work/src/commands/attendance/index.js +9 -0
  7. package/dist/packages/vps-work/src/commands/attendance/scheduling.js +183 -0
  8. package/dist/packages/vps-work/src/commands/attendance/shortcuts.js +131 -0
  9. package/dist/packages/vps-work/src/commands/auth/check.js +34 -0
  10. package/dist/packages/vps-work/src/commands/auth/index.js +21 -0
  11. package/dist/packages/vps-work/src/commands/auth/list.js +50 -0
  12. package/dist/packages/vps-work/src/commands/auth/scopes.js +17 -0
  13. package/dist/packages/vps-work/src/commands/auth/status.js +127 -0
  14. package/dist/packages/vps-work/src/commands/auth/tenants.js +35 -0
  15. package/dist/packages/vps-work/src/commands/auth/use.js +40 -0
  16. package/dist/packages/vps-work/src/commands/calendar/index.js +7 -0
  17. package/dist/packages/vps-work/src/commands/calendar/shortcuts.js +598 -0
  18. package/dist/packages/vps-work/src/commands/catalog.js +29 -0
  19. package/dist/packages/vps-work/src/commands/cli-install.js +70 -0
  20. package/dist/packages/vps-work/src/commands/collab-invoke.js +19 -0
  21. package/dist/packages/vps-work/src/commands/corporate/index.js +204 -0
  22. package/dist/packages/vps-work/src/commands/creative/index.js +369 -0
  23. package/dist/packages/vps-work/src/commands/documents/index.js +991 -0
  24. package/dist/packages/vps-work/src/commands/drive/index.js +435 -0
  25. package/dist/packages/vps-work/src/commands/exec.js +46 -0
  26. package/dist/packages/vps-work/src/commands/hr/business.js +22 -0
  27. package/dist/packages/vps-work/src/commands/hr/data-view-shortcuts.js +519 -0
  28. package/dist/packages/vps-work/src/commands/hr/index.js +15 -0
  29. package/dist/packages/vps-work/src/commands/hr/job-responsibilities.js +266 -0
  30. package/dist/packages/vps-work/src/commands/hr/payslip.js +45 -0
  31. package/dist/packages/vps-work/src/commands/hr/shortcuts.js +146 -0
  32. package/dist/packages/vps-work/src/commands/im/im-message-payload.js +150 -0
  33. package/dist/packages/vps-work/src/commands/im/index.js +7 -0
  34. package/dist/packages/vps-work/src/commands/im/shortcuts.js +856 -0
  35. package/dist/packages/vps-work/src/commands/knowledge/index.js +36 -0
  36. package/dist/packages/vps-work/src/commands/login.js +52 -0
  37. package/dist/packages/vps-work/src/commands/logout.js +12 -0
  38. package/dist/packages/vps-work/src/commands/mail/email.js +132 -0
  39. package/dist/packages/vps-work/src/commands/mail/index.js +9 -0
  40. package/dist/packages/vps-work/src/commands/mail/shortcuts.js +43 -0
  41. package/dist/packages/vps-work/src/commands/meeting/index.js +7 -0
  42. package/dist/packages/vps-work/src/commands/meeting/shortcuts.js +120 -0
  43. package/dist/packages/vps-work/src/commands/odoo-invoke.js +21 -0
  44. package/dist/packages/vps-work/src/commands/odoo-leaf-registry.js +13 -0
  45. package/dist/packages/vps-work/src/commands/office/index.js +59 -0
  46. package/dist/packages/vps-work/src/commands/okr/index.js +7 -0
  47. package/dist/packages/vps-work/src/commands/okr/shortcuts.js +75 -0
  48. package/dist/packages/vps-work/src/commands/org-structure.js +347 -0
  49. package/dist/packages/vps-work/src/commands/popup/index.js +147 -0
  50. package/dist/packages/vps-work/src/commands/product/abnormal-statistics.js +250 -0
  51. package/dist/packages/vps-work/src/commands/product/data-view-shortcuts.js +253 -0
  52. package/dist/packages/vps-work/src/commands/product/index.js +11 -0
  53. package/dist/packages/vps-work/src/commands/purchase/index.js +45 -0
  54. package/dist/packages/vps-work/src/commands/reauth.js +127 -0
  55. package/dist/packages/vps-work/src/commands/report/index.js +7 -0
  56. package/dist/packages/vps-work/src/commands/report/missing-daily-reports.js +9 -0
  57. package/dist/packages/vps-work/src/commands/report/shortcuts.js +251 -0
  58. package/dist/packages/vps-work/src/commands/sales/index.js +11 -0
  59. package/dist/packages/vps-work/src/commands/sales/period-params.js +75 -0
  60. package/dist/packages/vps-work/src/commands/sales/shortcuts-personnel.js +124 -0
  61. package/dist/packages/vps-work/src/commands/sales/shortcuts.js +800 -0
  62. package/dist/packages/vps-work/src/commands/shortcut-common.js +88 -0
  63. package/dist/packages/vps-work/src/commands/shortcut-params.js +22 -0
  64. package/dist/packages/vps-work/src/commands/skill/index.js +136 -0
  65. package/dist/packages/vps-work/src/commands/skill/personal.js +167 -0
  66. package/dist/packages/vps-work/src/commands/store/index.js +82 -0
  67. package/dist/packages/vps-work/src/commands/task/index.js +7 -0
  68. package/dist/packages/vps-work/src/commands/task/task-center.js +595 -0
  69. package/dist/packages/vps-work/src/commands/tools/index.js +127 -0
  70. package/dist/packages/vps-work/src/commands/vemory/index.js +7 -0
  71. package/dist/packages/vps-work/src/commands/vemory/shortcuts.js +53 -0
  72. package/dist/packages/vps-work/src/commands/vertu-aisales.js +30 -0
  73. package/dist/packages/vps-work/src/commands/vertu-erp.js +30 -0
  74. package/dist/packages/vps-work/src/core/aisales-mcp-client.js +35 -0
  75. package/dist/packages/vps-work/src/core/auth.js +240 -0
  76. package/dist/packages/vps-work/src/core/cli-bin.js +11 -0
  77. package/dist/packages/vps-work/src/core/cli-root-help.js +190 -0
  78. package/dist/packages/vps-work/src/core/client.js +158 -0
  79. package/dist/packages/vps-work/src/core/config.js +183 -0
  80. package/dist/packages/vps-work/src/core/corporate-client.js +52 -0
  81. package/dist/packages/vps-work/src/core/creative-attachment-download.js +48 -0
  82. package/dist/packages/vps-work/src/core/documents-client.js +57 -0
  83. package/dist/packages/vps-work/src/core/endpoints.js +46 -0
  84. package/dist/packages/vps-work/src/core/erp-mcp-client.js +11 -0
  85. package/dist/packages/vps-work/src/core/errors.js +107 -0
  86. package/dist/packages/vps-work/src/core/execute-catalog-client.js +42 -0
  87. package/dist/packages/vps-work/src/core/hermes-client.js +70 -0
  88. package/dist/packages/vps-work/src/core/im-attachment-download.js +109 -0
  89. package/dist/packages/vps-work/src/core/im-client.js +64 -0
  90. package/dist/packages/vps-work/src/core/keychain.js +58 -0
  91. package/dist/packages/vps-work/src/core/mail-client.js +82 -0
  92. package/dist/packages/vps-work/src/core/mcp-client.js +129 -0
  93. package/dist/packages/vps-work/src/core/office-client.js +25 -0
  94. package/dist/packages/vps-work/src/core/office-files.js +82 -0
  95. package/dist/packages/vps-work/src/core/paths.js +70 -0
  96. package/dist/packages/vps-work/src/core/public-result.js +43 -0
  97. package/dist/packages/vps-work/src/core/sales-invoice-attachments-client.js +75 -0
  98. package/dist/packages/vps-work/src/core/sales-query-client.js +452 -0
  99. package/dist/packages/vps-work/src/core/skill-catalog.js +158 -0
  100. package/dist/packages/vps-work/src/core/skill-key.js +19 -0
  101. package/dist/packages/vps-work/src/core/v-drive-store.js +392 -0
  102. package/dist/packages/vps-work/src/core/vertu-agent-env.js +32 -0
  103. package/dist/packages/vps-work/src/core/vps-auth-scopes.js +73 -0
  104. package/dist/packages/vps-work/src/core/vps-service-auth.js +382 -0
  105. package/dist/packages/vps-work/src/core/vps-service-config.js +83 -0
  106. package/dist/packages/vps-work/src/core/vps-service-http.js +187 -0
  107. package/dist/packages/vps-work/src/utils/at-file.js +34 -0
  108. package/dist/packages/vps-work/src/utils/cli-option-helpers.js +52 -0
  109. package/dist/packages/vps-work/src/utils/frontmatter.js +26 -0
  110. package/dist/packages/vps-work/src/utils/normalize-argv.js +51 -0
  111. package/dist/packages/vps-work/src/utils/parse-json-arg.js +85 -0
  112. package/dist/packages/vps-work/src/utils/prompt.js +46 -0
  113. package/dist/packages/vps-work/src/utils/read-text.js +44 -0
  114. package/dist/packages/vps-work/src/utils/schema-to-options.js +307 -0
  115. package/dist/packages/vps-work/src/utils/unwrap-envelope.js +31 -0
  116. package/dist/shared/im-org-tree/index.js +200 -0
  117. package/dist/vps-work.cjs +3 -3
  118. package/package.json +1 -1
  119. package/skills/vertu-aisales/SKILL.md +4 -2
@@ -0,0 +1,519 @@
1
+ import { VertuError } from '../../core/errors.js';
2
+ import { runVpsServiceDataView, vpsServiceGet } from '../../core/sales-query-client.js';
3
+ import { attachSharedShortcutOptions, mergeShortcutParams, parseOptionalInt, resolveShortcutVpsServiceUrl, } from '../shortcut-common.js';
4
+ const HR_PERSONNEL_VIEW = 'hr-personnel-directory';
5
+ export function applyHrDirectoryScope(filters, includeNonDirectory = false) {
6
+ if (includeNonDirectory) {
7
+ return { ...filters };
8
+ }
9
+ return { ...filters, directory_visible: true };
10
+ }
11
+ export function assertHrDirectoryScopeApplied(result, includeNonDirectory = false) {
12
+ if (includeNonDirectory) {
13
+ return;
14
+ }
15
+ const resultRecord = asRecord(result);
16
+ const scope = asRecord(resultRecord?.scope);
17
+ if (scope?.contacts_directory === true) {
18
+ return;
19
+ }
20
+ throw new VertuError('当前 vps-service 尚未应用通讯录人员口径,已拒绝返回可能错误的 HR 结果。请先部署包含 directory_visible 支持的 vps-service。');
21
+ }
22
+ function printResult(opts, result) {
23
+ if (opts.json !== false) {
24
+ console.log(JSON.stringify(result, null, 2));
25
+ return;
26
+ }
27
+ console.log(typeof result === 'string' ? result : JSON.stringify(result));
28
+ }
29
+ function outputRows(value) {
30
+ const record = asRecord(value);
31
+ const rows = record ? record.rows : undefined;
32
+ return Array.isArray(rows)
33
+ ? rows.filter((row) => asRecord(row) !== null)
34
+ : [];
35
+ }
36
+ function isValidateOnly(opts) {
37
+ return opts.validateOnly === true || opts.dryRun === true;
38
+ }
39
+ function asRecord(value) {
40
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
41
+ ? value
42
+ : null;
43
+ }
44
+ function numberValue(value) {
45
+ if (typeof value === 'number' && Number.isFinite(value)) {
46
+ return value;
47
+ }
48
+ if (typeof value === 'string' && value.trim()) {
49
+ const n = Number(value);
50
+ return Number.isFinite(n) ? n : undefined;
51
+ }
52
+ return undefined;
53
+ }
54
+ function stringValue(value) {
55
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
56
+ }
57
+ function findIdentityRecord(payload) {
58
+ const queue = [payload];
59
+ const seen = new Set();
60
+ while (queue.length) {
61
+ const current = queue.shift();
62
+ if (!current || seen.has(current)) {
63
+ continue;
64
+ }
65
+ seen.add(current);
66
+ const record = asRecord(current);
67
+ if (!record) {
68
+ continue;
69
+ }
70
+ if ('employee_id' in record ||
71
+ 'employeeId' in record ||
72
+ 'manager_id' in record ||
73
+ 'managerEmployeeId' in record ||
74
+ 'user_id' in record ||
75
+ 'employee' in record ||
76
+ 'manager' in record) {
77
+ return record;
78
+ }
79
+ for (const key of ['result', 'data', 'payload', 'context', 'user', 'userinfo']) {
80
+ if (key in record) {
81
+ queue.push(record[key]);
82
+ }
83
+ }
84
+ }
85
+ return {};
86
+ }
87
+ export function extractHrIdentity(payload) {
88
+ const record = findIdentityRecord(payload);
89
+ const employee = asRecord(record.employee);
90
+ const manager = asRecord(record.manager);
91
+ return {
92
+ employeeId: numberValue(record.employee_id) ??
93
+ numberValue(record.employeeId) ??
94
+ numberValue(employee?.id),
95
+ employeeName: stringValue(record.employee_name) ??
96
+ stringValue(record.employeeName) ??
97
+ stringValue(employee?.name) ??
98
+ stringValue(record.name),
99
+ managerEmployeeId: numberValue(record.manager_id) ??
100
+ numberValue(record.manager_employee_id) ??
101
+ numberValue(record.managerEmployeeId) ??
102
+ numberValue(manager?.id),
103
+ managerName: stringValue(record.manager_name) ??
104
+ stringValue(record.managerName) ??
105
+ stringValue(manager?.name),
106
+ managerUserId: numberValue(record.manager_user_id) ??
107
+ numberValue(record.managerUserId) ??
108
+ numberValue(manager?.user_id) ??
109
+ numberValue(manager?.userId),
110
+ };
111
+ }
112
+ async function fetchCurrentHrIdentity(opts, cmd) {
113
+ const serviceBaseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
114
+ const payload = await vpsServiceGet('/v1/auth/me', {}, serviceBaseUrl, 'Vertu 身份');
115
+ return extractHrIdentity(payload);
116
+ }
117
+ function requireEmployeeId(identity, purpose) {
118
+ if (!identity.employeeId) {
119
+ throw new VertuError(`当前登录用户没有 employee_id,无法查询${purpose}。请先执行 \`vps-work hr +me\` 核对身份。`);
120
+ }
121
+ return identity.employeeId;
122
+ }
123
+ function addPersonnelOptions(cmd, limitDefault = '100') {
124
+ return addDirectoryScopeOption(cmd)
125
+ .option('--name <name>', '姓名包含')
126
+ .option('--barcode <code>', '工号包含')
127
+ .option('--employee-id <id>', 'hr.employee id')
128
+ .option('--user-id <id>', 'res.users id')
129
+ .option('--manager-employee-id <id>', '直属上级 hr.employee id')
130
+ .option('--manager-user-id <id>', '直属上级 res.users id')
131
+ .option('--manager-name <name>', '直属上级姓名包含')
132
+ .option('--status <name>', '在职状态包含')
133
+ .option('--department <name>', '当前部门包含')
134
+ .option('--department-l1 <name>', '一级部门包含')
135
+ .option('--department-l2 <name>', '二级部门包含')
136
+ .option('--job <name>', '工作岗位包含')
137
+ .option('--job-name <name>', '职位档案名称包含')
138
+ .option('--contract-status <status>', '合同状态包含')
139
+ .option('--limit <n>', '返回行数', limitDefault);
140
+ }
141
+ function addDirectoryScopeOption(cmd) {
142
+ return cmd.option('--include-non-directory', '包含授权部门内通讯录不可见人员(需要 hr.profile.history:read)', false);
143
+ }
144
+ function addNumberFilter(filters, key, raw, flag) {
145
+ const value = parseOptionalInt(raw, flag);
146
+ if (value !== undefined) {
147
+ filters[key] = value;
148
+ }
149
+ }
150
+ function buildPersonnelFilters(opts) {
151
+ const filters = {};
152
+ addNumberFilter(filters, 'employee_id', opts.employeeId, '--employee-id');
153
+ addNumberFilter(filters, 'user_id', opts.userId, '--user-id');
154
+ addNumberFilter(filters, 'manager_employee_id', opts.managerEmployeeId, '--manager-employee-id');
155
+ addNumberFilter(filters, 'manager_user_id', opts.managerUserId, '--manager-user-id');
156
+ if (opts.name) {
157
+ filters.name = opts.name;
158
+ }
159
+ if (opts.barcode) {
160
+ filters.barcode = opts.barcode;
161
+ }
162
+ if (opts.managerName) {
163
+ filters.manager_name = opts.managerName;
164
+ }
165
+ if (opts.status) {
166
+ filters.status = opts.status;
167
+ }
168
+ if (opts.department) {
169
+ filters.department = opts.department;
170
+ }
171
+ if (opts.departmentL1) {
172
+ filters.department_l1 = opts.departmentL1;
173
+ }
174
+ if (opts.departmentL2) {
175
+ filters.department_l2 = opts.departmentL2;
176
+ }
177
+ if (opts.job) {
178
+ filters.job = opts.job;
179
+ }
180
+ if (opts.jobName) {
181
+ filters.job_name = opts.jobName;
182
+ }
183
+ if (opts.contractStatus) {
184
+ filters.contract_status = opts.contractStatus;
185
+ }
186
+ return filters;
187
+ }
188
+ function hasPersonTarget(opts) {
189
+ return Boolean(opts.name ||
190
+ opts.barcode ||
191
+ opts.employeeId ||
192
+ opts.userId ||
193
+ opts.managerEmployeeId ||
194
+ opts.managerUserId ||
195
+ opts.managerName ||
196
+ opts.department ||
197
+ opts.departmentL1 ||
198
+ opts.departmentL2 ||
199
+ opts.job ||
200
+ opts.jobName);
201
+ }
202
+ async function runHrPersonnelView(opts, cmd, filters, limitDefault) {
203
+ const merged = applyHrDirectoryScope(await mergeShortcutParams(filters, opts), opts.includeNonDirectory);
204
+ const limit = parseOptionalInt(opts.limit, '--limit') ?? limitDefault;
205
+ const serviceBaseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
206
+ if (isValidateOnly(opts)) {
207
+ printResult(opts, {
208
+ validateOnly: true,
209
+ endpoint: `${serviceBaseUrl}/v1/data-view/query`,
210
+ payload: {
211
+ view_id: HR_PERSONNEL_VIEW,
212
+ filters: merged,
213
+ limit,
214
+ },
215
+ });
216
+ return;
217
+ }
218
+ const result = await runVpsServiceDataView(HR_PERSONNEL_VIEW, merged, limit, serviceBaseUrl);
219
+ assertHrDirectoryScopeApplied(result, opts.includeNonDirectory);
220
+ printResult(opts, result);
221
+ }
222
+ function dataViewRows(value) {
223
+ return outputRows(value);
224
+ }
225
+ export function dataViewTotalRows(value) {
226
+ const record = asRecord(value);
227
+ const pagination = asRecord(record?.pagination);
228
+ return numberValue(pagination?.total_rows) ?? 0;
229
+ }
230
+ function addHeadcountOptions(cmd) {
231
+ return addDirectoryScopeOption(cmd)
232
+ .option('--department <name>', '当前部门包含')
233
+ .option('--department-l1 <name>', '一级部门包含')
234
+ .option('--department-l2 <name>', '二级部门包含')
235
+ .option('--manager-employee-id <id>', '直属上级 hr.employee id')
236
+ .option('--manager-user-id <id>', '直属上级 res.users id')
237
+ .option('--manager-name <name>', '直属上级姓名包含')
238
+ .option('--job <name>', '工作岗位包含')
239
+ .option('--job-name <name>', '职位档案名称包含');
240
+ }
241
+ function buildHeadcountBaseFilters(opts) {
242
+ const filters = {};
243
+ addNumberFilter(filters, 'manager_employee_id', opts.managerEmployeeId, '--manager-employee-id');
244
+ addNumberFilter(filters, 'manager_user_id', opts.managerUserId, '--manager-user-id');
245
+ if (opts.managerName) {
246
+ filters.manager_name = opts.managerName;
247
+ }
248
+ if (opts.department) {
249
+ filters.department = opts.department;
250
+ }
251
+ if (opts.departmentL1) {
252
+ filters.department_l1 = opts.departmentL1;
253
+ }
254
+ if (opts.departmentL2) {
255
+ filters.department_l2 = opts.departmentL2;
256
+ }
257
+ if (opts.job) {
258
+ filters.job = opts.job;
259
+ }
260
+ if (opts.jobName) {
261
+ filters.job_name = opts.jobName;
262
+ }
263
+ return filters;
264
+ }
265
+ async function countPersonnelRows(serviceBaseUrl, filters, includeNonDirectory) {
266
+ const result = await runVpsServiceDataView(HR_PERSONNEL_VIEW, filters, 1, serviceBaseUrl);
267
+ assertHrDirectoryScopeApplied(result, includeNonDirectory);
268
+ return dataViewTotalRows(result);
269
+ }
270
+ async function runHeadcount(opts, cmd) {
271
+ const serviceBaseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
272
+ const baseFilters = applyHrDirectoryScope(await mergeShortcutParams(buildHeadcountBaseFilters(opts), opts), opts.includeNonDirectory);
273
+ const endpoint = `${serviceBaseUrl}/v1/data-view/query`;
274
+ const countPlan = [
275
+ ['total', baseFilters],
276
+ ['active', { ...baseFilters, status: '在职' }],
277
+ ['departed', { ...baseFilters, status: '离职' }],
278
+ ['external_excluded', { ...baseFilters, status: '外部' }],
279
+ ['active_regular', { ...baseFilters, status: '在职-正式员工' }],
280
+ ['active_probation', { ...baseFilters, status: '在职-试用期员工' }],
281
+ ['active_part_time', { ...baseFilters, status: '在职-兼职员工' }],
282
+ ['active_intern', { ...baseFilters, status: '在职-实习生' }],
283
+ ];
284
+ if (isValidateOnly(opts)) {
285
+ printResult(opts, {
286
+ validateOnly: true,
287
+ endpoint,
288
+ requests: countPlan.map(([key, filters]) => ({
289
+ key,
290
+ payload: {
291
+ view_id: HR_PERSONNEL_VIEW,
292
+ filters,
293
+ limit: 1,
294
+ },
295
+ })),
296
+ });
297
+ return;
298
+ }
299
+ const counts = Object.fromEntries(await Promise.all(countPlan.map(async ([key, filters]) => [
300
+ key,
301
+ await countPersonnelRows(serviceBaseUrl, filters, opts.includeNonDirectory === true),
302
+ ])));
303
+ const knownActiveBreakdown = counts.active_regular +
304
+ counts.active_probation +
305
+ counts.active_part_time +
306
+ counts.active_intern;
307
+ printResult(opts, {
308
+ view_id: HR_PERSONNEL_VIEW,
309
+ scope: opts.includeNonDirectory ? 'all_hr_personnel' : 'contacts',
310
+ filters: baseFilters,
311
+ summary: {
312
+ total_personnel_records: counts.total,
313
+ active_headcount: counts.active,
314
+ departed_records: counts.departed,
315
+ external_excluded: counts.external_excluded,
316
+ company_headcount: counts.active,
317
+ },
318
+ active_breakdown: {
319
+ regular: counts.active_regular,
320
+ probation: counts.active_probation,
321
+ part_time: counts.active_part_time,
322
+ intern: counts.active_intern,
323
+ other_active: Math.max(0, counts.active - knownActiveBreakdown),
324
+ },
325
+ note: opts.includeNonDirectory
326
+ ? '当前为完整 HR 档案口径;total_personnel_records 包含离职与外部不计人数档案。'
327
+ : '默认与通讯录可见人员口径一致;使用 --include-non-directory 查看完整 HR 档案。',
328
+ });
329
+ }
330
+ async function resolveCurrentManagerEmployeeId(identity, opts, cmd) {
331
+ if (identity.managerEmployeeId) {
332
+ return identity.managerEmployeeId;
333
+ }
334
+ const employeeId = requireEmployeeId(identity, '直属上司');
335
+ const serviceBaseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
336
+ const result = await runVpsServiceDataView(HR_PERSONNEL_VIEW, applyHrDirectoryScope({ employee_id: employeeId }, opts.includeNonDirectory), 1, serviceBaseUrl);
337
+ assertHrDirectoryScopeApplied(result, opts.includeNonDirectory);
338
+ const managerEmployeeId = numberValue(dataViewRows(result)[0]?.manager_employee_id);
339
+ if (!managerEmployeeId) {
340
+ throw new VertuError('当前登录用户没有 manager_employee_id,无法查询直属上司。');
341
+ }
342
+ return managerEmployeeId;
343
+ }
344
+ function parseMaxDepth(raw) {
345
+ const value = parseOptionalInt(raw, '--max-depth') ?? 20;
346
+ if (value < 1) {
347
+ throw new VertuError('--max-depth must be >= 1.');
348
+ }
349
+ return Math.min(value, 50);
350
+ }
351
+ function resultEnvelope(firstResult, rows) {
352
+ const first = asRecord(firstResult) ?? {};
353
+ return {
354
+ view: first.view,
355
+ columns: Array.isArray(first.columns)
356
+ ? ['level', 'manager_chain_text', ...first.columns.filter((column) => column !== 'level' && column !== 'manager_chain_text')]
357
+ : undefined,
358
+ rows,
359
+ pagination: {
360
+ page: 1,
361
+ page_size: rows.length,
362
+ offset: 0,
363
+ returned_rows: rows.length,
364
+ total_rows: rows.length,
365
+ total_pages: 1,
366
+ has_more: false,
367
+ next_page: null,
368
+ },
369
+ };
370
+ }
371
+ async function runRecursiveSubordinates(opts, cmd, identity) {
372
+ const rootEmployeeId = requireEmployeeId(identity, '下属');
373
+ const status = opts.status;
374
+ const limit = parseOptionalInt(opts.limit, '--limit') ?? 100;
375
+ const maxDepth = parseMaxDepth(opts.maxDepth);
376
+ const serviceBaseUrl = resolveShortcutVpsServiceUrl(opts, cmd);
377
+ if (isValidateOnly(opts)) {
378
+ const filters = { manager_employee_id: rootEmployeeId };
379
+ if (status) {
380
+ filters.status = status;
381
+ }
382
+ printResult(opts, {
383
+ validateOnly: true,
384
+ recursive: true,
385
+ root_manager_employee_id: rootEmployeeId,
386
+ max_depth: maxDepth,
387
+ endpoint: `${serviceBaseUrl}/v1/data-view/query`,
388
+ first_payload: {
389
+ view_id: HR_PERSONNEL_VIEW,
390
+ filters,
391
+ limit,
392
+ },
393
+ });
394
+ return;
395
+ }
396
+ const rows = [];
397
+ const seenEmployees = new Set();
398
+ let frontier = [
399
+ {
400
+ managerEmployeeId: rootEmployeeId,
401
+ level: 1,
402
+ managerChain: [identity.employeeName ?? `employee:${rootEmployeeId}`],
403
+ },
404
+ ];
405
+ let firstResult = null;
406
+ while (frontier.length && rows.length < limit) {
407
+ const next = [];
408
+ for (const item of frontier) {
409
+ if (item.level > maxDepth || rows.length >= limit) {
410
+ continue;
411
+ }
412
+ const filters = { manager_employee_id: item.managerEmployeeId };
413
+ if (status) {
414
+ filters.status = status;
415
+ }
416
+ const remaining = Math.max(1, limit - rows.length);
417
+ const result = await runVpsServiceDataView(HR_PERSONNEL_VIEW, applyHrDirectoryScope(filters, opts.includeNonDirectory), remaining, serviceBaseUrl);
418
+ assertHrDirectoryScopeApplied(result, opts.includeNonDirectory);
419
+ if (!firstResult) {
420
+ firstResult = result;
421
+ }
422
+ for (const row of dataViewRows(result)) {
423
+ const employeeId = numberValue(row.employee_id);
424
+ if (!employeeId || seenEmployees.has(employeeId)) {
425
+ continue;
426
+ }
427
+ seenEmployees.add(employeeId);
428
+ const name = stringValue(row.name) ?? `employee:${employeeId}`;
429
+ const chain = [...item.managerChain, name];
430
+ rows.push({
431
+ level: item.level,
432
+ manager_chain: item.managerChain,
433
+ manager_chain_text: item.managerChain.join(' / '),
434
+ ...row,
435
+ });
436
+ if (rows.length >= limit) {
437
+ break;
438
+ }
439
+ next.push({
440
+ managerEmployeeId: employeeId,
441
+ level: item.level + 1,
442
+ managerChain: chain,
443
+ });
444
+ }
445
+ }
446
+ frontier = next;
447
+ }
448
+ printResult(opts, firstResult ? resultEnvelope(firstResult, rows) : resultEnvelope({}, rows));
449
+ }
450
+ export function attachHrDataViewShortcuts(parent) {
451
+ attachSharedShortcutOptions(addPersonnelOptions(parent
452
+ .command('+personnel-info')
453
+ .description('HR人员信息(默认通讯录内部在职口径,排除外部与离职人员)'))).action(async (opts, cmd) => {
454
+ await runHrPersonnelView(opts, cmd, buildPersonnelFilters(opts), 100);
455
+ });
456
+ attachSharedShortcutOptions(addHeadcountOptions(parent
457
+ .command('+headcount')
458
+ .description('人事人数统计(服务端 count,不拉人员明细)'))).action(async (opts, cmd) => {
459
+ await runHeadcount(opts, cmd);
460
+ });
461
+ attachSharedShortcutOptions(addDirectoryScopeOption(parent
462
+ .command('+profile')
463
+ .description('我的人事档案(部门、岗位、上级、入职、工龄、合同)')
464
+ .option('--limit <n>', '返回行数', '5'))).action(async (opts, cmd) => {
465
+ const identity = await fetchCurrentHrIdentity(opts, cmd);
466
+ await runHrPersonnelView(opts, cmd, { employee_id: requireEmployeeId(identity, '本人档案') }, 5);
467
+ });
468
+ attachSharedShortcutOptions(addDirectoryScopeOption(parent
469
+ .command('+tenure')
470
+ .description('我的工龄与入职/转正/离职信息')
471
+ .option('--limit <n>', '返回行数', '5'))).action(async (opts, cmd) => {
472
+ const identity = await fetchCurrentHrIdentity(opts, cmd);
473
+ await runHrPersonnelView(opts, cmd, { employee_id: requireEmployeeId(identity, '工龄') }, 5);
474
+ });
475
+ attachSharedShortcutOptions(addDirectoryScopeOption(parent
476
+ .command('+manager')
477
+ .description('我的直属上司档案')
478
+ .option('--limit <n>', '返回行数', '5'))).action(async (opts, cmd) => {
479
+ const identity = await fetchCurrentHrIdentity(opts, cmd);
480
+ const managerEmployeeId = await resolveCurrentManagerEmployeeId(identity, opts, cmd);
481
+ await runHrPersonnelView(opts, cmd, { employee_id: managerEmployeeId }, 5);
482
+ });
483
+ attachSharedShortcutOptions(addDirectoryScopeOption(parent
484
+ .command('+subordinates')
485
+ .description('我的下属(默认递归到最深层;--direct 仅直属)')
486
+ .option('--status <name>', '在职状态包含', '在职')
487
+ .option('--direct', '仅查询直属下属', false)
488
+ .option('--max-depth <n>', '递归最大层级(默认 20)')
489
+ .option('--limit <n>', '返回行数', '100'))).action(async (opts, cmd) => {
490
+ const identity = await fetchCurrentHrIdentity(opts, cmd);
491
+ if (!opts.direct) {
492
+ await runRecursiveSubordinates(opts, cmd, identity);
493
+ return;
494
+ }
495
+ const filters = {
496
+ manager_employee_id: requireEmployeeId(identity, '直属下属'),
497
+ };
498
+ if (opts.status) {
499
+ filters.status = opts.status;
500
+ }
501
+ await runHrPersonnelView(opts, cmd, filters, 100);
502
+ });
503
+ attachSharedShortcutOptions(addPersonnelOptions(parent
504
+ .command('+job-details')
505
+ .description('岗位明细(按姓名、部门、岗位、上级等过滤)'))).action(async (opts, cmd) => {
506
+ await runHrPersonnelView(opts, cmd, buildPersonnelFilters(opts), 100);
507
+ });
508
+ attachSharedShortcutOptions(addPersonnelOptions(parent
509
+ .command('+contracts')
510
+ .description('合同信息(本人可查;查询授权范围内他人需要 hr.profile.contract:read)')
511
+ .option('--all', '不默认限定本人,但不能扩大服务端授权人员范围', false))).action(async (opts, cmd) => {
512
+ const filters = buildPersonnelFilters(opts);
513
+ if (!opts.all && !hasPersonTarget(opts)) {
514
+ const identity = await fetchCurrentHrIdentity(opts, cmd);
515
+ filters.employee_id = requireEmployeeId(identity, '合同');
516
+ }
517
+ await runHrPersonnelView(opts, cmd, filters, 100);
518
+ });
519
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from 'commander';
2
+ import { attachHrDataViewShortcuts } from './data-view-shortcuts.js';
3
+ import { attachHrShortcuts } from './shortcuts.js';
4
+ import { attachJobResponsibilitiesShortcut } from './job-responsibilities.js';
5
+ import { attachHrBusinessShortcuts } from './business.js';
6
+ /** Features · 人力资源 — `vps-work hr +…`(身份、组织树、人员档案、岗位、上下级、合同、工资条) */
7
+ export function buildHrCommand() {
8
+ const hr = new Command('hr')
9
+ .description('HR shortcuts: +me, +org-tree, +headcount, +profile, +manager, +subordinates, +job-details, +job-responsibilities, +contracts, +payslip, +personnel-info。');
10
+ attachHrShortcuts(hr);
11
+ attachHrDataViewShortcuts(hr);
12
+ attachJobResponsibilitiesShortcut(hr);
13
+ attachHrBusinessShortcuts(hr);
14
+ return hr;
15
+ }