weaver-work-cli 0.1.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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +422 -0
  3. package/dist/cmd/action.js +35 -0
  4. package/dist/cmd/commands/index.js +15 -0
  5. package/dist/cmd/config/index.js +64 -0
  6. package/dist/cmd/docs/index.js +56 -0
  7. package/dist/cmd/doctor/index.js +45 -0
  8. package/dist/cmd/e10/index.js +25 -0
  9. package/dist/cmd/index.js +18 -0
  10. package/dist/cmd/register.js +19 -0
  11. package/dist/cmd/root.js +36 -0
  12. package/dist/cmd/run/index.js +38 -0
  13. package/dist/cmd/setup/index.js +77 -0
  14. package/dist/cmd/skills/index.js +72 -0
  15. package/dist/core/config-store.js +43 -0
  16. package/dist/core/context.js +31 -0
  17. package/dist/core/errors.js +60 -0
  18. package/dist/core/hooks.js +41 -0
  19. package/dist/core/logger.js +27 -0
  20. package/dist/core/output.js +25 -0
  21. package/dist/core/write-with-retry.js +21 -0
  22. package/dist/index.js +11 -0
  23. package/dist/internal/e10/auth/commands.js +594 -0
  24. package/dist/internal/e10/auth/crypto.js +126 -0
  25. package/dist/internal/e10/auth/paths.js +12 -0
  26. package/dist/internal/e10/auth/session.js +449 -0
  27. package/dist/internal/e10/client.js +104 -0
  28. package/dist/internal/e10/context.js +20 -0
  29. package/dist/internal/e10/index.js +2 -0
  30. package/dist/internal/e10/types.js +1 -0
  31. package/dist/internal/skills/install.js +99 -0
  32. package/dist/internal/skills/reader.js +119 -0
  33. package/dist/services/command-runner.js +44 -0
  34. package/dist/shortcuts/index.js +4 -0
  35. package/dist/shortcuts/invoice/continuation.js +70 -0
  36. package/dist/shortcuts/invoice/errors.js +59 -0
  37. package/dist/shortcuts/invoice/host.js +337 -0
  38. package/dist/shortcuts/invoice/index.js +301 -0
  39. package/dist/shortcuts/invoice/manifest.js +288 -0
  40. package/dist/shortcuts/invoice/operations/add.js +106 -0
  41. package/dist/shortcuts/invoice/operations/delete.js +92 -0
  42. package/dist/shortcuts/invoice/operations/download.js +16 -0
  43. package/dist/shortcuts/invoice/operations/get.js +7 -0
  44. package/dist/shortcuts/invoice/operations/import.js +128 -0
  45. package/dist/shortcuts/invoice/operations/list.js +12 -0
  46. package/dist/shortcuts/invoice/operations/ocr-preview.js +40 -0
  47. package/dist/shortcuts/invoice/operations/registry.js +50 -0
  48. package/dist/shortcuts/invoice/operations/shared.js +640 -0
  49. package/dist/shortcuts/invoice/operations/types.js +1 -0
  50. package/dist/shortcuts/invoice/operations/update.js +109 -0
  51. package/dist/shortcuts/invoice/operations/upload.js +8 -0
  52. package/dist/shortcuts/invoice/operations/validate-preview.js +39 -0
  53. package/dist/shortcuts/invoice/operations.js +8 -0
  54. package/dist/types.js +1 -0
  55. package/docs/SKILL.md +36 -0
  56. package/docs/_catalog.md +12 -0
  57. package/docs/agent-invoice.md +53 -0
  58. package/docs/e10-auth.md +51 -0
  59. package/docs/invoice.md +99 -0
  60. package/package.json +51 -0
  61. package/scripts/clean.mjs +4 -0
  62. package/scripts/package-skill.mjs +164 -0
  63. package/scripts/sync-docs.mjs +17 -0
  64. package/skill-template/business-info.json +4 -0
  65. package/skill-template/domains/invoice.md +23 -0
  66. package/skill-template/domains/shared.md +8 -0
  67. package/skill-template/master-skill-template.md +29 -0
  68. package/skill-template/skill-template.md +60 -0
  69. package/skills/weaver-work-cli-invoice/SKILL.md +103 -0
  70. package/skills/weaver-work-cli-invoice/product.json +8 -0
  71. package/skills/weaver-work-cli-invoice/references/invoice-add.md +18 -0
  72. package/skills/weaver-work-cli-invoice/references/invoice-agent-entry.md +38 -0
  73. package/skills/weaver-work-cli-invoice/references/invoice-delete.md +20 -0
  74. package/skills/weaver-work-cli-invoice/references/invoice-detail.md +29 -0
  75. package/skills/weaver-work-cli-invoice/references/invoice-disabled-capabilities.md +26 -0
  76. package/skills/weaver-work-cli-invoice/references/invoice-download.md +22 -0
  77. package/skills/weaver-work-cli-invoice/references/invoice-enterprise-list.md +35 -0
  78. package/skills/weaver-work-cli-invoice/references/invoice-file-upload.md +28 -0
  79. package/skills/weaver-work-cli-invoice/references/invoice-import.md +28 -0
  80. package/skills/weaver-work-cli-invoice/references/invoice-ocr-preview.md +33 -0
  81. package/skills/weaver-work-cli-invoice/references/invoice-personal-list.md +47 -0
  82. package/skills/weaver-work-cli-invoice/references/invoice-update.md +38 -0
  83. package/skills/weaver-work-cli-invoice/references/invoice-validation-preview.md +26 -0
  84. package/skills/weaver-work-cli-shared/SKILL.md +53 -0
  85. package/skills/weaver-work-cli-shared/references/e10-auth-and-session.md +83 -0
  86. package/skills/weaver-work-cli-shared/references/high-risk-write.md +36 -0
  87. package/skills/weaver-work-cli-shared/references/json-output-contract.md +61 -0
  88. package/skills/weaver-work-cli-shared/references/weaver-work-cli-installation.md +61 -0
  89. package/templates/shortcut/index.ts.tpl +19 -0
  90. package/templates/shortcut/operations/ping.ts.tpl +7 -0
@@ -0,0 +1,288 @@
1
+ const listProperties = {
2
+ page_size: { type: 'integer', minimum: 1, maximum: 200 },
3
+ start_pos: { type: 'integer', minimum: 0 },
4
+ req_type: { type: 'integer', enum: [0, 1, 7, 8] },
5
+ mask: { type: 'integer', minimum: 0 },
6
+ mext: { type: 'integer', minimum: 0 },
7
+ content: { type: 'string' },
8
+ types: { type: 'array', items: { type: 'integer' } },
9
+ date_begin: { type: 'integer', minimum: 0 },
10
+ date_end: { type: 'integer', minimum: 0 },
11
+ create_tm_begin: { type: 'integer', minimum: 0 },
12
+ create_tm_end: { type: 'integer', minimum: 0 },
13
+ payer_company: { type: 'string' },
14
+ buyer_company: { type: 'string' },
15
+ payer_taxno: { type: 'string' },
16
+ buyer_taxno: { type: 'string' },
17
+ code: { type: 'string' },
18
+ number: { type: 'string' },
19
+ total_begin: { type: 'string', pattern: '^\\d+(\\.\\d{1,2})?$' },
20
+ total_end: { type: 'string', pattern: '^\\d+(\\.\\d{1,2})?$' },
21
+ attribute: { type: 'integer', enum: [0, 1, 2] },
22
+ sreim: {
23
+ type: 'integer',
24
+ enum: [0, 1, 2, 3, 4],
25
+ default: 3,
26
+ description: '报销状态:0=全部发票,1=发票报销中,2=报销完成,3=未报销发票,4=不可报销',
27
+ },
28
+ valids: { type: 'array', items: { type: 'integer', enum: [0, 1, 2, 3, 4, 5] } },
29
+ sources: { type: 'array', items: { type: 'integer' } },
30
+ is_lian: { type: 'integer', enum: [0, 1, 2] },
31
+ is_modify: { type: 'integer', enum: [0, 1, 2] },
32
+ tag_ids: { type: 'array', items: { type: 'integer' } },
33
+ tag_condition_type: { type: 'integer', enum: [0, 1] },
34
+ sorts: {
35
+ type: 'array',
36
+ items: {
37
+ type: 'object',
38
+ additionalProperties: false,
39
+ properties: {
40
+ sort_type: { type: 'integer', enum: [0, 1, 2, 5, 8, 9, 10] },
41
+ sort: { type: 'integer', enum: [0, 1] },
42
+ },
43
+ required: ['sort_type', 'sort'],
44
+ },
45
+ },
46
+ bill_type: { type: 'integer' },
47
+ };
48
+ export const invoiceOperations = [
49
+ {
50
+ name: 'invoice.list',
51
+ risk: 'read',
52
+ scope: 'personal',
53
+ fixed: { flag: 0, defaultSreim: 3 },
54
+ inputSchema: { type: 'object', additionalProperties: false, properties: listProperties },
55
+ },
56
+ {
57
+ name: 'invoice.enterprise.list',
58
+ risk: 'read',
59
+ scope: 'enterprise',
60
+ fixed: { flag: 6, defaultSreim: 3 },
61
+ inputSchema: { type: 'object', additionalProperties: false, properties: listProperties },
62
+ },
63
+ {
64
+ name: 'invoice.get',
65
+ risk: 'read',
66
+ inputSchema: {
67
+ type: 'object',
68
+ additionalProperties: false,
69
+ properties: {
70
+ fid: { type: 'string', pattern: '^[1-9]\\d*$' },
71
+ number: { type: 'string', minLength: 1 },
72
+ },
73
+ oneOf: [{ required: ['fid'] }, { required: ['number'] }],
74
+ },
75
+ },
76
+ {
77
+ name: 'invoice.upload',
78
+ risk: 'external-artifact',
79
+ fixed: { ocr: 1 },
80
+ inputSchema: {
81
+ type: 'object',
82
+ additionalProperties: false,
83
+ properties: {
84
+ file: { type: 'string', minLength: 1 },
85
+ url: { type: 'string', format: 'uri' },
86
+ },
87
+ oneOf: [{ required: ['file'] }, { required: ['url'] }],
88
+ },
89
+ },
90
+ {
91
+ name: 'invoice.ocr.preview',
92
+ risk: 'read-with-upload',
93
+ fixed: { flag: 13, is_save: 1, is_sync: 1, operate_type: 0 },
94
+ inputSchema: {
95
+ type: 'object',
96
+ additionalProperties: false,
97
+ properties: {
98
+ file: { type: 'string', minLength: 1 },
99
+ url: { type: 'string', format: 'uri' },
100
+ upload: { type: 'object' },
101
+ is_save: { const: 1 },
102
+ },
103
+ oneOf: [{ required: ['file'] }, { required: ['url'] }, { required: ['upload'] }],
104
+ },
105
+ },
106
+ {
107
+ name: 'invoice.validate.preview',
108
+ risk: 'external-read',
109
+ fixed: { flag: 100, is_save: 1, needLog: false },
110
+ inputSchema: {
111
+ type: 'object',
112
+ additionalProperties: false,
113
+ properties: {
114
+ fid: { type: 'string', pattern: '^[1-9]\\d*$' },
115
+ scope: { type: 'string', enum: ['personal', 'enterprise'] },
116
+ },
117
+ required: ['fid', 'scope'],
118
+ },
119
+ },
120
+ {
121
+ name: 'invoice.import.prepare',
122
+ risk: 'read-before-write',
123
+ inputSchema: {
124
+ type: 'object',
125
+ additionalProperties: false,
126
+ properties: {
127
+ file: { type: 'string', minLength: 1 },
128
+ validate: { type: 'boolean' },
129
+ syncToOa: { type: 'boolean' },
130
+ },
131
+ required: ['file', 'validate', 'syncToOa'],
132
+ },
133
+ },
134
+ {
135
+ name: 'invoice.import.apply',
136
+ risk: 'high-risk-write',
137
+ inputSchema: {
138
+ type: 'object',
139
+ additionalProperties: false,
140
+ properties: {
141
+ file: { type: 'string', minLength: 1 },
142
+ continuation: { type: 'string', minLength: 1 },
143
+ confirm: { const: true },
144
+ },
145
+ required: ['file', 'continuation', 'confirm'],
146
+ },
147
+ },
148
+ {
149
+ name: 'invoice.add.prepare',
150
+ risk: 'read-before-write',
151
+ inputSchema: {
152
+ type: 'object',
153
+ additionalProperties: false,
154
+ properties: {
155
+ info: { type: 'object' },
156
+ scope: { type: 'string', enum: ['personal', 'enterprise'] },
157
+ },
158
+ required: ['info', 'scope'],
159
+ },
160
+ },
161
+ {
162
+ name: 'invoice.add.apply',
163
+ risk: 'high-risk-write',
164
+ inputSchema: {
165
+ type: 'object',
166
+ additionalProperties: false,
167
+ properties: {
168
+ info: { type: 'object' },
169
+ continuation: { type: 'string', minLength: 1 },
170
+ confirm: { const: true },
171
+ },
172
+ required: ['info', 'continuation', 'confirm'],
173
+ },
174
+ },
175
+ {
176
+ name: 'invoice.update.prepare',
177
+ risk: 'read-before-write',
178
+ inputSchema: {
179
+ type: 'object',
180
+ additionalProperties: false,
181
+ properties: {
182
+ fid: { type: 'string', pattern: '^[1-9]\\d*$' },
183
+ scope: { type: 'string', enum: ['personal', 'enterprise'] },
184
+ changes: {
185
+ type: 'object',
186
+ minProperties: 1,
187
+ additionalProperties: false,
188
+ properties: {
189
+ code: { type: 'string', maxLength: 500 },
190
+ number: { type: 'string', minLength: 1, maxLength: 500 },
191
+ 'comm_info.pro.date': { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
192
+ 'comm_info.price.amount': { type: 'string', pattern: '^\\d+(\\.\\d{1,2})?$' },
193
+ 'comm_info.price.total': { type: 'string', pattern: '^\\d+(\\.\\d{1,2})?$' },
194
+ 'comm_info.price.dtax': { type: 'string', pattern: '^\\d+(\\.\\d{1,2})?$' },
195
+ 'comm_info.buyer.company': { type: 'string', maxLength: 500 },
196
+ 'comm_info.buyer.tcode': { type: 'string', maxLength: 500 },
197
+ 'comm_info.payer.company': { type: 'string', maxLength: 500 },
198
+ 'comm_info.payer.tcode': { type: 'string', maxLength: 500 },
199
+ fylx: { type: 'string', maxLength: 500 },
200
+ currency: { type: 'string', maxLength: 500 },
201
+ currencyName: { type: 'string', maxLength: 500 },
202
+ },
203
+ },
204
+ },
205
+ required: ['fid', 'scope', 'changes'],
206
+ },
207
+ },
208
+ {
209
+ name: 'invoice.update.apply',
210
+ risk: 'high-risk-write',
211
+ inputSchema: {
212
+ type: 'object',
213
+ additionalProperties: false,
214
+ properties: {
215
+ continuation: { type: 'string', minLength: 1 },
216
+ confirm: { const: true },
217
+ },
218
+ required: ['continuation', 'confirm'],
219
+ },
220
+ },
221
+ {
222
+ name: 'invoice.download',
223
+ risk: 'local-file-write',
224
+ inputSchema: {
225
+ type: 'object',
226
+ additionalProperties: false,
227
+ properties: {
228
+ fid: { type: 'string', pattern: '^[1-9]\\d*$' },
229
+ fileId: { type: 'string', pattern: '^[1-9]\\d*$' },
230
+ output: { type: 'string', minLength: 1 },
231
+ },
232
+ required: ['fid', 'fileId', 'output'],
233
+ },
234
+ },
235
+ {
236
+ name: 'invoice.delete.prepare',
237
+ risk: 'read-before-write',
238
+ inputSchema: {
239
+ type: 'object',
240
+ additionalProperties: false,
241
+ properties: {
242
+ fid: { type: 'string', pattern: '^[1-9]\\d*$' },
243
+ scope: { type: 'string', enum: ['personal', 'enterprise'] },
244
+ },
245
+ required: ['fid'],
246
+ },
247
+ },
248
+ {
249
+ name: 'invoice.delete.apply',
250
+ risk: 'high-risk-write',
251
+ inputSchema: {
252
+ type: 'object',
253
+ additionalProperties: false,
254
+ properties: {
255
+ continuation: { type: 'string', minLength: 1 },
256
+ confirm: { const: true },
257
+ },
258
+ required: ['continuation', 'confirm'],
259
+ },
260
+ },
261
+ ];
262
+ export const invoiceManifest = {
263
+ schemaVersion: 1,
264
+ name: 'invoice',
265
+ title: '泛微E10业票通发票管理',
266
+ version: '0.1.0',
267
+ host: 'e10-invoice',
268
+ status: 'builtin',
269
+ sourceSkill: 'weaver-e10-yepiaotong',
270
+ operations: invoiceOperations,
271
+ withheldOperations: [
272
+ {
273
+ name: 'invoice.share',
274
+ reason: '缺少接收人 appid/cid/uid 的稳定解析来源和共享关系回查契约。',
275
+ },
276
+ {
277
+ name: 'invoice.transfer',
278
+ reason: '缺少接收人归属回查契约,转让会改变发票持有人,暂不让 Agent 猜测。',
279
+ },
280
+ {
281
+ name: 'invoice.tag',
282
+ reason: '缺少标签定义来源、当前关系查询和移除/替换语义。',
283
+ },
284
+ ],
285
+ };
286
+ export function hasInvoiceOperation(operation) {
287
+ return invoiceOperations.some((item) => item.name === operation);
288
+ }
@@ -0,0 +1,106 @@
1
+ import { InvoiceCliError, invoiceConfirmationError, invoicePartialError, invoiceValidationError, isInvoiceNetworkError } from '../errors.js';
2
+ import { addPreview, asPositiveString, assertNotDuplicate, detailInfo, getDetail, identityFingerprint, normalizeAddInfo, normalizeScope, objectFingerprint, operateType, preview, reqBase, sessionMatches, workflow, } from './shared.js';
3
+ export class InvoiceAddPrepareOperation {
4
+ name = 'invoice.add.prepare';
5
+ async execute(input, { host, continuation }) {
6
+ const scope = normalizeScope(input.scope);
7
+ const info = normalizeAddInfo(input.info);
8
+ await assertNotDuplicate(info, host);
9
+ const session = await host.getSession();
10
+ const continuationToken = await continuation.issue({
11
+ operation: 'invoice.add.apply',
12
+ workflow: workflow('invoice.add', 'AWAITING_CONFIRMATION'),
13
+ baseUrl: session.baseUrl,
14
+ profile: session.profile,
15
+ userId: session.userId,
16
+ scope,
17
+ fingerprint: objectFingerprint(info),
18
+ identity: identityFingerprint(info),
19
+ preview: addPreview(info, scope),
20
+ });
21
+ return {
22
+ status: 'AWAITING_CONFIRMATION',
23
+ data: {
24
+ preview: addPreview(info, scope),
25
+ normalizedInfo: info,
26
+ continuation: continuationToken,
27
+ expiresInSeconds: 600,
28
+ workflow: workflow('invoice.add', 'AWAITING_CONFIRMATION'),
29
+ },
30
+ };
31
+ }
32
+ }
33
+ export class InvoiceAddApplyOperation {
34
+ name = 'invoice.add.apply';
35
+ async execute(input, { host, continuation }) {
36
+ if (input.confirm !== true)
37
+ throw invoiceConfirmationError('invoice.add.apply', { hint: '重新调用时传入 confirm=true' });
38
+ const lease = await continuation.verify(input.continuation, 'invoice.add.apply');
39
+ if (!lease.scope || typeof lease.fingerprint !== 'string' || typeof lease.identity !== 'string') {
40
+ throw invoiceValidationError('continuation_invalid', '新增 continuation 缺少有效上下文或发票指纹');
41
+ }
42
+ const session = await host.getSession();
43
+ if (!sessionMatches(lease, session))
44
+ throw invoiceValidationError('context_mismatch', '确认上下文已变化,请重新准备新增');
45
+ const info = normalizeAddInfo(input.info);
46
+ if (objectFingerprint(info) !== lease.fingerprint)
47
+ throw invoiceValidationError('target_changed', '待新增发票内容与确认时不一致,请重新准备新增');
48
+ await assertNotDuplicate(info, host);
49
+ let added;
50
+ try {
51
+ added = await host.invoiceRequest({
52
+ path: '/api/inc/add',
53
+ method: 'POST',
54
+ body: { infos: [info], operate_type: operateType(lease.scope), req_base: reqBase() },
55
+ });
56
+ }
57
+ catch (error) {
58
+ if (isInvoiceNetworkError(error)) {
59
+ throw invoicePartialError('新增请求发送后连接中断,结果不确定,禁止自动重试', {
60
+ preview: lease.preview,
61
+ workflow: workflow('invoice.add', 'EXECUTING'),
62
+ }, { stage: 'add' });
63
+ }
64
+ throw error;
65
+ }
66
+ const item = added?.infos?.[0];
67
+ if (!item) {
68
+ throw invoicePartialError('新增接口返回成功但缺少单张结果,结果不确定', {
69
+ preview: lease.preview,
70
+ result: added,
71
+ workflow: workflow('invoice.add', 'EXECUTING'),
72
+ }, { stage: 'add-response' });
73
+ }
74
+ if (item?.ret?.ret !== 0 || !item.info) {
75
+ throw new InvoiceCliError('api', 'add_item_failed', item?.ret?.message || '新增发票失败', {
76
+ code: item?.ret?.ret,
77
+ details: { result: item },
78
+ exitCode: 1,
79
+ });
80
+ }
81
+ const fid = asPositiveString(String(item.info.fid || item.info.id), '新增结果 fid');
82
+ let savedInfo;
83
+ try {
84
+ savedInfo = detailInfo(await getDetail({ fid }, host));
85
+ }
86
+ catch {
87
+ throw invoicePartialError('新增接口已返回成功,但详情回查失败,结果不确定', {
88
+ fid,
89
+ result: added,
90
+ workflow: workflow('invoice.add', 'VERIFYING'),
91
+ }, { stage: 'readback' });
92
+ }
93
+ if (identityFingerprint(savedInfo) !== lease.identity) {
94
+ throw invoicePartialError('新增后回查的票面身份字段不一致', {
95
+ fid,
96
+ result: added,
97
+ detail: preview(savedInfo),
98
+ workflow: workflow('invoice.add', 'VERIFYING'),
99
+ }, { stage: 'readback' });
100
+ }
101
+ return {
102
+ status: 'COMPLETE',
103
+ data: { fid, scope: lease.scope, saved: true, verified: true, info: savedInfo, result: added, workflow: workflow('invoice.add', 'COMPLETE') },
104
+ };
105
+ }
106
+ }
@@ -0,0 +1,92 @@
1
+ import { InvoiceCliError, invoiceConfirmationError, invoicePartialError, invoiceValidationError, isInvoiceNetworkError } from '../errors.js';
2
+ import { detailFingerprint, detailInfo, getDetail, normalizeScope, operateType, preview, reqBase, sessionMatches, workflow, } from './shared.js';
3
+ export class InvoiceDeletePrepareOperation {
4
+ name = 'invoice.delete.prepare';
5
+ async execute(input, { host, continuation }) {
6
+ const scope = normalizeScope(input.scope || 'personal');
7
+ const result = await getDetail(input, host);
8
+ const info = detailInfo(result);
9
+ const item = preview(info);
10
+ const session = await host.getSession();
11
+ const continuationToken = await continuation.issue({
12
+ operation: 'invoice.delete.apply',
13
+ workflow: workflow('invoice.delete', 'AWAITING_CONFIRMATION'),
14
+ baseUrl: session.baseUrl,
15
+ profile: session.profile,
16
+ userId: session.userId,
17
+ scope,
18
+ fid: item.fid,
19
+ fingerprint: detailFingerprint(info),
20
+ preview: item,
21
+ });
22
+ return { status: 'AWAITING_CONFIRMATION', data: { preview: item, continuation: continuationToken, expiresInSeconds: 600, workflow: workflow('invoice.delete', 'AWAITING_CONFIRMATION') } };
23
+ }
24
+ }
25
+ export class InvoiceDeleteApplyOperation {
26
+ name = 'invoice.delete.apply';
27
+ async execute(input, { host, continuation }) {
28
+ if (input.confirm !== true)
29
+ throw invoiceConfirmationError('invoice.delete.apply', { hint: '重新调用时传入 confirm=true' });
30
+ const lease = await continuation.verify(input.continuation, 'invoice.delete.apply');
31
+ if (typeof lease.fid !== 'string' || typeof lease.fingerprint !== 'string') {
32
+ throw invoiceValidationError('continuation_invalid', '删除 continuation 缺少有效目标或状态快照');
33
+ }
34
+ const session = await host.getSession();
35
+ if (!sessionMatches(lease, session))
36
+ throw invoiceValidationError('context_mismatch', '确认上下文已变化,请重新准备删除');
37
+ const info = detailInfo(await getDetail({ fid: lease.fid }, host));
38
+ if (detailFingerprint(info) !== lease.fingerprint)
39
+ throw invoiceValidationError('target_changed', '发票在确认后发生变化,请重新准备删除');
40
+ const p = info.comm_info?.pro || {};
41
+ if ((p.sreim ?? 0) !== 0 || (p.status ?? 0) !== 0 || (info.modify_info?.del ?? 0) !== 0) {
42
+ throw invoiceValidationError('invoice_not_deletable', '发票当前状态不允许删除', { status: preview(info) });
43
+ }
44
+ let deleted;
45
+ try {
46
+ deleted = await host.invoiceRequest({
47
+ path: '/api/inc/del',
48
+ method: 'POST',
49
+ body: { ids: [lease.fid], operate_type: operateType(lease.scope || 'personal'), flag: 0, req_base: reqBase() },
50
+ });
51
+ }
52
+ catch (error) {
53
+ if (isInvoiceNetworkError(error)) {
54
+ throw invoicePartialError('删除请求发送后连接中断,结果不确定,禁止自动重试', {
55
+ attempted: [lease.fid],
56
+ workflow: workflow('invoice.delete', 'EXECUTING'),
57
+ }, { stage: 'delete' });
58
+ }
59
+ throw error;
60
+ }
61
+ const failed = Array.isArray(deleted.fails) ? deleted.fails : [];
62
+ if (failed.length > 0) {
63
+ throw invoicePartialError('发票删除部分失败,结果不确定时禁止自动重试', {
64
+ attempted: [lease.fid],
65
+ failed,
66
+ workflow: workflow('invoice.delete', 'EXECUTING'),
67
+ }, { stage: 'delete' });
68
+ }
69
+ let verified = false;
70
+ try {
71
+ const after = await getDetail({ fid: lease.fid }, host);
72
+ verified = !after?.infos?.some((candidate) => candidate?.ret?.ret === 0 && candidate.info);
73
+ }
74
+ catch (error) {
75
+ if (error instanceof InvoiceCliError && error.type === 'api')
76
+ verified = true;
77
+ else {
78
+ throw invoicePartialError('删除接口已返回成功,但回查失败,结果不确定', {
79
+ attempted: [lease.fid],
80
+ workflow: workflow('invoice.delete', 'VERIFYING'),
81
+ }, { stage: 'readback' });
82
+ }
83
+ }
84
+ if (!verified) {
85
+ throw invoicePartialError('删除接口已返回成功,但回查仍发现发票,结果不确定', {
86
+ attempted: [lease.fid],
87
+ workflow: workflow('invoice.delete', 'VERIFYING'),
88
+ }, { stage: 'readback' });
89
+ }
90
+ return { status: 'COMPLETE', data: { deleted: [lease.fid], verified: true, result: deleted, workflow: workflow('invoice.delete', 'COMPLETE') } };
91
+ }
92
+ }
@@ -0,0 +1,16 @@
1
+ import { invoiceValidationError } from '../errors.js';
2
+ import { asPositiveString, associatedFileIds, detailInfo, getDetail, resolveDownloadOutput, writeDownload, } from './shared.js';
3
+ export class InvoiceDownloadOperation {
4
+ name = 'invoice.download';
5
+ async execute(input, { host }) {
6
+ const fid = asPositiveString(input.fid, 'fid');
7
+ const fileId = asPositiveString(input.fileId, 'fileId');
8
+ const info = detailInfo(await getDetail({ fid }, host));
9
+ if (!associatedFileIds(info).has(fileId))
10
+ throw invoiceValidationError('file_not_associated', 'fileId 不属于目标发票,拒绝下载');
11
+ const output = await resolveDownloadOutput(input.output);
12
+ const downloaded = await host.invoiceDownload({ id: fileId, fid, needLog: false });
13
+ await writeDownload(output, downloaded.content);
14
+ return { status: 'COMPLETE', data: { fid, fileId, output, size: downloaded.content.length, contentType: downloaded.contentType } };
15
+ }
16
+ }
@@ -0,0 +1,7 @@
1
+ import { getDetail } from './shared.js';
2
+ export class InvoiceGetOperation {
3
+ name = 'invoice.get';
4
+ async execute(input, { host }) {
5
+ return { status: 'COMPLETE', data: await getDetail(input, host) };
6
+ }
7
+ }
@@ -0,0 +1,128 @@
1
+ import { InvoiceCliError, invoiceConfirmationError, invoicePartialError, invoiceValidationError, isInvoiceNetworkError } from '../errors.js';
2
+ import { asPositiveString, detailInfo, getDetail, importFile, importOptions, preview, sessionMatches, workflow, } from './shared.js';
3
+ export class InvoiceImportPrepareOperation {
4
+ name = 'invoice.import.prepare';
5
+ async execute(input, { host, continuation }) {
6
+ const options = importOptions(input);
7
+ const prepared = await importFile(input);
8
+ const session = await host.getSession();
9
+ const filePreview = {
10
+ name: prepared.file.name,
11
+ size: prepared.size,
12
+ sha256: prepared.sha256,
13
+ validate: options.validate,
14
+ validationSkipped: !options.validate,
15
+ syncToOa: options.syncToOa,
16
+ targetScope: 'personal',
17
+ };
18
+ const continuationToken = await continuation.issue({
19
+ operation: 'invoice.import.apply',
20
+ workflow: workflow('invoice.import', 'AWAITING_CONFIRMATION'),
21
+ baseUrl: session.baseUrl,
22
+ profile: session.profile,
23
+ userId: session.userId,
24
+ sha256: prepared.sha256,
25
+ options,
26
+ preview: filePreview,
27
+ });
28
+ return {
29
+ status: 'AWAITING_CONFIRMATION',
30
+ data: {
31
+ preview: filePreview,
32
+ continuation: continuationToken,
33
+ expiresInSeconds: 600,
34
+ workflow: workflow('invoice.import', 'AWAITING_CONFIRMATION'),
35
+ },
36
+ };
37
+ }
38
+ }
39
+ export class InvoiceImportApplyOperation {
40
+ name = 'invoice.import.apply';
41
+ async execute(input, { host, continuation }) {
42
+ if (input.confirm !== true)
43
+ throw invoiceConfirmationError('invoice.import.apply', { hint: '重新调用时传入 confirm=true' });
44
+ const lease = await continuation.verify(input.continuation, 'invoice.import.apply');
45
+ if (!lease.options || typeof lease.options.validate !== 'boolean' || typeof lease.options.syncToOa !== 'boolean' || typeof lease.sha256 !== 'string') {
46
+ throw invoiceValidationError('continuation_invalid', '导入 continuation 缺少有效文件指纹或选项');
47
+ }
48
+ const session = await host.getSession();
49
+ if (!sessionMatches(lease, session))
50
+ throw invoiceValidationError('context_mismatch', '确认上下文已变化,请重新准备导入');
51
+ const prepared = await importFile(input);
52
+ if (prepared.sha256 !== lease.sha256)
53
+ throw invoiceValidationError('target_changed', '导入文件与确认时不一致,请重新准备导入');
54
+ const fields = { allSuccess: lease.options.validate ? '0' : '1' };
55
+ if (!lease.options.validate)
56
+ fields.filedMap = JSON.stringify({ isToOa: lease.options.syncToOa ? '1' : '0' });
57
+ let result;
58
+ try {
59
+ result = await host.invoiceMultipartRequest({
60
+ path: '/api/app/inc/biz/addInvoiceByFile',
61
+ fields,
62
+ file: prepared.file,
63
+ timeoutMs: 90_000,
64
+ });
65
+ }
66
+ catch (error) {
67
+ if (isInvoiceNetworkError(error)) {
68
+ throw invoicePartialError('导入请求发送后连接中断,结果不确定,禁止自动重试', {
69
+ preview: lease.preview,
70
+ workflow: workflow('invoice.import', 'EXECUTING'),
71
+ }, { stage: 'import' });
72
+ }
73
+ throw error;
74
+ }
75
+ const items = Array.isArray(result?.infos) ? result.infos : [];
76
+ if (!items.length) {
77
+ throw invoicePartialError('导入接口返回成功但没有逐张结果,结果不确定', {
78
+ preview: lease.preview,
79
+ result,
80
+ workflow: workflow('invoice.import', 'EXECUTING'),
81
+ }, { stage: 'import-response' });
82
+ }
83
+ const succeeded = items.filter((item) => item?.ret?.ret === 0 && (item.info?.fid || item.info?.id));
84
+ const failed = items.filter((item) => item?.ret?.ret !== 0 || !(item.info?.fid || item.info?.id));
85
+ if (!succeeded.length) {
86
+ throw new InvoiceCliError('api', 'import_failed', failed[0]?.ret?.message || '文件导入没有成功发票', {
87
+ code: failed[0]?.ret?.ret,
88
+ details: { failed },
89
+ exitCode: 1,
90
+ });
91
+ }
92
+ const verified = [];
93
+ for (const item of succeeded) {
94
+ const fid = asPositiveString(String(item.info.fid || item.info.id), '导入结果 fid');
95
+ try {
96
+ const info = detailInfo(await getDetail({ fid }, host));
97
+ verified.push({ fid, info });
98
+ }
99
+ catch {
100
+ throw invoicePartialError('导入已返回成功,但详情回查失败,结果不确定', {
101
+ fids: succeeded.map((candidate) => String(candidate.info?.fid || candidate.info?.id)),
102
+ result,
103
+ workflow: workflow('invoice.import', 'VERIFYING'),
104
+ }, { stage: 'readback' });
105
+ }
106
+ }
107
+ if (failed.length) {
108
+ throw invoicePartialError('文件导入部分成功,禁止自动重试整个文件', {
109
+ succeeded: verified.map((item) => ({ fid: item.fid, preview: preview(item.info) })),
110
+ failed,
111
+ result,
112
+ workflow: workflow('invoice.import', 'VERIFYING'),
113
+ }, { stage: 'import-items' });
114
+ }
115
+ return {
116
+ status: 'COMPLETE',
117
+ data: {
118
+ imported: verified.map((item) => ({ fid: item.fid, preview: preview(item.info) })),
119
+ validationRequested: lease.options.validate,
120
+ validationSkipped: !lease.options.validate,
121
+ syncToOa: lease.options.syncToOa,
122
+ verified: true,
123
+ result,
124
+ workflow: workflow('invoice.import', 'COMPLETE'),
125
+ },
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,12 @@
1
+ import { listInvoices } from './shared.js';
2
+ export class InvoiceListOperation {
3
+ name;
4
+ flag;
5
+ constructor(name, flag) {
6
+ this.name = name;
7
+ this.flag = flag;
8
+ }
9
+ execute(input, { host }) {
10
+ return listInvoices(input, host, this.flag);
11
+ }
12
+ }