@getpeppr/cli 0.8.1 → 0.8.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/dist/index.js CHANGED
@@ -130,6 +130,518 @@ Validating: ${pc.bold(filename)}
130
130
  return lines.join("\n");
131
131
  }
132
132
 
133
+ // ../sdk/dist/core/canonical-schemes.js
134
+ var ALIAS_TO_EAS = /* @__PURE__ */ new Map([
135
+ ["AD:VAT", "9922"],
136
+ ["AE:TIN", "0235"],
137
+ ["AL:VAT", "9923"],
138
+ ["AT:CID", "9916"],
139
+ ["AT:GOV", "9915"],
140
+ ["AT:KUR", "9919"],
141
+ ["AT:VAT", "9914"],
142
+ ["AU:ABN", "0151"],
143
+ ["BA:VAT", "9924"],
144
+ ["BE:CBE", "9956"],
145
+ ["BE:EN", "0208"],
146
+ ["BE:VAT", "9925"],
147
+ ["BG:VAT", "9926"],
148
+ ["CH:UIDB", "0183"],
149
+ ["CH:VAT", "9927"],
150
+ ["CY:VAT", "9928"],
151
+ ["CZ:VAT", "9929"],
152
+ ["DE:GEBA", "0246"],
153
+ ["DE:LID", "9958"],
154
+ ["DE:LWID", "0204"],
155
+ ["DE:VAT", "9930"],
156
+ ["DK:CPR", "9901"],
157
+ ["DK:CVR", "9902"],
158
+ ["DK:DIGST", "0184"],
159
+ ["DK:ERST", "0198"],
160
+ ["DK:P", "0096"],
161
+ ["DK:SE", "9904"],
162
+ ["DK:VANS", "9905"],
163
+ ["DUNS", "0060"],
164
+ ["EE:CC", "0191"],
165
+ ["EE:VAT", "9931"],
166
+ ["ES:VAT", "9920"],
167
+ ["EU:NAL", "0130"],
168
+ ["EU:REID", "9913"],
169
+ ["EU:VAT", "9912"],
170
+ ["FI:NSI", "0215"],
171
+ ["FI:ORG", "0212"],
172
+ ["FI:OVT", "0037"],
173
+ ["FI:OVT2", "0216"],
174
+ ["FI:VAT", "0213"],
175
+ ["FR:CTC", "0225"],
176
+ ["FR:SIRENE", "0002"],
177
+ ["FR:SIRET", "0009"],
178
+ ["FR:VAT", "9957"],
179
+ ["GB:VAT", "9932"],
180
+ ["GLN", "0088"],
181
+ ["GR:VAT", "9933"],
182
+ ["GS1", "0209"],
183
+ ["HR:VAT", "9934"],
184
+ ["HU:VAT", "9910"],
185
+ ["IBAN", "9918"],
186
+ ["IE:VAT", "9935"],
187
+ ["IS:KT", "9917"],
188
+ ["IS:KTNR", "0196"],
189
+ ["IT:CF", "9907"],
190
+ ["IT:CFI", "0210"],
191
+ ["IT:COD", "0205"],
192
+ ["IT:CUUO", "0201"],
193
+ ["IT:FTI", "0097"],
194
+ ["IT:IPA", "9921"],
195
+ ["IT:IVA", "0211"],
196
+ ["IT:SECETI", "0142"],
197
+ ["IT:SIA", "0135"],
198
+ ["IT:VAT", "9906"],
199
+ ["JP:IIN", "0221"],
200
+ ["JP:SST", "0188"],
201
+ ["LEI", "0199"],
202
+ ["LI:VAT", "9936"],
203
+ ["LT:LEC", "0200"],
204
+ ["LT:VAT", "9937"],
205
+ ["LU:MAT", "0240"],
206
+ ["LU:VAT", "9938"],
207
+ ["LV:URN", "0218"],
208
+ ["LV:VAT", "9939"],
209
+ ["MC:VAT", "9940"],
210
+ ["ME:VAT", "9941"],
211
+ ["MK:VAT", "9942"],
212
+ ["MT:VAT", "9943"],
213
+ ["MY:EIF", "0230"],
214
+ ["NG:TID", "0244"],
215
+ ["NL:KVK", "0106"],
216
+ ["NL:OIN", "9954"],
217
+ ["NL:OINO", "0190"],
218
+ ["NL:VAT", "9944"],
219
+ ["NO:ORG", "0192"],
220
+ ["NO:ORGNR", "9908"],
221
+ ["NO:VAT", "9909"],
222
+ ["OM:VAT", "0248"],
223
+ ["PL:VAT", "9945"],
224
+ ["PT:VAT", "9946"],
225
+ ["RO:VAT", "9947"],
226
+ ["RS:VAT", "9948"],
227
+ ["SE:ORGNR", "0007"],
228
+ ["SE:VAT", "9955"],
229
+ ["SG:UEN", "0195"],
230
+ ["SI:VAT", "9949"],
231
+ ["SK:DIC", "0245"],
232
+ ["SK:ICO", "0158"],
233
+ ["SK:VAT", "9950"],
234
+ ["SM:VAT", "9951"],
235
+ ["SPIS", "0242"],
236
+ ["TR:VAT", "9952"],
237
+ ["UBLBE", "0193"],
238
+ ["US:EIN", "9959"],
239
+ ["VA:VAT", "9953"]
240
+ ]);
241
+ function asciiUpper(value) {
242
+ let out = "";
243
+ for (const char of value) {
244
+ const code = char.charCodeAt(0);
245
+ out += code >= 97 && code <= 122 ? String.fromCharCode(code - 32) : char;
246
+ }
247
+ return out;
248
+ }
249
+ function canonicalScheme(scheme) {
250
+ return lookupCanonicalScheme(scheme) ?? scheme.trim();
251
+ }
252
+ function lookupCanonicalScheme(scheme) {
253
+ return ALIAS_TO_EAS.get(asciiUpper(scheme.trim()));
254
+ }
255
+ var EAS_TO_COUNTRY = /* @__PURE__ */ new Map([
256
+ ["0002", "FR"],
257
+ ["0007", "SE"],
258
+ ["0009", "FR"],
259
+ ["0037", "FI"],
260
+ ["0096", "DK"],
261
+ ["0097", "IT"],
262
+ ["0106", "NL"],
263
+ ["0135", "IT"],
264
+ ["0142", "IT"],
265
+ ["0151", "AU"],
266
+ ["0158", "SK"],
267
+ ["0183", "CH"],
268
+ ["0184", "DK"],
269
+ ["0188", "JP"],
270
+ ["0190", "NL"],
271
+ ["0191", "EE"],
272
+ ["0192", "NO"],
273
+ ["0193", "BE"],
274
+ ["0195", "SG"],
275
+ ["0196", "IS"],
276
+ ["0198", "DK"],
277
+ ["0200", "LT"],
278
+ ["0201", "IT"],
279
+ ["0204", "DE"],
280
+ ["0205", "IT"],
281
+ ["0208", "BE"],
282
+ ["0210", "IT"],
283
+ ["0211", "IT"],
284
+ ["0212", "FI"],
285
+ ["0213", "FI"],
286
+ ["0215", "FI"],
287
+ ["0216", "FI"],
288
+ ["0218", "LV"],
289
+ ["0221", "JP"],
290
+ ["0225", "FR"],
291
+ ["0230", "MY"],
292
+ ["0235", "AE"],
293
+ ["0240", "LU"],
294
+ ["0244", "NG"],
295
+ ["0245", "SK"],
296
+ ["0246", "DE"],
297
+ ["0248", "OM"],
298
+ ["9901", "DK"],
299
+ ["9902", "DK"],
300
+ ["9904", "DK"],
301
+ ["9905", "DK"],
302
+ ["9906", "IT"],
303
+ ["9907", "IT"],
304
+ ["9908", "NO"],
305
+ ["9909", "NO"],
306
+ ["9910", "HU"],
307
+ ["9914", "AT"],
308
+ ["9915", "AT"],
309
+ ["9916", "AT"],
310
+ ["9917", "IS"],
311
+ ["9919", "AT"],
312
+ ["9920", "ES"],
313
+ ["9921", "IT"],
314
+ ["9922", "AD"],
315
+ ["9923", "AL"],
316
+ ["9924", "BA"],
317
+ ["9925", "BE"],
318
+ ["9926", "BG"],
319
+ ["9927", "CH"],
320
+ ["9928", "CY"],
321
+ ["9929", "CZ"],
322
+ ["9930", "DE"],
323
+ ["9931", "EE"],
324
+ ["9932", "GB"],
325
+ ["9933", "GR"],
326
+ ["9934", "HR"],
327
+ ["9935", "IE"],
328
+ ["9936", "LI"],
329
+ ["9937", "LT"],
330
+ ["9938", "LU"],
331
+ ["9939", "LV"],
332
+ ["9940", "MC"],
333
+ ["9941", "ME"],
334
+ ["9942", "MK"],
335
+ ["9943", "MT"],
336
+ ["9944", "NL"],
337
+ ["9945", "PL"],
338
+ ["9946", "PT"],
339
+ ["9947", "RO"],
340
+ ["9948", "RS"],
341
+ ["9949", "SI"],
342
+ ["9950", "SK"],
343
+ ["9951", "SM"],
344
+ ["9952", "TR"],
345
+ ["9953", "VA"],
346
+ ["9954", "NL"],
347
+ ["9955", "SE"],
348
+ ["9956", "BE"],
349
+ ["9957", "FR"],
350
+ ["9958", "DE"],
351
+ ["9959", "US"]
352
+ ]);
353
+ function countryForScheme(scheme) {
354
+ return EAS_TO_COUNTRY.get(canonicalScheme(scheme));
355
+ }
356
+ var CANONICAL_SCHEME_COUNT = ALIAS_TO_EAS.size;
357
+ var SCHEME_COUNTRY_COUNT = EAS_TO_COUNTRY.size;
358
+
359
+ // ../sdk/dist/core/peppol-id.js
360
+ function isWellFormedPeppolId(peppolId) {
361
+ if (!peppolId.includes(":"))
362
+ return false;
363
+ const { scheme, id } = parsePeppolId(peppolId);
364
+ return id.trim().length > 0 && (/^\d{4}$/.test(scheme) || EAS_LITERAL_SCHEMES.has(scheme));
365
+ }
366
+ var EAS_LITERAL_SCHEMES = /* @__PURE__ */ new Set(["AN", "AQ", "AS", "AU", "EM", "SEPA"]);
367
+ function parsePeppolId(peppolId) {
368
+ const first = peppolId.indexOf(":");
369
+ if (first === -1) {
370
+ return { scheme: canonicalScheme(peppolId), id: "" };
371
+ }
372
+ const second = peppolId.indexOf(":", first + 1);
373
+ if (second !== -1) {
374
+ const resolved = lookupCanonicalScheme(peppolId.slice(0, second));
375
+ if (resolved !== void 0) {
376
+ return { scheme: resolved, id: peppolId.slice(second + 1) };
377
+ }
378
+ }
379
+ return {
380
+ scheme: canonicalScheme(peppolId.slice(0, first)),
381
+ id: peppolId.slice(first + 1)
382
+ };
383
+ }
384
+
385
+ // ../sdk/dist/core/iso6523-icd-codes.js
386
+ var ICD_CODES = /* @__PURE__ */ new Set([
387
+ "0002",
388
+ "0003",
389
+ "0004",
390
+ "0005",
391
+ "0006",
392
+ "0007",
393
+ "0008",
394
+ "0009",
395
+ "0010",
396
+ "0011",
397
+ "0012",
398
+ "0013",
399
+ "0014",
400
+ "0015",
401
+ "0016",
402
+ "0017",
403
+ "0018",
404
+ "0019",
405
+ "0020",
406
+ "0021",
407
+ "0022",
408
+ "0023",
409
+ "0024",
410
+ "0025",
411
+ "0026",
412
+ "0027",
413
+ "0028",
414
+ "0029",
415
+ "0030",
416
+ "0031",
417
+ "0032",
418
+ "0033",
419
+ "0034",
420
+ "0035",
421
+ "0036",
422
+ "0037",
423
+ "0038",
424
+ "0039",
425
+ "0040",
426
+ "0041",
427
+ "0042",
428
+ "0043",
429
+ "0044",
430
+ "0045",
431
+ "0046",
432
+ "0047",
433
+ "0048",
434
+ "0049",
435
+ "0050",
436
+ "0051",
437
+ "0052",
438
+ "0053",
439
+ "0054",
440
+ "0055",
441
+ "0056",
442
+ "0057",
443
+ "0058",
444
+ "0059",
445
+ "0060",
446
+ "0061",
447
+ "0062",
448
+ "0063",
449
+ "0064",
450
+ "0065",
451
+ "0066",
452
+ "0067",
453
+ "0068",
454
+ "0069",
455
+ "0070",
456
+ "0071",
457
+ "0072",
458
+ "0073",
459
+ "0074",
460
+ "0075",
461
+ "0076",
462
+ "0077",
463
+ "0078",
464
+ "0079",
465
+ "0080",
466
+ "0081",
467
+ "0082",
468
+ "0083",
469
+ "0084",
470
+ "0085",
471
+ "0086",
472
+ "0087",
473
+ "0088",
474
+ "0089",
475
+ "0090",
476
+ "0091",
477
+ "0093",
478
+ "0094",
479
+ "0095",
480
+ "0096",
481
+ "0097",
482
+ "0098",
483
+ "0099",
484
+ "0100",
485
+ "0101",
486
+ "0102",
487
+ "0104",
488
+ "0105",
489
+ "0106",
490
+ "0107",
491
+ "0108",
492
+ "0109",
493
+ "0110",
494
+ "0111",
495
+ "0112",
496
+ "0113",
497
+ "0114",
498
+ "0115",
499
+ "0116",
500
+ "0117",
501
+ "0118",
502
+ "0119",
503
+ "0120",
504
+ "0121",
505
+ "0122",
506
+ "0123",
507
+ "0124",
508
+ "0125",
509
+ "0126",
510
+ "0127",
511
+ "0128",
512
+ "0129",
513
+ "0130",
514
+ "0131",
515
+ "0132",
516
+ "0133",
517
+ "0134",
518
+ "0135",
519
+ "0136",
520
+ "0137",
521
+ "0138",
522
+ "0139",
523
+ "0140",
524
+ "0141",
525
+ "0142",
526
+ "0143",
527
+ "0144",
528
+ "0145",
529
+ "0146",
530
+ "0147",
531
+ "0148",
532
+ "0149",
533
+ "0150",
534
+ "0151",
535
+ "0152",
536
+ "0153",
537
+ "0154",
538
+ "0155",
539
+ "0156",
540
+ "0157",
541
+ "0158",
542
+ "0159",
543
+ "0160",
544
+ "0161",
545
+ "0162",
546
+ "0163",
547
+ "0164",
548
+ "0165",
549
+ "0166",
550
+ "0167",
551
+ "0168",
552
+ "0169",
553
+ "0170",
554
+ "0171",
555
+ "0172",
556
+ "0173",
557
+ "0174",
558
+ "0175",
559
+ "0176",
560
+ "0177",
561
+ "0178",
562
+ "0179",
563
+ "0180",
564
+ "0183",
565
+ "0184",
566
+ "0185",
567
+ "0186",
568
+ "0187",
569
+ "0188",
570
+ "0189",
571
+ "0190",
572
+ "0191",
573
+ "0192",
574
+ "0193",
575
+ "0194",
576
+ "0195",
577
+ "0196",
578
+ "0197",
579
+ "0198",
580
+ "0199",
581
+ "0200",
582
+ "0201",
583
+ "0202",
584
+ "0203",
585
+ "0204",
586
+ "0205",
587
+ "0206",
588
+ "0207",
589
+ "0208",
590
+ "0209",
591
+ "0210",
592
+ "0211",
593
+ "0212",
594
+ "0213",
595
+ "0214",
596
+ "0215",
597
+ "0216",
598
+ "0217",
599
+ "0218",
600
+ "0219",
601
+ "0220",
602
+ "0221",
603
+ "0222",
604
+ "0223",
605
+ "0224",
606
+ "0225",
607
+ "0226",
608
+ "0227",
609
+ "0228",
610
+ "0229",
611
+ "0230",
612
+ "0231",
613
+ "0232",
614
+ "0233",
615
+ "0234",
616
+ "0235",
617
+ "0236",
618
+ "0237",
619
+ "0238",
620
+ "0239",
621
+ "0240",
622
+ "0241",
623
+ "0242",
624
+ "0243",
625
+ "0244",
626
+ "0245"
627
+ ]);
628
+ var ISO6523_ICD_CODES = Object.freeze({
629
+ has: (v) => ICD_CODES.has(v),
630
+ get size() {
631
+ return ICD_CODES.size;
632
+ },
633
+ keys: () => ICD_CODES.keys(),
634
+ values: () => ICD_CODES.values(),
635
+ entries: () => ICD_CODES.entries(),
636
+ forEach: (fn, thisArg) => ICD_CODES.forEach((v, v2) => fn.call(thisArg, v, v2, ISO6523_ICD_CODES)),
637
+ [Symbol.iterator]: () => ICD_CODES[Symbol.iterator]()
638
+ });
639
+ function canCarryPartyIdentification(scheme, context) {
640
+ if (ICD_CODES.has(scheme))
641
+ return true;
642
+ return scheme === "SEPA" && context !== "AccountingCustomerParty";
643
+ }
644
+
133
645
  // ../sdk/dist/core/ubl-builder.js
134
646
  var UBL_NS = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2";
135
647
  var CAC_NS = "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2";
@@ -139,6 +651,24 @@ var PEPPOL_CUSTOMIZATION_ID = "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.
139
651
  var PEPPOL_PROFILE_ID = "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0";
140
652
  var DEFAULT_UNIT = "EA";
141
653
  var DEFAULT_PAYMENT_MEANS = 30;
654
+ var EXEMPTION_REASON_CATEGORIES = /* @__PURE__ */ new Set(["E", "AE", "K", "G", "O"]);
655
+ var EXEMPTION_REASON_RULES = {
656
+ E: "BR-E-10",
657
+ AE: "BR-AE-10",
658
+ K: "BR-IC-10",
659
+ G: "BR-G-10",
660
+ O: "BR-O-10"
661
+ };
662
+ var UblBuilderInputError = class extends Error {
663
+ field;
664
+ ruleId;
665
+ constructor(message, field, ruleId) {
666
+ super(message);
667
+ this.field = field;
668
+ this.ruleId = ruleId;
669
+ this.name = "UblBuilderInputError";
670
+ }
671
+ };
142
672
  var UNIT_CODE_MAP = {
143
673
  each: "EA",
144
674
  piece: "EA",
@@ -181,13 +711,28 @@ function formatDate(dateStr) {
181
711
  function formatAmount(amount) {
182
712
  return amount.toFixed(2);
183
713
  }
714
+ function formatVatRate(vatRate, field) {
715
+ if (typeof vatRate !== "number" || !Number.isFinite(vatRate)) {
716
+ throw new UblBuilderInputError("vatRate must be a finite number.", field);
717
+ }
718
+ return String(vatRate);
719
+ }
184
720
  function round2(n) {
185
721
  return Math.round(n * 100) / 100;
186
722
  }
187
- function parsePeppolId(peppolId) {
188
- const scheme = peppolId.split(":")[0];
189
- const id = peppolId.split(":").slice(1).join(":");
190
- return { scheme, id };
723
+ function normalizedTaxExemptReason(vatCategory, reason) {
724
+ if (!EXEMPTION_REASON_CATEGORIES.has(vatCategory) || typeof reason !== "string") {
725
+ return void 0;
726
+ }
727
+ const normalized = reason.trim();
728
+ for (const character of normalized) {
729
+ const codePoint = character.codePointAt(0);
730
+ const allowed = codePoint === 9 || codePoint === 10 || codePoint === 13 || codePoint >= 32 && codePoint <= 55295 || codePoint >= 57344 && codePoint <= 65533 || codePoint >= 65536 && codePoint <= 1114111;
731
+ if (!allowed) {
732
+ throw new UblBuilderInputError("taxExemptReason contains an invalid XML character.", "taxExemptReason");
733
+ }
734
+ }
735
+ return normalized || void 0;
191
736
  }
192
737
  function buildPartyXml(party, role) {
193
738
  const { scheme: endpointScheme, id: endpointId } = parsePeppolId(party.peppolId);
@@ -195,9 +740,28 @@ function buildPartyXml(party, role) {
195
740
  <cac:${role}>
196
741
  <cac:Party>
197
742
  <cbc:EndpointID schemeID="${escapeXml(endpointScheme)}">${escapeXml(endpointId)}</cbc:EndpointID>
198
- <cac:PartyIdentification>
743
+ ${// ⛔ DEUX listes, pas une. `BR-CL-25` juge l'EndpointID ci-dessus contre la
744
+ // liste EAS (101 valeurs, jusqu'à `9959`) ; `BR-CL-10` juge CE champ-ci
745
+ // contre la liste ISO 6523 ICD (240 valeurs, `0002`–`0245`). `9932` (GB),
746
+ // `9935` (IE) et `9930` (DE) sont légaux là-haut et FATALS ici.
747
+ //
748
+ // Le champ (BT-29) est optionnel : l'omettre est la seule écriture
749
+ // conforme pour ces schemes, et c'est ce que le tir réseau de GPR-1102
750
+ // avait déjà mesuré — `9932`/`9935` n'apparaissent ni en BT-29 ni en
751
+ // BT-30, seulement en BT-31. Émettre quand même rendait `BR-CL-10` fatale
752
+ // pour tout vendeur britannique, irlandais ou allemand en `9930`,
753
+ // y compris en écrivant la forme numérique (GPR-1110).
754
+ //
755
+ // ⚠️ BR-CO-26 exige alors qu'un autre identifiant porte l'expéditeur —
756
+ // `vatNumber` (BT-31) ou `companyId` (BT-30) ci-dessous. `validateInvoice`
757
+ // AVERTIT quand il n'y en a aucun ; il ne refuse pas, et `toXml()` rend
758
+ // donc bel et bien un document que le réseau rejettera. C'est délibéré :
759
+ // `from` est déclaré « deprecated and ignored » à l'envoi, où l'expéditeur
760
+ // vient de la clé API — bloquer ici casserait des appelants dont le
761
+ // document part très bien.
762
+ canCarryPartyIdentification(endpointScheme, role) ? `<cac:PartyIdentification>
199
763
  <cbc:ID schemeID="${escapeXml(endpointScheme)}">${escapeXml(endpointId)}</cbc:ID>
200
- </cac:PartyIdentification>
764
+ </cac:PartyIdentification>` : ""}
201
765
  <cac:PartyName>
202
766
  <cbc:Name>${escapeXml(party.name)}</cbc:Name>
203
767
  </cac:PartyName>
@@ -231,9 +795,12 @@ function buildPayeePartyXml(party) {
231
795
  const { scheme, id } = parsePeppolId(party.peppolId);
232
796
  return `
233
797
  <cac:PayeeParty>
234
- <cac:PartyIdentification>
798
+ ${// Même règle qu'au-dessus : `BR-CL-10` a pour contexte TOUT
799
+ // `cac:PartyIdentification/cbc:ID[@schemeID]`, PayeeParty compris. Le
800
+ // bénéficiaire (BT-60) reste identifié par son nom, toujours émis.
801
+ canCarryPartyIdentification(scheme, "PayeeParty") ? `<cac:PartyIdentification>
235
802
  <cbc:ID schemeID="${escapeXml(scheme)}">${escapeXml(id)}</cbc:ID>
236
- </cac:PartyIdentification>
803
+ </cac:PartyIdentification>` : ""}
237
804
  <cac:PartyName>
238
805
  <cbc:Name>${escapeXml(party.name)}</cbc:Name>
239
806
  </cac:PartyName>
@@ -347,8 +914,8 @@ function buildDocumentAllowanceChargeXml(item, isCharge, currency) {
347
914
  <cbc:AllowanceChargeReason>${escapeXml(item.reason)}</cbc:AllowanceChargeReason>
348
915
  <cbc:Amount currencyID="${escapeXml(currency)}">${formatAmount(item.amount)}</cbc:Amount>
349
916
  <cac:TaxCategory>
350
- <cbc:ID>${vatCategory}</cbc:ID>
351
- <cbc:Percent>${item.vatRate}</cbc:Percent>
917
+ <cbc:ID>${escapeXml(vatCategory)}</cbc:ID>
918
+ ${vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(item.vatRate, "vatRate")}</cbc:Percent>`}
352
919
  <cac:TaxScheme>
353
920
  <cbc:ID>VAT</cbc:ID>
354
921
  </cac:TaxScheme>
@@ -388,8 +955,8 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
388
955
  <cbc:ID>${escapeXml(line.itemId)}</cbc:ID>
389
956
  </cac:SellersItemIdentification>` : ""}
390
957
  <cac:ClassifiedTaxCategory>
391
- <cbc:ID>${vatCategory}</cbc:ID>
392
- <cbc:Percent>${line.vatRate}</cbc:Percent>
958
+ <cbc:ID>${escapeXml(vatCategory)}</cbc:ID>
959
+ ${vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(line.vatRate, "vatRate")}</cbc:Percent>`}
393
960
  <cac:TaxScheme>
394
961
  <cbc:ID>VAT</cbc:ID>
395
962
  </cac:TaxScheme>
@@ -414,39 +981,60 @@ function buildDocumentLineXml(line, index, currency, lineTag, qtyTag) {
414
981
  function buildInvoiceLineXml(line, index, currency) {
415
982
  return buildDocumentLineXml(line, index, currency, "InvoiceLine", "InvoicedQuantity");
416
983
  }
417
- function calculateTaxSubtotals(lines, allowances, charges) {
984
+ function calculateTaxSubtotals(lines, allowances, charges, options = {}) {
418
985
  const groups = /* @__PURE__ */ new Map();
419
- function addToGroup(vatCategory, vatRate, amount) {
420
- const key = `${vatCategory}-${vatRate}`;
986
+ function addToGroup(vatCategory, vatRate, amount, taxExemptReason, field = "taxExemptReason") {
987
+ if (typeof vatCategory !== "string") {
988
+ throw new UblBuilderInputError("vatCategory must be a string.", `${field}.vatCategory`);
989
+ }
990
+ formatVatRate(vatRate, `${field}.vatRate`);
991
+ if (options.forUbl && vatCategory === "O" && vatRate !== 0) {
992
+ throw new UblBuilderInputError("Category O must use vatRate 0 in SDK input.", `${field}.vatRate`);
993
+ }
994
+ const effectiveVatRate = options.forUbl && vatCategory === "O" ? 0 : vatRate;
995
+ const reason = options.forUbl ? normalizedTaxExemptReason(vatCategory, taxExemptReason) : void 0;
996
+ const key = `${vatCategory}-${effectiveVatRate}`;
421
997
  const existing = groups.get(key);
422
998
  if (existing) {
999
+ if (reason && existing.taxExemptReason && reason !== existing.taxExemptReason) {
1000
+ throw new UblBuilderInputError(`Conflicting taxExemptReason values for VAT group ${vatCategory}/${effectiveVatRate}.`, "taxExemptReason");
1001
+ }
1002
+ existing.taxExemptReason ??= reason;
423
1003
  existing.taxableAmount = round2(existing.taxableAmount + amount);
424
1004
  } else {
425
1005
  groups.set(key, {
426
- vatRate,
1006
+ vatRate: effectiveVatRate,
427
1007
  vatCategory,
1008
+ taxExemptReason: reason,
428
1009
  taxableAmount: amount,
429
1010
  taxAmount: 0
430
1011
  // computed once per group below (BR-CO-17)
431
1012
  });
432
1013
  }
433
1014
  }
434
- for (const line of lines) {
435
- addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line));
1015
+ for (const [index, line] of lines.entries()) {
1016
+ addToGroup(line.vatCategory ?? "S", line.vatRate, calculateLineExtensionAmount(line), line.taxExemptReason, `lines[${index}]`);
436
1017
  }
437
- for (const a of allowances ?? []) {
438
- addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount);
1018
+ for (const [index, a] of (allowances ?? []).entries()) {
1019
+ addToGroup(a.vatCategory ?? "S", a.vatRate, -a.amount, a.taxExemptReason, `allowances[${index}]`);
439
1020
  }
440
- for (const c of charges ?? []) {
441
- addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount);
1021
+ for (const [index, c] of (charges ?? []).entries()) {
1022
+ addToGroup(c.vatCategory ?? "S", c.vatRate, c.amount, c.taxExemptReason, `charges[${index}]`);
1023
+ }
1024
+ if (options.forUbl) {
1025
+ for (const subtotal of groups.values()) {
1026
+ if (EXEMPTION_REASON_CATEGORIES.has(subtotal.vatCategory) && !subtotal.taxExemptReason) {
1027
+ throw new UblBuilderInputError(`VAT category ${subtotal.vatCategory} requires a non-empty taxExemptReason.`, "taxExemptReason", EXEMPTION_REASON_RULES[subtotal.vatCategory]);
1028
+ }
1029
+ }
442
1030
  }
443
1031
  return Array.from(groups.values()).map((subtotal) => ({
444
1032
  ...subtotal,
445
1033
  taxAmount: round2(subtotal.taxableAmount * (subtotal.vatRate / 100))
446
1034
  }));
447
1035
  }
448
- function calculateDocumentTotals(lines, allowances, charges) {
449
- const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges);
1036
+ function calculateDocumentTotals(lines, allowances, charges, options = {}) {
1037
+ const taxSubtotals = calculateTaxSubtotals(lines, allowances, charges, options);
450
1038
  const lineExtensionAmount = lines.reduce((sum, line) => sum + calculateLineExtensionAmount(line), 0);
451
1039
  const allowanceTotalAmount = (allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
452
1040
  const chargeTotalAmount = (charges ?? []).reduce((sum, c) => sum + c.amount, 0);
@@ -470,8 +1058,9 @@ function buildTaxTotalXml(taxSubtotals, totalTax, currency) {
470
1058
  <cbc:TaxableAmount currencyID="${escapeXml(currency)}">${formatAmount(st.taxableAmount)}</cbc:TaxableAmount>
471
1059
  <cbc:TaxAmount currencyID="${escapeXml(currency)}">${formatAmount(st.taxAmount)}</cbc:TaxAmount>
472
1060
  <cac:TaxCategory>
473
- <cbc:ID>${st.vatCategory}</cbc:ID>
474
- <cbc:Percent>${st.vatRate}</cbc:Percent>
1061
+ <cbc:ID>${escapeXml(st.vatCategory)}</cbc:ID>
1062
+ ${st.vatCategory === "O" ? "" : `<cbc:Percent>${formatVatRate(st.vatRate, "vatRate")}</cbc:Percent>`}
1063
+ ${st.taxExemptReason ? `<cbc:TaxExemptionReason>${escapeXml(st.taxExemptReason)}</cbc:TaxExemptionReason>` : ""}
475
1064
  <cac:TaxScheme>
476
1065
  <cbc:ID>VAT</cbc:ID>
477
1066
  </cac:TaxScheme>
@@ -540,7 +1129,7 @@ function buildInvoiceXml(input) {
540
1129
  const date = formatDate(input.date);
541
1130
  const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
542
1131
  const hasTaxCurrency = input.taxCurrency && input.taxCurrency !== currency;
543
- const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges);
1132
+ const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges, { forUbl: true });
544
1133
  const linesXml = input.lines.map((line, i) => buildInvoiceLineXml(line, i, currency)).join("");
545
1134
  return `<?xml version="1.0" encoding="UTF-8"?>
546
1135
  <Invoice xmlns="${UBL_NS}"
@@ -587,7 +1176,7 @@ function buildCreditNoteXml(input) {
587
1176
  const date = formatDate(input.date);
588
1177
  const dueDate = input.dueDate ? formatDate(input.dueDate) : void 0;
589
1178
  const hasTaxCurrency = input.taxCurrency && input.taxCurrency !== currency;
590
- const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges);
1179
+ const totals = calculateDocumentTotals(input.lines, input.allowances, input.charges, { forUbl: true });
591
1180
  const linesXml = input.lines.map((line, i) => buildCreditNoteLineXml(line, i, currency)).join("");
592
1181
  return `<?xml version="1.0" encoding="UTF-8"?>
593
1182
  <CreditNote xmlns="${CREDIT_NOTE_NS}"
@@ -964,8 +1553,8 @@ function validateParty(party, path) {
964
1553
  if (party.peppolId === void 0 || party.peppolId === null || party.peppolId === "") {
965
1554
  errors.push(error(`${path}.peppolId`, "Peppol participant ID is required", void 0, 'Format: "scheme:id", e.g. "0208:0685660237" for Belgian companies'));
966
1555
  } else if (!assertString(party.peppolId, `${path}.peppolId`, errors)) {
967
- } else if (!party.peppolId.includes(":")) {
968
- errors.push(error(`${path}.peppolId`, `Invalid Peppol ID format: "${party.peppolId}"`, void 0, 'Must be "scheme:id" format. Common schemes: 0208 (Belgium), 0009 (France SIRET), 0204 (Germany Leitweg)'));
1556
+ } else if (!isWellFormedPeppolId(party.peppolId)) {
1557
+ errors.push(error(`${path}.peppolId`, `Invalid Peppol ID format: "${party.peppolId}"`, void 0, 'Must be "scheme:id" \u2014 e.g. "0208:0685660237" or "GB:VAT:123456789". The scheme alone ("GB:VAT") is not an identifier.'));
969
1558
  }
970
1559
  if (party.country === void 0 || party.country === null || party.country === "") {
971
1560
  errors.push(error(`${path}.country`, "Country code is required", "BR-11"));
@@ -1047,6 +1636,14 @@ function validateInvoice(input) {
1047
1636
  }
1048
1637
  if (input.from) {
1049
1638
  warnings.push(warning("from", "Seller info is determined by your API key. The 'from' field is deprecated and ignored."));
1639
+ const fromId = input.from.peppolId;
1640
+ if (typeof fromId === "string" && fromId.length > 0) {
1641
+ const { scheme } = parsePeppolId(fromId);
1642
+ const bt29Written = canCarryPartyIdentification(scheme, "AccountingSupplierParty");
1643
+ if (!bt29Written && !input.from.vatNumber && !input.from.companyId) {
1644
+ warnings.push(warning("from.peppolId", `Scheme "${scheme}" cannot carry a seller identifier (BT-29), so that field is omitted. Add a vatNumber (BT-31) or companyId (BT-30), or the network will reject the document under BR-CO-26.`, "BR-CO-26"));
1645
+ }
1646
+ }
1050
1647
  }
1051
1648
  if (!input.to) {
1052
1649
  errors.push(error("to", "Buyer (to) is required", "BR-07"));
@@ -1066,15 +1663,36 @@ function validateInvoice(input) {
1066
1663
  if (ppId === void 0 || ppId === null || ppId === "") {
1067
1664
  errors.push(error("payeeParty.peppolId", "Payee party Peppol ID is required", void 0, 'Format: "scheme:id", e.g. "0208:0685660237"'));
1068
1665
  } else if (!assertString(ppId, "payeeParty.peppolId", errors)) {
1069
- } else if (!ppId.includes(":")) {
1070
- errors.push(error("payeeParty.peppolId", `Invalid Peppol ID format: "${ppId}"`, void 0, 'Must be "scheme:id" format'));
1666
+ } else if (!isWellFormedPeppolId(ppId)) {
1667
+ errors.push(error("payeeParty.peppolId", `Invalid Peppol ID format: "${ppId}"`, void 0, 'Must be "scheme:id" \u2014 e.g. "0208:0685660237". The scheme alone ("GB:VAT") is not an identifier.'));
1071
1668
  }
1072
1669
  }
1073
- if (!input.lines || input.lines.length === 0) {
1670
+ const linesValue = input.lines;
1671
+ if (!Array.isArray(linesValue)) {
1672
+ errors.push(error("lines", "Line items must be an array", void 0));
1673
+ } else if (linesValue.length === 0) {
1074
1674
  errors.push(error("lines", "At least one line item is required", "BR-16", "Add items to the lines array"));
1075
1675
  } else {
1076
- for (let i = 0; i < input.lines.length; i++) {
1077
- errors.push(...validateLine(input.lines[i], i, input.isCreditNote));
1676
+ for (const [i, line] of linesValue.entries()) {
1677
+ if (line === null || typeof line !== "object") {
1678
+ errors.push(error(`lines[${i}]`, `Line item ${i} must be an object`, void 0));
1679
+ continue;
1680
+ }
1681
+ errors.push(...validateLine(line, i, input.isCreditNote));
1682
+ }
1683
+ }
1684
+ for (const field of ["allowances", "charges"]) {
1685
+ const value = input[field];
1686
+ if (value === void 0)
1687
+ continue;
1688
+ if (!Array.isArray(value)) {
1689
+ errors.push(error(field, `${field} must be an array`, void 0));
1690
+ continue;
1691
+ }
1692
+ for (const [index, item] of value.entries()) {
1693
+ if (item === null || typeof item !== "object") {
1694
+ errors.push(error(`${field}[${index}]`, `${field}[${index}] must be an object`, void 0));
1695
+ }
1078
1696
  }
1079
1697
  }
1080
1698
  if (input.date) {
@@ -1136,154 +1754,679 @@ function validateInvoice(input) {
1136
1754
  };
1137
1755
  }
1138
1756
 
1139
- // ../sdk/dist/core/status-precedence.js
1140
- var STATUS_PRECEDENCE = [
1141
- { status: "failed", family: "terminal-failure" },
1142
- { status: "rejected", family: "terminal-failure" },
1143
- { status: "paid", family: "terminal-success" },
1144
- { status: "partially_paid", family: "progress" },
1145
- { status: "accepted", family: "progress" },
1146
- { status: "conditionally_accepted", family: "progress" },
1147
- { status: "under_query", family: "progress" },
1148
- { status: "in_process", family: "progress" },
1149
- { status: "cleared", family: "progress" },
1150
- { status: "delivered", family: "progress" },
1151
- { status: "acknowledged", family: "progress" },
1152
- // Terminal for developer wait semantics only — stays rank 40 (non-terminal)
1153
- // in the projection guard (§3.12 two-level terminality).
1154
- { status: "no_action", family: "terminal-failure" },
1155
- { status: "submitted", family: "progress" },
1156
- { status: "unknown", family: "fallback" }
1157
- ];
1158
- function statusFamily(status) {
1159
- return STATUS_PRECEDENCE.find((e) => e.status === status)?.family ?? "fallback";
1757
+ // ../sdk/dist/core/schematron.js
1758
+ function violation(ruleId, severity, message, field) {
1759
+ return { ruleId, severity, message, field };
1160
1760
  }
1161
- var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
1162
-
1163
- // ../sdk/dist/version.js
1164
- var SDK_VERSION = "4.3.0";
1165
-
1166
- // ../sdk/dist/core/client.js
1167
- function findHeaderCaseInsensitive(headers, name) {
1168
- if (!headers)
1169
- return void 0;
1170
- const target = name.toLowerCase();
1171
- for (const [key, value] of Object.entries(headers)) {
1172
- if (key.toLowerCase() === target)
1173
- return value;
1761
+ var _knownUnitCodes;
1762
+ function getKnownUnitCodes() {
1763
+ if (!_knownUnitCodes) {
1764
+ _knownUnitCodes = new Set(getAllUnits().map((u) => u.code));
1174
1765
  }
1175
- return void 0;
1176
- }
1177
- var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
1178
- function sleep(ms) {
1179
- return new Promise((resolve4) => setTimeout(resolve4, ms));
1766
+ return _knownUnitCodes;
1180
1767
  }
1181
- function calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs) {
1182
- if (retryAfterMs !== void 0)
1183
- return Math.min(retryAfterMs, maxDelayMs);
1184
- const exponentialDelay = initialDelayMs * Math.pow(2, attempt);
1185
- const jitter = Math.random() * initialDelayMs;
1186
- return Math.min(exponentialDelay + jitter, maxDelayMs);
1768
+ var VALID_VAT_CATEGORIES = /* @__PURE__ */ new Set(["S", "Z", "E", "AE", "K", "G", "O", "L", "M", "B"]);
1769
+ var SENDABLE_VAT_CATEGORIES = ["S", "Z", "E", "AE", "K", "G", "O"];
1770
+ var UNROUTABLE_VAT_CATEGORIES = /* @__PURE__ */ new Set(["L", "M", "B"]);
1771
+ function computeLineNet(line) {
1772
+ const baseQty = line.baseQuantity ?? 1;
1773
+ if (baseQty === 0)
1774
+ return NaN;
1775
+ const baseAmount = line.quantity * line.unitPrice / baseQty;
1776
+ const chargeTotal = (line.charges ?? []).reduce((sum, c) => sum + c.amount, 0);
1777
+ const allowanceTotal = (line.allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
1778
+ return baseAmount + chargeTotal - allowanceTotal;
1187
1779
  }
1188
- function parseRetryAfter(headerValue) {
1189
- if (!headerValue)
1190
- return void 0;
1191
- const seconds = Number(headerValue);
1192
- if (Number.isFinite(seconds) && seconds >= 0) {
1193
- return seconds * 1e3;
1194
- }
1195
- const dateMs = Date.parse(headerValue);
1196
- if (!Number.isNaN(dateMs)) {
1197
- const delayMs = dateMs - Date.now();
1198
- return delayMs > 0 ? delayMs : 0;
1780
+ var br02 = (input) => {
1781
+ if (!input.number?.trim()) {
1782
+ return [violation("BR-02", "error", "Invoice number is required.", "number")];
1199
1783
  }
1200
- return void 0;
1201
- }
1202
- var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
1203
- function stripControls(value) {
1204
- return value.replace(CONTROL_CHARACTERS, " ");
1205
- }
1206
- function readOwn(source, key) {
1207
- return Object.hasOwn(source, key) ? source[key] : void 0;
1208
- }
1209
- function readSentence(source, key) {
1210
- const value = readOwn(source, key);
1211
- if (typeof value !== "string")
1212
- return null;
1213
- const cleaned = stripControls(value).trim();
1214
- return cleaned === "" ? null : cleaned;
1215
- }
1216
- function safeDocsUrl(value) {
1217
- if (typeof value !== "string")
1218
- return null;
1219
- let parsed;
1220
- try {
1221
- parsed = new URL(value);
1222
- } catch {
1223
- return null;
1784
+ return [];
1785
+ };
1786
+ var br03 = (input) => {
1787
+ if (!input.date) {
1788
+ return [
1789
+ violation("BR-03", "warning", "Invoice issue date is not set. The SDK will default to today's date.", "date")
1790
+ ];
1224
1791
  }
1225
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
1226
- return null;
1227
- return parsed.href;
1228
- }
1229
- function formatApiErrorMessage(status, rawBody) {
1230
- const verbatim = `getpeppr API error (${status}): ${stripControls(rawBody)}`;
1231
- let parsed;
1232
- try {
1233
- parsed = JSON.parse(rawBody);
1234
- } catch {
1235
- return verbatim;
1792
+ return [];
1793
+ };
1794
+ var br06 = (input) => {
1795
+ if (input.from && !input.from.vatNumber) {
1796
+ return [
1797
+ violation("BR-06", "warning", "Seller party has no VAT number. The gateway will use the account's VAT registration.", "from.vatNumber")
1798
+ ];
1236
1799
  }
1237
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1238
- return verbatim;
1800
+ return [];
1801
+ };
1802
+ var br07 = (input) => {
1803
+ if (!input.to?.name?.trim()) {
1804
+ return [violation("BR-07", "error", "Buyer name is required.", "to.name")];
1239
1805
  }
1240
- const sentence = readSentence(parsed, "message") ?? readSentence(parsed, "error");
1241
- if (sentence === null)
1242
- return verbatim;
1243
- const link = safeDocsUrl(readOwn(parsed, "docs"));
1244
- return `getpeppr API error (${status}): ${sentence}${link ? ` See ${link}` : ""}`;
1245
- }
1246
- function isRetryableError(error2) {
1247
- if (error2 instanceof PeppolApiError) {
1248
- return RETRYABLE_STATUS_CODES.has(error2.statusCode);
1806
+ return [];
1807
+ };
1808
+ var br08 = (input) => {
1809
+ if (!input.lines || input.lines.length === 0) {
1810
+ return [violation("BR-08", "error", "Invoice must have at least one line item.", "lines")];
1249
1811
  }
1250
- if (error2 instanceof Error && error2.name === "AbortError") {
1251
- return true;
1812
+ return [];
1813
+ };
1814
+ var br09 = (input) => {
1815
+ if (!input.dueDate && !input.paymentTerms) {
1816
+ return [
1817
+ violation("BR-09", "warning", "Neither due date nor payment terms specified. At least one is recommended.", "dueDate")
1818
+ ];
1252
1819
  }
1253
- if (error2 instanceof TypeError && /fetch failed|network|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT/i.test(error2.message)) {
1254
- return true;
1820
+ return [];
1821
+ };
1822
+ var br10 = (input) => {
1823
+ if (!input.buyerReference && !input.orderReference) {
1824
+ return [
1825
+ violation("BR-10", "warning", "Neither buyerReference nor orderReference specified. Peppol BIS 3.0 requires at least one.", "buyerReference")
1826
+ ];
1255
1827
  }
1256
- return false;
1257
- }
1258
- var DEFAULT_BASE_URL = "https://api.getpeppr.dev/v1";
1259
- var GetpepprAdapter = class {
1260
- name = "getpeppr";
1261
- baseUrl;
1262
- apiKey;
1263
- timeout;
1264
- retryConfig;
1265
- onRequest;
1266
- onResponse;
1267
- constructor(config) {
1268
- this.apiKey = config.apiKey;
1269
- this.timeout = config.timeout ?? 3e4;
1270
- this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
1271
- this.retryConfig = {
1272
- maxRetries: config.retry?.maxRetries ?? 3,
1273
- initialDelayMs: config.retry?.initialDelayMs ?? 500,
1274
- maxDelayMs: config.retry?.maxDelayMs ?? 3e4
1275
- };
1276
- this.onRequest = config.onRequest;
1277
- this.onResponse = config.onResponse;
1828
+ return [];
1829
+ };
1830
+ var brCo10 = (input) => {
1831
+ const violations = [];
1832
+ if (!input.lines)
1833
+ return violations;
1834
+ for (let i = 0; i < input.lines.length; i++) {
1835
+ const line = input.lines[i];
1836
+ const net = computeLineNet(line);
1837
+ if (!Number.isFinite(net)) {
1838
+ const baseQty = line.baseQuantity ?? 1;
1839
+ const detail = baseQty === 0 ? "baseQuantity is 0, causing division by zero." : "Computed line amount is not a finite number.";
1840
+ violations.push(violation("BR-CO-10", "error", `Line ${i}: invalid net amount. ${detail}`, `lines[${i}]`));
1841
+ }
1278
1842
  }
1279
- async request(method, path, body, extraHeaders) {
1280
- const { maxRetries, initialDelayMs, maxDelayMs } = this.retryConfig;
1281
- let lastError;
1282
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1283
- try {
1284
- return await this.doRequest(method, path, body, extraHeaders);
1285
- } catch (err) {
1286
- lastError = err;
1843
+ return violations;
1844
+ };
1845
+ var brCo13 = (input) => {
1846
+ if (!input.lines || input.lines.length === 0)
1847
+ return [];
1848
+ let totalVat = 0;
1849
+ for (let i = 0; i < input.lines.length; i++) {
1850
+ const line = input.lines[i];
1851
+ const net = computeLineNet(line);
1852
+ if (!Number.isFinite(net))
1853
+ continue;
1854
+ totalVat += net * (line.vatRate / 100);
1855
+ }
1856
+ for (const allowance of input.allowances ?? []) {
1857
+ totalVat -= allowance.amount * (allowance.vatRate / 100);
1858
+ }
1859
+ for (const charge of input.charges ?? []) {
1860
+ totalVat += charge.amount * (charge.vatRate / 100);
1861
+ }
1862
+ if (!Number.isFinite(totalVat)) {
1863
+ return [
1864
+ violation("BR-CO-13", "error", "Computed total VAT amount is not a finite number. Check line amounts and VAT rates.")
1865
+ ];
1866
+ }
1867
+ if (totalVat < -0.01) {
1868
+ return [
1869
+ violation("BR-CO-13", "warning", `Computed total VAT is negative (${totalVat.toFixed(2)}). This is unusual for an invoice.`)
1870
+ ];
1871
+ }
1872
+ return [];
1873
+ };
1874
+ var brCo15 = (input) => {
1875
+ if (!input.lines || input.lines.length === 0)
1876
+ return [];
1877
+ let lineTotal = 0;
1878
+ let vatTotal = 0;
1879
+ for (const line of input.lines) {
1880
+ const net = computeLineNet(line);
1881
+ if (!Number.isFinite(net))
1882
+ continue;
1883
+ lineTotal += net;
1884
+ vatTotal += net * (line.vatRate / 100);
1885
+ }
1886
+ for (const allowance of input.allowances ?? []) {
1887
+ lineTotal -= allowance.amount;
1888
+ vatTotal -= allowance.amount * (allowance.vatRate / 100);
1889
+ }
1890
+ for (const charge of input.charges ?? []) {
1891
+ lineTotal += charge.amount;
1892
+ vatTotal += charge.amount * (charge.vatRate / 100);
1893
+ }
1894
+ const taxInclusive = lineTotal + vatTotal;
1895
+ if (!Number.isFinite(taxInclusive)) {
1896
+ return [
1897
+ violation("BR-CO-15", "error", "Computed tax-inclusive amount is not a finite number.")
1898
+ ];
1899
+ }
1900
+ if (taxInclusive < -0.01) {
1901
+ return [
1902
+ violation("BR-CO-15", "warning", `Computed tax-inclusive amount is negative (${taxInclusive.toFixed(2)}). Consider using a credit note instead.`)
1903
+ ];
1904
+ }
1905
+ return [];
1906
+ };
1907
+ var brCo16 = (input) => {
1908
+ if (!input.lines || input.lines.length === 0)
1909
+ return [];
1910
+ let lineTotal = 0;
1911
+ let vatTotal = 0;
1912
+ for (const line of input.lines) {
1913
+ const net = computeLineNet(line);
1914
+ if (!Number.isFinite(net))
1915
+ continue;
1916
+ lineTotal += net;
1917
+ vatTotal += net * (line.vatRate / 100);
1918
+ }
1919
+ for (const allowance of input.allowances ?? []) {
1920
+ lineTotal -= allowance.amount;
1921
+ vatTotal -= allowance.amount * (allowance.vatRate / 100);
1922
+ }
1923
+ for (const charge of input.charges ?? []) {
1924
+ lineTotal += charge.amount;
1925
+ vatTotal += charge.amount * (charge.vatRate / 100);
1926
+ }
1927
+ const taxInclusive = lineTotal + vatTotal;
1928
+ const prepaid = input.prepaidAmount ?? 0;
1929
+ const rounding = input.roundingAmount ?? 0;
1930
+ const payable = taxInclusive - prepaid + rounding;
1931
+ if (!Number.isFinite(payable)) {
1932
+ return [
1933
+ violation("BR-CO-16", "error", "Computed payable amount is not a finite number.")
1934
+ ];
1935
+ }
1936
+ if (payable < -0.01) {
1937
+ return [
1938
+ violation("BR-CO-16", "warning", `Computed payable amount is negative (${payable.toFixed(2)}). Prepaid amount (${prepaid}) exceeds the invoice total.`)
1939
+ ];
1940
+ }
1941
+ return [];
1942
+ };
1943
+ var brS05 = (input) => {
1944
+ const violations = [];
1945
+ if (!input.lines)
1946
+ return violations;
1947
+ for (let i = 0; i < input.lines.length; i++) {
1948
+ const line = input.lines[i];
1949
+ const category = line.vatCategory ?? "S";
1950
+ if (category === "S" && (line.vatRate === void 0 || line.vatRate <= 0)) {
1951
+ violations.push(violation("BR-S-05", "error", `Line ${i}: standard rate (S) requires vatRate > 0, got ${line.vatRate ?? "undefined"}.`, `lines[${i}].vatRate`));
1952
+ }
1953
+ }
1954
+ return violations;
1955
+ };
1956
+ var brZ05 = (input) => {
1957
+ const violations = [];
1958
+ if (!input.lines)
1959
+ return violations;
1960
+ for (let i = 0; i < input.lines.length; i++) {
1961
+ const line = input.lines[i];
1962
+ if (line.vatCategory === "Z" && line.vatRate !== 0) {
1963
+ violations.push(violation("BR-Z-05", "error", `Line ${i}: zero-rated (Z) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
1964
+ }
1965
+ }
1966
+ return violations;
1967
+ };
1968
+ var brE05 = (input) => {
1969
+ const violations = [];
1970
+ if (!input.lines)
1971
+ return violations;
1972
+ for (let i = 0; i < input.lines.length; i++) {
1973
+ const line = input.lines[i];
1974
+ if (line.vatCategory === "E" && line.vatRate !== 0) {
1975
+ violations.push(violation("BR-E-05", "error", `Line ${i}: exempt (E) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
1976
+ }
1977
+ }
1978
+ return violations;
1979
+ };
1980
+ var brAe05 = (input) => {
1981
+ const violations = [];
1982
+ if (!input.lines)
1983
+ return violations;
1984
+ for (let i = 0; i < input.lines.length; i++) {
1985
+ const line = input.lines[i];
1986
+ if (line.vatCategory === "AE" && line.vatRate !== 0) {
1987
+ violations.push(violation("BR-AE-05", "error", `Line ${i}: reverse charge (AE) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
1988
+ }
1989
+ }
1990
+ return violations;
1991
+ };
1992
+ var brG05 = (input) => {
1993
+ const violations = [];
1994
+ for (let i = 0; i < (input.lines ?? []).length; i++) {
1995
+ const line = input.lines[i];
1996
+ if (line.vatCategory === "G" && line.vatRate !== 0) {
1997
+ violations.push(violation("BR-G-05", "error", `Line ${i}: export outside the EU (G) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
1998
+ }
1999
+ }
2000
+ return violations;
2001
+ };
2002
+ var brIc05 = (input) => {
2003
+ const violations = [];
2004
+ for (let i = 0; i < (input.lines ?? []).length; i++) {
2005
+ const line = input.lines[i];
2006
+ if (line.vatCategory === "K" && line.vatRate !== 0) {
2007
+ violations.push(violation("BR-IC-05", "error", `Line ${i}: intra-community supply (K) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
2008
+ }
2009
+ }
2010
+ return violations;
2011
+ };
2012
+ var documentAdjustmentVatRates = (input) => {
2013
+ const violations = [];
2014
+ const configs = /* @__PURE__ */ new Map([
2015
+ ["S", { allowanceRule: "BR-S-06", chargeRule: "BR-S-07", valid: (rate) => rate > 0, label: "standard rate (S)" }],
2016
+ ["Z", { allowanceRule: "BR-Z-06", chargeRule: "BR-Z-07", valid: (rate) => rate === 0, label: "zero-rated (Z)" }],
2017
+ ["E", { allowanceRule: "BR-E-06", chargeRule: "BR-E-07", valid: (rate) => rate === 0, label: "exempt (E)" }],
2018
+ ["AE", { allowanceRule: "BR-AE-06", chargeRule: "BR-AE-07", valid: (rate) => rate === 0, label: "reverse charge (AE)" }],
2019
+ ["G", { allowanceRule: "BR-G-06", chargeRule: "BR-G-07", valid: (rate) => rate === 0, label: "export outside the EU (G)" }],
2020
+ ["K", { allowanceRule: "BR-IC-06", chargeRule: "BR-IC-07", valid: (rate) => rate === 0, label: "intra-community supply (K)" }]
2021
+ ]);
2022
+ const check = (items, kind) => {
2023
+ (items ?? []).forEach((item, index) => {
2024
+ const category = item.vatCategory ?? "S";
2025
+ const config = configs.get(category);
2026
+ if (!config || config.valid(item.vatRate))
2027
+ return;
2028
+ const ruleId = kind === "allowance" ? config.allowanceRule : config.chargeRule;
2029
+ violations.push(violation(ruleId, "error", `Document ${kind} ${index}: ${config.label} has an invalid vatRate (${item.vatRate}).`, `${kind === "allowance" ? "allowances" : "charges"}[${index}].vatRate`));
2030
+ });
2031
+ };
2032
+ check(input.allowances, "allowance");
2033
+ check(input.charges, "charge");
2034
+ return violations;
2035
+ };
2036
+ function exemptionReasonRule(vatCategory, ruleId, label) {
2037
+ return (input) => {
2038
+ const groups = /* @__PURE__ */ new Map();
2039
+ const add = (category, rate, reason, field) => {
2040
+ if (category !== vatCategory)
2041
+ return;
2042
+ const effectiveRate = category === "O" ? 0 : rate;
2043
+ const key = `${category}-${effectiveRate}`;
2044
+ const hasReason = typeof reason === "string" && reason.trim().length > 0;
2045
+ const existing = groups.get(key);
2046
+ if (existing) {
2047
+ existing.hasReason ||= hasReason;
2048
+ } else {
2049
+ groups.set(key, { hasReason, field });
2050
+ }
2051
+ };
2052
+ (input.lines ?? []).forEach((line, index) => {
2053
+ add(line.vatCategory ?? "S", line.vatRate, line.taxExemptReason, `lines[${index}].taxExemptReason`);
2054
+ });
2055
+ (input.allowances ?? []).forEach((item, index) => {
2056
+ add(item.vatCategory ?? "S", item.vatRate, item.taxExemptReason, `allowances[${index}].taxExemptReason`);
2057
+ });
2058
+ (input.charges ?? []).forEach((item, index) => {
2059
+ add(item.vatCategory ?? "S", item.vatRate, item.taxExemptReason, `charges[${index}].taxExemptReason`);
2060
+ });
2061
+ return [...groups.values()].filter((group) => !group.hasReason).map((group) => violation(ruleId, "error", `${label} requires a non-empty taxExemptReason in its VAT breakdown.`, group.field));
2062
+ };
2063
+ }
2064
+ var brE10 = exemptionReasonRule("E", "BR-E-10", "Exempt from VAT (E)");
2065
+ var brAe10 = exemptionReasonRule("AE", "BR-AE-10", "Reverse charge (AE)");
2066
+ var brG10 = exemptionReasonRule("G", "BR-G-10", "Export outside the EU (G)");
2067
+ var brO10 = exemptionReasonRule("O", "BR-O-10", "Not subject to VAT (O)");
2068
+ var brIc10 = exemptionReasonRule("K", "BR-IC-10", "Intra-community supply (K)");
2069
+ var builderVatCategoryValidity = (input) => {
2070
+ const violations = [];
2071
+ const check = (category, field) => {
2072
+ if (category !== void 0 && !VALID_VAT_CATEGORIES.has(category)) {
2073
+ violations.push(violation("BR-CL-17", "error", "VAT category is not an EN 16931 code.", field));
2074
+ }
2075
+ };
2076
+ (input.lines ?? []).forEach((item, index) => check(item.vatCategory, `lines[${index}].vatCategory`));
2077
+ (input.allowances ?? []).forEach((item, index) => check(item.vatCategory, `allowances[${index}].vatCategory`));
2078
+ (input.charges ?? []).forEach((item, index) => check(item.vatCategory, `charges[${index}].vatCategory`));
2079
+ return violations;
2080
+ };
2081
+ var UBL_BUILDER_VAT_RULES = [
2082
+ builderVatCategoryValidity,
2083
+ brS05,
2084
+ brZ05,
2085
+ brE05,
2086
+ brAe05,
2087
+ brG05,
2088
+ brIc05,
2089
+ documentAdjustmentVatRates,
2090
+ brE10,
2091
+ brAe10,
2092
+ brG10,
2093
+ brO10,
2094
+ brIc10
2095
+ ];
2096
+ function validateUblBuilderVat(input) {
2097
+ const shapeViolations = [];
2098
+ const checkCollection = (value, field) => {
2099
+ if (field === "lines" || value !== void 0) {
2100
+ if (!Array.isArray(value)) {
2101
+ shapeViolations.push(violation("SDK-INPUT", "error", `${field} must be an array.`, field));
2102
+ return;
2103
+ }
2104
+ }
2105
+ if (!Array.isArray(value))
2106
+ return;
2107
+ for (const [index, item] of value.entries()) {
2108
+ const itemField = `${field}[${index}]`;
2109
+ if (item === null || typeof item !== "object") {
2110
+ shapeViolations.push(violation("SDK-INPUT", "error", `${itemField} must be an object.`, itemField));
2111
+ continue;
2112
+ }
2113
+ const candidate = item;
2114
+ if (typeof candidate.vatRate !== "number" || !Number.isFinite(candidate.vatRate)) {
2115
+ shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.vatRate must be a finite number.`, `${itemField}.vatRate`));
2116
+ }
2117
+ if (candidate.vatCategory !== void 0 && typeof candidate.vatCategory !== "string") {
2118
+ shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.vatCategory must be a string.`, `${itemField}.vatCategory`));
2119
+ }
2120
+ if (candidate.taxExemptReason !== void 0 && typeof candidate.taxExemptReason !== "string") {
2121
+ shapeViolations.push(violation("SDK-INPUT", "error", `${itemField}.taxExemptReason must be a string.`, `${itemField}.taxExemptReason`));
2122
+ }
2123
+ }
2124
+ };
2125
+ checkCollection(input.lines, "lines");
2126
+ checkCollection(input.allowances, "allowances");
2127
+ checkCollection(input.charges, "charges");
2128
+ if (shapeViolations.length > 0)
2129
+ return shapeViolations;
2130
+ const oRateViolations = [];
2131
+ const checkORates = (items, field) => {
2132
+ (items ?? []).forEach((item, index) => {
2133
+ if (item.vatCategory === "O" && item.vatRate !== 0) {
2134
+ oRateViolations.push(violation("SDK-INPUT", "error", `Category O must use vatRate 0 in SDK input.`, `${field}[${index}].vatRate`));
2135
+ }
2136
+ });
2137
+ };
2138
+ checkORates(input.lines, "lines");
2139
+ checkORates(input.allowances, "allowances");
2140
+ checkORates(input.charges, "charges");
2141
+ return [
2142
+ ...oRateViolations,
2143
+ ...UBL_BUILDER_VAT_RULES.flatMap((rule) => rule(input))
2144
+ ];
2145
+ }
2146
+ var peppolR004 = (input) => {
2147
+ if (!input.to?.peppolId) {
2148
+ return [
2149
+ violation("PEPPOL-EN16931-R004", "error", "Buyer electronic address (peppolId) is required for Peppol delivery.", "to.peppolId")
2150
+ ];
2151
+ }
2152
+ return [];
2153
+ };
2154
+ var vatCategoryCodes = (input) => {
2155
+ const violations = [];
2156
+ const sendable = SENDABLE_VAT_CATEGORIES.join(", ");
2157
+ const echo = (v) => v.length <= 16 ? v : `${v.slice(0, 16)}\u2026`;
2158
+ const check = (cat, field, label) => {
2159
+ if (cat === void 0 || cat === null)
2160
+ return;
2161
+ if (!VALID_VAT_CATEGORIES.has(cat)) {
2162
+ violations.push(violation("BR-CL-17", "error", `${label}: "${echo(cat)}" is not a VAT category code. Use one of: ${sendable}. Codes are case-sensitive \u2014 "AE" is reverse charge, "ae" is not a category.`, field));
2163
+ return;
2164
+ }
2165
+ if (UNROUTABLE_VAT_CATEGORIES.has(cat)) {
2166
+ violations.push(violation("unsupported_vat_category", "error", `${label}: VAT category "${echo(cat)}" is valid under EN 16931 but getpeppr cannot route it \u2014 our provider has no vocabulary for it. Sendable categories: ${sendable}.`, field));
2167
+ }
2168
+ };
2169
+ (input.lines ?? []).forEach((line, i) => check(line.vatCategory, `lines[${i}].vatCategory`, `Line ${i}`));
2170
+ (input.allowances ?? []).forEach((a, i) => check(a.vatCategory, `allowances[${i}].vatCategory`, `Allowance ${i}`));
2171
+ (input.charges ?? []).forEach((c, i) => check(c.vatCategory, `charges[${i}].vatCategory`, `Charge ${i}`));
2172
+ return violations;
2173
+ };
2174
+ var peppolR080 = (input) => {
2175
+ const violations = [];
2176
+ if (!input.lines)
2177
+ return violations;
2178
+ const knownCodes = getKnownUnitCodes();
2179
+ for (let i = 0; i < input.lines.length; i++) {
2180
+ const line = input.lines[i];
2181
+ if (line.unit) {
2182
+ const resolved = resolveUnit(line.unit);
2183
+ if (!knownCodes.has(resolved)) {
2184
+ violations.push(violation("PEPPOL-EN16931-R080", "warning", `Line ${i}: unit "${line.unit}" (resolved: "${resolved}") is not a known UN/ECE Rec20 code. Common codes: EA, HUR, DAY, KGM.`, `lines[${i}].unit`));
2185
+ }
2186
+ }
2187
+ }
2188
+ return violations;
2189
+ };
2190
+ var ALL_RULES = [
2191
+ // Required fields (BR)
2192
+ br02,
2193
+ br03,
2194
+ br06,
2195
+ br07,
2196
+ br08,
2197
+ br09,
2198
+ br10,
2199
+ // Calculations (BR-CO)
2200
+ brCo10,
2201
+ brCo13,
2202
+ brCo15,
2203
+ brCo16,
2204
+ // Tax categories (one family per category)
2205
+ brS05,
2206
+ brZ05,
2207
+ brE05,
2208
+ brAe05,
2209
+ brG05,
2210
+ brIc05,
2211
+ documentAdjustmentVatRates,
2212
+ brE10,
2213
+ brAe10,
2214
+ brG10,
2215
+ brO10,
2216
+ brIc10,
2217
+ // Peppol-specific
2218
+ peppolR004,
2219
+ vatCategoryCodes,
2220
+ peppolR080
2221
+ ];
2222
+ function validateSchematron(input) {
2223
+ const errors = [];
2224
+ const warnings = [];
2225
+ for (const rule of ALL_RULES) {
2226
+ const violations = rule(input);
2227
+ for (const v of violations) {
2228
+ if (v.severity === "error") {
2229
+ errors.push(v);
2230
+ } else {
2231
+ warnings.push(v);
2232
+ }
2233
+ }
2234
+ }
2235
+ return {
2236
+ valid: errors.length === 0,
2237
+ coverage: { rulesChecked: SDK_SCHEMATRON_RULE_IDS.length, ofNetworkFatalRules: "partial" },
2238
+ errors,
2239
+ warnings
2240
+ };
2241
+ }
2242
+ var SDK_SCHEMATRON_RULE_IDS = [
2243
+ "BR-02",
2244
+ "BR-03",
2245
+ "BR-06",
2246
+ "BR-07",
2247
+ "BR-08",
2248
+ "BR-09",
2249
+ "BR-10",
2250
+ "BR-CL-17",
2251
+ "BR-CO-10",
2252
+ "BR-CO-13",
2253
+ "BR-CO-15",
2254
+ "BR-CO-16",
2255
+ "BR-S-05",
2256
+ "BR-Z-05",
2257
+ "BR-E-05",
2258
+ "BR-AE-05",
2259
+ "BR-G-05",
2260
+ "BR-IC-05",
2261
+ "BR-S-06",
2262
+ "BR-S-07",
2263
+ "BR-Z-06",
2264
+ "BR-Z-07",
2265
+ "BR-E-06",
2266
+ "BR-E-07",
2267
+ "BR-AE-06",
2268
+ "BR-AE-07",
2269
+ "BR-G-06",
2270
+ "BR-G-07",
2271
+ "BR-IC-06",
2272
+ "BR-IC-07",
2273
+ "BR-E-10",
2274
+ "BR-AE-10",
2275
+ "BR-G-10",
2276
+ "BR-O-10",
2277
+ "BR-IC-10",
2278
+ "PEPPOL-EN16931-R004",
2279
+ "PEPPOL-EN16931-R080"
2280
+ ];
2281
+
2282
+ // ../sdk/dist/core/status-precedence.js
2283
+ var STATUS_PRECEDENCE = [
2284
+ { status: "failed", family: "terminal-failure" },
2285
+ { status: "rejected", family: "terminal-failure" },
2286
+ { status: "paid", family: "terminal-success" },
2287
+ { status: "partially_paid", family: "progress" },
2288
+ { status: "accepted", family: "progress" },
2289
+ { status: "conditionally_accepted", family: "progress" },
2290
+ { status: "under_query", family: "progress" },
2291
+ { status: "in_process", family: "progress" },
2292
+ { status: "cleared", family: "progress" },
2293
+ { status: "delivered", family: "progress" },
2294
+ { status: "acknowledged", family: "progress" },
2295
+ // Terminal for developer wait semantics only — stays rank 40 (non-terminal)
2296
+ // in the projection guard (§3.12 two-level terminality).
2297
+ { status: "no_action", family: "terminal-failure" },
2298
+ { status: "submitted", family: "progress" },
2299
+ { status: "unknown", family: "fallback" }
2300
+ ];
2301
+ function statusFamily(status) {
2302
+ return STATUS_PRECEDENCE.find((e) => e.status === status)?.family ?? "fallback";
2303
+ }
2304
+ var TERMINAL_FAILURE_STATUSES = STATUS_PRECEDENCE.filter((e) => e.family === "terminal-failure").map((e) => e.status);
2305
+
2306
+ // ../sdk/dist/version.js
2307
+ var SDK_VERSION = "4.6.0";
2308
+
2309
+ // ../sdk/dist/core/client.js
2310
+ function findHeaderCaseInsensitive(headers, name) {
2311
+ if (!headers)
2312
+ return void 0;
2313
+ const target = name.toLowerCase();
2314
+ for (const [key, value] of Object.entries(headers)) {
2315
+ if (key.toLowerCase() === target)
2316
+ return value;
2317
+ }
2318
+ return void 0;
2319
+ }
2320
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
2321
+ function sleep(ms) {
2322
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
2323
+ }
2324
+ function calculateRetryDelay(attempt, initialDelayMs, maxDelayMs, retryAfterMs) {
2325
+ if (retryAfterMs !== void 0)
2326
+ return Math.min(retryAfterMs, maxDelayMs);
2327
+ const exponentialDelay = initialDelayMs * Math.pow(2, attempt);
2328
+ const jitter = Math.random() * initialDelayMs;
2329
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
2330
+ }
2331
+ function parseRetryAfter(headerValue) {
2332
+ if (!headerValue)
2333
+ return void 0;
2334
+ const seconds = Number(headerValue);
2335
+ if (Number.isFinite(seconds) && seconds >= 0) {
2336
+ return seconds * 1e3;
2337
+ }
2338
+ const dateMs = Date.parse(headerValue);
2339
+ if (!Number.isNaN(dateMs)) {
2340
+ const delayMs = dateMs - Date.now();
2341
+ return delayMs > 0 ? delayMs : 0;
2342
+ }
2343
+ return void 0;
2344
+ }
2345
+ var CONTROL_CHARACTERS = /[\u0000-\u001F\u007F-\u009F]/g;
2346
+ function stripControls(value) {
2347
+ return value.replace(CONTROL_CHARACTERS, " ");
2348
+ }
2349
+ function readOwn(source, key) {
2350
+ return Object.hasOwn(source, key) ? source[key] : void 0;
2351
+ }
2352
+ function readSentence(source, key) {
2353
+ const value = readOwn(source, key);
2354
+ if (typeof value !== "string")
2355
+ return null;
2356
+ const cleaned = stripControls(value).trim();
2357
+ return cleaned === "" ? null : cleaned;
2358
+ }
2359
+ function safeDocsUrl(value) {
2360
+ if (typeof value !== "string")
2361
+ return null;
2362
+ let parsed;
2363
+ try {
2364
+ parsed = new URL(value);
2365
+ } catch {
2366
+ return null;
2367
+ }
2368
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
2369
+ return null;
2370
+ return parsed.href;
2371
+ }
2372
+ function formatApiErrorMessage(status, rawBody) {
2373
+ const verbatim = `getpeppr API error (${status}): ${stripControls(rawBody)}`;
2374
+ let parsed;
2375
+ try {
2376
+ parsed = JSON.parse(rawBody);
2377
+ } catch {
2378
+ return verbatim;
2379
+ }
2380
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2381
+ return verbatim;
2382
+ }
2383
+ const sentence = readSentence(parsed, "message") ?? readSentence(parsed, "error");
2384
+ if (sentence === null)
2385
+ return verbatim;
2386
+ const link = safeDocsUrl(readOwn(parsed, "docs"));
2387
+ return `getpeppr API error (${status}): ${sentence}${link ? ` See ${link}` : ""}`;
2388
+ }
2389
+ function isRetryableError(error2) {
2390
+ if (error2 instanceof PeppolApiError) {
2391
+ return RETRYABLE_STATUS_CODES.has(error2.statusCode);
2392
+ }
2393
+ if (error2 instanceof Error && error2.name === "AbortError") {
2394
+ return true;
2395
+ }
2396
+ if (error2 instanceof TypeError && /fetch failed|network|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT/i.test(error2.message)) {
2397
+ return true;
2398
+ }
2399
+ return false;
2400
+ }
2401
+ var DEFAULT_BASE_URL = "https://api.getpeppr.dev/v1";
2402
+ var GetpepprAdapter = class {
2403
+ name = "getpeppr";
2404
+ baseUrl;
2405
+ apiKey;
2406
+ timeout;
2407
+ retryConfig;
2408
+ onRequest;
2409
+ onResponse;
2410
+ constructor(config) {
2411
+ this.apiKey = config.apiKey;
2412
+ this.timeout = config.timeout ?? 3e4;
2413
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
2414
+ this.retryConfig = {
2415
+ maxRetries: config.retry?.maxRetries ?? 3,
2416
+ initialDelayMs: config.retry?.initialDelayMs ?? 500,
2417
+ maxDelayMs: config.retry?.maxDelayMs ?? 3e4
2418
+ };
2419
+ this.onRequest = config.onRequest;
2420
+ this.onResponse = config.onResponse;
2421
+ }
2422
+ async request(method, path, body, extraHeaders) {
2423
+ const { maxRetries, initialDelayMs, maxDelayMs } = this.retryConfig;
2424
+ let lastError;
2425
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2426
+ try {
2427
+ return await this.doRequest(method, path, body, extraHeaders);
2428
+ } catch (err) {
2429
+ lastError = err;
1287
2430
  const is429 = err instanceof PeppolApiError && err.statusCode === 429;
1288
2431
  const isSafeMethod = /^(GET|DELETE|HEAD)$/i.test(method);
1289
2432
  const hasIdempotencyKey = !!findHeaderCaseInsensitive(extraHeaders, "Idempotency-Key");
@@ -1761,7 +2904,24 @@ var GetpepprAdapter = class {
1761
2904
  // Declared, never derived from the document: routing decides delivery, and
1762
2905
  // parsing a caller's XML for a destination would put a parse error on the
1763
2906
  // "whose invoice goes where" path.
1764
- to: options.to
2907
+ to: options.to,
2908
+ // ⛔ GPR-1129 — ce corps est une LISTE BLANCHE, exactement comme
2909
+ // `parseSendResult` l'est en sortie : tout champ non recopié ici est
2910
+ // supprimé en SILENCE, et la capacité correspondante devient
2911
+ // inatteignable pour quiconque passe par le SDK.
2912
+ //
2913
+ // Quatrième occurrence de cette classe, la première dans le sens REQUÊTE
2914
+ // (`rulebook` GPR-1069, `transmission` GPR-1089, `duplicateOf` GPR-1105
2915
+ // étaient des champs de RÉPONSE). Le préjudice n'est pas symétrique : un
2916
+ // champ de réponse jeté casse qui le lit, un champ de requête jeté
2917
+ // produit un refus que l'appelant ne peut relier à rien — il envoie
2918
+ // `sender`, la passerelle ne le voit jamais, et le 422 qu'il reçoit parle
2919
+ // d'une identité qu'il ne revendiquait pas.
2920
+ //
2921
+ // ⚠️ Conditionnel, jamais `sender: options.sender` : une clé présente à
2922
+ // `undefined` disparaît du JSON, donc l'écriture nue passerait les tests
2923
+ // tout en salissant le corps des envois standards.
2924
+ ...options.sender ? { sender: options.sender } : {}
1765
2925
  };
1766
2926
  const result = await this.request("POST", "/invoices/import", body);
1767
2927
  return parseSendResult(result);
@@ -1883,6 +3043,9 @@ function parseSendResult(body) {
1883
3043
  sendResult.transmission = { mode, bytePreservation };
1884
3044
  }
1885
3045
  }
3046
+ const duplicateOf = optionalWireId(readOwn(result, "duplicateOf"));
3047
+ if (duplicateOf)
3048
+ sendResult.duplicateOf = duplicateOf;
1886
3049
  const submissionId = optionalWireId(result.submissionId);
1887
3050
  if (submissionId)
1888
3051
  sendResult.submissionId = submissionId;
@@ -2253,9 +3416,11 @@ var Peppol = class {
2253
3416
  * API with `peppol.identity.get()`, which works with any key. This surface is
2254
3417
  * for platforms that onboard their customers as sub-tenants.
2255
3418
  *
2256
- * **Getting access:** platform mode is enabled by getpeppr on your account —
2257
- * email hello@getpeppr.dev to have it switched on. Once it is, you create the
2258
- * master key yourself at https://console.getpeppr.dev/api-keys.
3419
+ * **Getting access:** in the sandbox, an organisation admin starts the
3420
+ * platform sandbox trial from the console overview (or chooses "A platform
3421
+ * for my customers" at signup), then creates the sandbox master key at
3422
+ * https://console.getpeppr.dev/api-keys. Production platform access is set up
3423
+ * with our team — email hello@getpeppr.dev to request it.
2259
3424
  *
2260
3425
  * @see https://getpeppr.dev/docs/platform/legal-entities/
2261
3426
  */
@@ -2276,8 +3441,9 @@ var Peppol = class {
2276
3441
  this.legalEntities = new LegalEntityOperations(this.adapter);
2277
3442
  }
2278
3443
  /**
2279
- * Validate an invoice without sending it.
2280
- * Useful for pre-flight checks in your UI.
3444
+ * Validate the structured JSON send payload without sending it.
3445
+ * Useful for pre-flight checks in your UI; provider-side normalization still
3446
+ * applies on send. `toXml()` adds the stricter direct-UBL builder checks.
2281
3447
  */
2282
3448
  validate(input) {
2283
3449
  return validateInvoice(input);
@@ -2287,14 +3453,39 @@ var Peppol = class {
2287
3453
  * Useful for debugging or manual submission.
2288
3454
  */
2289
3455
  toXml(input) {
2290
- const validation = validateInvoice(input);
3456
+ const baseValidation = validateInvoice(input);
3457
+ const vatViolations = baseValidation.valid ? validateUblBuilderVat(input) : [];
3458
+ const validation = {
3459
+ valid: baseValidation.valid && vatViolations.every((item) => item.severity !== "error"),
3460
+ errors: [
3461
+ ...baseValidation.errors,
3462
+ ...vatViolations.filter((item) => item.severity === "error").map(({ field, message, ruleId }) => ({
3463
+ field: field ?? "invoice",
3464
+ message,
3465
+ ruleId: ruleId === "SDK-INPUT" ? void 0 : ruleId
3466
+ }))
3467
+ ],
3468
+ warnings: baseValidation.warnings
3469
+ };
2291
3470
  if (!validation.valid) {
2292
3471
  throw new PeppolValidationError(`Invoice validation failed: ${validation.errors.map((e) => e.message).join("; ")}`, validation);
2293
3472
  }
2294
- if (input.isCreditNote) {
2295
- return buildCreditNoteXml(input);
3473
+ try {
3474
+ if (input.isCreditNote) {
3475
+ return buildCreditNoteXml(input);
3476
+ }
3477
+ return buildInvoiceXml(input);
3478
+ } catch (error2) {
3479
+ if (error2 instanceof UblBuilderInputError) {
3480
+ const builderValidation = {
3481
+ valid: false,
3482
+ errors: [{ field: error2.field, message: error2.message, ruleId: error2.ruleId }],
3483
+ warnings: validation.warnings
3484
+ };
3485
+ throw new PeppolValidationError(`Invoice validation failed: ${error2.message}`, builderValidation);
3486
+ }
3487
+ throw error2;
2296
3488
  }
2297
- return buildInvoiceXml(input);
2298
3489
  }
2299
3490
  };
2300
3491
  async function* paginate(fetchPage, options) {
@@ -2312,6 +3503,18 @@ async function* paginate(fetchPage, options) {
2312
3503
  offset += page.data.length;
2313
3504
  }
2314
3505
  }
3506
+ function toGatewayInvoiceInput(input) {
3507
+ const stripReason = (item) => {
3508
+ const { taxExemptReason: _builderOnly, ...gatewayItem } = item;
3509
+ return gatewayItem;
3510
+ };
3511
+ return {
3512
+ ...input,
3513
+ lines: input.lines.map(stripReason),
3514
+ ...input.allowances ? { allowances: input.allowances.map(stripReason) } : {},
3515
+ ...input.charges ? { charges: input.charges.map(stripReason) } : {}
3516
+ };
3517
+ }
2315
3518
  var InvoiceOperations = class {
2316
3519
  adapter;
2317
3520
  constructor(adapter) {
@@ -2335,7 +3538,7 @@ var InvoiceOperations = class {
2335
3538
  throw new PeppolValidationError(`Invoice validation failed:
2336
3539
  ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n")}`, validation);
2337
3540
  }
2338
- const result = await this.adapter.createInvoice(input, options);
3541
+ const result = await this.adapter.createInvoice(toGatewayInvoiceInput(input), options);
2339
3542
  if (validation.warnings.length > 0) {
2340
3543
  result.warnings = validation.warnings;
2341
3544
  }
@@ -2375,7 +3578,7 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
2375
3578
  throw new PeppolValidationError(`Invoice validation failed:
2376
3579
  ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n")}`, validation);
2377
3580
  }
2378
- const result = await this.adapter.sendInvoice(input, options);
3581
+ const result = await this.adapter.sendInvoice(toGatewayInvoiceInput(input), options);
2379
3582
  if (validation.warnings.length > 0) {
2380
3583
  result.warnings = validation.warnings;
2381
3584
  }
@@ -2684,7 +3887,7 @@ var CreditNoteOperations = class {
2684
3887
  throw new PeppolValidationError(`Credit note validation failed:
2685
3888
  ${validation.errors.map((e) => ` - ${e.field}: ${e.message}`).join("\n")}`, validation);
2686
3889
  }
2687
- return this.adapter.sendInvoice(invoiceInput);
3890
+ return this.adapter.sendInvoice(toGatewayInvoiceInput(invoiceInput));
2688
3891
  }
2689
3892
  };
2690
3893
  var DirectoryOperations = class {
@@ -2702,12 +3905,10 @@ var DirectoryOperations = class {
2702
3905
  * ```
2703
3906
  */
2704
3907
  async lookup(peppolId) {
2705
- const colonIndex = peppolId.indexOf(":");
2706
- if (colonIndex === -1) {
3908
+ if (!peppolId.includes(":")) {
2707
3909
  throw new PeppolError('Invalid Peppol ID format. Expected "scheme:id" (e.g., "0208:0685660237")');
2708
3910
  }
2709
- const scheme = peppolId.slice(0, colonIndex);
2710
- const id = peppolId.slice(colonIndex + 1);
3911
+ const { scheme, id } = parsePeppolId(peppolId);
2711
3912
  return this.adapter.lookupDirectory(scheme, id);
2712
3913
  }
2713
3914
  /**
@@ -2907,9 +4108,11 @@ var LegalEntityOperations = class {
2907
4108
  /**
2908
4109
  * Create a sub-tenant Legal Entity for one of your customers.
2909
4110
  *
2910
- * **Platform accounts only — requires a master API key.** Platform mode is
2911
- * enabled by getpeppr: email hello@getpeppr.dev, then create the key at
2912
- * https://console.getpeppr.dev/api-keys.
4111
+ * **Platform accounts only — requires a master API key.** In the sandbox,
4112
+ * an organisation admin starts the platform sandbox trial from the console
4113
+ * overview and creates a sandbox master key at
4114
+ * https://console.getpeppr.dev/api-keys; production platform access is set
4115
+ * up with our team (hello@getpeppr.dev).
2913
4116
  *
2914
4117
  * Your own company's legal entity is managed in the console, on the Peppol
2915
4118
  * identity page (and read from the API with `peppol.identity.get()`); this
@@ -2936,9 +4139,11 @@ var LegalEntityOperations = class {
2936
4139
  /**
2937
4140
  * Fetch a single sub-tenant Legal Entity by id.
2938
4141
  *
2939
- * **Platform accounts only — requires a master API key.** Platform mode is
2940
- * enabled by getpeppr: email hello@getpeppr.dev, then create the key at
2941
- * https://console.getpeppr.dev/api-keys.
4142
+ * **Platform accounts only — requires a master API key.** In the sandbox,
4143
+ * an organisation admin starts the platform sandbox trial from the console
4144
+ * overview and creates a sandbox master key at
4145
+ * https://console.getpeppr.dev/api-keys; production platform access is set
4146
+ * up with our team (hello@getpeppr.dev).
2942
4147
  *
2943
4148
  * For production entities the `status` reflects the attestation lifecycle
2944
4149
  * (awaiting_authz → attested → active).
@@ -2949,9 +4154,11 @@ var LegalEntityOperations = class {
2949
4154
  /**
2950
4155
  * List your sub-tenant Legal Entities, newest first.
2951
4156
  *
2952
- * **Platform accounts only — requires a master API key.** Platform mode is
2953
- * enabled by getpeppr: email hello@getpeppr.dev, then create the key at
2954
- * https://console.getpeppr.dev/api-keys.
4157
+ * **Platform accounts only — requires a master API key.** In the sandbox,
4158
+ * an organisation admin starts the platform sandbox trial from the console
4159
+ * overview and creates a sandbox master key at
4160
+ * https://console.getpeppr.dev/api-keys; production platform access is set
4161
+ * up with our team (hello@getpeppr.dev).
2955
4162
  *
2956
4163
  * This lists the customers you have onboarded, never your own legal entity.
2957
4164
  */
@@ -2961,9 +4168,11 @@ var LegalEntityOperations = class {
2961
4168
  /**
2962
4169
  * Async iterator over all sub-tenant Legal Entities, handling pagination.
2963
4170
  *
2964
- * **Platform accounts only — requires a master API key.** Platform mode is
2965
- * enabled by getpeppr: email hello@getpeppr.dev, then create the key at
2966
- * https://console.getpeppr.dev/api-keys.
4171
+ * **Platform accounts only — requires a master API key.** In the sandbox,
4172
+ * an organisation admin starts the platform sandbox trial from the console
4173
+ * overview and creates a sandbox master key at
4174
+ * https://console.getpeppr.dev/api-keys; production platform access is set
4175
+ * up with our team (hello@getpeppr.dev).
2967
4176
  *
2968
4177
  * @example
2969
4178
  * ```ts
@@ -2977,9 +4186,11 @@ var LegalEntityOperations = class {
2977
4186
  * Archive (soft-delete) a sub-tenant Legal Entity. The id stays resolvable
2978
4187
  * for audit.
2979
4188
  *
2980
- * **Platform accounts only — requires a master API key.** Platform mode is
2981
- * enabled by getpeppr: email hello@getpeppr.dev, then create the key at
2982
- * https://console.getpeppr.dev/api-keys.
4189
+ * **Platform accounts only — requires a master API key.** In the sandbox,
4190
+ * an organisation admin starts the platform sandbox trial from the console
4191
+ * overview and creates a sandbox master key at
4192
+ * https://console.getpeppr.dev/api-keys; production platform access is set
4193
+ * up with our team (hello@getpeppr.dev).
2983
4194
  */
2984
4195
  async archive(id) {
2985
4196
  return this.adapter.archiveLegalEntity(id);
@@ -2988,525 +4199,183 @@ var LegalEntityOperations = class {
2988
4199
  * Request a sub-tenant attestation (production only). Emails the co-branded
2989
4200
  * confirmation link to the sub-tenant contact and returns the pending status.
2990
4201
  *
2991
- * **Platform accounts only — requires a master API key.** Platform mode is
2992
- * enabled by getpeppr: email hello@getpeppr.dev, then create the key at
2993
- * https://console.getpeppr.dev/api-keys.
2994
- *
2995
- * Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`;
2996
- * re-issuing mints a fresh token, so a retried call is safe.
2997
- *
2998
- * @example
2999
- * ```ts
3000
- * await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
3001
- * ```
3002
- */
3003
- async requestAttestation(id, input, options) {
3004
- return this.adapter.requestLegalEntityAttestation(id, input, options);
3005
- }
3006
- };
3007
- var BankAccountOperations = class {
3008
- adapter;
3009
- constructor(adapter) {
3010
- this.adapter = adapter;
3011
- }
3012
- /**
3013
- * List bank accounts with optional pagination.
3014
- *
3015
- * @example
3016
- * ```ts
3017
- * const result = await peppol.bankAccounts.list({ limit: 10 });
3018
- * console.log(result.data, result.meta);
3019
- * ```
3020
- */
3021
- async list(options) {
3022
- return this.adapter.listBankAccounts(options);
3023
- }
3024
- /**
3025
- * Get a single bank account by ID.
3026
- *
3027
- * @example
3028
- * ```ts
3029
- * const account = await peppol.bankAccounts.get("123");
3030
- * console.log(account.name, account.iban);
3031
- * ```
3032
- */
3033
- async get(id) {
3034
- return this.adapter.getBankAccount(id);
3035
- }
3036
- /**
3037
- * Create a new bank account.
3038
- *
3039
- * @example
3040
- * ```ts
3041
- * const account = await peppol.bankAccounts.create({
3042
- * name: "Main Account",
3043
- * iban: "BE68539007547034",
3044
- * bic: "BBRUBEBB",
3045
- * country: "BE",
3046
- * });
3047
- * ```
3048
- */
3049
- async create(input) {
3050
- return this.adapter.createBankAccount(input);
3051
- }
3052
- /**
3053
- * Update an existing bank account.
3054
- *
3055
- * @example
3056
- * ```ts
3057
- * const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" });
3058
- * ```
3059
- */
3060
- async update(id, input) {
3061
- return this.adapter.updateBankAccount(id, input);
3062
- }
3063
- /**
3064
- * Delete a bank account.
3065
- *
3066
- * @example
3067
- * ```ts
3068
- * await peppol.bankAccounts.delete("123");
3069
- * ```
3070
- */
3071
- async delete(id) {
3072
- return this.adapter.deleteBankAccount(id);
3073
- }
3074
- /**
3075
- * Async iterator over all bank accounts, automatically handling pagination.
4202
+ * **Platform accounts only — requires a master API key.** In the sandbox,
4203
+ * an organisation admin starts the platform sandbox trial from the console
4204
+ * overview and creates a sandbox master key at
4205
+ * https://console.getpeppr.dev/api-keys; production platform access is set
4206
+ * up with our team (hello@getpeppr.dev).
4207
+ *
4208
+ * Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`;
4209
+ * re-issuing mints a fresh token, so a retried call is safe.
3076
4210
  *
3077
4211
  * @example
3078
4212
  * ```ts
3079
- * for await (const account of peppol.bankAccounts.listAll()) {
3080
- * console.log(account.name, account.iban);
3081
- * }
4213
+ * await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
3082
4214
  * ```
3083
4215
  */
3084
- listAll(options) {
3085
- return paginate((offset, limit) => this.adapter.listBankAccounts({ ...options, offset, limit }), options);
4216
+ async requestAttestation(id, input, options) {
4217
+ return this.adapter.requestLegalEntityAttestation(id, input, options);
3086
4218
  }
3087
4219
  };
3088
- var TransportOperations = class {
4220
+ var BankAccountOperations = class {
3089
4221
  adapter;
3090
4222
  constructor(adapter) {
3091
4223
  this.adapter = adapter;
3092
4224
  }
3093
4225
  /**
3094
- * List all available transport types in the network.
3095
- * Returns global transport types (not account-scoped).
3096
- *
3097
- * @example
3098
- * ```ts
3099
- * const types = await peppol.transports.listTypes();
3100
- * console.log(types); // [{ code: "peppol", name: "Peppol BIS 3.0" }, ...]
3101
- * ```
3102
- */
3103
- async listTypes() {
3104
- return this.adapter.listTransportTypes();
3105
- }
3106
- /**
3107
- * List configured transports for this account.
4226
+ * List bank accounts with optional pagination.
3108
4227
  *
3109
4228
  * @example
3110
4229
  * ```ts
3111
- * const transports = await peppol.transports.list();
3112
- * console.log(transports); // [{ id: "t-1", transportTypeCode: "peppol", name: "..." }, ...]
4230
+ * const result = await peppol.bankAccounts.list({ limit: 10 });
4231
+ * console.log(result.data, result.meta);
3113
4232
  * ```
3114
4233
  */
3115
- async list() {
3116
- return this.adapter.listTransports();
4234
+ async list(options) {
4235
+ return this.adapter.listBankAccounts(options);
3117
4236
  }
3118
4237
  /**
3119
- * Get a single transport by code.
4238
+ * Get a single bank account by ID.
3120
4239
  *
3121
4240
  * @example
3122
4241
  * ```ts
3123
- * const transport = await peppol.transports.get("peppol");
4242
+ * const account = await peppol.bankAccounts.get("123");
4243
+ * console.log(account.name, account.iban);
3124
4244
  * ```
3125
4245
  */
3126
- async get(code) {
3127
- return this.adapter.getTransport(code);
4246
+ async get(id) {
4247
+ return this.adapter.getBankAccount(id);
3128
4248
  }
3129
4249
  /**
3130
- * Create a new transport.
4250
+ * Create a new bank account.
3131
4251
  *
3132
4252
  * @example
3133
4253
  * ```ts
3134
- * const transport = await peppol.transports.create({
3135
- * transportTypeCode: "peppol",
3136
- * email: "billing@acme.com",
4254
+ * const account = await peppol.bankAccounts.create({
4255
+ * name: "Main Account",
4256
+ * iban: "BE68539007547034",
4257
+ * bic: "BBRUBEBB",
4258
+ * country: "BE",
3137
4259
  * });
3138
4260
  * ```
3139
4261
  */
3140
4262
  async create(input) {
3141
- return this.adapter.createTransport(input);
3142
- }
3143
- /**
3144
- * Update an existing transport.
3145
- *
3146
- * @example
3147
- * ```ts
3148
- * const transport = await peppol.transports.update("peppol", { email: "new@acme.com" });
3149
- * ```
3150
- */
3151
- async update(code, input) {
3152
- return this.adapter.updateTransport(code, input);
4263
+ return this.adapter.createBankAccount(input);
3153
4264
  }
3154
4265
  /**
3155
- * Delete a transport.
4266
+ * Update an existing bank account.
3156
4267
  *
3157
4268
  * @example
3158
4269
  * ```ts
3159
- * await peppol.transports.delete("peppol");
3160
- * ```
3161
- */
3162
- async delete(code) {
3163
- return this.adapter.deleteTransport(code);
3164
- }
3165
- };
3166
-
3167
- // ../sdk/dist/core/schematron.js
3168
- function violation(ruleId, severity, message, field) {
3169
- return { ruleId, severity, message, field };
3170
- }
3171
- var _knownUnitCodes;
3172
- function getKnownUnitCodes() {
3173
- if (!_knownUnitCodes) {
3174
- _knownUnitCodes = new Set(getAllUnits().map((u) => u.code));
3175
- }
3176
- return _knownUnitCodes;
3177
- }
3178
- var VALID_VAT_CATEGORIES = /* @__PURE__ */ new Set(["S", "Z", "E", "AE", "K", "G", "O", "L", "M", "B"]);
3179
- var SENDABLE_VAT_CATEGORIES = ["S", "Z", "E", "AE", "K", "G", "O"];
3180
- var UNROUTABLE_VAT_CATEGORIES = /* @__PURE__ */ new Set(["L", "M", "B"]);
3181
- function computeLineNet(line) {
3182
- const baseQty = line.baseQuantity ?? 1;
3183
- if (baseQty === 0)
3184
- return NaN;
3185
- const baseAmount = line.quantity * line.unitPrice / baseQty;
3186
- const chargeTotal = (line.charges ?? []).reduce((sum, c) => sum + c.amount, 0);
3187
- const allowanceTotal = (line.allowances ?? []).reduce((sum, a) => sum + a.amount, 0);
3188
- return baseAmount + chargeTotal - allowanceTotal;
3189
- }
3190
- var br02 = (input) => {
3191
- if (!input.number?.trim()) {
3192
- return [violation("BR-02", "error", "Invoice number is required.", "number")];
3193
- }
3194
- return [];
3195
- };
3196
- var br03 = (input) => {
3197
- if (!input.date) {
3198
- return [
3199
- violation("BR-03", "warning", "Invoice issue date is not set. The SDK will default to today's date.", "date")
3200
- ];
3201
- }
3202
- return [];
3203
- };
3204
- var br06 = (input) => {
3205
- if (input.from && !input.from.vatNumber) {
3206
- return [
3207
- violation("BR-06", "warning", "Seller party has no VAT number. The gateway will use the account's VAT registration.", "from.vatNumber")
3208
- ];
3209
- }
3210
- return [];
3211
- };
3212
- var br07 = (input) => {
3213
- if (!input.to?.name?.trim()) {
3214
- return [violation("BR-07", "error", "Buyer name is required.", "to.name")];
3215
- }
3216
- return [];
3217
- };
3218
- var br08 = (input) => {
3219
- if (!input.lines || input.lines.length === 0) {
3220
- return [violation("BR-08", "error", "Invoice must have at least one line item.", "lines")];
3221
- }
3222
- return [];
3223
- };
3224
- var br09 = (input) => {
3225
- if (!input.dueDate && !input.paymentTerms) {
3226
- return [
3227
- violation("BR-09", "warning", "Neither due date nor payment terms specified. At least one is recommended.", "dueDate")
3228
- ];
3229
- }
3230
- return [];
3231
- };
3232
- var br10 = (input) => {
3233
- if (!input.buyerReference && !input.orderReference) {
3234
- return [
3235
- violation("BR-10", "warning", "Neither buyerReference nor orderReference specified. Peppol BIS 3.0 requires at least one.", "buyerReference")
3236
- ];
3237
- }
3238
- return [];
3239
- };
3240
- var brCo10 = (input) => {
3241
- const violations = [];
3242
- if (!input.lines)
3243
- return violations;
3244
- for (let i = 0; i < input.lines.length; i++) {
3245
- const line = input.lines[i];
3246
- const net = computeLineNet(line);
3247
- if (!Number.isFinite(net)) {
3248
- const baseQty = line.baseQuantity ?? 1;
3249
- const detail = baseQty === 0 ? "baseQuantity is 0, causing division by zero." : "Computed line amount is not a finite number.";
3250
- violations.push(violation("BR-CO-10", "error", `Line ${i}: invalid net amount. ${detail}`, `lines[${i}]`));
3251
- }
3252
- }
3253
- return violations;
3254
- };
3255
- var brCo13 = (input) => {
3256
- if (!input.lines || input.lines.length === 0)
3257
- return [];
3258
- let totalVat = 0;
3259
- for (let i = 0; i < input.lines.length; i++) {
3260
- const line = input.lines[i];
3261
- const net = computeLineNet(line);
3262
- if (!Number.isFinite(net))
3263
- continue;
3264
- totalVat += net * (line.vatRate / 100);
3265
- }
3266
- for (const allowance of input.allowances ?? []) {
3267
- totalVat -= allowance.amount * (allowance.vatRate / 100);
3268
- }
3269
- for (const charge of input.charges ?? []) {
3270
- totalVat += charge.amount * (charge.vatRate / 100);
3271
- }
3272
- if (!Number.isFinite(totalVat)) {
3273
- return [
3274
- violation("BR-CO-13", "error", "Computed total VAT amount is not a finite number. Check line amounts and VAT rates.")
3275
- ];
3276
- }
3277
- if (totalVat < -0.01) {
3278
- return [
3279
- violation("BR-CO-13", "warning", `Computed total VAT is negative (${totalVat.toFixed(2)}). This is unusual for an invoice.`)
3280
- ];
3281
- }
3282
- return [];
3283
- };
3284
- var brCo15 = (input) => {
3285
- if (!input.lines || input.lines.length === 0)
3286
- return [];
3287
- let lineTotal = 0;
3288
- let vatTotal = 0;
3289
- for (const line of input.lines) {
3290
- const net = computeLineNet(line);
3291
- if (!Number.isFinite(net))
3292
- continue;
3293
- lineTotal += net;
3294
- vatTotal += net * (line.vatRate / 100);
3295
- }
3296
- for (const allowance of input.allowances ?? []) {
3297
- lineTotal -= allowance.amount;
3298
- vatTotal -= allowance.amount * (allowance.vatRate / 100);
3299
- }
3300
- for (const charge of input.charges ?? []) {
3301
- lineTotal += charge.amount;
3302
- vatTotal += charge.amount * (charge.vatRate / 100);
3303
- }
3304
- const taxInclusive = lineTotal + vatTotal;
3305
- if (!Number.isFinite(taxInclusive)) {
3306
- return [
3307
- violation("BR-CO-15", "error", "Computed tax-inclusive amount is not a finite number.")
3308
- ];
3309
- }
3310
- if (taxInclusive < -0.01) {
3311
- return [
3312
- violation("BR-CO-15", "warning", `Computed tax-inclusive amount is negative (${taxInclusive.toFixed(2)}). Consider using a credit note instead.`)
3313
- ];
3314
- }
3315
- return [];
3316
- };
3317
- var brCo16 = (input) => {
3318
- if (!input.lines || input.lines.length === 0)
3319
- return [];
3320
- let lineTotal = 0;
3321
- let vatTotal = 0;
3322
- for (const line of input.lines) {
3323
- const net = computeLineNet(line);
3324
- if (!Number.isFinite(net))
3325
- continue;
3326
- lineTotal += net;
3327
- vatTotal += net * (line.vatRate / 100);
3328
- }
3329
- for (const allowance of input.allowances ?? []) {
3330
- lineTotal -= allowance.amount;
3331
- vatTotal -= allowance.amount * (allowance.vatRate / 100);
3332
- }
3333
- for (const charge of input.charges ?? []) {
3334
- lineTotal += charge.amount;
3335
- vatTotal += charge.amount * (charge.vatRate / 100);
3336
- }
3337
- const taxInclusive = lineTotal + vatTotal;
3338
- const prepaid = input.prepaidAmount ?? 0;
3339
- const rounding = input.roundingAmount ?? 0;
3340
- const payable = taxInclusive - prepaid + rounding;
3341
- if (!Number.isFinite(payable)) {
3342
- return [
3343
- violation("BR-CO-16", "error", "Computed payable amount is not a finite number.")
3344
- ];
4270
+ * const updated = await peppol.bankAccounts.update("123", { name: "Updated Name" });
4271
+ * ```
4272
+ */
4273
+ async update(id, input) {
4274
+ return this.adapter.updateBankAccount(id, input);
3345
4275
  }
3346
- if (payable < -0.01) {
3347
- return [
3348
- violation("BR-CO-16", "warning", `Computed payable amount is negative (${payable.toFixed(2)}). Prepaid amount (${prepaid}) exceeds the invoice total.`)
3349
- ];
4276
+ /**
4277
+ * Delete a bank account.
4278
+ *
4279
+ * @example
4280
+ * ```ts
4281
+ * await peppol.bankAccounts.delete("123");
4282
+ * ```
4283
+ */
4284
+ async delete(id) {
4285
+ return this.adapter.deleteBankAccount(id);
3350
4286
  }
3351
- return [];
3352
- };
3353
- var brS05 = (input) => {
3354
- const violations = [];
3355
- if (!input.lines)
3356
- return violations;
3357
- for (let i = 0; i < input.lines.length; i++) {
3358
- const line = input.lines[i];
3359
- const category = line.vatCategory ?? "S";
3360
- if (category === "S" && (line.vatRate === void 0 || line.vatRate <= 0)) {
3361
- violations.push(violation("BR-S-05", "error", `Line ${i}: standard rate (S) requires vatRate > 0, got ${line.vatRate ?? "undefined"}.`, `lines[${i}].vatRate`));
3362
- }
4287
+ /**
4288
+ * Async iterator over all bank accounts, automatically handling pagination.
4289
+ *
4290
+ * @example
4291
+ * ```ts
4292
+ * for await (const account of peppol.bankAccounts.listAll()) {
4293
+ * console.log(account.name, account.iban);
4294
+ * }
4295
+ * ```
4296
+ */
4297
+ listAll(options) {
4298
+ return paginate((offset, limit) => this.adapter.listBankAccounts({ ...options, offset, limit }), options);
3363
4299
  }
3364
- return violations;
3365
4300
  };
3366
- var brZ05 = (input) => {
3367
- const violations = [];
3368
- if (!input.lines)
3369
- return violations;
3370
- for (let i = 0; i < input.lines.length; i++) {
3371
- const line = input.lines[i];
3372
- if (line.vatCategory === "Z" && line.vatRate !== 0) {
3373
- violations.push(violation("BR-Z-05", "error", `Line ${i}: zero-rated (Z) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
3374
- }
4301
+ var TransportOperations = class {
4302
+ adapter;
4303
+ constructor(adapter) {
4304
+ this.adapter = adapter;
3375
4305
  }
3376
- return violations;
3377
- };
3378
- var brE05 = (input) => {
3379
- const violations = [];
3380
- if (!input.lines)
3381
- return violations;
3382
- for (let i = 0; i < input.lines.length; i++) {
3383
- const line = input.lines[i];
3384
- if (line.vatCategory === "E" && line.vatRate !== 0) {
3385
- violations.push(violation("BR-E-05", "error", `Line ${i}: exempt (E) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
3386
- }
4306
+ /**
4307
+ * List all available transport types in the network.
4308
+ * Returns global transport types (not account-scoped).
4309
+ *
4310
+ * @example
4311
+ * ```ts
4312
+ * const types = await peppol.transports.listTypes();
4313
+ * console.log(types); // [{ code: "peppol", name: "Peppol BIS 3.0" }, ...]
4314
+ * ```
4315
+ */
4316
+ async listTypes() {
4317
+ return this.adapter.listTransportTypes();
3387
4318
  }
3388
- return violations;
3389
- };
3390
- var brAe05 = (input) => {
3391
- const violations = [];
3392
- if (!input.lines)
3393
- return violations;
3394
- for (let i = 0; i < input.lines.length; i++) {
3395
- const line = input.lines[i];
3396
- if (line.vatCategory === "AE" && line.vatRate !== 0) {
3397
- violations.push(violation("BR-AE-05", "error", `Line ${i}: reverse charge (AE) requires vatRate = 0, got ${line.vatRate}.`, `lines[${i}].vatRate`));
3398
- }
4319
+ /**
4320
+ * List configured transports for this account.
4321
+ *
4322
+ * @example
4323
+ * ```ts
4324
+ * const transports = await peppol.transports.list();
4325
+ * console.log(transports); // [{ id: "t-1", transportTypeCode: "peppol", name: "..." }, ...]
4326
+ * ```
4327
+ */
4328
+ async list() {
4329
+ return this.adapter.listTransports();
3399
4330
  }
3400
- return violations;
3401
- };
3402
- var peppolR004 = (input) => {
3403
- if (!input.to?.peppolId) {
3404
- return [
3405
- violation("PEPPOL-EN16931-R004", "error", "Buyer electronic address (peppolId) is required for Peppol delivery.", "to.peppolId")
3406
- ];
4331
+ /**
4332
+ * Get a single transport by code.
4333
+ *
4334
+ * @example
4335
+ * ```ts
4336
+ * const transport = await peppol.transports.get("peppol");
4337
+ * ```
4338
+ */
4339
+ async get(code) {
4340
+ return this.adapter.getTransport(code);
3407
4341
  }
3408
- return [];
3409
- };
3410
- var vatCategoryCodes = (input) => {
3411
- const violations = [];
3412
- const sendable = SENDABLE_VAT_CATEGORIES.join(", ");
3413
- const echo = (v) => v.length <= 16 ? v : `${v.slice(0, 16)}\u2026`;
3414
- const check = (cat, field, label) => {
3415
- if (cat === void 0 || cat === null)
3416
- return;
3417
- if (!VALID_VAT_CATEGORIES.has(cat)) {
3418
- violations.push(violation("BR-CL-17", "error", `${label}: "${echo(cat)}" is not a VAT category code. Use one of: ${sendable}. Codes are case-sensitive \u2014 "AE" is reverse charge, "ae" is not a category.`, field));
3419
- return;
3420
- }
3421
- if (UNROUTABLE_VAT_CATEGORIES.has(cat)) {
3422
- violations.push(violation("unsupported_vat_category", "error", `${label}: VAT category "${echo(cat)}" is valid under EN 16931 but getpeppr cannot route it \u2014 our provider has no vocabulary for it. Sendable categories: ${sendable}.`, field));
3423
- }
3424
- };
3425
- (input.lines ?? []).forEach((line, i) => check(line.vatCategory, `lines[${i}].vatCategory`, `Line ${i}`));
3426
- (input.allowances ?? []).forEach((a, i) => check(a.vatCategory, `allowances[${i}].vatCategory`, `Allowance ${i}`));
3427
- (input.charges ?? []).forEach((c, i) => check(c.vatCategory, `charges[${i}].vatCategory`, `Charge ${i}`));
3428
- return violations;
3429
- };
3430
- var peppolR080 = (input) => {
3431
- const violations = [];
3432
- if (!input.lines)
3433
- return violations;
3434
- const knownCodes = getKnownUnitCodes();
3435
- for (let i = 0; i < input.lines.length; i++) {
3436
- const line = input.lines[i];
3437
- if (line.unit) {
3438
- const resolved = resolveUnit(line.unit);
3439
- if (!knownCodes.has(resolved)) {
3440
- violations.push(violation("PEPPOL-EN16931-R080", "warning", `Line ${i}: unit "${line.unit}" (resolved: "${resolved}") is not a known UN/ECE Rec20 code. Common codes: EA, HUR, DAY, KGM.`, `lines[${i}].unit`));
3441
- }
3442
- }
4342
+ /**
4343
+ * Create a new transport.
4344
+ *
4345
+ * @example
4346
+ * ```ts
4347
+ * const transport = await peppol.transports.create({
4348
+ * transportTypeCode: "peppol",
4349
+ * email: "billing@acme.com",
4350
+ * });
4351
+ * ```
4352
+ */
4353
+ async create(input) {
4354
+ return this.adapter.createTransport(input);
3443
4355
  }
3444
- return violations;
3445
- };
3446
- var ALL_RULES = [
3447
- // Required fields (BR)
3448
- br02,
3449
- br03,
3450
- br06,
3451
- br07,
3452
- br08,
3453
- br09,
3454
- br10,
3455
- // Calculations (BR-CO)
3456
- brCo10,
3457
- brCo13,
3458
- brCo15,
3459
- brCo16,
3460
- // Tax categories (one family per category)
3461
- brS05,
3462
- brZ05,
3463
- brE05,
3464
- brAe05,
3465
- // Peppol-specific
3466
- peppolR004,
3467
- vatCategoryCodes,
3468
- peppolR080
3469
- ];
3470
- function validateSchematron(input) {
3471
- const errors = [];
3472
- const warnings = [];
3473
- for (const rule of ALL_RULES) {
3474
- const violations = rule(input);
3475
- for (const v of violations) {
3476
- if (v.severity === "error") {
3477
- errors.push(v);
3478
- } else {
3479
- warnings.push(v);
3480
- }
3481
- }
4356
+ /**
4357
+ * Update an existing transport.
4358
+ *
4359
+ * @example
4360
+ * ```ts
4361
+ * const transport = await peppol.transports.update("peppol", { email: "new@acme.com" });
4362
+ * ```
4363
+ */
4364
+ async update(code, input) {
4365
+ return this.adapter.updateTransport(code, input);
3482
4366
  }
3483
- return {
3484
- valid: errors.length === 0,
3485
- coverage: { rulesChecked: SDK_SCHEMATRON_RULE_IDS.length, ofNetworkFatalRules: "partial" },
3486
- errors,
3487
- warnings
3488
- };
3489
- }
3490
- var SDK_SCHEMATRON_RULE_IDS = [
3491
- "BR-02",
3492
- "BR-03",
3493
- "BR-06",
3494
- "BR-07",
3495
- "BR-08",
3496
- "BR-09",
3497
- "BR-10",
3498
- "BR-CL-17",
3499
- "BR-CO-10",
3500
- "BR-CO-13",
3501
- "BR-CO-15",
3502
- "BR-CO-16",
3503
- "BR-S-05",
3504
- "BR-Z-05",
3505
- "BR-E-05",
3506
- "BR-AE-05",
3507
- "PEPPOL-EN16931-R004",
3508
- "PEPPOL-EN16931-R080"
3509
- ];
4367
+ /**
4368
+ * Delete a transport.
4369
+ *
4370
+ * @example
4371
+ * ```ts
4372
+ * await peppol.transports.delete("peppol");
4373
+ * ```
4374
+ */
4375
+ async delete(code) {
4376
+ return this.adapter.deleteTransport(code);
4377
+ }
4378
+ };
3510
4379
 
3511
4380
  // src/commands/validate.ts
3512
4381
  function runValidation(input) {
@@ -3927,12 +4796,11 @@ function validatePeppolId(raw) {
3927
4796
  error: `Invalid Peppol ID format: "${raw}". Expected format: scheme:id (e.g. 0208:0685660237)`
3928
4797
  };
3929
4798
  }
3930
- const scheme = raw.slice(0, colonIndex);
3931
- const id = raw.slice(colonIndex + 1);
4799
+ const { scheme, id } = parsePeppolId(raw);
3932
4800
  if (!/^\d{4}$/.test(scheme)) {
3933
4801
  return {
3934
4802
  ok: false,
3935
- error: `Invalid scheme "${scheme}". Must be exactly 4 digits (e.g. 0208).`
4803
+ error: `Invalid scheme "${scheme}". Must be a 4-digit EAS code (e.g. 0208) or a published symbolic scheme (e.g. GB:VAT).`
3936
4804
  };
3937
4805
  }
3938
4806
  if (!/^[A-Za-z0-9:.\-]+$/.test(id)) {
@@ -4179,19 +5047,17 @@ startxref
4179
5047
  190
4180
5048
  %%EOF`;
4181
5049
  var MINIMAL_TEST_PDF_BASE64 = Buffer.from(MINIMAL_PDF).toString("base64");
4182
- var SCHEME_COUNTRY_DEFAULTS = {
4183
- "0009": "FR",
4184
- "0204": "DE",
4185
- "0208": "BE",
4186
- "9925": "BE"
4187
- };
4188
5050
  function deriveCountryFromPeppolId(peppolId) {
4189
- const [scheme, identifier = ""] = peppolId.split(":");
4190
- const alphaPrefix = identifier.slice(0, 2);
5051
+ const { scheme, id } = parsePeppolId(peppolId);
5052
+ const published = countryForScheme(scheme);
5053
+ if (published !== void 0) {
5054
+ return published;
5055
+ }
5056
+ const alphaPrefix = id.slice(0, 2);
4191
5057
  if (/^[A-Za-z]{2}$/.test(alphaPrefix)) {
4192
5058
  return alphaPrefix.toUpperCase();
4193
5059
  }
4194
- return SCHEME_COUNTRY_DEFAULTS[scheme] ?? "BE";
5060
+ return "BE";
4195
5061
  }
4196
5062
  function buildDefaultSendPayload(overrides = {}) {
4197
5063
  const today = /* @__PURE__ */ new Date();
@@ -4225,10 +5091,12 @@ function buildDefaultSendPayload(overrides = {}) {
4225
5091
  unitPrice: amount,
4226
5092
  vatRate: 0,
4227
5093
  // vatCategory "O" = "Services outside scope of tax" (UBL 2.1 / EN 16931).
4228
- // Public entities (SPF Economie) are VAT-exempt. No `taxExemptReason` field
4229
- // exists in InvoiceLine Storecove derives exemption from the category code.
5094
+ // Public entities (SPF Economie) are VAT-exempt. The SDK pre-validator
5095
+ // requires the builder-only reason; the send transport strips it so
5096
+ // Storecove can still derive its own provider-specific text.
4230
5097
  // If sandbox returns 422 on this combination, fallback is vatRate: 21 + vatCategory: "S".
4231
- vatCategory: "O"
5098
+ vatCategory: "O",
5099
+ taxExemptReason: "Not subject to VAT"
4232
5100
  }
4233
5101
  ]
4234
5102
  };