ksef-client-ts 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -320,21 +320,21 @@ var init_rest_request = __esm({
320
320
  _query = [];
321
321
  _presigned = false;
322
322
  _skipAuthRetry = false;
323
- constructor(method, path) {
323
+ constructor(method, path2) {
324
324
  this.method = method;
325
- this.path = path;
325
+ this.path = path2;
326
326
  }
327
- static get(path) {
328
- return new _RestRequest("GET", path);
327
+ static get(path2) {
328
+ return new _RestRequest("GET", path2);
329
329
  }
330
- static post(path) {
331
- return new _RestRequest("POST", path);
330
+ static post(path2) {
331
+ return new _RestRequest("POST", path2);
332
332
  }
333
- static put(path) {
334
- return new _RestRequest("PUT", path);
333
+ static put(path2) {
334
+ return new _RestRequest("PUT", path2);
335
335
  }
336
- static delete(path) {
337
- return new _RestRequest("DELETE", path);
336
+ static delete(path2) {
337
+ return new _RestRequest("DELETE", path2);
338
338
  }
339
339
  body(data) {
340
340
  this._body = data;
@@ -654,9 +654,9 @@ var init_rest_client = __esm({
654
654
  return response;
655
655
  }
656
656
  buildUrl(request) {
657
- const path = this.routeBuilder.build(request.path);
657
+ const path2 = this.routeBuilder.build(request.path);
658
658
  const base = this.options.baseUrl;
659
- const url = new URL(`${base}${path}`);
659
+ const url = new URL(`${base}${path2}`);
660
660
  const query = request.getQuery();
661
661
  for (const [key, value] of query) {
662
662
  url.searchParams.append(key, value);
@@ -2668,11 +2668,11 @@ async function validateSchema(xml, options, _parsed) {
2668
2668
  const prefix = rootElement ? `/${rootElement}/` : "/";
2669
2669
  const validationErrors = result.error.issues.map((issue) => {
2670
2670
  const zodPath = issue.path.join("/");
2671
- const path = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
2671
+ const path2 = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
2672
2672
  return {
2673
2673
  code: mapZodErrorCode(issue),
2674
2674
  message: issue.message,
2675
- path
2675
+ path: path2
2676
2676
  };
2677
2677
  });
2678
2678
  return { valid: false, schemaType, errors: validationErrors };
@@ -2712,9 +2712,9 @@ function validateBusinessRules(xml, _parsed) {
2712
2712
  collectDateErrors(object, rootElement, errors);
2713
2713
  return { valid: errors.length === 0, schemaType, errors };
2714
2714
  }
2715
- function collectNipPeselErrors(obj, path, errors) {
2715
+ function collectNipPeselErrors(obj, path2, errors) {
2716
2716
  for (const [key, value] of Object.entries(obj)) {
2717
- const currentPath = path ? `${path}/${key}` : key;
2717
+ const currentPath = path2 ? `${path2}/${key}` : key;
2718
2718
  if (key === "NIP" && typeof value === "string") {
2719
2719
  if (!isValidNip(value)) {
2720
2720
  errors.push({
@@ -2793,6 +2793,88 @@ var init_invoice_validator = __esm({
2793
2793
  }
2794
2794
  });
2795
2795
 
2796
+ // src/models/document-structures/types.ts
2797
+ var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
2798
+ var init_types = __esm({
2799
+ "src/models/document-structures/types.ts"() {
2800
+ "use strict";
2801
+ SystemCode = {
2802
+ FA_2: "FA (2)",
2803
+ FA_3: "FA (3)",
2804
+ PEF_3: "PEF (3)",
2805
+ PEF_KOR_3: "PEF_KOR (3)",
2806
+ FA_RR_1: "FA_RR (1)"
2807
+ };
2808
+ FORM_CODES = {
2809
+ FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
2810
+ FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
2811
+ PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
2812
+ PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
2813
+ FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
2814
+ FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
2815
+ FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
2816
+ };
2817
+ DEFAULT_FORM_CODE = FORM_CODES.FA_3;
2818
+ INVOICE_TYPES_BY_SYSTEM_CODE = {
2819
+ [SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
2820
+ [SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
2821
+ [SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
2822
+ [SystemCode.PEF_KOR_3]: ["KorPef"],
2823
+ [SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
2824
+ };
2825
+ FORM_CODE_KEYS = {
2826
+ FA2: FORM_CODES.FA_2,
2827
+ FA3: FORM_CODES.FA_3,
2828
+ PEF3: FORM_CODES.PEF_3,
2829
+ PEFKOR3: FORM_CODES.PEF_KOR_3,
2830
+ FARR1: FORM_CODES.FA_RR_1
2831
+ };
2832
+ }
2833
+ });
2834
+
2835
+ // src/models/document-structures/helpers.ts
2836
+ function getFormCode(systemCode) {
2837
+ return SYSTEM_CODE_TO_FORM_CODE[systemCode];
2838
+ }
2839
+ function parseFormCode(raw) {
2840
+ const match = ALL_FORM_CODES.find(
2841
+ (fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
2842
+ );
2843
+ return match ?? raw;
2844
+ }
2845
+ function validateFormCodeForSession(formCode, sessionType) {
2846
+ if (sessionType === "online") return true;
2847
+ return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
2848
+ }
2849
+ var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
2850
+ var init_helpers = __esm({
2851
+ "src/models/document-structures/helpers.ts"() {
2852
+ "use strict";
2853
+ init_types();
2854
+ SYSTEM_CODE_TO_FORM_CODE = {
2855
+ [SystemCode.FA_2]: FORM_CODES.FA_2,
2856
+ [SystemCode.FA_3]: FORM_CODES.FA_3,
2857
+ [SystemCode.PEF_3]: FORM_CODES.PEF_3,
2858
+ [SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
2859
+ [SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
2860
+ };
2861
+ ALL_FORM_CODES = Object.values(FORM_CODES);
2862
+ BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
2863
+ SystemCode.PEF_3,
2864
+ SystemCode.PEF_KOR_3
2865
+ ]);
2866
+ }
2867
+ });
2868
+
2869
+ // src/models/document-structures/index.ts
2870
+ var init_document_structures = __esm({
2871
+ "src/models/document-structures/index.ts"() {
2872
+ "use strict";
2873
+ init_types();
2874
+ init_helpers();
2875
+ }
2876
+ });
2877
+
2796
2878
  // src/services/auth.ts
2797
2879
  var AuthService;
2798
2880
  var init_auth = __esm({
@@ -3358,20 +3440,20 @@ var init_lighthouse = __esm({
3358
3440
  this.lighthouseUrl = options.lighthouseUrl;
3359
3441
  this.timeout = options.timeout;
3360
3442
  }
3361
- async fetchJson(path) {
3443
+ async fetchJson(path2) {
3362
3444
  if (!this.lighthouseUrl) {
3363
3445
  throw new KSeFError(
3364
3446
  "Lighthouse API is not available for the DEMO environment. Use TEST or PROD instead."
3365
3447
  );
3366
3448
  }
3367
- const response = await fetch(`${this.lighthouseUrl}${path}`, {
3449
+ const response = await fetch(`${this.lighthouseUrl}${path2}`, {
3368
3450
  headers: { Accept: "application/json" },
3369
3451
  signal: AbortSignal.timeout(this.timeout)
3370
3452
  });
3371
3453
  if (!response.ok) {
3372
3454
  const body = await response.text();
3373
3455
  throw new KSeFError(
3374
- `Lighthouse ${path} failed: HTTP ${response.status} \u2014 ${body}`
3456
+ `Lighthouse ${path2} failed: HTTP ${response.status} \u2014 ${body}`
3375
3457
  );
3376
3458
  }
3377
3459
  return await response.json();
@@ -4351,6 +4433,391 @@ var init_zip = __esm({
4351
4433
  }
4352
4434
  });
4353
4435
 
4436
+ // src/offline/deadline.ts
4437
+ function getDefaultReason(mode) {
4438
+ switch (mode) {
4439
+ case "offline24":
4440
+ return "PLANNED";
4441
+ case "offline":
4442
+ return "SYSTEM_UNAVAILABLE";
4443
+ case "awaryjny":
4444
+ return "EMERGENCY";
4445
+ case "awaria_calkowita":
4446
+ return "TOTAL_FAILURE";
4447
+ }
4448
+ }
4449
+ function nextBusinessDay(from) {
4450
+ const d = new Date(from);
4451
+ d.setUTCDate(d.getUTCDate() + 1);
4452
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
4453
+ d.setUTCDate(d.getUTCDate() + 1);
4454
+ }
4455
+ return d;
4456
+ }
4457
+ function addBusinessDays(from, days) {
4458
+ if (!Number.isInteger(days) || days < 0) {
4459
+ throw new Error(`days must be a non-negative integer, got ${days}`);
4460
+ }
4461
+ const d = new Date(from);
4462
+ let remaining = days;
4463
+ while (remaining > 0) {
4464
+ d.setUTCDate(d.getUTCDate() + 1);
4465
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
4466
+ remaining--;
4467
+ }
4468
+ }
4469
+ return d;
4470
+ }
4471
+ function endOfDay(d) {
4472
+ const result = new Date(d);
4473
+ result.setUTCHours(23, 59, 59, 999);
4474
+ return result;
4475
+ }
4476
+ function getMaintenanceEndFallback(mw) {
4477
+ if (mw.endTime) {
4478
+ return new Date(mw.endTime);
4479
+ }
4480
+ const start = new Date(mw.startTime);
4481
+ start.setUTCDate(start.getUTCDate() + 7);
4482
+ return start;
4483
+ }
4484
+ function calculateOfflineDeadline(mode, invoiceDate, maintenanceWindow) {
4485
+ const date = typeof invoiceDate === "string" ? new Date(invoiceDate) : invoiceDate;
4486
+ switch (mode) {
4487
+ case "offline24": {
4488
+ return endOfDay(nextBusinessDay(date));
4489
+ }
4490
+ case "offline": {
4491
+ const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
4492
+ return endOfDay(nextBusinessDay(base));
4493
+ }
4494
+ case "awaryjny": {
4495
+ const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
4496
+ return endOfDay(addBusinessDays(base, 7));
4497
+ }
4498
+ case "awaria_calkowita": {
4499
+ return new Date(FAR_FUTURE);
4500
+ }
4501
+ }
4502
+ }
4503
+ function extendDeadlineForMaintenance(currentDeadline, maintenanceWindow, mode = "awaryjny") {
4504
+ const current = typeof currentDeadline === "string" ? new Date(currentDeadline) : new Date(currentDeadline);
4505
+ const mwEnd = getMaintenanceEndFallback(maintenanceWindow);
4506
+ if (mwEnd.getTime() > current.getTime()) {
4507
+ switch (mode) {
4508
+ case "offline24":
4509
+ case "offline":
4510
+ return endOfDay(nextBusinessDay(mwEnd));
4511
+ case "awaryjny":
4512
+ return endOfDay(addBusinessDays(mwEnd, 7));
4513
+ case "awaria_calkowita":
4514
+ return new Date(FAR_FUTURE);
4515
+ }
4516
+ }
4517
+ return current;
4518
+ }
4519
+ function isExpired(submitBy) {
4520
+ const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
4521
+ return Date.now() > deadline.getTime();
4522
+ }
4523
+ function getTimeUntilDeadline(submitBy) {
4524
+ const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
4525
+ return Math.max(0, deadline.getTime() - Date.now());
4526
+ }
4527
+ var FAR_FUTURE;
4528
+ var init_deadline = __esm({
4529
+ "src/offline/deadline.ts"() {
4530
+ "use strict";
4531
+ FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
4532
+ }
4533
+ });
4534
+
4535
+ // src/workflows/offline-invoice-workflow.ts
4536
+ var import_node_crypto2, OfflineInvoiceWorkflow;
4537
+ var init_offline_invoice_workflow = __esm({
4538
+ "src/workflows/offline-invoice-workflow.ts"() {
4539
+ "use strict";
4540
+ import_node_crypto2 = __toESM(require("crypto"), 1);
4541
+ init_ksef_api_error();
4542
+ init_deadline();
4543
+ init_document_structures();
4544
+ OfflineInvoiceWorkflow = class {
4545
+ constructor(qrService) {
4546
+ this.qrService = qrService;
4547
+ }
4548
+ async generate(input, options) {
4549
+ if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
4550
+ throw new Error("invoiceXml must not be empty");
4551
+ }
4552
+ if (!input.invoiceNumber) {
4553
+ throw new Error("invoiceNumber is required");
4554
+ }
4555
+ if (!input.sellerNip) {
4556
+ throw new Error("sellerNip is required");
4557
+ }
4558
+ const mode = options?.mode ?? "offline24";
4559
+ const reason = getDefaultReason(mode);
4560
+ const invoiceHashBase64 = import_node_crypto2.default.createHash("sha256").update(input.invoiceXml).digest("base64");
4561
+ const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4562
+ input.sellerNip,
4563
+ input.invoiceDate,
4564
+ invoiceHashBase64
4565
+ );
4566
+ let kod2Url;
4567
+ if (options?.certificate) {
4568
+ kod2Url = this.qrService.buildCertificateVerificationUrl(
4569
+ input.sellerIdentifier.type,
4570
+ input.sellerIdentifier.value,
4571
+ input.sellerNip,
4572
+ options.certificate.certificateSerial,
4573
+ invoiceHashBase64,
4574
+ options.certificate.privateKeyPem
4575
+ );
4576
+ }
4577
+ const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
4578
+ const metadata = {
4579
+ id: import_node_crypto2.default.randomUUID(),
4580
+ mode,
4581
+ reason,
4582
+ status: "GENERATED",
4583
+ invoiceNumber: input.invoiceNumber,
4584
+ invoiceDate: input.invoiceDate,
4585
+ invoiceXml: input.invoiceXml,
4586
+ sellerNip: input.sellerNip,
4587
+ sellerIdentifier: input.sellerIdentifier,
4588
+ buyerIdentifier: input.buyerIdentifier,
4589
+ totalAmount: input.totalAmount,
4590
+ currency: input.currency,
4591
+ kod1Url,
4592
+ kod2Url,
4593
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4594
+ submitBy,
4595
+ maintenanceWindowId: options?.maintenanceWindow?.id
4596
+ };
4597
+ if (options?.storage) {
4598
+ await options.storage.save(metadata);
4599
+ }
4600
+ return metadata;
4601
+ }
4602
+ async submit(client, options) {
4603
+ const { storage, checkExpiry = true } = options;
4604
+ let invoices;
4605
+ if (options.invoiceIds) {
4606
+ invoices = [];
4607
+ const notFound = [];
4608
+ for (const id of options.invoiceIds) {
4609
+ const inv = await storage.get(id);
4610
+ if (inv) {
4611
+ invoices.push(inv);
4612
+ } else {
4613
+ notFound.push(id);
4614
+ }
4615
+ }
4616
+ if (notFound.length > 0) {
4617
+ throw new Error(`Offline invoice(s) not found: ${notFound.join(", ")}`);
4618
+ }
4619
+ } else {
4620
+ invoices = await storage.list({ status: ["GENERATED", "QUEUED", "SUBMITTED"] });
4621
+ }
4622
+ const result = {
4623
+ total: invoices.length,
4624
+ submitted: 0,
4625
+ accepted: 0,
4626
+ rejected: 0,
4627
+ failed: 0,
4628
+ expired: 0,
4629
+ results: []
4630
+ };
4631
+ if (invoices.length === 0) return result;
4632
+ const pending = [];
4633
+ for (const inv of invoices) {
4634
+ if (checkExpiry && isExpired(inv.submitBy)) {
4635
+ await storage.update(inv.id, { status: "EXPIRED" });
4636
+ result.expired++;
4637
+ result.results.push({
4638
+ invoiceId: inv.id,
4639
+ invoiceNumber: inv.invoiceNumber,
4640
+ status: "EXPIRED"
4641
+ });
4642
+ } else {
4643
+ pending.push(inv);
4644
+ }
4645
+ }
4646
+ if (pending.length === 0) return result;
4647
+ await client.crypto.init();
4648
+ const encData = client.crypto.getEncryptionData();
4649
+ const formCode = options.formCode ?? DEFAULT_FORM_CODE;
4650
+ const openResp = await client.onlineSession.openSession(
4651
+ { formCode, encryption: encData.encryptionInfo }
4652
+ );
4653
+ const sessionRef = openResp.referenceNumber;
4654
+ try {
4655
+ for (const inv of pending) {
4656
+ await storage.update(inv.id, { status: "QUEUED" });
4657
+ try {
4658
+ const data = new TextEncoder().encode(inv.invoiceXml);
4659
+ const plainMeta = client.crypto.getFileMetadata(data);
4660
+ const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4661
+ const encMeta = client.crypto.getFileMetadata(encrypted);
4662
+ await storage.update(inv.id, { status: "SUBMITTED", submittedAt: (/* @__PURE__ */ new Date()).toISOString() });
4663
+ const resp = await client.onlineSession.sendInvoice(sessionRef, {
4664
+ invoiceHash: plainMeta.hashSHA,
4665
+ invoiceSize: plainMeta.fileSize,
4666
+ encryptedInvoiceHash: encMeta.hashSHA,
4667
+ encryptedInvoiceSize: encMeta.fileSize,
4668
+ encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
4669
+ offlineMode: true
4670
+ });
4671
+ await storage.update(inv.id, {
4672
+ status: "ACCEPTED",
4673
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
4674
+ ksefReferenceNumber: resp.referenceNumber
4675
+ });
4676
+ result.submitted++;
4677
+ result.accepted++;
4678
+ result.results.push({
4679
+ invoiceId: inv.id,
4680
+ invoiceNumber: inv.invoiceNumber,
4681
+ status: "ACCEPTED",
4682
+ ksefReferenceNumber: resp.referenceNumber
4683
+ });
4684
+ } catch (err) {
4685
+ const message = err instanceof Error ? err.message : String(err);
4686
+ if (err instanceof KSeFApiError) {
4687
+ await storage.update(inv.id, {
4688
+ status: "REJECTED",
4689
+ error: { code: err.statusCode, message }
4690
+ });
4691
+ result.submitted++;
4692
+ result.rejected++;
4693
+ result.results.push({
4694
+ invoiceId: inv.id,
4695
+ invoiceNumber: inv.invoiceNumber,
4696
+ status: "REJECTED",
4697
+ error: { code: err.statusCode, message }
4698
+ });
4699
+ } else {
4700
+ await storage.update(inv.id, {
4701
+ status: "QUEUED",
4702
+ error: { code: 0, message }
4703
+ });
4704
+ result.failed++;
4705
+ result.results.push({
4706
+ invoiceId: inv.id,
4707
+ invoiceNumber: inv.invoiceNumber,
4708
+ status: "QUEUED",
4709
+ error: { code: 0, message }
4710
+ });
4711
+ }
4712
+ }
4713
+ }
4714
+ } finally {
4715
+ try {
4716
+ await client.onlineSession.closeSession(sessionRef);
4717
+ } catch {
4718
+ }
4719
+ }
4720
+ return result;
4721
+ }
4722
+ // TODO(perf): Each correction opens a separate KSeF session.
4723
+ // For batch corrections, consider a correctBatch() sharing one session (like submit()).
4724
+ async correct(client, options) {
4725
+ const { storage, rejectedInvoiceId, correctedInvoiceXml } = options;
4726
+ const original = await storage.get(rejectedInvoiceId);
4727
+ if (!original) {
4728
+ throw new Error(`Offline invoice not found: ${rejectedInvoiceId}`);
4729
+ }
4730
+ if (original.status !== "REJECTED") {
4731
+ throw new Error(
4732
+ original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
4733
+ );
4734
+ }
4735
+ const originalHash = import_node_crypto2.default.createHash("sha256").update(original.invoiceXml).digest("base64");
4736
+ await client.crypto.init();
4737
+ const encData = client.crypto.getEncryptionData();
4738
+ const formCode = options.formCode ?? DEFAULT_FORM_CODE;
4739
+ const openResp = await client.onlineSession.openSession(
4740
+ { formCode, encryption: encData.encryptionInfo }
4741
+ );
4742
+ const sessionRef = openResp.referenceNumber;
4743
+ try {
4744
+ const data = new TextEncoder().encode(correctedInvoiceXml);
4745
+ const plainMeta = client.crypto.getFileMetadata(data);
4746
+ const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4747
+ const encMeta = client.crypto.getFileMetadata(encrypted);
4748
+ const correctionId = import_node_crypto2.default.randomUUID();
4749
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4750
+ const correctedHashBase64 = import_node_crypto2.default.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4751
+ const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4752
+ original.sellerNip,
4753
+ original.invoiceDate,
4754
+ correctedHashBase64
4755
+ );
4756
+ let kod2Url;
4757
+ if (options.certificate) {
4758
+ kod2Url = this.qrService.buildCertificateVerificationUrl(
4759
+ original.sellerIdentifier.type,
4760
+ original.sellerIdentifier.value,
4761
+ original.sellerNip,
4762
+ options.certificate.certificateSerial,
4763
+ correctedHashBase64,
4764
+ options.certificate.privateKeyPem
4765
+ );
4766
+ }
4767
+ const correctionMetadata = {
4768
+ id: correctionId,
4769
+ mode: original.mode,
4770
+ reason: original.reason,
4771
+ status: "SUBMITTED",
4772
+ invoiceNumber: original.invoiceNumber,
4773
+ invoiceDate: original.invoiceDate,
4774
+ invoiceXml: correctedInvoiceXml,
4775
+ sellerNip: original.sellerNip,
4776
+ sellerIdentifier: original.sellerIdentifier,
4777
+ buyerIdentifier: original.buyerIdentifier,
4778
+ kod1Url,
4779
+ kod2Url,
4780
+ generatedAt: submittedAt,
4781
+ submitBy: original.submitBy,
4782
+ submittedAt,
4783
+ correctedInvoiceId: rejectedInvoiceId
4784
+ };
4785
+ await storage.save(correctionMetadata);
4786
+ const resp = await client.onlineSession.sendInvoice(sessionRef, {
4787
+ invoiceHash: plainMeta.hashSHA,
4788
+ invoiceSize: plainMeta.fileSize,
4789
+ encryptedInvoiceHash: encMeta.hashSHA,
4790
+ encryptedInvoiceSize: encMeta.fileSize,
4791
+ encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
4792
+ offlineMode: true,
4793
+ hashOfCorrectedInvoice: originalHash
4794
+ });
4795
+ await storage.update(correctionId, {
4796
+ status: "ACCEPTED",
4797
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
4798
+ ksefReferenceNumber: resp.referenceNumber
4799
+ });
4800
+ await storage.update(rejectedInvoiceId, {
4801
+ status: "CORRECTED",
4802
+ correctedBy: correctionId
4803
+ });
4804
+ return {
4805
+ invoiceId: correctionId,
4806
+ invoiceNumber: correctionMetadata.invoiceNumber,
4807
+ status: "ACCEPTED",
4808
+ ksefReferenceNumber: resp.referenceNumber
4809
+ };
4810
+ } finally {
4811
+ try {
4812
+ await client.onlineSession.closeSession(sessionRef);
4813
+ } catch {
4814
+ }
4815
+ }
4816
+ }
4817
+ };
4818
+ }
4819
+ });
4820
+
4354
4821
  // src/client.ts
4355
4822
  var client_exports = {};
4356
4823
  __export(client_exports, {
@@ -4416,6 +4883,7 @@ var init_client = __esm({
4416
4883
  init_cryptography_service();
4417
4884
  init_verification_link_service();
4418
4885
  init_auth_xml_builder();
4886
+ init_offline_invoice_workflow();
4419
4887
  KSeFClient = class {
4420
4888
  auth;
4421
4889
  activeSessions;
@@ -4434,6 +4902,7 @@ var init_client = __esm({
4434
4902
  qr;
4435
4903
  options;
4436
4904
  authManager;
4905
+ _offline;
4437
4906
  constructor(options) {
4438
4907
  this.options = resolveOptions(options);
4439
4908
  const authManager = options?.authManager ?? new DefaultAuthManager(async () => {
@@ -4462,6 +4931,12 @@ var init_client = __esm({
4462
4931
  this.testData = new TestDataService(restClient, this.options.environmentName);
4463
4932
  this.qr = new VerificationLinkService(this.options.baseQrUrl);
4464
4933
  }
4934
+ get offline() {
4935
+ if (!this._offline) {
4936
+ this._offline = new OfflineInvoiceWorkflow(this.qr);
4937
+ }
4938
+ return this._offline;
4939
+ }
4465
4940
  async loginWithToken(token, nip) {
4466
4941
  const challenge = await this.auth.getChallenge();
4467
4942
  await this.crypto.init();
@@ -4542,8 +5017,10 @@ __export(index_exports, {
4542
5017
  FORM_CODES: () => FORM_CODES,
4543
5018
  FORM_CODE_KEYS: () => FORM_CODE_KEYS,
4544
5019
  FileHwmStore: () => FileHwmStore,
5020
+ FileOfflineInvoiceStorage: () => FileOfflineInvoiceStorage,
4545
5021
  INVOICE_TYPES_BY_SYSTEM_CODE: () => INVOICE_TYPES_BY_SYSTEM_CODE,
4546
5022
  InMemoryHwmStore: () => InMemoryHwmStore,
5023
+ InMemoryOfflineInvoiceStorage: () => InMemoryOfflineInvoiceStorage,
4547
5024
  InternalId: () => InternalId,
4548
5025
  InvoiceDownloadService: () => InvoiceDownloadService,
4549
5026
  InvoiceQueryFilterBuilder: () => InvoiceQueryFilterBuilder,
@@ -4567,6 +5044,7 @@ __export(index_exports, {
4567
5044
  LimitsService: () => LimitsService,
4568
5045
  Nip: () => Nip,
4569
5046
  NipVatUe: () => NipVatUe,
5047
+ OfflineInvoiceWorkflow: () => OfflineInvoiceWorkflow,
4570
5048
  OnlineSessionService: () => OnlineSessionService,
4571
5049
  PERMISSION_DESCRIPTION_MAX_LENGTH: () => PERMISSION_DESCRIPTION_MAX_LENGTH,
4572
5050
  PERMISSION_DESCRIPTION_MIN_LENGTH: () => PERMISSION_DESCRIPTION_MIN_LENGTH,
@@ -4596,6 +5074,7 @@ __export(index_exports, {
4596
5074
  UpoVersion: () => UpoVersion,
4597
5075
  VatUe: () => VatUe,
4598
5076
  VerificationLinkService: () => VerificationLinkService,
5077
+ addBusinessDays: () => addBusinessDays,
4599
5078
  authenticateWithCertificate: () => authenticateWithCertificate,
4600
5079
  authenticateWithExternalSignature: () => authenticateWithExternalSignature,
4601
5080
  authenticateWithPkcs12: () => authenticateWithPkcs12,
@@ -4603,6 +5082,7 @@ __export(index_exports, {
4603
5082
  batchValidationDetails: () => batchValidationDetails,
4604
5083
  buildUnsignedAuthTokenRequestXml: () => buildUnsignedAuthTokenRequestXml,
4605
5084
  calculateBackoff: () => calculateBackoff,
5085
+ calculateOfflineDeadline: () => calculateOfflineDeadline,
4606
5086
  createZip: () => createZip,
4607
5087
  decodeJwtPayload: () => decodeJwtPayload,
4608
5088
  deduplicateByKsefNumber: () => deduplicateByKsefNumber,
@@ -4612,9 +5092,14 @@ __export(index_exports, {
4612
5092
  defaultTransport: () => defaultTransport,
4613
5093
  exportAndDownload: () => exportAndDownload,
4614
5094
  exportInvoices: () => exportInvoices,
5095
+ extendDeadlineForMaintenance: () => extendDeadlineForMaintenance,
5096
+ extractInvoiceFields: () => extractInvoiceFields,
5097
+ getDefaultReason: () => getDefaultReason,
4615
5098
  getEffectiveStartDate: () => getEffectiveStartDate,
4616
5099
  getFormCode: () => getFormCode,
5100
+ getTimeUntilDeadline: () => getTimeUntilDeadline,
4617
5101
  incrementalExportAndDownload: () => incrementalExportAndDownload,
5102
+ isExpired: () => isExpired,
4618
5103
  isRetryableError: () => isRetryableError,
4619
5104
  isRetryableStatus: () => isRetryableStatus,
4620
5105
  isValidBase64: () => isValidBase64,
@@ -4632,6 +5117,7 @@ __export(index_exports, {
4632
5117
  isValidReferenceNumber: () => isValidReferenceNumber,
4633
5118
  isValidSha256Base64: () => isValidSha256Base64,
4634
5119
  isValidVatUe: () => isValidVatUe,
5120
+ nextBusinessDay: () => nextBusinessDay,
4635
5121
  openOnlineSession: () => openOnlineSession,
4636
5122
  openSendAndClose: () => openSendAndClose,
4637
5123
  parseFormCode: () => parseFormCode,
@@ -4689,65 +5175,8 @@ init_xml_to_object();
4689
5175
  init_schema_registry();
4690
5176
  init_invoice_validator();
4691
5177
 
4692
- // src/models/document-structures/types.ts
4693
- var SystemCode = {
4694
- FA_2: "FA (2)",
4695
- FA_3: "FA (3)",
4696
- PEF_3: "PEF (3)",
4697
- PEF_KOR_3: "PEF_KOR (3)",
4698
- FA_RR_1: "FA_RR (1)"
4699
- };
4700
- var FORM_CODES = {
4701
- FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
4702
- FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
4703
- PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
4704
- PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
4705
- FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
4706
- FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
4707
- FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
4708
- };
4709
- var DEFAULT_FORM_CODE = FORM_CODES.FA_3;
4710
- var INVOICE_TYPES_BY_SYSTEM_CODE = {
4711
- [SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
4712
- [SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
4713
- [SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
4714
- [SystemCode.PEF_KOR_3]: ["KorPef"],
4715
- [SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
4716
- };
4717
- var FORM_CODE_KEYS = {
4718
- FA2: FORM_CODES.FA_2,
4719
- FA3: FORM_CODES.FA_3,
4720
- PEF3: FORM_CODES.PEF_3,
4721
- PEFKOR3: FORM_CODES.PEF_KOR_3,
4722
- FARR1: FORM_CODES.FA_RR_1
4723
- };
4724
-
4725
- // src/models/document-structures/helpers.ts
4726
- var SYSTEM_CODE_TO_FORM_CODE = {
4727
- [SystemCode.FA_2]: FORM_CODES.FA_2,
4728
- [SystemCode.FA_3]: FORM_CODES.FA_3,
4729
- [SystemCode.PEF_3]: FORM_CODES.PEF_3,
4730
- [SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
4731
- [SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
4732
- };
4733
- var ALL_FORM_CODES = Object.values(FORM_CODES);
4734
- var BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
4735
- SystemCode.PEF_3,
4736
- SystemCode.PEF_KOR_3
4737
- ]);
4738
- function getFormCode(systemCode) {
4739
- return SYSTEM_CODE_TO_FORM_CODE[systemCode];
4740
- }
4741
- function parseFormCode(raw) {
4742
- const match = ALL_FORM_CODES.find(
4743
- (fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
4744
- );
4745
- return match ?? raw;
4746
- }
4747
- function validateFormCodeForSession(formCode, sessionType) {
4748
- if (sessionType === "online") return true;
4749
- return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
4750
- }
5178
+ // src/models/index.ts
5179
+ init_document_structures();
4751
5180
 
4752
5181
  // src/services/index.ts
4753
5182
  init_auth();
@@ -5499,6 +5928,9 @@ async function pollUntil(action, condition, options) {
5499
5928
  );
5500
5929
  }
5501
5930
 
5931
+ // src/workflows/online-session-workflow.ts
5932
+ init_document_structures();
5933
+
5502
5934
  // src/xml/upo-parser.ts
5503
5935
  var import_fast_xml_parser = require("fast-xml-parser");
5504
5936
  init_ksef_validation_error();
@@ -5617,6 +6049,46 @@ function parseUpoXml(xml) {
5617
6049
  };
5618
6050
  }
5619
6051
 
6052
+ // src/xml/invoice-field-extractor.ts
6053
+ var import_fast_xml_parser2 = require("fast-xml-parser");
6054
+ var invoiceParser = new import_fast_xml_parser2.XMLParser({
6055
+ ignoreAttributes: false,
6056
+ attributeNamePrefix: "@_",
6057
+ parseTagValue: false,
6058
+ parseAttributeValue: false,
6059
+ removeNSPrefix: true,
6060
+ trimValues: false
6061
+ });
6062
+ function extractInvoiceFields(xml) {
6063
+ const parsed = invoiceParser.parse(xml);
6064
+ const root = parsed?.Faktura;
6065
+ if (!root || typeof root !== "object") {
6066
+ throw new Error(
6067
+ "Cannot extract invoice fields: missing <Faktura> root element. Ensure the XML is a valid KSeF invoice (FA_2, FA_3, or FA_RR)."
6068
+ );
6069
+ }
6070
+ if (root.Fa && typeof root.Fa === "object") {
6071
+ const invoiceDate = nonEmptyString(root.Fa.P_1);
6072
+ const invoiceNumber = nonEmptyString(root.Fa.P_2);
6073
+ if (invoiceDate && invoiceNumber) {
6074
+ return { invoiceNumber, invoiceDate };
6075
+ }
6076
+ }
6077
+ if (root.FakturaRR && typeof root.FakturaRR === "object") {
6078
+ const invoiceDate = nonEmptyString(root.FakturaRR.P_4B);
6079
+ const invoiceNumber = nonEmptyString(root.FakturaRR.P_4C);
6080
+ if (invoiceDate && invoiceNumber) {
6081
+ return { invoiceNumber, invoiceDate };
6082
+ }
6083
+ }
6084
+ throw new Error(
6085
+ "Cannot extract invoice number and date from XML. Expected <Fa><P_1>/<P_2> (FA format) or <FakturaRR><P_4B>/<P_4C> (RR format)."
6086
+ );
6087
+ }
6088
+ function nonEmptyString(value) {
6089
+ return typeof value === "string" && value.length > 0 ? value : void 0;
6090
+ }
6091
+
5620
6092
  // src/workflows/online-session-workflow.ts
5621
6093
  init_invoice_validator();
5622
6094
  init_ksef_validation_error();
@@ -5699,6 +6171,7 @@ async function openSendAndClose(client, invoices, options) {
5699
6171
  }
5700
6172
 
5701
6173
  // src/workflows/batch-session-workflow.ts
6174
+ init_document_structures();
5702
6175
  async function uploadBatch(client, zipData, options) {
5703
6176
  await client.crypto.init();
5704
6177
  if (options?.validate) {
@@ -6119,7 +6592,140 @@ async function authenticateWithPkcs12(client, options) {
6119
6592
  });
6120
6593
  }
6121
6594
 
6595
+ // src/offline/index.ts
6596
+ init_deadline();
6597
+
6598
+ // src/offline/storage.ts
6599
+ function matchesFilter(invoice, filter) {
6600
+ if (filter.status !== void 0) {
6601
+ const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
6602
+ if (!statuses.includes(invoice.status)) return false;
6603
+ }
6604
+ if (filter.mode !== void 0 && invoice.mode !== filter.mode) return false;
6605
+ if (filter.sellerNip !== void 0 && invoice.sellerNip !== filter.sellerNip) return false;
6606
+ if (filter.expiringBefore !== void 0) {
6607
+ const cutoff = typeof filter.expiringBefore === "string" ? new Date(filter.expiringBefore).getTime() : filter.expiringBefore.getTime();
6608
+ if (new Date(invoice.submitBy).getTime() >= cutoff) return false;
6609
+ }
6610
+ return true;
6611
+ }
6612
+ var InMemoryOfflineInvoiceStorage = class {
6613
+ store = /* @__PURE__ */ new Map();
6614
+ async save(invoice) {
6615
+ this.store.set(invoice.id, JSON.parse(JSON.stringify(invoice)));
6616
+ }
6617
+ async get(id) {
6618
+ const entry = this.store.get(id);
6619
+ return entry ? JSON.parse(JSON.stringify(entry)) : null;
6620
+ }
6621
+ async list(filter) {
6622
+ const all = [...this.store.values()];
6623
+ if (!filter) return all.map((i) => JSON.parse(JSON.stringify(i)));
6624
+ return all.filter((i) => matchesFilter(i, filter)).map((i) => JSON.parse(JSON.stringify(i)));
6625
+ }
6626
+ async update(id, updates) {
6627
+ const existing = this.store.get(id);
6628
+ if (!existing) throw new Error(`Offline invoice not found: ${id}`);
6629
+ this.store.set(id, JSON.parse(JSON.stringify({ ...existing, ...updates })));
6630
+ }
6631
+ async delete(id) {
6632
+ this.store.delete(id);
6633
+ }
6634
+ };
6635
+
6636
+ // src/offline/file-storage.ts
6637
+ var fs2 = __toESM(require("fs/promises"), 1);
6638
+ var path = __toESM(require("path"), 1);
6639
+ var os = __toESM(require("os"), 1);
6640
+ function resolveDir(dir) {
6641
+ if (dir === "~" || dir.startsWith("~/")) {
6642
+ return path.join(os.homedir(), dir.slice(1));
6643
+ }
6644
+ return dir;
6645
+ }
6646
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6647
+ function validateId(id) {
6648
+ if (!UUID_RE.test(id)) {
6649
+ throw new Error(`Invalid invoice ID: ${id}`);
6650
+ }
6651
+ }
6652
+ var FileOfflineInvoiceStorage = class {
6653
+ dir;
6654
+ constructor(directory) {
6655
+ this.dir = resolveDir(directory ?? "~/.ksef/offline");
6656
+ }
6657
+ async ensureDir() {
6658
+ await fs2.mkdir(this.dir, { recursive: true });
6659
+ }
6660
+ filePath(id) {
6661
+ validateId(id);
6662
+ return path.join(this.dir, `${id}.json`);
6663
+ }
6664
+ async save(invoice) {
6665
+ await this.ensureDir();
6666
+ const file = this.filePath(invoice.id);
6667
+ const tmp = `${file}.tmp`;
6668
+ await fs2.writeFile(tmp, JSON.stringify(invoice, null, 2));
6669
+ await fs2.rename(tmp, file);
6670
+ }
6671
+ async get(id) {
6672
+ const file = this.filePath(id);
6673
+ try {
6674
+ return JSON.parse(await fs2.readFile(file, "utf-8"));
6675
+ } catch (err) {
6676
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
6677
+ return null;
6678
+ }
6679
+ console.warn(`Warning: Failed to read offline invoice ${id}: ${err instanceof Error ? err.message : String(err)}`);
6680
+ return null;
6681
+ }
6682
+ }
6683
+ async list(filter) {
6684
+ let files;
6685
+ try {
6686
+ files = (await fs2.readdir(this.dir)).filter((f) => f.endsWith(".json"));
6687
+ } catch {
6688
+ return [];
6689
+ }
6690
+ const results = [];
6691
+ for (const file of files) {
6692
+ try {
6693
+ const data = JSON.parse(
6694
+ await fs2.readFile(path.join(this.dir, file), "utf-8")
6695
+ );
6696
+ if (!filter || matchesFilter(data, filter)) {
6697
+ results.push(data);
6698
+ }
6699
+ } catch (err) {
6700
+ console.warn(`Warning: Skipping corrupt offline invoice file ${file}: ${err instanceof Error ? err.message : String(err)}`);
6701
+ }
6702
+ }
6703
+ return results;
6704
+ }
6705
+ /**
6706
+ * Update invoice metadata (read-modify-write).
6707
+ *
6708
+ * Note: No file locking — concurrent updates to the same ID may cause
6709
+ * lost writes. Acceptable for CLI (single process). Library consumers
6710
+ * running parallel operations should use external locking.
6711
+ */
6712
+ async update(id, updates) {
6713
+ const existing = await this.get(id);
6714
+ if (!existing) throw new Error(`Offline invoice not found: ${id}`);
6715
+ await this.save({ ...existing, ...updates });
6716
+ }
6717
+ async delete(id) {
6718
+ const file = this.filePath(id);
6719
+ try {
6720
+ await fs2.unlink(file);
6721
+ } catch (e) {
6722
+ if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
6723
+ }
6724
+ }
6725
+ };
6726
+
6122
6727
  // src/index.ts
6728
+ init_offline_invoice_workflow();
6123
6729
  init_client();
6124
6730
  // Annotate the CommonJS export names for ESM import in node:
6125
6731
  0 && (module.exports = {
@@ -6150,8 +6756,10 @@ init_client();
6150
6756
  FORM_CODES,
6151
6757
  FORM_CODE_KEYS,
6152
6758
  FileHwmStore,
6759
+ FileOfflineInvoiceStorage,
6153
6760
  INVOICE_TYPES_BY_SYSTEM_CODE,
6154
6761
  InMemoryHwmStore,
6762
+ InMemoryOfflineInvoiceStorage,
6155
6763
  InternalId,
6156
6764
  InvoiceDownloadService,
6157
6765
  InvoiceQueryFilterBuilder,
@@ -6175,6 +6783,7 @@ init_client();
6175
6783
  LimitsService,
6176
6784
  Nip,
6177
6785
  NipVatUe,
6786
+ OfflineInvoiceWorkflow,
6178
6787
  OnlineSessionService,
6179
6788
  PERMISSION_DESCRIPTION_MAX_LENGTH,
6180
6789
  PERMISSION_DESCRIPTION_MIN_LENGTH,
@@ -6204,6 +6813,7 @@ init_client();
6204
6813
  UpoVersion,
6205
6814
  VatUe,
6206
6815
  VerificationLinkService,
6816
+ addBusinessDays,
6207
6817
  authenticateWithCertificate,
6208
6818
  authenticateWithExternalSignature,
6209
6819
  authenticateWithPkcs12,
@@ -6211,6 +6821,7 @@ init_client();
6211
6821
  batchValidationDetails,
6212
6822
  buildUnsignedAuthTokenRequestXml,
6213
6823
  calculateBackoff,
6824
+ calculateOfflineDeadline,
6214
6825
  createZip,
6215
6826
  decodeJwtPayload,
6216
6827
  deduplicateByKsefNumber,
@@ -6220,9 +6831,14 @@ init_client();
6220
6831
  defaultTransport,
6221
6832
  exportAndDownload,
6222
6833
  exportInvoices,
6834
+ extendDeadlineForMaintenance,
6835
+ extractInvoiceFields,
6836
+ getDefaultReason,
6223
6837
  getEffectiveStartDate,
6224
6838
  getFormCode,
6839
+ getTimeUntilDeadline,
6225
6840
  incrementalExportAndDownload,
6841
+ isExpired,
6226
6842
  isRetryableError,
6227
6843
  isRetryableStatus,
6228
6844
  isValidBase64,
@@ -6240,6 +6856,7 @@ init_client();
6240
6856
  isValidReferenceNumber,
6241
6857
  isValidSha256Base64,
6242
6858
  isValidVatUe,
6859
+ nextBusinessDay,
6243
6860
  openOnlineSession,
6244
6861
  openSendAndClose,
6245
6862
  parseFormCode,