@getpeppr/cli 0.10.0 → 0.11.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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +9 -8
- package/dist/index.js +152 -74
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to `@getpeppr/cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The CLI bundles `@getpeppr/sdk` into its published artifact (tsup `noExternal`), so `validate`, `convert` and `init` run the SDK's validators, country rules and UBL builder locally — an SDK release only reaches CLI users once the CLI is rebundled. Rebundle-only releases are listed with the SDK version they ship.
|
|
6
6
|
|
|
7
|
+
## [0.11.1] — 2026-09-04
|
|
8
|
+
|
|
9
|
+
Rebundles `@getpeppr/sdk` 5.2.0 (GPR-1084).
|
|
10
|
+
|
|
11
|
+
- The `schematron` section of `getpeppr validate` now reports `BR-16` for a
|
|
12
|
+
missing invoice line and uses explicit `GETPEPPR-*` IDs for ten SDK-local
|
|
13
|
+
diagnostics that previously cited different official rules. The separate
|
|
14
|
+
`structure` and `countryRules` sections are unchanged (GPR-1267 tracks their
|
|
15
|
+
reverse audit).
|
|
16
|
+
- Validation predicates, warning/error severities and process exit codes are
|
|
17
|
+
unchanged. The unit warning also stops calling every code outside the SDK's
|
|
18
|
+
small convenience list invalid under Rec20, and the payable warning no longer
|
|
19
|
+
invents `prepaidAmount` as the only possible cause. Scripts that filter the
|
|
20
|
+
`schematron` result by an old rule ID or warning text must update that filter.
|
|
21
|
+
- Human-readable output now labels the section `Offline Checks (partial)` and
|
|
22
|
+
reports `Pre-flight checks passed` or `Pre-flight checks found errors`; it no
|
|
23
|
+
longer claims that the partial pass proves validity or non-compliance. The
|
|
24
|
+
`--json` shape and process exit codes are unchanged.
|
|
25
|
+
|
|
26
|
+
## [0.11.0] — 2026-09-04
|
|
27
|
+
|
|
28
|
+
Rebundles `@getpeppr/sdk` 5.1.0.
|
|
29
|
+
|
|
30
|
+
- **`getpeppr validate` follows BR-CL-01's two vocabularies** instead of a hand-written list of seven codes. Measured against the published 0.10.0 (which bundles SDK 5.0.0):
|
|
31
|
+
- **44 legal invoice codes stop being falsely refused** — `326` (partial invoice, named by `DE-R-017`), `382` and 42 others now pass. On 0.10.0 each exits 1.
|
|
32
|
+
- **Credit notes carrying `383`, `384`, `386`, `389` or `751` are now refused locally** with a `BR-CL-01` error suggesting legal credit-note codes. On 0.10.0 each passed local validation — every one is fatal on the network, so the document failed later and less clearly.
|
|
33
|
+
- The default is unchanged: omit `invoiceTypeCode` and the document kind decides (`380` invoice, `381` credit note).
|
|
34
|
+
- **The tarball now ships its `LICENSE` file** (the published 0.10.0 does not contain one) and the corrected README.
|
|
35
|
+
|
|
7
36
|
## [0.10.0] — 2026-08-28
|
|
8
37
|
|
|
9
38
|
Bundles SDK **5.0.0**. Minor rather than patch because inputs that used to pass
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zero Loop Labs Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @getpeppr/cli
|
|
2
2
|
|
|
3
|
-
Developer toolkit for Peppol e-invoicing. Scaffold invoices,
|
|
3
|
+
Developer toolkit for Peppol e-invoicing. Scaffold invoices, run offline pre-flight checks, convert to UBL XML, look up participants in the Peppol Directory, and send invoices to the Peppol network — all from the command line.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ The generated template includes a Belgian seller, the sandbox test receiver as b
|
|
|
30
30
|
|
|
31
31
|
### `getpeppr validate` — Validate an invoice
|
|
32
32
|
|
|
33
|
-
Run offline
|
|
33
|
+
Run partial offline pre-flight checks before the network applies the complete Peppol BIS 3.0 rulebooks:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
getpeppr validate invoice.json
|
|
@@ -44,14 +44,14 @@ Validating: invoice.json
|
|
|
44
44
|
── Structure ──────────────────────────────
|
|
45
45
|
✓ No errors
|
|
46
46
|
|
|
47
|
-
──
|
|
48
|
-
✓
|
|
47
|
+
── Offline Checks (partial) ───────────────────
|
|
48
|
+
✓ No findings
|
|
49
49
|
|
|
50
50
|
── Country Rules ────────────────────────────
|
|
51
51
|
⚠ No structured communication reference (BE-02)
|
|
52
52
|
|
|
53
53
|
── Summary ──────────────────────────────────
|
|
54
|
-
✓
|
|
54
|
+
✓ Pre-flight checks passed (1 warning)
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
| Flag | Description |
|
|
@@ -103,7 +103,7 @@ getpeppr lookup 0208:0685660237 --json
|
|
|
103
103
|
|
|
104
104
|
### `getpeppr login` — Save your API key
|
|
105
105
|
|
|
106
|
-
Store a getpeppr API key in `~/.config/getpeppr/credentials.json`
|
|
106
|
+
Store a getpeppr API key in `$XDG_CONFIG_HOME/getpeppr/credentials.json` — `~/.config/getpeppr/credentials.json` by default, `%APPDATA%\getpeppr\credentials.json` on Windows. On Unix-like systems the file is created and checked with mode `0600`; Windows has no POSIX mode. Sandbox and live keys can coexist.
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
109
|
# Interactive (recommended) — prompts for the key with masked input
|
|
@@ -149,6 +149,7 @@ getpeppr send invoice.json --prod
|
|
|
149
149
|
| `--to <peppol-id>` | Recipient Peppol ID (when no file) |
|
|
150
150
|
| `--amount <number>` | Line amount in major currency units |
|
|
151
151
|
| `--currency <iso>` | ISO 4217 code (default `EUR`) |
|
|
152
|
+
| `--country <iso>` | Recipient country (ISO 3166-1 alpha-2) when no file |
|
|
152
153
|
| `--desc <text>` | Line description |
|
|
153
154
|
| `--attachment` | Attach the bundled test PDF |
|
|
154
155
|
| `--watch` | Poll status until terminal (60s timeout) |
|
|
@@ -189,7 +190,7 @@ If no legal entity exists in the environment yet, `whoami` says so and points to
|
|
|
189
190
|
getpeppr logout
|
|
190
191
|
```
|
|
191
192
|
|
|
192
|
-
Removes
|
|
193
|
+
Removes the credentials file. No-op if the file doesn't exist.
|
|
193
194
|
|
|
194
195
|
## Exit codes
|
|
195
196
|
|
|
@@ -234,7 +235,7 @@ GETPEPPR_API_KEY=$SANDBOX_KEY getpeppr send invoice.json --json
|
|
|
234
235
|
The CLI runs three validation engines from the [@getpeppr/sdk](https://www.npmjs.com/package/@getpeppr/sdk):
|
|
235
236
|
|
|
236
237
|
1. **Structure** — Required fields, type checks, format validation
|
|
237
|
-
2. **
|
|
238
|
+
2. **Offline Checks** — 37 registered partial pre-flight checks: exact network-rule equivalents plus explicit `GETPEPPR-*` diagnostics. The legacy provider-capability diagnostic `unsupported_vat_category` can also appear, but is outside that count. This is not a Peppol conformance verdict.
|
|
238
239
|
3. **Country Rules** — Belgium (BE), France (FR), Italy (IT), Netherlands (NL), Germany (DE)
|
|
239
240
|
|
|
240
241
|
Scaffolding (`init`), validation, and conversion run fully offline — no API key or network connection required. Only `lookup`, `send`, and `whoami` need a network connection. Before a sandbox `send`, the CLI reads `GET /v1/identity`: its own generated fixture is adapted to the returned O/0 or AE/0 profile, while a user-edited file is never rewritten silently and is refused locally if its tax mode conflicts with the sender.
|
package/dist/index.js
CHANGED
|
@@ -65,7 +65,7 @@ function formatWarning(item) {
|
|
|
65
65
|
function formatSection(title, errors, warnings) {
|
|
66
66
|
const lines = [sectionHeader(title)];
|
|
67
67
|
if (errors.length === 0 && warnings.length === 0) {
|
|
68
|
-
lines.push(` ${pc.green("\u2713")}
|
|
68
|
+
lines.push(` ${pc.green("\u2713")} No findings`);
|
|
69
69
|
return lines.join("\n");
|
|
70
70
|
}
|
|
71
71
|
if (errors.length === 0) {
|
|
@@ -94,7 +94,7 @@ Validating: ${pc.bold(filename)}
|
|
|
94
94
|
lines.push("");
|
|
95
95
|
lines.push(
|
|
96
96
|
formatSection(
|
|
97
|
-
"
|
|
97
|
+
"Offline Checks (partial)",
|
|
98
98
|
result.schematron.errors,
|
|
99
99
|
result.schematron.warnings
|
|
100
100
|
)
|
|
@@ -111,10 +111,10 @@ Validating: ${pc.bold(filename)}
|
|
|
111
111
|
lines.push(sectionHeader("Summary"));
|
|
112
112
|
const { totalErrors, totalWarnings, valid } = result;
|
|
113
113
|
if (valid && totalWarnings === 0) {
|
|
114
|
-
lines.push(` ${pc.green(pc.bold("\u2713
|
|
114
|
+
lines.push(` ${pc.green(pc.bold("\u2713 Pre-flight checks passed"))}`);
|
|
115
115
|
} else if (valid) {
|
|
116
116
|
lines.push(
|
|
117
|
-
` ${pc.green(pc.bold("\u2713
|
|
117
|
+
` ${pc.green(pc.bold("\u2713 Pre-flight checks passed"))} ${pc.dim(`(${totalWarnings} warning${totalWarnings === 1 ? "" : "s"})`)}`
|
|
118
118
|
);
|
|
119
119
|
} else {
|
|
120
120
|
const parts = [];
|
|
@@ -123,7 +123,7 @@ Validating: ${pc.bold(filename)}
|
|
|
123
123
|
parts.push(`${totalWarnings} warning${totalWarnings === 1 ? "" : "s"}`);
|
|
124
124
|
}
|
|
125
125
|
lines.push(
|
|
126
|
-
` ${pc.red(pc.bold(
|
|
126
|
+
` ${pc.red(pc.bold("\u2717 Pre-flight checks found errors"))} ${pc.dim(`(${parts.join(", ")})`)}`
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
129
|
lines.push("");
|
|
@@ -773,8 +773,9 @@ function formatBaseQuantity(baseQuantity, field) {
|
|
|
773
773
|
}
|
|
774
774
|
return `${digits.slice(0, outputPoint)}.${digits.slice(outputPoint)}`;
|
|
775
775
|
}
|
|
776
|
-
function
|
|
777
|
-
|
|
776
|
+
function roundUblCurrencyAmount(value) {
|
|
777
|
+
const rounded = Math.round((value + Math.sign(value) * Number.EPSILON) * 100) / 100;
|
|
778
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
778
779
|
}
|
|
779
780
|
function normalizedTaxExemptReason(vatCategory, reason) {
|
|
780
781
|
if (!EXEMPTION_REASON_CATEGORIES.has(vatCategory) || typeof reason !== "string") {
|
|
@@ -1063,7 +1064,7 @@ function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
|
|
|
1063
1064
|
throw new UblBuilderInputError(`Conflicting taxExemptReason values for VAT group ${vatCategory}/${effectiveVatRate}.`, "taxExemptReason");
|
|
1064
1065
|
}
|
|
1065
1066
|
existing.taxExemptReason ??= reason;
|
|
1066
|
-
existing.taxableAmount
|
|
1067
|
+
existing.taxableAmount += amount;
|
|
1067
1068
|
} else {
|
|
1068
1069
|
groups.set(key, {
|
|
1069
1070
|
vatRate: effectiveVatRate,
|
|
@@ -1092,8 +1093,8 @@ function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
|
|
|
1092
1093
|
}
|
|
1093
1094
|
}
|
|
1094
1095
|
return Array.from(groups.values()).map((subtotal) => {
|
|
1095
|
-
const taxableAmount =
|
|
1096
|
-
const taxAmount =
|
|
1096
|
+
const taxableAmount = roundUblCurrencyAmount(subtotal.taxableAmount);
|
|
1097
|
+
const taxAmount = roundUblCurrencyAmount(taxableAmount * (subtotal.vatRate / 100));
|
|
1097
1098
|
if (!survivesCents(taxableAmount) || !survivesCents(taxAmount)) {
|
|
1098
1099
|
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, "totals", DERIVED_AMOUNT_CONTRACT_RULE);
|
|
1099
1100
|
}
|
|
@@ -1105,12 +1106,12 @@ function calculateDocumentTotals(lines, allowances, charges, options = {}) {
|
|
|
1105
1106
|
const lineExtensionAmount = lines.reduce((sum, line, index) => sum + calculateLineExtensionAmount(line, index), 0);
|
|
1106
1107
|
const allowanceTotalAmount = (allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
|
|
1107
1108
|
const chargeTotalAmount = (charges ?? []).reduce((sum, c) => sum + c.amount, 0);
|
|
1108
|
-
const taxExclusiveAmount =
|
|
1109
|
+
const taxExclusiveAmount = roundUblCurrencyAmount(lineExtensionAmount - allowanceTotalAmount + chargeTotalAmount);
|
|
1109
1110
|
if (!survivesCents(allowanceTotalAmount) || !survivesCents(chargeTotalAmount) || !survivesCents(taxExclusiveAmount)) {
|
|
1110
1111
|
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, "totals", DERIVED_AMOUNT_CONTRACT_RULE);
|
|
1111
1112
|
}
|
|
1112
|
-
const totalTax =
|
|
1113
|
-
const taxInclusiveAmount =
|
|
1113
|
+
const totalTax = roundUblCurrencyAmount(taxSubtotals.reduce((sum, st) => sum + st.taxAmount, 0));
|
|
1114
|
+
const taxInclusiveAmount = roundUblCurrencyAmount(taxExclusiveAmount + totalTax);
|
|
1114
1115
|
if (!survivesCents(totalTax) || !survivesCents(taxInclusiveAmount)) {
|
|
1115
1116
|
throw new UblBuilderInputError(NON_FINITE_DERIVED_AMOUNT_MESSAGE, "totals", DERIVED_AMOUNT_CONTRACT_RULE);
|
|
1116
1117
|
}
|
|
@@ -1145,7 +1146,7 @@ function buildTaxTotalXml(taxSubtotals, totalTax, currency) {
|
|
|
1145
1146
|
</cac:TaxTotal>`;
|
|
1146
1147
|
}
|
|
1147
1148
|
function buildTaxCurrencyTotalXml(totalTax, taxCurrency, rate) {
|
|
1148
|
-
const convertedAmount =
|
|
1149
|
+
const convertedAmount = roundUblCurrencyAmount(totalTax * rate);
|
|
1149
1150
|
return `<cac:TaxTotal>
|
|
1150
1151
|
<cbc:TaxAmount currencyID="${escapeXml(taxCurrency)}">${formatAmount(convertedAmount)}</cbc:TaxAmount>
|
|
1151
1152
|
</cac:TaxTotal>`;
|
|
@@ -1606,6 +1607,79 @@ function resolveUnit(input) {
|
|
|
1606
1607
|
function getAllUnits() {
|
|
1607
1608
|
return Array.from(UNIT_CODES.entries()).map(([code, name]) => ({ code, name })).sort((a, b) => a.code.localeCompare(b.code));
|
|
1608
1609
|
}
|
|
1610
|
+
var INVOICE_TYPE_CODES = [
|
|
1611
|
+
71,
|
|
1612
|
+
80,
|
|
1613
|
+
81,
|
|
1614
|
+
82,
|
|
1615
|
+
84,
|
|
1616
|
+
102,
|
|
1617
|
+
130,
|
|
1618
|
+
202,
|
|
1619
|
+
203,
|
|
1620
|
+
204,
|
|
1621
|
+
211,
|
|
1622
|
+
218,
|
|
1623
|
+
219,
|
|
1624
|
+
295,
|
|
1625
|
+
325,
|
|
1626
|
+
326,
|
|
1627
|
+
331,
|
|
1628
|
+
380,
|
|
1629
|
+
382,
|
|
1630
|
+
383,
|
|
1631
|
+
384,
|
|
1632
|
+
385,
|
|
1633
|
+
386,
|
|
1634
|
+
387,
|
|
1635
|
+
388,
|
|
1636
|
+
389,
|
|
1637
|
+
390,
|
|
1638
|
+
393,
|
|
1639
|
+
394,
|
|
1640
|
+
395,
|
|
1641
|
+
456,
|
|
1642
|
+
457,
|
|
1643
|
+
471,
|
|
1644
|
+
472,
|
|
1645
|
+
473,
|
|
1646
|
+
500,
|
|
1647
|
+
501,
|
|
1648
|
+
527,
|
|
1649
|
+
553,
|
|
1650
|
+
575,
|
|
1651
|
+
623,
|
|
1652
|
+
633,
|
|
1653
|
+
751,
|
|
1654
|
+
780,
|
|
1655
|
+
817,
|
|
1656
|
+
870,
|
|
1657
|
+
875,
|
|
1658
|
+
876,
|
|
1659
|
+
877,
|
|
1660
|
+
935
|
|
1661
|
+
];
|
|
1662
|
+
var CREDIT_NOTE_TYPE_CODES = [
|
|
1663
|
+
81,
|
|
1664
|
+
83,
|
|
1665
|
+
261,
|
|
1666
|
+
262,
|
|
1667
|
+
296,
|
|
1668
|
+
308,
|
|
1669
|
+
381,
|
|
1670
|
+
396,
|
|
1671
|
+
420,
|
|
1672
|
+
458,
|
|
1673
|
+
502,
|
|
1674
|
+
503,
|
|
1675
|
+
532
|
|
1676
|
+
];
|
|
1677
|
+
function getInvoiceTypeCodes() {
|
|
1678
|
+
return [...INVOICE_TYPE_CODES];
|
|
1679
|
+
}
|
|
1680
|
+
function getCreditNoteTypeCodes() {
|
|
1681
|
+
return [...CREDIT_NOTE_TYPE_CODES];
|
|
1682
|
+
}
|
|
1609
1683
|
|
|
1610
1684
|
// ../sdk/dist/core/validator.js
|
|
1611
1685
|
function error(field, message, ruleId, suggestion) {
|
|
@@ -1736,9 +1810,12 @@ function validateInvoice(input) {
|
|
|
1736
1810
|
} else if (!input.number.trim()) {
|
|
1737
1811
|
errors.push(error("number", "Invoice number is required", "BR-02", "Must be unique per supplier"));
|
|
1738
1812
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1813
|
+
if (input.invoiceTypeCode != null) {
|
|
1814
|
+
const isCreditNote = input.isCreditNote === true;
|
|
1815
|
+
const legalCodes = isCreditNote ? getCreditNoteTypeCodes() : getInvoiceTypeCodes();
|
|
1816
|
+
if (!legalCodes.includes(input.invoiceTypeCode)) {
|
|
1817
|
+
errors.push(error("invoiceTypeCode", `Invalid ${isCreditNote ? "credit note" : "invoice"} type code: ${input.invoiceTypeCode}`, "BR-CL-01", `Valid ${isCreditNote ? "credit note" : "invoice"} type codes: ${legalCodes.join(", ")}`));
|
|
1818
|
+
}
|
|
1742
1819
|
}
|
|
1743
1820
|
if (input.isCreditNote) {
|
|
1744
1821
|
const ref = input.invoiceReference;
|
|
@@ -1910,12 +1987,12 @@ function validateInvoice(input) {
|
|
|
1910
1987
|
function violation(ruleId, severity, message, field) {
|
|
1911
1988
|
return { ruleId, severity, message, field };
|
|
1912
1989
|
}
|
|
1913
|
-
var
|
|
1914
|
-
function
|
|
1915
|
-
if (!
|
|
1916
|
-
|
|
1990
|
+
var _sdkUnitCodes;
|
|
1991
|
+
function getSdkUnitCodes() {
|
|
1992
|
+
if (!_sdkUnitCodes) {
|
|
1993
|
+
_sdkUnitCodes = new Set(getAllUnits().map((u) => u.code));
|
|
1917
1994
|
}
|
|
1918
|
-
return
|
|
1995
|
+
return _sdkUnitCodes;
|
|
1919
1996
|
}
|
|
1920
1997
|
var VALID_VAT_CATEGORIES = /* @__PURE__ */ new Set(["S", "Z", "E", "AE", "K", "G", "O", "L", "M", "B"]);
|
|
1921
1998
|
var SENDABLE_VAT_CATEGORIES = ["S", "Z", "E", "AE", "K", "G", "O"];
|
|
@@ -1935,18 +2012,18 @@ var br02 = (input) => {
|
|
|
1935
2012
|
}
|
|
1936
2013
|
return [];
|
|
1937
2014
|
};
|
|
1938
|
-
var
|
|
2015
|
+
var issueDateDefaulted = (input) => {
|
|
1939
2016
|
if (!input.date) {
|
|
1940
2017
|
return [
|
|
1941
|
-
violation("
|
|
2018
|
+
violation("GETPEPPR-ISSUE-DATE-DEFAULTED", "warning", "Invoice issue date is not set. The SDK will default to today's date.", "date")
|
|
1942
2019
|
];
|
|
1943
2020
|
}
|
|
1944
2021
|
return [];
|
|
1945
2022
|
};
|
|
1946
|
-
var
|
|
2023
|
+
var sellerVatRecommendation = (input) => {
|
|
1947
2024
|
if (input.from && !input.from.vatNumber) {
|
|
1948
2025
|
return [
|
|
1949
|
-
violation("
|
|
2026
|
+
violation("GETPEPPR-FROM-VAT-RECOMMENDED", "warning", "Seller party has no VAT number. The gateway will use the account's VAT registration.", "from.vatNumber")
|
|
1950
2027
|
];
|
|
1951
2028
|
}
|
|
1952
2029
|
return [];
|
|
@@ -1957,29 +2034,29 @@ var br07 = (input) => {
|
|
|
1957
2034
|
}
|
|
1958
2035
|
return [];
|
|
1959
2036
|
};
|
|
1960
|
-
var
|
|
2037
|
+
var br16 = (input) => {
|
|
1961
2038
|
if (!input.lines || input.lines.length === 0) {
|
|
1962
|
-
return [violation("BR-
|
|
2039
|
+
return [violation("BR-16", "error", "Invoice must have at least one line item.", "lines")];
|
|
1963
2040
|
}
|
|
1964
2041
|
return [];
|
|
1965
2042
|
};
|
|
1966
|
-
var
|
|
2043
|
+
var paymentTimingRecommendation = (input) => {
|
|
1967
2044
|
if (!input.dueDate && !input.paymentTerms) {
|
|
1968
2045
|
return [
|
|
1969
|
-
violation("
|
|
2046
|
+
violation("GETPEPPR-PAYMENT-TIMING-RECOMMENDED", "warning", "Neither due date nor payment terms specified. At least one is recommended.", "dueDate")
|
|
1970
2047
|
];
|
|
1971
2048
|
}
|
|
1972
2049
|
return [];
|
|
1973
2050
|
};
|
|
1974
|
-
var
|
|
2051
|
+
var buyerOrOrderReferenceRecommendation = (input) => {
|
|
1975
2052
|
if (!input.buyerReference && !input.orderReference) {
|
|
1976
2053
|
return [
|
|
1977
|
-
violation("
|
|
2054
|
+
violation("GETPEPPR-BUYER-OR-ORDER-REFERENCE-RECOMMENDED", "warning", "Neither buyerReference nor orderReference specified. Peppol BIS 3.0 requires at least one.", "buyerReference")
|
|
1978
2055
|
];
|
|
1979
2056
|
}
|
|
1980
2057
|
return [];
|
|
1981
2058
|
};
|
|
1982
|
-
var
|
|
2059
|
+
var lineNetSanity = (input) => {
|
|
1983
2060
|
const violations = [];
|
|
1984
2061
|
if (!input.lines)
|
|
1985
2062
|
return violations;
|
|
@@ -1989,12 +2066,12 @@ var brCo10 = (input) => {
|
|
|
1989
2066
|
if (!Number.isFinite(net)) {
|
|
1990
2067
|
const baseQty = line.baseQuantity ?? 1;
|
|
1991
2068
|
const detail = baseQty === 0 ? "baseQuantity is 0, causing division by zero." : "Computed line amount is not a finite number.";
|
|
1992
|
-
violations.push(violation("
|
|
2069
|
+
violations.push(violation("GETPEPPR-LINE-NET-SANITY", "error", `Line ${i}: invalid net amount. ${detail}`, `lines[${i}]`));
|
|
1993
2070
|
}
|
|
1994
2071
|
}
|
|
1995
2072
|
return violations;
|
|
1996
2073
|
};
|
|
1997
|
-
var
|
|
2074
|
+
var computedVatSanity = (input) => {
|
|
1998
2075
|
if (!input.lines || input.lines.length === 0)
|
|
1999
2076
|
return [];
|
|
2000
2077
|
let totalVat = 0;
|
|
@@ -2013,17 +2090,17 @@ var brCo13 = (input) => {
|
|
|
2013
2090
|
}
|
|
2014
2091
|
if (!Number.isFinite(totalVat)) {
|
|
2015
2092
|
return [
|
|
2016
|
-
violation("
|
|
2093
|
+
violation("GETPEPPR-COMPUTED-VAT-SANITY", "error", "Computed total VAT amount is not a finite number. Check line amounts and VAT rates.")
|
|
2017
2094
|
];
|
|
2018
2095
|
}
|
|
2019
2096
|
if (totalVat < -0.01) {
|
|
2020
2097
|
return [
|
|
2021
|
-
violation("
|
|
2098
|
+
violation("GETPEPPR-COMPUTED-VAT-SANITY", "warning", `Computed total VAT is negative (${totalVat.toFixed(2)}). This is unusual for an invoice.`)
|
|
2022
2099
|
];
|
|
2023
2100
|
}
|
|
2024
2101
|
return [];
|
|
2025
2102
|
};
|
|
2026
|
-
var
|
|
2103
|
+
var taxInclusiveSanity = (input) => {
|
|
2027
2104
|
if (!input.lines || input.lines.length === 0)
|
|
2028
2105
|
return [];
|
|
2029
2106
|
let lineTotal = 0;
|
|
@@ -2046,17 +2123,17 @@ var brCo15 = (input) => {
|
|
|
2046
2123
|
const taxInclusive = lineTotal + vatTotal;
|
|
2047
2124
|
if (!Number.isFinite(taxInclusive)) {
|
|
2048
2125
|
return [
|
|
2049
|
-
violation("
|
|
2126
|
+
violation("GETPEPPR-TAX-INCLUSIVE-SANITY", "error", "Computed tax-inclusive amount is not a finite number.")
|
|
2050
2127
|
];
|
|
2051
2128
|
}
|
|
2052
2129
|
if (taxInclusive < -0.01) {
|
|
2053
2130
|
return [
|
|
2054
|
-
violation("
|
|
2131
|
+
violation("GETPEPPR-TAX-INCLUSIVE-SANITY", "warning", `Computed tax-inclusive amount is negative (${taxInclusive.toFixed(2)}). Consider using a credit note instead.`)
|
|
2055
2132
|
];
|
|
2056
2133
|
}
|
|
2057
2134
|
return [];
|
|
2058
2135
|
};
|
|
2059
|
-
var
|
|
2136
|
+
var payableSanity = (input) => {
|
|
2060
2137
|
if (!input.lines || input.lines.length === 0)
|
|
2061
2138
|
return [];
|
|
2062
2139
|
let lineTotal = 0;
|
|
@@ -2082,12 +2159,12 @@ var brCo16 = (input) => {
|
|
|
2082
2159
|
const payable = taxInclusive - prepaid + rounding;
|
|
2083
2160
|
if (!Number.isFinite(payable)) {
|
|
2084
2161
|
return [
|
|
2085
|
-
violation("
|
|
2162
|
+
violation("GETPEPPR-PAYABLE-SANITY", "error", "Computed payable amount is not a finite number.")
|
|
2086
2163
|
];
|
|
2087
2164
|
}
|
|
2088
2165
|
if (payable < -0.01) {
|
|
2089
2166
|
return [
|
|
2090
|
-
violation("
|
|
2167
|
+
violation("GETPEPPR-PAYABLE-SANITY", "warning", `Computed payable amount is negative (${payable.toFixed(2)}). Check invoice totals, prepaid amount (${prepaid}), and rounding amount (${rounding}).`)
|
|
2091
2168
|
];
|
|
2092
2169
|
}
|
|
2093
2170
|
return [];
|
|
@@ -2295,10 +2372,10 @@ function validateUblBuilderVat(input) {
|
|
|
2295
2372
|
...UBL_BUILDER_VAT_RULES.flatMap((rule) => rule(input))
|
|
2296
2373
|
];
|
|
2297
2374
|
}
|
|
2298
|
-
var
|
|
2375
|
+
var buyerPeppolIdRequired = (input) => {
|
|
2299
2376
|
if (!input.to?.peppolId) {
|
|
2300
2377
|
return [
|
|
2301
|
-
violation("PEPPOL-
|
|
2378
|
+
violation("GETPEPPR-BUYER-PEPPOL-ID-REQUIRED", "error", "Buyer electronic address (peppolId) is required for Peppol delivery.", "to.peppolId")
|
|
2302
2379
|
];
|
|
2303
2380
|
}
|
|
2304
2381
|
return [];
|
|
@@ -2323,17 +2400,17 @@ var vatCategoryCodes = (input) => {
|
|
|
2323
2400
|
(input.charges ?? []).forEach((c, i) => check(c.vatCategory, `charges[${i}].vatCategory`, `Charge ${i}`));
|
|
2324
2401
|
return violations;
|
|
2325
2402
|
};
|
|
2326
|
-
var
|
|
2403
|
+
var unitCodeRecognised = (input) => {
|
|
2327
2404
|
const violations = [];
|
|
2328
2405
|
if (!input.lines)
|
|
2329
2406
|
return violations;
|
|
2330
|
-
const knownCodes =
|
|
2407
|
+
const knownCodes = getSdkUnitCodes();
|
|
2331
2408
|
for (let i = 0; i < input.lines.length; i++) {
|
|
2332
2409
|
const line = input.lines[i];
|
|
2333
2410
|
if (line.unit) {
|
|
2334
2411
|
const resolved = resolveUnit(line.unit);
|
|
2335
2412
|
if (!knownCodes.has(resolved)) {
|
|
2336
|
-
violations.push(violation("
|
|
2413
|
+
violations.push(violation("GETPEPPR-UNIT-CODE-RECOGNISED", "warning", `Line ${i}: unit "${line.unit}" (resolved: "${resolved}") is not in the SDK's common unit list. It may still be a valid UN/ECE Rec20/21 code; network validation decides. Common codes: EA, HUR, DAY, KGM.`, `lines[${i}].unit`));
|
|
2337
2414
|
}
|
|
2338
2415
|
}
|
|
2339
2416
|
}
|
|
@@ -2342,17 +2419,17 @@ var peppolR080 = (input) => {
|
|
|
2342
2419
|
var ALL_RULES = [
|
|
2343
2420
|
// Required fields (BR)
|
|
2344
2421
|
br02,
|
|
2345
|
-
|
|
2346
|
-
|
|
2422
|
+
issueDateDefaulted,
|
|
2423
|
+
sellerVatRecommendation,
|
|
2347
2424
|
br07,
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2425
|
+
br16,
|
|
2426
|
+
paymentTimingRecommendation,
|
|
2427
|
+
buyerOrOrderReferenceRecommendation,
|
|
2351
2428
|
// Calculations (BR-CO)
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2429
|
+
lineNetSanity,
|
|
2430
|
+
computedVatSanity,
|
|
2431
|
+
taxInclusiveSanity,
|
|
2432
|
+
payableSanity,
|
|
2356
2433
|
// Tax categories (one family per category)
|
|
2357
2434
|
brS05,
|
|
2358
2435
|
brZ05,
|
|
@@ -2367,9 +2444,9 @@ var ALL_RULES = [
|
|
|
2367
2444
|
brO10,
|
|
2368
2445
|
brIc10,
|
|
2369
2446
|
// Peppol-specific
|
|
2370
|
-
|
|
2447
|
+
buyerPeppolIdRequired,
|
|
2371
2448
|
vatCategoryCodes,
|
|
2372
|
-
|
|
2449
|
+
unitCodeRecognised
|
|
2373
2450
|
];
|
|
2374
2451
|
function validateSchematron(input) {
|
|
2375
2452
|
const errors = [];
|
|
@@ -2393,17 +2470,17 @@ function validateSchematron(input) {
|
|
|
2393
2470
|
}
|
|
2394
2471
|
var SDK_SCHEMATRON_RULE_IDS = [
|
|
2395
2472
|
"BR-02",
|
|
2396
|
-
"
|
|
2397
|
-
"
|
|
2473
|
+
"GETPEPPR-ISSUE-DATE-DEFAULTED",
|
|
2474
|
+
"GETPEPPR-FROM-VAT-RECOMMENDED",
|
|
2398
2475
|
"BR-07",
|
|
2399
|
-
"BR-
|
|
2400
|
-
"
|
|
2401
|
-
"
|
|
2476
|
+
"BR-16",
|
|
2477
|
+
"GETPEPPR-PAYMENT-TIMING-RECOMMENDED",
|
|
2478
|
+
"GETPEPPR-BUYER-OR-ORDER-REFERENCE-RECOMMENDED",
|
|
2402
2479
|
"BR-CL-17",
|
|
2403
|
-
"
|
|
2404
|
-
"
|
|
2405
|
-
"
|
|
2406
|
-
"
|
|
2480
|
+
"GETPEPPR-LINE-NET-SANITY",
|
|
2481
|
+
"GETPEPPR-COMPUTED-VAT-SANITY",
|
|
2482
|
+
"GETPEPPR-TAX-INCLUSIVE-SANITY",
|
|
2483
|
+
"GETPEPPR-PAYABLE-SANITY",
|
|
2407
2484
|
"BR-S-05",
|
|
2408
2485
|
"BR-Z-05",
|
|
2409
2486
|
"BR-E-05",
|
|
@@ -2427,8 +2504,8 @@ var SDK_SCHEMATRON_RULE_IDS = [
|
|
|
2427
2504
|
"BR-G-10",
|
|
2428
2505
|
"BR-O-10",
|
|
2429
2506
|
"BR-IC-10",
|
|
2430
|
-
"PEPPOL-
|
|
2431
|
-
"
|
|
2507
|
+
"GETPEPPR-BUYER-PEPPOL-ID-REQUIRED",
|
|
2508
|
+
"GETPEPPR-UNIT-CODE-RECOGNISED"
|
|
2432
2509
|
];
|
|
2433
2510
|
|
|
2434
2511
|
// ../sdk/dist/core/status-precedence.js
|
|
@@ -2456,7 +2533,7 @@ function statusFamily(status) {
|
|
|
2456
2533
|
var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
|
|
2457
2534
|
|
|
2458
2535
|
// ../sdk/dist/version.js
|
|
2459
|
-
var SDK_VERSION = "5.
|
|
2536
|
+
var SDK_VERSION = "5.2.0";
|
|
2460
2537
|
|
|
2461
2538
|
// ../sdk/dist/core/api-result.js
|
|
2462
2539
|
var API_RESULT_HEADER_NAMES = {
|
|
@@ -4081,7 +4158,8 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
|
|
|
4081
4158
|
/**
|
|
4082
4159
|
* Validate an invoice server-side using the getpeppr gateway's offline SDK-backed checks.
|
|
4083
4160
|
* The gateway runs SDK validation, verifies UBL XML generation, and evaluates offline
|
|
4084
|
-
*
|
|
4161
|
+
* pre-flight checks without sending the invoice to Storecove. This is not a Peppol
|
|
4162
|
+
* conformance verdict.
|
|
4085
4163
|
*
|
|
4086
4164
|
* @example
|
|
4087
4165
|
* ```ts
|
|
@@ -5880,7 +5958,7 @@ async function promptEnvironment() {
|
|
|
5880
5958
|
});
|
|
5881
5959
|
}
|
|
5882
5960
|
function registerLoginCommand(program2) {
|
|
5883
|
-
program2.command("login").description("Save a getpeppr API key to
|
|
5961
|
+
program2.command("login").description("Save a getpeppr API key to the credentials file ($XDG_CONFIG_HOME/getpeppr, %APPDATA%\\getpeppr on Windows)").option("--key <key>", "API key \u2014 for CI/scripted use only; visible in `ps` and shell history. Prefer the interactive prompt or GETPEPPR_API_KEY env var.").option("--sandbox", "store as sandbox key (default)").option("--live", "store as live (production) key").action(async (flags) => {
|
|
5884
5962
|
if (!flags.live && !flags.sandbox && process.stdin.isTTY !== true) {
|
|
5885
5963
|
exitWithError("Error: --sandbox or --live required when stdin is not a TTY (CI mode).");
|
|
5886
5964
|
}
|
|
@@ -6012,7 +6090,7 @@ function registerWhoamiCommand(program2) {
|
|
|
6012
6090
|
// src/commands/logout.ts
|
|
6013
6091
|
import pc9 from "picocolors";
|
|
6014
6092
|
function registerLogoutCommand(program2) {
|
|
6015
|
-
program2.command("logout").description("Remove
|
|
6093
|
+
program2.command("logout").description("Remove the stored credentials file").action(() => {
|
|
6016
6094
|
const path = getCredentialsPath();
|
|
6017
6095
|
const removed = deleteCredentials();
|
|
6018
6096
|
if (removed) {
|