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.
Files changed (164) hide show
  1. package/README.en.md +48 -0
  2. package/README.md +152 -22
  3. package/agents/README.md +6 -1
  4. package/agents/api.md +64 -12
  5. package/agents/pitfalls.md +12 -1
  6. package/agents/recipes.md +67 -8
  7. package/agents/skills/kassza/SKILL.md +3 -0
  8. package/agents/skills/migrate-from-szamlazz-js/SKILL.md +39 -0
  9. package/dist/batch/index.cjs +346 -0
  10. package/dist/batch/index.d.cts +91 -0
  11. package/dist/batch/index.d.ts +91 -0
  12. package/dist/batch/index.js +339 -0
  13. package/dist/cli.cjs +105 -14
  14. package/dist/cli.js +104 -13
  15. package/dist/{client-C-PdfL_P.cjs → client-CLFvJCVW.cjs} +358 -181
  16. package/dist/{client-CfaS1-Jx.js → client-Cvf-hTz_.js} +311 -152
  17. package/dist/{client-6MkeuFqD.d.ts → client-DeWWQ-RZ.d.ts} +99 -26
  18. package/dist/{client-B5FryKxJ.d.cts → client-SVZu25MB.d.cts} +99 -26
  19. package/dist/{context-DXNbTPwz.d.cts → context-CFNKzStE.d.cts} +82 -58
  20. package/dist/{context-BxQlIlug.d.ts → context-kb33TgzE.d.ts} +82 -58
  21. package/dist/cookie-stores/index.cjs +21 -150
  22. package/dist/cookie-stores/index.d.cts +4 -63
  23. package/dist/cookie-stores/index.d.ts +4 -63
  24. package/dist/cookie-stores/index.js +3 -144
  25. package/dist/create-CflXrPip.js +204 -0
  26. package/dist/create-CvUvLUaT.cjs +257 -0
  27. package/dist/{create-once-DgMtKKcG.cjs → create-once-9yxR_Er_.cjs} +285 -33
  28. package/dist/create-once-Bh6fOWkW.d.cts +20 -0
  29. package/dist/{create-once-Cz621l8r.js → create-once-Ca2gMk1h.js} +250 -34
  30. package/dist/create-once-VKp0tVow.d.ts +20 -0
  31. package/dist/data-link/index.cjs +4 -4
  32. package/dist/data-link/index.d.cts +2 -52
  33. package/dist/data-link/index.d.ts +2 -52
  34. package/dist/data-link/index.js +4 -4
  35. package/dist/delegation/index.cjs +4 -4
  36. package/dist/delegation/index.d.cts +2 -2
  37. package/dist/delegation/index.d.ts +2 -2
  38. package/dist/delegation/index.js +4 -4
  39. package/dist/{errors-DLrb0g64.js → errors-DtN4g5Z9.js} +9 -1
  40. package/dist/{errors-DxeuTslP.cjs → errors-EE96_6D_.cjs} +20 -0
  41. package/dist/{get-Csm2n0rZ.cjs → get-BdMTseh4.cjs} +9 -183
  42. package/dist/{get-DT3JEgQ6.js → get-CZEShCcj.js} +4 -124
  43. package/dist/index-BhUPY_EH.d.cts +109 -0
  44. package/dist/index-BqGgQ4t3.d.ts +109 -0
  45. package/dist/{index-CYAGCdKJ.d.cts → index-C-7yWV7L.d.cts} +24 -1
  46. package/dist/{index-CYAGCdKJ.d.ts → index-C-7yWV7L.d.ts} +24 -1
  47. package/dist/index.cjs +19 -5
  48. package/dist/index.d.cts +10 -20
  49. package/dist/index.d.ts +10 -20
  50. package/dist/index.js +6 -6
  51. package/dist/ipn/index.cjs +26 -5
  52. package/dist/ipn/index.d.cts +12 -1
  53. package/dist/ipn/index.d.ts +12 -1
  54. package/dist/ipn/index.js +26 -6
  55. package/dist/{issue-AGRohSm7.cjs → issue-Bin4tKD_.cjs} +416 -342
  56. package/dist/{issue-DtSLpVdJ.js → issue-DMA1Nbr1.js} +394 -266
  57. package/dist/journal/index.cjs +454 -0
  58. package/dist/journal/index.d.cts +18 -0
  59. package/dist/journal/index.d.ts +18 -0
  60. package/dist/journal/index.js +445 -0
  61. package/dist/journal-DvI81Kt7.d.cts +114 -0
  62. package/dist/journal-Y9qrIB8G.d.ts +114 -0
  63. package/dist/mcp/index.cjs +1 -1
  64. package/dist/mcp/index.d.cts +1 -1
  65. package/dist/mcp/index.d.ts +1 -1
  66. package/dist/mcp/index.js +1 -1
  67. package/dist/money/index.cjs +2 -1
  68. package/dist/money/index.d.cts +2 -2
  69. package/dist/money/index.d.ts +2 -2
  70. package/dist/money/index.js +2 -2
  71. package/dist/{money-t7uy1n91.js → money-Cw457SD6.js} +95 -3
  72. package/dist/{money-BxytJW6f.cjs → money-K9j0wPit.cjs} +99 -1
  73. package/dist/nav/index.cjs +2 -2
  74. package/dist/nav/index.d.cts +1 -1
  75. package/dist/nav/index.d.ts +1 -1
  76. package/dist/nav/index.js +2 -2
  77. package/dist/{nav-BEwcX7-N.cjs → nav-BNbc8U0R.cjs} +1 -1
  78. package/dist/{nav-CK1WgYo7.js → nav-BcCerwWS.js} +1 -1
  79. package/dist/node/index.cjs +124 -0
  80. package/dist/node/index.d.cts +35 -0
  81. package/dist/node/index.d.ts +35 -0
  82. package/dist/node/index.js +120 -0
  83. package/dist/observe/index.cjs +311 -0
  84. package/dist/observe/index.d.cts +53 -0
  85. package/dist/observe/index.d.ts +53 -0
  86. package/dist/observe/index.js +303 -0
  87. package/dist/once-BN_3B5OF.cjs +103 -0
  88. package/dist/once-CqqZxsXG.js +74 -0
  89. package/dist/{parse-C608ehqM.js → parse-Cr7yQ3_z.js} +2 -2
  90. package/dist/{parse-BJj-aqKy.cjs → parse-DOA2XQ3e.cjs} +2 -2
  91. package/dist/payments/barion.cjs +26 -2
  92. package/dist/payments/barion.d.cts +2 -2
  93. package/dist/payments/barion.d.ts +2 -2
  94. package/dist/payments/barion.js +26 -2
  95. package/dist/payments/index.cjs +7 -2
  96. package/dist/payments/index.d.cts +4 -4
  97. package/dist/payments/index.d.ts +4 -4
  98. package/dist/payments/index.js +3 -3
  99. package/dist/payments/paypal.cjs +16 -2
  100. package/dist/payments/paypal.d.cts +2 -2
  101. package/dist/payments/paypal.d.ts +2 -2
  102. package/dist/payments/paypal.js +16 -2
  103. package/dist/payments/revolut.cjs +16 -2
  104. package/dist/payments/revolut.d.cts +2 -2
  105. package/dist/payments/revolut.d.ts +2 -2
  106. package/dist/payments/revolut.js +16 -2
  107. package/dist/payments/simplepay.cjs +36 -4
  108. package/dist/payments/simplepay.d.cts +2 -2
  109. package/dist/payments/simplepay.d.ts +2 -2
  110. package/dist/payments/simplepay.js +36 -4
  111. package/dist/payments/stripe.cjs +19 -2
  112. package/dist/payments/stripe.d.cts +2 -2
  113. package/dist/payments/stripe.d.ts +2 -2
  114. package/dist/payments/stripe.js +19 -2
  115. package/dist/push-BXj0Sf0Z.d.ts +52 -0
  116. package/dist/push-D4nNbCNV.d.cts +52 -0
  117. package/dist/reference-B5TATqb5.js +124 -0
  118. package/dist/reference-BIdYMK0t.cjs +183 -0
  119. package/dist/reports/index.cjs +1 -1
  120. package/dist/reports/index.d.cts +1 -1
  121. package/dist/reports/index.d.ts +1 -1
  122. package/dist/reports/index.js +1 -1
  123. package/dist/{serialize-DSE_o9RK.js → serialize-B2eFh3D-.js} +5 -1
  124. package/dist/{serialize-BXOww1Uq.cjs → serialize-CquIsxA-.cjs} +5 -1
  125. package/dist/{server-Cd8HB661.js → server-BrJUbXs7.js} +8 -7
  126. package/dist/{server-C6PoNkdD.cjs → server-Dw9j8Mnd.cjs} +17 -16
  127. package/dist/session-Ckl5eJhV.d.ts +7 -0
  128. package/dist/{session-CKOuE6Jc.cjs → session-DeM5vjvC.cjs} +69 -13
  129. package/dist/{session-rMKaJcmq.js → session-SaEEXu2N.js} +58 -14
  130. package/dist/session-YbChChx7.d.cts +7 -0
  131. package/dist/storage/fs.cjs +1 -1
  132. package/dist/storage/fs.js +1 -1
  133. package/dist/storage/index.cjs +1 -1
  134. package/dist/storage/index.js +1 -1
  135. package/dist/store-C1rHd9DB.d.cts +19 -0
  136. package/dist/store-C1rHd9DB.d.ts +19 -0
  137. package/dist/stores/index.cjs +24 -0
  138. package/dist/stores/index.d.cts +4 -0
  139. package/dist/stores/index.d.ts +4 -0
  140. package/dist/stores/index.js +3 -0
  141. package/dist/stores-BNrS0dWl.cjs +506 -0
  142. package/dist/stores-DhjGBrl_.js +441 -0
  143. package/dist/testing/index.cjs +28 -16
  144. package/dist/testing/index.d.cts +6 -3
  145. package/dist/testing/index.d.ts +6 -3
  146. package/dist/testing/index.js +26 -14
  147. package/dist/{types-BVeR7KyU.d.ts → types-B1A8mUIE.d.cts} +13 -1
  148. package/dist/{types-BVeR7KyU.d.cts → types-ByAEvGsZ.d.ts} +13 -1
  149. package/dist/{types-k--zHf4l.d.ts → types-D1zDOH_F.d.ts} +1 -1
  150. package/dist/{types-DH8QoNZK.d.cts → types-lnKkQdot.d.cts} +1 -1
  151. package/dist/warnings-B3DXnveC.d.cts +31 -0
  152. package/dist/warnings-B3DXnveC.d.ts +31 -0
  153. package/dist/warnings-DeIw5RFj.cjs +20 -0
  154. package/dist/warnings-DwTl5J-2.js +15 -0
  155. package/dist/{webhook-CrEGohbg.js → webhook-CQVShAfI.js} +44 -1
  156. package/dist/{webhook-81giRMdQ.cjs → webhook-Ckj-_hPD.cjs} +67 -0
  157. package/dist/webhook-CmdhGiH5.d.cts +11 -0
  158. package/dist/webhook-D6lEejob.d.ts +11 -0
  159. package/llms.txt +4 -3
  160. package/package.json +92 -4
  161. package/dist/session-Dm_45x8K.d.cts +0 -11
  162. package/dist/session-Dm_45x8K.d.ts +0 -11
  163. package/dist/webhook-CpCMHhn2.d.ts +0 -6
  164. package/dist/webhook-CpMJuP6c.d.cts +0 -6
@@ -1,29 +1,53 @@
1
- const require_errors = require("./errors-DxeuTslP.cjs");
2
- const require_serialize = require("./serialize-BXOww1Uq.cjs");
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-BxytJW6f.cjs");
7
- //#region src/core/once.ts
8
- const DEFAULT_RECOVERY_DELAY_MS = 1e3;
9
- const UNCERTAIN_CATEGORIES = /* @__PURE__ */ new Set([
10
- "network",
11
- "timeout",
12
- "partial_success",
13
- "duplicate",
14
- "unexpected_response",
15
- "unknown"
16
- ]);
17
- function isUncertainOutcome(error) {
18
- return error instanceof require_errors.SzamlazzError && UNCERTAIN_CATEGORIES.has(error.category);
19
- }
20
- function resolveRecoveryDelay(value) {
21
- if (value === void 0) return DEFAULT_RECOVERY_DELAY_MS;
22
- if (!Number.isFinite(value) || value < 0) throw new require_errors.SzamlazzError(`A recoveryDelayMs értéke nemnegatív szám legyen, kapott: ${value}`, { category: "configuration" });
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 wait(ms, signal) {
26
- if (ms <= 0) return Promise.resolve();
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 recoverAfterFailure(lookup, delayMs, signal) {
44
- for (let attempt = 1; attempt <= 2; attempt++) {
45
- await wait(delayMs * attempt, signal);
46
- const found = await lookup().catch((error) => {
47
- if (error instanceof require_errors.SzamlazzError && error.retryable) return void 0;
48
- throw error;
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 unknownOutcomeError(error, reference) {
55
- return new require_errors.SzamlazzError(error.message, {
56
- category: error.category,
57
- code: error.code,
58
- action: error.action,
59
- httpStatus: error.httpStatus,
60
- rawResponse: error.rawResponse,
61
- details: {
62
- ...error.details,
63
- outcome: "unknown",
64
- reference
65
- },
66
- hint: `Nem tudni biztosan, hogy a(z) ${reference} bizonylat elkészült-e, és a visszakeresés sem találta meg. Ne állítsd ki kézzel újra: később hívd meg ugyanígy a createOnce-t, az előbb visszakeres.`,
67
- cause: error
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
- async function createInvoiceOnce(api, input, options = {}) {
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
- if (options.lookupFirst !== false) {
114
- const existing = await lookup(api, externalId, orderNumber, type, options);
115
- if (existing) return existingResult(existing, externalId, false);
116
- }
117
- try {
118
- const invoice = await api.create({
119
- ...input,
120
- orderNumber,
121
- externalId
122
- }, options);
123
- return {
124
- number: invoice.number,
125
- created: true,
126
- externalId,
127
- invoice
128
- };
129
- } catch (error) {
130
- if (!isUncertainOutcome(error)) throw error;
131
- const recovered = await recoverAfterFailure(() => lookup(api, externalId, orderNumber, type, options), delayMs, options.signal);
132
- if (recovered) return existingResult(recovered, externalId, error.category !== "duplicate");
133
- if (error.category === "duplicate") throw error;
134
- throw unknownOutcomeError(error, `${orderNumber} rendelés`);
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$2(message) {
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$2(`A bruttó végösszeg (grossTotal) nemnegatív szám legyen, kapott: ${input.grossTotal}`);
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$2(`Devizás (${input.currency}) összegnél add meg az árfolyamot (exchangeRate), mert a 900 000 Ft-os határt forintban kell nézni.`);
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$1(message, hint) {
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$1(`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.");
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$1(`A(z) „${item.name}” tétel mennyisége nem pozitív, a nyugta nem alakítható számlává.`);
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$1("Add meg az átalakítandó nyugta számát (receiptNumber).");
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$1(`A(z) ${receiptNumber} sztornó nyugta, számlává nem alakítható.`);
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$1(`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.");
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/receipts/common.ts
292
- const RECEIPT_XSD_BASE_URL = "https://www.szamlazz.hu/szamla/docs/xsds/";
293
- function receiptValidationError(message) {
294
- return new require_errors.SzamlazzError(message, { category: "validation" });
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 requireText(value, message) {
297
- const trimmed = value?.trim();
298
- if (!trimmed) throw receiptValidationError(message);
299
- return trimmed;
376
+ function skipped$1(orderNumber, reason) {
377
+ return {
378
+ kind: "skipped",
379
+ orderNumber,
380
+ reason
381
+ };
300
382
  }
301
- function optionalText(value) {
302
- const trimmed = value?.trim();
303
- return trimmed ? trimmed : void 0;
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
- const templates = new Set(RECEIPT_PDF_TEMPLATES);
306
- function validateTemplate(template) {
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 requireReceiptNumber(value) {
312
- return requireText(value, "Add meg a nyugtaszámot (receiptNumber).");
391
+ function decimalsFor(currency) {
392
+ return require_vat.isHuf(currency) ? 0 : 2;
313
393
  }
314
- //#endregion
315
- //#region src/receipts/create.ts
316
- const CREATE_RECEIPT_NAMESPACE = "http://www.szamlazz.hu/xmlnyugtacreate";
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 formatAmount(value, currency, decimals) {
361
- return require_vat.isHuf(currency) ? value.toFixed(decimals) : value;
362
- }
363
- function itemNode(item, currency, unit) {
364
- const { input, amounts } = item;
365
- const ledger = input.ledger;
366
- return require_serialize.el("tetel", [
367
- require_serialize.el("megnevezes", input.name.trim()),
368
- require_serialize.optionalEl("azonosito", optionalText(input.identifier)),
369
- require_serialize.el("mennyiseg", amounts.quantity),
370
- require_serialize.el("mennyisegiEgyseg", optionalText(input.unit) ?? unit),
371
- require_serialize.el("nettoEgysegar", amounts.netUnitPrice),
372
- require_serialize.el("afakulcs", String(item.vat)),
373
- require_serialize.el("netto", formatAmount(amounts.netAmount, currency, MONEY_DECIMALS)),
374
- require_serialize.el("afa", formatAmount(amounts.vatAmount, currency, MONEY_DECIMALS)),
375
- require_serialize.el("brutto", formatAmount(amounts.grossAmount, currency, 0)),
376
- ledger && require_serialize.optionalEl("fokonyv", [require_serialize.optionalEl("arbevetel", optionalText(ledger.revenue)), require_serialize.optionalEl("afa", optionalText(ledger.vat))]),
377
- require_serialize.optionalEl("megjegyzes", optionalText(input.comment)),
378
- require_serialize.optionalEl("torloKod", input.dataDeletionCode)
379
- ]);
380
- }
381
- function paymentNodes(payments, items) {
382
- if (payments === void 0 || payments.length === 0) return void 0;
383
- const nodes = payments.map((payment, index) => {
384
- const method = requireText(payment.method, `A(z) ${index + 1}. kifizetés fizetési eszköze (method) kötelező.`);
385
- if (!Number.isFinite(payment.amount)) throw receiptValidationError(`A(z) ${index + 1}. kifizetés összege nem érvényes szám.`);
386
- return require_serialize.el("kifizetes", [
387
- require_serialize.el("fizetoeszkoz", method),
388
- require_serialize.el("osszeg", payment.amount),
389
- require_serialize.optionalEl("leiras", optionalText(payment.description))
390
- ]);
391
- });
392
- const paid = require_rounding.roundMoney(payments.reduce((sum, payment) => sum + payment.amount, 0), MONEY_DECIMALS);
393
- const gross = require_rounding.roundMoney(items.reduce((sum, item) => sum + item.amounts.grossAmount, 0), MONEY_DECIMALS);
394
- if (paid !== gross) throw receiptValidationError(`A kifizetések összege (${paid}) eltér a nyugta bruttó végösszegétől (${gross}) (340).`);
395
- return require_serialize.el("kifizetesek", nodes);
396
- }
397
- function resolvePrefix(value) {
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
- if (rate === void 0 || !Number.isFinite(rate) || rate <= 0) throw receiptValidationError(`Devizás nyugtánál (${currency}) add meg az árfolyamot (exchangeRate) pozitív számként.`);
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
- rate,
410
- bank: requireText(bank, `Devizás nyugtánál (${currency}) add meg az árfolyamot jegyző bankot (exchangeBank).`)
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 buildCreateReceiptXml(credentials, defaults, input) {
414
- const prefix = resolvePrefix(input.prefix ?? defaults.prefix);
415
- const paymentMethod = requireText(input.paymentMethod ?? defaults.paymentMethod, "Add meg a fizetési módot (paymentMethod) a hívásban vagy a receipts alapbeállításaiban.");
416
- const currency = optionalText(input.currency ?? defaults.currency) ?? DEFAULT_CURRENCY$1;
417
- const exchange = resolveExchange(currency, input.exchangeRate ?? defaults.exchangeRate, input.exchangeBank ?? defaults.exchangeBank);
418
- const template = validateTemplate(input.template ?? defaults.template);
419
- const items = calculateReceiptItems(input.items ?? [], currency);
420
- const unit = optionalText(defaults.unit) ?? DEFAULT_UNIT;
421
- const payments = paymentNodes(input.payments, items);
422
- const downloadPdf = input.downloadPdf ?? defaults.downloadPdf ?? true;
423
- return require_serialize.buildXmlDocument({
424
- root: "xmlnyugtacreate",
425
- namespace: CREATE_RECEIPT_NAMESPACE,
426
- schemaLocation: `${RECEIPT_XSD_BASE_URL}nyugtacreate/xmlnyugtacreate.xsd`,
427
- children: [
428
- require_serialize.el("beallitasok", [...credentials, require_serialize.el("pdfLetoltes", downloadPdf)]),
429
- require_serialize.el("fejlec", [
430
- require_serialize.optionalEl("hivasAzonosito", optionalText(input.callId)),
431
- require_serialize.el("elotag", prefix),
432
- require_serialize.el("fizmod", paymentMethod),
433
- require_serialize.el("penznem", currency),
434
- require_serialize.optionalEl("devizaarf", exchange.rate),
435
- require_serialize.optionalEl("devizabank", exchange.bank),
436
- require_serialize.optionalEl("megjegyzes", optionalText(input.comment)),
437
- require_serialize.optionalEl("pdfSablon", template),
438
- require_serialize.optionalEl("fokonyvVevo", optionalText(input.customerLedgerId ?? defaults.customerLedgerId)),
439
- require_serialize.optionalEl("rendelesSzam", optionalText(input.orderNumber))
440
- ]),
441
- require_serialize.el("tetelek", items.map((item) => itemNode(item, currency, unit))),
442
- payments
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
- const DUPLICATE_CALL_ID_CODE = 338;
447
- function enrichDuplicateError(error, input) {
448
- if (!(error instanceof require_errors.SzamlazzError) || error.code !== DUPLICATE_CALL_ID_CODE) return error;
449
- const lookup = input.orderNumber ? ` A meglévő nyugtát a getReceipt({ orderNumber: '${input.orderNumber}' }) hívással kérdezheted le, ha a fiókban tiltva van a rendelésszám ismétlődése.` : " A Számla Agent hívásazonosító alapján nem ad lekérdezési lehetőséget, ezért a meglévő nyugtát a Számlázz.hu felületén keresd meg, vagy adj meg rendelésszámot (orderNumber) is.";
450
- return new require_errors.SzamlazzError(error.message, {
451
- category: "duplicate",
452
- code: error.code,
453
- hint: `Ezzel a hívásazonosítóval (callId: ${input.callId ?? ""}) már készült nyugta, a nyugta nem jött létre újra.${lookup}`,
454
- action: error.action,
455
- httpStatus: error.httpStatus,
456
- rawResponse: error.rawResponse,
457
- cause: error
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
- async function createReceipt(ctx, defaults, input, options = {}) {
461
- const xml = buildCreateReceiptXml(ctx.credentials, defaults, input);
462
- try {
463
- return await ctx.execute({
464
- action: "createReceipt",
465
- xml,
466
- signal: options.signal,
467
- safeToRetry: optionalText(input.callId) !== void 0
468
- }, require_parse.parseReceiptResponse);
469
- } catch (error) {
470
- throw enrichDuplicateError(error, input);
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
- const countryNames = new Intl.DisplayNames(["hu"], { type: "region" });
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 countryNames.of(value.toUpperCase()) ?? value;
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 recovered = await recoverAfterFailure(lookup, resolveRecoveryDelay(options.recoveryDelayMs), options.signal);
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 skipped(orderNumber, PARTIAL_REFUND_REASON);
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, "DEFAULT_RECOVERY_DELAY_MS", {
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 RECEIPT_MAX_GROSS_HUF;
908
+ return DEFAULT_LOCK_TTL_SECONDS;
787
909
  }
788
910
  });
789
- Object.defineProperty(exports, "RECEIPT_ONLY_VAT_CODES", {
911
+ Object.defineProperty(exports, "DEFAULT_LOCK_WAIT_MS", {
790
912
  enumerable: true,
791
913
  get: function() {
792
- return RECEIPT_ONLY_VAT_CODES;
914
+ return DEFAULT_LOCK_WAIT_MS;
793
915
  }
794
916
  });
795
- Object.defineProperty(exports, "RECEIPT_XSD_BASE_URL", {
917
+ Object.defineProperty(exports, "ONCE_LOCK_KEY_PREFIX", {
796
918
  enumerable: true,
797
919
  get: function() {
798
- return RECEIPT_XSD_BASE_URL;
920
+ return ONCE_LOCK_KEY_PREFIX;
799
921
  }
800
922
  });
801
- Object.defineProperty(exports, "buildCreateReceiptXml", {
923
+ Object.defineProperty(exports, "RECEIPT_MAX_GROSS_HUF", {
802
924
  enumerable: true,
803
925
  get: function() {
804
- return buildCreateReceiptXml;
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, "createInvoiceOnce", {
953
+ Object.defineProperty(exports, "correctionExternalId", {
838
954
  enumerable: true,
839
955
  get: function() {
840
- return createInvoiceOnce;
956
+ return correctionExternalId;
841
957
  }
842
958
  });
843
- Object.defineProperty(exports, "createReceipt", {
959
+ Object.defineProperty(exports, "createInvoiceOnce", {
844
960
  enumerable: true,
845
961
  get: function() {
846
- return createReceipt;
962
+ return createInvoiceOnce;
847
963
  }
848
964
  });
849
- Object.defineProperty(exports, "invoiceOnceExternalId", {
965
+ Object.defineProperty(exports, "guardOnce", {
850
966
  enumerable: true,
851
967
  get: function() {
852
- return invoiceOnceExternalId;
968
+ return guardOnce;
853
969
  }
854
970
  });
855
- Object.defineProperty(exports, "isUncertainOutcome", {
971
+ Object.defineProperty(exports, "invoiceOnceExternalId", {
856
972
  enumerable: true,
857
973
  get: function() {
858
- return isUncertainOutcome;
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
- });