kassza 0.12.0 → 0.14.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.en.md +48 -0
- package/README.md +427 -32
- package/agents/README.md +26 -6
- package/agents/api.md +218 -3
- package/agents/pitfalls.md +27 -3
- package/agents/recipes.md +280 -51
- package/agents/skills/kassza/SKILL.md +14 -8
- package/agents/skills/migrate-from-szamlazz-js/SKILL.md +39 -0
- package/dist/batch/index.cjs +346 -0
- package/dist/batch/index.d.cts +91 -0
- package/dist/batch/index.d.ts +91 -0
- package/dist/batch/index.js +339 -0
- package/dist/cli.cjs +643 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +643 -0
- package/dist/client-CLFvJCVW.cjs +1452 -0
- package/dist/{client-CL3fuH6A.js → client-Cvf-hTz_.js} +306 -515
- package/dist/client-DeWWQ-RZ.d.ts +341 -0
- package/dist/client-SVZu25MB.d.cts +341 -0
- package/dist/context-CFNKzStE.d.cts +449 -0
- package/dist/context-kb33TgzE.d.ts +449 -0
- package/dist/cookie-stores/index.cjs +21 -150
- package/dist/cookie-stores/index.d.cts +4 -63
- package/dist/cookie-stores/index.d.ts +4 -63
- package/dist/cookie-stores/index.js +3 -144
- package/dist/create-CflXrPip.js +204 -0
- package/dist/create-CvUvLUaT.cjs +257 -0
- package/dist/{create-once-D0pwuenl.cjs → create-once-9yxR_Er_.cjs} +291 -33
- package/dist/create-once-Bh6fOWkW.d.cts +20 -0
- package/dist/{create-once-CqdYmQD8.js → create-once-Ca2gMk1h.js} +250 -34
- package/dist/create-once-VKp0tVow.d.ts +20 -0
- package/dist/{crypto-B8lERvR1.cjs → crypto-Dpn4FHQ6.cjs} +9 -0
- package/dist/{crypto-DK_osBVy.js → crypto-usDg8L2K.js} +4 -1
- package/dist/data-link/index.cjs +232 -0
- package/dist/data-link/index.d.cts +29 -0
- package/dist/data-link/index.d.ts +29 -0
- package/dist/data-link/index.js +224 -0
- package/dist/delegation/index.cjs +35 -34
- package/dist/delegation/index.d.cts +2 -1
- package/dist/delegation/index.d.ts +2 -1
- package/dist/delegation/index.js +4 -3
- package/dist/{errors-DLrb0g64.js → errors-DtN4g5Z9.js} +9 -1
- package/dist/{errors-DxeuTslP.cjs → errors-EE96_6D_.cjs} +20 -0
- package/dist/get-BdMTseh4.cjs +271 -0
- package/dist/get-Byc9T5WO.d.cts +151 -0
- package/dist/get-Byc9T5WO.d.ts +151 -0
- package/dist/get-CZEShCcj.js +260 -0
- package/dist/index-BhUPY_EH.d.cts +109 -0
- package/dist/index-BqGgQ4t3.d.ts +109 -0
- package/dist/{index-CYAGCdKJ.d.cts → index-C-7yWV7L.d.cts} +24 -1
- package/dist/{index-CYAGCdKJ.d.ts → index-C-7yWV7L.d.ts} +24 -1
- package/dist/index.cjs +19 -5
- package/dist/index.d.cts +11 -19
- package/dist/index.d.ts +11 -19
- package/dist/index.js +6 -6
- package/dist/ipn/index.cjs +26 -5
- package/dist/ipn/index.d.cts +12 -1
- package/dist/ipn/index.d.ts +12 -1
- package/dist/ipn/index.js +26 -6
- package/dist/issue-Bin4tKD_.cjs +988 -0
- package/dist/issue-DMA1Nbr1.js +905 -0
- package/dist/journal/index.cjs +454 -0
- package/dist/journal/index.d.cts +18 -0
- package/dist/journal/index.d.ts +18 -0
- package/dist/journal/index.js +445 -0
- package/dist/journal-DvI81Kt7.d.cts +114 -0
- package/dist/journal-Y9qrIB8G.d.ts +114 -0
- package/dist/mcp/index.cjs +10 -0
- package/dist/mcp/index.d.cts +71 -0
- package/dist/mcp/index.d.ts +71 -0
- package/dist/mcp/index.js +2 -0
- package/dist/money/index.cjs +2 -1
- package/dist/money/index.d.cts +2 -2
- package/dist/money/index.d.ts +2 -2
- package/dist/money/index.js +2 -2
- package/dist/{money-DAA7OwiO.js → money-Cw457SD6.js} +120 -12
- package/dist/{money-DrhGkWHW.cjs → money-K9j0wPit.cjs} +124 -10
- package/dist/nav/index.cjs +1046 -0
- package/dist/nav/index.d.cts +247 -0
- package/dist/nav/index.d.ts +247 -0
- package/dist/nav/index.js +1012 -0
- package/dist/nav-BNbc8U0R.cjs +203 -0
- package/dist/nav-BcCerwWS.js +156 -0
- package/dist/node/index.cjs +124 -0
- package/dist/node/index.d.cts +35 -0
- package/dist/node/index.d.ts +35 -0
- package/dist/node/index.js +120 -0
- package/dist/observe/index.cjs +311 -0
- package/dist/observe/index.d.cts +53 -0
- package/dist/observe/index.d.ts +53 -0
- package/dist/observe/index.js +303 -0
- package/dist/once-BN_3B5OF.cjs +103 -0
- package/dist/once-CqqZxsXG.js +74 -0
- package/dist/parse-Cr7yQ3_z.js +291 -0
- package/dist/parse-DOA2XQ3e.cjs +380 -0
- package/dist/payments/barion.cjs +26 -2
- package/dist/payments/barion.d.cts +2 -2
- package/dist/payments/barion.d.ts +2 -2
- package/dist/payments/barion.js +26 -2
- package/dist/payments/index.cjs +7 -2
- package/dist/payments/index.d.cts +4 -4
- package/dist/payments/index.d.ts +4 -4
- package/dist/payments/index.js +3 -3
- package/dist/payments/paypal.cjs +16 -2
- package/dist/payments/paypal.d.cts +2 -2
- package/dist/payments/paypal.d.ts +2 -2
- package/dist/payments/paypal.js +16 -2
- package/dist/payments/revolut.cjs +17 -3
- package/dist/payments/revolut.d.cts +2 -2
- package/dist/payments/revolut.d.ts +2 -2
- package/dist/payments/revolut.js +17 -3
- package/dist/payments/simplepay.cjs +37 -5
- package/dist/payments/simplepay.d.cts +2 -2
- package/dist/payments/simplepay.d.ts +2 -2
- package/dist/payments/simplepay.js +37 -5
- package/dist/payments/stripe.cjs +20 -3
- package/dist/payments/stripe.d.cts +2 -2
- package/dist/payments/stripe.d.ts +2 -2
- package/dist/payments/stripe.js +20 -3
- package/dist/push-BXj0Sf0Z.d.ts +52 -0
- package/dist/push-D4nNbCNV.d.cts +52 -0
- package/dist/reference-B5TATqb5.js +124 -0
- package/dist/reference-BIdYMK0t.cjs +183 -0
- package/dist/reports/index.cjs +11 -164
- package/dist/reports/index.d.cts +1 -1
- package/dist/reports/index.d.ts +1 -1
- package/dist/reports/index.js +1 -154
- package/dist/request-body-CH6ZGOrq.cjs +34 -0
- package/dist/request-body-uZHMRlQr.js +29 -0
- package/dist/serialize-B2eFh3D-.js +219 -0
- package/dist/serialize-CquIsxA-.cjs +290 -0
- package/dist/server-BrJUbXs7.js +1505 -0
- package/dist/server-Dw9j8Mnd.cjs +1558 -0
- package/dist/session-Ckl5eJhV.d.ts +7 -0
- package/dist/{session-CKOuE6Jc.cjs → session-DeM5vjvC.cjs} +69 -13
- package/dist/{session-rMKaJcmq.js → session-SaEEXu2N.js} +58 -14
- package/dist/session-YbChChx7.d.cts +7 -0
- package/dist/storage/fs.cjs +1 -1
- package/dist/storage/fs.js +1 -1
- package/dist/storage/index.cjs +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/store-C1rHd9DB.d.cts +19 -0
- package/dist/store-C1rHd9DB.d.ts +19 -0
- package/dist/stores/index.cjs +24 -0
- package/dist/stores/index.d.cts +4 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.js +3 -0
- package/dist/stores-BNrS0dWl.cjs +506 -0
- package/dist/stores-DhjGBrl_.js +441 -0
- package/dist/testing/index.cjs +1500 -15
- package/dist/testing/index.d.cts +198 -3
- package/dist/testing/index.d.ts +198 -3
- package/dist/testing/index.js +1498 -15
- package/dist/{types-COf1LAD-.d.ts → types-B1A8mUIE.d.cts} +14 -2
- package/dist/{types-COf1LAD-.d.cts → types-ByAEvGsZ.d.ts} +14 -2
- package/dist/{types-k--zHf4l.d.ts → types-D1zDOH_F.d.ts} +1 -1
- package/dist/{types-DH8QoNZK.d.cts → types-lnKkQdot.d.cts} +1 -1
- package/dist/warnings-B3DXnveC.d.cts +31 -0
- package/dist/warnings-B3DXnveC.d.ts +31 -0
- package/dist/warnings-DeIw5RFj.cjs +20 -0
- package/dist/warnings-DwTl5J-2.js +15 -0
- package/dist/{webhook-D4lvtex1.js → webhook-CQVShAfI.js} +47 -26
- package/dist/{webhook-C76bV-4R.cjs → webhook-Ckj-_hPD.cjs} +70 -25
- package/dist/webhook-CmdhGiH5.d.cts +11 -0
- package/dist/webhook-D6lEejob.d.ts +11 -0
- package/llms.txt +6 -5
- package/package.json +134 -4
- package/dist/client-CickRRFr.d.ts +0 -838
- package/dist/client-CsmGIFRS.cjs +0 -1637
- package/dist/client-DvWZoX_E.d.cts +0 -838
- package/dist/issue-BsyYdrIG.cjs +0 -1531
- package/dist/issue-C6Fcjvln.js +0 -1274
- package/dist/session-Dm_45x8K.d.cts +0 -11
- package/dist/session-Dm_45x8K.d.ts +0 -11
- package/dist/webhook-CfaDgcL0.d.cts +0 -6
- package/dist/webhook-q2OzNwd7.d.ts +0 -6
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
import { a as optionalEl, d as findChildren, f as parseXml, l as childText, n as buildXmlDocument, o as XmlParseError, r as el, t as XSI_NAMESPACE, u as findChild } from "../serialize-B2eFh3D-.js";
|
|
2
|
+
import { a as todayInBudapest, n as toAgentDate } from "../dates-CXyj7Azy.js";
|
|
3
|
+
import { n as decimalPlaces, r as roundMoney } from "../rounding-BLyHjjI5.js";
|
|
4
|
+
import { n as digestHex, t as bytesToHex } from "../crypto-usDg8L2K.js";
|
|
5
|
+
import { o as NAV_VAT_CATEGORIES, t as compareReceiptNumbers } from "../nav-BcCerwWS.js";
|
|
6
|
+
//#region src/nav/errors.ts
|
|
7
|
+
const NAV_RECEIPT_ERROR_CODES = {
|
|
8
|
+
EPGP0002: {
|
|
9
|
+
name: "NEM_LETEZO_SZOFTVER",
|
|
10
|
+
message: "Nem létező nyugtakiállító szoftver megnevezés.",
|
|
11
|
+
category: "business",
|
|
12
|
+
hint: "Rögzítsd a szoftver nevét a registerSoftware hívással (/issuing-software/create), mielőtt adatot küldesz."
|
|
13
|
+
},
|
|
14
|
+
EPGP0009: {
|
|
15
|
+
name: "NYUGTAADAT_MODOSITAS_NEM_ENGEDELYEZETT",
|
|
16
|
+
message: "A nyugta-adatszolgáltatás módosítása nem engedélyezett.",
|
|
17
|
+
category: "business",
|
|
18
|
+
hint: "Érvénytelenített vagy már módosított adatszolgáltatás nem módosítható; a legutóbbi RECORDED rekordot módosítsd."
|
|
19
|
+
},
|
|
20
|
+
EPGP0010: {
|
|
21
|
+
name: "HIBAS_NYUGTAADAT_MENTES",
|
|
22
|
+
message: "Hiba a nyugta-adatszolgáltatás mentése során.",
|
|
23
|
+
category: "technical"
|
|
24
|
+
},
|
|
25
|
+
EPGP0011: {
|
|
26
|
+
name: "NEM_LETEZO_NYUGTAADAT",
|
|
27
|
+
message: "A megadott nyugta-adatszolgáltatás nem létezik.",
|
|
28
|
+
category: "business"
|
|
29
|
+
},
|
|
30
|
+
EPGP0013: {
|
|
31
|
+
name: "ISMERETLEN_DEVIZA_KOD",
|
|
32
|
+
message: "Ismeretlen pénznemkód.",
|
|
33
|
+
category: "validation",
|
|
34
|
+
hint: "Csak a currencies() (/currency/list) által visszaadott pénznem küldhető."
|
|
35
|
+
},
|
|
36
|
+
EPGP0017: {
|
|
37
|
+
name: "HIBAS_AFA_KATEGORIA",
|
|
38
|
+
message: "Nem található a megadott ÁFA kategória.",
|
|
39
|
+
category: "validation",
|
|
40
|
+
hint: "Csak a vatCategories() (/vat-category/list) által a tárgynapra érvényes kategória küldhető."
|
|
41
|
+
},
|
|
42
|
+
EPGP0020: {
|
|
43
|
+
name: "DUPLIKALT_AFA_KATEGORIA",
|
|
44
|
+
message: "Egy ÁFA kategória többször szerepel az adatszolgáltatásban.",
|
|
45
|
+
category: "validation"
|
|
46
|
+
},
|
|
47
|
+
EPGP0046: {
|
|
48
|
+
name: "ELTERO_TOKEN_HIBA",
|
|
49
|
+
message: "A kérés törzsszáma eltér a tokenben szereplő törzsszámtól.",
|
|
50
|
+
category: "auth"
|
|
51
|
+
},
|
|
52
|
+
EPGP0051: {
|
|
53
|
+
name: "LETEZO_HELYESBITO_ADATSZOLGALTATAS",
|
|
54
|
+
message: "Erre az adatszolgáltatásra már létezik helyesbítő adatszolgáltatás.",
|
|
55
|
+
category: "business",
|
|
56
|
+
hint: "Egy rekord csak egyszer módosítható; a módosítással létrejött új rekordot módosítsd tovább."
|
|
57
|
+
},
|
|
58
|
+
EPGP9000: {
|
|
59
|
+
name: "INTERNAL_ERROR",
|
|
60
|
+
message: "Belső, váratlan NAV hiba.",
|
|
61
|
+
category: "technical"
|
|
62
|
+
},
|
|
63
|
+
EPGP9001: {
|
|
64
|
+
name: "REQUEST_VALIDACIO_SIKERTELEN",
|
|
65
|
+
message: "A kérés nem felel meg a NAV validációs szabályainak.",
|
|
66
|
+
category: "validation"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const RETRYABLE_CATEGORIES = /* @__PURE__ */ new Set([
|
|
70
|
+
"network",
|
|
71
|
+
"timeout",
|
|
72
|
+
"technical"
|
|
73
|
+
]);
|
|
74
|
+
var NavReceiptError = class extends Error {
|
|
75
|
+
name = "NavReceiptError";
|
|
76
|
+
category;
|
|
77
|
+
code;
|
|
78
|
+
operation;
|
|
79
|
+
httpStatus;
|
|
80
|
+
fieldErrors;
|
|
81
|
+
hint;
|
|
82
|
+
rawResponse;
|
|
83
|
+
details;
|
|
84
|
+
constructor(message, options) {
|
|
85
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
86
|
+
this.category = options.category;
|
|
87
|
+
this.code = options.code;
|
|
88
|
+
this.operation = options.operation;
|
|
89
|
+
this.httpStatus = options.httpStatus;
|
|
90
|
+
this.fieldErrors = options.fieldErrors ?? [];
|
|
91
|
+
this.hint = options.hint;
|
|
92
|
+
this.rawResponse = options.rawResponse;
|
|
93
|
+
this.details = options.details;
|
|
94
|
+
}
|
|
95
|
+
get retryable() {
|
|
96
|
+
return RETRYABLE_CATEGORIES.has(this.category);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function isNavReceiptError(error) {
|
|
100
|
+
return error instanceof NavReceiptError;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/nav/parse.ts
|
|
104
|
+
const RAW_PREVIEW_LENGTH = 2e3;
|
|
105
|
+
const UNAUTHORIZED$1 = 401;
|
|
106
|
+
const FORBIDDEN = 403;
|
|
107
|
+
const SERVER_ERROR = 500;
|
|
108
|
+
const ERROR_ROOT_CATEGORIES = /* @__PURE__ */ new Map([
|
|
109
|
+
["TechnicalErrorResponse", "technical"],
|
|
110
|
+
["BusinessErrorResponse", "business"],
|
|
111
|
+
["InvalidRequestResponse", "validation"]
|
|
112
|
+
]);
|
|
113
|
+
function preview(body) {
|
|
114
|
+
const trimmed = body.trim();
|
|
115
|
+
return trimmed === "" ? void 0 : trimmed.slice(0, RAW_PREVIEW_LENGTH);
|
|
116
|
+
}
|
|
117
|
+
function statusCategory(status) {
|
|
118
|
+
if (status === UNAUTHORIZED$1 || status === FORBIDDEN) return "auth";
|
|
119
|
+
if (status >= SERVER_ERROR) return "technical";
|
|
120
|
+
return "unexpected_response";
|
|
121
|
+
}
|
|
122
|
+
function unexpected(operation, status, body, detail, cause) {
|
|
123
|
+
const category = status >= 200 && status < 300 ? "unexpected_response" : statusCategory(status);
|
|
124
|
+
return new NavReceiptError(`Váratlan NAV válasz (${operation}, HTTP ${status}): ${detail}`, {
|
|
125
|
+
category,
|
|
126
|
+
operation,
|
|
127
|
+
httpStatus: status,
|
|
128
|
+
rawResponse: preview(body),
|
|
129
|
+
cause
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function fieldErrors(root) {
|
|
133
|
+
return findChildren(root, "error").map((entry) => ({
|
|
134
|
+
field: childText(entry, "field"),
|
|
135
|
+
error: childText(entry, "error")
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
function errorFromRoot(operation, status, body, root) {
|
|
139
|
+
const code = childText(root, "errorCode");
|
|
140
|
+
const info = code === void 0 ? void 0 : NAV_RECEIPT_ERROR_CODES[code];
|
|
141
|
+
const message = childText(root, "message") ?? info?.message ?? "ismeretlen hiba";
|
|
142
|
+
const fields = fieldErrors(root);
|
|
143
|
+
const details = fields.map((entry) => [entry.field, entry.error].filter(Boolean).join(": ")).filter(Boolean);
|
|
144
|
+
const category = (status === UNAUTHORIZED$1 || status === FORBIDDEN ? "auth" : void 0) ?? info?.category ?? ERROR_ROOT_CATEGORIES.get(root.name) ?? statusCategory(status);
|
|
145
|
+
const suffix = details.length > 0 ? ` (${details.join("; ")})` : "";
|
|
146
|
+
return new NavReceiptError(`NAV hiba${code ? ` [${code}]` : ""}: ${message}${suffix}`, {
|
|
147
|
+
category,
|
|
148
|
+
code,
|
|
149
|
+
operation,
|
|
150
|
+
httpStatus: status,
|
|
151
|
+
fieldErrors: fields,
|
|
152
|
+
hint: info?.hint,
|
|
153
|
+
rawResponse: preview(body)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function parseNavResponse(operation, expectedRoot, status, body) {
|
|
157
|
+
if (body.trim() === "") throw unexpected(operation, status, body, "üres válasz.");
|
|
158
|
+
let root;
|
|
159
|
+
try {
|
|
160
|
+
root = parseXml(body);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!(error instanceof XmlParseError)) throw error;
|
|
163
|
+
throw unexpected(operation, status, body, "a válasz nem XML.", error);
|
|
164
|
+
}
|
|
165
|
+
if (ERROR_ROOT_CATEGORIES.has(root.name) || childText(root, "resultCode") === "ERROR") throw errorFromRoot(operation, status, body, root);
|
|
166
|
+
if (status < 200 || status >= 300) throw unexpected(operation, status, body, `a válasz gyökéreleme ${root.name}.`);
|
|
167
|
+
if (root.name !== expectedRoot) throw unexpected(operation, status, body, `${expectedRoot} helyett ${root.name} érkezett.`);
|
|
168
|
+
return root;
|
|
169
|
+
}
|
|
170
|
+
function requiredText(element, name, operation) {
|
|
171
|
+
const value = childText(element, name);
|
|
172
|
+
if (value === void 0) throw new NavReceiptError(`A NAV válaszából (${operation}) hiányzik a(z) ${name} mező.`, {
|
|
173
|
+
category: "unexpected_response",
|
|
174
|
+
operation
|
|
175
|
+
});
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
function requiredNumber(element, name, operation) {
|
|
179
|
+
const value = Number(requiredText(element, name, operation));
|
|
180
|
+
if (!Number.isFinite(value)) throw new NavReceiptError(`A NAV válaszában (${operation}) a(z) ${name} nem szám.`, {
|
|
181
|
+
category: "unexpected_response",
|
|
182
|
+
operation
|
|
183
|
+
});
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
function status(element, operation) {
|
|
187
|
+
const value = requiredText(element, "status", operation);
|
|
188
|
+
if (value !== "RECORDED" && value !== "INVALIDATED") throw new NavReceiptError(`Ismeretlen adatszolgáltatás-státusz: ${value}.`, {
|
|
189
|
+
category: "unexpected_response",
|
|
190
|
+
operation
|
|
191
|
+
});
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
function softwareName(element, operation) {
|
|
195
|
+
return requiredText(findChild(element, "issuingSoftware"), "name", operation);
|
|
196
|
+
}
|
|
197
|
+
function parseAuthTokenResponse(root) {
|
|
198
|
+
const token = childText(root, "token");
|
|
199
|
+
const validTo = childText(root, "validTo");
|
|
200
|
+
if (!token || !validTo) throw new NavReceiptError("A NAV nem adott hozzáférési tokent.", {
|
|
201
|
+
category: "auth",
|
|
202
|
+
operation: "authToken"
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
token,
|
|
206
|
+
validTo
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function parseIdResponse(root, operation) {
|
|
210
|
+
return requiredText(root, "id", operation);
|
|
211
|
+
}
|
|
212
|
+
function listItem(element) {
|
|
213
|
+
const operation = "listReceipts";
|
|
214
|
+
return {
|
|
215
|
+
id: requiredText(element, "id", operation),
|
|
216
|
+
applicableDate: requiredText(element, "applicableDate", operation),
|
|
217
|
+
serialNumber: requiredText(element, "serialNumber", operation),
|
|
218
|
+
numberOfSaleDocument: requiredNumber(element, "numberOfSaleDocument", operation),
|
|
219
|
+
numberOfModifyingDocument: requiredNumber(element, "numberOfModifyingDocument", operation),
|
|
220
|
+
totalAmount: requiredNumber(element, "totalAmount", operation),
|
|
221
|
+
totalAmountInForint: requiredNumber(element, "totalAmountInForint", operation),
|
|
222
|
+
status: status(element, operation),
|
|
223
|
+
softwareName: softwareName(element, operation)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function parseReceiptListResponse(root) {
|
|
227
|
+
const operation = "listReceipts";
|
|
228
|
+
const paging = findChild(root, "paging");
|
|
229
|
+
return {
|
|
230
|
+
items: findChildren(findChild(root, "result"), "receipt").map(listItem),
|
|
231
|
+
page: requiredNumber(paging, "page", operation),
|
|
232
|
+
pageSize: requiredNumber(paging, "pageSize", operation),
|
|
233
|
+
totalRowCount: requiredNumber(paging, "totalRowCount", operation)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function vatRows(root, operation) {
|
|
237
|
+
return findChildren(findChild(root, "vatCategoryItems"), "vatCategory").map((row) => ({
|
|
238
|
+
vat: requiredText(row, "vat", operation),
|
|
239
|
+
saleDocument: requiredNumber(row, "saleDocument", operation),
|
|
240
|
+
modifyingDocument: requiredNumber(row, "modifyingDocument", operation)
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
function parseReceiptDetailResponse(root) {
|
|
244
|
+
const operation = "receiptDetail";
|
|
245
|
+
const rate = childText(root, "exchangeRate");
|
|
246
|
+
return {
|
|
247
|
+
id: requiredText(root, "id", operation),
|
|
248
|
+
softwareName: softwareName(root, operation),
|
|
249
|
+
applicableDate: requiredText(root, "applicableDate", operation),
|
|
250
|
+
status: status(root, operation),
|
|
251
|
+
serialNumber: requiredText(root, "serialNumber", operation),
|
|
252
|
+
currency: requiredText(root, "currency", operation),
|
|
253
|
+
exchangeRate: rate === void 0 ? null : Number(rate),
|
|
254
|
+
vatCategories: vatRows(root, operation),
|
|
255
|
+
total: requiredNumber(root, "total", operation),
|
|
256
|
+
totalAmountInForint: requiredNumber(root, "totalAmountInForint", operation),
|
|
257
|
+
numberOfSaleDocument: requiredNumber(root, "numberOfSaleDocument", operation),
|
|
258
|
+
numberOfModifyingDocument: requiredNumber(root, "numberOfModifyingDocument", operation)
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function parseIssuingSoftwareListResponse(root) {
|
|
262
|
+
return findChildren(findChild(root, "issuingSoftwareList"), "software").map((software) => requiredText(software, "name", "listIssuingSoftware"));
|
|
263
|
+
}
|
|
264
|
+
function parseVatCategoryResponse(root) {
|
|
265
|
+
return findChildren(findChild(root, "categories"), "category").map((category) => ({
|
|
266
|
+
name: requiredText(category, "name", "vatCategories"),
|
|
267
|
+
validFrom: requiredText(category, "validFrom", "vatCategories"),
|
|
268
|
+
validTo: childText(category, "validTo")
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
function parseCurrencyResponse(root) {
|
|
272
|
+
return findChildren(findChild(root, "currencies"), "currency").map((currency) => ({
|
|
273
|
+
code: requiredText(currency, "code", "currencies"),
|
|
274
|
+
name: requiredText(currency, "name", "currencies")
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/nav/sha3.ts
|
|
279
|
+
const RATE_BYTES = 72;
|
|
280
|
+
const OUTPUT_BYTES = 64;
|
|
281
|
+
const LANE_BYTES = 8;
|
|
282
|
+
const LANES = 25;
|
|
283
|
+
const ROUNDS = 24;
|
|
284
|
+
const DOMAIN_SUFFIX = 6;
|
|
285
|
+
const FINAL_BIT = 128;
|
|
286
|
+
const MASK_64 = (1n << 64n) - 1n;
|
|
287
|
+
const ROUND_CONSTANTS = [
|
|
288
|
+
1n,
|
|
289
|
+
32898n,
|
|
290
|
+
9223372036854808714n,
|
|
291
|
+
9223372039002292224n,
|
|
292
|
+
32907n,
|
|
293
|
+
2147483649n,
|
|
294
|
+
9223372039002292353n,
|
|
295
|
+
9223372036854808585n,
|
|
296
|
+
138n,
|
|
297
|
+
136n,
|
|
298
|
+
2147516425n,
|
|
299
|
+
2147483658n,
|
|
300
|
+
2147516555n,
|
|
301
|
+
9223372036854775947n,
|
|
302
|
+
9223372036854808713n,
|
|
303
|
+
9223372036854808579n,
|
|
304
|
+
9223372036854808578n,
|
|
305
|
+
9223372036854775936n,
|
|
306
|
+
32778n,
|
|
307
|
+
9223372039002259466n,
|
|
308
|
+
9223372039002292353n,
|
|
309
|
+
9223372036854808704n,
|
|
310
|
+
2147483649n,
|
|
311
|
+
9223372039002292232n
|
|
312
|
+
];
|
|
313
|
+
const ROTATIONS = [
|
|
314
|
+
0,
|
|
315
|
+
1,
|
|
316
|
+
62,
|
|
317
|
+
28,
|
|
318
|
+
27,
|
|
319
|
+
36,
|
|
320
|
+
44,
|
|
321
|
+
6,
|
|
322
|
+
55,
|
|
323
|
+
20,
|
|
324
|
+
3,
|
|
325
|
+
10,
|
|
326
|
+
43,
|
|
327
|
+
25,
|
|
328
|
+
39,
|
|
329
|
+
41,
|
|
330
|
+
45,
|
|
331
|
+
15,
|
|
332
|
+
21,
|
|
333
|
+
8,
|
|
334
|
+
18,
|
|
335
|
+
2,
|
|
336
|
+
61,
|
|
337
|
+
56,
|
|
338
|
+
14
|
|
339
|
+
];
|
|
340
|
+
function lane(lanes, index) {
|
|
341
|
+
return lanes[index] ?? 0n;
|
|
342
|
+
}
|
|
343
|
+
function rotateLeft(value, shift) {
|
|
344
|
+
if (shift === 0) return value;
|
|
345
|
+
const amount = BigInt(shift);
|
|
346
|
+
return (value << amount | value >> 64n - amount) & MASK_64;
|
|
347
|
+
}
|
|
348
|
+
function keccakF1600(state) {
|
|
349
|
+
const columns = new Array(5).fill(0n);
|
|
350
|
+
const rotated = new Array(LANES).fill(0n);
|
|
351
|
+
for (let round = 0; round < ROUNDS; round++) {
|
|
352
|
+
for (let x = 0; x < 5; x++) columns[x] = lane(state, x) ^ lane(state, x + 5) ^ lane(state, x + 10) ^ lane(state, x + 15) ^ lane(state, x + 20);
|
|
353
|
+
for (let x = 0; x < 5; x++) {
|
|
354
|
+
const delta = lane(columns, (x + 4) % 5) ^ rotateLeft(lane(columns, (x + 1) % 5), 1);
|
|
355
|
+
for (let y = 0; y < LANES; y += 5) state[x + y] = lane(state, x + y) ^ delta;
|
|
356
|
+
}
|
|
357
|
+
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) {
|
|
358
|
+
const source = x + 5 * y;
|
|
359
|
+
rotated[y + 5 * ((2 * x + 3 * y) % 5)] = rotateLeft(lane(state, source), ROTATIONS[source] ?? 0);
|
|
360
|
+
}
|
|
361
|
+
for (let y = 0; y < LANES; y += 5) for (let x = 0; x < 5; x++) {
|
|
362
|
+
const current = lane(rotated, x + y);
|
|
363
|
+
const next = lane(rotated, (x + 1) % 5 + y);
|
|
364
|
+
const afterNext = lane(rotated, (x + 2) % 5 + y);
|
|
365
|
+
state[x + y] = (current ^ ~next & afterNext) & MASK_64;
|
|
366
|
+
}
|
|
367
|
+
state[0] = lane(state, 0) ^ (ROUND_CONSTANTS[round] ?? 0n);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function pad(data) {
|
|
371
|
+
const blocks = Math.floor(data.length / RATE_BYTES) + 1;
|
|
372
|
+
const padded = new Uint8Array(blocks * RATE_BYTES);
|
|
373
|
+
padded.set(data);
|
|
374
|
+
padded[data.length] = (padded[data.length] ?? 0) ^ DOMAIN_SUFFIX;
|
|
375
|
+
padded[padded.length - 1] = (padded[padded.length - 1] ?? 0) ^ FINAL_BIT;
|
|
376
|
+
return padded;
|
|
377
|
+
}
|
|
378
|
+
function sha3_512(data) {
|
|
379
|
+
const state = new Array(LANES).fill(0n);
|
|
380
|
+
const padded = pad(data);
|
|
381
|
+
const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
|
|
382
|
+
for (let offset = 0; offset < padded.length; offset += RATE_BYTES) {
|
|
383
|
+
for (let index = 0; index < RATE_BYTES / LANE_BYTES; index++) state[index] = lane(state, index) ^ view.getBigUint64(offset + index * LANE_BYTES, true);
|
|
384
|
+
keccakF1600(state);
|
|
385
|
+
}
|
|
386
|
+
const output = new Uint8Array(OUTPUT_BYTES);
|
|
387
|
+
const outputView = new DataView(output.buffer);
|
|
388
|
+
for (let index = 0; index < OUTPUT_BYTES / LANE_BYTES; index++) outputView.setBigUint64(index * LANE_BYTES, lane(state, index), true);
|
|
389
|
+
return output;
|
|
390
|
+
}
|
|
391
|
+
const encoder = new TextEncoder();
|
|
392
|
+
function sha3_512Hex(input) {
|
|
393
|
+
return bytesToHex(sha3_512(typeof input === "string" ? encoder.encode(input) : input));
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/nav/validate.ts
|
|
397
|
+
const NAV_SERIAL_NUMBER_PATTERN = /^[A-Za-zÁÉÍÓÖŐÚÜŰáéíóöőúüű0-9_/\-\\](?:[A-Za-zÁÉÍÓÖŐÚÜŰáéíóöőúüű0-9/_\-\\ ]*[A-Za-zÁÉÍÓÖŐÚÜŰáéíóöőúüű0-9/_\-\\])?$/;
|
|
398
|
+
const NAV_SOFTWARE_NAME_PATTERN = /^[A-ZÁÉÍÓÖŐÚÜŰa-záéíóöőúüű0-9.\s\-@+_]+$/;
|
|
399
|
+
const NAV_VAT_CATEGORY_PATTERN = /^[A-Za-z0-9áéíóöőúüűÁÉÍÓÖŐÚÜŰ% ]+$/;
|
|
400
|
+
const NAV_REPORT_ID_PATTERN = /^[0-9]{8}_[0-9]{8}_[0-9]+$/;
|
|
401
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
402
|
+
const CURRENCY_PATTERN = /^[A-Z]{3}$/;
|
|
403
|
+
const TAXPAYER_ID_PATTERN = /^\d{8}$/;
|
|
404
|
+
const MAX_SERIAL_LENGTH = 50;
|
|
405
|
+
const MAX_SOFTWARE_NAME_LENGTH = 100;
|
|
406
|
+
const MAX_VAT_NAME_LENGTH = 50;
|
|
407
|
+
const MAX_REPORT_ID_LENGTH = 50;
|
|
408
|
+
const MAX_ROW_AMOUNT = 999999999.99;
|
|
409
|
+
const MAX_COUNT = 9999999;
|
|
410
|
+
const MIN_EXCHANGE_RATE = 1;
|
|
411
|
+
const MAX_EXCHANGE_RATE = 1e3;
|
|
412
|
+
const AMOUNT_DECIMALS = 2;
|
|
413
|
+
const RATE_DECIMALS = 4;
|
|
414
|
+
const AMOUNT_TOLERANCE$2 = .005;
|
|
415
|
+
function invalid$1(message, hint) {
|
|
416
|
+
return new NavReceiptError(message, {
|
|
417
|
+
category: "validation",
|
|
418
|
+
hint
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function assertDate(value, label) {
|
|
422
|
+
const parsed = DATE_PATTERN.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00Z`) : void 0;
|
|
423
|
+
if (!parsed || Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) throw invalid$1(`A(z) ${label} nem érvényes ÉÉÉÉ-HH-NN dátum: "${value}".`);
|
|
424
|
+
}
|
|
425
|
+
function navDate(value, label = "dátum") {
|
|
426
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
427
|
+
assertDate(trimmed, label);
|
|
428
|
+
return trimmed;
|
|
429
|
+
}
|
|
430
|
+
function navTaxpayerId(taxNumber) {
|
|
431
|
+
const digits = typeof taxNumber === "string" ? taxNumber.replace(/[\s-]/g, "") : "";
|
|
432
|
+
const taxpayerId = digits.slice(0, 8);
|
|
433
|
+
if (!TAXPAYER_ID_PATTERN.test(taxpayerId) || digits.length !== 8 && digits.length !== 11) throw new NavReceiptError(`Az adószám (${taxNumber}) nem 8 jegyű törzsszám vagy 11 jegyű adószám.`, { category: "configuration" });
|
|
434
|
+
return taxpayerId;
|
|
435
|
+
}
|
|
436
|
+
function assertSoftwareName(name) {
|
|
437
|
+
const trimmed = typeof name === "string" ? name.trim() : "";
|
|
438
|
+
if (trimmed === "" || trimmed.length > MAX_SOFTWARE_NAME_LENGTH || !NAV_SOFTWARE_NAME_PATTERN.test(trimmed)) throw invalid$1(`A nyugtakiállító szoftver neve („${name}”) legfeljebb 100 karakter lehet, és csak betűt, számot, szóközt és a . - @ + _ jeleket tartalmazhatja.`);
|
|
439
|
+
return trimmed;
|
|
440
|
+
}
|
|
441
|
+
function assertReportId(id) {
|
|
442
|
+
const trimmed = typeof id === "string" ? id.trim() : "";
|
|
443
|
+
if (trimmed.length > MAX_REPORT_ID_LENGTH || !NAV_REPORT_ID_PATTERN.test(trimmed)) throw invalid$1(`Az adatszolgáltatás azonosítója („${id}”) nem TÖRZSSZÁM_ÉÉÉÉHHNN_SORSZÁM formátumú.`);
|
|
444
|
+
return trimmed;
|
|
445
|
+
}
|
|
446
|
+
function assertAmount(value, label, min, max) {
|
|
447
|
+
if (!Number.isFinite(value) || value < min || value > max) throw invalid$1(`A(z) ${label} (${value}) a megengedett ${min} és ${max} közötti tartományon kívül esik.`);
|
|
448
|
+
if (decimalPlaces(value) > AMOUNT_DECIMALS) throw invalid$1(`A(z) ${label} (${value}) legfeljebb 2 tizedesjegyet tartalmazhat.`);
|
|
449
|
+
}
|
|
450
|
+
function assertCount(value, label) {
|
|
451
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_COUNT) throw invalid$1(`A(z) ${label} (${value}) 0 és ${MAX_COUNT} közötti egész szám legyen.`);
|
|
452
|
+
}
|
|
453
|
+
function assertExchangeRate(currency, rate) {
|
|
454
|
+
if (currency === "HUF") {
|
|
455
|
+
if (rate !== null && rate !== 1) throw invalid$1(`Forintos adatszolgáltatásnál az árfolyam null vagy 1 lehet, kapott: ${rate}.`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (rate === null || !Number.isFinite(rate) || rate < MIN_EXCHANGE_RATE || rate > MAX_EXCHANGE_RATE) throw invalid$1(`Devizás (${currency}) adatszolgáltatásnál az 1 egységre vonatkozó árfolyam 1 és 1000 között legyen, kapott: ${rate}.`, "A NAV csak 1 és 1000 közötti, legfeljebb 4 tizedesjegyű árfolyamot fogad el.");
|
|
459
|
+
if (decimalPlaces(rate) > RATE_DECIMALS) throw invalid$1(`Az árfolyam (${rate}) legfeljebb 4 tizedesjegyet tartalmazhat.`);
|
|
460
|
+
}
|
|
461
|
+
function assertVatCategories(data) {
|
|
462
|
+
if (!Array.isArray(data.vatCategories) || data.vatCategories.length === 0) throw invalid$1("Az adatszolgáltatásban legalább egy ÁFA kategóriának szerepelnie kell.");
|
|
463
|
+
const seen = /* @__PURE__ */ new Set();
|
|
464
|
+
let sum = 0;
|
|
465
|
+
for (const row of data.vatCategories) {
|
|
466
|
+
const name = typeof row.vat === "string" ? row.vat : "";
|
|
467
|
+
if (name === "" || name.length > MAX_VAT_NAME_LENGTH || !NAV_VAT_CATEGORY_PATTERN.test(name)) throw invalid$1(`Érvénytelen ÁFA kategória név: „${row.vat}”.`);
|
|
468
|
+
if (seen.has(name)) throw invalid$1(`A(z) „${name}” ÁFA kategória többször szerepel (EPGP0020).`);
|
|
469
|
+
seen.add(name);
|
|
470
|
+
assertAmount(row.saleDocument, `„${name}” értékesítési összege`, 0, MAX_ROW_AMOUNT);
|
|
471
|
+
assertAmount(row.modifyingDocument, `„${name}” módosító összege`, -999999999.99, MAX_ROW_AMOUNT);
|
|
472
|
+
sum += row.saleDocument + row.modifyingDocument;
|
|
473
|
+
}
|
|
474
|
+
return sum;
|
|
475
|
+
}
|
|
476
|
+
function validateNavReceiptData(data, options = {}) {
|
|
477
|
+
assertDate(data.applicableDate, "tárgynap (applicableDate)");
|
|
478
|
+
const today = options.today ?? todayInBudapest();
|
|
479
|
+
if (data.applicableDate > today) throw invalid$1(`A tárgynap (${data.applicableDate}) nem lehet jövőbeli.`);
|
|
480
|
+
if (typeof data.serialNumber !== "string" || data.serialNumber.length > MAX_SERIAL_LENGTH || !NAV_SERIAL_NUMBER_PATTERN.test(data.serialNumber)) throw invalid$1(`A kezdő nyugtasorszám („${data.serialNumber}”) legfeljebb 50 karakter lehet, és csak betűt, számot, szóközt (belül), valamint - _ / \\ jelet tartalmazhat.`);
|
|
481
|
+
if (!CURRENCY_PATTERN.test(data.currency)) throw invalid$1(`A pénznem („${data.currency}”) nem háromjegyű, nagybetűs ISO 4217 kód.`);
|
|
482
|
+
assertExchangeRate(data.currency, data.exchangeRate);
|
|
483
|
+
const sum = assertVatCategories(data);
|
|
484
|
+
if (!Number.isFinite(data.total) || decimalPlaces(data.total) > AMOUNT_DECIMALS) throw invalid$1(`A végösszeg (${data.total}) érvénytelen vagy 2-nél több tizedesjegyű.`);
|
|
485
|
+
if (Math.abs(sum - data.total) > AMOUNT_TOLERANCE$2) throw invalid$1(`A végösszeg (${data.total}) nem egyezik az ÁFA kategóriák összegével (${Math.round(sum * 100) / 100}).`);
|
|
486
|
+
assertCount(data.numberOfSaleDocument, "nyugták száma (numberOfSaleDocument)");
|
|
487
|
+
assertCount(data.numberOfModifyingDocument, "módosító bizonylatok száma (numberOfModifyingDocument)");
|
|
488
|
+
if (data.numberOfSaleDocument === 0 && data.numberOfModifyingDocument === 0) throw invalid$1("A nyugták és a módosító bizonylatok száma közül legalább az egyik nem lehet 0.");
|
|
489
|
+
}
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/nav/xml.ts
|
|
492
|
+
const NAV_RECEIPT_NAMESPACE = "http://schemas.nav.gov.hu/NTCA/1.0/receipt";
|
|
493
|
+
const NAV_SERVICE_NAMESPACE = "http://schemas.nav.gov.hu/NTCA/2.0/common/service";
|
|
494
|
+
const NAV_AUTH_NAMESPACE = "http://schemas.nav.gov.hu/NTCA/2.0/common/authservice";
|
|
495
|
+
const NAV_PAGING_NAMESPACE = "http://schemas.nav.gov.hu/NTCA/2.0/common/paging";
|
|
496
|
+
function contextNode(context) {
|
|
497
|
+
return el("s:context", [el("s:requestId", context.requestId), el("s:timestamp", context.timestamp)]);
|
|
498
|
+
}
|
|
499
|
+
function document(root, children, namespaces = {}) {
|
|
500
|
+
return buildXmlDocument({
|
|
501
|
+
root,
|
|
502
|
+
namespace: NAV_RECEIPT_NAMESPACE,
|
|
503
|
+
namespaces: {
|
|
504
|
+
s: NAV_SERVICE_NAMESPACE,
|
|
505
|
+
...namespaces
|
|
506
|
+
},
|
|
507
|
+
children
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function amount(value) {
|
|
511
|
+
return roundMoney(value, 2);
|
|
512
|
+
}
|
|
513
|
+
function buildAuthTokenXml(context, auth) {
|
|
514
|
+
return buildXmlDocument({
|
|
515
|
+
root: "AuthTokenRequest",
|
|
516
|
+
namespace: NAV_RECEIPT_NAMESPACE,
|
|
517
|
+
namespaces: {
|
|
518
|
+
auth: NAV_AUTH_NAMESPACE,
|
|
519
|
+
s: NAV_SERVICE_NAMESPACE
|
|
520
|
+
},
|
|
521
|
+
children: [
|
|
522
|
+
contextNode(context),
|
|
523
|
+
el("auth:auth", [
|
|
524
|
+
el("auth:login", auth.login),
|
|
525
|
+
el("auth:passwordHash", auth.passwordHash, { cryptoType: "SHA-512" }),
|
|
526
|
+
el("auth:taxNumber", auth.taxPayerId),
|
|
527
|
+
optionalEl("auth:predecessorTaxNumber", auth.predecessorTaxPayerId),
|
|
528
|
+
el("auth:requestSignature", auth.requestSignature, { cryptoType: "SHA3-512" })
|
|
529
|
+
]),
|
|
530
|
+
el("auth:requestVersion", "1.0"),
|
|
531
|
+
el("auth:headerVersion", "1.0")
|
|
532
|
+
]
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function exchangeRateNode(data) {
|
|
536
|
+
if (data.currency === "HUF" || data.exchangeRate === null) return el("exchangeRate", null, { "xsi:nil": "true" });
|
|
537
|
+
return el("exchangeRate", roundMoney(data.exchangeRate, 4));
|
|
538
|
+
}
|
|
539
|
+
function receiptBody(data) {
|
|
540
|
+
return [
|
|
541
|
+
el("currency", data.currency),
|
|
542
|
+
exchangeRateNode(data),
|
|
543
|
+
el("vatCategoryItems", data.vatCategories.map((row) => el("vatCategory", [
|
|
544
|
+
el("vat", row.vat),
|
|
545
|
+
el("saleDocument", amount(row.saleDocument)),
|
|
546
|
+
el("modifyingDocument", amount(row.modifyingDocument))
|
|
547
|
+
]))),
|
|
548
|
+
el("total", amount(data.total)),
|
|
549
|
+
el("numberOfSaleDocument", data.numberOfSaleDocument),
|
|
550
|
+
el("numberOfModifyingDocument", data.numberOfModifyingDocument)
|
|
551
|
+
];
|
|
552
|
+
}
|
|
553
|
+
function buildCreateReceiptXml(context, taxPayerId, softwareName, data) {
|
|
554
|
+
return document("CreateReceiptRequest", [
|
|
555
|
+
contextNode(context),
|
|
556
|
+
el("taxPayerId", taxPayerId),
|
|
557
|
+
el("issuingSoftware", [el("name", softwareName)]),
|
|
558
|
+
el("applicableDate", data.applicableDate),
|
|
559
|
+
el("serialNumber", data.serialNumber),
|
|
560
|
+
...receiptBody(data)
|
|
561
|
+
], { xsi: XSI_NAMESPACE });
|
|
562
|
+
}
|
|
563
|
+
function buildModifyReceiptXml(context, id, taxPayerId, softwareName, data) {
|
|
564
|
+
return document("ModifyReceiptRequest", [
|
|
565
|
+
contextNode(context),
|
|
566
|
+
el("id", id),
|
|
567
|
+
el("taxPayerId", taxPayerId),
|
|
568
|
+
el("issuingSoftware", [el("name", softwareName)]),
|
|
569
|
+
el("applicableDate", data.applicableDate),
|
|
570
|
+
...receiptBody(data)
|
|
571
|
+
], { xsi: XSI_NAMESPACE });
|
|
572
|
+
}
|
|
573
|
+
function buildReceiptListXml(context, taxPayerId, query) {
|
|
574
|
+
return document("ReceiptListRequest", [
|
|
575
|
+
contextNode(context),
|
|
576
|
+
el("paging", [el("page:pageSize", query.pageSize ?? 100), el("page:page", query.page ?? 1)]),
|
|
577
|
+
el("ordering", [el("property", query.orderBy ?? "APPLICABLE_DATE"), el("direction", query.direction ?? "ASC")]),
|
|
578
|
+
el("taxPayerId", taxPayerId),
|
|
579
|
+
el("from", query.from),
|
|
580
|
+
el("to", query.to)
|
|
581
|
+
], { page: NAV_PAGING_NAMESPACE });
|
|
582
|
+
}
|
|
583
|
+
function buildReceiptIdXml(root, context, id, taxPayerId) {
|
|
584
|
+
return document(root, [
|
|
585
|
+
contextNode(context),
|
|
586
|
+
el("id", id),
|
|
587
|
+
el("taxPayerId", taxPayerId)
|
|
588
|
+
]);
|
|
589
|
+
}
|
|
590
|
+
function buildCreateIssuingSoftwareXml(context, taxPayerId, name) {
|
|
591
|
+
return document("CreateIssuingSoftwareRequest", [
|
|
592
|
+
contextNode(context),
|
|
593
|
+
el("taxPayerId", taxPayerId),
|
|
594
|
+
el("name", name)
|
|
595
|
+
]);
|
|
596
|
+
}
|
|
597
|
+
function buildIssuingSoftwareListXml(context, taxPayerId) {
|
|
598
|
+
return document("IssuingSoftwareListRequest", [contextNode(context), el("taxPayerId", taxPayerId)]);
|
|
599
|
+
}
|
|
600
|
+
function buildContextOnlyXml(root, context) {
|
|
601
|
+
return document(root, [contextNode(context)]);
|
|
602
|
+
}
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/nav/client.ts
|
|
605
|
+
const NAV_RECEIPT_URLS = {
|
|
606
|
+
test: "https://bv-receipt-if.enyugta.nav.gov.hu/v1",
|
|
607
|
+
production: "https://receipt-if.enyugta.nav.gov.hu/v1"
|
|
608
|
+
};
|
|
609
|
+
const NAV_DEFAULT_TIMEOUT_MS = 15e3;
|
|
610
|
+
const NAV_MAX_LIST_PAGES = 1e3;
|
|
611
|
+
const OPERATION_PATHS = {
|
|
612
|
+
authToken: "/auth/token",
|
|
613
|
+
listReceipts: "/receipt/list",
|
|
614
|
+
createReceipt: "/receipt/create",
|
|
615
|
+
receiptDetail: "/receipt/detail",
|
|
616
|
+
modifyReceipt: "/receipt/modify",
|
|
617
|
+
invalidateReceipt: "/receipt/invalidate",
|
|
618
|
+
createIssuingSoftware: "/issuing-software/create",
|
|
619
|
+
listIssuingSoftware: "/issuing-software/list",
|
|
620
|
+
vatCategories: "/vat-category/list",
|
|
621
|
+
currencies: "/currency/list"
|
|
622
|
+
};
|
|
623
|
+
const TOKEN_REFRESH_MARGIN_MS = 3e4;
|
|
624
|
+
const LEGACY_REQUEST_ID_LENGTH = 30;
|
|
625
|
+
const REQUEST_ID_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
626
|
+
const UNBIASED_BYTE_LIMIT = 248;
|
|
627
|
+
const MAX_LOGIN_LENGTH = 50;
|
|
628
|
+
const PASSWORD_HASH_PATTERN = /^[0-9A-Fa-f]{128}$/;
|
|
629
|
+
const UNAUTHORIZED = 401;
|
|
630
|
+
const AMOUNT_TOLERANCE$1 = .005;
|
|
631
|
+
const TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?Z$/;
|
|
632
|
+
async function navPasswordHash(password) {
|
|
633
|
+
return (await digestHex("SHA-512", password)).toUpperCase();
|
|
634
|
+
}
|
|
635
|
+
function navSignatureTimestamp(timestamp) {
|
|
636
|
+
const match = TIMESTAMP_PATTERN.exec(timestamp);
|
|
637
|
+
if (!match) throw new NavReceiptError(`Érvénytelen UTC időbélyeg az aláíráshoz: ${timestamp}`, { category: "validation" });
|
|
638
|
+
return match.slice(1, 7).join("");
|
|
639
|
+
}
|
|
640
|
+
function navRequestSignature(requestId, timestamp, signatureKey) {
|
|
641
|
+
return sha3_512Hex(`${requestId}${navSignatureTimestamp(timestamp)}${signatureKey}`).toUpperCase();
|
|
642
|
+
}
|
|
643
|
+
function navLegacyRequestId() {
|
|
644
|
+
let id = "";
|
|
645
|
+
while (id.length < LEGACY_REQUEST_ID_LENGTH) for (const byte of crypto.getRandomValues(new Uint8Array(LEGACY_REQUEST_ID_LENGTH))) {
|
|
646
|
+
if (byte >= UNBIASED_BYTE_LIMIT || id.length >= LEGACY_REQUEST_ID_LENGTH) continue;
|
|
647
|
+
id += REQUEST_ID_ALPHABET[byte % 62];
|
|
648
|
+
}
|
|
649
|
+
return id;
|
|
650
|
+
}
|
|
651
|
+
function configuration(message, hint) {
|
|
652
|
+
return new NavReceiptError(message, {
|
|
653
|
+
category: "configuration",
|
|
654
|
+
hint
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function required(value, label) {
|
|
658
|
+
if (typeof value !== "string" || value.trim() === "") throw configuration(`Hiányzik a NAV ${label}.`);
|
|
659
|
+
return value;
|
|
660
|
+
}
|
|
661
|
+
function resolveOptions(options) {
|
|
662
|
+
if (options.environment !== "test" && options.environment !== "production") throw configuration("Add meg a NAV környezetet: environment: \"test\" vagy \"production\".", "A teszt és az éles NAV rendszer külön technikai felhasználót használ; a kassza nem választ helyetted.");
|
|
663
|
+
const login = required(options.login, "technikai felhasználó login neve").trim();
|
|
664
|
+
if (login.length > MAX_LOGIN_LENGTH) throw configuration("A NAV technikai felhasználó login neve legfeljebb 50 karakter lehet.");
|
|
665
|
+
const passwordHash = options.passwordHash?.trim();
|
|
666
|
+
if (passwordHash !== void 0 && !PASSWORD_HASH_PATTERN.test(passwordHash)) throw configuration("A passwordHash 128 hexadecimális karakterből álló SHA-512 hash legyen.");
|
|
667
|
+
if (passwordHash === void 0 && !options.password) throw configuration("Add meg a technikai felhasználó jelszavát (password) vagy annak SHA-512 hash-ét (passwordHash).");
|
|
668
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
669
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw configuration(`A timeoutMs pozitív szám legyen, kapott: ${timeoutMs}`);
|
|
670
|
+
return {
|
|
671
|
+
baseUrl: (options.baseUrl ?? NAV_RECEIPT_URLS[options.environment]).replace(/\/+$/, ""),
|
|
672
|
+
login,
|
|
673
|
+
password: options.password,
|
|
674
|
+
passwordHash: passwordHash?.toUpperCase(),
|
|
675
|
+
signatureKey: required(options.signatureKey, "aláírókulcs (signatureKey)"),
|
|
676
|
+
taxPayerId: navTaxpayerId(required(options.taxNumber, "adószám (taxNumber)")),
|
|
677
|
+
predecessorTaxPayerId: options.predecessorTaxNumber === void 0 ? void 0 : navTaxpayerId(options.predecessorTaxNumber),
|
|
678
|
+
softwareName: options.softwareName === void 0 ? void 0 : assertSoftwareName(options.softwareName),
|
|
679
|
+
timeoutMs
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function sameReport(existing, data) {
|
|
683
|
+
return Math.abs(existing.totalAmount - data.total) <= AMOUNT_TOLERANCE$1 && existing.numberOfSaleDocument === data.numberOfSaleDocument && existing.numberOfModifyingDocument === data.numberOfModifyingDocument;
|
|
684
|
+
}
|
|
685
|
+
function createNavReceiptClient(options) {
|
|
686
|
+
const resolved = resolveOptions(options);
|
|
687
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
688
|
+
if (typeof fetchImpl !== "function") throw configuration("Nem található fetch implementáció. Add meg a fetch opciót.");
|
|
689
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
690
|
+
const allowWrite = options.allowWrite === true;
|
|
691
|
+
let cachedToken;
|
|
692
|
+
let pendingToken;
|
|
693
|
+
let passwordHashPromise;
|
|
694
|
+
const passwordHash = () => {
|
|
695
|
+
if (resolved.passwordHash !== void 0) return Promise.resolve(resolved.passwordHash);
|
|
696
|
+
passwordHashPromise ??= navPasswordHash(resolved.password ?? "");
|
|
697
|
+
return passwordHashPromise;
|
|
698
|
+
};
|
|
699
|
+
async function send(operation, xml, signal, token) {
|
|
700
|
+
signal?.throwIfAborted();
|
|
701
|
+
const timeout = AbortSignal.timeout(resolved.timeoutMs);
|
|
702
|
+
const combined = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
703
|
+
const headers = {
|
|
704
|
+
"content-type": "application/xml; charset=UTF-8",
|
|
705
|
+
accept: "application/xml"
|
|
706
|
+
};
|
|
707
|
+
if (token !== void 0) headers.authorization = `Bearer ${token}`;
|
|
708
|
+
try {
|
|
709
|
+
const response = await fetchImpl(`${resolved.baseUrl}${OPERATION_PATHS[operation]}`, {
|
|
710
|
+
method: "POST",
|
|
711
|
+
headers,
|
|
712
|
+
body: xml,
|
|
713
|
+
signal: combined
|
|
714
|
+
});
|
|
715
|
+
return {
|
|
716
|
+
status: response.status,
|
|
717
|
+
body: await response.text()
|
|
718
|
+
};
|
|
719
|
+
} catch (error) {
|
|
720
|
+
if (signal?.aborted) throw signal.reason;
|
|
721
|
+
if (timeout.aborted) throw new NavReceiptError(`A NAV nem válaszolt ${resolved.timeoutMs} ms alatt (${operation}).`, {
|
|
722
|
+
category: "timeout",
|
|
723
|
+
operation,
|
|
724
|
+
cause: error
|
|
725
|
+
});
|
|
726
|
+
throw new NavReceiptError(`Hálózati hiba a NAV elérésekor (${operation}).`, {
|
|
727
|
+
category: "network",
|
|
728
|
+
operation,
|
|
729
|
+
cause: error
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function requestToken(signal) {
|
|
734
|
+
const timestamp = now().toISOString();
|
|
735
|
+
const requestId = navLegacyRequestId();
|
|
736
|
+
const response = await send("authToken", buildAuthTokenXml({
|
|
737
|
+
requestId,
|
|
738
|
+
timestamp
|
|
739
|
+
}, {
|
|
740
|
+
login: resolved.login,
|
|
741
|
+
passwordHash: await passwordHash(),
|
|
742
|
+
taxPayerId: resolved.taxPayerId,
|
|
743
|
+
predecessorTaxPayerId: resolved.predecessorTaxPayerId,
|
|
744
|
+
requestSignature: navRequestSignature(requestId, timestamp, resolved.signatureKey)
|
|
745
|
+
}), signal, void 0);
|
|
746
|
+
const token = parseAuthTokenResponse(parseNavResponse("authToken", "AuthTokenResponse", response.status, response.body));
|
|
747
|
+
const validUntil = Date.parse(token.validTo);
|
|
748
|
+
cachedToken = {
|
|
749
|
+
value: token,
|
|
750
|
+
expiresAt: Number.isFinite(validUntil) ? validUntil - TOKEN_REFRESH_MARGIN_MS : 0
|
|
751
|
+
};
|
|
752
|
+
return token;
|
|
753
|
+
}
|
|
754
|
+
async function authenticate(requestOptions = {}) {
|
|
755
|
+
pendingToken ??= requestToken(requestOptions.signal).finally(() => {
|
|
756
|
+
pendingToken = void 0;
|
|
757
|
+
});
|
|
758
|
+
return pendingToken;
|
|
759
|
+
}
|
|
760
|
+
async function currentToken(signal) {
|
|
761
|
+
if (cachedToken && cachedToken.expiresAt > now().getTime()) return cachedToken.value.token;
|
|
762
|
+
return (await authenticate({ signal })).token;
|
|
763
|
+
}
|
|
764
|
+
function context() {
|
|
765
|
+
return {
|
|
766
|
+
requestId: crypto.randomUUID(),
|
|
767
|
+
timestamp: now().toISOString()
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
async function call(operation, expectedRoot, build, parse, requestOptions) {
|
|
771
|
+
for (let attempt = 1;; attempt++) {
|
|
772
|
+
const token = await currentToken(requestOptions.signal);
|
|
773
|
+
const response = await send(operation, build(context()), requestOptions.signal, token);
|
|
774
|
+
if (response.status === UNAUTHORIZED && attempt === 1) {
|
|
775
|
+
cachedToken = void 0;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
return parse(parseNavResponse(operation, expectedRoot, response.status, response.body));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function assertWritable(operation) {
|
|
782
|
+
if (allowWrite) return;
|
|
783
|
+
throw new NavReceiptError("A NAV kliens csak olvasásra van beállítva, ezért nem küld adatot.", {
|
|
784
|
+
category: "write_blocked",
|
|
785
|
+
operation,
|
|
786
|
+
hint: "Ha a Számlázz.hu már beküldi a nyugtáidat, ne küldd be te is, mert az dupla adatszolgáltatás. Ha biztosan neked kell beküldeni (például papír nyugtatömb), add meg az allowWrite: true opciót."
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
function softwareFor(name) {
|
|
790
|
+
const software = name === void 0 ? resolved.softwareName : assertSoftwareName(name);
|
|
791
|
+
if (software === void 0) throw configuration("Add meg a nyugtakiállító szoftver nevét (softwareName), ahogy a NAV-nál rögzítve van.");
|
|
792
|
+
return software;
|
|
793
|
+
}
|
|
794
|
+
function query(input) {
|
|
795
|
+
const from = navDate(input.from, "lekérdezés kezdete (from)");
|
|
796
|
+
const to = navDate(input.to, "lekérdezés vége (to)");
|
|
797
|
+
if (to < from) throw new NavReceiptError(`A lekérdezés vége (${to}) nem lehet korábbi a kezdeténél (${from}).`, {
|
|
798
|
+
category: "validation",
|
|
799
|
+
operation: "listReceipts"
|
|
800
|
+
});
|
|
801
|
+
const pageSize = input.pageSize ?? 100;
|
|
802
|
+
const page = input.page ?? 1;
|
|
803
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw new NavReceiptError(`A pageSize 1 és 100 közötti egész szám legyen, kapott: ${pageSize}`, {
|
|
804
|
+
category: "validation",
|
|
805
|
+
operation: "listReceipts"
|
|
806
|
+
});
|
|
807
|
+
if (!Number.isInteger(page) || page < 1) throw new NavReceiptError(`A page legalább 1 egész szám legyen, kapott: ${page}`, {
|
|
808
|
+
category: "validation",
|
|
809
|
+
operation: "listReceipts"
|
|
810
|
+
});
|
|
811
|
+
return {
|
|
812
|
+
...input,
|
|
813
|
+
from,
|
|
814
|
+
to,
|
|
815
|
+
page,
|
|
816
|
+
pageSize
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
async function listReports(input, requestOptions = {}) {
|
|
820
|
+
const checked = query(input);
|
|
821
|
+
return call("listReceipts", "ReceiptListResponse", (requestContext) => buildReceiptListXml(requestContext, resolved.taxPayerId, checked), parseReceiptListResponse, requestOptions);
|
|
822
|
+
}
|
|
823
|
+
async function listAllReports(input, requestOptions = {}) {
|
|
824
|
+
const items = [];
|
|
825
|
+
for (let page = 1; page <= NAV_MAX_LIST_PAGES; page++) {
|
|
826
|
+
const result = await listReports({
|
|
827
|
+
...input,
|
|
828
|
+
page,
|
|
829
|
+
pageSize: 100
|
|
830
|
+
}, requestOptions);
|
|
831
|
+
items.push(...result.items);
|
|
832
|
+
if (result.items.length === 0 || items.length >= result.totalRowCount) return items;
|
|
833
|
+
}
|
|
834
|
+
throw new NavReceiptError(`A NAV listázás ${NAV_MAX_LIST_PAGES} oldal után sem ért véget; szűkítsd az időszakot.`, {
|
|
835
|
+
category: "unexpected_response",
|
|
836
|
+
operation: "listReceipts"
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
async function submitReport(data, submitOptions = {}) {
|
|
840
|
+
assertWritable("createReceipt");
|
|
841
|
+
const software = softwareFor(submitOptions.softwareName);
|
|
842
|
+
validateNavReceiptData(data, { today: todayInBudapest(now()) });
|
|
843
|
+
const existing = (await listAllReports({
|
|
844
|
+
from: data.applicableDate,
|
|
845
|
+
to: data.applicableDate
|
|
846
|
+
}, { signal: submitOptions.signal })).find((item) => item.status === "RECORDED" && item.serialNumber === data.serialNumber);
|
|
847
|
+
if (existing) {
|
|
848
|
+
if (sameReport(existing, data)) return {
|
|
849
|
+
id: existing.id,
|
|
850
|
+
created: false,
|
|
851
|
+
softwareName: existing.softwareName
|
|
852
|
+
};
|
|
853
|
+
throw new NavReceiptError(`Erre a tárgynapra (${data.applicableDate}) és kezdő sorszámra (${data.serialNumber}) már van rögzített adatszolgáltatás (${existing.id}) eltérő adatokkal.`, {
|
|
854
|
+
category: "conflict",
|
|
855
|
+
operation: "createReceipt",
|
|
856
|
+
details: {
|
|
857
|
+
existingId: existing.id,
|
|
858
|
+
existingSoftware: existing.softwareName
|
|
859
|
+
},
|
|
860
|
+
hint: "Ha a korábbi adat hibás, javítsd a modifyReport(id, …) hívással; ha más szoftver küldte be (például a Számlázz.hu), ne küldd be újra."
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
id: await call("createReceipt", "CreateReceiptResponse", (requestContext) => buildCreateReceiptXml(requestContext, resolved.taxPayerId, software, data), (root) => parseIdResponse(root, "createReceipt"), { signal: submitOptions.signal }),
|
|
865
|
+
created: true,
|
|
866
|
+
softwareName: software
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
async function modifyReport(id, data, submitOptions = {}) {
|
|
870
|
+
assertWritable("modifyReceipt");
|
|
871
|
+
const reportId = assertReportId(id);
|
|
872
|
+
const software = softwareFor(submitOptions.softwareName);
|
|
873
|
+
validateNavReceiptData(data, { today: todayInBudapest(now()) });
|
|
874
|
+
return { id: await call("modifyReceipt", "ModifyReceiptResponse", (requestContext) => buildModifyReceiptXml(requestContext, reportId, resolved.taxPayerId, software, data), (root) => parseIdResponse(root, "modifyReceipt"), { signal: submitOptions.signal }) };
|
|
875
|
+
}
|
|
876
|
+
async function invalidateReport(id, requestOptions = {}) {
|
|
877
|
+
assertWritable("invalidateReceipt");
|
|
878
|
+
const reportId = assertReportId(id);
|
|
879
|
+
await call("invalidateReceipt", "InvalidateReceiptResponse", (requestContext) => buildReceiptIdXml("InvalidateReceiptRequest", requestContext, reportId, resolved.taxPayerId), () => void 0, requestOptions);
|
|
880
|
+
}
|
|
881
|
+
function listSoftware(requestOptions = {}) {
|
|
882
|
+
return call("listIssuingSoftware", "IssuingSoftwareListResponse", (requestContext) => buildIssuingSoftwareListXml(requestContext, resolved.taxPayerId), parseIssuingSoftwareListResponse, requestOptions);
|
|
883
|
+
}
|
|
884
|
+
async function registerSoftware(name, requestOptions = {}) {
|
|
885
|
+
assertWritable("createIssuingSoftware");
|
|
886
|
+
const software = softwareFor(name);
|
|
887
|
+
if ((await listSoftware(requestOptions)).includes(software)) return { created: false };
|
|
888
|
+
await call("createIssuingSoftware", "CreateIssuingSoftwareResponse", (requestContext) => buildCreateIssuingSoftwareXml(requestContext, resolved.taxPayerId, software), () => void 0, requestOptions);
|
|
889
|
+
return { created: true };
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
environment: options.environment,
|
|
893
|
+
taxPayerId: resolved.taxPayerId,
|
|
894
|
+
canWrite: allowWrite,
|
|
895
|
+
authenticate,
|
|
896
|
+
listReports,
|
|
897
|
+
listAllReports,
|
|
898
|
+
getReport: async (id, requestOptions = {}) => {
|
|
899
|
+
const reportId = assertReportId(id);
|
|
900
|
+
return call("receiptDetail", "ReceiptDetailResponse", (requestContext) => buildReceiptIdXml("ReceiptDetailRequest", requestContext, reportId, resolved.taxPayerId), parseReceiptDetailResponse, requestOptions);
|
|
901
|
+
},
|
|
902
|
+
submitReport,
|
|
903
|
+
modifyReport,
|
|
904
|
+
invalidateReport,
|
|
905
|
+
registerSoftware,
|
|
906
|
+
listSoftware,
|
|
907
|
+
vatCategories: (requestOptions = {}) => call("vatCategories", "VatCategoryResponse", (requestContext) => buildContextOnlyXml("VatCategoryRequest", requestContext), parseVatCategoryResponse, requestOptions),
|
|
908
|
+
currencies: (requestOptions = {}) => call("currencies", "CurrencyResponse", (requestContext) => buildContextOnlyXml("CurrencyRequest", requestContext), parseCurrencyResponse, requestOptions)
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
//#endregion
|
|
912
|
+
//#region src/nav/paper.ts
|
|
913
|
+
function invalid(message) {
|
|
914
|
+
return new NavReceiptError(message, { category: "validation" });
|
|
915
|
+
}
|
|
916
|
+
function categoryOrder(name) {
|
|
917
|
+
const index = NAV_VAT_CATEGORIES.indexOf(name);
|
|
918
|
+
return index === -1 ? NAV_VAT_CATEGORIES.length : index;
|
|
919
|
+
}
|
|
920
|
+
function paperReceiptReport(day) {
|
|
921
|
+
if (!Array.isArray(day.entries) || day.entries.length === 0) throw invalid("A papír nyugtatömb napi jelentéséhez legalább egy tétel kell.");
|
|
922
|
+
const currency = (day.currency ?? "HUF").trim().toUpperCase();
|
|
923
|
+
const totals = /* @__PURE__ */ new Map();
|
|
924
|
+
const sales = /* @__PURE__ */ new Set();
|
|
925
|
+
const modifying = /* @__PURE__ */ new Set();
|
|
926
|
+
for (const entry of day.entries) {
|
|
927
|
+
const number = typeof entry.number === "string" ? entry.number.trim() : "";
|
|
928
|
+
if (number === "") throw invalid("Minden papír nyugtatételhez add meg a nyugta sorszámát.");
|
|
929
|
+
if (!Number.isFinite(entry.gross)) throw invalid(`A(z) ${number} nyugta bruttó összege érvénytelen: ${entry.gross}`);
|
|
930
|
+
if (entry.modifying !== true && entry.gross < 0) throw invalid(`A(z) ${number} eladási nyugta összege nem lehet negatív; a visszavételt jelöld modifying: true értékkel.`);
|
|
931
|
+
const row = totals.get(entry.vat) ?? {
|
|
932
|
+
sale: 0,
|
|
933
|
+
modifying: 0
|
|
934
|
+
};
|
|
935
|
+
if (entry.modifying === true) {
|
|
936
|
+
row.modifying += entry.gross;
|
|
937
|
+
modifying.add(number);
|
|
938
|
+
} else {
|
|
939
|
+
row.sale += entry.gross;
|
|
940
|
+
sales.add(number);
|
|
941
|
+
}
|
|
942
|
+
totals.set(entry.vat, row);
|
|
943
|
+
}
|
|
944
|
+
const overlap = [...sales].filter((number) => modifying.has(number));
|
|
945
|
+
if (overlap.length > 0) throw invalid(`Ugyanaz a sorszám nem lehet egyszerre eladási és módosító bizonylat: ${overlap.join(", ")}.`);
|
|
946
|
+
const vatCategories = [...totals.entries()].map(([vat, row]) => ({
|
|
947
|
+
vat,
|
|
948
|
+
saleDocument: roundMoney(row.sale, 2),
|
|
949
|
+
modifyingDocument: roundMoney(row.modifying, 2)
|
|
950
|
+
})).sort((a, b) => categoryOrder(a.vat) - categoryOrder(b.vat) || a.vat.localeCompare(b.vat));
|
|
951
|
+
const numbers = [...sales, ...modifying].sort(compareReceiptNumbers);
|
|
952
|
+
return {
|
|
953
|
+
applicableDate: toAgentDate(day.applicableDate),
|
|
954
|
+
serialNumber: numbers[0] ?? "",
|
|
955
|
+
currency,
|
|
956
|
+
exchangeRate: currency === "HUF" ? null : day.exchangeRate ?? null,
|
|
957
|
+
vatCategories,
|
|
958
|
+
total: roundMoney(vatCategories.reduce((sum, row) => sum + row.saleDocument + row.modifyingDocument, 0), 2),
|
|
959
|
+
numberOfSaleDocument: sales.size,
|
|
960
|
+
numberOfModifyingDocument: modifying.size
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
//#endregion
|
|
964
|
+
//#region src/nav/reconcile.ts
|
|
965
|
+
const AMOUNT_TOLERANCE = .005;
|
|
966
|
+
function keyOf(applicableDate, serialNumber) {
|
|
967
|
+
return JSON.stringify([applicableDate, serialNumber]);
|
|
968
|
+
}
|
|
969
|
+
function differences(local, remote) {
|
|
970
|
+
const found = [];
|
|
971
|
+
if (Math.abs(roundMoney(local.total, 2) - remote.totalAmount) > AMOUNT_TOLERANCE) found.push(`végösszeg: helyi ${local.total}, NAV ${remote.totalAmount}`);
|
|
972
|
+
if (local.numberOfSaleDocument !== remote.numberOfSaleDocument) found.push(`nyugták száma: helyi ${local.numberOfSaleDocument}, NAV ${remote.numberOfSaleDocument}`);
|
|
973
|
+
if (local.numberOfModifyingDocument !== remote.numberOfModifyingDocument) found.push(`módosító bizonylatok száma: helyi ${local.numberOfModifyingDocument}, NAV ${remote.numberOfModifyingDocument}`);
|
|
974
|
+
return found;
|
|
975
|
+
}
|
|
976
|
+
function reconcileNavReports(local, remote) {
|
|
977
|
+
const recorded = /* @__PURE__ */ new Map();
|
|
978
|
+
for (const item of remote) {
|
|
979
|
+
if (item.status !== "RECORDED") continue;
|
|
980
|
+
const key = keyOf(item.applicableDate, item.serialNumber);
|
|
981
|
+
recorded.set(key, [...recorded.get(key) ?? [], item]);
|
|
982
|
+
}
|
|
983
|
+
const matched = [];
|
|
984
|
+
const mismatched = [];
|
|
985
|
+
const missing = [];
|
|
986
|
+
for (const report of local) {
|
|
987
|
+
const key = keyOf(report.applicableDate, report.serialNumber);
|
|
988
|
+
const counterpart = recorded.get(key)?.shift();
|
|
989
|
+
if (!counterpart) {
|
|
990
|
+
missing.push(report);
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const found = differences(report, counterpart);
|
|
994
|
+
if (found.length === 0) matched.push({
|
|
995
|
+
local: report,
|
|
996
|
+
remote: counterpart
|
|
997
|
+
});
|
|
998
|
+
else mismatched.push({
|
|
999
|
+
local: report,
|
|
1000
|
+
remote: counterpart,
|
|
1001
|
+
differences: found
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
matched,
|
|
1006
|
+
mismatched,
|
|
1007
|
+
missing,
|
|
1008
|
+
unexpected: [...recorded.values()].flat()
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
//#endregion
|
|
1012
|
+
export { NAV_AUTH_NAMESPACE, NAV_DEFAULT_TIMEOUT_MS, NAV_MAX_LIST_PAGES, NAV_PAGING_NAMESPACE, NAV_RECEIPT_ERROR_CODES, NAV_RECEIPT_NAMESPACE, NAV_RECEIPT_URLS, NAV_REPORT_ID_PATTERN, NAV_SERIAL_NUMBER_PATTERN, NAV_SERVICE_NAMESPACE, NAV_SOFTWARE_NAME_PATTERN, NAV_VAT_CATEGORY_PATTERN, NavReceiptError, buildAuthTokenXml, buildContextOnlyXml, buildCreateIssuingSoftwareXml, buildCreateReceiptXml, buildIssuingSoftwareListXml, buildModifyReceiptXml, buildReceiptIdXml, buildReceiptListXml, createNavReceiptClient, isNavReceiptError, navLegacyRequestId, navPasswordHash, navRequestSignature, navSignatureTimestamp, navTaxpayerId, paperReceiptReport, parseNavResponse, reconcileNavReports, sha3_512, sha3_512Hex, validateNavReceiptData };
|