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,127 @@
1
+ import { Command } from 'commander';
2
+ import { OdooClient, extractSessionIdFromSetCookie } from '../core/client.js';
3
+ import { loadCredentials, loadPassword, persistPassword, persistSession, } from '../core/auth.js';
4
+ import { PRIMARY_ALIAS, isValidAlias, listEndpoints, readConfig, } from '../core/config.js';
5
+ import { promptSecret } from '../utils/prompt.js';
6
+ import { VertuError } from '../core/errors.js';
7
+ export function buildReauthCommand() {
8
+ return new Command('reauth')
9
+ .description('仅供人工刷新 legacy Odoo AI CLI 会话;Agent/Bot/Hermes 禁止使用。')
10
+ .option('--endpoint <alias>', 'Refresh only the named endpoint (default = primary). Defaults to refreshing every configured endpoint.')
11
+ .option('--all', 'Explicitly request all endpoints (default behavior when --endpoint is omitted).')
12
+ .option('--no-remember-password', 'When prompting for a missing password, do not persist it for next time (default: persist).')
13
+ .action(async (opts) => {
14
+ const targets = await collectTargets(opts);
15
+ if (!targets.length) {
16
+ throw new VertuError('No legacy endpoints to refresh. Human operators may configure one with `vps-work auth login --legacy-odoo-cli`; Agent/Bot/Hermes must use injected app_key credentials instead.');
17
+ }
18
+ const results = [];
19
+ for (const target of targets) {
20
+ results.push(await refreshOne(target, opts));
21
+ }
22
+ printSummary(results);
23
+ const failed = results.filter((r) => r.status === 'failed').length;
24
+ if (failed > 0) {
25
+ process.exitCode = 2;
26
+ }
27
+ });
28
+ }
29
+ async function collectTargets(opts) {
30
+ const config = await readConfig();
31
+ if (opts.endpoint) {
32
+ const alias = opts.endpoint.trim();
33
+ if (alias !== PRIMARY_ALIAS && !isValidAlias(alias)) {
34
+ throw new VertuError(`Invalid --endpoint alias "${alias}"; expected ^[a-z][a-z0-9-]*$ (e.g. "im", "default").`);
35
+ }
36
+ let baseUrl;
37
+ if (alias === PRIMARY_ALIAS) {
38
+ baseUrl = config.defaultBaseUrl;
39
+ }
40
+ else {
41
+ baseUrl = config.endpoints[alias]?.baseUrl;
42
+ }
43
+ if (!baseUrl) {
44
+ throw new VertuError(`Endpoint "${alias}" is not configured. A human operator may configure this legacy endpoint; Agent/Bot/Hermes must not log in with a password.`);
45
+ }
46
+ return [{ alias, baseUrl }];
47
+ }
48
+ // Default = all known endpoints (primary + every alias).
49
+ return listEndpoints(config);
50
+ }
51
+ async function refreshOne(target, opts) {
52
+ const { alias, baseUrl } = target;
53
+ const existing = await loadCredentials({ baseUrlOverride: baseUrl });
54
+ const login = existing.profile.login;
55
+ if (!login) {
56
+ return {
57
+ alias,
58
+ baseUrl,
59
+ status: 'skipped',
60
+ detail: 'no human legacy login recorded; Agent/Bot/Hermes must use injected app_key credentials',
61
+ };
62
+ }
63
+ let password = await loadPassword(baseUrl);
64
+ let promptedPassword = false;
65
+ if (!password) {
66
+ if (!process.stdin.isTTY) {
67
+ return {
68
+ alias,
69
+ baseUrl,
70
+ status: 'skipped',
71
+ detail: 'no password remembered and stdin is not a TTY',
72
+ };
73
+ }
74
+ password = await promptSecret(`Password for ${login} @ ${alias} (${baseUrl})`);
75
+ promptedPassword = true;
76
+ }
77
+ const db = existing.profile.db;
78
+ try {
79
+ const client = new OdooClient({ baseUrl });
80
+ const params = { login, password };
81
+ if (db)
82
+ params.db = db;
83
+ const { result, setCookie } = await client.jsonRpc('/ai/cli/v1/login', params, { noBearer: true });
84
+ const sessionId = extractSessionIdFromSetCookie(setCookie) ?? result.session_id;
85
+ if (!sessionId) {
86
+ return {
87
+ alias,
88
+ baseUrl,
89
+ status: 'failed',
90
+ detail: 'login succeeded but no session_id was returned',
91
+ };
92
+ }
93
+ // skipDefaultUpdate=true:reauth 只刷新会话,绝不应改动 defaultBaseUrl。
94
+ // 默认行为是遍历所有端点(primary + 各 alias),若不跳过则最后被处理的 alias
95
+ // (字母序如 `im`)会把主端点的 defaultBaseUrl 覆盖掉,导致后续无 --endpoint 的
96
+ // 普通命令全部错误地路由到次端点。
97
+ const skipDefaultUpdate = alias !== PRIMARY_ALIAS;
98
+ await persistSession(baseUrl, sessionId, { db: result.db ?? db ?? undefined, login: result.login }, skipDefaultUpdate ? { skipDefaultUpdate: true } : undefined);
99
+ if (promptedPassword && opts.rememberPassword !== false) {
100
+ await persistPassword(baseUrl, password);
101
+ }
102
+ return {
103
+ alias,
104
+ baseUrl,
105
+ status: 'ok',
106
+ detail: 'session refreshed',
107
+ uid: result.uid,
108
+ login: result.login,
109
+ };
110
+ }
111
+ catch (err) {
112
+ return {
113
+ alias,
114
+ baseUrl,
115
+ status: 'failed',
116
+ detail: err.message,
117
+ };
118
+ }
119
+ }
120
+ function printSummary(results) {
121
+ const labelWidth = Math.max(5, ...results.map((r) => r.alias.length));
122
+ for (const r of results) {
123
+ const tag = r.status === 'ok' ? 'OK ' : r.status === 'skipped' ? 'SKIP ' : 'FAIL ';
124
+ const who = r.uid && r.login ? ` ${r.login} (uid=${r.uid})` : '';
125
+ console.log(`[${tag}] ${r.alias.padEnd(labelWidth)} ${r.baseUrl} →${who} ${r.detail}`);
126
+ }
127
+ }
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ import { attachReportShortcuts } from './shortcuts.js';
3
+ export function buildReportCommand() {
4
+ const report = new Command('report').description('Daily report shortcuts: required/check-in missing, personal/department reports, tasks, channel options, submit, sync.');
5
+ attachReportShortcuts(report);
6
+ return report;
7
+ }
@@ -0,0 +1,9 @@
1
+ import { resolveVpsServiceUrl, vpsServiceGet } from '../../core/sales-query-client.js';
2
+ export const MISSING_DAILY_REPORTS_PATH = '/v1/daily-report/missing';
3
+ export function missingDailyReportsUrl(date, serviceBaseUrl) {
4
+ const query = new URLSearchParams({ date, response_format: 'json' });
5
+ return `${resolveVpsServiceUrl(serviceBaseUrl)}${MISSING_DAILY_REPORTS_PATH}?${query.toString()}`;
6
+ }
7
+ export async function queryMissingDailyReports(date, serviceBaseUrl) {
8
+ return vpsServiceGet(MISSING_DAILY_REPORTS_PATH, { date, response_format: 'json' }, serviceBaseUrl, '应填未交日报');
9
+ }
@@ -0,0 +1,251 @@
1
+ import { VertuError } from '../../core/errors.js';
2
+ import { imRequest, printImResult } from '../../core/im-client.js';
3
+ import { parseJsonArg } from '../../utils/parse-json-arg.js';
4
+ import { attachSharedShortcutOptions, mergeShortcutParams, parseOptionalFloat, parseOptionalInt, previewShortcutRequest, runSkillShortcutExecute, } from '../shortcut-common.js';
5
+ import { missingDailyReportsUrl, queryMissingDailyReports, } from './missing-daily-reports.js';
6
+ const REPORT_SKILL = 'vps-daily-report-assistant';
7
+ function resolveBaseUrl(opts, cmd) {
8
+ const merged = cmd.optsWithGlobals();
9
+ return opts.baseUrl ?? merged.baseUrl;
10
+ }
11
+ function putString(target, key, raw) {
12
+ const value = raw?.trim();
13
+ if (value) {
14
+ target[key] = value;
15
+ }
16
+ }
17
+ function putInt(target, key, raw, flag) {
18
+ const value = parseOptionalInt(raw, flag);
19
+ if (value !== undefined) {
20
+ target[key] = value;
21
+ }
22
+ }
23
+ function putFloat(target, key, raw, flag) {
24
+ const value = parseOptionalFloat(raw, flag);
25
+ if (value !== undefined) {
26
+ target[key] = value;
27
+ }
28
+ }
29
+ function requireYmd(raw, flag) {
30
+ const value = raw?.trim();
31
+ if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
32
+ throw new VertuError(`${flag} YYYY-MM-DD is required.`);
33
+ }
34
+ return value;
35
+ }
36
+ function parseItemsJson(raw) {
37
+ const value = raw?.trim();
38
+ if (!value) {
39
+ return undefined;
40
+ }
41
+ const parsed = parseJsonArg(value, '--items-json');
42
+ if (!Array.isArray(parsed) || parsed.length === 0) {
43
+ throw new VertuError('--items-json must be a non-empty JSON array.');
44
+ }
45
+ return parsed;
46
+ }
47
+ function hasSubmitItems(body) {
48
+ return Array.isArray(body.items) && body.items.length > 0;
49
+ }
50
+ function validateSubmitBody(body) {
51
+ if (typeof body.tomorrow !== 'string' || body.tomorrow.trim().length === 0) {
52
+ throw new VertuError('--tomorrow is required.');
53
+ }
54
+ if (hasSubmitItems(body)) {
55
+ return;
56
+ }
57
+ const missing = ['task_id', 'progress', 'spent_hours', 'content'].filter((key) => {
58
+ const value = body[key];
59
+ return value === undefined || value === null || value === '';
60
+ });
61
+ if (missing.length) {
62
+ throw new VertuError('Either provide --items-json/--params items, or provide --task-id --progress --spent-hours --content.');
63
+ }
64
+ }
65
+ export function attachReportShortcuts(parent) {
66
+ attachSharedShortcutOptions(parent
67
+ .command('+missing-daily-reports')
68
+ .description('每日应填但未提交日报名单(/v1/daily-report/missing)')
69
+ .requiredOption('--date <ymd>', 'YYYY-MM-DD')).action(async (opts, cmd) => {
70
+ const date = requireYmd(opts.date, '--date');
71
+ const baseUrl = resolveBaseUrl(opts, cmd);
72
+ if (opts.validateOnly || opts.dryRun) {
73
+ printImResult(opts.json, {
74
+ validateOnly: true,
75
+ method: 'GET',
76
+ endpoint: missingDailyReportsUrl(date, baseUrl),
77
+ });
78
+ return;
79
+ }
80
+ const result = await queryMissingDailyReports(date, baseUrl);
81
+ printImResult(opts.json, result);
82
+ });
83
+ attachSharedShortcutOptions(parent
84
+ .command('+checkin-missing')
85
+ .description('打卡但未提交日报名单(wechat.checkin.daily.report.compare)')
86
+ .requiredOption('--date <ymd>', 'YYYY-MM-DD')
87
+ .option('--department-id <id>', 'hr.department id;包含子部门')
88
+ .option('--limit <n>', '最多返回条数', '500')).action(async (opts, cmd) => {
89
+ const base = {
90
+ date: requireYmd(opts.date, '--date'),
91
+ limit: parseOptionalInt(opts.limit, '--limit') ?? 500,
92
+ };
93
+ const departmentId = parseOptionalInt(opts.departmentId, '--department-id');
94
+ if (departmentId !== undefined) {
95
+ base.department_id = departmentId;
96
+ }
97
+ await runSkillShortcutExecute(REPORT_SKILL, 'checkin_missing', base, opts, cmd);
98
+ });
99
+ attachSharedShortcutOptions(parent
100
+ .command('+mine')
101
+ .description('日报列表(/v1/daily-report/mine)')
102
+ .option('--user-id <id>', '管理员查询的 res.users id;省略=当前登录用户')
103
+ .option('--start-time <date>', 'YYYY-MM-DD,默认今天')
104
+ .option('--end-time <date>', 'YYYY-MM-DD,默认今天')).action(async (opts, cmd) => {
105
+ const query = {};
106
+ putInt(query, 'user_id', opts.userId, '--user-id');
107
+ putString(query, 'start_time', opts.startTime);
108
+ putString(query, 'end_time', opts.endTime);
109
+ const result = await imRequest('GET', '/v1/daily-report/mine', {
110
+ baseUrl: resolveBaseUrl(opts, cmd),
111
+ query,
112
+ });
113
+ printImResult(opts.json, result);
114
+ });
115
+ attachSharedShortcutOptions(parent
116
+ .command('+department')
117
+ .description('本部门及子部门成员日报(/v1/daily-report/department)')
118
+ .requiredOption('--date <ymd>', 'YYYY-MM-DD')
119
+ .option('--limit <n>', '最多返回条数', '500')
120
+ .option('--department-path <path>', '指定其他部门完整路径;需要跨部门日报权限')
121
+ .option('--all-departments', '查询所有部门;需要全公司日报权限', false)).action(async (opts, cmd) => {
122
+ if (opts.allDepartments && opts.departmentPath?.trim()) {
123
+ throw new VertuError('--department-path and --all-departments cannot be used together.');
124
+ }
125
+ const query = {
126
+ date: requireYmd(opts.date, '--date'),
127
+ limit: parseOptionalInt(opts.limit, '--limit') ?? 500,
128
+ };
129
+ putString(query, 'department_path', opts.departmentPath);
130
+ if (opts.allDepartments) {
131
+ query.all_departments = true;
132
+ }
133
+ const result = await imRequest('GET', '/v1/daily-report/department', {
134
+ baseUrl: resolveBaseUrl(opts, cmd),
135
+ query,
136
+ });
137
+ printImResult(opts.json, result);
138
+ });
139
+ attachSharedShortcutOptions(parent
140
+ .command('+tasks')
141
+ .description('我的可汇报研发任务(/v1/daily-report/tasks)')
142
+ .option('--limit <n>', '最多返回条数', '50')).action(async (opts, cmd) => {
143
+ const query = { limit: parseOptionalInt(opts.limit, '--limit') ?? 50 };
144
+ const result = await imRequest('GET', '/v1/daily-report/tasks', {
145
+ baseUrl: resolveBaseUrl(opts, cmd),
146
+ query,
147
+ });
148
+ printImResult(opts.json, result);
149
+ });
150
+ attachSharedShortcutOptions(parent.command('+im-channel-options')
151
+ .description('查看当前账号可选择的日报群聊及默认群(/v1/daily-report/im-channel-options)')
152
+ .option('--user-id <id>', '仅集成 API Key 查询指定员工时使用;账号会话始终查询本人')).action(async (opts, cmd) => {
153
+ const result = await imRequest('GET', '/v1/daily-report/im-channel-options', {
154
+ baseUrl: resolveBaseUrl(opts, cmd),
155
+ query: opts.userId ? { user_id: parseOptionalInt(opts.userId, '--user-id') } : undefined,
156
+ });
157
+ printImResult(opts.json, result);
158
+ });
159
+ attachSharedShortcutOptions(parent
160
+ .command('+submit')
161
+ .description('提交今日日报(/v1/daily-report/submit)')
162
+ .option('--task-id <id>', '任务中心 UUID;单任务提交时必填')
163
+ .option('--progress <n>', '目标进度 0-100;单任务提交时必填')
164
+ .option('--spent-hours <h>', '今日投入小时数;单任务提交时必填')
165
+ .option('--content <text>', '今日工作内容;多行会拆成多条进度')
166
+ .option('--tomorrow <text>', '明日计划')
167
+ .option('--items-json <json>', '多任务 items 数组,元素含任务中心 UUID task_id/progress/spent_hours/content')
168
+ .option('--im-channel-id <id>', '本系统 IM 群聊 id;先用 +im-channel-options 查看,省略时由服务端选择默认日报群或部门群')
169
+ .option('--legacy-push', '改走旧 PMS/小小V IM 推送接口(默认关闭)', false)
170
+ .option('--channel-id <id>', '旧 IM channel_id;仅 --legacy-push 时有效')
171
+ .option('--chat-group-name <name>', '旧 IM 群名称;仅 --legacy-push 时有效')).action(async (opts, cmd) => {
172
+ const base = {};
173
+ const items = parseItemsJson(opts.itemsJson);
174
+ if (items) {
175
+ base.items = items;
176
+ }
177
+ putString(base, 'task_id', opts.taskId);
178
+ putInt(base, 'progress', opts.progress, '--progress');
179
+ putFloat(base, 'spent_hours', opts.spentHours, '--spent-hours');
180
+ putString(base, 'content', opts.content);
181
+ putString(base, 'tomorrow', opts.tomorrow);
182
+ putString(base, 'im_channel_id', opts.imChannelId);
183
+ if (opts.legacyPush) {
184
+ base.legacy_push = true;
185
+ }
186
+ putInt(base, 'channel_id', opts.channelId, '--channel-id');
187
+ putString(base, 'chat_group_name', opts.chatGroupName);
188
+ const body = await mergeShortcutParams(base, opts);
189
+ validateSubmitBody(body);
190
+ if (previewShortcutRequest(opts, cmd, 'POST', '/v1/daily-report/submit', { body }))
191
+ return;
192
+ const result = await imRequest('POST', '/v1/daily-report/submit', {
193
+ baseUrl: resolveBaseUrl(opts, cmd),
194
+ body,
195
+ });
196
+ printImResult(opts.json, result);
197
+ });
198
+ attachSharedShortcutOptions(parent
199
+ .command('+submit-existing-progress')
200
+ .description('提交今天已经记录的任务进展,不重复更新任务进度')
201
+ .requiredOption('--tomorrow <text>', '明日计划,至少一条')
202
+ .option('--im-channel-id <id>', '本系统 IM 群聊 id;先用 +im-channel-options 查看,省略时由服务端选择默认日报群或部门群')).action(async (opts, cmd) => {
203
+ const base = {};
204
+ putString(base, 'tomorrow', opts.tomorrow);
205
+ putString(base, 'im_channel_id', opts.imChannelId);
206
+ const body = await mergeShortcutParams(base, opts);
207
+ if (typeof body.tomorrow !== 'string' || body.tomorrow.trim().length === 0) {
208
+ throw new VertuError('--tomorrow is required.');
209
+ }
210
+ const path = '/v1/daily-report/submit-existing-progress';
211
+ if (previewShortcutRequest(opts, cmd, 'POST', path, { body }))
212
+ return;
213
+ const result = await imRequest('POST', path, {
214
+ baseUrl: resolveBaseUrl(opts, cmd),
215
+ body,
216
+ });
217
+ printImResult(opts.json, result);
218
+ });
219
+ attachSharedShortcutOptions(parent
220
+ .command('+delete-today')
221
+ .description('管理员删除今天的日报测试数据(/v1/daily-report/admin/delete-today)')
222
+ .option('--user-id <id>', '被删除用户 res.users id;省略=当前登录用户')
223
+ .option('--with-progress', '同时删除日报 payload 中的今日任务进度并回算任务进度', false)
224
+ .option('--keep-im-message', '保留本系统 IM 中的日报消息;默认撤回', false)
225
+ .option('--confirm', '确认删除当日日报测试数据', false)).action(async (opts, cmd) => {
226
+ if (!opts.confirm) {
227
+ throw new VertuError('删除今日日报需要 --confirm。');
228
+ }
229
+ const body = { confirm: true };
230
+ putInt(body, 'user_id', opts.userId, '--user-id');
231
+ if (opts.withProgress) {
232
+ body.with_progress = true;
233
+ }
234
+ if (opts.keepImMessage) {
235
+ body.keep_im_message = true;
236
+ }
237
+ const result = await imRequest('POST', '/v1/daily-report/admin/delete-today', {
238
+ baseUrl: resolveBaseUrl(opts, cmd),
239
+ body,
240
+ });
241
+ printImResult(opts.json, result);
242
+ });
243
+ attachSharedShortcutOptions(parent.command('+sync').description('同步日报助手会话与今日日报卡片(/v1/daily-report/sync)')).action(async (opts, cmd) => {
244
+ const body = await mergeShortcutParams({}, opts);
245
+ const result = await imRequest('POST', '/v1/daily-report/sync', {
246
+ baseUrl: resolveBaseUrl(opts, cmd),
247
+ body,
248
+ });
249
+ printImResult(opts.json, result);
250
+ });
251
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import { attachSalesPersonnelShortcuts } from './shortcuts-personnel.js';
3
+ import { attachSalesShortcuts } from './shortcuts.js';
4
+ /** Features · 销售与经营 — Lark 风格 `vps-work sales +…` 快捷命令。 */
5
+ export function buildSalesCommand() {
6
+ const sales = new Command('sales')
7
+ .description('Sales shortcuts: +headline-kpi, +orders, +order-finance, +invoice-status, +reconciled-receipts, +dealer-orders 等。');
8
+ attachSalesShortcuts(sales);
9
+ attachSalesPersonnelShortcuts(sales);
10
+ return sales;
11
+ }
@@ -0,0 +1,75 @@
1
+ import { parseOptionalInt } from '../shortcut-common.js';
2
+ const DAILY_SALES_SKILL = 'vps-daily-sales-report';
3
+ export function dailySalesSkillKey() {
4
+ return DAILY_SALES_SKILL;
5
+ }
6
+ /**
7
+ * 时间窗 + 部门 + 通用 limit / include_deposit / 商品维度过滤。
8
+ * `defaultPeriod` 仅在未传 period 且未传 start/end 时生效。
9
+ */
10
+ export function buildSalesPeriodParams(opts, defaultPeriod) {
11
+ const p = {};
12
+ if (opts.startDate) {
13
+ p.start_date = opts.startDate;
14
+ }
15
+ if (opts.endDate) {
16
+ p.end_date = opts.endDate;
17
+ }
18
+ if (opts.period) {
19
+ p.period = opts.period;
20
+ }
21
+ else if (!opts.startDate && !opts.endDate && defaultPeriod) {
22
+ p.period = defaultPeriod;
23
+ }
24
+ const days = parseOptionalInt(opts.days, '--days');
25
+ if (days !== undefined) {
26
+ p.days = days;
27
+ }
28
+ if (opts.deptL1) {
29
+ p.dept_l1 = opts.deptL1;
30
+ }
31
+ if (opts.deptL2) {
32
+ p.dept_l2 = opts.deptL2;
33
+ }
34
+ if (opts.deptL3) {
35
+ p.dept_l3 = opts.deptL3;
36
+ }
37
+ if (opts.deptL4) {
38
+ p.dept_l4 = opts.deptL4;
39
+ }
40
+ if (opts.deptL5) {
41
+ p.dept_l5 = opts.deptL5;
42
+ }
43
+ if (opts.departmentMode) {
44
+ p.department_mode = opts.departmentMode;
45
+ }
46
+ const limit = parseOptionalInt(opts.limit, '--limit');
47
+ if (limit !== undefined) {
48
+ p.limit = limit;
49
+ }
50
+ if (opts.includeDeposit) {
51
+ p.include_deposit = true;
52
+ }
53
+ if (opts.productSubcategory) {
54
+ p.product_subcategory = opts.productSubcategory;
55
+ }
56
+ if (opts.productSubcategories) {
57
+ p.product_subcategories = opts.productSubcategories;
58
+ }
59
+ if (opts.productCategory) {
60
+ p.product_category = opts.productCategory;
61
+ }
62
+ if (opts.productMajorCategories) {
63
+ p.product_major_categories = opts.productMajorCategories;
64
+ }
65
+ if (opts.productStyle) {
66
+ p.product_style = opts.productStyle;
67
+ }
68
+ if (opts.productStyleDetails) {
69
+ p.product_style_details = opts.productStyleDetails;
70
+ }
71
+ if (opts.smartWearable) {
72
+ p.smart_wearable = true;
73
+ }
74
+ return p;
75
+ }
@@ -0,0 +1,124 @@
1
+ import { attachSharedShortcutOptions, parseOptionalInt, runSkillShortcutExecute, } from '../shortcut-common.js';
2
+ import { buildSalesPeriodParams, } from './period-params.js';
3
+ const PERSONNEL_SKILL = 'vps-sale-personnel-efficiency';
4
+ function withOkrSource(params, opts) {
5
+ if (opts.okrSource) {
6
+ params.okr_source = opts.okrSource;
7
+ }
8
+ return params;
9
+ }
10
+ function personnelPeriod(cmd) {
11
+ return cmd
12
+ .option('--period <name>', 'this_month | last_month | this_quarter | YYYY-MM-DD | …')
13
+ .option('--start-date <date>', 'YYYY-MM-DD;单独传表示单日,配合 --end-date 表示范围')
14
+ .option('--end-date <date>', 'YYYY-MM-DD;单独传表示单日,配合 --start-date 表示范围')
15
+ .option('--days <n>', 'With period=last_n_days')
16
+ .option('--dept-l1 <name>', '一级部门')
17
+ .option('--dept-l2 <name>', '二级部门')
18
+ .option('--dept-l3 <name>', '三级部门')
19
+ .option('--dept-l4 <name>', '四级部门')
20
+ .option('--dept-l5 <name>', '五级部门')
21
+ .option('--include-deposit', '含定金口径', false);
22
+ }
23
+ export function attachSalesPersonnelShortcuts(parent) {
24
+ attachSharedShortcutOptions(personnelPeriod(parent
25
+ .command('+per-person-kpi')
26
+ .description('销售人效 / 人均产出(per_person_kpi)')
27
+ .option('--dept-level <n>', '聚合层级 1-3', '2')
28
+ .option('--on-duty-status <label>', '在职 | 全部', '在职')
29
+ .option('--page <n>', '页码,从 1 开始', '1'))).action(async (opts, cmd) => {
30
+ const p = buildSalesPeriodParams(opts, 'last_month');
31
+ const level = parseOptionalInt(opts.deptLevel, '--dept-level');
32
+ if (level !== undefined) {
33
+ p.dept_level = level;
34
+ }
35
+ if (opts.onDutyStatus) {
36
+ p.on_duty_status = opts.onDutyStatus;
37
+ }
38
+ const page = parseOptionalInt(opts.page, '--page');
39
+ if (page !== undefined) {
40
+ p.page = page;
41
+ }
42
+ await runSkillShortcutExecute(PERSONNEL_SKILL, 'per_person_kpi', p, opts, cmd);
43
+ });
44
+ attachSharedShortcutOptions(personnelPeriod(parent
45
+ .command('+personal-okr')
46
+ .description('销售员个人业绩目标达成(personal_okr_achievement)')
47
+ .option('--salesperson-like <name>', '姓名 ILIKE')
48
+ .option('--work-id <code>', '工号')
49
+ .option('--only-below-target', '仅未达标', false)
50
+ .option('--order-by <field>', 'rate_asc | rate_desc | amount_desc | name', 'rate_asc')
51
+ .option('--limit <n>', 'Max rows', '30')
52
+ .option('--page <n>', '页码,从 1 开始', '1')
53
+ .option('--okr-source <source>', 'postgres | legacy(默认 postgres;旧 PMS/BI 口径传 legacy)'))).action(async (opts, cmd) => {
54
+ const p = buildSalesPeriodParams(opts, 'this_quarter');
55
+ withOkrSource(p, opts);
56
+ if (opts.salespersonLike) {
57
+ p.salesperson_like = opts.salespersonLike;
58
+ }
59
+ if (opts.workId) {
60
+ p.work_id = opts.workId;
61
+ }
62
+ if (opts.onlyBelowTarget) {
63
+ p.only_below_target = true;
64
+ }
65
+ if (opts.orderBy) {
66
+ p.order_by = opts.orderBy;
67
+ }
68
+ const limit = parseOptionalInt(opts.limit, '--limit');
69
+ if (limit !== undefined) {
70
+ p.limit = limit;
71
+ }
72
+ const page = parseOptionalInt(opts.page, '--page');
73
+ if (page !== undefined) {
74
+ p.page = page;
75
+ }
76
+ await runSkillShortcutExecute(PERSONNEL_SKILL, 'personal_okr_achievement', p, opts, cmd);
77
+ });
78
+ attachSharedShortcutOptions(personnelPeriod(parent
79
+ .command('+personal-monthly-okr')
80
+ .description('个人月度业绩目标明细(personal_monthly_okr)')
81
+ .option('--name <text>', '姓名 ILIKE')
82
+ .option('--barcode <code>', '工号')
83
+ .option('--limit <n>', 'Max rows', '100')
84
+ .option('--page <n>', '页码,从 1 开始', '1')
85
+ .option('--okr-source <source>', 'postgres | legacy(默认 postgres;旧 PMS/BI 口径传 legacy)'))).action(async (opts, cmd) => {
86
+ const p = buildSalesPeriodParams(opts, 'this_month');
87
+ withOkrSource(p, opts);
88
+ if (opts.name) {
89
+ p.name = opts.name;
90
+ }
91
+ if (opts.barcode) {
92
+ p.barcode = opts.barcode;
93
+ }
94
+ const limit = parseOptionalInt(opts.limit, '--limit');
95
+ if (limit !== undefined) {
96
+ p.limit = limit;
97
+ }
98
+ const page = parseOptionalInt(opts.page, '--page');
99
+ if (page !== undefined) {
100
+ p.page = page;
101
+ }
102
+ await runSkillShortcutExecute(PERSONNEL_SKILL, 'personal_monthly_okr', p, opts, cmd);
103
+ });
104
+ attachSharedShortcutOptions(personnelPeriod(parent
105
+ .command('+team-pk')
106
+ .description('团队/个人 PK 榜(team_pk_ranking)')
107
+ .option('--mode <name>', 'team | person', 'person')
108
+ .option('--limit <n>', 'Max rows', '20')
109
+ .option('--page <n>', '页码,从 1 开始', '1'))).action(async (opts, cmd) => {
110
+ const p = buildSalesPeriodParams(opts, 'this_month');
111
+ if (opts.mode) {
112
+ p.mode = opts.mode;
113
+ }
114
+ const limit = parseOptionalInt(opts.limit, '--limit');
115
+ if (limit !== undefined) {
116
+ p.limit = limit;
117
+ }
118
+ const page = parseOptionalInt(opts.page, '--page');
119
+ if (page !== undefined) {
120
+ p.page = page;
121
+ }
122
+ await runSkillShortcutExecute(PERSONNEL_SKILL, 'team_pk_ranking', p, opts, cmd);
123
+ });
124
+ }