@perrylink/dsh-einvoice 0.1.0-alpha.0 → 0.1.0-alpha.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.
- package/README.md +5 -2
- package/lib/plugin/storage.d.ts +72 -0
- package/lib/plugin/storage.js +20 -0
- package/lib/plugin/tools.js +240 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
File-level Chinese e-invoice ingest, parse and pool for DeepSeek Harness (SKU-04 S1).
|
|
4
4
|
|
|
5
|
-
> **Status: S1 / public demo.** The customer evidence gate (2 enterprises + 1 bookkeeping firm + 100 real invoices) is still PARKED. S1 uses public/open-source and synthetic fixtures; its acceptance rows are `not counted` toward GA. Do not describe this package as GA-ready.
|
|
5
|
+
> **Status: S1+S2 / public demo (0.1.0-alpha.1).** The customer evidence gate (2 enterprises + 1 bookkeeping firm + 100 real invoices) is still PARKED. S1 uses public/open-source and synthetic fixtures; its acceptance rows are `not counted` toward GA. Do not describe this package as GA-ready.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -20,8 +20,11 @@ The bundle inserts one Host row. It requires the base profile's `tools` and `sto
|
|
|
20
20
|
| `invoice.ingest` | Ingest one local OFD/XML file, compute SHA-256, store the original locally, idempotent by bytes | R1 local write |
|
|
21
21
|
| `invoice.parse` | Parse old OFD `Attachs/original_invoice.xml` or 数电票 `Tags/CustomTag.xml` + ObjectRef; return eight fields, lines, evidence and unknown fields | R0 |
|
|
22
22
|
| `invoice.query` | Query the invoice pool by entity, period, lifecycle or text | R0 |
|
|
23
|
+
| `invoice.dedup` | Group duplicates by invoice number or seller+date+total | R0 |
|
|
24
|
+
| `invoice.verify` | Queue parsed invoices for manual verification; optional buyer-tax-id mismatch flags | R1 local write |
|
|
25
|
+
| `invoice.receipt` | Hash a local receipt, append it, move lifecycle to verified/quarantined; idempotent by receipt bytes | R1 local write |
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
S3 adds red/blue matching, archive packages and archive checks; S2 verification is manual/local (no tax-bureau network call).
|
|
25
28
|
|
|
26
29
|
## Configuration
|
|
27
30
|
|
package/lib/plugin/storage.d.ts
CHANGED
|
@@ -15,6 +15,33 @@ export declare const lineSchema: z.ZodObject<{
|
|
|
15
15
|
tax_rate: z.ZodOptional<z.ZodString>;
|
|
16
16
|
tax_cents: z.ZodOptional<z.ZodNumber>;
|
|
17
17
|
}, z.core.$strip>;
|
|
18
|
+
export declare const verificationSchema: z.ZodObject<{
|
|
19
|
+
state: z.ZodEnum<{
|
|
20
|
+
verified: "verified";
|
|
21
|
+
pending: "pending";
|
|
22
|
+
failed: "failed";
|
|
23
|
+
manual: "manual";
|
|
24
|
+
}>;
|
|
25
|
+
queued_at: z.ZodOptional<z.ZodString>;
|
|
26
|
+
updated_at: z.ZodOptional<z.ZodString>;
|
|
27
|
+
attempts: z.ZodDefault<z.ZodNumber>;
|
|
28
|
+
last_result: z.ZodOptional<z.ZodString>;
|
|
29
|
+
manual_reason: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export declare const receiptSchema: z.ZodObject<{
|
|
32
|
+
receipt_id: z.ZodString;
|
|
33
|
+
result_code: z.ZodString;
|
|
34
|
+
result: z.ZodEnum<{
|
|
35
|
+
pass: "pass";
|
|
36
|
+
fail: "fail";
|
|
37
|
+
unknown: "unknown";
|
|
38
|
+
}>;
|
|
39
|
+
verified_at: z.ZodString;
|
|
40
|
+
operator: z.ZodString;
|
|
41
|
+
receipt_sha256: z.ZodString;
|
|
42
|
+
receipt_path: z.ZodString;
|
|
43
|
+
recorded_at: z.ZodString;
|
|
44
|
+
}, z.core.$strip>;
|
|
18
45
|
export declare const invoiceSchema: z.ZodObject<{
|
|
19
46
|
doc_id: z.ZodString;
|
|
20
47
|
entity_id: z.ZodString;
|
|
@@ -53,6 +80,33 @@ export declare const invoiceSchema: z.ZodObject<{
|
|
|
53
80
|
archived: "archived";
|
|
54
81
|
quarantined: "quarantined";
|
|
55
82
|
}>;
|
|
83
|
+
verification: z.ZodOptional<z.ZodObject<{
|
|
84
|
+
state: z.ZodEnum<{
|
|
85
|
+
verified: "verified";
|
|
86
|
+
pending: "pending";
|
|
87
|
+
failed: "failed";
|
|
88
|
+
manual: "manual";
|
|
89
|
+
}>;
|
|
90
|
+
queued_at: z.ZodOptional<z.ZodString>;
|
|
91
|
+
updated_at: z.ZodOptional<z.ZodString>;
|
|
92
|
+
attempts: z.ZodDefault<z.ZodNumber>;
|
|
93
|
+
last_result: z.ZodOptional<z.ZodString>;
|
|
94
|
+
manual_reason: z.ZodOptional<z.ZodString>;
|
|
95
|
+
}, z.core.$strip>>;
|
|
96
|
+
receipts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
97
|
+
receipt_id: z.ZodString;
|
|
98
|
+
result_code: z.ZodString;
|
|
99
|
+
result: z.ZodEnum<{
|
|
100
|
+
pass: "pass";
|
|
101
|
+
fail: "fail";
|
|
102
|
+
unknown: "unknown";
|
|
103
|
+
}>;
|
|
104
|
+
verified_at: z.ZodString;
|
|
105
|
+
operator: z.ZodString;
|
|
106
|
+
receipt_sha256: z.ZodString;
|
|
107
|
+
receipt_path: z.ZodString;
|
|
108
|
+
recorded_at: z.ZodString;
|
|
109
|
+
}, z.core.$strip>>>;
|
|
56
110
|
source_path: z.ZodString;
|
|
57
111
|
source_sha256: z.ZodString;
|
|
58
112
|
source_size: z.ZodNumber;
|
|
@@ -116,6 +170,24 @@ export declare const finInvoiceSpec: {
|
|
|
116
170
|
actor: string;
|
|
117
171
|
evidence_sha256: string;
|
|
118
172
|
invoice_code?: string | undefined;
|
|
173
|
+
verification?: {
|
|
174
|
+
state: "verified" | "pending" | "failed" | "manual";
|
|
175
|
+
attempts: number;
|
|
176
|
+
queued_at?: string | undefined;
|
|
177
|
+
updated_at?: string | undefined;
|
|
178
|
+
last_result?: string | undefined;
|
|
179
|
+
manual_reason?: string | undefined;
|
|
180
|
+
} | undefined;
|
|
181
|
+
receipts?: {
|
|
182
|
+
receipt_id: string;
|
|
183
|
+
result_code: string;
|
|
184
|
+
result: "pass" | "fail" | "unknown";
|
|
185
|
+
verified_at: string;
|
|
186
|
+
operator: string;
|
|
187
|
+
receipt_sha256: string;
|
|
188
|
+
receipt_path: string;
|
|
189
|
+
recorded_at: string;
|
|
190
|
+
}[] | undefined;
|
|
119
191
|
}>;
|
|
120
192
|
};
|
|
121
193
|
};
|
package/lib/plugin/storage.js
CHANGED
|
@@ -19,6 +19,24 @@ export const lineSchema = z.object({
|
|
|
19
19
|
tax_rate: z.string().optional(),
|
|
20
20
|
tax_cents: z.number().int().optional(),
|
|
21
21
|
});
|
|
22
|
+
export const verificationSchema = z.object({
|
|
23
|
+
state: z.enum(['pending', 'verified', 'failed', 'manual']),
|
|
24
|
+
queued_at: z.string().optional(),
|
|
25
|
+
updated_at: z.string().optional(),
|
|
26
|
+
attempts: z.number().int().nonnegative().default(0),
|
|
27
|
+
last_result: z.string().optional(),
|
|
28
|
+
manual_reason: z.string().optional(),
|
|
29
|
+
});
|
|
30
|
+
export const receiptSchema = z.object({
|
|
31
|
+
receipt_id: z.string(),
|
|
32
|
+
result_code: z.string(),
|
|
33
|
+
result: z.enum(['pass', 'fail', 'unknown']),
|
|
34
|
+
verified_at: z.string(),
|
|
35
|
+
operator: z.string(),
|
|
36
|
+
receipt_sha256: z.string(),
|
|
37
|
+
receipt_path: z.string(),
|
|
38
|
+
recorded_at: z.string(),
|
|
39
|
+
});
|
|
22
40
|
export const invoiceSchema = z.object({
|
|
23
41
|
doc_id: z.string().min(1),
|
|
24
42
|
entity_id: z.string(),
|
|
@@ -37,6 +55,8 @@ export const invoiceSchema = z.object({
|
|
|
37
55
|
unknown_fields: z.record(z.string(), z.unknown()),
|
|
38
56
|
parser_version: z.string(),
|
|
39
57
|
lifecycle: z.enum(['received', 'parsed', 'verified', 'archived', 'quarantined']),
|
|
58
|
+
verification: verificationSchema.optional(),
|
|
59
|
+
receipts: z.array(receiptSchema).optional(),
|
|
40
60
|
source_path: z.string(),
|
|
41
61
|
source_sha256: z.string(),
|
|
42
62
|
source_size: z.number().int().nonnegative(),
|
package/lib/plugin/tools.js
CHANGED
|
@@ -300,5 +300,244 @@ export function createTools(deps) {
|
|
|
300
300
|
};
|
|
301
301
|
},
|
|
302
302
|
});
|
|
303
|
-
|
|
303
|
+
const dedup = defineTool({
|
|
304
|
+
name: 'invoice.dedup',
|
|
305
|
+
description: 'Group parsed invoices by invoice number, or by seller+date+total when the number is absent; returns duplicate groups and their basis. Read-only.',
|
|
306
|
+
parameters: {
|
|
307
|
+
filter: {
|
|
308
|
+
type: 'object',
|
|
309
|
+
additionalProperties: false,
|
|
310
|
+
properties: {
|
|
311
|
+
entity_id: { type: 'string' },
|
|
312
|
+
period: { type: 'string', description: 'YYYY-MM from issue_date' },
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
doc_ids: { type: 'array', items: { type: 'string' }, description: 'Optional subset; defaults to the whole pool' },
|
|
316
|
+
},
|
|
317
|
+
output: {
|
|
318
|
+
schema: {
|
|
319
|
+
type: 'object',
|
|
320
|
+
additionalProperties: false,
|
|
321
|
+
properties: {
|
|
322
|
+
groups: {
|
|
323
|
+
type: 'array',
|
|
324
|
+
required: true,
|
|
325
|
+
items: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
additionalProperties: false,
|
|
328
|
+
properties: {
|
|
329
|
+
fingerprint: { type: 'string', required: true },
|
|
330
|
+
kind: { type: 'string', required: true },
|
|
331
|
+
basis: { type: 'string', required: true },
|
|
332
|
+
members: {
|
|
333
|
+
type: 'array',
|
|
334
|
+
required: true,
|
|
335
|
+
items: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
additionalProperties: false,
|
|
338
|
+
properties: {
|
|
339
|
+
doc_id: { type: 'string', required: true },
|
|
340
|
+
invoice_no: { type: 'string', required: true },
|
|
341
|
+
seller_name: { type: 'string', required: true },
|
|
342
|
+
total_cents: { type: 'number', required: true },
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
total: { type: 'number', required: true },
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
render: (_args, value) => text(`dedup: ${value.total} duplicate groups`),
|
|
353
|
+
},
|
|
354
|
+
async execute(args) {
|
|
355
|
+
const filter = args.filter ?? {};
|
|
356
|
+
const selected = args.doc_ids?.length
|
|
357
|
+
? args.doc_ids.map((id) => deps.table.get(id)).filter((record) => record !== undefined)
|
|
358
|
+
: [...deps.table.entries()].map(([, record]) => record);
|
|
359
|
+
const groups = new Map();
|
|
360
|
+
for (const record of selected) {
|
|
361
|
+
if (filter.entity_id && record.entity_id !== filter.entity_id)
|
|
362
|
+
continue;
|
|
363
|
+
if (filter.period && invoicePeriod(record.issue_date) !== filter.period)
|
|
364
|
+
continue;
|
|
365
|
+
const byNumber = record.invoice_no !== '';
|
|
366
|
+
const fingerprint = byNumber
|
|
367
|
+
? `invoice_no:${record.invoice_no}`
|
|
368
|
+
: `seller_date_total:${record.seller.tax_id}:${record.issue_date}:${record.total_cents}`;
|
|
369
|
+
const kind = byNumber ? 'invoice_no' : 'seller_date_total';
|
|
370
|
+
const basis = byNumber ? 'same invoice number' : 'same seller tax id + issue date + total';
|
|
371
|
+
let group = groups.get(fingerprint);
|
|
372
|
+
if (!group) {
|
|
373
|
+
group = { fingerprint, kind, basis, members: [] };
|
|
374
|
+
groups.set(fingerprint, group);
|
|
375
|
+
}
|
|
376
|
+
group.members.push({
|
|
377
|
+
doc_id: record.doc_id,
|
|
378
|
+
invoice_no: record.invoice_no,
|
|
379
|
+
seller_name: record.seller.name,
|
|
380
|
+
total_cents: record.total_cents,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
const duplicateGroups = [...groups.values()].filter((group) => group.members.length > 1);
|
|
384
|
+
return { groups: duplicateGroups, total: duplicateGroups.length };
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
const verify = defineTool({
|
|
388
|
+
name: 'invoice.verify',
|
|
389
|
+
description: 'Queue parsed invoices for manual/semi-automatic verification. S2 has no tax-bureau network call: it records a pending local queue and optional entity-tax-id mismatch flags.',
|
|
390
|
+
parameters: {
|
|
391
|
+
invoice_ids: { type: 'array', items: { type: 'string' }, description: 'Optional subset; defaults to parsed invoices' },
|
|
392
|
+
entity_id: { type: 'string' },
|
|
393
|
+
period: { type: 'string', description: 'YYYY-MM from issue_date' },
|
|
394
|
+
entity_tax_id: { type: 'string', description: 'Expected buyer tax id for header validation' },
|
|
395
|
+
limit: { type: 'number', description: '1..200, default 100' },
|
|
396
|
+
},
|
|
397
|
+
output: {
|
|
398
|
+
schema: {
|
|
399
|
+
type: 'object',
|
|
400
|
+
additionalProperties: false,
|
|
401
|
+
properties: {
|
|
402
|
+
queue: {
|
|
403
|
+
type: 'array',
|
|
404
|
+
required: true,
|
|
405
|
+
items: {
|
|
406
|
+
type: 'object',
|
|
407
|
+
additionalProperties: false,
|
|
408
|
+
properties: {
|
|
409
|
+
doc_id: { type: 'string', required: true },
|
|
410
|
+
invoice_no: { type: 'string', required: true },
|
|
411
|
+
seller_name: { type: 'string', required: true },
|
|
412
|
+
total_cents: { type: 'number', required: true },
|
|
413
|
+
state: { type: 'string', required: true },
|
|
414
|
+
entity_match: { type: 'boolean', required: true },
|
|
415
|
+
needs_review: { type: 'array', items: { type: 'string' }, required: true },
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
total: { type: 'number', required: true },
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
render: (_args, value) => text(`verify queue: ${value.total} pending`),
|
|
423
|
+
},
|
|
424
|
+
async execute(args) {
|
|
425
|
+
const limit = Math.max(1, Math.min(200, Math.floor(args.limit ?? 100)));
|
|
426
|
+
const expected = args.entity_tax_id?.trim().toUpperCase();
|
|
427
|
+
const selected = args.invoice_ids?.length
|
|
428
|
+
? args.invoice_ids.map((id) => deps.table.get(id)).filter((record) => record !== undefined)
|
|
429
|
+
: [...deps.table.entries()].map(([, record]) => record);
|
|
430
|
+
const queue = [];
|
|
431
|
+
for (const record of selected) {
|
|
432
|
+
if (queue.length >= limit)
|
|
433
|
+
break;
|
|
434
|
+
if (record.lifecycle !== 'parsed' && record.lifecycle !== 'verified')
|
|
435
|
+
continue;
|
|
436
|
+
if (args.entity_id && record.entity_id !== args.entity_id)
|
|
437
|
+
continue;
|
|
438
|
+
if (args.period && invoicePeriod(record.issue_date) !== args.period)
|
|
439
|
+
continue;
|
|
440
|
+
const entityMatch = !expected || record.buyer.tax_id.toUpperCase() === expected;
|
|
441
|
+
const needsReview = [];
|
|
442
|
+
if (!entityMatch)
|
|
443
|
+
needsReview.push('entity_mismatch');
|
|
444
|
+
if (!record.buyer.tax_id)
|
|
445
|
+
needsReview.push('missing_buyer_tax_id');
|
|
446
|
+
const now = new Date().toISOString();
|
|
447
|
+
const updated = {
|
|
448
|
+
...record,
|
|
449
|
+
verification: {
|
|
450
|
+
state: 'pending',
|
|
451
|
+
queued_at: record.verification?.queued_at ?? now,
|
|
452
|
+
updated_at: now,
|
|
453
|
+
attempts: record.verification?.attempts ?? 0,
|
|
454
|
+
manual_reason: needsReview.join(',') || record.verification?.manual_reason,
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
await deps.table.put(record.doc_id, updated);
|
|
458
|
+
queue.push({
|
|
459
|
+
doc_id: record.doc_id,
|
|
460
|
+
invoice_no: record.invoice_no,
|
|
461
|
+
seller_name: record.seller.name,
|
|
462
|
+
total_cents: record.total_cents,
|
|
463
|
+
state: 'pending',
|
|
464
|
+
entity_match: entityMatch,
|
|
465
|
+
needs_review: needsReview,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return { queue, total: queue.length };
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
const receipt = defineTool({
|
|
472
|
+
name: 'invoice.receipt',
|
|
473
|
+
description: 'Record a local verification receipt for one parsed invoice: hash the receipt file, append an immutable receipt record, and move the invoice to verified/quarantined. Local R1 write; no tax-bureau write.',
|
|
474
|
+
parameters: {
|
|
475
|
+
invoice_id: { type: 'string', required: true },
|
|
476
|
+
receipt_file: { type: 'string', required: true, description: 'Local receipt file under an allowed input root' },
|
|
477
|
+
result_code: { type: 'string', required: true },
|
|
478
|
+
result: { type: 'string', enum: ['pass', 'fail', 'unknown'], required: true },
|
|
479
|
+
verified_at: { type: 'string', required: true },
|
|
480
|
+
operator: { type: 'string', required: true },
|
|
481
|
+
},
|
|
482
|
+
output: {
|
|
483
|
+
schema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
additionalProperties: false,
|
|
486
|
+
properties: {
|
|
487
|
+
receipt_id: { type: 'string', required: true },
|
|
488
|
+
receipt_sha256: { type: 'string', required: true },
|
|
489
|
+
duplicate: { type: 'boolean', required: true },
|
|
490
|
+
lifecycle: { type: 'string', required: true },
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
render: (_args, value) => text(`receipt ${value.receipt_id} (${value.duplicate ? 'duplicate' : 'recorded'}), invoice=${value.lifecycle}`),
|
|
494
|
+
},
|
|
495
|
+
async execute(args) {
|
|
496
|
+
const record = deps.table.get(args.invoice_id);
|
|
497
|
+
if (!record)
|
|
498
|
+
throw new InvoiceError('E_INV_DOC_NOT_FOUND', `unknown invoice: ${args.invoice_id}`);
|
|
499
|
+
if (!args.result_code.trim() || !args.verified_at.trim() || !args.operator.trim()) {
|
|
500
|
+
throw new InvoiceError('E_INV_RECEIPT_INCOMPLETE', 'result_code, verified_at and operator are required', { needs_review: true });
|
|
501
|
+
}
|
|
502
|
+
const resolved = await resolveAllowedPath(args.receipt_file, deps.inputRoots);
|
|
503
|
+
const info = await stat(resolved);
|
|
504
|
+
if (!info.isFile() || info.size === 0)
|
|
505
|
+
throw new InvoiceError('E_INV_RECEIPT_INCOMPLETE', 'receipt file is empty or not a file');
|
|
506
|
+
const buffer = await readFile(resolved);
|
|
507
|
+
const receiptSha = sha256Buffer(buffer);
|
|
508
|
+
const existing = (record.receipts ?? []).find((item) => item.receipt_sha256 === receiptSha);
|
|
509
|
+
if (existing) {
|
|
510
|
+
return { receipt_id: existing.receipt_id, receipt_sha256: receiptSha, duplicate: true, lifecycle: record.lifecycle };
|
|
511
|
+
}
|
|
512
|
+
const now = new Date().toISOString();
|
|
513
|
+
const receiptRecord = {
|
|
514
|
+
receipt_id: newDocId(),
|
|
515
|
+
result_code: args.result_code.trim(),
|
|
516
|
+
result: args.result,
|
|
517
|
+
verified_at: args.verified_at.trim(),
|
|
518
|
+
operator: args.operator.trim(),
|
|
519
|
+
receipt_sha256: receiptSha,
|
|
520
|
+
receipt_path: resolved,
|
|
521
|
+
recorded_at: now,
|
|
522
|
+
};
|
|
523
|
+
const lifecycle = args.result === 'pass' ? 'verified' : args.result === 'fail' ? 'quarantined' : 'parsed';
|
|
524
|
+
const updated = {
|
|
525
|
+
...record,
|
|
526
|
+
lifecycle,
|
|
527
|
+
receipts: [...(record.receipts ?? []), receiptRecord],
|
|
528
|
+
verification: {
|
|
529
|
+
state: args.result === 'pass' ? 'verified' : args.result === 'fail' ? 'failed' : 'manual',
|
|
530
|
+
queued_at: record.verification?.queued_at,
|
|
531
|
+
updated_at: now,
|
|
532
|
+
attempts: record.verification?.attempts ?? 0,
|
|
533
|
+
last_result: args.result_code.trim(),
|
|
534
|
+
manual_reason: args.result === 'unknown' ? 'unknown_result' : undefined,
|
|
535
|
+
},
|
|
536
|
+
updated_at: now,
|
|
537
|
+
};
|
|
538
|
+
await deps.table.put(record.doc_id, updated);
|
|
539
|
+
return { receipt_id: receiptRecord.receipt_id, receipt_sha256: receiptSha, duplicate: false, lifecycle };
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
return [ingest, parse, query, dedup, verify, receipt];
|
|
304
543
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perrylink/dsh-einvoice",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
4
|
"description": "File-level Chinese e-invoice (OFD/XML) ingest, parse and pool for DeepSeek Harness (SKU-04 S1).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/plugin/index.js",
|