@perrylink/dsh-einvoice 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +1 -1
- package/lib/adapters/ofd-zip-xml.js +4 -4
- package/lib/core/invoice.d.ts +2 -0
- package/lib/core/invoice.js +14 -0
- 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+S2+S3 / public demo (0.1.0-alpha.
|
|
5
|
+
> **Status: S1+S2+S3 / public demo (0.1.0-alpha.3).** 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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** OFD (ZIP + XML) and standalone XML reader for SKU-04 S1. */
|
|
2
2
|
import { unzipSync } from 'fflate';
|
|
3
3
|
import { XMLParser } from 'fast-xml-parser';
|
|
4
|
-
import { InvoiceError, amountToCents, normalizeDate, normalizeTaxId, } from '../core/invoice.js';
|
|
4
|
+
import { InvoiceError, amountToCents, priceToCents, normalizeDate, normalizeTaxId, } from '../core/invoice.js';
|
|
5
5
|
export const PARSER_ID = 'ofd-zip-xml';
|
|
6
6
|
export const PARSER_VERSION = 'ofd-zip-xml@1';
|
|
7
7
|
const xmlParser = new XMLParser({
|
|
@@ -96,7 +96,7 @@ function parseOldInvoice(root) {
|
|
|
96
96
|
line_no: index + 1,
|
|
97
97
|
description: textAt(line, 'Item'),
|
|
98
98
|
quantity: textAt(line, 'Quantity') || undefined,
|
|
99
|
-
unit_price_cents: textAt(line, 'Price') ?
|
|
99
|
+
unit_price_cents: textAt(line, 'Price') ? priceToCents(textAt(line, 'Price')) : undefined,
|
|
100
100
|
amount_cents: amount ? amountToCents(amount) : 0,
|
|
101
101
|
tax_rate: textAt(line, 'TaxScheme') || undefined,
|
|
102
102
|
tax_cents: tax ? amountToCents(tax) : undefined,
|
|
@@ -240,7 +240,7 @@ function parseDigitalInvoice(root, textMap) {
|
|
|
240
240
|
line_no: index + 1,
|
|
241
241
|
description,
|
|
242
242
|
quantity: textMap.get(quantityRefs[index] ?? '') || undefined,
|
|
243
|
-
unit_price_cents: textMap.get(priceRefs[index] ?? '') ?
|
|
243
|
+
unit_price_cents: textMap.get(priceRefs[index] ?? '') ? priceToCents(textMap.get(priceRefs[index] ?? '') ?? '') : undefined,
|
|
244
244
|
amount_cents: amountText ? amountToCents(amountText) : 0,
|
|
245
245
|
tax_rate: textMap.get(taxSchemeRefs[index] ?? '') || undefined,
|
|
246
246
|
tax_cents: taxText ? amountToCents(taxText) : undefined,
|
|
@@ -299,7 +299,7 @@ function parseGenericInvoice(root) {
|
|
|
299
299
|
line_no: index + 1,
|
|
300
300
|
description: textAt(line, 'Description') || textAt(line, 'Item'),
|
|
301
301
|
quantity: textAt(line, 'Quantity') || undefined,
|
|
302
|
-
unit_price_cents: textAt(line, 'UnitPrice') ?
|
|
302
|
+
unit_price_cents: textAt(line, 'UnitPrice') ? priceToCents(textAt(line, 'UnitPrice')) : undefined,
|
|
303
303
|
amount_cents: textAt(line, 'Amount') ? amountToCents(textAt(line, 'Amount')) : 0,
|
|
304
304
|
tax_rate: textAt(line, 'TaxRate') || undefined,
|
|
305
305
|
tax_cents: textAt(line, 'TaxAmount') ? amountToCents(textAt(line, 'TaxAmount')) : undefined,
|
package/lib/core/invoice.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export declare class InvoiceError extends Error {
|
|
|
48
48
|
/** Parse a decimal amount (yuan) into integer cents without floating point. */
|
|
49
49
|
export declare function amountToCents(raw: unknown): number;
|
|
50
50
|
/** Normalize the common Chinese date spellings to YYYY-MM-DD. */
|
|
51
|
+
/** Unit prices may carry tax-calculation precision; round half-up to integer cents. */
|
|
52
|
+
export declare function priceToCents(raw: unknown): number;
|
|
51
53
|
export declare function normalizeDate(raw: unknown): string;
|
|
52
54
|
/** Validate the shape of a PRC taxpayer id without claiming checksum validity. */
|
|
53
55
|
export declare function normalizeTaxId(raw: unknown): string;
|
package/lib/core/invoice.js
CHANGED
|
@@ -37,6 +37,20 @@ export function amountToCents(raw) {
|
|
|
37
37
|
return Number(cents);
|
|
38
38
|
}
|
|
39
39
|
/** Normalize the common Chinese date spellings to YYYY-MM-DD. */
|
|
40
|
+
/** Unit prices may carry tax-calculation precision; round half-up to integer cents. */
|
|
41
|
+
export function priceToCents(raw) {
|
|
42
|
+
if (typeof raw !== 'string' && typeof raw !== 'number')
|
|
43
|
+
throw new InvoiceError('E_INV_FIELD_MISSING', 'price is not a value');
|
|
44
|
+
const normalized = String(raw).trim().replace(/[¥¥,,\s]/g, '');
|
|
45
|
+
const match = /^(-?)(\d+)(?:\.(\d{1,12}))?$/.exec(normalized);
|
|
46
|
+
if (!match)
|
|
47
|
+
throw new InvoiceError('E_INV_FIELD_MISSING', `price is not a decimal: ${String(raw).slice(0, 80)}`);
|
|
48
|
+
const sign = match[1] === '-' ? -1n : 1n;
|
|
49
|
+
const whole = BigInt(match[2]);
|
|
50
|
+
const fraction = BigInt((match[3] ?? '').padEnd(12, '0'));
|
|
51
|
+
const cents = (whole * 100n + (fraction + 5000000000n) / 10000000000n);
|
|
52
|
+
return Number(sign * cents);
|
|
53
|
+
}
|
|
40
54
|
export function normalizeDate(raw) {
|
|
41
55
|
if (typeof raw !== 'string' || raw.trim() === '') {
|
|
42
56
|
throw new InvoiceError('E_INV_FIELD_MISSING', 'issue date is empty');
|
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.3",
|
|
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",
|