kassza 0.13.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 +152 -22
- package/agents/README.md +6 -1
- package/agents/api.md +64 -12
- package/agents/pitfalls.md +12 -1
- package/agents/recipes.md +67 -8
- package/agents/skills/kassza/SKILL.md +3 -0
- 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 +105 -14
- package/dist/cli.js +104 -13
- package/dist/{client-C-PdfL_P.cjs → client-CLFvJCVW.cjs} +358 -181
- package/dist/{client-CfaS1-Jx.js → client-Cvf-hTz_.js} +311 -152
- package/dist/{client-6MkeuFqD.d.ts → client-DeWWQ-RZ.d.ts} +99 -26
- package/dist/{client-B5FryKxJ.d.cts → client-SVZu25MB.d.cts} +99 -26
- package/dist/{context-DXNbTPwz.d.cts → context-CFNKzStE.d.cts} +82 -58
- package/dist/{context-BxQlIlug.d.ts → context-kb33TgzE.d.ts} +82 -58
- 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-DgMtKKcG.cjs → create-once-9yxR_Er_.cjs} +285 -33
- package/dist/create-once-Bh6fOWkW.d.cts +20 -0
- package/dist/{create-once-Cz621l8r.js → create-once-Ca2gMk1h.js} +250 -34
- package/dist/create-once-VKp0tVow.d.ts +20 -0
- package/dist/data-link/index.cjs +4 -4
- package/dist/data-link/index.d.cts +2 -52
- package/dist/data-link/index.d.ts +2 -52
- package/dist/data-link/index.js +4 -4
- package/dist/delegation/index.cjs +4 -4
- package/dist/delegation/index.d.cts +2 -2
- package/dist/delegation/index.d.ts +2 -2
- package/dist/delegation/index.js +4 -4
- package/dist/{errors-DLrb0g64.js → errors-DtN4g5Z9.js} +9 -1
- package/dist/{errors-DxeuTslP.cjs → errors-EE96_6D_.cjs} +20 -0
- package/dist/{get-Csm2n0rZ.cjs → get-BdMTseh4.cjs} +9 -183
- package/dist/{get-DT3JEgQ6.js → get-CZEShCcj.js} +4 -124
- 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 +10 -20
- package/dist/index.d.ts +10 -20
- 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-AGRohSm7.cjs → issue-Bin4tKD_.cjs} +416 -342
- package/dist/{issue-DtSLpVdJ.js → issue-DMA1Nbr1.js} +394 -266
- 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 +1 -1
- package/dist/mcp/index.d.cts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- 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-t7uy1n91.js → money-Cw457SD6.js} +95 -3
- package/dist/{money-BxytJW6f.cjs → money-K9j0wPit.cjs} +99 -1
- package/dist/nav/index.cjs +2 -2
- package/dist/nav/index.d.cts +1 -1
- package/dist/nav/index.d.ts +1 -1
- package/dist/nav/index.js +2 -2
- package/dist/{nav-BEwcX7-N.cjs → nav-BNbc8U0R.cjs} +1 -1
- package/dist/{nav-CK1WgYo7.js → nav-BcCerwWS.js} +1 -1
- 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-C608ehqM.js → parse-Cr7yQ3_z.js} +2 -2
- package/dist/{parse-BJj-aqKy.cjs → parse-DOA2XQ3e.cjs} +2 -2
- 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 +16 -2
- package/dist/payments/revolut.d.cts +2 -2
- package/dist/payments/revolut.d.ts +2 -2
- package/dist/payments/revolut.js +16 -2
- package/dist/payments/simplepay.cjs +36 -4
- package/dist/payments/simplepay.d.cts +2 -2
- package/dist/payments/simplepay.d.ts +2 -2
- package/dist/payments/simplepay.js +36 -4
- package/dist/payments/stripe.cjs +19 -2
- package/dist/payments/stripe.d.cts +2 -2
- package/dist/payments/stripe.d.ts +2 -2
- package/dist/payments/stripe.js +19 -2
- 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 +1 -1
- package/dist/reports/index.d.cts +1 -1
- package/dist/reports/index.d.ts +1 -1
- package/dist/reports/index.js +1 -1
- package/dist/{serialize-DSE_o9RK.js → serialize-B2eFh3D-.js} +5 -1
- package/dist/{serialize-BXOww1Uq.cjs → serialize-CquIsxA-.cjs} +5 -1
- package/dist/{server-Cd8HB661.js → server-BrJUbXs7.js} +8 -7
- package/dist/{server-C6PoNkdD.cjs → server-Dw9j8Mnd.cjs} +17 -16
- 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 +28 -16
- package/dist/testing/index.d.cts +6 -3
- package/dist/testing/index.d.ts +6 -3
- package/dist/testing/index.js +26 -14
- package/dist/{types-BVeR7KyU.d.ts → types-B1A8mUIE.d.cts} +13 -1
- package/dist/{types-BVeR7KyU.d.cts → types-ByAEvGsZ.d.ts} +13 -1
- 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-CrEGohbg.js → webhook-CQVShAfI.js} +44 -1
- package/dist/{webhook-81giRMdQ.cjs → webhook-Ckj-_hPD.cjs} +67 -0
- package/dist/webhook-CmdhGiH5.d.cts +11 -0
- package/dist/webhook-D6lEejob.d.ts +11 -0
- package/llms.txt +4 -3
- package/package.json +92 -4
- package/dist/session-Dm_45x8K.d.cts +0 -11
- package/dist/session-Dm_45x8K.d.ts +0 -11
- package/dist/webhook-CpCMHhn2.d.ts +0 -6
- package/dist/webhook-CpMJuP6c.d.cts +0 -6
|
@@ -1,29 +1,53 @@
|
|
|
1
|
-
const require_errors = require("./errors-
|
|
2
|
-
const
|
|
3
|
-
const require_parse = require("./parse-BJj-aqKy.cjs");
|
|
1
|
+
const require_errors = require("./errors-EE96_6D_.cjs");
|
|
2
|
+
const require_once = require("./once-BN_3B5OF.cjs");
|
|
4
3
|
const require_rounding = require("./rounding-TxoWy2ww.cjs");
|
|
5
4
|
const require_vat = require("./vat-g35srbNG.cjs");
|
|
6
|
-
const require_money = require("./money-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (
|
|
5
|
+
const require_money = require("./money-K9j0wPit.cjs");
|
|
6
|
+
const require_create = require("./create-CvUvLUaT.cjs");
|
|
7
|
+
//#region src/core/once-guard.ts
|
|
8
|
+
const ONCE_LOCK_KEY_PREFIX = "szamlazz:once:";
|
|
9
|
+
const DEFAULT_LOCK_TTL_SECONDS = 600;
|
|
10
|
+
const DEFAULT_LOCK_WAIT_MS = 1e4;
|
|
11
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
12
|
+
function configurationError(message) {
|
|
13
|
+
return new require_errors.SzamlazzError(message, { category: "configuration" });
|
|
14
|
+
}
|
|
15
|
+
function resolveTtl(value) {
|
|
16
|
+
if (value === void 0) return 600;
|
|
17
|
+
if (!Number.isFinite(value) || value < 1) throw configurationError(`A lockTtlSeconds értéke legalább 1 másodperc legyen, kapott: ${String(value)}`);
|
|
18
|
+
return Math.ceil(value);
|
|
19
|
+
}
|
|
20
|
+
function resolveWait(value) {
|
|
21
|
+
if (value === void 0) return DEFAULT_LOCK_WAIT_MS;
|
|
22
|
+
if (!Number.isFinite(value) || value < 0) throw configurationError(`A lockWaitMs értéke nemnegatív szám legyen, kapott: ${String(value)}`);
|
|
23
23
|
return value;
|
|
24
24
|
}
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
25
|
+
function resolveFailureMode(value) {
|
|
26
|
+
if (value === void 0 || value === "throw" || value === "proceed") return value ?? "throw";
|
|
27
|
+
throw configurationError(`A lockFailure értéke 'throw' vagy 'proceed' lehet, kapott: ${String(value)}`);
|
|
28
|
+
}
|
|
29
|
+
const encoder = new TextEncoder();
|
|
30
|
+
async function onceLockKey(scope, key) {
|
|
31
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(`${scope}\n${key}`));
|
|
32
|
+
const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"));
|
|
33
|
+
return `${ONCE_LOCK_KEY_PREFIX}${hex.join("")}`;
|
|
34
|
+
}
|
|
35
|
+
function inProgressError(reference, waitMs) {
|
|
36
|
+
return new require_errors.SzamlazzError(`Egy másik folyamat éppen kiállítja ezt a bizonylatot (${reference}), ${Math.ceil(waitMs / 1e3)} másodperc várakozás után sem végzett.`, {
|
|
37
|
+
category: "in_progress",
|
|
38
|
+
details: { reference },
|
|
39
|
+
hint: "Ne állítsd ki kézzel. Néhány másodperc múlva hívd újra ugyanígy a createOnce-t: a zár felszabadulása után visszakeresi a bizonylatot. Webhookban adj vissza 5xx választ, hogy a szolgáltató később újraküldje."
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function lockUnavailableError(reference, error) {
|
|
43
|
+
return new require_errors.SzamlazzError(`A createOnce zár tárolója nem érhető el, ezért a kassza nem állította ki a bizonylatot (${reference}).`, {
|
|
44
|
+
category: "store_unavailable",
|
|
45
|
+
details: { reference },
|
|
46
|
+
cause: error,
|
|
47
|
+
hint: "Állítsd helyre a tárolót (például a Redist), és hívd újra. Ha zár nélkül is ki akarod állítani, add meg a lockFailure: 'proceed' beállítást."
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function sleep(ms, signal) {
|
|
27
51
|
return new Promise((resolve, reject) => {
|
|
28
52
|
if (signal?.aborted) {
|
|
29
53
|
reject(signal.reason);
|
|
@@ -40,32 +64,85 @@ function wait(ms, signal) {
|
|
|
40
64
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
41
65
|
});
|
|
42
66
|
}
|
|
43
|
-
async function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
async function releaseLock(store, lockKey, token, warn) {
|
|
68
|
+
try {
|
|
69
|
+
if (store.deleteIfEquals) {
|
|
70
|
+
await store.deleteIfEquals(lockKey, token);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (await store.get(lockKey) === token) await store.delete(lockKey);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
warn?.({
|
|
76
|
+
kind: "lock",
|
|
77
|
+
message: "A createOnce zárat nem sikerült felszabadítani. A zár a lejárati ideje végén magától megszűnik, addig az azonos bizonylatra érkező hívások in_progress hibát kapnak.",
|
|
78
|
+
error,
|
|
79
|
+
operation: "release"
|
|
49
80
|
});
|
|
50
|
-
if (found) return found;
|
|
51
81
|
}
|
|
52
|
-
return null;
|
|
53
82
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
async function runLocked(guard, key, options, recheck, run) {
|
|
84
|
+
const store = options.lock === false ? void 0 : options.lock ?? guard.lock;
|
|
85
|
+
if (!store) return run(recheck);
|
|
86
|
+
const setIfAbsent = store.setIfAbsent?.bind(store);
|
|
87
|
+
if (!setIfAbsent) throw configurationError("A createOnce zárához olyan tároló kell, amely támogatja a setIfAbsent műveletet (például a kassza/stores Redis adapterei vagy a Durable Object tároló). A Cloudflare KV erre nem alkalmas.");
|
|
88
|
+
const ttlSeconds = resolveTtl(options.lockTtlSeconds);
|
|
89
|
+
const waitMs = resolveWait(options.lockWaitMs);
|
|
90
|
+
const failureMode = resolveFailureMode(options.lockFailure);
|
|
91
|
+
const lockKey = await onceLockKey(guard.scope, key);
|
|
92
|
+
const token = crypto.randomUUID();
|
|
93
|
+
const deadline = Date.now() + waitMs;
|
|
94
|
+
let waited = false;
|
|
95
|
+
for (;;) {
|
|
96
|
+
let acquired;
|
|
97
|
+
try {
|
|
98
|
+
acquired = await setIfAbsent(lockKey, token, ttlSeconds);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (failureMode === "throw") throw lockUnavailableError(key, error);
|
|
101
|
+
guard.warn?.({
|
|
102
|
+
kind: "lock",
|
|
103
|
+
message: "A createOnce zár tárolója nem érhető el, a kassza zár nélkül folytatja (lockFailure: proceed). A folyamatok közötti dupla kiállítás elleni védelem most nem él.",
|
|
104
|
+
error,
|
|
105
|
+
operation: "setIfAbsent"
|
|
106
|
+
});
|
|
107
|
+
return run(recheck || waited);
|
|
108
|
+
}
|
|
109
|
+
if (acquired) break;
|
|
110
|
+
const remaining = deadline - Date.now();
|
|
111
|
+
if (remaining <= 0) throw inProgressError(key, waitMs);
|
|
112
|
+
waited = true;
|
|
113
|
+
await sleep(Math.min(500, remaining), options.signal);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
return await run(recheck || waited);
|
|
117
|
+
} finally {
|
|
118
|
+
await releaseLock(store, lockKey, token, guard.warn);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isSharedFailure(error) {
|
|
122
|
+
return error instanceof require_errors.SzamlazzError && !require_once.isUncertainOutcome(error);
|
|
123
|
+
}
|
|
124
|
+
async function guardOnce(guard, key, options, run, joined) {
|
|
125
|
+
if (!guard) return run(false);
|
|
126
|
+
const id = `${guard.scope}\n${key}`;
|
|
127
|
+
let recheck = false;
|
|
128
|
+
for (;;) {
|
|
129
|
+
const pending = inflight.get(id);
|
|
130
|
+
if (!pending) break;
|
|
131
|
+
try {
|
|
132
|
+
return joined(await pending);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (isSharedFailure(error)) throw error;
|
|
135
|
+
recheck = true;
|
|
136
|
+
}
|
|
137
|
+
options.signal?.throwIfAborted();
|
|
138
|
+
}
|
|
139
|
+
const leader = runLocked(guard, key, options, recheck, run);
|
|
140
|
+
inflight.set(id, leader);
|
|
141
|
+
try {
|
|
142
|
+
return await leader;
|
|
143
|
+
} finally {
|
|
144
|
+
if (inflight.get(id) === leader) inflight.delete(id);
|
|
145
|
+
}
|
|
69
146
|
}
|
|
70
147
|
//#endregion
|
|
71
148
|
//#region src/invoices/create-once.ts
|
|
@@ -105,47 +182,59 @@ function existingResult(details, externalId, created) {
|
|
|
105
182
|
details
|
|
106
183
|
};
|
|
107
184
|
}
|
|
108
|
-
|
|
185
|
+
function invoiceOnceGuardKey(type, orderNumber, externalId, options) {
|
|
186
|
+
if (options.matchOrderNumber === false) return `invoice:externalId:${externalId}`;
|
|
187
|
+
return `invoice:order:${type}:${orderNumber}`;
|
|
188
|
+
}
|
|
189
|
+
async function createInvoiceOnce(api, input, options = {}, guard) {
|
|
109
190
|
const orderNumber = requireOrderNumber(input);
|
|
110
191
|
const type = input.type ?? "invoice";
|
|
111
192
|
const externalId = input.externalId?.trim() || invoiceOnceExternalId(type, orderNumber);
|
|
112
|
-
const delayMs = resolveRecoveryDelay(options.recoveryDelayMs);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
orderNumber,
|
|
121
|
-
externalId
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
193
|
+
const delayMs = require_once.resolveRecoveryDelay(options.recoveryDelayMs);
|
|
194
|
+
const normalized = {
|
|
195
|
+
...input,
|
|
196
|
+
orderNumber,
|
|
197
|
+
externalId
|
|
198
|
+
};
|
|
199
|
+
const run = async (recheck) => {
|
|
200
|
+
if (recheck || options.lookupFirst !== false) {
|
|
201
|
+
const existing = await lookup(api, externalId, orderNumber, type, options);
|
|
202
|
+
if (existing) return existingResult(existing, externalId, false);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const invoice = await api.create(normalized, options);
|
|
206
|
+
return {
|
|
207
|
+
number: invoice.number,
|
|
208
|
+
created: true,
|
|
209
|
+
externalId,
|
|
210
|
+
invoice
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (!require_once.isUncertainOutcome(error)) throw error;
|
|
214
|
+
const state = {};
|
|
215
|
+
const recovered = await require_once.recoverAfterFailure(() => lookup(api, externalId, orderNumber, type, options), delayMs, options.signal, state);
|
|
216
|
+
if (recovered) return existingResult(recovered, externalId, error.category !== "duplicate");
|
|
217
|
+
if (error.category === "duplicate" && !state.lookupError) throw error;
|
|
218
|
+
throw require_once.unknownOutcomeError(error, `${orderNumber} rendelés`, { lookupError: state.lookupError });
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
return guardOnce(guard, invoiceOnceGuardKey(type, orderNumber, externalId, options), options, run, (result) => ({
|
|
222
|
+
...result,
|
|
223
|
+
created: false
|
|
224
|
+
}));
|
|
136
225
|
}
|
|
137
226
|
//#endregion
|
|
138
227
|
//#region src/documents/choose.ts
|
|
139
228
|
const RECEIPT_MAX_GROSS_HUF = 9e5;
|
|
140
229
|
const forintFormatter = new Intl.NumberFormat("hu-HU", { maximumFractionDigits: 2 });
|
|
141
|
-
function validation$
|
|
230
|
+
function validation$3(message) {
|
|
142
231
|
return new require_errors.SzamlazzError(message, { category: "validation" });
|
|
143
232
|
}
|
|
144
233
|
function grossInHuf(input) {
|
|
145
|
-
if (!Number.isFinite(input.grossTotal) || input.grossTotal < 0) throw validation$
|
|
234
|
+
if (!Number.isFinite(input.grossTotal) || input.grossTotal < 0) throw validation$3(`A bruttó végösszeg (grossTotal) nemnegatív szám legyen, kapott: ${input.grossTotal}`);
|
|
146
235
|
if (require_vat.isHuf(input.currency)) return input.grossTotal;
|
|
147
236
|
const rate = input.exchangeRate;
|
|
148
|
-
if (rate === void 0 || !Number.isFinite(rate) || rate <= 0) throw validation$
|
|
237
|
+
if (rate === void 0 || !Number.isFinite(rate) || rate <= 0) throw validation$3(`Devizás (${input.currency}) összegnél add meg az árfolyamot (exchangeRate), mert a 900 000 Ft-os határt forintban kell nézni.`);
|
|
149
238
|
return input.grossTotal * rate;
|
|
150
239
|
}
|
|
151
240
|
function isBusinessBuyer(buyer) {
|
|
@@ -176,23 +265,9 @@ function chooseDocument(input) {
|
|
|
176
265
|
};
|
|
177
266
|
}
|
|
178
267
|
//#endregion
|
|
179
|
-
//#region src/receipts/types.ts
|
|
180
|
-
const RECEIPT_ONLY_VAT_CODES = [
|
|
181
|
-
"ÁKK",
|
|
182
|
-
"MAA",
|
|
183
|
-
"EU",
|
|
184
|
-
"EUK"
|
|
185
|
-
];
|
|
186
|
-
const RECEIPT_PDF_TEMPLATES = [
|
|
187
|
-
"A",
|
|
188
|
-
"N",
|
|
189
|
-
"J",
|
|
190
|
-
"L"
|
|
191
|
-
];
|
|
192
|
-
//#endregion
|
|
193
268
|
//#region src/receipts/convert.ts
|
|
194
|
-
const receiptOnlyCodes = new Set(RECEIPT_ONLY_VAT_CODES);
|
|
195
|
-
function validation$
|
|
269
|
+
const receiptOnlyCodes = new Set(require_create.RECEIPT_ONLY_VAT_CODES);
|
|
270
|
+
function validation$2(message, hint) {
|
|
196
271
|
return new require_errors.SzamlazzError(message, {
|
|
197
272
|
category: "validation",
|
|
198
273
|
hint
|
|
@@ -204,14 +279,14 @@ function conversionExternalId(receiptNumber) {
|
|
|
204
279
|
function invoiceVat(item, mapping) {
|
|
205
280
|
if (typeof item.vat === "string" && receiptOnlyCodes.has(item.vat)) {
|
|
206
281
|
const mapped = mapping?.[item.vat];
|
|
207
|
-
if (mapped === void 0) throw validation$
|
|
282
|
+
if (mapped === void 0) throw validation$2(`A(z) „${item.name}” tétel áfakódja (${item.vat}) csak nyugtán használható, számlán nincs automatikus megfelelője.`, "Add meg a számlán használandó áfakulcsot a vatMapping opcióban, a könyvelőddel egyeztetve.");
|
|
208
283
|
return mapped;
|
|
209
284
|
}
|
|
210
285
|
return item.vat;
|
|
211
286
|
}
|
|
212
287
|
function receiptItemsToInvoiceItems(receipt, mapping) {
|
|
213
288
|
return receipt.items.map((item) => {
|
|
214
|
-
if (!(item.quantity > 0)) throw validation$
|
|
289
|
+
if (!(item.quantity > 0)) throw validation$2(`A(z) „${item.name}” tétel mennyisége nem pozitív, a nyugta nem alakítható számlává.`);
|
|
215
290
|
return {
|
|
216
291
|
name: item.name,
|
|
217
292
|
identifier: item.identifier,
|
|
@@ -229,12 +304,12 @@ function conversionComment(receipt, reversal, extra) {
|
|
|
229
304
|
}
|
|
230
305
|
async function convertReceiptToInvoice(api, input, options = {}) {
|
|
231
306
|
const receiptNumber = input.receiptNumber?.trim();
|
|
232
|
-
if (!receiptNumber) throw validation$
|
|
307
|
+
if (!receiptNumber) throw validation$2("Add meg az átalakítandó nyugta számát (receiptNumber).");
|
|
233
308
|
const receipt = await api.receipts.get({
|
|
234
309
|
receiptNumber,
|
|
235
310
|
downloadPdf: false
|
|
236
311
|
}, options);
|
|
237
|
-
if (receipt.type !== "receipt") throw validation$
|
|
312
|
+
if (receipt.type !== "receipt") throw validation$2(`A(z) ${receiptNumber} sztornó nyugta, számlává nem alakítható.`);
|
|
238
313
|
const items = receiptItemsToInvoiceItems(receipt, input.vatMapping);
|
|
239
314
|
const externalId = conversionExternalId(receipt.number);
|
|
240
315
|
const orderNumber = input.orderNumber?.trim() || receipt.orderNumber || `CONV-${receipt.number}`;
|
|
@@ -250,7 +325,7 @@ async function convertReceiptToInvoice(api, input, options = {}) {
|
|
|
250
325
|
};
|
|
251
326
|
let reversal;
|
|
252
327
|
if (receipt.isReversed) {
|
|
253
|
-
if (input.allowAlreadyReversed !== true) throw validation$
|
|
328
|
+
if (input.allowAlreadyReversed !== true) throw validation$2(`A(z) ${receipt.number} nyugta már sztornózva van, ezért nem állítok ki rá számlát.`, "Ha a sztornót egy korábbi, félbeszakadt átalakítás végezte, hívd újra allowAlreadyReversed: true beállítással.");
|
|
254
329
|
} else reversal = await api.receipts.reverse({
|
|
255
330
|
receiptNumber: receipt.number,
|
|
256
331
|
callId: `CONV-${receipt.number}`,
|
|
@@ -288,187 +363,228 @@ async function convertReceiptToInvoice(api, input, options = {}) {
|
|
|
288
363
|
};
|
|
289
364
|
}
|
|
290
365
|
//#endregion
|
|
291
|
-
//#region src/
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
366
|
+
//#region src/payments/partial-refund.ts
|
|
367
|
+
const CORRECTION_SUFFIX = "/R-";
|
|
368
|
+
const MAX_PLAIN_REFUND_KEY = 24;
|
|
369
|
+
const AMOUNT_TOLERANCE$1 = .005;
|
|
370
|
+
function validation$1(message, hint) {
|
|
371
|
+
return new require_errors.SzamlazzError(message, {
|
|
372
|
+
category: "validation",
|
|
373
|
+
hint
|
|
374
|
+
});
|
|
295
375
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
376
|
+
function skipped$1(orderNumber, reason) {
|
|
377
|
+
return {
|
|
378
|
+
kind: "skipped",
|
|
379
|
+
orderNumber,
|
|
380
|
+
reason
|
|
381
|
+
};
|
|
300
382
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
383
|
+
async function refundKey(id) {
|
|
384
|
+
if (id.length <= MAX_PLAIN_REFUND_KEY && /^[A-Za-z0-9_.:-]+$/.test(id)) return id;
|
|
385
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(id));
|
|
386
|
+
return Array.from(new Uint8Array(digest).slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
304
387
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (template === void 0) return void 0;
|
|
308
|
-
if (!templates.has(template)) throw receiptValidationError(`Ismeretlen nyugta PDF sablon: ${String(template)}. Lehetséges értékek: A (normál A4), N (80 mm), J (jegy), L (jegy logóval).`);
|
|
309
|
-
return template;
|
|
388
|
+
async function correctionExternalId(orderNumber, refundId) {
|
|
389
|
+
return `${orderNumber}${CORRECTION_SUFFIX}${await refundKey(refundId)}`;
|
|
310
390
|
}
|
|
311
|
-
function
|
|
312
|
-
return
|
|
391
|
+
function decimalsFor(currency) {
|
|
392
|
+
return require_vat.isHuf(currency) ? 0 : 2;
|
|
313
393
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const DEFAULT_CURRENCY$1 = "HUF";
|
|
318
|
-
const DEFAULT_UNIT = "db";
|
|
319
|
-
const PREFIX_PATTERN = /^[A-Z0-9]+$/;
|
|
320
|
-
const AMOUNT_TOLERANCE$1 = 2;
|
|
321
|
-
const MONEY_DECIMALS = 2;
|
|
322
|
-
const receiptOnlyVatCodes = new Set(RECEIPT_ONLY_VAT_CODES);
|
|
323
|
-
function calculateItem(item, currency, index) {
|
|
324
|
-
const vat = typeof item.vat === "string" && receiptOnlyVatCodes.has(item.vat) ? 0 : item.vat;
|
|
325
|
-
try {
|
|
326
|
-
return require_money.calculateReceiptItem({
|
|
327
|
-
...item,
|
|
328
|
-
vat
|
|
329
|
-
}, currency);
|
|
330
|
-
} catch (error) {
|
|
331
|
-
if (!(error instanceof require_errors.SzamlazzError)) throw error;
|
|
332
|
-
throw receiptValidationError(`${index + 1}. tétel (${item.name}): ${error.message}`);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
function assertHufAmounts(amounts, label, vat) {
|
|
336
|
-
if (!Number.isInteger(amounts.grossAmount)) throw receiptValidationError(`${label}: forintos nyugtán a bruttó összegnek egész számnak kell lennie (363).`);
|
|
337
|
-
if (require_rounding.decimalPlaces(amounts.netAmount) > MONEY_DECIMALS) throw receiptValidationError(`${label}: forintos nyugtán a nettó összeg legfeljebb 2 tizedesjegyet tartalmazhat (364).`);
|
|
338
|
-
if (require_rounding.decimalPlaces(amounts.vatAmount) > MONEY_DECIMALS) throw receiptValidationError(`${label}: forintos nyugtán az áfa összeg legfeljebb 2 tizedesjegyet tartalmazhat (365).`);
|
|
339
|
-
if (require_rounding.roundMoney(amounts.netAmount + amounts.vatAmount, MONEY_DECIMALS) !== amounts.grossAmount) throw receiptValidationError(`${label}: a nettó és az áfa összegének pontosan ki kell adnia a bruttót (261).`);
|
|
340
|
-
if (Math.abs(amounts.netUnitPrice * amounts.quantity - amounts.netAmount) > AMOUNT_TOLERANCE$1) throw receiptValidationError(`${label}: a nettó egységár és a mennyiség szorzata nem egyezik a nettó összeggel (259).`);
|
|
341
|
-
const expectedVat = amounts.netAmount * require_vat.vatPercentage(vat) / 100;
|
|
342
|
-
if (Math.abs(expectedVat - amounts.vatAmount) > AMOUNT_TOLERANCE$1) throw receiptValidationError(`${label}: az áfa összeg nem egyezik a nettó összeg és az áfakulcs szorzatával (260).`);
|
|
343
|
-
}
|
|
344
|
-
function calculateReceiptItems(items, currency) {
|
|
345
|
-
if (items.length === 0) throw receiptValidationError("A nyugtán legalább egy tételnek szerepelnie kell.");
|
|
346
|
-
return items.map((input, index) => {
|
|
347
|
-
requireText(input.name, `A(z) ${index + 1}. tétel megnevezése (name) kötelező.`);
|
|
348
|
-
const amounts = calculateItem(input, currency, index);
|
|
349
|
-
if (require_vat.isHuf(currency)) assertHufAmounts(amounts, `${index + 1}. tétel (${input.name})`, input.vat);
|
|
350
|
-
if (input.dataDeletionCode !== void 0) {
|
|
351
|
-
if (!Number.isInteger(input.dataDeletionCode) || input.dataDeletionCode < 0) throw receiptValidationError(`A(z) ${index + 1}. tétel adattörlő kódja (dataDeletionCode) nemnegatív egész szám legyen.`);
|
|
352
|
-
}
|
|
353
|
-
return {
|
|
354
|
-
input,
|
|
355
|
-
amounts,
|
|
356
|
-
vat: input.vat
|
|
357
|
-
};
|
|
358
|
-
});
|
|
394
|
+
function vatOfItem(item) {
|
|
395
|
+
if (item.vatCode && require_vat.isVatRate(item.vatCode)) return item.vatCode;
|
|
396
|
+
return require_vat.isVatRate(item.vat) ? item.vat : void 0;
|
|
359
397
|
}
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const prefix = requireText(value, "Add meg a nyugta előtagját (prefix) a hívásban vagy a receipts alapbeállításaiban.");
|
|
399
|
-
if (!PREFIX_PATTERN.test(prefix)) throw receiptValidationError(`A nyugta előtag csak nagybetűt és számot tartalmazhat, kapott: ${prefix} (337).`);
|
|
400
|
-
return prefix;
|
|
401
|
-
}
|
|
402
|
-
function resolveExchange(currency, rate, bank) {
|
|
403
|
-
if (require_vat.isHuf(currency)) return {
|
|
404
|
-
rate: void 0,
|
|
405
|
-
bank: void 0
|
|
398
|
+
function refundableItems(details) {
|
|
399
|
+
const items = [];
|
|
400
|
+
for (const item of details.items) {
|
|
401
|
+
const vat = vatOfItem(item);
|
|
402
|
+
if (vat === void 0 || item.grossAmount < 0) return void 0;
|
|
403
|
+
items.push({
|
|
404
|
+
name: item.name,
|
|
405
|
+
vat,
|
|
406
|
+
grossAmount: item.grossAmount,
|
|
407
|
+
...item.unit === void 0 ? {} : { unit: item.unit },
|
|
408
|
+
...item.identifier === void 0 ? {} : { identifier: item.identifier }
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return items.length > 0 ? items : void 0;
|
|
412
|
+
}
|
|
413
|
+
function receiptItems(receipt) {
|
|
414
|
+
return receipt.items.map((item) => ({
|
|
415
|
+
name: item.name,
|
|
416
|
+
vat: item.vat,
|
|
417
|
+
grossAmount: item.grossAmount,
|
|
418
|
+
...item.unit === void 0 ? {} : { unit: item.unit }
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
function buyerOf(details) {
|
|
422
|
+
const { buyer } = details;
|
|
423
|
+
const address = buyer.address;
|
|
424
|
+
if (!buyer.name || !address?.zip || !address.city || !address.address) return void 0;
|
|
425
|
+
return {
|
|
426
|
+
name: buyer.name,
|
|
427
|
+
zip: address.zip,
|
|
428
|
+
city: address.city,
|
|
429
|
+
address: address.address,
|
|
430
|
+
country: address.country,
|
|
431
|
+
email: buyer.email,
|
|
432
|
+
taxNumber: buyer.taxNumber,
|
|
433
|
+
groupTaxNumber: buyer.groupTaxNumber,
|
|
434
|
+
euTaxNumber: buyer.euTaxNumber,
|
|
435
|
+
identifier: buyer.identifier
|
|
406
436
|
};
|
|
407
|
-
|
|
437
|
+
}
|
|
438
|
+
function sameCurrency(a, b) {
|
|
439
|
+
if (require_vat.isHuf(a) && require_vat.isHuf(b)) return true;
|
|
440
|
+
return a?.trim().toUpperCase() === b?.trim().toUpperCase();
|
|
441
|
+
}
|
|
442
|
+
function assertRefunds(refunds, currency, documentNumber) {
|
|
443
|
+
for (const refund of refunds) if (!sameCurrency(refund.amount.currency, currency)) throw validation$1(`A(z) ${refund.id} visszatérítés pénzneme (${refund.amount.currency}) eltér a(z) ${documentNumber} bizonylatétól (${currency ?? "HUF"}).`);
|
|
444
|
+
}
|
|
445
|
+
function proposal(orderNumber, document, documentNumber, items, refunds, currency, reason) {
|
|
408
446
|
return {
|
|
409
|
-
|
|
410
|
-
|
|
447
|
+
kind: "refund-proposal",
|
|
448
|
+
orderNumber,
|
|
449
|
+
document,
|
|
450
|
+
documentNumber,
|
|
451
|
+
refunds: refunds.map((refund) => ({
|
|
452
|
+
refundId: refund.id,
|
|
453
|
+
grossTotal: refund.amount.value,
|
|
454
|
+
items: require_money.allocateRefund(items, refund.amount.value, {
|
|
455
|
+
decimals: decimalsFor(currency),
|
|
456
|
+
refundedBefore: refund.refundedBefore
|
|
457
|
+
})
|
|
458
|
+
})),
|
|
459
|
+
reason
|
|
411
460
|
};
|
|
412
461
|
}
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
462
|
+
function allocationItems(allocations) {
|
|
463
|
+
return allocations.map((allocation) => ({
|
|
464
|
+
name: allocation.name,
|
|
465
|
+
quantity: -1,
|
|
466
|
+
unit: allocation.unit,
|
|
467
|
+
identifier: allocation.identifier,
|
|
468
|
+
grossUnitPrice: allocation.grossAmount,
|
|
469
|
+
vat: allocation.vat
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
function negated(items) {
|
|
473
|
+
return items.map((item) => ({
|
|
474
|
+
...item,
|
|
475
|
+
quantity: -Math.abs(item.quantity ?? 1)
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
const REFUND_ITEMS_HINT = "A refundItems a visszatérített tételeket pozitív bruttó egységárral (grossUnitPrice) adja vissza; a kassza fordítja negatívba.";
|
|
479
|
+
function assertRefundItems(items, refundId) {
|
|
480
|
+
for (const item of items) {
|
|
481
|
+
const price = item.grossUnitPrice;
|
|
482
|
+
const quantity = item.quantity ?? 1;
|
|
483
|
+
if (typeof price !== "number" || !Number.isFinite(price) || price <= 0) throw validation$1(`A refundItems „${item.name}” tételének nincs pozitív bruttó egységára (grossUnitPrice) a(z) ${refundId} visszatérítésnél.`, REFUND_ITEMS_HINT);
|
|
484
|
+
if (!Number.isFinite(quantity) || quantity === 0) throw validation$1(`A refundItems „${item.name}” tételének mennyisége érvénytelen (${quantity}) a(z) ${refundId} visszatérítésnél.`, REFUND_ITEMS_HINT);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function grossOf(items) {
|
|
488
|
+
return require_rounding.roundMoney(items.reduce((sum, item) => sum + Math.abs(item.quantity ?? 1) * (item.grossUnitPrice ?? 0), 0), 2);
|
|
489
|
+
}
|
|
490
|
+
async function correctionItems(refund, details, items, options) {
|
|
491
|
+
const custom = await options.refundItems?.({
|
|
492
|
+
refund,
|
|
493
|
+
invoiceNumber: details.header.number,
|
|
494
|
+
items
|
|
444
495
|
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
496
|
+
if (custom && custom.length > 0) {
|
|
497
|
+
assertRefundItems(custom, refund.id);
|
|
498
|
+
const gross = grossOf(custom);
|
|
499
|
+
if (!(Math.abs(gross - refund.amount.value) <= AMOUNT_TOLERANCE$1)) throw validation$1(`A refundItems tételeinek bruttó összege (${gross}) eltér a(z) ${refund.id} visszatérítés összegétől (${refund.amount.value}).`, REFUND_ITEMS_HINT);
|
|
500
|
+
return negated(custom);
|
|
501
|
+
}
|
|
502
|
+
return allocationItems(require_money.allocateRefund(items, refund.amount.value, {
|
|
503
|
+
decimals: decimalsFor(details.header.currency),
|
|
504
|
+
refundedBefore: refund.refundedBefore
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
async function issueCorrection(api, payment, orderNumber, details, refund, items, options) {
|
|
508
|
+
const buyer = options.buyer ?? buyerOf(details);
|
|
509
|
+
if (!buyer) throw validation$1(`A(z) ${details.header.number} számla vevőjének címe nem olvasható ki, így a helyesbítő számla nem állítható ki.`, "Add meg a vevőt a buyer opcióban.");
|
|
510
|
+
const externalId = await correctionExternalId(orderNumber, refund.id);
|
|
511
|
+
const header = details.header;
|
|
512
|
+
const foreign = !require_vat.isHuf(header.currency);
|
|
513
|
+
const extra = options.invoice ?? {};
|
|
514
|
+
const invoice = await api.invoices.createOnce({
|
|
515
|
+
...extra,
|
|
516
|
+
type: "corrective",
|
|
517
|
+
correctedInvoiceNumber: header.number,
|
|
518
|
+
paid: true,
|
|
519
|
+
paymentMethod: extra.paymentMethod ?? header.paymentMethod ?? payment.method,
|
|
520
|
+
currency: header.currency,
|
|
521
|
+
exchangeRate: foreign ? header.exchangeRate : void 0,
|
|
522
|
+
exchangeBank: foreign ? header.exchangeBank : void 0,
|
|
523
|
+
orderNumber,
|
|
524
|
+
externalId,
|
|
525
|
+
buyer,
|
|
526
|
+
items,
|
|
527
|
+
comment: extra.comment ?? `Részleges visszatérítés (${payment.provider}: ${refund.id}).`
|
|
528
|
+
}, {
|
|
529
|
+
signal: options.signal,
|
|
530
|
+
recoveryDelayMs: options.recoveryDelayMs,
|
|
531
|
+
matchOrderNumber: false
|
|
458
532
|
});
|
|
533
|
+
return {
|
|
534
|
+
refundId: refund.id,
|
|
535
|
+
externalId,
|
|
536
|
+
number: invoice.number,
|
|
537
|
+
created: invoice.created,
|
|
538
|
+
grossTotal: -refund.amount.value,
|
|
539
|
+
invoice
|
|
540
|
+
};
|
|
459
541
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
542
|
+
function mixedVatReason(rates) {
|
|
543
|
+
return `Az eredeti számlán több áfakulcs van (${[...rates].join(", ")}), ezért a kassza nem tudja, melyik tétel került visszatérítésre. Add meg a refundItems opciót, vagy ha arányos szétosztás a helyes (például százalékos kedvezmény), a partialRefund: 'proportional' beállítást.`;
|
|
544
|
+
}
|
|
545
|
+
async function correctInvoice(api, payment, orderNumber, details, refunds, options) {
|
|
546
|
+
const header = details.header;
|
|
547
|
+
if (header.type === "reversal" || header.reversed === true) return skipped$1(orderNumber, `A(z) ${header.number} számlát már sztornózták, ezért nem készül helyesbítő számla.`);
|
|
548
|
+
const items = refundableItems(details);
|
|
549
|
+
if (!items) throw validation$1(`A(z) ${header.number} számla tételei nem olvashatók ki egyértelműen (áfakulcs vagy negatív tétel), így a helyesbítés nem számolható.`, "Állítsd ki a helyesbítő számlát kézzel.");
|
|
550
|
+
assertRefunds(refunds, header.currency, header.number);
|
|
551
|
+
const rates = new Set(items.map((item) => String(item.vat)));
|
|
552
|
+
const mode = options.partialRefund ?? "auto";
|
|
553
|
+
if (rates.size > 1 && mode === "auto" && !options.refundItems) return proposal(orderNumber, "invoice", header.number, items, refunds, header.currency, mixedVatReason(rates));
|
|
554
|
+
const corrections = [];
|
|
555
|
+
for (const refund of refunds) {
|
|
556
|
+
const correction = await issueCorrection(api, payment, orderNumber, details, refund, await correctionItems(refund, details, items, options), options);
|
|
557
|
+
corrections.push(correction);
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
kind: "correction",
|
|
561
|
+
orderNumber,
|
|
562
|
+
correctedNumber: header.number,
|
|
563
|
+
created: corrections.some((correction) => correction.created),
|
|
564
|
+
corrections
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const RECEIPT_PROPOSAL_REASON = "Nyugtánál a részleges visszatérítés jogi menete nem egyértelmű (sztornó és új nyugta a megmaradó tételekről), ezért a kassza nem állít ki automatikusan bizonylatot. A javasolt tételbontást egyeztesd a könyvelőddel.";
|
|
568
|
+
async function issueForPartialRefund(api, payment, orderNumber, options) {
|
|
569
|
+
if (options.partialRefund === "skip") return skipped$1(orderNumber, "Részleges visszatérítés: a partialRefund: skip beállítás miatt nem készül bizonylat.");
|
|
570
|
+
const refunds = payment.refunds ?? [];
|
|
571
|
+
if (refunds.length === 0) return skipped$1(orderNumber, `Részleges visszatérítés: a(z) ${payment.provider} esemény nem adta át tételesen a visszatérítéseket (azonosító és összeg), ezért a kassza nem tud idempotens helyesbítést kiállítani. Állítsd ki kézzel, vagy kapcsold be a szolgáltató részletes lekérdezését.`);
|
|
572
|
+
const find = (externalId) => api.invoices.find({ externalId }, { includePdf: false }, { signal: options.signal });
|
|
573
|
+
const invoice = await find(orderNumber);
|
|
574
|
+
if (invoice) return correctInvoice(api, payment, orderNumber, invoice, refunds, options);
|
|
575
|
+
const receipt = await api.receipts.find({
|
|
576
|
+
orderNumber,
|
|
577
|
+
downloadPdf: false
|
|
578
|
+
}, { signal: options.signal });
|
|
579
|
+
if (!receipt) return skipped$1(orderNumber, `A(z) ${payment.provider} részleges visszatérítéshez (${payment.id}) nem található kiállított bizonylat.`);
|
|
580
|
+
const original = receipt.type === "reversal" ? receipt.reversedReceiptNumber ?? receipt.number : receipt.number;
|
|
581
|
+
if (receipt.type === "reversal" || receipt.isReversed) {
|
|
582
|
+
const converted = await find(conversionExternalId(original));
|
|
583
|
+
if (converted) return correctInvoice(api, payment, orderNumber, converted, refunds, options);
|
|
584
|
+
return skipped$1(orderNumber, `A(z) ${original} nyugtát már sztornózták.`);
|
|
471
585
|
}
|
|
586
|
+
assertRefunds(refunds, receipt.currency, receipt.number);
|
|
587
|
+
return proposal(orderNumber, "receipt", receipt.number, receiptItems(receipt), refunds, receipt.currency, RECEIPT_PROPOSAL_REASON);
|
|
472
588
|
}
|
|
473
589
|
//#endregion
|
|
474
590
|
//#region src/payments/issue.ts
|
|
@@ -478,7 +594,6 @@ const DEFAULT_CURRENCY = "HUF";
|
|
|
478
594
|
const DEFAULT_EXCHANGE_BANK = "MNB";
|
|
479
595
|
const INVOICE_REVERSAL_SUFFIX = "/SS";
|
|
480
596
|
const RECEIPT_REVERSAL_SUFFIX = "/SN";
|
|
481
|
-
const PARTIAL_REFUND_REASON = "Részleges visszatérítés: a kassza ezt nem sztornózza automatikusan. Számlánál helyesbítő számlát, nyugtánál a könyvelőddel egyeztetett módon új bizonylatot kell kiállítani.";
|
|
482
597
|
function validation(message, hint) {
|
|
483
598
|
return new require_errors.SzamlazzError(message, {
|
|
484
599
|
category: "validation",
|
|
@@ -495,13 +610,25 @@ function skipped(orderNumber, reason) {
|
|
|
495
610
|
function paymentOrderNumber(payment) {
|
|
496
611
|
return `${payment.provider.toUpperCase()}-${payment.id}`;
|
|
497
612
|
}
|
|
498
|
-
|
|
613
|
+
let countryNames;
|
|
614
|
+
function regionName(code) {
|
|
615
|
+
if (countryNames === void 0) try {
|
|
616
|
+
countryNames = new Intl.DisplayNames(["hu"], { type: "region" });
|
|
617
|
+
} catch {
|
|
618
|
+
countryNames = null;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
return countryNames?.of(code);
|
|
622
|
+
} catch {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
499
626
|
function countryName(code) {
|
|
500
627
|
const value = code?.trim();
|
|
501
628
|
if (!value) return void 0;
|
|
502
629
|
if (/^[A-Za-z]{2}$/.test(value)) {
|
|
503
630
|
if (value.toUpperCase() === "HU") return void 0;
|
|
504
|
-
return
|
|
631
|
+
return regionName(value.toUpperCase()) ?? value;
|
|
505
632
|
}
|
|
506
633
|
return value;
|
|
507
634
|
}
|
|
@@ -568,7 +695,7 @@ function currencyOf(payment, options) {
|
|
|
568
695
|
return paid || configured || DEFAULT_CURRENCY;
|
|
569
696
|
}
|
|
570
697
|
function documentGross(items, currency, kind) {
|
|
571
|
-
if (kind === "receipt") return require_rounding.roundMoney(calculateReceiptItems(items, currency).reduce((sum, item) => sum + item.amounts.grossAmount, 0), 2);
|
|
698
|
+
if (kind === "receipt") return require_rounding.roundMoney(require_create.calculateReceiptItems(items, currency).reduce((sum, item) => sum + item.amounts.grossAmount, 0), 2);
|
|
572
699
|
return require_rounding.roundMoney(items.reduce((sum, item) => sum + require_money.calculateInvoiceItem(item, currency).grossAmount, 0), 2);
|
|
573
700
|
}
|
|
574
701
|
function assertAmountMatches(payment, items, currency, kind, options) {
|
|
@@ -682,8 +809,9 @@ async function reverseInvoiceOnce(api, orderNumber, invoiceNumber, options) {
|
|
|
682
809
|
created: true
|
|
683
810
|
};
|
|
684
811
|
} catch (error) {
|
|
685
|
-
if (!isUncertainOutcome(error)) throw error;
|
|
686
|
-
const
|
|
812
|
+
if (!require_once.isUncertainOutcome(error)) throw error;
|
|
813
|
+
const delay = require_once.resolveRecoveryDelay(options.recoveryDelayMs);
|
|
814
|
+
const recovered = await require_once.recoverAfterFailure(lookup, delay, options.signal);
|
|
687
815
|
if (!recovered) throw error;
|
|
688
816
|
return {
|
|
689
817
|
...base,
|
|
@@ -731,13 +859,13 @@ async function reverseReceiptForRefund(api, payment, orderNumber, options) {
|
|
|
731
859
|
}, { signal: options.signal });
|
|
732
860
|
return receiptReversal(orderNumber, receipt.number, reversal.number, true);
|
|
733
861
|
} catch (error) {
|
|
734
|
-
if (!isUncertainOutcome(error)) throw error;
|
|
735
|
-
const delay = resolveRecoveryDelay(options.recoveryDelayMs);
|
|
862
|
+
if (!require_once.isUncertainOutcome(error)) throw error;
|
|
863
|
+
const delay = require_once.resolveRecoveryDelay(options.recoveryDelayMs);
|
|
736
864
|
const reversedLookup = async () => {
|
|
737
865
|
const current = await lookup();
|
|
738
866
|
return current && isReversedReceipt(current) ? current : null;
|
|
739
867
|
};
|
|
740
|
-
const recovered = await recoverAfterFailure(reversedLookup, delay, options.signal);
|
|
868
|
+
const recovered = await require_once.recoverAfterFailure(reversedLookup, delay, options.signal);
|
|
741
869
|
const created = error.category !== "duplicate";
|
|
742
870
|
if (recovered) {
|
|
743
871
|
const number = recovered.type === "reversal" ? recovered.number : void 0;
|
|
@@ -757,10 +885,10 @@ function assertPaidAmount(payment) {
|
|
|
757
885
|
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw validation(`A(z) ${payment.provider} fizetés összege érvénytelen: ${value}`);
|
|
758
886
|
}
|
|
759
887
|
async function issueForPayment(api, payment, options = {}) {
|
|
760
|
-
resolveRecoveryDelay(options.recoveryDelayMs);
|
|
888
|
+
require_once.resolveRecoveryDelay(options.recoveryDelayMs);
|
|
761
889
|
const orderNumber = options.orderNumber?.trim() || paymentOrderNumber(payment);
|
|
762
890
|
if (payment.kind === "refunded") return reverseForRefund(api, payment, orderNumber, options);
|
|
763
|
-
if (payment.kind === "partially-refunded") return
|
|
891
|
+
if (payment.kind === "partially-refunded") return issueForPartialRefund(api, payment, orderNumber, options);
|
|
764
892
|
if (payment.kind !== "paid") return skipped(orderNumber, `A fizetés állapota (${payment.kind}) nem sikeres fizetés, ezért nem készül bizonylat.`);
|
|
765
893
|
assertPaidAmount(payment);
|
|
766
894
|
if (payment.amount?.value === 0) return skipped(orderNumber, "A fizetett összeg 0, ezért nem készül bizonylat.");
|
|
@@ -774,34 +902,28 @@ async function issueForPayment(api, payment, options = {}) {
|
|
|
774
902
|
return issueInvoice(api, payment, orderNumber, items, currency, buyer, decision, options);
|
|
775
903
|
}
|
|
776
904
|
//#endregion
|
|
777
|
-
Object.defineProperty(exports, "
|
|
778
|
-
enumerable: true,
|
|
779
|
-
get: function() {
|
|
780
|
-
return DEFAULT_RECOVERY_DELAY_MS;
|
|
781
|
-
}
|
|
782
|
-
});
|
|
783
|
-
Object.defineProperty(exports, "RECEIPT_MAX_GROSS_HUF", {
|
|
905
|
+
Object.defineProperty(exports, "DEFAULT_LOCK_TTL_SECONDS", {
|
|
784
906
|
enumerable: true,
|
|
785
907
|
get: function() {
|
|
786
|
-
return
|
|
908
|
+
return DEFAULT_LOCK_TTL_SECONDS;
|
|
787
909
|
}
|
|
788
910
|
});
|
|
789
|
-
Object.defineProperty(exports, "
|
|
911
|
+
Object.defineProperty(exports, "DEFAULT_LOCK_WAIT_MS", {
|
|
790
912
|
enumerable: true,
|
|
791
913
|
get: function() {
|
|
792
|
-
return
|
|
914
|
+
return DEFAULT_LOCK_WAIT_MS;
|
|
793
915
|
}
|
|
794
916
|
});
|
|
795
|
-
Object.defineProperty(exports, "
|
|
917
|
+
Object.defineProperty(exports, "ONCE_LOCK_KEY_PREFIX", {
|
|
796
918
|
enumerable: true,
|
|
797
919
|
get: function() {
|
|
798
|
-
return
|
|
920
|
+
return ONCE_LOCK_KEY_PREFIX;
|
|
799
921
|
}
|
|
800
922
|
});
|
|
801
|
-
Object.defineProperty(exports, "
|
|
923
|
+
Object.defineProperty(exports, "RECEIPT_MAX_GROSS_HUF", {
|
|
802
924
|
enumerable: true,
|
|
803
925
|
get: function() {
|
|
804
|
-
return
|
|
926
|
+
return RECEIPT_MAX_GROSS_HUF;
|
|
805
927
|
}
|
|
806
928
|
});
|
|
807
929
|
Object.defineProperty(exports, "buyerFromCustomer", {
|
|
@@ -810,12 +932,6 @@ Object.defineProperty(exports, "buyerFromCustomer", {
|
|
|
810
932
|
return buyerFromCustomer;
|
|
811
933
|
}
|
|
812
934
|
});
|
|
813
|
-
Object.defineProperty(exports, "calculateReceiptItems", {
|
|
814
|
-
enumerable: true,
|
|
815
|
-
get: function() {
|
|
816
|
-
return calculateReceiptItems;
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
935
|
Object.defineProperty(exports, "chooseDocument", {
|
|
820
936
|
enumerable: true,
|
|
821
937
|
get: function() {
|
|
@@ -834,28 +950,28 @@ Object.defineProperty(exports, "convertReceiptToInvoice", {
|
|
|
834
950
|
return convertReceiptToInvoice;
|
|
835
951
|
}
|
|
836
952
|
});
|
|
837
|
-
Object.defineProperty(exports, "
|
|
953
|
+
Object.defineProperty(exports, "correctionExternalId", {
|
|
838
954
|
enumerable: true,
|
|
839
955
|
get: function() {
|
|
840
|
-
return
|
|
956
|
+
return correctionExternalId;
|
|
841
957
|
}
|
|
842
958
|
});
|
|
843
|
-
Object.defineProperty(exports, "
|
|
959
|
+
Object.defineProperty(exports, "createInvoiceOnce", {
|
|
844
960
|
enumerable: true,
|
|
845
961
|
get: function() {
|
|
846
|
-
return
|
|
962
|
+
return createInvoiceOnce;
|
|
847
963
|
}
|
|
848
964
|
});
|
|
849
|
-
Object.defineProperty(exports, "
|
|
965
|
+
Object.defineProperty(exports, "guardOnce", {
|
|
850
966
|
enumerable: true,
|
|
851
967
|
get: function() {
|
|
852
|
-
return
|
|
968
|
+
return guardOnce;
|
|
853
969
|
}
|
|
854
970
|
});
|
|
855
|
-
Object.defineProperty(exports, "
|
|
971
|
+
Object.defineProperty(exports, "invoiceOnceExternalId", {
|
|
856
972
|
enumerable: true,
|
|
857
973
|
get: function() {
|
|
858
|
-
return
|
|
974
|
+
return invoiceOnceExternalId;
|
|
859
975
|
}
|
|
860
976
|
});
|
|
861
977
|
Object.defineProperty(exports, "issueForPayment", {
|
|
@@ -864,51 +980,9 @@ Object.defineProperty(exports, "issueForPayment", {
|
|
|
864
980
|
return issueForPayment;
|
|
865
981
|
}
|
|
866
982
|
});
|
|
867
|
-
Object.defineProperty(exports, "optionalText", {
|
|
868
|
-
enumerable: true,
|
|
869
|
-
get: function() {
|
|
870
|
-
return optionalText;
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
983
|
Object.defineProperty(exports, "paymentOrderNumber", {
|
|
874
984
|
enumerable: true,
|
|
875
985
|
get: function() {
|
|
876
986
|
return paymentOrderNumber;
|
|
877
987
|
}
|
|
878
988
|
});
|
|
879
|
-
Object.defineProperty(exports, "receiptValidationError", {
|
|
880
|
-
enumerable: true,
|
|
881
|
-
get: function() {
|
|
882
|
-
return receiptValidationError;
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
Object.defineProperty(exports, "recoverAfterFailure", {
|
|
886
|
-
enumerable: true,
|
|
887
|
-
get: function() {
|
|
888
|
-
return recoverAfterFailure;
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
Object.defineProperty(exports, "requireReceiptNumber", {
|
|
892
|
-
enumerable: true,
|
|
893
|
-
get: function() {
|
|
894
|
-
return requireReceiptNumber;
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
Object.defineProperty(exports, "resolveRecoveryDelay", {
|
|
898
|
-
enumerable: true,
|
|
899
|
-
get: function() {
|
|
900
|
-
return resolveRecoveryDelay;
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
Object.defineProperty(exports, "unknownOutcomeError", {
|
|
904
|
-
enumerable: true,
|
|
905
|
-
get: function() {
|
|
906
|
-
return unknownOutcomeError;
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
Object.defineProperty(exports, "validateTemplate", {
|
|
910
|
-
enumerable: true,
|
|
911
|
-
get: function() {
|
|
912
|
-
return validateTemplate;
|
|
913
|
-
}
|
|
914
|
-
});
|