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/README.md +2 -1
- package/dist/cli.js +1014 -128
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +695 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +199 -1
- package/dist/index.d.ts +199 -1
- package/dist/index.js +684 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -298,21 +298,21 @@ var init_rest_request = __esm({
|
|
|
298
298
|
_query = [];
|
|
299
299
|
_presigned = false;
|
|
300
300
|
_skipAuthRetry = false;
|
|
301
|
-
constructor(method,
|
|
301
|
+
constructor(method, path2) {
|
|
302
302
|
this.method = method;
|
|
303
|
-
this.path =
|
|
303
|
+
this.path = path2;
|
|
304
304
|
}
|
|
305
|
-
static get(
|
|
306
|
-
return new _RestRequest("GET",
|
|
305
|
+
static get(path2) {
|
|
306
|
+
return new _RestRequest("GET", path2);
|
|
307
307
|
}
|
|
308
|
-
static post(
|
|
309
|
-
return new _RestRequest("POST",
|
|
308
|
+
static post(path2) {
|
|
309
|
+
return new _RestRequest("POST", path2);
|
|
310
310
|
}
|
|
311
|
-
static put(
|
|
312
|
-
return new _RestRequest("PUT",
|
|
311
|
+
static put(path2) {
|
|
312
|
+
return new _RestRequest("PUT", path2);
|
|
313
313
|
}
|
|
314
|
-
static delete(
|
|
315
|
-
return new _RestRequest("DELETE",
|
|
314
|
+
static delete(path2) {
|
|
315
|
+
return new _RestRequest("DELETE", path2);
|
|
316
316
|
}
|
|
317
317
|
body(data) {
|
|
318
318
|
this._body = data;
|
|
@@ -632,9 +632,9 @@ var init_rest_client = __esm({
|
|
|
632
632
|
return response;
|
|
633
633
|
}
|
|
634
634
|
buildUrl(request) {
|
|
635
|
-
const
|
|
635
|
+
const path2 = this.routeBuilder.build(request.path);
|
|
636
636
|
const base = this.options.baseUrl;
|
|
637
|
-
const url = new URL(`${base}${
|
|
637
|
+
const url = new URL(`${base}${path2}`);
|
|
638
638
|
const query = request.getQuery();
|
|
639
639
|
for (const [key, value] of query) {
|
|
640
640
|
url.searchParams.append(key, value);
|
|
@@ -2645,11 +2645,11 @@ async function validateSchema(xml, options, _parsed) {
|
|
|
2645
2645
|
const prefix = rootElement ? `/${rootElement}/` : "/";
|
|
2646
2646
|
const validationErrors = result.error.issues.map((issue) => {
|
|
2647
2647
|
const zodPath = issue.path.join("/");
|
|
2648
|
-
const
|
|
2648
|
+
const path2 = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
|
|
2649
2649
|
return {
|
|
2650
2650
|
code: mapZodErrorCode(issue),
|
|
2651
2651
|
message: issue.message,
|
|
2652
|
-
path
|
|
2652
|
+
path: path2
|
|
2653
2653
|
};
|
|
2654
2654
|
});
|
|
2655
2655
|
return { valid: false, schemaType, errors: validationErrors };
|
|
@@ -2689,9 +2689,9 @@ function validateBusinessRules(xml, _parsed) {
|
|
|
2689
2689
|
collectDateErrors(object, rootElement, errors);
|
|
2690
2690
|
return { valid: errors.length === 0, schemaType, errors };
|
|
2691
2691
|
}
|
|
2692
|
-
function collectNipPeselErrors(obj,
|
|
2692
|
+
function collectNipPeselErrors(obj, path2, errors) {
|
|
2693
2693
|
for (const [key, value] of Object.entries(obj)) {
|
|
2694
|
-
const currentPath =
|
|
2694
|
+
const currentPath = path2 ? `${path2}/${key}` : key;
|
|
2695
2695
|
if (key === "NIP" && typeof value === "string") {
|
|
2696
2696
|
if (!isValidNip(value)) {
|
|
2697
2697
|
errors.push({
|
|
@@ -2770,6 +2770,88 @@ var init_invoice_validator = __esm({
|
|
|
2770
2770
|
}
|
|
2771
2771
|
});
|
|
2772
2772
|
|
|
2773
|
+
// src/models/document-structures/types.ts
|
|
2774
|
+
var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
|
|
2775
|
+
var init_types = __esm({
|
|
2776
|
+
"src/models/document-structures/types.ts"() {
|
|
2777
|
+
"use strict";
|
|
2778
|
+
SystemCode = {
|
|
2779
|
+
FA_2: "FA (2)",
|
|
2780
|
+
FA_3: "FA (3)",
|
|
2781
|
+
PEF_3: "PEF (3)",
|
|
2782
|
+
PEF_KOR_3: "PEF_KOR (3)",
|
|
2783
|
+
FA_RR_1: "FA_RR (1)"
|
|
2784
|
+
};
|
|
2785
|
+
FORM_CODES = {
|
|
2786
|
+
FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
|
|
2787
|
+
FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
|
|
2788
|
+
PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2789
|
+
PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2790
|
+
FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
|
|
2791
|
+
FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
|
|
2792
|
+
FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
|
|
2793
|
+
};
|
|
2794
|
+
DEFAULT_FORM_CODE = FORM_CODES.FA_3;
|
|
2795
|
+
INVOICE_TYPES_BY_SYSTEM_CODE = {
|
|
2796
|
+
[SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2797
|
+
[SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2798
|
+
[SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
|
|
2799
|
+
[SystemCode.PEF_KOR_3]: ["KorPef"],
|
|
2800
|
+
[SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
|
|
2801
|
+
};
|
|
2802
|
+
FORM_CODE_KEYS = {
|
|
2803
|
+
FA2: FORM_CODES.FA_2,
|
|
2804
|
+
FA3: FORM_CODES.FA_3,
|
|
2805
|
+
PEF3: FORM_CODES.PEF_3,
|
|
2806
|
+
PEFKOR3: FORM_CODES.PEF_KOR_3,
|
|
2807
|
+
FARR1: FORM_CODES.FA_RR_1
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
// src/models/document-structures/helpers.ts
|
|
2813
|
+
function getFormCode(systemCode) {
|
|
2814
|
+
return SYSTEM_CODE_TO_FORM_CODE[systemCode];
|
|
2815
|
+
}
|
|
2816
|
+
function parseFormCode(raw) {
|
|
2817
|
+
const match = ALL_FORM_CODES.find(
|
|
2818
|
+
(fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
|
|
2819
|
+
);
|
|
2820
|
+
return match ?? raw;
|
|
2821
|
+
}
|
|
2822
|
+
function validateFormCodeForSession(formCode, sessionType) {
|
|
2823
|
+
if (sessionType === "online") return true;
|
|
2824
|
+
return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
|
|
2825
|
+
}
|
|
2826
|
+
var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
|
|
2827
|
+
var init_helpers = __esm({
|
|
2828
|
+
"src/models/document-structures/helpers.ts"() {
|
|
2829
|
+
"use strict";
|
|
2830
|
+
init_types();
|
|
2831
|
+
SYSTEM_CODE_TO_FORM_CODE = {
|
|
2832
|
+
[SystemCode.FA_2]: FORM_CODES.FA_2,
|
|
2833
|
+
[SystemCode.FA_3]: FORM_CODES.FA_3,
|
|
2834
|
+
[SystemCode.PEF_3]: FORM_CODES.PEF_3,
|
|
2835
|
+
[SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
|
|
2836
|
+
[SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
|
|
2837
|
+
};
|
|
2838
|
+
ALL_FORM_CODES = Object.values(FORM_CODES);
|
|
2839
|
+
BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
|
|
2840
|
+
SystemCode.PEF_3,
|
|
2841
|
+
SystemCode.PEF_KOR_3
|
|
2842
|
+
]);
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2845
|
+
|
|
2846
|
+
// src/models/document-structures/index.ts
|
|
2847
|
+
var init_document_structures = __esm({
|
|
2848
|
+
"src/models/document-structures/index.ts"() {
|
|
2849
|
+
"use strict";
|
|
2850
|
+
init_types();
|
|
2851
|
+
init_helpers();
|
|
2852
|
+
}
|
|
2853
|
+
});
|
|
2854
|
+
|
|
2773
2855
|
// src/services/auth.ts
|
|
2774
2856
|
var AuthService;
|
|
2775
2857
|
var init_auth = __esm({
|
|
@@ -3335,20 +3417,20 @@ var init_lighthouse = __esm({
|
|
|
3335
3417
|
this.lighthouseUrl = options.lighthouseUrl;
|
|
3336
3418
|
this.timeout = options.timeout;
|
|
3337
3419
|
}
|
|
3338
|
-
async fetchJson(
|
|
3420
|
+
async fetchJson(path2) {
|
|
3339
3421
|
if (!this.lighthouseUrl) {
|
|
3340
3422
|
throw new KSeFError(
|
|
3341
3423
|
"Lighthouse API is not available for the DEMO environment. Use TEST or PROD instead."
|
|
3342
3424
|
);
|
|
3343
3425
|
}
|
|
3344
|
-
const response = await fetch(`${this.lighthouseUrl}${
|
|
3426
|
+
const response = await fetch(`${this.lighthouseUrl}${path2}`, {
|
|
3345
3427
|
headers: { Accept: "application/json" },
|
|
3346
3428
|
signal: AbortSignal.timeout(this.timeout)
|
|
3347
3429
|
});
|
|
3348
3430
|
if (!response.ok) {
|
|
3349
3431
|
const body = await response.text();
|
|
3350
3432
|
throw new KSeFError(
|
|
3351
|
-
`Lighthouse ${
|
|
3433
|
+
`Lighthouse ${path2} failed: HTTP ${response.status} \u2014 ${body}`
|
|
3352
3434
|
);
|
|
3353
3435
|
}
|
|
3354
3436
|
return await response.json();
|
|
@@ -4328,6 +4410,391 @@ var init_zip = __esm({
|
|
|
4328
4410
|
}
|
|
4329
4411
|
});
|
|
4330
4412
|
|
|
4413
|
+
// src/offline/deadline.ts
|
|
4414
|
+
function getDefaultReason(mode) {
|
|
4415
|
+
switch (mode) {
|
|
4416
|
+
case "offline24":
|
|
4417
|
+
return "PLANNED";
|
|
4418
|
+
case "offline":
|
|
4419
|
+
return "SYSTEM_UNAVAILABLE";
|
|
4420
|
+
case "awaryjny":
|
|
4421
|
+
return "EMERGENCY";
|
|
4422
|
+
case "awaria_calkowita":
|
|
4423
|
+
return "TOTAL_FAILURE";
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
function nextBusinessDay(from) {
|
|
4427
|
+
const d = new Date(from);
|
|
4428
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
4429
|
+
while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
|
|
4430
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
4431
|
+
}
|
|
4432
|
+
return d;
|
|
4433
|
+
}
|
|
4434
|
+
function addBusinessDays(from, days) {
|
|
4435
|
+
if (!Number.isInteger(days) || days < 0) {
|
|
4436
|
+
throw new Error(`days must be a non-negative integer, got ${days}`);
|
|
4437
|
+
}
|
|
4438
|
+
const d = new Date(from);
|
|
4439
|
+
let remaining = days;
|
|
4440
|
+
while (remaining > 0) {
|
|
4441
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
4442
|
+
if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
|
|
4443
|
+
remaining--;
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return d;
|
|
4447
|
+
}
|
|
4448
|
+
function endOfDay(d) {
|
|
4449
|
+
const result = new Date(d);
|
|
4450
|
+
result.setUTCHours(23, 59, 59, 999);
|
|
4451
|
+
return result;
|
|
4452
|
+
}
|
|
4453
|
+
function getMaintenanceEndFallback(mw) {
|
|
4454
|
+
if (mw.endTime) {
|
|
4455
|
+
return new Date(mw.endTime);
|
|
4456
|
+
}
|
|
4457
|
+
const start = new Date(mw.startTime);
|
|
4458
|
+
start.setUTCDate(start.getUTCDate() + 7);
|
|
4459
|
+
return start;
|
|
4460
|
+
}
|
|
4461
|
+
function calculateOfflineDeadline(mode, invoiceDate, maintenanceWindow) {
|
|
4462
|
+
const date = typeof invoiceDate === "string" ? new Date(invoiceDate) : invoiceDate;
|
|
4463
|
+
switch (mode) {
|
|
4464
|
+
case "offline24": {
|
|
4465
|
+
return endOfDay(nextBusinessDay(date));
|
|
4466
|
+
}
|
|
4467
|
+
case "offline": {
|
|
4468
|
+
const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
|
|
4469
|
+
return endOfDay(nextBusinessDay(base));
|
|
4470
|
+
}
|
|
4471
|
+
case "awaryjny": {
|
|
4472
|
+
const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
|
|
4473
|
+
return endOfDay(addBusinessDays(base, 7));
|
|
4474
|
+
}
|
|
4475
|
+
case "awaria_calkowita": {
|
|
4476
|
+
return new Date(FAR_FUTURE);
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
function extendDeadlineForMaintenance(currentDeadline, maintenanceWindow, mode = "awaryjny") {
|
|
4481
|
+
const current = typeof currentDeadline === "string" ? new Date(currentDeadline) : new Date(currentDeadline);
|
|
4482
|
+
const mwEnd = getMaintenanceEndFallback(maintenanceWindow);
|
|
4483
|
+
if (mwEnd.getTime() > current.getTime()) {
|
|
4484
|
+
switch (mode) {
|
|
4485
|
+
case "offline24":
|
|
4486
|
+
case "offline":
|
|
4487
|
+
return endOfDay(nextBusinessDay(mwEnd));
|
|
4488
|
+
case "awaryjny":
|
|
4489
|
+
return endOfDay(addBusinessDays(mwEnd, 7));
|
|
4490
|
+
case "awaria_calkowita":
|
|
4491
|
+
return new Date(FAR_FUTURE);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
return current;
|
|
4495
|
+
}
|
|
4496
|
+
function isExpired(submitBy) {
|
|
4497
|
+
const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
|
|
4498
|
+
return Date.now() > deadline.getTime();
|
|
4499
|
+
}
|
|
4500
|
+
function getTimeUntilDeadline(submitBy) {
|
|
4501
|
+
const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
|
|
4502
|
+
return Math.max(0, deadline.getTime() - Date.now());
|
|
4503
|
+
}
|
|
4504
|
+
var FAR_FUTURE;
|
|
4505
|
+
var init_deadline = __esm({
|
|
4506
|
+
"src/offline/deadline.ts"() {
|
|
4507
|
+
"use strict";
|
|
4508
|
+
FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
|
|
4509
|
+
}
|
|
4510
|
+
});
|
|
4511
|
+
|
|
4512
|
+
// src/workflows/offline-invoice-workflow.ts
|
|
4513
|
+
import crypto6 from "crypto";
|
|
4514
|
+
var OfflineInvoiceWorkflow;
|
|
4515
|
+
var init_offline_invoice_workflow = __esm({
|
|
4516
|
+
"src/workflows/offline-invoice-workflow.ts"() {
|
|
4517
|
+
"use strict";
|
|
4518
|
+
init_ksef_api_error();
|
|
4519
|
+
init_deadline();
|
|
4520
|
+
init_document_structures();
|
|
4521
|
+
OfflineInvoiceWorkflow = class {
|
|
4522
|
+
constructor(qrService) {
|
|
4523
|
+
this.qrService = qrService;
|
|
4524
|
+
}
|
|
4525
|
+
async generate(input, options) {
|
|
4526
|
+
if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
|
|
4527
|
+
throw new Error("invoiceXml must not be empty");
|
|
4528
|
+
}
|
|
4529
|
+
if (!input.invoiceNumber) {
|
|
4530
|
+
throw new Error("invoiceNumber is required");
|
|
4531
|
+
}
|
|
4532
|
+
if (!input.sellerNip) {
|
|
4533
|
+
throw new Error("sellerNip is required");
|
|
4534
|
+
}
|
|
4535
|
+
const mode = options?.mode ?? "offline24";
|
|
4536
|
+
const reason = getDefaultReason(mode);
|
|
4537
|
+
const invoiceHashBase64 = crypto6.createHash("sha256").update(input.invoiceXml).digest("base64");
|
|
4538
|
+
const kod1Url = this.qrService.buildInvoiceVerificationUrl(
|
|
4539
|
+
input.sellerNip,
|
|
4540
|
+
input.invoiceDate,
|
|
4541
|
+
invoiceHashBase64
|
|
4542
|
+
);
|
|
4543
|
+
let kod2Url;
|
|
4544
|
+
if (options?.certificate) {
|
|
4545
|
+
kod2Url = this.qrService.buildCertificateVerificationUrl(
|
|
4546
|
+
input.sellerIdentifier.type,
|
|
4547
|
+
input.sellerIdentifier.value,
|
|
4548
|
+
input.sellerNip,
|
|
4549
|
+
options.certificate.certificateSerial,
|
|
4550
|
+
invoiceHashBase64,
|
|
4551
|
+
options.certificate.privateKeyPem
|
|
4552
|
+
);
|
|
4553
|
+
}
|
|
4554
|
+
const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
|
|
4555
|
+
const metadata = {
|
|
4556
|
+
id: crypto6.randomUUID(),
|
|
4557
|
+
mode,
|
|
4558
|
+
reason,
|
|
4559
|
+
status: "GENERATED",
|
|
4560
|
+
invoiceNumber: input.invoiceNumber,
|
|
4561
|
+
invoiceDate: input.invoiceDate,
|
|
4562
|
+
invoiceXml: input.invoiceXml,
|
|
4563
|
+
sellerNip: input.sellerNip,
|
|
4564
|
+
sellerIdentifier: input.sellerIdentifier,
|
|
4565
|
+
buyerIdentifier: input.buyerIdentifier,
|
|
4566
|
+
totalAmount: input.totalAmount,
|
|
4567
|
+
currency: input.currency,
|
|
4568
|
+
kod1Url,
|
|
4569
|
+
kod2Url,
|
|
4570
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4571
|
+
submitBy,
|
|
4572
|
+
maintenanceWindowId: options?.maintenanceWindow?.id
|
|
4573
|
+
};
|
|
4574
|
+
if (options?.storage) {
|
|
4575
|
+
await options.storage.save(metadata);
|
|
4576
|
+
}
|
|
4577
|
+
return metadata;
|
|
4578
|
+
}
|
|
4579
|
+
async submit(client, options) {
|
|
4580
|
+
const { storage, checkExpiry = true } = options;
|
|
4581
|
+
let invoices;
|
|
4582
|
+
if (options.invoiceIds) {
|
|
4583
|
+
invoices = [];
|
|
4584
|
+
const notFound = [];
|
|
4585
|
+
for (const id of options.invoiceIds) {
|
|
4586
|
+
const inv = await storage.get(id);
|
|
4587
|
+
if (inv) {
|
|
4588
|
+
invoices.push(inv);
|
|
4589
|
+
} else {
|
|
4590
|
+
notFound.push(id);
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
if (notFound.length > 0) {
|
|
4594
|
+
throw new Error(`Offline invoice(s) not found: ${notFound.join(", ")}`);
|
|
4595
|
+
}
|
|
4596
|
+
} else {
|
|
4597
|
+
invoices = await storage.list({ status: ["GENERATED", "QUEUED", "SUBMITTED"] });
|
|
4598
|
+
}
|
|
4599
|
+
const result = {
|
|
4600
|
+
total: invoices.length,
|
|
4601
|
+
submitted: 0,
|
|
4602
|
+
accepted: 0,
|
|
4603
|
+
rejected: 0,
|
|
4604
|
+
failed: 0,
|
|
4605
|
+
expired: 0,
|
|
4606
|
+
results: []
|
|
4607
|
+
};
|
|
4608
|
+
if (invoices.length === 0) return result;
|
|
4609
|
+
const pending = [];
|
|
4610
|
+
for (const inv of invoices) {
|
|
4611
|
+
if (checkExpiry && isExpired(inv.submitBy)) {
|
|
4612
|
+
await storage.update(inv.id, { status: "EXPIRED" });
|
|
4613
|
+
result.expired++;
|
|
4614
|
+
result.results.push({
|
|
4615
|
+
invoiceId: inv.id,
|
|
4616
|
+
invoiceNumber: inv.invoiceNumber,
|
|
4617
|
+
status: "EXPIRED"
|
|
4618
|
+
});
|
|
4619
|
+
} else {
|
|
4620
|
+
pending.push(inv);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
if (pending.length === 0) return result;
|
|
4624
|
+
await client.crypto.init();
|
|
4625
|
+
const encData = client.crypto.getEncryptionData();
|
|
4626
|
+
const formCode = options.formCode ?? DEFAULT_FORM_CODE;
|
|
4627
|
+
const openResp = await client.onlineSession.openSession(
|
|
4628
|
+
{ formCode, encryption: encData.encryptionInfo }
|
|
4629
|
+
);
|
|
4630
|
+
const sessionRef = openResp.referenceNumber;
|
|
4631
|
+
try {
|
|
4632
|
+
for (const inv of pending) {
|
|
4633
|
+
await storage.update(inv.id, { status: "QUEUED" });
|
|
4634
|
+
try {
|
|
4635
|
+
const data = new TextEncoder().encode(inv.invoiceXml);
|
|
4636
|
+
const plainMeta = client.crypto.getFileMetadata(data);
|
|
4637
|
+
const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
|
|
4638
|
+
const encMeta = client.crypto.getFileMetadata(encrypted);
|
|
4639
|
+
await storage.update(inv.id, { status: "SUBMITTED", submittedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4640
|
+
const resp = await client.onlineSession.sendInvoice(sessionRef, {
|
|
4641
|
+
invoiceHash: plainMeta.hashSHA,
|
|
4642
|
+
invoiceSize: plainMeta.fileSize,
|
|
4643
|
+
encryptedInvoiceHash: encMeta.hashSHA,
|
|
4644
|
+
encryptedInvoiceSize: encMeta.fileSize,
|
|
4645
|
+
encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
|
|
4646
|
+
offlineMode: true
|
|
4647
|
+
});
|
|
4648
|
+
await storage.update(inv.id, {
|
|
4649
|
+
status: "ACCEPTED",
|
|
4650
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4651
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
4652
|
+
});
|
|
4653
|
+
result.submitted++;
|
|
4654
|
+
result.accepted++;
|
|
4655
|
+
result.results.push({
|
|
4656
|
+
invoiceId: inv.id,
|
|
4657
|
+
invoiceNumber: inv.invoiceNumber,
|
|
4658
|
+
status: "ACCEPTED",
|
|
4659
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
4660
|
+
});
|
|
4661
|
+
} catch (err) {
|
|
4662
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4663
|
+
if (err instanceof KSeFApiError) {
|
|
4664
|
+
await storage.update(inv.id, {
|
|
4665
|
+
status: "REJECTED",
|
|
4666
|
+
error: { code: err.statusCode, message }
|
|
4667
|
+
});
|
|
4668
|
+
result.submitted++;
|
|
4669
|
+
result.rejected++;
|
|
4670
|
+
result.results.push({
|
|
4671
|
+
invoiceId: inv.id,
|
|
4672
|
+
invoiceNumber: inv.invoiceNumber,
|
|
4673
|
+
status: "REJECTED",
|
|
4674
|
+
error: { code: err.statusCode, message }
|
|
4675
|
+
});
|
|
4676
|
+
} else {
|
|
4677
|
+
await storage.update(inv.id, {
|
|
4678
|
+
status: "QUEUED",
|
|
4679
|
+
error: { code: 0, message }
|
|
4680
|
+
});
|
|
4681
|
+
result.failed++;
|
|
4682
|
+
result.results.push({
|
|
4683
|
+
invoiceId: inv.id,
|
|
4684
|
+
invoiceNumber: inv.invoiceNumber,
|
|
4685
|
+
status: "QUEUED",
|
|
4686
|
+
error: { code: 0, message }
|
|
4687
|
+
});
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
} finally {
|
|
4692
|
+
try {
|
|
4693
|
+
await client.onlineSession.closeSession(sessionRef);
|
|
4694
|
+
} catch {
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
return result;
|
|
4698
|
+
}
|
|
4699
|
+
// TODO(perf): Each correction opens a separate KSeF session.
|
|
4700
|
+
// For batch corrections, consider a correctBatch() sharing one session (like submit()).
|
|
4701
|
+
async correct(client, options) {
|
|
4702
|
+
const { storage, rejectedInvoiceId, correctedInvoiceXml } = options;
|
|
4703
|
+
const original = await storage.get(rejectedInvoiceId);
|
|
4704
|
+
if (!original) {
|
|
4705
|
+
throw new Error(`Offline invoice not found: ${rejectedInvoiceId}`);
|
|
4706
|
+
}
|
|
4707
|
+
if (original.status !== "REJECTED") {
|
|
4708
|
+
throw new Error(
|
|
4709
|
+
original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
|
|
4710
|
+
);
|
|
4711
|
+
}
|
|
4712
|
+
const originalHash = crypto6.createHash("sha256").update(original.invoiceXml).digest("base64");
|
|
4713
|
+
await client.crypto.init();
|
|
4714
|
+
const encData = client.crypto.getEncryptionData();
|
|
4715
|
+
const formCode = options.formCode ?? DEFAULT_FORM_CODE;
|
|
4716
|
+
const openResp = await client.onlineSession.openSession(
|
|
4717
|
+
{ formCode, encryption: encData.encryptionInfo }
|
|
4718
|
+
);
|
|
4719
|
+
const sessionRef = openResp.referenceNumber;
|
|
4720
|
+
try {
|
|
4721
|
+
const data = new TextEncoder().encode(correctedInvoiceXml);
|
|
4722
|
+
const plainMeta = client.crypto.getFileMetadata(data);
|
|
4723
|
+
const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
|
|
4724
|
+
const encMeta = client.crypto.getFileMetadata(encrypted);
|
|
4725
|
+
const correctionId = crypto6.randomUUID();
|
|
4726
|
+
const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4727
|
+
const correctedHashBase64 = crypto6.createHash("sha256").update(correctedInvoiceXml).digest("base64");
|
|
4728
|
+
const kod1Url = this.qrService.buildInvoiceVerificationUrl(
|
|
4729
|
+
original.sellerNip,
|
|
4730
|
+
original.invoiceDate,
|
|
4731
|
+
correctedHashBase64
|
|
4732
|
+
);
|
|
4733
|
+
let kod2Url;
|
|
4734
|
+
if (options.certificate) {
|
|
4735
|
+
kod2Url = this.qrService.buildCertificateVerificationUrl(
|
|
4736
|
+
original.sellerIdentifier.type,
|
|
4737
|
+
original.sellerIdentifier.value,
|
|
4738
|
+
original.sellerNip,
|
|
4739
|
+
options.certificate.certificateSerial,
|
|
4740
|
+
correctedHashBase64,
|
|
4741
|
+
options.certificate.privateKeyPem
|
|
4742
|
+
);
|
|
4743
|
+
}
|
|
4744
|
+
const correctionMetadata = {
|
|
4745
|
+
id: correctionId,
|
|
4746
|
+
mode: original.mode,
|
|
4747
|
+
reason: original.reason,
|
|
4748
|
+
status: "SUBMITTED",
|
|
4749
|
+
invoiceNumber: original.invoiceNumber,
|
|
4750
|
+
invoiceDate: original.invoiceDate,
|
|
4751
|
+
invoiceXml: correctedInvoiceXml,
|
|
4752
|
+
sellerNip: original.sellerNip,
|
|
4753
|
+
sellerIdentifier: original.sellerIdentifier,
|
|
4754
|
+
buyerIdentifier: original.buyerIdentifier,
|
|
4755
|
+
kod1Url,
|
|
4756
|
+
kod2Url,
|
|
4757
|
+
generatedAt: submittedAt,
|
|
4758
|
+
submitBy: original.submitBy,
|
|
4759
|
+
submittedAt,
|
|
4760
|
+
correctedInvoiceId: rejectedInvoiceId
|
|
4761
|
+
};
|
|
4762
|
+
await storage.save(correctionMetadata);
|
|
4763
|
+
const resp = await client.onlineSession.sendInvoice(sessionRef, {
|
|
4764
|
+
invoiceHash: plainMeta.hashSHA,
|
|
4765
|
+
invoiceSize: plainMeta.fileSize,
|
|
4766
|
+
encryptedInvoiceHash: encMeta.hashSHA,
|
|
4767
|
+
encryptedInvoiceSize: encMeta.fileSize,
|
|
4768
|
+
encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
|
|
4769
|
+
offlineMode: true,
|
|
4770
|
+
hashOfCorrectedInvoice: originalHash
|
|
4771
|
+
});
|
|
4772
|
+
await storage.update(correctionId, {
|
|
4773
|
+
status: "ACCEPTED",
|
|
4774
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4775
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
4776
|
+
});
|
|
4777
|
+
await storage.update(rejectedInvoiceId, {
|
|
4778
|
+
status: "CORRECTED",
|
|
4779
|
+
correctedBy: correctionId
|
|
4780
|
+
});
|
|
4781
|
+
return {
|
|
4782
|
+
invoiceId: correctionId,
|
|
4783
|
+
invoiceNumber: correctionMetadata.invoiceNumber,
|
|
4784
|
+
status: "ACCEPTED",
|
|
4785
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
4786
|
+
};
|
|
4787
|
+
} finally {
|
|
4788
|
+
try {
|
|
4789
|
+
await client.onlineSession.closeSession(sessionRef);
|
|
4790
|
+
} catch {
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
};
|
|
4795
|
+
}
|
|
4796
|
+
});
|
|
4797
|
+
|
|
4331
4798
|
// src/client.ts
|
|
4332
4799
|
var client_exports = {};
|
|
4333
4800
|
__export(client_exports, {
|
|
@@ -4393,6 +4860,7 @@ var init_client = __esm({
|
|
|
4393
4860
|
init_cryptography_service();
|
|
4394
4861
|
init_verification_link_service();
|
|
4395
4862
|
init_auth_xml_builder();
|
|
4863
|
+
init_offline_invoice_workflow();
|
|
4396
4864
|
KSeFClient = class {
|
|
4397
4865
|
auth;
|
|
4398
4866
|
activeSessions;
|
|
@@ -4411,6 +4879,7 @@ var init_client = __esm({
|
|
|
4411
4879
|
qr;
|
|
4412
4880
|
options;
|
|
4413
4881
|
authManager;
|
|
4882
|
+
_offline;
|
|
4414
4883
|
constructor(options) {
|
|
4415
4884
|
this.options = resolveOptions(options);
|
|
4416
4885
|
const authManager = options?.authManager ?? new DefaultAuthManager(async () => {
|
|
@@ -4439,6 +4908,12 @@ var init_client = __esm({
|
|
|
4439
4908
|
this.testData = new TestDataService(restClient, this.options.environmentName);
|
|
4440
4909
|
this.qr = new VerificationLinkService(this.options.baseQrUrl);
|
|
4441
4910
|
}
|
|
4911
|
+
get offline() {
|
|
4912
|
+
if (!this._offline) {
|
|
4913
|
+
this._offline = new OfflineInvoiceWorkflow(this.qr);
|
|
4914
|
+
}
|
|
4915
|
+
return this._offline;
|
|
4916
|
+
}
|
|
4442
4917
|
async loginWithToken(token, nip) {
|
|
4443
4918
|
const challenge = await this.auth.getChallenge();
|
|
4444
4919
|
await this.crypto.init();
|
|
@@ -4522,65 +4997,8 @@ init_xml_to_object();
|
|
|
4522
4997
|
init_schema_registry();
|
|
4523
4998
|
init_invoice_validator();
|
|
4524
4999
|
|
|
4525
|
-
// src/models/
|
|
4526
|
-
|
|
4527
|
-
FA_2: "FA (2)",
|
|
4528
|
-
FA_3: "FA (3)",
|
|
4529
|
-
PEF_3: "PEF (3)",
|
|
4530
|
-
PEF_KOR_3: "PEF_KOR (3)",
|
|
4531
|
-
FA_RR_1: "FA_RR (1)"
|
|
4532
|
-
};
|
|
4533
|
-
var FORM_CODES = {
|
|
4534
|
-
FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
|
|
4535
|
-
FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
|
|
4536
|
-
PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
|
|
4537
|
-
PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
|
|
4538
|
-
FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
|
|
4539
|
-
FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
|
|
4540
|
-
FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
|
|
4541
|
-
};
|
|
4542
|
-
var DEFAULT_FORM_CODE = FORM_CODES.FA_3;
|
|
4543
|
-
var INVOICE_TYPES_BY_SYSTEM_CODE = {
|
|
4544
|
-
[SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
4545
|
-
[SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
4546
|
-
[SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
|
|
4547
|
-
[SystemCode.PEF_KOR_3]: ["KorPef"],
|
|
4548
|
-
[SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
|
|
4549
|
-
};
|
|
4550
|
-
var FORM_CODE_KEYS = {
|
|
4551
|
-
FA2: FORM_CODES.FA_2,
|
|
4552
|
-
FA3: FORM_CODES.FA_3,
|
|
4553
|
-
PEF3: FORM_CODES.PEF_3,
|
|
4554
|
-
PEFKOR3: FORM_CODES.PEF_KOR_3,
|
|
4555
|
-
FARR1: FORM_CODES.FA_RR_1
|
|
4556
|
-
};
|
|
4557
|
-
|
|
4558
|
-
// src/models/document-structures/helpers.ts
|
|
4559
|
-
var SYSTEM_CODE_TO_FORM_CODE = {
|
|
4560
|
-
[SystemCode.FA_2]: FORM_CODES.FA_2,
|
|
4561
|
-
[SystemCode.FA_3]: FORM_CODES.FA_3,
|
|
4562
|
-
[SystemCode.PEF_3]: FORM_CODES.PEF_3,
|
|
4563
|
-
[SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
|
|
4564
|
-
[SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
|
|
4565
|
-
};
|
|
4566
|
-
var ALL_FORM_CODES = Object.values(FORM_CODES);
|
|
4567
|
-
var BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
|
|
4568
|
-
SystemCode.PEF_3,
|
|
4569
|
-
SystemCode.PEF_KOR_3
|
|
4570
|
-
]);
|
|
4571
|
-
function getFormCode(systemCode) {
|
|
4572
|
-
return SYSTEM_CODE_TO_FORM_CODE[systemCode];
|
|
4573
|
-
}
|
|
4574
|
-
function parseFormCode(raw) {
|
|
4575
|
-
const match = ALL_FORM_CODES.find(
|
|
4576
|
-
(fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
|
|
4577
|
-
);
|
|
4578
|
-
return match ?? raw;
|
|
4579
|
-
}
|
|
4580
|
-
function validateFormCodeForSession(formCode, sessionType) {
|
|
4581
|
-
if (sessionType === "online") return true;
|
|
4582
|
-
return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
|
|
4583
|
-
}
|
|
5000
|
+
// src/models/index.ts
|
|
5001
|
+
init_document_structures();
|
|
4584
5002
|
|
|
4585
5003
|
// src/services/index.ts
|
|
4586
5004
|
init_auth();
|
|
@@ -5332,6 +5750,9 @@ async function pollUntil(action, condition, options) {
|
|
|
5332
5750
|
);
|
|
5333
5751
|
}
|
|
5334
5752
|
|
|
5753
|
+
// src/workflows/online-session-workflow.ts
|
|
5754
|
+
init_document_structures();
|
|
5755
|
+
|
|
5335
5756
|
// src/xml/upo-parser.ts
|
|
5336
5757
|
init_ksef_validation_error();
|
|
5337
5758
|
import { XMLParser } from "fast-xml-parser";
|
|
@@ -5450,6 +5871,46 @@ function parseUpoXml(xml) {
|
|
|
5450
5871
|
};
|
|
5451
5872
|
}
|
|
5452
5873
|
|
|
5874
|
+
// src/xml/invoice-field-extractor.ts
|
|
5875
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
5876
|
+
var invoiceParser = new XMLParser2({
|
|
5877
|
+
ignoreAttributes: false,
|
|
5878
|
+
attributeNamePrefix: "@_",
|
|
5879
|
+
parseTagValue: false,
|
|
5880
|
+
parseAttributeValue: false,
|
|
5881
|
+
removeNSPrefix: true,
|
|
5882
|
+
trimValues: false
|
|
5883
|
+
});
|
|
5884
|
+
function extractInvoiceFields(xml) {
|
|
5885
|
+
const parsed = invoiceParser.parse(xml);
|
|
5886
|
+
const root = parsed?.Faktura;
|
|
5887
|
+
if (!root || typeof root !== "object") {
|
|
5888
|
+
throw new Error(
|
|
5889
|
+
"Cannot extract invoice fields: missing <Faktura> root element. Ensure the XML is a valid KSeF invoice (FA_2, FA_3, or FA_RR)."
|
|
5890
|
+
);
|
|
5891
|
+
}
|
|
5892
|
+
if (root.Fa && typeof root.Fa === "object") {
|
|
5893
|
+
const invoiceDate = nonEmptyString(root.Fa.P_1);
|
|
5894
|
+
const invoiceNumber = nonEmptyString(root.Fa.P_2);
|
|
5895
|
+
if (invoiceDate && invoiceNumber) {
|
|
5896
|
+
return { invoiceNumber, invoiceDate };
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
if (root.FakturaRR && typeof root.FakturaRR === "object") {
|
|
5900
|
+
const invoiceDate = nonEmptyString(root.FakturaRR.P_4B);
|
|
5901
|
+
const invoiceNumber = nonEmptyString(root.FakturaRR.P_4C);
|
|
5902
|
+
if (invoiceDate && invoiceNumber) {
|
|
5903
|
+
return { invoiceNumber, invoiceDate };
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
throw new Error(
|
|
5907
|
+
"Cannot extract invoice number and date from XML. Expected <Fa><P_1>/<P_2> (FA format) or <FakturaRR><P_4B>/<P_4C> (RR format)."
|
|
5908
|
+
);
|
|
5909
|
+
}
|
|
5910
|
+
function nonEmptyString(value) {
|
|
5911
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5453
5914
|
// src/workflows/online-session-workflow.ts
|
|
5454
5915
|
init_invoice_validator();
|
|
5455
5916
|
init_ksef_validation_error();
|
|
@@ -5532,6 +5993,7 @@ async function openSendAndClose(client, invoices, options) {
|
|
|
5532
5993
|
}
|
|
5533
5994
|
|
|
5534
5995
|
// src/workflows/batch-session-workflow.ts
|
|
5996
|
+
init_document_structures();
|
|
5535
5997
|
async function uploadBatch(client, zipData, options) {
|
|
5536
5998
|
await client.crypto.init();
|
|
5537
5999
|
if (options?.validate) {
|
|
@@ -5952,7 +6414,140 @@ async function authenticateWithPkcs12(client, options) {
|
|
|
5952
6414
|
});
|
|
5953
6415
|
}
|
|
5954
6416
|
|
|
6417
|
+
// src/offline/index.ts
|
|
6418
|
+
init_deadline();
|
|
6419
|
+
|
|
6420
|
+
// src/offline/storage.ts
|
|
6421
|
+
function matchesFilter(invoice, filter) {
|
|
6422
|
+
if (filter.status !== void 0) {
|
|
6423
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
6424
|
+
if (!statuses.includes(invoice.status)) return false;
|
|
6425
|
+
}
|
|
6426
|
+
if (filter.mode !== void 0 && invoice.mode !== filter.mode) return false;
|
|
6427
|
+
if (filter.sellerNip !== void 0 && invoice.sellerNip !== filter.sellerNip) return false;
|
|
6428
|
+
if (filter.expiringBefore !== void 0) {
|
|
6429
|
+
const cutoff = typeof filter.expiringBefore === "string" ? new Date(filter.expiringBefore).getTime() : filter.expiringBefore.getTime();
|
|
6430
|
+
if (new Date(invoice.submitBy).getTime() >= cutoff) return false;
|
|
6431
|
+
}
|
|
6432
|
+
return true;
|
|
6433
|
+
}
|
|
6434
|
+
var InMemoryOfflineInvoiceStorage = class {
|
|
6435
|
+
store = /* @__PURE__ */ new Map();
|
|
6436
|
+
async save(invoice) {
|
|
6437
|
+
this.store.set(invoice.id, JSON.parse(JSON.stringify(invoice)));
|
|
6438
|
+
}
|
|
6439
|
+
async get(id) {
|
|
6440
|
+
const entry = this.store.get(id);
|
|
6441
|
+
return entry ? JSON.parse(JSON.stringify(entry)) : null;
|
|
6442
|
+
}
|
|
6443
|
+
async list(filter) {
|
|
6444
|
+
const all = [...this.store.values()];
|
|
6445
|
+
if (!filter) return all.map((i) => JSON.parse(JSON.stringify(i)));
|
|
6446
|
+
return all.filter((i) => matchesFilter(i, filter)).map((i) => JSON.parse(JSON.stringify(i)));
|
|
6447
|
+
}
|
|
6448
|
+
async update(id, updates) {
|
|
6449
|
+
const existing = this.store.get(id);
|
|
6450
|
+
if (!existing) throw new Error(`Offline invoice not found: ${id}`);
|
|
6451
|
+
this.store.set(id, JSON.parse(JSON.stringify({ ...existing, ...updates })));
|
|
6452
|
+
}
|
|
6453
|
+
async delete(id) {
|
|
6454
|
+
this.store.delete(id);
|
|
6455
|
+
}
|
|
6456
|
+
};
|
|
6457
|
+
|
|
6458
|
+
// src/offline/file-storage.ts
|
|
6459
|
+
import * as fs2 from "fs/promises";
|
|
6460
|
+
import * as path from "path";
|
|
6461
|
+
import * as os from "os";
|
|
6462
|
+
function resolveDir(dir) {
|
|
6463
|
+
if (dir === "~" || dir.startsWith("~/")) {
|
|
6464
|
+
return path.join(os.homedir(), dir.slice(1));
|
|
6465
|
+
}
|
|
6466
|
+
return dir;
|
|
6467
|
+
}
|
|
6468
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6469
|
+
function validateId(id) {
|
|
6470
|
+
if (!UUID_RE.test(id)) {
|
|
6471
|
+
throw new Error(`Invalid invoice ID: ${id}`);
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
var FileOfflineInvoiceStorage = class {
|
|
6475
|
+
dir;
|
|
6476
|
+
constructor(directory) {
|
|
6477
|
+
this.dir = resolveDir(directory ?? "~/.ksef/offline");
|
|
6478
|
+
}
|
|
6479
|
+
async ensureDir() {
|
|
6480
|
+
await fs2.mkdir(this.dir, { recursive: true });
|
|
6481
|
+
}
|
|
6482
|
+
filePath(id) {
|
|
6483
|
+
validateId(id);
|
|
6484
|
+
return path.join(this.dir, `${id}.json`);
|
|
6485
|
+
}
|
|
6486
|
+
async save(invoice) {
|
|
6487
|
+
await this.ensureDir();
|
|
6488
|
+
const file = this.filePath(invoice.id);
|
|
6489
|
+
const tmp = `${file}.tmp`;
|
|
6490
|
+
await fs2.writeFile(tmp, JSON.stringify(invoice, null, 2));
|
|
6491
|
+
await fs2.rename(tmp, file);
|
|
6492
|
+
}
|
|
6493
|
+
async get(id) {
|
|
6494
|
+
const file = this.filePath(id);
|
|
6495
|
+
try {
|
|
6496
|
+
return JSON.parse(await fs2.readFile(file, "utf-8"));
|
|
6497
|
+
} catch (err) {
|
|
6498
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
6499
|
+
return null;
|
|
6500
|
+
}
|
|
6501
|
+
console.warn(`Warning: Failed to read offline invoice ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6502
|
+
return null;
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
async list(filter) {
|
|
6506
|
+
let files;
|
|
6507
|
+
try {
|
|
6508
|
+
files = (await fs2.readdir(this.dir)).filter((f) => f.endsWith(".json"));
|
|
6509
|
+
} catch {
|
|
6510
|
+
return [];
|
|
6511
|
+
}
|
|
6512
|
+
const results = [];
|
|
6513
|
+
for (const file of files) {
|
|
6514
|
+
try {
|
|
6515
|
+
const data = JSON.parse(
|
|
6516
|
+
await fs2.readFile(path.join(this.dir, file), "utf-8")
|
|
6517
|
+
);
|
|
6518
|
+
if (!filter || matchesFilter(data, filter)) {
|
|
6519
|
+
results.push(data);
|
|
6520
|
+
}
|
|
6521
|
+
} catch (err) {
|
|
6522
|
+
console.warn(`Warning: Skipping corrupt offline invoice file ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
return results;
|
|
6526
|
+
}
|
|
6527
|
+
/**
|
|
6528
|
+
* Update invoice metadata (read-modify-write).
|
|
6529
|
+
*
|
|
6530
|
+
* Note: No file locking — concurrent updates to the same ID may cause
|
|
6531
|
+
* lost writes. Acceptable for CLI (single process). Library consumers
|
|
6532
|
+
* running parallel operations should use external locking.
|
|
6533
|
+
*/
|
|
6534
|
+
async update(id, updates) {
|
|
6535
|
+
const existing = await this.get(id);
|
|
6536
|
+
if (!existing) throw new Error(`Offline invoice not found: ${id}`);
|
|
6537
|
+
await this.save({ ...existing, ...updates });
|
|
6538
|
+
}
|
|
6539
|
+
async delete(id) {
|
|
6540
|
+
const file = this.filePath(id);
|
|
6541
|
+
try {
|
|
6542
|
+
await fs2.unlink(file);
|
|
6543
|
+
} catch (e) {
|
|
6544
|
+
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
};
|
|
6548
|
+
|
|
5955
6549
|
// src/index.ts
|
|
6550
|
+
init_offline_invoice_workflow();
|
|
5956
6551
|
init_client();
|
|
5957
6552
|
export {
|
|
5958
6553
|
ActiveSessionsService,
|
|
@@ -5982,8 +6577,10 @@ export {
|
|
|
5982
6577
|
FORM_CODES,
|
|
5983
6578
|
FORM_CODE_KEYS,
|
|
5984
6579
|
FileHwmStore,
|
|
6580
|
+
FileOfflineInvoiceStorage,
|
|
5985
6581
|
INVOICE_TYPES_BY_SYSTEM_CODE,
|
|
5986
6582
|
InMemoryHwmStore,
|
|
6583
|
+
InMemoryOfflineInvoiceStorage,
|
|
5987
6584
|
InternalId,
|
|
5988
6585
|
InvoiceDownloadService,
|
|
5989
6586
|
InvoiceQueryFilterBuilder,
|
|
@@ -6007,6 +6604,7 @@ export {
|
|
|
6007
6604
|
LimitsService,
|
|
6008
6605
|
Nip,
|
|
6009
6606
|
NipVatUe,
|
|
6607
|
+
OfflineInvoiceWorkflow,
|
|
6010
6608
|
OnlineSessionService,
|
|
6011
6609
|
PERMISSION_DESCRIPTION_MAX_LENGTH,
|
|
6012
6610
|
PERMISSION_DESCRIPTION_MIN_LENGTH,
|
|
@@ -6036,6 +6634,7 @@ export {
|
|
|
6036
6634
|
UpoVersion,
|
|
6037
6635
|
VatUe,
|
|
6038
6636
|
VerificationLinkService,
|
|
6637
|
+
addBusinessDays,
|
|
6039
6638
|
authenticateWithCertificate,
|
|
6040
6639
|
authenticateWithExternalSignature,
|
|
6041
6640
|
authenticateWithPkcs12,
|
|
@@ -6043,6 +6642,7 @@ export {
|
|
|
6043
6642
|
batchValidationDetails,
|
|
6044
6643
|
buildUnsignedAuthTokenRequestXml,
|
|
6045
6644
|
calculateBackoff,
|
|
6645
|
+
calculateOfflineDeadline,
|
|
6046
6646
|
createZip,
|
|
6047
6647
|
decodeJwtPayload,
|
|
6048
6648
|
deduplicateByKsefNumber,
|
|
@@ -6052,9 +6652,14 @@ export {
|
|
|
6052
6652
|
defaultTransport,
|
|
6053
6653
|
exportAndDownload,
|
|
6054
6654
|
exportInvoices,
|
|
6655
|
+
extendDeadlineForMaintenance,
|
|
6656
|
+
extractInvoiceFields,
|
|
6657
|
+
getDefaultReason,
|
|
6055
6658
|
getEffectiveStartDate,
|
|
6056
6659
|
getFormCode,
|
|
6660
|
+
getTimeUntilDeadline,
|
|
6057
6661
|
incrementalExportAndDownload,
|
|
6662
|
+
isExpired,
|
|
6058
6663
|
isRetryableError,
|
|
6059
6664
|
isRetryableStatus,
|
|
6060
6665
|
isValidBase64,
|
|
@@ -6072,6 +6677,7 @@ export {
|
|
|
6072
6677
|
isValidReferenceNumber,
|
|
6073
6678
|
isValidSha256Base64,
|
|
6074
6679
|
isValidVatUe,
|
|
6680
|
+
nextBusinessDay,
|
|
6075
6681
|
openOnlineSession,
|
|
6076
6682
|
openSendAndClose,
|
|
6077
6683
|
parseFormCode,
|