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/cli.js
CHANGED
|
@@ -281,8 +281,8 @@ function isRetryableError(error, policy) {
|
|
|
281
281
|
if (code && RETRYABLE_ERROR_CODES.has(code)) return true;
|
|
282
282
|
return false;
|
|
283
283
|
}
|
|
284
|
-
function isRetryableStatus(
|
|
285
|
-
return policy.retryableStatusCodes.includes(
|
|
284
|
+
function isRetryableStatus(status7, policy) {
|
|
285
|
+
return policy.retryableStatusCodes.includes(status7);
|
|
286
286
|
}
|
|
287
287
|
function sleep(ms) {
|
|
288
288
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
@@ -508,9 +508,9 @@ var init_rest_client = __esm({
|
|
|
508
508
|
return response;
|
|
509
509
|
}
|
|
510
510
|
buildUrl(request) {
|
|
511
|
-
const
|
|
511
|
+
const path10 = this.routeBuilder.build(request.path);
|
|
512
512
|
const base = this.options.baseUrl;
|
|
513
|
-
const url2 = new URL(`${base}${
|
|
513
|
+
const url2 = new URL(`${base}${path10}`);
|
|
514
514
|
const query2 = request.getQuery();
|
|
515
515
|
for (const [key, value] of query2) {
|
|
516
516
|
url2.searchParams.append(key, value);
|
|
@@ -681,21 +681,21 @@ var init_rest_request = __esm({
|
|
|
681
681
|
_query = [];
|
|
682
682
|
_presigned = false;
|
|
683
683
|
_skipAuthRetry = false;
|
|
684
|
-
constructor(method,
|
|
684
|
+
constructor(method, path10) {
|
|
685
685
|
this.method = method;
|
|
686
|
-
this.path =
|
|
686
|
+
this.path = path10;
|
|
687
687
|
}
|
|
688
|
-
static get(
|
|
689
|
-
return new _RestRequest("GET",
|
|
688
|
+
static get(path10) {
|
|
689
|
+
return new _RestRequest("GET", path10);
|
|
690
690
|
}
|
|
691
|
-
static post(
|
|
692
|
-
return new _RestRequest("POST",
|
|
691
|
+
static post(path10) {
|
|
692
|
+
return new _RestRequest("POST", path10);
|
|
693
693
|
}
|
|
694
|
-
static put(
|
|
695
|
-
return new _RestRequest("PUT",
|
|
694
|
+
static put(path10) {
|
|
695
|
+
return new _RestRequest("PUT", path10);
|
|
696
696
|
}
|
|
697
|
-
static delete(
|
|
698
|
-
return new _RestRequest("DELETE",
|
|
697
|
+
static delete(path10) {
|
|
698
|
+
return new _RestRequest("DELETE", path10);
|
|
699
699
|
}
|
|
700
700
|
body(data) {
|
|
701
701
|
this._body = data;
|
|
@@ -1094,8 +1094,8 @@ var init_session_status = __esm({
|
|
|
1094
1094
|
if (filter.dateModifiedFrom) req.query("dateModifiedFrom", filter.dateModifiedFrom);
|
|
1095
1095
|
if (filter.dateModifiedTo) req.query("dateModifiedTo", filter.dateModifiedTo);
|
|
1096
1096
|
if (filter.statuses) {
|
|
1097
|
-
for (const
|
|
1098
|
-
req.query("statuses",
|
|
1097
|
+
for (const status7 of filter.statuses) {
|
|
1098
|
+
req.query("statuses", status7);
|
|
1099
1099
|
}
|
|
1100
1100
|
}
|
|
1101
1101
|
}
|
|
@@ -1469,20 +1469,20 @@ var init_lighthouse = __esm({
|
|
|
1469
1469
|
this.lighthouseUrl = options.lighthouseUrl;
|
|
1470
1470
|
this.timeout = options.timeout;
|
|
1471
1471
|
}
|
|
1472
|
-
async fetchJson(
|
|
1472
|
+
async fetchJson(path10) {
|
|
1473
1473
|
if (!this.lighthouseUrl) {
|
|
1474
1474
|
throw new KSeFError(
|
|
1475
1475
|
"Lighthouse API is not available for the DEMO environment. Use TEST or PROD instead."
|
|
1476
1476
|
);
|
|
1477
1477
|
}
|
|
1478
|
-
const response = await fetch(`${this.lighthouseUrl}${
|
|
1478
|
+
const response = await fetch(`${this.lighthouseUrl}${path10}`, {
|
|
1479
1479
|
headers: { Accept: "application/json" },
|
|
1480
1480
|
signal: AbortSignal.timeout(this.timeout)
|
|
1481
1481
|
});
|
|
1482
1482
|
if (!response.ok) {
|
|
1483
1483
|
const body = await response.text();
|
|
1484
1484
|
throw new KSeFError(
|
|
1485
|
-
`Lighthouse ${
|
|
1485
|
+
`Lighthouse ${path10} failed: HTTP ${response.status} \u2014 ${body}`
|
|
1486
1486
|
);
|
|
1487
1487
|
}
|
|
1488
1488
|
return await response.json();
|
|
@@ -2087,12 +2087,450 @@ var init_auth_xml_builder = __esm({
|
|
|
2087
2087
|
}
|
|
2088
2088
|
});
|
|
2089
2089
|
|
|
2090
|
+
// src/offline/deadline.ts
|
|
2091
|
+
function getDefaultReason(mode) {
|
|
2092
|
+
switch (mode) {
|
|
2093
|
+
case "offline24":
|
|
2094
|
+
return "PLANNED";
|
|
2095
|
+
case "offline":
|
|
2096
|
+
return "SYSTEM_UNAVAILABLE";
|
|
2097
|
+
case "awaryjny":
|
|
2098
|
+
return "EMERGENCY";
|
|
2099
|
+
case "awaria_calkowita":
|
|
2100
|
+
return "TOTAL_FAILURE";
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
function nextBusinessDay(from) {
|
|
2104
|
+
const d = new Date(from);
|
|
2105
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
2106
|
+
while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
|
|
2107
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
2108
|
+
}
|
|
2109
|
+
return d;
|
|
2110
|
+
}
|
|
2111
|
+
function addBusinessDays(from, days) {
|
|
2112
|
+
if (!Number.isInteger(days) || days < 0) {
|
|
2113
|
+
throw new Error(`days must be a non-negative integer, got ${days}`);
|
|
2114
|
+
}
|
|
2115
|
+
const d = new Date(from);
|
|
2116
|
+
let remaining = days;
|
|
2117
|
+
while (remaining > 0) {
|
|
2118
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
2119
|
+
if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
|
|
2120
|
+
remaining--;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return d;
|
|
2124
|
+
}
|
|
2125
|
+
function endOfDay(d) {
|
|
2126
|
+
const result = new Date(d);
|
|
2127
|
+
result.setUTCHours(23, 59, 59, 999);
|
|
2128
|
+
return result;
|
|
2129
|
+
}
|
|
2130
|
+
function getMaintenanceEndFallback(mw) {
|
|
2131
|
+
if (mw.endTime) {
|
|
2132
|
+
return new Date(mw.endTime);
|
|
2133
|
+
}
|
|
2134
|
+
const start = new Date(mw.startTime);
|
|
2135
|
+
start.setUTCDate(start.getUTCDate() + 7);
|
|
2136
|
+
return start;
|
|
2137
|
+
}
|
|
2138
|
+
function calculateOfflineDeadline(mode, invoiceDate, maintenanceWindow) {
|
|
2139
|
+
const date = typeof invoiceDate === "string" ? new Date(invoiceDate) : invoiceDate;
|
|
2140
|
+
switch (mode) {
|
|
2141
|
+
case "offline24": {
|
|
2142
|
+
return endOfDay(nextBusinessDay(date));
|
|
2143
|
+
}
|
|
2144
|
+
case "offline": {
|
|
2145
|
+
const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
|
|
2146
|
+
return endOfDay(nextBusinessDay(base));
|
|
2147
|
+
}
|
|
2148
|
+
case "awaryjny": {
|
|
2149
|
+
const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
|
|
2150
|
+
return endOfDay(addBusinessDays(base, 7));
|
|
2151
|
+
}
|
|
2152
|
+
case "awaria_calkowita": {
|
|
2153
|
+
return new Date(FAR_FUTURE);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
function isExpired(submitBy) {
|
|
2158
|
+
const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
|
|
2159
|
+
return Date.now() > deadline.getTime();
|
|
2160
|
+
}
|
|
2161
|
+
var FAR_FUTURE;
|
|
2162
|
+
var init_deadline = __esm({
|
|
2163
|
+
"src/offline/deadline.ts"() {
|
|
2164
|
+
"use strict";
|
|
2165
|
+
FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
// src/models/document-structures/types.ts
|
|
2170
|
+
var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
|
|
2171
|
+
var init_types = __esm({
|
|
2172
|
+
"src/models/document-structures/types.ts"() {
|
|
2173
|
+
"use strict";
|
|
2174
|
+
SystemCode = {
|
|
2175
|
+
FA_2: "FA (2)",
|
|
2176
|
+
FA_3: "FA (3)",
|
|
2177
|
+
PEF_3: "PEF (3)",
|
|
2178
|
+
PEF_KOR_3: "PEF_KOR (3)",
|
|
2179
|
+
FA_RR_1: "FA_RR (1)"
|
|
2180
|
+
};
|
|
2181
|
+
FORM_CODES = {
|
|
2182
|
+
FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
|
|
2183
|
+
FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
|
|
2184
|
+
PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2185
|
+
PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2186
|
+
FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
|
|
2187
|
+
FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
|
|
2188
|
+
FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
|
|
2189
|
+
};
|
|
2190
|
+
DEFAULT_FORM_CODE = FORM_CODES.FA_3;
|
|
2191
|
+
INVOICE_TYPES_BY_SYSTEM_CODE = {
|
|
2192
|
+
[SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2193
|
+
[SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2194
|
+
[SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
|
|
2195
|
+
[SystemCode.PEF_KOR_3]: ["KorPef"],
|
|
2196
|
+
[SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
|
|
2197
|
+
};
|
|
2198
|
+
FORM_CODE_KEYS = {
|
|
2199
|
+
FA2: FORM_CODES.FA_2,
|
|
2200
|
+
FA3: FORM_CODES.FA_3,
|
|
2201
|
+
PEF3: FORM_CODES.PEF_3,
|
|
2202
|
+
PEFKOR3: FORM_CODES.PEF_KOR_3,
|
|
2203
|
+
FARR1: FORM_CODES.FA_RR_1
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2208
|
+
// src/models/document-structures/helpers.ts
|
|
2209
|
+
function validateFormCodeForSession(formCode, sessionType) {
|
|
2210
|
+
if (sessionType === "online") return true;
|
|
2211
|
+
return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
|
|
2212
|
+
}
|
|
2213
|
+
var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
|
|
2214
|
+
var init_helpers = __esm({
|
|
2215
|
+
"src/models/document-structures/helpers.ts"() {
|
|
2216
|
+
"use strict";
|
|
2217
|
+
init_types();
|
|
2218
|
+
SYSTEM_CODE_TO_FORM_CODE = {
|
|
2219
|
+
[SystemCode.FA_2]: FORM_CODES.FA_2,
|
|
2220
|
+
[SystemCode.FA_3]: FORM_CODES.FA_3,
|
|
2221
|
+
[SystemCode.PEF_3]: FORM_CODES.PEF_3,
|
|
2222
|
+
[SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
|
|
2223
|
+
[SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
|
|
2224
|
+
};
|
|
2225
|
+
ALL_FORM_CODES = Object.values(FORM_CODES);
|
|
2226
|
+
BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
|
|
2227
|
+
SystemCode.PEF_3,
|
|
2228
|
+
SystemCode.PEF_KOR_3
|
|
2229
|
+
]);
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
|
|
2233
|
+
// src/models/document-structures/index.ts
|
|
2234
|
+
var init_document_structures = __esm({
|
|
2235
|
+
"src/models/document-structures/index.ts"() {
|
|
2236
|
+
"use strict";
|
|
2237
|
+
init_types();
|
|
2238
|
+
init_helpers();
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
// src/workflows/offline-invoice-workflow.ts
|
|
2243
|
+
import crypto3 from "crypto";
|
|
2244
|
+
var OfflineInvoiceWorkflow;
|
|
2245
|
+
var init_offline_invoice_workflow = __esm({
|
|
2246
|
+
"src/workflows/offline-invoice-workflow.ts"() {
|
|
2247
|
+
"use strict";
|
|
2248
|
+
init_ksef_api_error();
|
|
2249
|
+
init_deadline();
|
|
2250
|
+
init_document_structures();
|
|
2251
|
+
OfflineInvoiceWorkflow = class {
|
|
2252
|
+
constructor(qrService) {
|
|
2253
|
+
this.qrService = qrService;
|
|
2254
|
+
}
|
|
2255
|
+
async generate(input, options) {
|
|
2256
|
+
if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
|
|
2257
|
+
throw new Error("invoiceXml must not be empty");
|
|
2258
|
+
}
|
|
2259
|
+
if (!input.invoiceNumber) {
|
|
2260
|
+
throw new Error("invoiceNumber is required");
|
|
2261
|
+
}
|
|
2262
|
+
if (!input.sellerNip) {
|
|
2263
|
+
throw new Error("sellerNip is required");
|
|
2264
|
+
}
|
|
2265
|
+
const mode = options?.mode ?? "offline24";
|
|
2266
|
+
const reason = getDefaultReason(mode);
|
|
2267
|
+
const invoiceHashBase64 = crypto3.createHash("sha256").update(input.invoiceXml).digest("base64");
|
|
2268
|
+
const kod1Url = this.qrService.buildInvoiceVerificationUrl(
|
|
2269
|
+
input.sellerNip,
|
|
2270
|
+
input.invoiceDate,
|
|
2271
|
+
invoiceHashBase64
|
|
2272
|
+
);
|
|
2273
|
+
let kod2Url;
|
|
2274
|
+
if (options?.certificate) {
|
|
2275
|
+
kod2Url = this.qrService.buildCertificateVerificationUrl(
|
|
2276
|
+
input.sellerIdentifier.type,
|
|
2277
|
+
input.sellerIdentifier.value,
|
|
2278
|
+
input.sellerNip,
|
|
2279
|
+
options.certificate.certificateSerial,
|
|
2280
|
+
invoiceHashBase64,
|
|
2281
|
+
options.certificate.privateKeyPem
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
|
|
2285
|
+
const metadata = {
|
|
2286
|
+
id: crypto3.randomUUID(),
|
|
2287
|
+
mode,
|
|
2288
|
+
reason,
|
|
2289
|
+
status: "GENERATED",
|
|
2290
|
+
invoiceNumber: input.invoiceNumber,
|
|
2291
|
+
invoiceDate: input.invoiceDate,
|
|
2292
|
+
invoiceXml: input.invoiceXml,
|
|
2293
|
+
sellerNip: input.sellerNip,
|
|
2294
|
+
sellerIdentifier: input.sellerIdentifier,
|
|
2295
|
+
buyerIdentifier: input.buyerIdentifier,
|
|
2296
|
+
totalAmount: input.totalAmount,
|
|
2297
|
+
currency: input.currency,
|
|
2298
|
+
kod1Url,
|
|
2299
|
+
kod2Url,
|
|
2300
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2301
|
+
submitBy,
|
|
2302
|
+
maintenanceWindowId: options?.maintenanceWindow?.id
|
|
2303
|
+
};
|
|
2304
|
+
if (options?.storage) {
|
|
2305
|
+
await options.storage.save(metadata);
|
|
2306
|
+
}
|
|
2307
|
+
return metadata;
|
|
2308
|
+
}
|
|
2309
|
+
async submit(client, options) {
|
|
2310
|
+
const { storage, checkExpiry = true } = options;
|
|
2311
|
+
let invoices2;
|
|
2312
|
+
if (options.invoiceIds) {
|
|
2313
|
+
invoices2 = [];
|
|
2314
|
+
const notFound = [];
|
|
2315
|
+
for (const id of options.invoiceIds) {
|
|
2316
|
+
const inv = await storage.get(id);
|
|
2317
|
+
if (inv) {
|
|
2318
|
+
invoices2.push(inv);
|
|
2319
|
+
} else {
|
|
2320
|
+
notFound.push(id);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (notFound.length > 0) {
|
|
2324
|
+
throw new Error(`Offline invoice(s) not found: ${notFound.join(", ")}`);
|
|
2325
|
+
}
|
|
2326
|
+
} else {
|
|
2327
|
+
invoices2 = await storage.list({ status: ["GENERATED", "QUEUED", "SUBMITTED"] });
|
|
2328
|
+
}
|
|
2329
|
+
const result = {
|
|
2330
|
+
total: invoices2.length,
|
|
2331
|
+
submitted: 0,
|
|
2332
|
+
accepted: 0,
|
|
2333
|
+
rejected: 0,
|
|
2334
|
+
failed: 0,
|
|
2335
|
+
expired: 0,
|
|
2336
|
+
results: []
|
|
2337
|
+
};
|
|
2338
|
+
if (invoices2.length === 0) return result;
|
|
2339
|
+
const pending = [];
|
|
2340
|
+
for (const inv of invoices2) {
|
|
2341
|
+
if (checkExpiry && isExpired(inv.submitBy)) {
|
|
2342
|
+
await storage.update(inv.id, { status: "EXPIRED" });
|
|
2343
|
+
result.expired++;
|
|
2344
|
+
result.results.push({
|
|
2345
|
+
invoiceId: inv.id,
|
|
2346
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2347
|
+
status: "EXPIRED"
|
|
2348
|
+
});
|
|
2349
|
+
} else {
|
|
2350
|
+
pending.push(inv);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
if (pending.length === 0) return result;
|
|
2354
|
+
await client.crypto.init();
|
|
2355
|
+
const encData = client.crypto.getEncryptionData();
|
|
2356
|
+
const formCode = options.formCode ?? DEFAULT_FORM_CODE;
|
|
2357
|
+
const openResp = await client.onlineSession.openSession(
|
|
2358
|
+
{ formCode, encryption: encData.encryptionInfo }
|
|
2359
|
+
);
|
|
2360
|
+
const sessionRef = openResp.referenceNumber;
|
|
2361
|
+
try {
|
|
2362
|
+
for (const inv of pending) {
|
|
2363
|
+
await storage.update(inv.id, { status: "QUEUED" });
|
|
2364
|
+
try {
|
|
2365
|
+
const data = new TextEncoder().encode(inv.invoiceXml);
|
|
2366
|
+
const plainMeta = client.crypto.getFileMetadata(data);
|
|
2367
|
+
const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
|
|
2368
|
+
const encMeta = client.crypto.getFileMetadata(encrypted);
|
|
2369
|
+
await storage.update(inv.id, { status: "SUBMITTED", submittedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2370
|
+
const resp = await client.onlineSession.sendInvoice(sessionRef, {
|
|
2371
|
+
invoiceHash: plainMeta.hashSHA,
|
|
2372
|
+
invoiceSize: plainMeta.fileSize,
|
|
2373
|
+
encryptedInvoiceHash: encMeta.hashSHA,
|
|
2374
|
+
encryptedInvoiceSize: encMeta.fileSize,
|
|
2375
|
+
encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
|
|
2376
|
+
offlineMode: true
|
|
2377
|
+
});
|
|
2378
|
+
await storage.update(inv.id, {
|
|
2379
|
+
status: "ACCEPTED",
|
|
2380
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2381
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2382
|
+
});
|
|
2383
|
+
result.submitted++;
|
|
2384
|
+
result.accepted++;
|
|
2385
|
+
result.results.push({
|
|
2386
|
+
invoiceId: inv.id,
|
|
2387
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2388
|
+
status: "ACCEPTED",
|
|
2389
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2390
|
+
});
|
|
2391
|
+
} catch (err) {
|
|
2392
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2393
|
+
if (err instanceof KSeFApiError) {
|
|
2394
|
+
await storage.update(inv.id, {
|
|
2395
|
+
status: "REJECTED",
|
|
2396
|
+
error: { code: err.statusCode, message }
|
|
2397
|
+
});
|
|
2398
|
+
result.submitted++;
|
|
2399
|
+
result.rejected++;
|
|
2400
|
+
result.results.push({
|
|
2401
|
+
invoiceId: inv.id,
|
|
2402
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2403
|
+
status: "REJECTED",
|
|
2404
|
+
error: { code: err.statusCode, message }
|
|
2405
|
+
});
|
|
2406
|
+
} else {
|
|
2407
|
+
await storage.update(inv.id, {
|
|
2408
|
+
status: "QUEUED",
|
|
2409
|
+
error: { code: 0, message }
|
|
2410
|
+
});
|
|
2411
|
+
result.failed++;
|
|
2412
|
+
result.results.push({
|
|
2413
|
+
invoiceId: inv.id,
|
|
2414
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2415
|
+
status: "QUEUED",
|
|
2416
|
+
error: { code: 0, message }
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
} finally {
|
|
2422
|
+
try {
|
|
2423
|
+
await client.onlineSession.closeSession(sessionRef);
|
|
2424
|
+
} catch {
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return result;
|
|
2428
|
+
}
|
|
2429
|
+
// TODO(perf): Each correction opens a separate KSeF session.
|
|
2430
|
+
// For batch corrections, consider a correctBatch() sharing one session (like submit()).
|
|
2431
|
+
async correct(client, options) {
|
|
2432
|
+
const { storage, rejectedInvoiceId, correctedInvoiceXml } = options;
|
|
2433
|
+
const original = await storage.get(rejectedInvoiceId);
|
|
2434
|
+
if (!original) {
|
|
2435
|
+
throw new Error(`Offline invoice not found: ${rejectedInvoiceId}`);
|
|
2436
|
+
}
|
|
2437
|
+
if (original.status !== "REJECTED") {
|
|
2438
|
+
throw new Error(
|
|
2439
|
+
original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
const originalHash = crypto3.createHash("sha256").update(original.invoiceXml).digest("base64");
|
|
2443
|
+
await client.crypto.init();
|
|
2444
|
+
const encData = client.crypto.getEncryptionData();
|
|
2445
|
+
const formCode = options.formCode ?? DEFAULT_FORM_CODE;
|
|
2446
|
+
const openResp = await client.onlineSession.openSession(
|
|
2447
|
+
{ formCode, encryption: encData.encryptionInfo }
|
|
2448
|
+
);
|
|
2449
|
+
const sessionRef = openResp.referenceNumber;
|
|
2450
|
+
try {
|
|
2451
|
+
const data = new TextEncoder().encode(correctedInvoiceXml);
|
|
2452
|
+
const plainMeta = client.crypto.getFileMetadata(data);
|
|
2453
|
+
const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
|
|
2454
|
+
const encMeta = client.crypto.getFileMetadata(encrypted);
|
|
2455
|
+
const correctionId = crypto3.randomUUID();
|
|
2456
|
+
const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2457
|
+
const correctedHashBase64 = crypto3.createHash("sha256").update(correctedInvoiceXml).digest("base64");
|
|
2458
|
+
const kod1Url = this.qrService.buildInvoiceVerificationUrl(
|
|
2459
|
+
original.sellerNip,
|
|
2460
|
+
original.invoiceDate,
|
|
2461
|
+
correctedHashBase64
|
|
2462
|
+
);
|
|
2463
|
+
let kod2Url;
|
|
2464
|
+
if (options.certificate) {
|
|
2465
|
+
kod2Url = this.qrService.buildCertificateVerificationUrl(
|
|
2466
|
+
original.sellerIdentifier.type,
|
|
2467
|
+
original.sellerIdentifier.value,
|
|
2468
|
+
original.sellerNip,
|
|
2469
|
+
options.certificate.certificateSerial,
|
|
2470
|
+
correctedHashBase64,
|
|
2471
|
+
options.certificate.privateKeyPem
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
const correctionMetadata = {
|
|
2475
|
+
id: correctionId,
|
|
2476
|
+
mode: original.mode,
|
|
2477
|
+
reason: original.reason,
|
|
2478
|
+
status: "SUBMITTED",
|
|
2479
|
+
invoiceNumber: original.invoiceNumber,
|
|
2480
|
+
invoiceDate: original.invoiceDate,
|
|
2481
|
+
invoiceXml: correctedInvoiceXml,
|
|
2482
|
+
sellerNip: original.sellerNip,
|
|
2483
|
+
sellerIdentifier: original.sellerIdentifier,
|
|
2484
|
+
buyerIdentifier: original.buyerIdentifier,
|
|
2485
|
+
kod1Url,
|
|
2486
|
+
kod2Url,
|
|
2487
|
+
generatedAt: submittedAt,
|
|
2488
|
+
submitBy: original.submitBy,
|
|
2489
|
+
submittedAt,
|
|
2490
|
+
correctedInvoiceId: rejectedInvoiceId
|
|
2491
|
+
};
|
|
2492
|
+
await storage.save(correctionMetadata);
|
|
2493
|
+
const resp = await client.onlineSession.sendInvoice(sessionRef, {
|
|
2494
|
+
invoiceHash: plainMeta.hashSHA,
|
|
2495
|
+
invoiceSize: plainMeta.fileSize,
|
|
2496
|
+
encryptedInvoiceHash: encMeta.hashSHA,
|
|
2497
|
+
encryptedInvoiceSize: encMeta.fileSize,
|
|
2498
|
+
encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
|
|
2499
|
+
offlineMode: true,
|
|
2500
|
+
hashOfCorrectedInvoice: originalHash
|
|
2501
|
+
});
|
|
2502
|
+
await storage.update(correctionId, {
|
|
2503
|
+
status: "ACCEPTED",
|
|
2504
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2505
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2506
|
+
});
|
|
2507
|
+
await storage.update(rejectedInvoiceId, {
|
|
2508
|
+
status: "CORRECTED",
|
|
2509
|
+
correctedBy: correctionId
|
|
2510
|
+
});
|
|
2511
|
+
return {
|
|
2512
|
+
invoiceId: correctionId,
|
|
2513
|
+
invoiceNumber: correctionMetadata.invoiceNumber,
|
|
2514
|
+
status: "ACCEPTED",
|
|
2515
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2516
|
+
};
|
|
2517
|
+
} finally {
|
|
2518
|
+
try {
|
|
2519
|
+
await client.onlineSession.closeSession(sessionRef);
|
|
2520
|
+
} catch {
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
|
|
2090
2528
|
// src/crypto/signature-service.ts
|
|
2091
2529
|
var signature_service_exports = {};
|
|
2092
2530
|
__export(signature_service_exports, {
|
|
2093
2531
|
SignatureService: () => SignatureService
|
|
2094
2532
|
});
|
|
2095
|
-
import * as
|
|
2533
|
+
import * as crypto4 from "crypto";
|
|
2096
2534
|
import { ExclusiveCanonicalization } from "xml-crypto";
|
|
2097
2535
|
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
|
|
2098
2536
|
function extractDerFromPem(pem) {
|
|
@@ -2112,7 +2550,7 @@ function canonicalize(elem) {
|
|
|
2112
2550
|
function computeRootDigest(doc) {
|
|
2113
2551
|
const root = doc.documentElement;
|
|
2114
2552
|
const canonical = canonicalize(root);
|
|
2115
|
-
return
|
|
2553
|
+
return crypto4.createHash("sha256").update(canonical, "utf-8").digest("base64");
|
|
2116
2554
|
}
|
|
2117
2555
|
function computeSignedPropertiesDigest(qualifyingPropertiesXml) {
|
|
2118
2556
|
const parser = new DOMParser();
|
|
@@ -2122,7 +2560,7 @@ function computeSignedPropertiesDigest(qualifyingPropertiesXml) {
|
|
|
2122
2560
|
throw new Error("SignedProperties element not found in QualifyingProperties");
|
|
2123
2561
|
}
|
|
2124
2562
|
const canonical = canonicalize(signedProps);
|
|
2125
|
-
return
|
|
2563
|
+
return crypto4.createHash("sha256").update(canonical, "utf-8").digest("base64");
|
|
2126
2564
|
}
|
|
2127
2565
|
function buildSignedInfo(signatureAlgorithm, rootDigest, signedPropertiesDigest) {
|
|
2128
2566
|
return [
|
|
@@ -2153,12 +2591,12 @@ function computeSignatureValue(canonicalSignedInfo, privateKey, isEc) {
|
|
|
2153
2591
|
const data = Buffer.from(canonicalSignedInfo, "utf-8");
|
|
2154
2592
|
let signature;
|
|
2155
2593
|
if (isEc) {
|
|
2156
|
-
signature =
|
|
2594
|
+
signature = crypto4.sign("sha256", data, {
|
|
2157
2595
|
key: privateKey,
|
|
2158
2596
|
dsaEncoding: "ieee-p1363"
|
|
2159
2597
|
});
|
|
2160
2598
|
} else {
|
|
2161
|
-
signature =
|
|
2599
|
+
signature = crypto4.sign("sha256", data, privateKey);
|
|
2162
2600
|
}
|
|
2163
2601
|
return signature.toString("base64");
|
|
2164
2602
|
}
|
|
@@ -2258,11 +2696,11 @@ var init_signature_service = __esm({
|
|
|
2258
2696
|
}
|
|
2259
2697
|
const certDer = extractDerFromPem(certPem);
|
|
2260
2698
|
const certBase64 = certDer.toString("base64");
|
|
2261
|
-
const certDigest =
|
|
2262
|
-
const x5093 = new
|
|
2699
|
+
const certDigest = crypto4.createHash("sha256").update(certDer).digest("base64");
|
|
2700
|
+
const x5093 = new crypto4.X509Certificate(certPem);
|
|
2263
2701
|
const issuerName = normalizeIssuerDn(x5093.issuer);
|
|
2264
2702
|
const serialNumber = hexSerialToDecimal(x5093.serialNumber);
|
|
2265
|
-
const privateKey =
|
|
2703
|
+
const privateKey = crypto4.createPrivateKey(
|
|
2266
2704
|
passphrase ? { key: privateKeyPem, format: "pem", passphrase } : privateKeyPem
|
|
2267
2705
|
);
|
|
2268
2706
|
const isEc = privateKey.asymmetricKeyType === "ec";
|
|
@@ -2427,6 +2865,7 @@ var init_client = __esm({
|
|
|
2427
2865
|
init_cryptography_service();
|
|
2428
2866
|
init_verification_link_service();
|
|
2429
2867
|
init_auth_xml_builder();
|
|
2868
|
+
init_offline_invoice_workflow();
|
|
2430
2869
|
KSeFClient = class {
|
|
2431
2870
|
auth;
|
|
2432
2871
|
activeSessions;
|
|
@@ -2445,6 +2884,7 @@ var init_client = __esm({
|
|
|
2445
2884
|
qr;
|
|
2446
2885
|
options;
|
|
2447
2886
|
authManager;
|
|
2887
|
+
_offline;
|
|
2448
2888
|
constructor(options) {
|
|
2449
2889
|
this.options = resolveOptions(options);
|
|
2450
2890
|
const authManager = options?.authManager ?? new DefaultAuthManager(async () => {
|
|
@@ -2473,6 +2913,12 @@ var init_client = __esm({
|
|
|
2473
2913
|
this.testData = new TestDataService(restClient, this.options.environmentName);
|
|
2474
2914
|
this.qr = new VerificationLinkService(this.options.baseQrUrl);
|
|
2475
2915
|
}
|
|
2916
|
+
get offline() {
|
|
2917
|
+
if (!this._offline) {
|
|
2918
|
+
this._offline = new OfflineInvoiceWorkflow(this.qr);
|
|
2919
|
+
}
|
|
2920
|
+
return this._offline;
|
|
2921
|
+
}
|
|
2476
2922
|
async loginWithToken(token, nip) {
|
|
2477
2923
|
const challenge2 = await this.auth.getChallenge();
|
|
2478
2924
|
await this.crypto.init();
|
|
@@ -2507,10 +2953,10 @@ var init_client = __esm({
|
|
|
2507
2953
|
}
|
|
2508
2954
|
async awaitAuthReady(referenceNumber, authToken) {
|
|
2509
2955
|
for (let i = 0; i < 30; i++) {
|
|
2510
|
-
const
|
|
2511
|
-
if (
|
|
2512
|
-
if (
|
|
2513
|
-
throw new Error(`Authentication failed with status ${
|
|
2956
|
+
const status7 = await this.auth.getAuthStatus(referenceNumber, authToken);
|
|
2957
|
+
if (status7.status.code === 200) return;
|
|
2958
|
+
if (status7.status.code !== 100) {
|
|
2959
|
+
throw new Error(`Authentication failed with status ${status7.status.code}: ${status7.status.description}`);
|
|
2514
2960
|
}
|
|
2515
2961
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
2516
2962
|
}
|
|
@@ -2545,79 +2991,6 @@ var init_polling = __esm({
|
|
|
2545
2991
|
}
|
|
2546
2992
|
});
|
|
2547
2993
|
|
|
2548
|
-
// src/models/document-structures/types.ts
|
|
2549
|
-
var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
|
|
2550
|
-
var init_types = __esm({
|
|
2551
|
-
"src/models/document-structures/types.ts"() {
|
|
2552
|
-
"use strict";
|
|
2553
|
-
SystemCode = {
|
|
2554
|
-
FA_2: "FA (2)",
|
|
2555
|
-
FA_3: "FA (3)",
|
|
2556
|
-
PEF_3: "PEF (3)",
|
|
2557
|
-
PEF_KOR_3: "PEF_KOR (3)",
|
|
2558
|
-
FA_RR_1: "FA_RR (1)"
|
|
2559
|
-
};
|
|
2560
|
-
FORM_CODES = {
|
|
2561
|
-
FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
|
|
2562
|
-
FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
|
|
2563
|
-
PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2564
|
-
PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2565
|
-
FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
|
|
2566
|
-
FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
|
|
2567
|
-
FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
|
|
2568
|
-
};
|
|
2569
|
-
DEFAULT_FORM_CODE = FORM_CODES.FA_3;
|
|
2570
|
-
INVOICE_TYPES_BY_SYSTEM_CODE = {
|
|
2571
|
-
[SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2572
|
-
[SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2573
|
-
[SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
|
|
2574
|
-
[SystemCode.PEF_KOR_3]: ["KorPef"],
|
|
2575
|
-
[SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
|
|
2576
|
-
};
|
|
2577
|
-
FORM_CODE_KEYS = {
|
|
2578
|
-
FA2: FORM_CODES.FA_2,
|
|
2579
|
-
FA3: FORM_CODES.FA_3,
|
|
2580
|
-
PEF3: FORM_CODES.PEF_3,
|
|
2581
|
-
PEFKOR3: FORM_CODES.PEF_KOR_3,
|
|
2582
|
-
FARR1: FORM_CODES.FA_RR_1
|
|
2583
|
-
};
|
|
2584
|
-
}
|
|
2585
|
-
});
|
|
2586
|
-
|
|
2587
|
-
// src/models/document-structures/helpers.ts
|
|
2588
|
-
function validateFormCodeForSession(formCode, sessionType) {
|
|
2589
|
-
if (sessionType === "online") return true;
|
|
2590
|
-
return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
|
|
2591
|
-
}
|
|
2592
|
-
var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
|
|
2593
|
-
var init_helpers = __esm({
|
|
2594
|
-
"src/models/document-structures/helpers.ts"() {
|
|
2595
|
-
"use strict";
|
|
2596
|
-
init_types();
|
|
2597
|
-
SYSTEM_CODE_TO_FORM_CODE = {
|
|
2598
|
-
[SystemCode.FA_2]: FORM_CODES.FA_2,
|
|
2599
|
-
[SystemCode.FA_3]: FORM_CODES.FA_3,
|
|
2600
|
-
[SystemCode.PEF_3]: FORM_CODES.PEF_3,
|
|
2601
|
-
[SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
|
|
2602
|
-
[SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
|
|
2603
|
-
};
|
|
2604
|
-
ALL_FORM_CODES = Object.values(FORM_CODES);
|
|
2605
|
-
BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
|
|
2606
|
-
SystemCode.PEF_3,
|
|
2607
|
-
SystemCode.PEF_KOR_3
|
|
2608
|
-
]);
|
|
2609
|
-
}
|
|
2610
|
-
});
|
|
2611
|
-
|
|
2612
|
-
// src/models/document-structures/index.ts
|
|
2613
|
-
var init_document_structures = __esm({
|
|
2614
|
-
"src/models/document-structures/index.ts"() {
|
|
2615
|
-
"use strict";
|
|
2616
|
-
init_types();
|
|
2617
|
-
init_helpers();
|
|
2618
|
-
}
|
|
2619
|
-
});
|
|
2620
|
-
|
|
2621
2994
|
// src/xml/upo-parser.ts
|
|
2622
2995
|
import { XMLParser } from "fast-xml-parser";
|
|
2623
2996
|
function isRecord(value) {
|
|
@@ -2742,11 +3115,58 @@ var init_upo_parser = __esm({
|
|
|
2742
3115
|
}
|
|
2743
3116
|
});
|
|
2744
3117
|
|
|
3118
|
+
// src/xml/invoice-field-extractor.ts
|
|
3119
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
3120
|
+
function extractInvoiceFields(xml) {
|
|
3121
|
+
const parsed = invoiceParser.parse(xml);
|
|
3122
|
+
const root = parsed?.Faktura;
|
|
3123
|
+
if (!root || typeof root !== "object") {
|
|
3124
|
+
throw new Error(
|
|
3125
|
+
"Cannot extract invoice fields: missing <Faktura> root element. Ensure the XML is a valid KSeF invoice (FA_2, FA_3, or FA_RR)."
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
if (root.Fa && typeof root.Fa === "object") {
|
|
3129
|
+
const invoiceDate = nonEmptyString(root.Fa.P_1);
|
|
3130
|
+
const invoiceNumber = nonEmptyString(root.Fa.P_2);
|
|
3131
|
+
if (invoiceDate && invoiceNumber) {
|
|
3132
|
+
return { invoiceNumber, invoiceDate };
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
if (root.FakturaRR && typeof root.FakturaRR === "object") {
|
|
3136
|
+
const invoiceDate = nonEmptyString(root.FakturaRR.P_4B);
|
|
3137
|
+
const invoiceNumber = nonEmptyString(root.FakturaRR.P_4C);
|
|
3138
|
+
if (invoiceDate && invoiceNumber) {
|
|
3139
|
+
return { invoiceNumber, invoiceDate };
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
throw new Error(
|
|
3143
|
+
"Cannot extract invoice number and date from XML. Expected <Fa><P_1>/<P_2> (FA format) or <FakturaRR><P_4B>/<P_4C> (RR format)."
|
|
3144
|
+
);
|
|
3145
|
+
}
|
|
3146
|
+
function nonEmptyString(value) {
|
|
3147
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3148
|
+
}
|
|
3149
|
+
var invoiceParser;
|
|
3150
|
+
var init_invoice_field_extractor = __esm({
|
|
3151
|
+
"src/xml/invoice-field-extractor.ts"() {
|
|
3152
|
+
"use strict";
|
|
3153
|
+
invoiceParser = new XMLParser2({
|
|
3154
|
+
ignoreAttributes: false,
|
|
3155
|
+
attributeNamePrefix: "@_",
|
|
3156
|
+
parseTagValue: false,
|
|
3157
|
+
parseAttributeValue: false,
|
|
3158
|
+
removeNSPrefix: true,
|
|
3159
|
+
trimValues: false
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
});
|
|
3163
|
+
|
|
2745
3164
|
// src/xml/index.ts
|
|
2746
3165
|
var init_xml = __esm({
|
|
2747
3166
|
"src/xml/index.ts"() {
|
|
2748
3167
|
"use strict";
|
|
2749
3168
|
init_upo_parser();
|
|
3169
|
+
init_invoice_field_extractor();
|
|
2750
3170
|
}
|
|
2751
3171
|
});
|
|
2752
3172
|
|
|
@@ -4488,11 +4908,11 @@ async function validateSchema(xml, options, _parsed) {
|
|
|
4488
4908
|
const prefix = rootElement ? `/${rootElement}/` : "/";
|
|
4489
4909
|
const validationErrors = result.error.issues.map((issue) => {
|
|
4490
4910
|
const zodPath = issue.path.join("/");
|
|
4491
|
-
const
|
|
4911
|
+
const path10 = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
|
|
4492
4912
|
return {
|
|
4493
4913
|
code: mapZodErrorCode(issue),
|
|
4494
4914
|
message: issue.message,
|
|
4495
|
-
path:
|
|
4915
|
+
path: path10
|
|
4496
4916
|
};
|
|
4497
4917
|
});
|
|
4498
4918
|
return { valid: false, schemaType, errors: validationErrors };
|
|
@@ -4532,9 +4952,9 @@ function validateBusinessRules(xml, _parsed) {
|
|
|
4532
4952
|
collectDateErrors(object, rootElement, errors);
|
|
4533
4953
|
return { valid: errors.length === 0, schemaType, errors };
|
|
4534
4954
|
}
|
|
4535
|
-
function collectNipPeselErrors(obj,
|
|
4955
|
+
function collectNipPeselErrors(obj, path10, errors) {
|
|
4536
4956
|
for (const [key, value] of Object.entries(obj)) {
|
|
4537
|
-
const currentPath =
|
|
4957
|
+
const currentPath = path10 ? `${path10}/${key}` : key;
|
|
4538
4958
|
if (key === "NIP" && typeof value === "string") {
|
|
4539
4959
|
if (!isValidNip(value)) {
|
|
4540
4960
|
errors.push({
|
|
@@ -4614,7 +5034,7 @@ var init_invoice_validator = __esm({
|
|
|
4614
5034
|
});
|
|
4615
5035
|
|
|
4616
5036
|
// src/builders/batch-file.ts
|
|
4617
|
-
import * as
|
|
5037
|
+
import * as crypto5 from "crypto";
|
|
4618
5038
|
function splitBuffer(data, maxPartSize) {
|
|
4619
5039
|
if (data.length <= maxPartSize) {
|
|
4620
5040
|
return [data];
|
|
@@ -4626,7 +5046,7 @@ function splitBuffer(data, maxPartSize) {
|
|
|
4626
5046
|
return parts;
|
|
4627
5047
|
}
|
|
4628
5048
|
function sha256Base64(data) {
|
|
4629
|
-
return
|
|
5049
|
+
return crypto5.createHash("sha256").update(data).digest("base64");
|
|
4630
5050
|
}
|
|
4631
5051
|
var BATCH_MAX_PART_SIZE, BATCH_MAX_TOTAL_SIZE, BATCH_MAX_PARTS, BatchFileBuilder;
|
|
4632
5052
|
var init_batch_file = __esm({
|
|
@@ -4937,10 +5357,10 @@ var init_batch_session_workflow = __esm({
|
|
|
4937
5357
|
});
|
|
4938
5358
|
|
|
4939
5359
|
// src/cli/index.ts
|
|
4940
|
-
import { readFileSync as
|
|
5360
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4941
5361
|
import { fileURLToPath } from "url";
|
|
4942
5362
|
import { dirname as dirname2, resolve } from "path";
|
|
4943
|
-
import { defineCommand as
|
|
5363
|
+
import { defineCommand as defineCommand18, runMain } from "citty";
|
|
4944
5364
|
|
|
4945
5365
|
// src/cli/commands/config.ts
|
|
4946
5366
|
import { defineCommand } from "citty";
|
|
@@ -5506,13 +5926,13 @@ var login = defineCommand2({
|
|
|
5506
5926
|
if (token) {
|
|
5507
5927
|
await client.loginWithToken(token, nip);
|
|
5508
5928
|
} else if (args.p12) {
|
|
5509
|
-
const
|
|
5510
|
-
const p12Buffer =
|
|
5929
|
+
const fs15 = await import("fs");
|
|
5930
|
+
const p12Buffer = fs15.readFileSync(args.p12);
|
|
5511
5931
|
await client.loginWithPkcs12(p12Buffer, args["p12-password"] ?? "", nip);
|
|
5512
5932
|
} else if (args.cert && args.key) {
|
|
5513
|
-
const
|
|
5514
|
-
const certPem =
|
|
5515
|
-
const keyPem =
|
|
5933
|
+
const fs15 = await import("fs");
|
|
5934
|
+
const certPem = fs15.readFileSync(args.cert, "utf-8");
|
|
5935
|
+
const keyPem = fs15.readFileSync(args.key, "utf-8");
|
|
5516
5936
|
await client.loginWithCertificate(certPem, keyPem, nip, args["key-password"]);
|
|
5517
5937
|
} else {
|
|
5518
5938
|
throw new Error("Provide --token, --p12, or both --cert and --key for authentication.");
|
|
@@ -7616,12 +8036,12 @@ import fs10 from "fs";
|
|
|
7616
8036
|
import path7 from "path";
|
|
7617
8037
|
|
|
7618
8038
|
// src/crypto/certificate-service.ts
|
|
7619
|
-
import * as
|
|
8039
|
+
import * as crypto6 from "crypto";
|
|
7620
8040
|
import * as x5092 from "@peculiar/x509";
|
|
7621
8041
|
var CertificateService = class {
|
|
7622
8042
|
static getSha256Fingerprint(certPem) {
|
|
7623
8043
|
const der = extractDerFromPem2(certPem);
|
|
7624
|
-
return
|
|
8044
|
+
return crypto6.createHash("sha256").update(der).digest("hex").toUpperCase();
|
|
7625
8045
|
}
|
|
7626
8046
|
static async generatePersonalCertificate(givenName, surname, serialNumber, commonName, method = "RSA") {
|
|
7627
8047
|
const nameParts = [];
|
|
@@ -7644,7 +8064,7 @@ var CertificateService = class {
|
|
|
7644
8064
|
}
|
|
7645
8065
|
};
|
|
7646
8066
|
async function generateSelfSigned(subject2, method) {
|
|
7647
|
-
x5092.cryptoProvider.set(
|
|
8067
|
+
x5092.cryptoProvider.set(crypto6.webcrypto);
|
|
7648
8068
|
let algorithm;
|
|
7649
8069
|
let signingAlgorithm;
|
|
7650
8070
|
if (method === "ECDSA") {
|
|
@@ -7659,7 +8079,7 @@ async function generateSelfSigned(subject2, method) {
|
|
|
7659
8079
|
};
|
|
7660
8080
|
signingAlgorithm = algorithm;
|
|
7661
8081
|
}
|
|
7662
|
-
const keys = await
|
|
8082
|
+
const keys = await crypto6.webcrypto.subtle.generateKey(
|
|
7663
8083
|
algorithm,
|
|
7664
8084
|
true,
|
|
7665
8085
|
["sign", "verify"]
|
|
@@ -7676,9 +8096,9 @@ async function generateSelfSigned(subject2, method) {
|
|
|
7676
8096
|
serialNumber: Date.now().toString(16)
|
|
7677
8097
|
});
|
|
7678
8098
|
const certPem = cert.toString("pem");
|
|
7679
|
-
const pkcs8 = await
|
|
8099
|
+
const pkcs8 = await crypto6.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
|
|
7680
8100
|
const privateKeyPem = pemEncode2(new Uint8Array(pkcs8), "PRIVATE KEY");
|
|
7681
|
-
const fingerprint =
|
|
8101
|
+
const fingerprint = crypto6.createHash("sha256").update(Buffer.from(cert.rawData)).digest("hex").toUpperCase();
|
|
7682
8102
|
return { certificatePem: certPem, privateKeyPem, fingerprint };
|
|
7683
8103
|
}
|
|
7684
8104
|
function extractDerFromPem2(pem) {
|
|
@@ -8136,6 +8556,7 @@ var invoice2 = defineCommand9({
|
|
|
8136
8556
|
format: { type: "string", description: "Output format: png or svg (default: png)" },
|
|
8137
8557
|
size: { type: "string", description: "QR code size in pixels (default: 300)" },
|
|
8138
8558
|
label: { type: "string", description: "Label text (SVG only)" },
|
|
8559
|
+
offline: { type: "boolean", description: 'Use "OFFLINE" as label (SVG, overrides --label)' },
|
|
8139
8560
|
o: { type: "string", description: "Output file path" },
|
|
8140
8561
|
env: { type: "string", description: "Environment (test/demo/prod)" },
|
|
8141
8562
|
json: { type: "boolean", description: "Output as JSON" },
|
|
@@ -8155,7 +8576,8 @@ var invoice2 = defineCommand9({
|
|
|
8155
8576
|
return;
|
|
8156
8577
|
}
|
|
8157
8578
|
if (format === "svg") {
|
|
8158
|
-
const
|
|
8579
|
+
const effectiveLabel = args.offline ? "OFFLINE" : args.label;
|
|
8580
|
+
const svg = effectiveLabel ? await QrCodeService.generateQrCodeSvgWithLabel(url2, effectiveLabel, { width: size }) : await QrCodeService.generateQrCodeSvg(url2, { width: size });
|
|
8159
8581
|
if (args.o) {
|
|
8160
8582
|
fs11.writeFileSync(args.o, svg);
|
|
8161
8583
|
outputSuccess(`QR code saved to ${args.o}
|
|
@@ -9022,11 +9444,11 @@ var doctorCommand = defineCommand14({
|
|
|
9022
9444
|
const controller = new AbortController();
|
|
9023
9445
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
9024
9446
|
try {
|
|
9025
|
-
const
|
|
9447
|
+
const status7 = await client.lighthouse.getStatus();
|
|
9026
9448
|
checks.push({
|
|
9027
9449
|
name: "connectivity",
|
|
9028
9450
|
status: "pass",
|
|
9029
|
-
message: `API reachable (${
|
|
9451
|
+
message: `API reachable (${status7.status})`
|
|
9030
9452
|
});
|
|
9031
9453
|
} finally {
|
|
9032
9454
|
clearTimeout(timeout);
|
|
@@ -9470,11 +9892,474 @@ var setupCommand = defineCommand16({
|
|
|
9470
9892
|
}
|
|
9471
9893
|
});
|
|
9472
9894
|
|
|
9895
|
+
// src/cli/commands/offline.ts
|
|
9896
|
+
import * as fs14 from "fs";
|
|
9897
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
9898
|
+
import { consola as consola15 } from "consola";
|
|
9899
|
+
|
|
9900
|
+
// src/offline/file-storage.ts
|
|
9901
|
+
import * as fs13 from "fs/promises";
|
|
9902
|
+
import * as path9 from "path";
|
|
9903
|
+
import * as os6 from "os";
|
|
9904
|
+
|
|
9905
|
+
// src/offline/storage.ts
|
|
9906
|
+
function matchesFilter(invoice3, filter) {
|
|
9907
|
+
if (filter.status !== void 0) {
|
|
9908
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
9909
|
+
if (!statuses.includes(invoice3.status)) return false;
|
|
9910
|
+
}
|
|
9911
|
+
if (filter.mode !== void 0 && invoice3.mode !== filter.mode) return false;
|
|
9912
|
+
if (filter.sellerNip !== void 0 && invoice3.sellerNip !== filter.sellerNip) return false;
|
|
9913
|
+
if (filter.expiringBefore !== void 0) {
|
|
9914
|
+
const cutoff = typeof filter.expiringBefore === "string" ? new Date(filter.expiringBefore).getTime() : filter.expiringBefore.getTime();
|
|
9915
|
+
if (new Date(invoice3.submitBy).getTime() >= cutoff) return false;
|
|
9916
|
+
}
|
|
9917
|
+
return true;
|
|
9918
|
+
}
|
|
9919
|
+
|
|
9920
|
+
// src/offline/file-storage.ts
|
|
9921
|
+
function resolveDir(dir) {
|
|
9922
|
+
if (dir === "~" || dir.startsWith("~/")) {
|
|
9923
|
+
return path9.join(os6.homedir(), dir.slice(1));
|
|
9924
|
+
}
|
|
9925
|
+
return dir;
|
|
9926
|
+
}
|
|
9927
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9928
|
+
function validateId(id) {
|
|
9929
|
+
if (!UUID_RE.test(id)) {
|
|
9930
|
+
throw new Error(`Invalid invoice ID: ${id}`);
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
var FileOfflineInvoiceStorage = class {
|
|
9934
|
+
dir;
|
|
9935
|
+
constructor(directory) {
|
|
9936
|
+
this.dir = resolveDir(directory ?? "~/.ksef/offline");
|
|
9937
|
+
}
|
|
9938
|
+
async ensureDir() {
|
|
9939
|
+
await fs13.mkdir(this.dir, { recursive: true });
|
|
9940
|
+
}
|
|
9941
|
+
filePath(id) {
|
|
9942
|
+
validateId(id);
|
|
9943
|
+
return path9.join(this.dir, `${id}.json`);
|
|
9944
|
+
}
|
|
9945
|
+
async save(invoice3) {
|
|
9946
|
+
await this.ensureDir();
|
|
9947
|
+
const file = this.filePath(invoice3.id);
|
|
9948
|
+
const tmp = `${file}.tmp`;
|
|
9949
|
+
await fs13.writeFile(tmp, JSON.stringify(invoice3, null, 2));
|
|
9950
|
+
await fs13.rename(tmp, file);
|
|
9951
|
+
}
|
|
9952
|
+
async get(id) {
|
|
9953
|
+
const file = this.filePath(id);
|
|
9954
|
+
try {
|
|
9955
|
+
return JSON.parse(await fs13.readFile(file, "utf-8"));
|
|
9956
|
+
} catch (err) {
|
|
9957
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
9958
|
+
return null;
|
|
9959
|
+
}
|
|
9960
|
+
console.warn(`Warning: Failed to read offline invoice ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9961
|
+
return null;
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9964
|
+
async list(filter) {
|
|
9965
|
+
let files;
|
|
9966
|
+
try {
|
|
9967
|
+
files = (await fs13.readdir(this.dir)).filter((f) => f.endsWith(".json"));
|
|
9968
|
+
} catch {
|
|
9969
|
+
return [];
|
|
9970
|
+
}
|
|
9971
|
+
const results = [];
|
|
9972
|
+
for (const file of files) {
|
|
9973
|
+
try {
|
|
9974
|
+
const data = JSON.parse(
|
|
9975
|
+
await fs13.readFile(path9.join(this.dir, file), "utf-8")
|
|
9976
|
+
);
|
|
9977
|
+
if (!filter || matchesFilter(data, filter)) {
|
|
9978
|
+
results.push(data);
|
|
9979
|
+
}
|
|
9980
|
+
} catch (err) {
|
|
9981
|
+
console.warn(`Warning: Skipping corrupt offline invoice file ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
return results;
|
|
9985
|
+
}
|
|
9986
|
+
/**
|
|
9987
|
+
* Update invoice metadata (read-modify-write).
|
|
9988
|
+
*
|
|
9989
|
+
* Note: No file locking — concurrent updates to the same ID may cause
|
|
9990
|
+
* lost writes. Acceptable for CLI (single process). Library consumers
|
|
9991
|
+
* running parallel operations should use external locking.
|
|
9992
|
+
*/
|
|
9993
|
+
async update(id, updates) {
|
|
9994
|
+
const existing = await this.get(id);
|
|
9995
|
+
if (!existing) throw new Error(`Offline invoice not found: ${id}`);
|
|
9996
|
+
await this.save({ ...existing, ...updates });
|
|
9997
|
+
}
|
|
9998
|
+
async delete(id) {
|
|
9999
|
+
const file = this.filePath(id);
|
|
10000
|
+
try {
|
|
10001
|
+
await fs13.unlink(file);
|
|
10002
|
+
} catch (e) {
|
|
10003
|
+
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
};
|
|
10007
|
+
|
|
10008
|
+
// src/cli/commands/offline.ts
|
|
10009
|
+
init_invoice_field_extractor();
|
|
10010
|
+
function getGlobalOpts14(args) {
|
|
10011
|
+
return {
|
|
10012
|
+
env: args.env,
|
|
10013
|
+
json: args.json,
|
|
10014
|
+
verbose: args.verbose,
|
|
10015
|
+
timeout: args.timeout,
|
|
10016
|
+
nip: args.nip
|
|
10017
|
+
};
|
|
10018
|
+
}
|
|
10019
|
+
function getStorage(args) {
|
|
10020
|
+
return new FileOfflineInvoiceStorage(args["store-dir"]);
|
|
10021
|
+
}
|
|
10022
|
+
var globalArgs = {
|
|
10023
|
+
env: { type: "string", description: "Environment (test/demo/prod)" },
|
|
10024
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
10025
|
+
verbose: { type: "boolean", description: "Verbose output" },
|
|
10026
|
+
timeout: { type: "string", description: "Request timeout (ms)" },
|
|
10027
|
+
nip: { type: "string", description: "NIP number" },
|
|
10028
|
+
"store-dir": { type: "string", description: "Offline invoice store directory" }
|
|
10029
|
+
};
|
|
10030
|
+
var generate3 = defineCommand17({
|
|
10031
|
+
meta: { name: "generate", description: "Generate offline invoice metadata with QR codes" },
|
|
10032
|
+
args: {
|
|
10033
|
+
...globalArgs,
|
|
10034
|
+
xml: { type: "positional", description: "Invoice XML file path", required: true },
|
|
10035
|
+
mode: { type: "string", description: "Offline mode (offline24/offline/awaryjny)" },
|
|
10036
|
+
key: { type: "string", description: "Private key PEM file for KOD II signing" },
|
|
10037
|
+
"cert-serial": { type: "string", description: "Certificate serial number (hex)" },
|
|
10038
|
+
"context-type": { type: "string", description: "Seller context type (default: Nip)" },
|
|
10039
|
+
"context-id": { type: "string", description: "Seller context value" },
|
|
10040
|
+
"qr-format": { type: "string", description: "QR format: png or svg (default: png)" },
|
|
10041
|
+
"qr-out": { type: "string", description: "Directory to save QR images" },
|
|
10042
|
+
"no-store": { type: "boolean", description: "Do not save to local store" }
|
|
10043
|
+
},
|
|
10044
|
+
run({ args }) {
|
|
10045
|
+
return withErrorHandler(async () => {
|
|
10046
|
+
const globalOpts = getGlobalOpts14(args);
|
|
10047
|
+
const config = loadConfig();
|
|
10048
|
+
const xmlPath = args.xml;
|
|
10049
|
+
if (!fs14.existsSync(xmlPath)) {
|
|
10050
|
+
throw new Error(`File not found: ${xmlPath}`);
|
|
10051
|
+
}
|
|
10052
|
+
const invoiceXml = fs14.readFileSync(xmlPath, "utf-8");
|
|
10053
|
+
const sellerNip = args.nip ?? config.nip;
|
|
10054
|
+
if (!sellerNip) {
|
|
10055
|
+
throw new Error("NIP is required. Use --nip or set via `ksef config set --nip`");
|
|
10056
|
+
}
|
|
10057
|
+
const contextType = args["context-type"] ?? "Nip";
|
|
10058
|
+
const contextId = args["context-id"] ?? sellerNip;
|
|
10059
|
+
const client = createClient(globalOpts);
|
|
10060
|
+
const workflow = client.offline;
|
|
10061
|
+
if (args.key && !args["cert-serial"]) {
|
|
10062
|
+
throw new Error("--cert-serial is required when using --key");
|
|
10063
|
+
}
|
|
10064
|
+
const certificate2 = args.key ? {
|
|
10065
|
+
privateKeyPem: fs14.readFileSync(args.key, "utf-8"),
|
|
10066
|
+
certificateSerial: args["cert-serial"]
|
|
10067
|
+
} : void 0;
|
|
10068
|
+
const validModes = ["offline24", "offline", "awaryjny", "awaria_calkowita"];
|
|
10069
|
+
const modeArg = args.mode ?? "offline24";
|
|
10070
|
+
if (!validModes.includes(modeArg)) {
|
|
10071
|
+
throw new Error(`Invalid --mode "${modeArg}". Must be one of: ${validModes.join(", ")}`);
|
|
10072
|
+
}
|
|
10073
|
+
const storage = args["no-store"] ? void 0 : getStorage(args);
|
|
10074
|
+
const { invoiceNumber, invoiceDate } = extractInvoiceFields(invoiceXml);
|
|
10075
|
+
const metadata = await workflow.generate(
|
|
10076
|
+
{
|
|
10077
|
+
invoiceNumber,
|
|
10078
|
+
invoiceDate,
|
|
10079
|
+
invoiceXml,
|
|
10080
|
+
sellerNip,
|
|
10081
|
+
sellerIdentifier: { type: contextType, value: contextId }
|
|
10082
|
+
},
|
|
10083
|
+
{
|
|
10084
|
+
mode: modeArg,
|
|
10085
|
+
certificate: certificate2,
|
|
10086
|
+
storage
|
|
10087
|
+
}
|
|
10088
|
+
);
|
|
10089
|
+
if (args["qr-out"]) {
|
|
10090
|
+
const outDir = args["qr-out"];
|
|
10091
|
+
fs14.mkdirSync(outDir, { recursive: true });
|
|
10092
|
+
const format = args["qr-format"] ?? "png";
|
|
10093
|
+
if (format !== "png" && format !== "svg") {
|
|
10094
|
+
throw new Error(`Invalid --qr-format "${format}". Must be one of: png, svg`);
|
|
10095
|
+
}
|
|
10096
|
+
const shortId = metadata.id.slice(0, 8);
|
|
10097
|
+
if (format === "svg") {
|
|
10098
|
+
const svg1 = await QrCodeService.generateQrCodeSvgWithLabel(metadata.kod1Url, "OFFLINE");
|
|
10099
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod1.svg`, svg1);
|
|
10100
|
+
if (metadata.kod2Url) {
|
|
10101
|
+
const svg2 = await QrCodeService.generateQrCodeSvgWithLabel(metadata.kod2Url, "CERTYFIKAT");
|
|
10102
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod2.svg`, svg2);
|
|
10103
|
+
}
|
|
10104
|
+
} else {
|
|
10105
|
+
const png1 = await QrCodeService.generateQrCode(metadata.kod1Url);
|
|
10106
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod1.png`, png1);
|
|
10107
|
+
if (metadata.kod2Url) {
|
|
10108
|
+
const png2 = await QrCodeService.generateQrCode(metadata.kod2Url);
|
|
10109
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod2.png`, png2);
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
outputSuccess(`QR codes saved to ${outDir}/`);
|
|
10113
|
+
}
|
|
10114
|
+
if (!certificate2) {
|
|
10115
|
+
outputWarning("No certificate provided \u2014 KOD II QR code was not generated. Use --key and --cert-serial for offline compliance.");
|
|
10116
|
+
}
|
|
10117
|
+
if (args.json) {
|
|
10118
|
+
outputResult(metadata, { json: true });
|
|
10119
|
+
} else {
|
|
10120
|
+
outputKeyValue({
|
|
10121
|
+
ID: metadata.id,
|
|
10122
|
+
Mode: metadata.mode,
|
|
10123
|
+
Status: metadata.status,
|
|
10124
|
+
Deadline: metadata.submitBy,
|
|
10125
|
+
"KOD I": metadata.kod1Url,
|
|
10126
|
+
"KOD II": metadata.kod2Url ?? "(not generated)"
|
|
10127
|
+
}, { json: false });
|
|
10128
|
+
}
|
|
10129
|
+
});
|
|
10130
|
+
}
|
|
10131
|
+
});
|
|
10132
|
+
var list4 = defineCommand17({
|
|
10133
|
+
meta: { name: "list", description: "List stored offline invoices" },
|
|
10134
|
+
args: {
|
|
10135
|
+
...globalArgs,
|
|
10136
|
+
status: { type: "string", description: "Filter by status" },
|
|
10137
|
+
mode: { type: "string", description: "Filter by mode" },
|
|
10138
|
+
expiring: { type: "boolean", description: "Show only invoices expiring within 24h" }
|
|
10139
|
+
},
|
|
10140
|
+
run({ args }) {
|
|
10141
|
+
return withErrorHandler(async () => {
|
|
10142
|
+
const storage = getStorage(args);
|
|
10143
|
+
const filter = {};
|
|
10144
|
+
if (args.status) filter.status = args.status;
|
|
10145
|
+
if (args.mode) filter.mode = args.mode;
|
|
10146
|
+
if (args.expiring) {
|
|
10147
|
+
const cutoff = new Date(Date.now() + 24 * 60 * 60 * 1e3);
|
|
10148
|
+
filter.expiringBefore = cutoff.toISOString();
|
|
10149
|
+
}
|
|
10150
|
+
const invoices2 = await storage.list(Object.keys(filter).length > 0 ? filter : void 0);
|
|
10151
|
+
if (invoices2.length === 0) {
|
|
10152
|
+
if (args.json) {
|
|
10153
|
+
outputResult([], { json: true });
|
|
10154
|
+
} else {
|
|
10155
|
+
outputSuccess("No offline invoices found.");
|
|
10156
|
+
}
|
|
10157
|
+
return;
|
|
10158
|
+
}
|
|
10159
|
+
outputTable(
|
|
10160
|
+
invoices2.map((i) => ({
|
|
10161
|
+
id: i.id.slice(0, 8),
|
|
10162
|
+
number: i.invoiceNumber,
|
|
10163
|
+
mode: i.mode,
|
|
10164
|
+
status: i.status,
|
|
10165
|
+
deadline: i.submitBy.slice(0, 19),
|
|
10166
|
+
nip: i.sellerNip
|
|
10167
|
+
})),
|
|
10168
|
+
[
|
|
10169
|
+
{ key: "id", label: "ID" },
|
|
10170
|
+
{ key: "number", label: "Number" },
|
|
10171
|
+
{ key: "mode", label: "Mode" },
|
|
10172
|
+
{ key: "status", label: "Status" },
|
|
10173
|
+
{ key: "deadline", label: "Deadline" },
|
|
10174
|
+
{ key: "nip", label: "NIP" }
|
|
10175
|
+
],
|
|
10176
|
+
{ json: args.json }
|
|
10177
|
+
);
|
|
10178
|
+
});
|
|
10179
|
+
}
|
|
10180
|
+
});
|
|
10181
|
+
var status6 = defineCommand17({
|
|
10182
|
+
meta: { name: "status", description: "Show offline invoice details" },
|
|
10183
|
+
args: {
|
|
10184
|
+
...globalArgs,
|
|
10185
|
+
id: { type: "positional", description: "Invoice ID", required: true }
|
|
10186
|
+
},
|
|
10187
|
+
run({ args }) {
|
|
10188
|
+
return withErrorHandler(async () => {
|
|
10189
|
+
const storage = getStorage(args);
|
|
10190
|
+
const invoice3 = await storage.get(args.id);
|
|
10191
|
+
if (!invoice3) {
|
|
10192
|
+
throw new Error(`Offline invoice not found: ${args.id}`);
|
|
10193
|
+
}
|
|
10194
|
+
if (args.json) {
|
|
10195
|
+
outputResult(invoice3, { json: true });
|
|
10196
|
+
} else {
|
|
10197
|
+
outputKeyValue({
|
|
10198
|
+
ID: invoice3.id,
|
|
10199
|
+
Number: invoice3.invoiceNumber,
|
|
10200
|
+
Date: invoice3.invoiceDate,
|
|
10201
|
+
Mode: invoice3.mode,
|
|
10202
|
+
Reason: invoice3.reason,
|
|
10203
|
+
Status: invoice3.status,
|
|
10204
|
+
"Seller NIP": invoice3.sellerNip,
|
|
10205
|
+
Deadline: invoice3.submitBy,
|
|
10206
|
+
"Generated At": invoice3.generatedAt,
|
|
10207
|
+
"Submitted At": invoice3.submittedAt ?? "-",
|
|
10208
|
+
"KSeF Reference": invoice3.ksefReferenceNumber ?? "-",
|
|
10209
|
+
"KOD I URL": invoice3.kod1Url,
|
|
10210
|
+
"KOD II URL": invoice3.kod2Url ?? "-",
|
|
10211
|
+
Error: invoice3.error ? `${invoice3.error.code}: ${invoice3.error.message}` : "-"
|
|
10212
|
+
}, { json: false });
|
|
10213
|
+
}
|
|
10214
|
+
});
|
|
10215
|
+
}
|
|
10216
|
+
});
|
|
10217
|
+
var submit = defineCommand17({
|
|
10218
|
+
meta: { name: "submit", description: "Submit offline invoices to KSeF" },
|
|
10219
|
+
args: {
|
|
10220
|
+
...globalArgs,
|
|
10221
|
+
all: { type: "boolean", description: "Submit all pending invoices" },
|
|
10222
|
+
"no-check-expiry": { type: "boolean", description: "Skip expiry check" },
|
|
10223
|
+
ids: { type: "positional", description: "Invoice IDs to submit" }
|
|
10224
|
+
},
|
|
10225
|
+
run({ args }) {
|
|
10226
|
+
return withErrorHandler(async () => {
|
|
10227
|
+
const globalOpts = getGlobalOpts14(args);
|
|
10228
|
+
const { client } = await requireSession(globalOpts);
|
|
10229
|
+
const storage = getStorage(args);
|
|
10230
|
+
const invoiceIds = args.ids ? args.ids.split(",").map((s) => s.trim()) : void 0;
|
|
10231
|
+
if (args.all && invoiceIds) {
|
|
10232
|
+
throw new Error("Cannot use --all together with specific invoice IDs. Use one or the other.");
|
|
10233
|
+
}
|
|
10234
|
+
if (!args.all && !invoiceIds) {
|
|
10235
|
+
throw new Error("Specify invoice IDs or use --all to submit all pending invoices.");
|
|
10236
|
+
}
|
|
10237
|
+
const result = await client.offline.submit(client, {
|
|
10238
|
+
storage,
|
|
10239
|
+
invoiceIds: args.all ? void 0 : invoiceIds,
|
|
10240
|
+
checkExpiry: !args["no-check-expiry"]
|
|
10241
|
+
});
|
|
10242
|
+
if (result.total === 0) {
|
|
10243
|
+
outputSuccess("No pending invoices to submit.");
|
|
10244
|
+
return;
|
|
10245
|
+
}
|
|
10246
|
+
if (args.json) {
|
|
10247
|
+
outputResult(result, { json: true });
|
|
10248
|
+
} else {
|
|
10249
|
+
outputSuccess(
|
|
10250
|
+
`Submitted: ${result.submitted}, Accepted: ${result.accepted}, Rejected: ${result.rejected}, Expired: ${result.expired}`
|
|
10251
|
+
);
|
|
10252
|
+
if (result.results.length > 0) {
|
|
10253
|
+
outputTable(
|
|
10254
|
+
result.results.map((r) => ({
|
|
10255
|
+
id: r.invoiceId.slice(0, 8),
|
|
10256
|
+
number: r.invoiceNumber,
|
|
10257
|
+
status: r.status,
|
|
10258
|
+
ref: r.ksefReferenceNumber ?? "-"
|
|
10259
|
+
})),
|
|
10260
|
+
[
|
|
10261
|
+
{ key: "id", label: "ID" },
|
|
10262
|
+
{ key: "number", label: "Number" },
|
|
10263
|
+
{ key: "status", label: "Status" },
|
|
10264
|
+
{ key: "ref", label: "KSeF Ref" }
|
|
10265
|
+
],
|
|
10266
|
+
{ json: false }
|
|
10267
|
+
);
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
});
|
|
10271
|
+
}
|
|
10272
|
+
});
|
|
10273
|
+
var correct = defineCommand17({
|
|
10274
|
+
meta: { name: "correct", description: "Technical correction for rejected offline invoice" },
|
|
10275
|
+
args: {
|
|
10276
|
+
...globalArgs,
|
|
10277
|
+
id: { type: "positional", description: "Rejected invoice ID", required: true },
|
|
10278
|
+
xml: { type: "positional", description: "Corrected XML file path", required: true }
|
|
10279
|
+
},
|
|
10280
|
+
run({ args }) {
|
|
10281
|
+
return withErrorHandler(async () => {
|
|
10282
|
+
const globalOpts = getGlobalOpts14(args);
|
|
10283
|
+
const { client } = await requireSession(globalOpts);
|
|
10284
|
+
const storage = getStorage(args);
|
|
10285
|
+
const xmlPath = args.xml;
|
|
10286
|
+
if (!fs14.existsSync(xmlPath)) {
|
|
10287
|
+
throw new Error(`File not found: ${xmlPath}`);
|
|
10288
|
+
}
|
|
10289
|
+
const correctedXml = fs14.readFileSync(xmlPath, "utf-8");
|
|
10290
|
+
const result = await client.offline.correct(client, {
|
|
10291
|
+
rejectedInvoiceId: args.id,
|
|
10292
|
+
correctedInvoiceXml: correctedXml,
|
|
10293
|
+
storage
|
|
10294
|
+
});
|
|
10295
|
+
if (args.json) {
|
|
10296
|
+
outputResult(result, { json: true });
|
|
10297
|
+
} else {
|
|
10298
|
+
outputSuccess(`Correction submitted. KSeF ref: ${result.ksefReferenceNumber ?? "pending"}`);
|
|
10299
|
+
}
|
|
10300
|
+
});
|
|
10301
|
+
}
|
|
10302
|
+
});
|
|
10303
|
+
var del = defineCommand17({
|
|
10304
|
+
meta: { name: "delete", description: "Delete offline invoice from local store" },
|
|
10305
|
+
args: {
|
|
10306
|
+
...globalArgs,
|
|
10307
|
+
id: { type: "positional", description: "Invoice ID to delete" },
|
|
10308
|
+
expired: { type: "boolean", description: "Delete all expired invoices" },
|
|
10309
|
+
force: { type: "boolean", description: "Skip confirmation prompt" }
|
|
10310
|
+
},
|
|
10311
|
+
run({ args }) {
|
|
10312
|
+
return withErrorHandler(async () => {
|
|
10313
|
+
const storage = getStorage(args);
|
|
10314
|
+
if (args.expired) {
|
|
10315
|
+
const expired = await storage.list({ status: "EXPIRED" });
|
|
10316
|
+
if (expired.length === 0) {
|
|
10317
|
+
outputSuccess("No expired invoices to delete.");
|
|
10318
|
+
return;
|
|
10319
|
+
}
|
|
10320
|
+
if (!args.force) {
|
|
10321
|
+
if (!process.stdin.isTTY) {
|
|
10322
|
+
throw new Error(
|
|
10323
|
+
`${expired.length} expired invoice(s) would be deleted. Use --force to confirm in non-interactive mode.`
|
|
10324
|
+
);
|
|
10325
|
+
}
|
|
10326
|
+
const confirmed = await consola15.prompt(
|
|
10327
|
+
`Delete ${expired.length} expired invoice(s)?`,
|
|
10328
|
+
{ type: "confirm", initial: false }
|
|
10329
|
+
);
|
|
10330
|
+
if (!confirmed) {
|
|
10331
|
+
consola15.info("Cancelled.");
|
|
10332
|
+
return;
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
for (const inv of expired) {
|
|
10336
|
+
await storage.delete(inv.id);
|
|
10337
|
+
}
|
|
10338
|
+
outputSuccess(`Deleted ${expired.length} expired invoice(s).`);
|
|
10339
|
+
return;
|
|
10340
|
+
}
|
|
10341
|
+
if (!args.id) {
|
|
10342
|
+
throw new Error("Specify an invoice ID or use --expired to delete all expired invoices.");
|
|
10343
|
+
}
|
|
10344
|
+
const invoice3 = await storage.get(args.id);
|
|
10345
|
+
if (!invoice3) {
|
|
10346
|
+
throw new Error(`Offline invoice not found: ${args.id}`);
|
|
10347
|
+
}
|
|
10348
|
+
await storage.delete(args.id);
|
|
10349
|
+
outputSuccess(`Deleted offline invoice ${args.id}`);
|
|
10350
|
+
});
|
|
10351
|
+
}
|
|
10352
|
+
});
|
|
10353
|
+
var offlineCommand = defineCommand17({
|
|
10354
|
+
meta: { name: "offline", description: "Offline invoice management" },
|
|
10355
|
+
subCommands: { generate: generate3, list: list4, status: status6, submit, correct, delete: del }
|
|
10356
|
+
});
|
|
10357
|
+
|
|
9473
10358
|
// src/cli/index.ts
|
|
9474
10359
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
9475
|
-
var pkg = JSON.parse(
|
|
10360
|
+
var pkg = JSON.parse(readFileSync9(resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
9476
10361
|
var version = pkg.version;
|
|
9477
|
-
var main =
|
|
10362
|
+
var main = defineCommand18({
|
|
9478
10363
|
meta: {
|
|
9479
10364
|
name: "ksef",
|
|
9480
10365
|
version,
|
|
@@ -9494,6 +10379,7 @@ var main = defineCommand17({
|
|
|
9494
10379
|
limits: limitsCommand,
|
|
9495
10380
|
peppol: peppolCommand,
|
|
9496
10381
|
"test-data": testDataCommand,
|
|
10382
|
+
offline: offlineCommand,
|
|
9497
10383
|
doctor: doctorCommand,
|
|
9498
10384
|
completion: completionCommand
|
|
9499
10385
|
}
|