ksef-client-ts 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +1198 -158
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +962 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +307 -7
- package/dist/index.d.ts +307 -7
- package/dist/index.js +945 -119
- 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;
|
|
@@ -988,6 +988,32 @@ var init_online_session = __esm({
|
|
|
988
988
|
}
|
|
989
989
|
});
|
|
990
990
|
|
|
991
|
+
// src/utils/concurrency.ts
|
|
992
|
+
async function runWithConcurrency(tasks, parallelism) {
|
|
993
|
+
if (tasks.length === 0) return;
|
|
994
|
+
const limit = Math.max(1, Math.min(parallelism, tasks.length));
|
|
995
|
+
const controller = new AbortController();
|
|
996
|
+
let index = 0;
|
|
997
|
+
const workers = Array.from({ length: limit }, async () => {
|
|
998
|
+
while (index < tasks.length && !controller.signal.aborted) {
|
|
999
|
+
const current = index;
|
|
1000
|
+
index += 1;
|
|
1001
|
+
try {
|
|
1002
|
+
await tasks[current](controller.signal);
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
controller.abort();
|
|
1005
|
+
throw err;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
await Promise.all(workers);
|
|
1010
|
+
}
|
|
1011
|
+
var init_concurrency = __esm({
|
|
1012
|
+
"src/utils/concurrency.ts"() {
|
|
1013
|
+
"use strict";
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
|
|
991
1017
|
// src/services/batch-session.ts
|
|
992
1018
|
var BatchSessionService;
|
|
993
1019
|
var init_batch_session = __esm({
|
|
@@ -996,6 +1022,7 @@ var init_batch_session = __esm({
|
|
|
996
1022
|
init_ksef_feature();
|
|
997
1023
|
init_rest_request();
|
|
998
1024
|
init_routes();
|
|
1025
|
+
init_concurrency();
|
|
999
1026
|
BatchSessionService = class {
|
|
1000
1027
|
restClient;
|
|
1001
1028
|
constructor(restClient) {
|
|
@@ -1009,12 +1036,10 @@ var init_batch_session = __esm({
|
|
|
1009
1036
|
const response = await this.restClient.execute(req);
|
|
1010
1037
|
return response.body;
|
|
1011
1038
|
}
|
|
1012
|
-
async sendParts(openResponse, parts) {
|
|
1013
|
-
const
|
|
1014
|
-
const tasks = parts.map(async (
|
|
1015
|
-
const uploadReq =
|
|
1016
|
-
(r) => r.ordinalNumber === part.ordinalNumber
|
|
1017
|
-
);
|
|
1039
|
+
async sendParts(openResponse, parts, parallelism) {
|
|
1040
|
+
const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
|
|
1041
|
+
const tasks = parts.map((part) => async (signal) => {
|
|
1042
|
+
const uploadReq = uploadMap.get(part.ordinalNumber);
|
|
1018
1043
|
if (!uploadReq) {
|
|
1019
1044
|
throw new Error(`No upload request found for part ${part.ordinalNumber}`);
|
|
1020
1045
|
}
|
|
@@ -1022,25 +1047,38 @@ var init_batch_session = __esm({
|
|
|
1022
1047
|
for (const [k, v] of Object.entries(uploadReq.headers)) {
|
|
1023
1048
|
if (v != null) headers[k] = v;
|
|
1024
1049
|
}
|
|
1025
|
-
await fetch(uploadReq.url, {
|
|
1050
|
+
const resp = await fetch(uploadReq.url, {
|
|
1026
1051
|
method: uploadReq.method,
|
|
1027
1052
|
headers,
|
|
1028
|
-
body: part.data
|
|
1053
|
+
body: part.data,
|
|
1054
|
+
signal
|
|
1029
1055
|
});
|
|
1056
|
+
if (!resp.ok) {
|
|
1057
|
+
const body = await resp.text().catch(() => "");
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1030
1062
|
});
|
|
1031
|
-
|
|
1063
|
+
if (parallelism !== void 0) {
|
|
1064
|
+
await runWithConcurrency(tasks, parallelism);
|
|
1065
|
+
} else {
|
|
1066
|
+
const ac = new AbortController();
|
|
1067
|
+
await Promise.all(tasks.map((t) => t(ac.signal).catch((err) => {
|
|
1068
|
+
ac.abort();
|
|
1069
|
+
throw err;
|
|
1070
|
+
})));
|
|
1071
|
+
}
|
|
1032
1072
|
}
|
|
1033
1073
|
/**
|
|
1034
|
-
* Upload parts
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1074
|
+
* Upload parts using streaming bodies (`duplex: 'half'`).
|
|
1075
|
+
* By default uploads sequentially to avoid backpressure issues.
|
|
1076
|
+
* Pass `parallelism` to enable bounded concurrent uploads.
|
|
1037
1077
|
*/
|
|
1038
|
-
async sendPartsWithStream(openResponse, parts) {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
const uploadReq =
|
|
1042
|
-
(r) => r.ordinalNumber === part.ordinalNumber
|
|
1043
|
-
);
|
|
1078
|
+
async sendPartsWithStream(openResponse, parts, parallelism) {
|
|
1079
|
+
const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
|
|
1080
|
+
const uploadPart = async (part, signal) => {
|
|
1081
|
+
const uploadReq = uploadMap.get(part.ordinalNumber);
|
|
1044
1082
|
if (!uploadReq) {
|
|
1045
1083
|
throw new Error(`No upload request found for part ${part.ordinalNumber}`);
|
|
1046
1084
|
}
|
|
@@ -1052,11 +1090,23 @@ var init_batch_session = __esm({
|
|
|
1052
1090
|
method: uploadReq.method,
|
|
1053
1091
|
headers,
|
|
1054
1092
|
body: part.dataStream,
|
|
1093
|
+
signal,
|
|
1055
1094
|
// @ts-expect-error -- Node 18+ undici supports duplex for streaming body
|
|
1056
1095
|
duplex: "half"
|
|
1057
1096
|
});
|
|
1058
1097
|
if (!resp.ok) {
|
|
1059
|
-
|
|
1098
|
+
const body = await resp.text().catch(() => "");
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
if (parallelism !== void 0) {
|
|
1105
|
+
await runWithConcurrency(parts.map((p) => (signal) => uploadPart(p, signal)), parallelism);
|
|
1106
|
+
} else {
|
|
1107
|
+
const { signal } = new AbortController();
|
|
1108
|
+
for (const part of parts) {
|
|
1109
|
+
await uploadPart(part, signal);
|
|
1060
1110
|
}
|
|
1061
1111
|
}
|
|
1062
1112
|
}
|
|
@@ -1094,8 +1144,8 @@ var init_session_status = __esm({
|
|
|
1094
1144
|
if (filter.dateModifiedFrom) req.query("dateModifiedFrom", filter.dateModifiedFrom);
|
|
1095
1145
|
if (filter.dateModifiedTo) req.query("dateModifiedTo", filter.dateModifiedTo);
|
|
1096
1146
|
if (filter.statuses) {
|
|
1097
|
-
for (const
|
|
1098
|
-
req.query("statuses",
|
|
1147
|
+
for (const status7 of filter.statuses) {
|
|
1148
|
+
req.query("statuses", status7);
|
|
1099
1149
|
}
|
|
1100
1150
|
}
|
|
1101
1151
|
}
|
|
@@ -1169,7 +1219,10 @@ var init_invoice_download = __esm({
|
|
|
1169
1219
|
async getInvoice(ksefNumber) {
|
|
1170
1220
|
const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
|
|
1171
1221
|
const response = await this.restClient.executeRaw(req);
|
|
1172
|
-
return
|
|
1222
|
+
return {
|
|
1223
|
+
xml: new TextDecoder().decode(response.body),
|
|
1224
|
+
hash: response.headers.get("x-ms-meta-hash") ?? void 0
|
|
1225
|
+
};
|
|
1173
1226
|
}
|
|
1174
1227
|
async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
|
|
1175
1228
|
const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
|
|
@@ -1469,20 +1522,20 @@ var init_lighthouse = __esm({
|
|
|
1469
1522
|
this.lighthouseUrl = options.lighthouseUrl;
|
|
1470
1523
|
this.timeout = options.timeout;
|
|
1471
1524
|
}
|
|
1472
|
-
async fetchJson(
|
|
1525
|
+
async fetchJson(path10) {
|
|
1473
1526
|
if (!this.lighthouseUrl) {
|
|
1474
1527
|
throw new KSeFError(
|
|
1475
1528
|
"Lighthouse API is not available for the DEMO environment. Use TEST or PROD instead."
|
|
1476
1529
|
);
|
|
1477
1530
|
}
|
|
1478
|
-
const response = await fetch(`${this.lighthouseUrl}${
|
|
1531
|
+
const response = await fetch(`${this.lighthouseUrl}${path10}`, {
|
|
1479
1532
|
headers: { Accept: "application/json" },
|
|
1480
1533
|
signal: AbortSignal.timeout(this.timeout)
|
|
1481
1534
|
});
|
|
1482
1535
|
if (!response.ok) {
|
|
1483
1536
|
const body = await response.text();
|
|
1484
1537
|
throw new KSeFError(
|
|
1485
|
-
`Lighthouse ${
|
|
1538
|
+
`Lighthouse ${path10} failed: HTTP ${response.status} \u2014 ${body}`
|
|
1486
1539
|
);
|
|
1487
1540
|
}
|
|
1488
1541
|
return await response.json();
|
|
@@ -2087,12 +2140,517 @@ var init_auth_xml_builder = __esm({
|
|
|
2087
2140
|
}
|
|
2088
2141
|
});
|
|
2089
2142
|
|
|
2143
|
+
// src/offline/holidays.ts
|
|
2144
|
+
function toDateKey(d) {
|
|
2145
|
+
return d.toISOString().slice(0, 10);
|
|
2146
|
+
}
|
|
2147
|
+
function dateKey(year, month, day) {
|
|
2148
|
+
return toDateKey(new Date(Date.UTC(year, month - 1, day)));
|
|
2149
|
+
}
|
|
2150
|
+
function addDays(d, n) {
|
|
2151
|
+
const result = new Date(d);
|
|
2152
|
+
result.setUTCDate(result.getUTCDate() + n);
|
|
2153
|
+
return result;
|
|
2154
|
+
}
|
|
2155
|
+
function computeEasterSunday(year) {
|
|
2156
|
+
const a = year % 19;
|
|
2157
|
+
const b = Math.floor(year / 100);
|
|
2158
|
+
const c = year % 100;
|
|
2159
|
+
const d = Math.floor(b / 4);
|
|
2160
|
+
const e = b % 4;
|
|
2161
|
+
const f = Math.floor((b + 8) / 25);
|
|
2162
|
+
const g = Math.floor((b - f + 1) / 3);
|
|
2163
|
+
const h = (19 * a + b - d - g + 15) % 30;
|
|
2164
|
+
const i = Math.floor(c / 4);
|
|
2165
|
+
const k = c % 4;
|
|
2166
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
2167
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
2168
|
+
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
2169
|
+
const day = (h + l - 7 * m + 114) % 31 + 1;
|
|
2170
|
+
return new Date(Date.UTC(year, month - 1, day));
|
|
2171
|
+
}
|
|
2172
|
+
function getPolishHolidays(year) {
|
|
2173
|
+
const cached = holidayCache.get(year);
|
|
2174
|
+
if (cached) return cached;
|
|
2175
|
+
const easter = computeEasterSunday(year);
|
|
2176
|
+
const holidays = /* @__PURE__ */ new Set([
|
|
2177
|
+
// Fixed
|
|
2178
|
+
dateKey(year, 1, 1),
|
|
2179
|
+
dateKey(year, 1, 6),
|
|
2180
|
+
dateKey(year, 5, 1),
|
|
2181
|
+
dateKey(year, 5, 3),
|
|
2182
|
+
dateKey(year, 8, 15),
|
|
2183
|
+
dateKey(year, 11, 1),
|
|
2184
|
+
dateKey(year, 11, 11),
|
|
2185
|
+
dateKey(year, 12, 25),
|
|
2186
|
+
dateKey(year, 12, 26),
|
|
2187
|
+
// Wigilia — added by Dz.U. 2024 poz. 1965, effective from 2025
|
|
2188
|
+
...year >= 2025 ? [dateKey(year, 12, 24)] : [],
|
|
2189
|
+
// Moveable
|
|
2190
|
+
toDateKey(easter),
|
|
2191
|
+
toDateKey(addDays(easter, 1)),
|
|
2192
|
+
toDateKey(addDays(easter, 49)),
|
|
2193
|
+
toDateKey(addDays(easter, 60))
|
|
2194
|
+
]);
|
|
2195
|
+
holidayCache.set(year, holidays);
|
|
2196
|
+
return holidays;
|
|
2197
|
+
}
|
|
2198
|
+
function isPolishHoliday(date) {
|
|
2199
|
+
return getPolishHolidays(date.getUTCFullYear()).has(toDateKey(date));
|
|
2200
|
+
}
|
|
2201
|
+
var holidayCache;
|
|
2202
|
+
var init_holidays = __esm({
|
|
2203
|
+
"src/offline/holidays.ts"() {
|
|
2204
|
+
"use strict";
|
|
2205
|
+
holidayCache = /* @__PURE__ */ new Map();
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
|
|
2209
|
+
// src/offline/deadline.ts
|
|
2210
|
+
function getDefaultReason(mode) {
|
|
2211
|
+
switch (mode) {
|
|
2212
|
+
case "offline24":
|
|
2213
|
+
return "PLANNED";
|
|
2214
|
+
case "offline":
|
|
2215
|
+
return "SYSTEM_UNAVAILABLE";
|
|
2216
|
+
case "awaryjny":
|
|
2217
|
+
return "EMERGENCY";
|
|
2218
|
+
case "awaria_calkowita":
|
|
2219
|
+
return "TOTAL_FAILURE";
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
function nextBusinessDay(from) {
|
|
2223
|
+
const d = new Date(from);
|
|
2224
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
2225
|
+
while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
|
|
2226
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
2227
|
+
}
|
|
2228
|
+
return d;
|
|
2229
|
+
}
|
|
2230
|
+
function addBusinessDays(from, days) {
|
|
2231
|
+
if (!Number.isInteger(days) || days < 0) {
|
|
2232
|
+
throw new Error(`days must be a non-negative integer, got ${days}`);
|
|
2233
|
+
}
|
|
2234
|
+
const d = new Date(from);
|
|
2235
|
+
let remaining = days;
|
|
2236
|
+
while (remaining > 0) {
|
|
2237
|
+
d.setUTCDate(d.getUTCDate() + 1);
|
|
2238
|
+
if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
|
|
2239
|
+
remaining--;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
return d;
|
|
2243
|
+
}
|
|
2244
|
+
function endOfDay(d) {
|
|
2245
|
+
const result = new Date(d);
|
|
2246
|
+
result.setUTCHours(23, 59, 59, 999);
|
|
2247
|
+
return result;
|
|
2248
|
+
}
|
|
2249
|
+
function getMaintenanceEndFallback(mw) {
|
|
2250
|
+
if (mw.endTime) {
|
|
2251
|
+
return new Date(mw.endTime);
|
|
2252
|
+
}
|
|
2253
|
+
const start = new Date(mw.startTime);
|
|
2254
|
+
start.setUTCDate(start.getUTCDate() + 7);
|
|
2255
|
+
return start;
|
|
2256
|
+
}
|
|
2257
|
+
function calculateOfflineDeadline(mode, invoiceDate, maintenanceWindow) {
|
|
2258
|
+
const date = typeof invoiceDate === "string" ? new Date(invoiceDate) : invoiceDate;
|
|
2259
|
+
switch (mode) {
|
|
2260
|
+
case "offline24": {
|
|
2261
|
+
return endOfDay(nextBusinessDay(date));
|
|
2262
|
+
}
|
|
2263
|
+
case "offline": {
|
|
2264
|
+
const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
|
|
2265
|
+
return endOfDay(nextBusinessDay(base));
|
|
2266
|
+
}
|
|
2267
|
+
case "awaryjny": {
|
|
2268
|
+
const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
|
|
2269
|
+
return endOfDay(addBusinessDays(base, 7));
|
|
2270
|
+
}
|
|
2271
|
+
case "awaria_calkowita": {
|
|
2272
|
+
return new Date(FAR_FUTURE);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
function isExpired(submitBy) {
|
|
2277
|
+
const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
|
|
2278
|
+
return Date.now() > deadline.getTime();
|
|
2279
|
+
}
|
|
2280
|
+
var FAR_FUTURE;
|
|
2281
|
+
var init_deadline = __esm({
|
|
2282
|
+
"src/offline/deadline.ts"() {
|
|
2283
|
+
"use strict";
|
|
2284
|
+
init_holidays();
|
|
2285
|
+
FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
|
|
2286
|
+
}
|
|
2287
|
+
});
|
|
2288
|
+
|
|
2289
|
+
// src/models/document-structures/types.ts
|
|
2290
|
+
var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
|
|
2291
|
+
var init_types = __esm({
|
|
2292
|
+
"src/models/document-structures/types.ts"() {
|
|
2293
|
+
"use strict";
|
|
2294
|
+
SystemCode = {
|
|
2295
|
+
FA_2: "FA (2)",
|
|
2296
|
+
FA_3: "FA (3)",
|
|
2297
|
+
PEF_3: "PEF (3)",
|
|
2298
|
+
PEF_KOR_3: "PEF_KOR (3)",
|
|
2299
|
+
FA_RR_1: "FA_RR (1)"
|
|
2300
|
+
};
|
|
2301
|
+
FORM_CODES = {
|
|
2302
|
+
FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
|
|
2303
|
+
FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
|
|
2304
|
+
PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2305
|
+
PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
|
|
2306
|
+
FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
|
|
2307
|
+
FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
|
|
2308
|
+
FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
|
|
2309
|
+
};
|
|
2310
|
+
DEFAULT_FORM_CODE = FORM_CODES.FA_3;
|
|
2311
|
+
INVOICE_TYPES_BY_SYSTEM_CODE = {
|
|
2312
|
+
[SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2313
|
+
[SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
|
|
2314
|
+
[SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
|
|
2315
|
+
[SystemCode.PEF_KOR_3]: ["KorPef"],
|
|
2316
|
+
[SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
|
|
2317
|
+
};
|
|
2318
|
+
FORM_CODE_KEYS = {
|
|
2319
|
+
FA2: FORM_CODES.FA_2,
|
|
2320
|
+
FA3: FORM_CODES.FA_3,
|
|
2321
|
+
PEF3: FORM_CODES.PEF_3,
|
|
2322
|
+
PEFKOR3: FORM_CODES.PEF_KOR_3,
|
|
2323
|
+
FARR1: FORM_CODES.FA_RR_1
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
|
|
2328
|
+
// src/models/document-structures/helpers.ts
|
|
2329
|
+
function validateFormCodeForSession(formCode, sessionType) {
|
|
2330
|
+
if (sessionType === "online") return true;
|
|
2331
|
+
return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
|
|
2332
|
+
}
|
|
2333
|
+
var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
|
|
2334
|
+
var init_helpers = __esm({
|
|
2335
|
+
"src/models/document-structures/helpers.ts"() {
|
|
2336
|
+
"use strict";
|
|
2337
|
+
init_types();
|
|
2338
|
+
SYSTEM_CODE_TO_FORM_CODE = {
|
|
2339
|
+
[SystemCode.FA_2]: FORM_CODES.FA_2,
|
|
2340
|
+
[SystemCode.FA_3]: FORM_CODES.FA_3,
|
|
2341
|
+
[SystemCode.PEF_3]: FORM_CODES.PEF_3,
|
|
2342
|
+
[SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
|
|
2343
|
+
[SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
|
|
2344
|
+
};
|
|
2345
|
+
ALL_FORM_CODES = Object.values(FORM_CODES);
|
|
2346
|
+
BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
|
|
2347
|
+
SystemCode.PEF_3,
|
|
2348
|
+
SystemCode.PEF_KOR_3
|
|
2349
|
+
]);
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
|
|
2353
|
+
// src/models/document-structures/index.ts
|
|
2354
|
+
var init_document_structures = __esm({
|
|
2355
|
+
"src/models/document-structures/index.ts"() {
|
|
2356
|
+
"use strict";
|
|
2357
|
+
init_types();
|
|
2358
|
+
init_helpers();
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2362
|
+
// src/workflows/offline-invoice-workflow.ts
|
|
2363
|
+
import crypto3 from "crypto";
|
|
2364
|
+
var OfflineInvoiceWorkflow;
|
|
2365
|
+
var init_offline_invoice_workflow = __esm({
|
|
2366
|
+
"src/workflows/offline-invoice-workflow.ts"() {
|
|
2367
|
+
"use strict";
|
|
2368
|
+
init_ksef_api_error();
|
|
2369
|
+
init_deadline();
|
|
2370
|
+
init_document_structures();
|
|
2371
|
+
OfflineInvoiceWorkflow = class {
|
|
2372
|
+
constructor(qrService) {
|
|
2373
|
+
this.qrService = qrService;
|
|
2374
|
+
}
|
|
2375
|
+
async generate(input, options) {
|
|
2376
|
+
if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
|
|
2377
|
+
throw new Error("invoiceXml must not be empty");
|
|
2378
|
+
}
|
|
2379
|
+
if (!input.invoiceNumber) {
|
|
2380
|
+
throw new Error("invoiceNumber is required");
|
|
2381
|
+
}
|
|
2382
|
+
if (!input.sellerNip) {
|
|
2383
|
+
throw new Error("sellerNip is required");
|
|
2384
|
+
}
|
|
2385
|
+
const mode = options?.mode ?? "offline24";
|
|
2386
|
+
const reason = getDefaultReason(mode);
|
|
2387
|
+
const invoiceHashBase64 = crypto3.createHash("sha256").update(input.invoiceXml).digest("base64");
|
|
2388
|
+
const kod1Url = this.qrService.buildInvoiceVerificationUrl(
|
|
2389
|
+
input.sellerNip,
|
|
2390
|
+
input.invoiceDate,
|
|
2391
|
+
invoiceHashBase64
|
|
2392
|
+
);
|
|
2393
|
+
let kod2Url;
|
|
2394
|
+
if (options?.certificate) {
|
|
2395
|
+
kod2Url = this.qrService.buildCertificateVerificationUrl(
|
|
2396
|
+
input.sellerIdentifier.type,
|
|
2397
|
+
input.sellerIdentifier.value,
|
|
2398
|
+
input.sellerNip,
|
|
2399
|
+
options.certificate.certificateSerial,
|
|
2400
|
+
invoiceHashBase64,
|
|
2401
|
+
options.certificate.privateKeyPem
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
|
|
2405
|
+
const metadata = {
|
|
2406
|
+
id: crypto3.randomUUID(),
|
|
2407
|
+
mode,
|
|
2408
|
+
reason,
|
|
2409
|
+
status: "GENERATED",
|
|
2410
|
+
invoiceNumber: input.invoiceNumber,
|
|
2411
|
+
invoiceDate: input.invoiceDate,
|
|
2412
|
+
invoiceXml: input.invoiceXml,
|
|
2413
|
+
sellerNip: input.sellerNip,
|
|
2414
|
+
sellerIdentifier: input.sellerIdentifier,
|
|
2415
|
+
buyerIdentifier: input.buyerIdentifier,
|
|
2416
|
+
totalAmount: input.totalAmount,
|
|
2417
|
+
currency: input.currency,
|
|
2418
|
+
kod1Url,
|
|
2419
|
+
kod2Url,
|
|
2420
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2421
|
+
submitBy,
|
|
2422
|
+
maintenanceWindowId: options?.maintenanceWindow?.id
|
|
2423
|
+
};
|
|
2424
|
+
if (options?.storage) {
|
|
2425
|
+
await options.storage.save(metadata);
|
|
2426
|
+
}
|
|
2427
|
+
return metadata;
|
|
2428
|
+
}
|
|
2429
|
+
async submit(client, options) {
|
|
2430
|
+
const { storage, checkExpiry = true } = options;
|
|
2431
|
+
let invoices2;
|
|
2432
|
+
if (options.invoiceIds) {
|
|
2433
|
+
invoices2 = [];
|
|
2434
|
+
const notFound = [];
|
|
2435
|
+
for (const id of options.invoiceIds) {
|
|
2436
|
+
const inv = await storage.get(id);
|
|
2437
|
+
if (inv) {
|
|
2438
|
+
invoices2.push(inv);
|
|
2439
|
+
} else {
|
|
2440
|
+
notFound.push(id);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (notFound.length > 0) {
|
|
2444
|
+
throw new Error(`Offline invoice(s) not found: ${notFound.join(", ")}`);
|
|
2445
|
+
}
|
|
2446
|
+
} else {
|
|
2447
|
+
invoices2 = await storage.list({ status: ["GENERATED", "QUEUED", "SUBMITTED"] });
|
|
2448
|
+
}
|
|
2449
|
+
const result = {
|
|
2450
|
+
total: invoices2.length,
|
|
2451
|
+
submitted: 0,
|
|
2452
|
+
accepted: 0,
|
|
2453
|
+
rejected: 0,
|
|
2454
|
+
failed: 0,
|
|
2455
|
+
expired: 0,
|
|
2456
|
+
results: []
|
|
2457
|
+
};
|
|
2458
|
+
if (invoices2.length === 0) return result;
|
|
2459
|
+
const pending = [];
|
|
2460
|
+
for (const inv of invoices2) {
|
|
2461
|
+
if (checkExpiry && isExpired(inv.submitBy)) {
|
|
2462
|
+
await storage.update(inv.id, { status: "EXPIRED" });
|
|
2463
|
+
result.expired++;
|
|
2464
|
+
result.results.push({
|
|
2465
|
+
invoiceId: inv.id,
|
|
2466
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2467
|
+
status: "EXPIRED"
|
|
2468
|
+
});
|
|
2469
|
+
} else {
|
|
2470
|
+
pending.push(inv);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
if (pending.length === 0) return result;
|
|
2474
|
+
await client.crypto.init();
|
|
2475
|
+
const encData = client.crypto.getEncryptionData();
|
|
2476
|
+
const formCode = options.formCode ?? DEFAULT_FORM_CODE;
|
|
2477
|
+
const openResp = await client.onlineSession.openSession(
|
|
2478
|
+
{ formCode, encryption: encData.encryptionInfo }
|
|
2479
|
+
);
|
|
2480
|
+
const sessionRef = openResp.referenceNumber;
|
|
2481
|
+
try {
|
|
2482
|
+
for (const inv of pending) {
|
|
2483
|
+
await storage.update(inv.id, { status: "QUEUED" });
|
|
2484
|
+
try {
|
|
2485
|
+
const data = new TextEncoder().encode(inv.invoiceXml);
|
|
2486
|
+
const plainMeta = client.crypto.getFileMetadata(data);
|
|
2487
|
+
const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
|
|
2488
|
+
const encMeta = client.crypto.getFileMetadata(encrypted);
|
|
2489
|
+
await storage.update(inv.id, { status: "SUBMITTED", submittedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2490
|
+
const resp = await client.onlineSession.sendInvoice(sessionRef, {
|
|
2491
|
+
invoiceHash: plainMeta.hashSHA,
|
|
2492
|
+
invoiceSize: plainMeta.fileSize,
|
|
2493
|
+
encryptedInvoiceHash: encMeta.hashSHA,
|
|
2494
|
+
encryptedInvoiceSize: encMeta.fileSize,
|
|
2495
|
+
encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
|
|
2496
|
+
offlineMode: true
|
|
2497
|
+
});
|
|
2498
|
+
await storage.update(inv.id, {
|
|
2499
|
+
status: "ACCEPTED",
|
|
2500
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2501
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2502
|
+
});
|
|
2503
|
+
result.submitted++;
|
|
2504
|
+
result.accepted++;
|
|
2505
|
+
result.results.push({
|
|
2506
|
+
invoiceId: inv.id,
|
|
2507
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2508
|
+
status: "ACCEPTED",
|
|
2509
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2510
|
+
});
|
|
2511
|
+
} catch (err) {
|
|
2512
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2513
|
+
if (err instanceof KSeFApiError) {
|
|
2514
|
+
await storage.update(inv.id, {
|
|
2515
|
+
status: "REJECTED",
|
|
2516
|
+
error: { code: err.statusCode, message }
|
|
2517
|
+
});
|
|
2518
|
+
result.submitted++;
|
|
2519
|
+
result.rejected++;
|
|
2520
|
+
result.results.push({
|
|
2521
|
+
invoiceId: inv.id,
|
|
2522
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2523
|
+
status: "REJECTED",
|
|
2524
|
+
error: { code: err.statusCode, message }
|
|
2525
|
+
});
|
|
2526
|
+
} else {
|
|
2527
|
+
await storage.update(inv.id, {
|
|
2528
|
+
status: "QUEUED",
|
|
2529
|
+
error: { code: 0, message }
|
|
2530
|
+
});
|
|
2531
|
+
result.failed++;
|
|
2532
|
+
result.results.push({
|
|
2533
|
+
invoiceId: inv.id,
|
|
2534
|
+
invoiceNumber: inv.invoiceNumber,
|
|
2535
|
+
status: "QUEUED",
|
|
2536
|
+
error: { code: 0, message }
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
} finally {
|
|
2542
|
+
try {
|
|
2543
|
+
await client.onlineSession.closeSession(sessionRef);
|
|
2544
|
+
} catch {
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return result;
|
|
2548
|
+
}
|
|
2549
|
+
// TODO(perf): Each correction opens a separate KSeF session.
|
|
2550
|
+
// For batch corrections, consider a correctBatch() sharing one session (like submit()).
|
|
2551
|
+
async correct(client, options) {
|
|
2552
|
+
const { storage, rejectedInvoiceId, correctedInvoiceXml } = options;
|
|
2553
|
+
const original = await storage.get(rejectedInvoiceId);
|
|
2554
|
+
if (!original) {
|
|
2555
|
+
throw new Error(`Offline invoice not found: ${rejectedInvoiceId}`);
|
|
2556
|
+
}
|
|
2557
|
+
if (original.status !== "REJECTED") {
|
|
2558
|
+
throw new Error(
|
|
2559
|
+
original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
const originalHash = crypto3.createHash("sha256").update(original.invoiceXml).digest("base64");
|
|
2563
|
+
await client.crypto.init();
|
|
2564
|
+
const encData = client.crypto.getEncryptionData();
|
|
2565
|
+
const formCode = options.formCode ?? DEFAULT_FORM_CODE;
|
|
2566
|
+
const openResp = await client.onlineSession.openSession(
|
|
2567
|
+
{ formCode, encryption: encData.encryptionInfo }
|
|
2568
|
+
);
|
|
2569
|
+
const sessionRef = openResp.referenceNumber;
|
|
2570
|
+
try {
|
|
2571
|
+
const data = new TextEncoder().encode(correctedInvoiceXml);
|
|
2572
|
+
const plainMeta = client.crypto.getFileMetadata(data);
|
|
2573
|
+
const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
|
|
2574
|
+
const encMeta = client.crypto.getFileMetadata(encrypted);
|
|
2575
|
+
const correctionId = crypto3.randomUUID();
|
|
2576
|
+
const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2577
|
+
const correctedHashBase64 = crypto3.createHash("sha256").update(correctedInvoiceXml).digest("base64");
|
|
2578
|
+
const kod1Url = this.qrService.buildInvoiceVerificationUrl(
|
|
2579
|
+
original.sellerNip,
|
|
2580
|
+
original.invoiceDate,
|
|
2581
|
+
correctedHashBase64
|
|
2582
|
+
);
|
|
2583
|
+
let kod2Url;
|
|
2584
|
+
if (options.certificate) {
|
|
2585
|
+
kod2Url = this.qrService.buildCertificateVerificationUrl(
|
|
2586
|
+
original.sellerIdentifier.type,
|
|
2587
|
+
original.sellerIdentifier.value,
|
|
2588
|
+
original.sellerNip,
|
|
2589
|
+
options.certificate.certificateSerial,
|
|
2590
|
+
correctedHashBase64,
|
|
2591
|
+
options.certificate.privateKeyPem
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
const correctionMetadata = {
|
|
2595
|
+
id: correctionId,
|
|
2596
|
+
mode: original.mode,
|
|
2597
|
+
reason: original.reason,
|
|
2598
|
+
status: "SUBMITTED",
|
|
2599
|
+
invoiceNumber: original.invoiceNumber,
|
|
2600
|
+
invoiceDate: original.invoiceDate,
|
|
2601
|
+
invoiceXml: correctedInvoiceXml,
|
|
2602
|
+
sellerNip: original.sellerNip,
|
|
2603
|
+
sellerIdentifier: original.sellerIdentifier,
|
|
2604
|
+
buyerIdentifier: original.buyerIdentifier,
|
|
2605
|
+
kod1Url,
|
|
2606
|
+
kod2Url,
|
|
2607
|
+
generatedAt: submittedAt,
|
|
2608
|
+
submitBy: original.submitBy,
|
|
2609
|
+
submittedAt,
|
|
2610
|
+
correctedInvoiceId: rejectedInvoiceId
|
|
2611
|
+
};
|
|
2612
|
+
await storage.save(correctionMetadata);
|
|
2613
|
+
const resp = await client.onlineSession.sendInvoice(sessionRef, {
|
|
2614
|
+
invoiceHash: plainMeta.hashSHA,
|
|
2615
|
+
invoiceSize: plainMeta.fileSize,
|
|
2616
|
+
encryptedInvoiceHash: encMeta.hashSHA,
|
|
2617
|
+
encryptedInvoiceSize: encMeta.fileSize,
|
|
2618
|
+
encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
|
|
2619
|
+
offlineMode: true,
|
|
2620
|
+
hashOfCorrectedInvoice: originalHash
|
|
2621
|
+
});
|
|
2622
|
+
await storage.update(correctionId, {
|
|
2623
|
+
status: "ACCEPTED",
|
|
2624
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2625
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2626
|
+
});
|
|
2627
|
+
await storage.update(rejectedInvoiceId, {
|
|
2628
|
+
status: "CORRECTED",
|
|
2629
|
+
correctedBy: correctionId
|
|
2630
|
+
});
|
|
2631
|
+
return {
|
|
2632
|
+
invoiceId: correctionId,
|
|
2633
|
+
invoiceNumber: correctionMetadata.invoiceNumber,
|
|
2634
|
+
status: "ACCEPTED",
|
|
2635
|
+
ksefReferenceNumber: resp.referenceNumber
|
|
2636
|
+
};
|
|
2637
|
+
} finally {
|
|
2638
|
+
try {
|
|
2639
|
+
await client.onlineSession.closeSession(sessionRef);
|
|
2640
|
+
} catch {
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
});
|
|
2647
|
+
|
|
2090
2648
|
// src/crypto/signature-service.ts
|
|
2091
2649
|
var signature_service_exports = {};
|
|
2092
2650
|
__export(signature_service_exports, {
|
|
2093
2651
|
SignatureService: () => SignatureService
|
|
2094
2652
|
});
|
|
2095
|
-
import * as
|
|
2653
|
+
import * as crypto4 from "crypto";
|
|
2096
2654
|
import { ExclusiveCanonicalization } from "xml-crypto";
|
|
2097
2655
|
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
|
|
2098
2656
|
function extractDerFromPem(pem) {
|
|
@@ -2112,7 +2670,7 @@ function canonicalize(elem) {
|
|
|
2112
2670
|
function computeRootDigest(doc) {
|
|
2113
2671
|
const root = doc.documentElement;
|
|
2114
2672
|
const canonical = canonicalize(root);
|
|
2115
|
-
return
|
|
2673
|
+
return crypto4.createHash("sha256").update(canonical, "utf-8").digest("base64");
|
|
2116
2674
|
}
|
|
2117
2675
|
function computeSignedPropertiesDigest(qualifyingPropertiesXml) {
|
|
2118
2676
|
const parser = new DOMParser();
|
|
@@ -2122,7 +2680,7 @@ function computeSignedPropertiesDigest(qualifyingPropertiesXml) {
|
|
|
2122
2680
|
throw new Error("SignedProperties element not found in QualifyingProperties");
|
|
2123
2681
|
}
|
|
2124
2682
|
const canonical = canonicalize(signedProps);
|
|
2125
|
-
return
|
|
2683
|
+
return crypto4.createHash("sha256").update(canonical, "utf-8").digest("base64");
|
|
2126
2684
|
}
|
|
2127
2685
|
function buildSignedInfo(signatureAlgorithm, rootDigest, signedPropertiesDigest) {
|
|
2128
2686
|
return [
|
|
@@ -2153,12 +2711,12 @@ function computeSignatureValue(canonicalSignedInfo, privateKey, isEc) {
|
|
|
2153
2711
|
const data = Buffer.from(canonicalSignedInfo, "utf-8");
|
|
2154
2712
|
let signature;
|
|
2155
2713
|
if (isEc) {
|
|
2156
|
-
signature =
|
|
2714
|
+
signature = crypto4.sign("sha256", data, {
|
|
2157
2715
|
key: privateKey,
|
|
2158
2716
|
dsaEncoding: "ieee-p1363"
|
|
2159
2717
|
});
|
|
2160
2718
|
} else {
|
|
2161
|
-
signature =
|
|
2719
|
+
signature = crypto4.sign("sha256", data, privateKey);
|
|
2162
2720
|
}
|
|
2163
2721
|
return signature.toString("base64");
|
|
2164
2722
|
}
|
|
@@ -2258,11 +2816,11 @@ var init_signature_service = __esm({
|
|
|
2258
2816
|
}
|
|
2259
2817
|
const certDer = extractDerFromPem(certPem);
|
|
2260
2818
|
const certBase64 = certDer.toString("base64");
|
|
2261
|
-
const certDigest =
|
|
2262
|
-
const x5093 = new
|
|
2819
|
+
const certDigest = crypto4.createHash("sha256").update(certDer).digest("base64");
|
|
2820
|
+
const x5093 = new crypto4.X509Certificate(certPem);
|
|
2263
2821
|
const issuerName = normalizeIssuerDn(x5093.issuer);
|
|
2264
2822
|
const serialNumber = hexSerialToDecimal(x5093.serialNumber);
|
|
2265
|
-
const privateKey =
|
|
2823
|
+
const privateKey = crypto4.createPrivateKey(
|
|
2266
2824
|
passphrase ? { key: privateKeyPem, format: "pem", passphrase } : privateKeyPem
|
|
2267
2825
|
);
|
|
2268
2826
|
const isEc = privateKey.asymmetricKeyType === "ec";
|
|
@@ -2427,6 +2985,7 @@ var init_client = __esm({
|
|
|
2427
2985
|
init_cryptography_service();
|
|
2428
2986
|
init_verification_link_service();
|
|
2429
2987
|
init_auth_xml_builder();
|
|
2988
|
+
init_offline_invoice_workflow();
|
|
2430
2989
|
KSeFClient = class {
|
|
2431
2990
|
auth;
|
|
2432
2991
|
activeSessions;
|
|
@@ -2445,6 +3004,8 @@ var init_client = __esm({
|
|
|
2445
3004
|
qr;
|
|
2446
3005
|
options;
|
|
2447
3006
|
authManager;
|
|
3007
|
+
_baseRestClientConfig;
|
|
3008
|
+
_offline;
|
|
2448
3009
|
constructor(options) {
|
|
2449
3010
|
this.options = resolveOptions(options);
|
|
2450
3011
|
const authManager = options?.authManager ?? new DefaultAuthManager(async () => {
|
|
@@ -2455,6 +3016,8 @@ var init_client = __esm({
|
|
|
2455
3016
|
});
|
|
2456
3017
|
this.authManager = authManager;
|
|
2457
3018
|
const restClientConfig = buildRestClientConfig(options, authManager);
|
|
3019
|
+
const { authManager: _am, ...baseConfig } = restClientConfig;
|
|
3020
|
+
this._baseRestClientConfig = baseConfig;
|
|
2458
3021
|
const restClient = new RestClient(this.options, restClientConfig);
|
|
2459
3022
|
const fetcher = new CertificateFetcher(restClient);
|
|
2460
3023
|
this.crypto = new CryptographyService(fetcher);
|
|
@@ -2473,6 +3036,16 @@ var init_client = __esm({
|
|
|
2473
3036
|
this.testData = new TestDataService(restClient, this.options.environmentName);
|
|
2474
3037
|
this.qr = new VerificationLinkService(this.options.baseQrUrl);
|
|
2475
3038
|
}
|
|
3039
|
+
get offline() {
|
|
3040
|
+
if (!this._offline) {
|
|
3041
|
+
this._offline = new OfflineInvoiceWorkflow(this.qr);
|
|
3042
|
+
}
|
|
3043
|
+
return this._offline;
|
|
3044
|
+
}
|
|
3045
|
+
/** @internal Create a RestClient with a different AuthManager, preserving transport/retry/rateLimit config. */
|
|
3046
|
+
createScopedRestClient(authManager) {
|
|
3047
|
+
return new RestClient(this.options, { ...this._baseRestClientConfig, authManager });
|
|
3048
|
+
}
|
|
2476
3049
|
async loginWithToken(token, nip) {
|
|
2477
3050
|
const challenge2 = await this.auth.getChallenge();
|
|
2478
3051
|
await this.crypto.init();
|
|
@@ -2507,10 +3080,10 @@ var init_client = __esm({
|
|
|
2507
3080
|
}
|
|
2508
3081
|
async awaitAuthReady(referenceNumber, authToken) {
|
|
2509
3082
|
for (let i = 0; i < 30; i++) {
|
|
2510
|
-
const
|
|
2511
|
-
if (
|
|
2512
|
-
if (
|
|
2513
|
-
throw new Error(`Authentication failed with status ${
|
|
3083
|
+
const status7 = await this.auth.getAuthStatus(referenceNumber, authToken);
|
|
3084
|
+
if (status7.status.code === 200) return;
|
|
3085
|
+
if (status7.status.code !== 100) {
|
|
3086
|
+
throw new Error(`Authentication failed with status ${status7.status.code}: ${status7.status.description}`);
|
|
2514
3087
|
}
|
|
2515
3088
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
2516
3089
|
}
|
|
@@ -2545,79 +3118,6 @@ var init_polling = __esm({
|
|
|
2545
3118
|
}
|
|
2546
3119
|
});
|
|
2547
3120
|
|
|
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
3121
|
// src/xml/upo-parser.ts
|
|
2622
3122
|
import { XMLParser } from "fast-xml-parser";
|
|
2623
3123
|
function isRecord(value) {
|
|
@@ -2742,11 +3242,58 @@ var init_upo_parser = __esm({
|
|
|
2742
3242
|
}
|
|
2743
3243
|
});
|
|
2744
3244
|
|
|
3245
|
+
// src/xml/invoice-field-extractor.ts
|
|
3246
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
3247
|
+
function extractInvoiceFields(xml) {
|
|
3248
|
+
const parsed = invoiceParser.parse(xml);
|
|
3249
|
+
const root = parsed?.Faktura;
|
|
3250
|
+
if (!root || typeof root !== "object") {
|
|
3251
|
+
throw new Error(
|
|
3252
|
+
"Cannot extract invoice fields: missing <Faktura> root element. Ensure the XML is a valid KSeF invoice (FA_2, FA_3, or FA_RR)."
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
if (root.Fa && typeof root.Fa === "object") {
|
|
3256
|
+
const invoiceDate = nonEmptyString(root.Fa.P_1);
|
|
3257
|
+
const invoiceNumber = nonEmptyString(root.Fa.P_2);
|
|
3258
|
+
if (invoiceDate && invoiceNumber) {
|
|
3259
|
+
return { invoiceNumber, invoiceDate };
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
if (root.FakturaRR && typeof root.FakturaRR === "object") {
|
|
3263
|
+
const invoiceDate = nonEmptyString(root.FakturaRR.P_4B);
|
|
3264
|
+
const invoiceNumber = nonEmptyString(root.FakturaRR.P_4C);
|
|
3265
|
+
if (invoiceDate && invoiceNumber) {
|
|
3266
|
+
return { invoiceNumber, invoiceDate };
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
throw new Error(
|
|
3270
|
+
"Cannot extract invoice number and date from XML. Expected <Fa><P_1>/<P_2> (FA format) or <FakturaRR><P_4B>/<P_4C> (RR format)."
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
function nonEmptyString(value) {
|
|
3274
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3275
|
+
}
|
|
3276
|
+
var invoiceParser;
|
|
3277
|
+
var init_invoice_field_extractor = __esm({
|
|
3278
|
+
"src/xml/invoice-field-extractor.ts"() {
|
|
3279
|
+
"use strict";
|
|
3280
|
+
invoiceParser = new XMLParser2({
|
|
3281
|
+
ignoreAttributes: false,
|
|
3282
|
+
attributeNamePrefix: "@_",
|
|
3283
|
+
parseTagValue: false,
|
|
3284
|
+
parseAttributeValue: false,
|
|
3285
|
+
removeNSPrefix: true,
|
|
3286
|
+
trimValues: false
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
});
|
|
3290
|
+
|
|
2745
3291
|
// src/xml/index.ts
|
|
2746
3292
|
var init_xml = __esm({
|
|
2747
3293
|
"src/xml/index.ts"() {
|
|
2748
3294
|
"use strict";
|
|
2749
3295
|
init_upo_parser();
|
|
3296
|
+
init_invoice_field_extractor();
|
|
2750
3297
|
}
|
|
2751
3298
|
});
|
|
2752
3299
|
|
|
@@ -4488,11 +5035,11 @@ async function validateSchema(xml, options, _parsed) {
|
|
|
4488
5035
|
const prefix = rootElement ? `/${rootElement}/` : "/";
|
|
4489
5036
|
const validationErrors = result.error.issues.map((issue) => {
|
|
4490
5037
|
const zodPath = issue.path.join("/");
|
|
4491
|
-
const
|
|
5038
|
+
const path10 = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
|
|
4492
5039
|
return {
|
|
4493
5040
|
code: mapZodErrorCode(issue),
|
|
4494
5041
|
message: issue.message,
|
|
4495
|
-
path:
|
|
5042
|
+
path: path10
|
|
4496
5043
|
};
|
|
4497
5044
|
});
|
|
4498
5045
|
return { valid: false, schemaType, errors: validationErrors };
|
|
@@ -4532,9 +5079,9 @@ function validateBusinessRules(xml, _parsed) {
|
|
|
4532
5079
|
collectDateErrors(object, rootElement, errors);
|
|
4533
5080
|
return { valid: errors.length === 0, schemaType, errors };
|
|
4534
5081
|
}
|
|
4535
|
-
function collectNipPeselErrors(obj,
|
|
5082
|
+
function collectNipPeselErrors(obj, path10, errors) {
|
|
4536
5083
|
for (const [key, value] of Object.entries(obj)) {
|
|
4537
|
-
const currentPath =
|
|
5084
|
+
const currentPath = path10 ? `${path10}/${key}` : key;
|
|
4538
5085
|
if (key === "NIP" && typeof value === "string") {
|
|
4539
5086
|
if (!isValidNip(value)) {
|
|
4540
5087
|
errors.push({
|
|
@@ -4614,7 +5161,7 @@ var init_invoice_validator = __esm({
|
|
|
4614
5161
|
});
|
|
4615
5162
|
|
|
4616
5163
|
// src/builders/batch-file.ts
|
|
4617
|
-
import * as
|
|
5164
|
+
import * as crypto6 from "crypto";
|
|
4618
5165
|
function splitBuffer(data, maxPartSize) {
|
|
4619
5166
|
if (data.length <= maxPartSize) {
|
|
4620
5167
|
return [data];
|
|
@@ -4625,8 +5172,8 @@ function splitBuffer(data, maxPartSize) {
|
|
|
4625
5172
|
}
|
|
4626
5173
|
return parts;
|
|
4627
5174
|
}
|
|
4628
|
-
function
|
|
4629
|
-
return
|
|
5175
|
+
function sha256Base642(data) {
|
|
5176
|
+
return crypto6.createHash("sha256").update(data).digest("base64");
|
|
4630
5177
|
}
|
|
4631
5178
|
var BATCH_MAX_PART_SIZE, BATCH_MAX_TOTAL_SIZE, BATCH_MAX_PARTS, BatchFileBuilder;
|
|
4632
5179
|
var init_batch_file = __esm({
|
|
@@ -4663,7 +5210,7 @@ var init_batch_file = __esm({
|
|
|
4663
5210
|
`Data requires ${rawParts.length} parts, exceeding maximum of ${BATCH_MAX_PARTS}`
|
|
4664
5211
|
);
|
|
4665
5212
|
}
|
|
4666
|
-
const zipHash =
|
|
5213
|
+
const zipHash = sha256Base642(zipBytes);
|
|
4667
5214
|
const encryptedParts = [];
|
|
4668
5215
|
const fileParts = rawParts.map((raw, i) => {
|
|
4669
5216
|
const encrypted = encryptFn(raw);
|
|
@@ -4671,7 +5218,7 @@ var init_batch_file = __esm({
|
|
|
4671
5218
|
return {
|
|
4672
5219
|
ordinalNumber: i + 1,
|
|
4673
5220
|
fileSize: encrypted.length,
|
|
4674
|
-
fileHash:
|
|
5221
|
+
fileHash: sha256Base642(encrypted)
|
|
4675
5222
|
};
|
|
4676
5223
|
});
|
|
4677
5224
|
return {
|
|
@@ -4754,7 +5301,7 @@ var init_batch_file = __esm({
|
|
|
4754
5301
|
encryptedChunks.push(value);
|
|
4755
5302
|
}
|
|
4756
5303
|
const encryptedData = new Uint8Array(Buffer.concat(encryptedChunks));
|
|
4757
|
-
const encryptedMeta =
|
|
5304
|
+
const encryptedMeta = sha256Base642(encryptedData);
|
|
4758
5305
|
fileParts.push({
|
|
4759
5306
|
ordinalNumber: partIndex + 1,
|
|
4760
5307
|
fileSize: encryptedData.byteLength,
|
|
@@ -4798,6 +5345,9 @@ __export(batch_session_workflow_exports, {
|
|
|
4798
5345
|
uploadBatchStreamParsed: () => uploadBatchStreamParsed
|
|
4799
5346
|
});
|
|
4800
5347
|
async function uploadBatch(client, zipData, options) {
|
|
5348
|
+
if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
|
|
5349
|
+
throw new Error("parallelism must be a positive integer");
|
|
5350
|
+
}
|
|
4801
5351
|
await client.crypto.init();
|
|
4802
5352
|
if (options?.validate) {
|
|
4803
5353
|
const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
|
|
@@ -4840,7 +5390,7 @@ async function uploadBatch(client, zipData, options) {
|
|
|
4840
5390
|
},
|
|
4841
5391
|
ordinalNumber: i + 1
|
|
4842
5392
|
}));
|
|
4843
|
-
await client.batchSession.sendParts(openResp, sendingParts);
|
|
5393
|
+
await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
|
|
4844
5394
|
await client.batchSession.closeSession(openResp.referenceNumber);
|
|
4845
5395
|
const result = await pollUntil(
|
|
4846
5396
|
() => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
|
|
@@ -4861,6 +5411,9 @@ async function uploadBatch(client, zipData, options) {
|
|
|
4861
5411
|
};
|
|
4862
5412
|
}
|
|
4863
5413
|
async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
|
|
5414
|
+
if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
|
|
5415
|
+
throw new Error("parallelism must be a positive integer");
|
|
5416
|
+
}
|
|
4864
5417
|
await client.crypto.init();
|
|
4865
5418
|
const encData = client.crypto.getEncryptionData();
|
|
4866
5419
|
const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
|
|
@@ -4882,7 +5435,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
|
|
|
4882
5435
|
},
|
|
4883
5436
|
options?.upoVersion
|
|
4884
5437
|
);
|
|
4885
|
-
await client.batchSession.sendPartsWithStream(openResp, streamParts);
|
|
5438
|
+
await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
|
|
4886
5439
|
await client.batchSession.closeSession(openResp.referenceNumber);
|
|
4887
5440
|
const result = await pollUntil(
|
|
4888
5441
|
() => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
|
|
@@ -4937,10 +5490,10 @@ var init_batch_session_workflow = __esm({
|
|
|
4937
5490
|
});
|
|
4938
5491
|
|
|
4939
5492
|
// src/cli/index.ts
|
|
4940
|
-
import { readFileSync as
|
|
5493
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4941
5494
|
import { fileURLToPath } from "url";
|
|
4942
5495
|
import { dirname as dirname2, resolve } from "path";
|
|
4943
|
-
import { defineCommand as
|
|
5496
|
+
import { defineCommand as defineCommand18, runMain } from "citty";
|
|
4944
5497
|
|
|
4945
5498
|
// src/cli/commands/config.ts
|
|
4946
5499
|
import { defineCommand } from "citty";
|
|
@@ -5506,13 +6059,13 @@ var login = defineCommand2({
|
|
|
5506
6059
|
if (token) {
|
|
5507
6060
|
await client.loginWithToken(token, nip);
|
|
5508
6061
|
} else if (args.p12) {
|
|
5509
|
-
const
|
|
5510
|
-
const p12Buffer =
|
|
6062
|
+
const fs15 = await import("fs");
|
|
6063
|
+
const p12Buffer = fs15.readFileSync(args.p12);
|
|
5511
6064
|
await client.loginWithPkcs12(p12Buffer, args["p12-password"] ?? "", nip);
|
|
5512
6065
|
} else if (args.cert && args.key) {
|
|
5513
|
-
const
|
|
5514
|
-
const certPem =
|
|
5515
|
-
const keyPem =
|
|
6066
|
+
const fs15 = await import("fs");
|
|
6067
|
+
const certPem = fs15.readFileSync(args.cert, "utf-8");
|
|
6068
|
+
const keyPem = fs15.readFileSync(args.key, "utf-8");
|
|
5516
6069
|
await client.loginWithCertificate(certPem, keyPem, nip, args["key-password"]);
|
|
5517
6070
|
} else {
|
|
5518
6071
|
throw new Error("Provide --token, --p12, or both --cert and --key for authentication.");
|
|
@@ -6226,6 +6779,17 @@ var FileHwmStore = class {
|
|
|
6226
6779
|
// src/workflows/invoice-export-workflow.ts
|
|
6227
6780
|
init_zip();
|
|
6228
6781
|
init_polling();
|
|
6782
|
+
|
|
6783
|
+
// src/utils/hash.ts
|
|
6784
|
+
import crypto5 from "crypto";
|
|
6785
|
+
function sha256Base64(data) {
|
|
6786
|
+
return crypto5.createHash("sha256").update(data).digest("base64");
|
|
6787
|
+
}
|
|
6788
|
+
function verifyHash(data, expectedHash) {
|
|
6789
|
+
return sha256Base64(data) === expectedHash;
|
|
6790
|
+
}
|
|
6791
|
+
|
|
6792
|
+
// src/workflows/invoice-export-workflow.ts
|
|
6229
6793
|
async function doExport(client, filters, options) {
|
|
6230
6794
|
await client.crypto.init();
|
|
6231
6795
|
const encData = client.crypto.getEncryptionData();
|
|
@@ -6254,6 +6818,7 @@ async function doExport(client, filters, options) {
|
|
|
6254
6818
|
url: p.url,
|
|
6255
6819
|
method: p.method,
|
|
6256
6820
|
partSize: p.partSize,
|
|
6821
|
+
partHash: p.partHash,
|
|
6257
6822
|
encryptedPartSize: p.encryptedPartSize,
|
|
6258
6823
|
encryptedPartHash: p.encryptedPartHash,
|
|
6259
6824
|
expirationDate: p.expirationDate
|
|
@@ -6315,6 +6880,9 @@ async function incrementalExportAndDownload(client, options) {
|
|
|
6315
6880
|
throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
|
|
6316
6881
|
}
|
|
6317
6882
|
const encryptedData = new Uint8Array(await resp.arrayBuffer());
|
|
6883
|
+
if (options.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
|
|
6884
|
+
throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
|
|
6885
|
+
}
|
|
6318
6886
|
const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
|
|
6319
6887
|
decryptedParts.push(decrypted);
|
|
6320
6888
|
}
|
|
@@ -6523,6 +7091,7 @@ var send = defineCommand5({
|
|
|
6523
7091
|
stream: { type: "boolean", description: "Use stream-based batch upload (for ZIP files, reduces memory usage)" },
|
|
6524
7092
|
formCode: { type: "string", description: "Document type: FA2, FA3, PEF3, PEFKOR3, FARR1 (default: FA3)" },
|
|
6525
7093
|
validate: { type: "boolean", description: "Validate XML before sending" },
|
|
7094
|
+
parallelism: { type: "string", description: "Number of concurrent part uploads (batch mode)" },
|
|
6526
7095
|
env: { type: "string", description: "Environment (test/demo/prod)" },
|
|
6527
7096
|
json: { type: "boolean", description: "Output as JSON" },
|
|
6528
7097
|
verbose: { type: "boolean", description: "Show HTTP request/response details" },
|
|
@@ -6536,6 +7105,10 @@ var send = defineCommand5({
|
|
|
6536
7105
|
const config = loadConfig();
|
|
6537
7106
|
const nip = args.nip ?? config.nip;
|
|
6538
7107
|
const filePath = args.path;
|
|
7108
|
+
const parallelism = args.parallelism ? Number(args.parallelism) : void 0;
|
|
7109
|
+
if (parallelism !== void 0 && (!Number.isInteger(parallelism) || parallelism < 1)) {
|
|
7110
|
+
throw new Error("--parallelism must be a positive integer");
|
|
7111
|
+
}
|
|
6539
7112
|
const formCodeKey = args.formCode;
|
|
6540
7113
|
let formCode = DEFAULT_FORM_CODE;
|
|
6541
7114
|
if (formCodeKey) {
|
|
@@ -6568,7 +7141,8 @@ var send = defineCommand5({
|
|
|
6568
7141
|
if (!args.json) consola9.start(`Sending batch via stream (${(zipSize / 1e6).toFixed(1)} MB)...`);
|
|
6569
7142
|
const result = await uploadBatchStream2(client, zipStreamFactory, zipSize, {
|
|
6570
7143
|
formCode,
|
|
6571
|
-
pollOptions: { intervalMs: 3e3 }
|
|
7144
|
+
pollOptions: { intervalMs: 3e3 },
|
|
7145
|
+
parallelism
|
|
6572
7146
|
});
|
|
6573
7147
|
if (args.json) {
|
|
6574
7148
|
outputResult(result, { json: true });
|
|
@@ -6632,7 +7206,7 @@ var send = defineCommand5({
|
|
|
6632
7206
|
{ formCode, batchFile: batchFileInfo, encryption: encryptionData.encryptionInfo }
|
|
6633
7207
|
);
|
|
6634
7208
|
saveOnlineSessionRef(openResult.referenceNumber);
|
|
6635
|
-
await client.batchSession.sendParts(openResult, parts);
|
|
7209
|
+
await client.batchSession.sendParts(openResult, parts, parallelism);
|
|
6636
7210
|
await client.batchSession.closeSession(openResult.referenceNumber);
|
|
6637
7211
|
clearOnlineSessionRef();
|
|
6638
7212
|
if (args.json) {
|
|
@@ -6697,9 +7271,9 @@ var get = defineCommand5({
|
|
|
6697
7271
|
return withErrorHandler(async () => {
|
|
6698
7272
|
const globalOpts = getGlobalOpts4(args);
|
|
6699
7273
|
const { client } = await requireSession(globalOpts);
|
|
6700
|
-
const xml = await client.invoices.getInvoice(args.ksefNumber);
|
|
7274
|
+
const { xml, hash } = await client.invoices.getInvoice(args.ksefNumber);
|
|
6701
7275
|
if (args.json) {
|
|
6702
|
-
outputResult({ ksefNumber: args.ksefNumber, xml }, { json: true });
|
|
7276
|
+
outputResult({ ksefNumber: args.ksefNumber, xml, hash }, { json: true });
|
|
6703
7277
|
return;
|
|
6704
7278
|
}
|
|
6705
7279
|
if (args.o) {
|
|
@@ -7616,12 +8190,12 @@ import fs10 from "fs";
|
|
|
7616
8190
|
import path7 from "path";
|
|
7617
8191
|
|
|
7618
8192
|
// src/crypto/certificate-service.ts
|
|
7619
|
-
import * as
|
|
8193
|
+
import * as crypto7 from "crypto";
|
|
7620
8194
|
import * as x5092 from "@peculiar/x509";
|
|
7621
8195
|
var CertificateService = class {
|
|
7622
8196
|
static getSha256Fingerprint(certPem) {
|
|
7623
8197
|
const der = extractDerFromPem2(certPem);
|
|
7624
|
-
return
|
|
8198
|
+
return crypto7.createHash("sha256").update(der).digest("hex").toUpperCase();
|
|
7625
8199
|
}
|
|
7626
8200
|
static async generatePersonalCertificate(givenName, surname, serialNumber, commonName, method = "RSA") {
|
|
7627
8201
|
const nameParts = [];
|
|
@@ -7644,7 +8218,7 @@ var CertificateService = class {
|
|
|
7644
8218
|
}
|
|
7645
8219
|
};
|
|
7646
8220
|
async function generateSelfSigned(subject2, method) {
|
|
7647
|
-
x5092.cryptoProvider.set(
|
|
8221
|
+
x5092.cryptoProvider.set(crypto7.webcrypto);
|
|
7648
8222
|
let algorithm;
|
|
7649
8223
|
let signingAlgorithm;
|
|
7650
8224
|
if (method === "ECDSA") {
|
|
@@ -7659,7 +8233,7 @@ async function generateSelfSigned(subject2, method) {
|
|
|
7659
8233
|
};
|
|
7660
8234
|
signingAlgorithm = algorithm;
|
|
7661
8235
|
}
|
|
7662
|
-
const keys = await
|
|
8236
|
+
const keys = await crypto7.webcrypto.subtle.generateKey(
|
|
7663
8237
|
algorithm,
|
|
7664
8238
|
true,
|
|
7665
8239
|
["sign", "verify"]
|
|
@@ -7676,9 +8250,9 @@ async function generateSelfSigned(subject2, method) {
|
|
|
7676
8250
|
serialNumber: Date.now().toString(16)
|
|
7677
8251
|
});
|
|
7678
8252
|
const certPem = cert.toString("pem");
|
|
7679
|
-
const pkcs8 = await
|
|
8253
|
+
const pkcs8 = await crypto7.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
|
|
7680
8254
|
const privateKeyPem = pemEncode2(new Uint8Array(pkcs8), "PRIVATE KEY");
|
|
7681
|
-
const fingerprint =
|
|
8255
|
+
const fingerprint = crypto7.createHash("sha256").update(Buffer.from(cert.rawData)).digest("hex").toUpperCase();
|
|
7682
8256
|
return { certificatePem: certPem, privateKeyPem, fingerprint };
|
|
7683
8257
|
}
|
|
7684
8258
|
function extractDerFromPem2(pem) {
|
|
@@ -8136,6 +8710,7 @@ var invoice2 = defineCommand9({
|
|
|
8136
8710
|
format: { type: "string", description: "Output format: png or svg (default: png)" },
|
|
8137
8711
|
size: { type: "string", description: "QR code size in pixels (default: 300)" },
|
|
8138
8712
|
label: { type: "string", description: "Label text (SVG only)" },
|
|
8713
|
+
offline: { type: "boolean", description: 'Use "OFFLINE" as label (SVG, overrides --label)' },
|
|
8139
8714
|
o: { type: "string", description: "Output file path" },
|
|
8140
8715
|
env: { type: "string", description: "Environment (test/demo/prod)" },
|
|
8141
8716
|
json: { type: "boolean", description: "Output as JSON" },
|
|
@@ -8155,7 +8730,8 @@ var invoice2 = defineCommand9({
|
|
|
8155
8730
|
return;
|
|
8156
8731
|
}
|
|
8157
8732
|
if (format === "svg") {
|
|
8158
|
-
const
|
|
8733
|
+
const effectiveLabel = args.offline ? "OFFLINE" : args.label;
|
|
8734
|
+
const svg = effectiveLabel ? await QrCodeService.generateQrCodeSvgWithLabel(url2, effectiveLabel, { width: size }) : await QrCodeService.generateQrCodeSvg(url2, { width: size });
|
|
8159
8735
|
if (args.o) {
|
|
8160
8736
|
fs11.writeFileSync(args.o, svg);
|
|
8161
8737
|
outputSuccess(`QR code saved to ${args.o}
|
|
@@ -9022,11 +9598,11 @@ var doctorCommand = defineCommand14({
|
|
|
9022
9598
|
const controller = new AbortController();
|
|
9023
9599
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
9024
9600
|
try {
|
|
9025
|
-
const
|
|
9601
|
+
const status7 = await client.lighthouse.getStatus();
|
|
9026
9602
|
checks.push({
|
|
9027
9603
|
name: "connectivity",
|
|
9028
9604
|
status: "pass",
|
|
9029
|
-
message: `API reachable (${
|
|
9605
|
+
message: `API reachable (${status7.status})`
|
|
9030
9606
|
});
|
|
9031
9607
|
} finally {
|
|
9032
9608
|
clearTimeout(timeout);
|
|
@@ -9470,11 +10046,474 @@ var setupCommand = defineCommand16({
|
|
|
9470
10046
|
}
|
|
9471
10047
|
});
|
|
9472
10048
|
|
|
10049
|
+
// src/cli/commands/offline.ts
|
|
10050
|
+
import * as fs14 from "fs";
|
|
10051
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
10052
|
+
import { consola as consola15 } from "consola";
|
|
10053
|
+
|
|
10054
|
+
// src/offline/file-storage.ts
|
|
10055
|
+
import * as fs13 from "fs/promises";
|
|
10056
|
+
import * as path9 from "path";
|
|
10057
|
+
import * as os6 from "os";
|
|
10058
|
+
|
|
10059
|
+
// src/offline/storage.ts
|
|
10060
|
+
function matchesFilter(invoice3, filter) {
|
|
10061
|
+
if (filter.status !== void 0) {
|
|
10062
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
10063
|
+
if (!statuses.includes(invoice3.status)) return false;
|
|
10064
|
+
}
|
|
10065
|
+
if (filter.mode !== void 0 && invoice3.mode !== filter.mode) return false;
|
|
10066
|
+
if (filter.sellerNip !== void 0 && invoice3.sellerNip !== filter.sellerNip) return false;
|
|
10067
|
+
if (filter.expiringBefore !== void 0) {
|
|
10068
|
+
const cutoff = typeof filter.expiringBefore === "string" ? new Date(filter.expiringBefore).getTime() : filter.expiringBefore.getTime();
|
|
10069
|
+
if (new Date(invoice3.submitBy).getTime() >= cutoff) return false;
|
|
10070
|
+
}
|
|
10071
|
+
return true;
|
|
10072
|
+
}
|
|
10073
|
+
|
|
10074
|
+
// src/offline/file-storage.ts
|
|
10075
|
+
function resolveDir(dir) {
|
|
10076
|
+
if (dir === "~" || dir.startsWith("~/")) {
|
|
10077
|
+
return path9.join(os6.homedir(), dir.slice(1));
|
|
10078
|
+
}
|
|
10079
|
+
return dir;
|
|
10080
|
+
}
|
|
10081
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10082
|
+
function validateId(id) {
|
|
10083
|
+
if (!UUID_RE.test(id)) {
|
|
10084
|
+
throw new Error(`Invalid invoice ID: ${id}`);
|
|
10085
|
+
}
|
|
10086
|
+
}
|
|
10087
|
+
var FileOfflineInvoiceStorage = class {
|
|
10088
|
+
dir;
|
|
10089
|
+
constructor(directory) {
|
|
10090
|
+
this.dir = resolveDir(directory ?? "~/.ksef/offline");
|
|
10091
|
+
}
|
|
10092
|
+
async ensureDir() {
|
|
10093
|
+
await fs13.mkdir(this.dir, { recursive: true });
|
|
10094
|
+
}
|
|
10095
|
+
filePath(id) {
|
|
10096
|
+
validateId(id);
|
|
10097
|
+
return path9.join(this.dir, `${id}.json`);
|
|
10098
|
+
}
|
|
10099
|
+
async save(invoice3) {
|
|
10100
|
+
await this.ensureDir();
|
|
10101
|
+
const file = this.filePath(invoice3.id);
|
|
10102
|
+
const tmp = `${file}.tmp`;
|
|
10103
|
+
await fs13.writeFile(tmp, JSON.stringify(invoice3, null, 2));
|
|
10104
|
+
await fs13.rename(tmp, file);
|
|
10105
|
+
}
|
|
10106
|
+
async get(id) {
|
|
10107
|
+
const file = this.filePath(id);
|
|
10108
|
+
try {
|
|
10109
|
+
return JSON.parse(await fs13.readFile(file, "utf-8"));
|
|
10110
|
+
} catch (err) {
|
|
10111
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
10112
|
+
return null;
|
|
10113
|
+
}
|
|
10114
|
+
console.warn(`Warning: Failed to read offline invoice ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10115
|
+
return null;
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
10118
|
+
async list(filter) {
|
|
10119
|
+
let files;
|
|
10120
|
+
try {
|
|
10121
|
+
files = (await fs13.readdir(this.dir)).filter((f) => f.endsWith(".json"));
|
|
10122
|
+
} catch {
|
|
10123
|
+
return [];
|
|
10124
|
+
}
|
|
10125
|
+
const results = [];
|
|
10126
|
+
for (const file of files) {
|
|
10127
|
+
try {
|
|
10128
|
+
const data = JSON.parse(
|
|
10129
|
+
await fs13.readFile(path9.join(this.dir, file), "utf-8")
|
|
10130
|
+
);
|
|
10131
|
+
if (!filter || matchesFilter(data, filter)) {
|
|
10132
|
+
results.push(data);
|
|
10133
|
+
}
|
|
10134
|
+
} catch (err) {
|
|
10135
|
+
console.warn(`Warning: Skipping corrupt offline invoice file ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
10138
|
+
return results;
|
|
10139
|
+
}
|
|
10140
|
+
/**
|
|
10141
|
+
* Update invoice metadata (read-modify-write).
|
|
10142
|
+
*
|
|
10143
|
+
* Note: No file locking — concurrent updates to the same ID may cause
|
|
10144
|
+
* lost writes. Acceptable for CLI (single process). Library consumers
|
|
10145
|
+
* running parallel operations should use external locking.
|
|
10146
|
+
*/
|
|
10147
|
+
async update(id, updates) {
|
|
10148
|
+
const existing = await this.get(id);
|
|
10149
|
+
if (!existing) throw new Error(`Offline invoice not found: ${id}`);
|
|
10150
|
+
await this.save({ ...existing, ...updates });
|
|
10151
|
+
}
|
|
10152
|
+
async delete(id) {
|
|
10153
|
+
const file = this.filePath(id);
|
|
10154
|
+
try {
|
|
10155
|
+
await fs13.unlink(file);
|
|
10156
|
+
} catch (e) {
|
|
10157
|
+
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
10158
|
+
}
|
|
10159
|
+
}
|
|
10160
|
+
};
|
|
10161
|
+
|
|
10162
|
+
// src/cli/commands/offline.ts
|
|
10163
|
+
init_invoice_field_extractor();
|
|
10164
|
+
function getGlobalOpts14(args) {
|
|
10165
|
+
return {
|
|
10166
|
+
env: args.env,
|
|
10167
|
+
json: args.json,
|
|
10168
|
+
verbose: args.verbose,
|
|
10169
|
+
timeout: args.timeout,
|
|
10170
|
+
nip: args.nip
|
|
10171
|
+
};
|
|
10172
|
+
}
|
|
10173
|
+
function getStorage(args) {
|
|
10174
|
+
return new FileOfflineInvoiceStorage(args["store-dir"]);
|
|
10175
|
+
}
|
|
10176
|
+
var globalArgs = {
|
|
10177
|
+
env: { type: "string", description: "Environment (test/demo/prod)" },
|
|
10178
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
10179
|
+
verbose: { type: "boolean", description: "Verbose output" },
|
|
10180
|
+
timeout: { type: "string", description: "Request timeout (ms)" },
|
|
10181
|
+
nip: { type: "string", description: "NIP number" },
|
|
10182
|
+
"store-dir": { type: "string", description: "Offline invoice store directory" }
|
|
10183
|
+
};
|
|
10184
|
+
var generate3 = defineCommand17({
|
|
10185
|
+
meta: { name: "generate", description: "Generate offline invoice metadata with QR codes" },
|
|
10186
|
+
args: {
|
|
10187
|
+
...globalArgs,
|
|
10188
|
+
xml: { type: "positional", description: "Invoice XML file path", required: true },
|
|
10189
|
+
mode: { type: "string", description: "Offline mode (offline24/offline/awaryjny)" },
|
|
10190
|
+
key: { type: "string", description: "Private key PEM file for KOD II signing" },
|
|
10191
|
+
"cert-serial": { type: "string", description: "Certificate serial number (hex)" },
|
|
10192
|
+
"context-type": { type: "string", description: "Seller context type (default: Nip)" },
|
|
10193
|
+
"context-id": { type: "string", description: "Seller context value" },
|
|
10194
|
+
"qr-format": { type: "string", description: "QR format: png or svg (default: png)" },
|
|
10195
|
+
"qr-out": { type: "string", description: "Directory to save QR images" },
|
|
10196
|
+
"no-store": { type: "boolean", description: "Do not save to local store" }
|
|
10197
|
+
},
|
|
10198
|
+
run({ args }) {
|
|
10199
|
+
return withErrorHandler(async () => {
|
|
10200
|
+
const globalOpts = getGlobalOpts14(args);
|
|
10201
|
+
const config = loadConfig();
|
|
10202
|
+
const xmlPath = args.xml;
|
|
10203
|
+
if (!fs14.existsSync(xmlPath)) {
|
|
10204
|
+
throw new Error(`File not found: ${xmlPath}`);
|
|
10205
|
+
}
|
|
10206
|
+
const invoiceXml = fs14.readFileSync(xmlPath, "utf-8");
|
|
10207
|
+
const sellerNip = args.nip ?? config.nip;
|
|
10208
|
+
if (!sellerNip) {
|
|
10209
|
+
throw new Error("NIP is required. Use --nip or set via `ksef config set --nip`");
|
|
10210
|
+
}
|
|
10211
|
+
const contextType = args["context-type"] ?? "Nip";
|
|
10212
|
+
const contextId = args["context-id"] ?? sellerNip;
|
|
10213
|
+
const client = createClient(globalOpts);
|
|
10214
|
+
const workflow = client.offline;
|
|
10215
|
+
if (args.key && !args["cert-serial"]) {
|
|
10216
|
+
throw new Error("--cert-serial is required when using --key");
|
|
10217
|
+
}
|
|
10218
|
+
const certificate2 = args.key ? {
|
|
10219
|
+
privateKeyPem: fs14.readFileSync(args.key, "utf-8"),
|
|
10220
|
+
certificateSerial: args["cert-serial"]
|
|
10221
|
+
} : void 0;
|
|
10222
|
+
const validModes = ["offline24", "offline", "awaryjny", "awaria_calkowita"];
|
|
10223
|
+
const modeArg = args.mode ?? "offline24";
|
|
10224
|
+
if (!validModes.includes(modeArg)) {
|
|
10225
|
+
throw new Error(`Invalid --mode "${modeArg}". Must be one of: ${validModes.join(", ")}`);
|
|
10226
|
+
}
|
|
10227
|
+
const storage = args["no-store"] ? void 0 : getStorage(args);
|
|
10228
|
+
const { invoiceNumber, invoiceDate } = extractInvoiceFields(invoiceXml);
|
|
10229
|
+
const metadata = await workflow.generate(
|
|
10230
|
+
{
|
|
10231
|
+
invoiceNumber,
|
|
10232
|
+
invoiceDate,
|
|
10233
|
+
invoiceXml,
|
|
10234
|
+
sellerNip,
|
|
10235
|
+
sellerIdentifier: { type: contextType, value: contextId }
|
|
10236
|
+
},
|
|
10237
|
+
{
|
|
10238
|
+
mode: modeArg,
|
|
10239
|
+
certificate: certificate2,
|
|
10240
|
+
storage
|
|
10241
|
+
}
|
|
10242
|
+
);
|
|
10243
|
+
if (args["qr-out"]) {
|
|
10244
|
+
const outDir = args["qr-out"];
|
|
10245
|
+
fs14.mkdirSync(outDir, { recursive: true });
|
|
10246
|
+
const format = args["qr-format"] ?? "png";
|
|
10247
|
+
if (format !== "png" && format !== "svg") {
|
|
10248
|
+
throw new Error(`Invalid --qr-format "${format}". Must be one of: png, svg`);
|
|
10249
|
+
}
|
|
10250
|
+
const shortId = metadata.id.slice(0, 8);
|
|
10251
|
+
if (format === "svg") {
|
|
10252
|
+
const svg1 = await QrCodeService.generateQrCodeSvgWithLabel(metadata.kod1Url, "OFFLINE");
|
|
10253
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod1.svg`, svg1);
|
|
10254
|
+
if (metadata.kod2Url) {
|
|
10255
|
+
const svg2 = await QrCodeService.generateQrCodeSvgWithLabel(metadata.kod2Url, "CERTYFIKAT");
|
|
10256
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod2.svg`, svg2);
|
|
10257
|
+
}
|
|
10258
|
+
} else {
|
|
10259
|
+
const png1 = await QrCodeService.generateQrCode(metadata.kod1Url);
|
|
10260
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod1.png`, png1);
|
|
10261
|
+
if (metadata.kod2Url) {
|
|
10262
|
+
const png2 = await QrCodeService.generateQrCode(metadata.kod2Url);
|
|
10263
|
+
fs14.writeFileSync(`${outDir}/${shortId}-kod2.png`, png2);
|
|
10264
|
+
}
|
|
10265
|
+
}
|
|
10266
|
+
outputSuccess(`QR codes saved to ${outDir}/`);
|
|
10267
|
+
}
|
|
10268
|
+
if (!certificate2) {
|
|
10269
|
+
outputWarning("No certificate provided \u2014 KOD II QR code was not generated. Use --key and --cert-serial for offline compliance.");
|
|
10270
|
+
}
|
|
10271
|
+
if (args.json) {
|
|
10272
|
+
outputResult(metadata, { json: true });
|
|
10273
|
+
} else {
|
|
10274
|
+
outputKeyValue({
|
|
10275
|
+
ID: metadata.id,
|
|
10276
|
+
Mode: metadata.mode,
|
|
10277
|
+
Status: metadata.status,
|
|
10278
|
+
Deadline: metadata.submitBy,
|
|
10279
|
+
"KOD I": metadata.kod1Url,
|
|
10280
|
+
"KOD II": metadata.kod2Url ?? "(not generated)"
|
|
10281
|
+
}, { json: false });
|
|
10282
|
+
}
|
|
10283
|
+
});
|
|
10284
|
+
}
|
|
10285
|
+
});
|
|
10286
|
+
var list4 = defineCommand17({
|
|
10287
|
+
meta: { name: "list", description: "List stored offline invoices" },
|
|
10288
|
+
args: {
|
|
10289
|
+
...globalArgs,
|
|
10290
|
+
status: { type: "string", description: "Filter by status" },
|
|
10291
|
+
mode: { type: "string", description: "Filter by mode" },
|
|
10292
|
+
expiring: { type: "boolean", description: "Show only invoices expiring within 24h" }
|
|
10293
|
+
},
|
|
10294
|
+
run({ args }) {
|
|
10295
|
+
return withErrorHandler(async () => {
|
|
10296
|
+
const storage = getStorage(args);
|
|
10297
|
+
const filter = {};
|
|
10298
|
+
if (args.status) filter.status = args.status;
|
|
10299
|
+
if (args.mode) filter.mode = args.mode;
|
|
10300
|
+
if (args.expiring) {
|
|
10301
|
+
const cutoff = new Date(Date.now() + 24 * 60 * 60 * 1e3);
|
|
10302
|
+
filter.expiringBefore = cutoff.toISOString();
|
|
10303
|
+
}
|
|
10304
|
+
const invoices2 = await storage.list(Object.keys(filter).length > 0 ? filter : void 0);
|
|
10305
|
+
if (invoices2.length === 0) {
|
|
10306
|
+
if (args.json) {
|
|
10307
|
+
outputResult([], { json: true });
|
|
10308
|
+
} else {
|
|
10309
|
+
outputSuccess("No offline invoices found.");
|
|
10310
|
+
}
|
|
10311
|
+
return;
|
|
10312
|
+
}
|
|
10313
|
+
outputTable(
|
|
10314
|
+
invoices2.map((i) => ({
|
|
10315
|
+
id: i.id.slice(0, 8),
|
|
10316
|
+
number: i.invoiceNumber,
|
|
10317
|
+
mode: i.mode,
|
|
10318
|
+
status: i.status,
|
|
10319
|
+
deadline: i.submitBy.slice(0, 19),
|
|
10320
|
+
nip: i.sellerNip
|
|
10321
|
+
})),
|
|
10322
|
+
[
|
|
10323
|
+
{ key: "id", label: "ID" },
|
|
10324
|
+
{ key: "number", label: "Number" },
|
|
10325
|
+
{ key: "mode", label: "Mode" },
|
|
10326
|
+
{ key: "status", label: "Status" },
|
|
10327
|
+
{ key: "deadline", label: "Deadline" },
|
|
10328
|
+
{ key: "nip", label: "NIP" }
|
|
10329
|
+
],
|
|
10330
|
+
{ json: args.json }
|
|
10331
|
+
);
|
|
10332
|
+
});
|
|
10333
|
+
}
|
|
10334
|
+
});
|
|
10335
|
+
var status6 = defineCommand17({
|
|
10336
|
+
meta: { name: "status", description: "Show offline invoice details" },
|
|
10337
|
+
args: {
|
|
10338
|
+
...globalArgs,
|
|
10339
|
+
id: { type: "positional", description: "Invoice ID", required: true }
|
|
10340
|
+
},
|
|
10341
|
+
run({ args }) {
|
|
10342
|
+
return withErrorHandler(async () => {
|
|
10343
|
+
const storage = getStorage(args);
|
|
10344
|
+
const invoice3 = await storage.get(args.id);
|
|
10345
|
+
if (!invoice3) {
|
|
10346
|
+
throw new Error(`Offline invoice not found: ${args.id}`);
|
|
10347
|
+
}
|
|
10348
|
+
if (args.json) {
|
|
10349
|
+
outputResult(invoice3, { json: true });
|
|
10350
|
+
} else {
|
|
10351
|
+
outputKeyValue({
|
|
10352
|
+
ID: invoice3.id,
|
|
10353
|
+
Number: invoice3.invoiceNumber,
|
|
10354
|
+
Date: invoice3.invoiceDate,
|
|
10355
|
+
Mode: invoice3.mode,
|
|
10356
|
+
Reason: invoice3.reason,
|
|
10357
|
+
Status: invoice3.status,
|
|
10358
|
+
"Seller NIP": invoice3.sellerNip,
|
|
10359
|
+
Deadline: invoice3.submitBy,
|
|
10360
|
+
"Generated At": invoice3.generatedAt,
|
|
10361
|
+
"Submitted At": invoice3.submittedAt ?? "-",
|
|
10362
|
+
"KSeF Reference": invoice3.ksefReferenceNumber ?? "-",
|
|
10363
|
+
"KOD I URL": invoice3.kod1Url,
|
|
10364
|
+
"KOD II URL": invoice3.kod2Url ?? "-",
|
|
10365
|
+
Error: invoice3.error ? `${invoice3.error.code}: ${invoice3.error.message}` : "-"
|
|
10366
|
+
}, { json: false });
|
|
10367
|
+
}
|
|
10368
|
+
});
|
|
10369
|
+
}
|
|
10370
|
+
});
|
|
10371
|
+
var submit = defineCommand17({
|
|
10372
|
+
meta: { name: "submit", description: "Submit offline invoices to KSeF" },
|
|
10373
|
+
args: {
|
|
10374
|
+
...globalArgs,
|
|
10375
|
+
all: { type: "boolean", description: "Submit all pending invoices" },
|
|
10376
|
+
"no-check-expiry": { type: "boolean", description: "Skip expiry check" },
|
|
10377
|
+
ids: { type: "positional", description: "Invoice IDs to submit" }
|
|
10378
|
+
},
|
|
10379
|
+
run({ args }) {
|
|
10380
|
+
return withErrorHandler(async () => {
|
|
10381
|
+
const globalOpts = getGlobalOpts14(args);
|
|
10382
|
+
const { client } = await requireSession(globalOpts);
|
|
10383
|
+
const storage = getStorage(args);
|
|
10384
|
+
const invoiceIds = args.ids ? args.ids.split(",").map((s) => s.trim()) : void 0;
|
|
10385
|
+
if (args.all && invoiceIds) {
|
|
10386
|
+
throw new Error("Cannot use --all together with specific invoice IDs. Use one or the other.");
|
|
10387
|
+
}
|
|
10388
|
+
if (!args.all && !invoiceIds) {
|
|
10389
|
+
throw new Error("Specify invoice IDs or use --all to submit all pending invoices.");
|
|
10390
|
+
}
|
|
10391
|
+
const result = await client.offline.submit(client, {
|
|
10392
|
+
storage,
|
|
10393
|
+
invoiceIds: args.all ? void 0 : invoiceIds,
|
|
10394
|
+
checkExpiry: !args["no-check-expiry"]
|
|
10395
|
+
});
|
|
10396
|
+
if (result.total === 0) {
|
|
10397
|
+
outputSuccess("No pending invoices to submit.");
|
|
10398
|
+
return;
|
|
10399
|
+
}
|
|
10400
|
+
if (args.json) {
|
|
10401
|
+
outputResult(result, { json: true });
|
|
10402
|
+
} else {
|
|
10403
|
+
outputSuccess(
|
|
10404
|
+
`Submitted: ${result.submitted}, Accepted: ${result.accepted}, Rejected: ${result.rejected}, Expired: ${result.expired}`
|
|
10405
|
+
);
|
|
10406
|
+
if (result.results.length > 0) {
|
|
10407
|
+
outputTable(
|
|
10408
|
+
result.results.map((r) => ({
|
|
10409
|
+
id: r.invoiceId.slice(0, 8),
|
|
10410
|
+
number: r.invoiceNumber,
|
|
10411
|
+
status: r.status,
|
|
10412
|
+
ref: r.ksefReferenceNumber ?? "-"
|
|
10413
|
+
})),
|
|
10414
|
+
[
|
|
10415
|
+
{ key: "id", label: "ID" },
|
|
10416
|
+
{ key: "number", label: "Number" },
|
|
10417
|
+
{ key: "status", label: "Status" },
|
|
10418
|
+
{ key: "ref", label: "KSeF Ref" }
|
|
10419
|
+
],
|
|
10420
|
+
{ json: false }
|
|
10421
|
+
);
|
|
10422
|
+
}
|
|
10423
|
+
}
|
|
10424
|
+
});
|
|
10425
|
+
}
|
|
10426
|
+
});
|
|
10427
|
+
var correct = defineCommand17({
|
|
10428
|
+
meta: { name: "correct", description: "Technical correction for rejected offline invoice" },
|
|
10429
|
+
args: {
|
|
10430
|
+
...globalArgs,
|
|
10431
|
+
id: { type: "positional", description: "Rejected invoice ID", required: true },
|
|
10432
|
+
xml: { type: "positional", description: "Corrected XML file path", required: true }
|
|
10433
|
+
},
|
|
10434
|
+
run({ args }) {
|
|
10435
|
+
return withErrorHandler(async () => {
|
|
10436
|
+
const globalOpts = getGlobalOpts14(args);
|
|
10437
|
+
const { client } = await requireSession(globalOpts);
|
|
10438
|
+
const storage = getStorage(args);
|
|
10439
|
+
const xmlPath = args.xml;
|
|
10440
|
+
if (!fs14.existsSync(xmlPath)) {
|
|
10441
|
+
throw new Error(`File not found: ${xmlPath}`);
|
|
10442
|
+
}
|
|
10443
|
+
const correctedXml = fs14.readFileSync(xmlPath, "utf-8");
|
|
10444
|
+
const result = await client.offline.correct(client, {
|
|
10445
|
+
rejectedInvoiceId: args.id,
|
|
10446
|
+
correctedInvoiceXml: correctedXml,
|
|
10447
|
+
storage
|
|
10448
|
+
});
|
|
10449
|
+
if (args.json) {
|
|
10450
|
+
outputResult(result, { json: true });
|
|
10451
|
+
} else {
|
|
10452
|
+
outputSuccess(`Correction submitted. KSeF ref: ${result.ksefReferenceNumber ?? "pending"}`);
|
|
10453
|
+
}
|
|
10454
|
+
});
|
|
10455
|
+
}
|
|
10456
|
+
});
|
|
10457
|
+
var del = defineCommand17({
|
|
10458
|
+
meta: { name: "delete", description: "Delete offline invoice from local store" },
|
|
10459
|
+
args: {
|
|
10460
|
+
...globalArgs,
|
|
10461
|
+
id: { type: "positional", description: "Invoice ID to delete" },
|
|
10462
|
+
expired: { type: "boolean", description: "Delete all expired invoices" },
|
|
10463
|
+
force: { type: "boolean", description: "Skip confirmation prompt" }
|
|
10464
|
+
},
|
|
10465
|
+
run({ args }) {
|
|
10466
|
+
return withErrorHandler(async () => {
|
|
10467
|
+
const storage = getStorage(args);
|
|
10468
|
+
if (args.expired) {
|
|
10469
|
+
const expired = await storage.list({ status: "EXPIRED" });
|
|
10470
|
+
if (expired.length === 0) {
|
|
10471
|
+
outputSuccess("No expired invoices to delete.");
|
|
10472
|
+
return;
|
|
10473
|
+
}
|
|
10474
|
+
if (!args.force) {
|
|
10475
|
+
if (!process.stdin.isTTY) {
|
|
10476
|
+
throw new Error(
|
|
10477
|
+
`${expired.length} expired invoice(s) would be deleted. Use --force to confirm in non-interactive mode.`
|
|
10478
|
+
);
|
|
10479
|
+
}
|
|
10480
|
+
const confirmed = await consola15.prompt(
|
|
10481
|
+
`Delete ${expired.length} expired invoice(s)?`,
|
|
10482
|
+
{ type: "confirm", initial: false }
|
|
10483
|
+
);
|
|
10484
|
+
if (!confirmed) {
|
|
10485
|
+
consola15.info("Cancelled.");
|
|
10486
|
+
return;
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
for (const inv of expired) {
|
|
10490
|
+
await storage.delete(inv.id);
|
|
10491
|
+
}
|
|
10492
|
+
outputSuccess(`Deleted ${expired.length} expired invoice(s).`);
|
|
10493
|
+
return;
|
|
10494
|
+
}
|
|
10495
|
+
if (!args.id) {
|
|
10496
|
+
throw new Error("Specify an invoice ID or use --expired to delete all expired invoices.");
|
|
10497
|
+
}
|
|
10498
|
+
const invoice3 = await storage.get(args.id);
|
|
10499
|
+
if (!invoice3) {
|
|
10500
|
+
throw new Error(`Offline invoice not found: ${args.id}`);
|
|
10501
|
+
}
|
|
10502
|
+
await storage.delete(args.id);
|
|
10503
|
+
outputSuccess(`Deleted offline invoice ${args.id}`);
|
|
10504
|
+
});
|
|
10505
|
+
}
|
|
10506
|
+
});
|
|
10507
|
+
var offlineCommand = defineCommand17({
|
|
10508
|
+
meta: { name: "offline", description: "Offline invoice management" },
|
|
10509
|
+
subCommands: { generate: generate3, list: list4, status: status6, submit, correct, delete: del }
|
|
10510
|
+
});
|
|
10511
|
+
|
|
9473
10512
|
// src/cli/index.ts
|
|
9474
10513
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
9475
|
-
var pkg = JSON.parse(
|
|
10514
|
+
var pkg = JSON.parse(readFileSync9(resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
9476
10515
|
var version = pkg.version;
|
|
9477
|
-
var main =
|
|
10516
|
+
var main = defineCommand18({
|
|
9478
10517
|
meta: {
|
|
9479
10518
|
name: "ksef",
|
|
9480
10519
|
version,
|
|
@@ -9494,6 +10533,7 @@ var main = defineCommand17({
|
|
|
9494
10533
|
limits: limitsCommand,
|
|
9495
10534
|
peppol: peppolCommand,
|
|
9496
10535
|
"test-data": testDataCommand,
|
|
10536
|
+
offline: offlineCommand,
|
|
9497
10537
|
doctor: doctorCommand,
|
|
9498
10538
|
completion: completionCommand
|
|
9499
10539
|
}
|