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
package/agents/recipes.md CHANGED
@@ -136,19 +136,21 @@ Other adapters with the same interface:
136
136
  - `fsStorage` from `kassza/storage/fs`
137
137
  - `memoryStorage`
138
138
 
139
- ## 7. Serverless and edge: share the session
139
+ ## 7. Serverless and edge: share the session, the ledger and the lock
140
140
 
141
141
  ```ts
142
142
  import { Redis } from '@upstash/redis'
143
143
  import { createKassza } from 'kassza'
144
- import { upstashRedisCookieStore } from 'kassza/cookie-stores'
144
+ import { upstashRedisStore } from 'kassza/stores'
145
145
 
146
- const kassza = createKassza({ cookieStore: upstashRedisCookieStore(Redis.fromEnv()) })
146
+ const store = upstashRedisStore(Redis.fromEnv())
147
+ const kassza = createKassza({ cookieStore: store, attemptLedger: store, createOnceLock: store })
147
148
  ```
148
149
 
149
- - On Cloudflare Workers, use `cloudflareKvCookieStore(env.KASSZA_KV)`.
150
- - Other stores: `ioredisCookieStore`, `nodeRedisCookieStore`, `customCookieStore({ get, set, delete })`.
151
- - If the store fails, kassza simply continues without a session.
150
+ - `createOnceLock` prevents a duplicate invoice when two instances process the same order at the same time.
151
+ - On Cloudflare Workers, `cloudflareKvStore(env.KASSZA_KV)` is fine for the session, but locks and the journal need `durableObjectStore(stub)` (KV has no atomic write).
152
+ - Other stores: `ioredisStore`, `nodeRedisStore`, `customStore({ get, set, delete })`. `diagnoseStore(store)` tells what a store is suitable for.
153
+ - If the session store fails, kassza continues without a session and reports it to `hooks.onWarning`.
152
154
 
153
155
  ## 8. Unit tests without calling Számlázz.hu
154
156
 
@@ -238,7 +240,9 @@ export const POST = stripeWebhook({
238
240
  - The handler verifies the signature and answers 400, 200 or 500 itself. A thrown error means 500, and the provider redelivers, which is safe: `issueForPayment` looks the document up first.
239
241
  - Catch non-retryable errors (a missing VAT rate, an incomplete billing address) and hand them to a human, otherwise the provider keeps redelivering a request that can never succeed. `parkForHuman` is your own function.
240
242
  - The same pattern works with `simplePayWebhook`, `barionWebhook`, `revolutWebhook` and `payPalWebhook`; only the credentials differ (see [api.md](./api.md)).
241
- - A full refund reverses the document. A partial refund is skipped on purpose: it needs a corrective document decided by a human.
243
+ - A full refund reverses the document. A partial refund of an invoice issues one corrective invoice per refund; with several VAT rates (and always for receipts) it returns a `refund-proposal` instead, unless you pass `refundItems` or `partialRefund: 'proportional'`.
244
+ - Add `dedupe: store` to acknowledge redelivered events without calling Számla Agent again.
245
+ - Under Express or NestJS, wrap the handler: `app.post(path, toNodeHandler(stripeWebhook({ ... })))` from `kassza/node`, mounted before `express.json()`.
242
246
  - The receipt prefix and payment method come from the client's `defaults.receipt` (recipe 1). Pass `receipt: { ... }` or `invoice: { ... }` only to override fields; a receipt prefix must never be one already used on invoices (error 336).
243
247
 
244
248
  ## 12. Receipt or invoice, and an invoice after a receipt
@@ -293,7 +297,7 @@ const { missing, mismatched } = reconcileNavReports(local, remote)
293
297
  ```
294
298
 
295
299
  - Számlázz.hu reports the receipts issued there after the NAV connection, so this client stays read-only (no `allowWrite`). Alert a human about `missing` and `mismatched` days.
296
- - `receiptsBetween` is your own query over stored `Receipt` objects (from `receipts.get`, `issueForPayment` results, or the data link in recipe 15).
300
+ - Get the receipts from `kassza/journal`: `journal.receipts({ from, to })` (see recipe 18). Without it you need your own query over stored `Receipt` objects.
297
301
  - Only receipts that Számlázz.hu does not know about, such as a paper pad used during an outage, are yours to submit: `nav.submitReport(paperReceiptReport({ applicableDate, entries }))` with `allowWrite: true`.
298
302
 
299
303
  ## 14. Platform invoicing for many companies (delegated invoicing)
@@ -385,3 +389,58 @@ The fake Agent runs the real kassza code (XML, multipart, response parsing, retr
385
389
 
386
390
  - The server is read-only by default. Add `"--allow-write"` to `args` to enable issuing and reversing; even then every write needs the confirmation code from the matching preview tool, for exactly the same input.
387
391
  - Start with a Számlázz.hu test account.
392
+
393
+ ## 18. Receipt journal for the NAV daily summary
394
+
395
+ ```ts
396
+ import { createKassza } from 'kassza'
397
+ import { createJournal, kvJournal } from 'kassza/journal'
398
+ import { navDailyReports } from 'kassza/reports'
399
+ import { upstashRedisStore } from 'kassza/stores'
400
+
401
+ const store = upstashRedisStore(Redis.fromEnv())
402
+ export const journal = createJournal(kvJournal(store))
403
+ export const kassza = createKassza({
404
+ createOnceLock: store,
405
+ hooks: { onDocument: (event) => journal.record(event), onDocumentError: 'throw' },
406
+ })
407
+
408
+ await journal.trackReceipt(order.id, () => kassza.receipts.createOnce({ orderNumber: order.id, items }))
409
+ const reports = navDailyReports(await journal.receipts({ from: '2026-10-01', to: '2026-10-31' }))
410
+ await journal.settle(kassza)
411
+ ```
412
+
413
+ - `trackReceipt` leaves a pending entry when the outcome is unknown; schedule `journal.settle(kassza)` (for example daily) to resolve them.
414
+ - `journal.reconcile(push.receipts)` with the data link archive adds receipts the journal missed.
415
+
416
+ ## 19. Monthly subscription invoicing
417
+
418
+ ```ts
419
+ import { billingPeriodAt, runBatch } from 'kassza/batch'
420
+
421
+ const period = billingPeriodAt({ interval: 'month', anchor: '2026-01-31', paymentDueInDays: 8 }, new Date())
422
+ const result = await runBatch(kassza, {
423
+ items: subscriptions.map((subscription) => ({
424
+ key: `SUB-${subscription.id}-${period.start}`,
425
+ document: () => ({ kind: 'invoice', input: invoiceFor(subscription, period) }),
426
+ })),
427
+ journal,
428
+ })
429
+ if (result.stopped) alertHuman(result.stopped.reason)
430
+ ```
431
+
432
+ - Run it from your scheduler (Vercel Cron, Workers Cron). A rerun reports already issued items as `existing`.
433
+ - Try `dryRun: true` first: invoices are previewed by Számlázz.hu, nothing is created.
434
+
435
+ ## 20. Observability without telemetry
436
+
437
+ ```ts
438
+ import { combineHooks, createMetricsRegistry, observe } from 'kassza/observe'
439
+
440
+ export const metrics = createMetricsRegistry()
441
+ export const kassza = createKassza({
442
+ hooks: combineHooks(observe({ logger, tracer, metrics }), { onDocument: (event) => journal.record(event) }),
443
+ })
444
+ ```
445
+
446
+ - Expose `metrics.renderPrometheus()` on an internal endpoint. Alert on `kassza_store_errors_total` and `kassza_unexpected_response_total`.
@@ -21,6 +21,9 @@ Before writing code, read the package docs from `node_modules/kassza/agents/`:
21
21
  6. Tests must use `createMockKassza()` or `createFakeAgentFetch()` from `kassza/testing`, never a real Agent key.
22
22
  7. Issue Agent receipts only for activities without the online cash register obligation, and only when all four receipt conditions hold (`chooseDocument()` checks them). A fixed shop, café or restaurant needs an online cash register instead.
23
23
  8. Never submit NAV receipt reports for receipts issued in Számlázz.hu; it reports them itself. Use `kassza/nav` read-only unless the user explicitly reports a paper receipt pad.
24
+ 9. In serverless or multi-instance deployments, pass a shared `createOnceLock` store from `kassza/stores` (Redis, Upstash or Durable Object; not Cloudflare KV), so two instances cannot issue the same order twice.
25
+ 10. When `onDocument` writes a journal used for NAV reports (`kassza/journal`), set `onDocumentError: 'throw'` so a missed write is never silent.
26
+ 11. Under Express or NestJS, wrap webhook handlers with `toNodeHandler` from `kassza/node` and mount them before `express.json()`.
24
27
 
25
28
  ## Checklist before finishing
26
29
 
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: migrate-from-szamlazz-js
3
+ description: Migrate a codebase from the szamlazz.js npm package to kassza. Use when the user asks to replace, upgrade or migrate away from szamlazz.js, or when code imports Client, Invoice, Buyer, Seller or Item from 'szamlazz.js' and the user wants kassza instead.
4
+ ---
5
+
6
+ # Migrate from szamlazz.js to kassza
7
+
8
+ Read `node_modules/kassza/agents/pitfalls.md` and `api.md` first.
9
+
10
+ ## Find the usage
11
+
12
+ Search for `from 'szamlazz.js'`, `require('szamlazz.js')`, `new Client(`, `issueInvoice(`, `getInvoiceData(` and `reverseInvoice(`. List every call site to the user before editing.
13
+
14
+ ## Mapping
15
+
16
+ | szamlazz.js | kassza |
17
+ |---|---|
18
+ | `new Client({ authToken })` | one shared `createKassza({ agentKey })` (or `SZAMLAZZ_AGENT_KEY`) |
19
+ | `requestInvoiceDownload`, `eInvoice`, `timeout` | `downloadPdf`, `eInvoice` (per invoice or in `defaults.invoice`), `timeoutMs` |
20
+ | `new Seller({ bank: { name, accountNumber }, email: { replyToAddress, subject, message } })` | `seller: { bank, bankAccount, emailReplyTo, emailSubject, emailText }` |
21
+ | `new Buyer({ ..., postAddress })` | plain object `buyer: { name, zip, city, address, taxNumber?, postal? }` |
22
+ | `new Item({ label, ... })` | `{ name, quantity, unit, vat, netUnitPrice | grossUnitPrice }` |
23
+ | `client.issueInvoice(new Invoice({...}))` | `kassza.invoices.createOnce({ orderNumber, ... })` |
24
+ | `prepaymentInvoice: true` | `type: 'advance'` |
25
+ | `adjustmentInvoiceNumber` | `type: 'corrective', correctedInvoiceNumber` |
26
+ | `PaymentMethods.*`, `Currencies.*`, `Languages.*` | plain strings: `'átutalás'`, `'HUF'`, `'hu'` |
27
+ | `getInvoiceData({ invoiceId | orderNumber, pdf })` | `invoices.get(number | { orderNumber }, { includePdf })` or `find(...)` |
28
+ | `reverseInvoice({ invoiceId, eInvoice, requestInvoiceDownload })` | `invoices.reverse({ invoiceNumber, eInvoice, downloadPdf })` |
29
+ | result `invoiceId`, string totals, `customerAccountUrl`, Buffer `pdf` | `number`, numeric totals, `buyerAccountUrl`, `Uint8Array` `pdf` |
30
+
31
+ ## Rules while migrating
32
+
33
+ 1. Every invoice gets an `orderNumber` from the user's order ID, and is issued with `createOnce`. Ask if no stable order ID exists.
34
+ 2. Delete any custom retry loop around invoice creation. kassza never resends a create.
35
+ 3. Keep `netUnitPrice` or `grossUnitPrice` as the user had it; never convert prices by hand.
36
+ 4. Replace string totals parsing (`Number(result.grossTotal)`) with the numeric fields.
37
+ 5. Replace error handling on message text with `isSzamlazzError(error)` and `error.category`.
38
+ 6. Update tests to `createMockKassza()` or `createFakeAgentFetch()` from `kassza/testing`.
39
+ 7. Remove `szamlazz.js` from `package.json` only after the user confirms every call site was migrated.
@@ -0,0 +1,346 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors-EE96_6D_.cjs");
3
+ const require_dates = require("../dates-CPgBodkP.cjs");
4
+ const require_create = require("../create-CvUvLUaT.cjs");
5
+ //#region src/batch/recurring.ts
6
+ const INTERVALS = /* @__PURE__ */ new Set([
7
+ "week",
8
+ "month",
9
+ "quarter",
10
+ "year"
11
+ ]);
12
+ const MONTHS = {
13
+ month: 1,
14
+ quarter: 3,
15
+ year: 12
16
+ };
17
+ function invalid$1(message) {
18
+ return new require_errors.SzamlazzError(message, { category: "validation" });
19
+ }
20
+ function parts(day) {
21
+ const [year, month, date] = day.split("-").map(Number);
22
+ return [
23
+ year,
24
+ month,
25
+ date
26
+ ];
27
+ }
28
+ function format(year, month, day) {
29
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
30
+ }
31
+ function daysInMonth(year, month) {
32
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
33
+ }
34
+ function addDays(day, days) {
35
+ const [year, month, date] = parts(day);
36
+ return new Date(Date.UTC(year, month - 1, date + days)).toISOString().slice(0, 10);
37
+ }
38
+ function shift(anchor, interval, count) {
39
+ if (interval === "week") return addDays(anchor, count * 7);
40
+ const [year, month, day] = parts(anchor);
41
+ const total = year * 12 + (month - 1) + count * MONTHS[interval];
42
+ const targetYear = Math.floor(total / 12);
43
+ const targetMonth = total % 12 + 1;
44
+ return format(targetYear, targetMonth, Math.min(day, daysInMonth(targetYear, targetMonth)));
45
+ }
46
+ function resolve(options) {
47
+ if (!INTERVALS.has(options.interval)) throw invalid$1(`Ismeretlen számlázási időszak: ${String(options.interval)}`);
48
+ let anchor;
49
+ try {
50
+ anchor = require_dates.toAgentDate(options.anchor);
51
+ } catch (error) {
52
+ throw invalid$1(error instanceof Error ? error.message : String(error));
53
+ }
54
+ const due = options.paymentDueInDays;
55
+ if (due !== void 0 && (!Number.isInteger(due) || due < 0)) throw invalid$1(`A paymentDueInDays nemnegatív egész legyen, kapott: ${due}`);
56
+ return {
57
+ anchor,
58
+ interval: options.interval
59
+ };
60
+ }
61
+ function billingPeriod(options, index) {
62
+ if (!Number.isInteger(index) || index < 0) throw invalid$1(`Az időszak sorszáma nemnegatív egész legyen, kapott: ${index}`);
63
+ const { anchor, interval } = resolve(options);
64
+ const start = shift(anchor, interval, index);
65
+ const end = addDays(shift(anchor, interval, index + 1), -1);
66
+ const due = options.paymentDueInDays;
67
+ return {
68
+ index,
69
+ interval,
70
+ start,
71
+ end,
72
+ ...due === void 0 ? {} : { dueDate: addDays(start, due) },
73
+ key: `${interval}:${start}`
74
+ };
75
+ }
76
+ function billingPeriodAt(options, date) {
77
+ const { anchor, interval } = resolve(options);
78
+ let day;
79
+ try {
80
+ day = require_dates.toAgentDate(date);
81
+ } catch (error) {
82
+ throw invalid$1(error instanceof Error ? error.message : String(error));
83
+ }
84
+ if (day < anchor) throw invalid$1(`A(z) ${day} dátum korábbi, mint az első időszak kezdete (${anchor}).`);
85
+ const [anchorYear, anchorMonth] = parts(anchor);
86
+ const [year, month] = parts(day);
87
+ let index = interval === "week" ? Math.floor((Date.parse(`${day}T00:00:00Z`) - Date.parse(`${anchor}T00:00:00Z`)) / 6048e5) : Math.floor(((year - anchorYear) * 12 + (month - anchorMonth)) / MONTHS[interval]);
88
+ while (index > 0 && shift(anchor, interval, index) > day) index -= 1;
89
+ while (shift(anchor, interval, index + 1) <= day) index += 1;
90
+ return billingPeriod(options, index);
91
+ }
92
+ function billingPeriodsBetween(options, from, to) {
93
+ const first = billingPeriodAt(options, from);
94
+ const last = billingPeriodAt(options, to);
95
+ const periods = [];
96
+ for (let index = first.index; index <= last.index; index++) periods.push(billingPeriod(options, index));
97
+ return periods;
98
+ }
99
+ function todayPeriod(options, now = /* @__PURE__ */ new Date()) {
100
+ return billingPeriodAt(options, require_dates.addBudapestDays(now, 0));
101
+ }
102
+ //#endregion
103
+ //#region src/batch/run.ts
104
+ const MAX_BATCH_CONCURRENCY = 4;
105
+ const DEFAULT_BATCH_RATE_PER_MINUTE = 30;
106
+ const STOPPING_CATEGORIES = /* @__PURE__ */ new Set([
107
+ "rate_limit",
108
+ "auth",
109
+ "account",
110
+ "configuration",
111
+ "maintenance",
112
+ "attempt_limit",
113
+ "store_unavailable"
114
+ ]);
115
+ function invalid(message) {
116
+ return new require_errors.SzamlazzError(message, { category: "validation" });
117
+ }
118
+ function resolveConcurrency(value) {
119
+ if (value === void 0) return 1;
120
+ if (!Number.isInteger(value) || value < 1 || value > 4) throw new require_errors.SzamlazzError(`A concurrency 1 és 4 közötti egész legyen, kapott: ${value}. A Számlázz.hu a párhuzamos kérések kerülését kéri.`, { category: "configuration" });
121
+ return value;
122
+ }
123
+ function resolveRate(value) {
124
+ if (value === void 0) return 30;
125
+ if (!Number.isFinite(value) || value <= 0) throw new require_errors.SzamlazzError(`A ratePerMinute pozitív szám legyen, kapott: ${value}`, { category: "configuration" });
126
+ return value;
127
+ }
128
+ function assertKeys(items) {
129
+ const seen = /* @__PURE__ */ new Set();
130
+ for (const item of items) {
131
+ const key = item.key?.trim();
132
+ if (!key) throw invalid("Minden tételhez adj meg kulcsot (key), ez lesz a rendelésszám.");
133
+ if (seen.has(key)) throw invalid(`A(z) ${key} kulcs kétszer szerepel a listában.`);
134
+ seen.add(key);
135
+ }
136
+ }
137
+ function withOrderNumber(input, key) {
138
+ const own = input.orderNumber?.trim();
139
+ if (own && own !== key) throw invalid(`A(z) ${key} tétel rendelésszáma (${own}) eltér a kulcstól. A batch a kulcsot használja rendelésszámként, hogy az újrafuttatás ne állítson ki duplát.`);
140
+ return {
141
+ ...input,
142
+ orderNumber: key
143
+ };
144
+ }
145
+ function wait(ms, signal) {
146
+ if (ms <= 0) return Promise.resolve();
147
+ return new Promise((resolve, reject) => {
148
+ if (signal?.aborted) {
149
+ reject(signal.reason);
150
+ return;
151
+ }
152
+ const timer = setTimeout(() => {
153
+ signal?.removeEventListener("abort", onAbort);
154
+ resolve();
155
+ }, ms);
156
+ const onAbort = () => {
157
+ clearTimeout(timer);
158
+ reject(signal?.reason);
159
+ };
160
+ signal?.addEventListener("abort", onAbort, { once: true });
161
+ });
162
+ }
163
+ function invoiceGross(result) {
164
+ return result.invoice?.grossTotal ?? result.details?.totals.grossAmount;
165
+ }
166
+ async function previewDocument(api, document, key, signal) {
167
+ if (document.kind === "invoice") return {
168
+ key,
169
+ status: "previewed",
170
+ kind: "invoice",
171
+ grossTotal: (await api.invoices.preview(withOrderNumber(document.input, key), { signal })).grossTotal
172
+ };
173
+ const input = withOrderNumber(document.input, key);
174
+ if (input.items.length === 0) throw invalid(`A(z) ${key} nyugtán nincs tétel.`);
175
+ const gross = require_create.calculateReceiptItems(input.items, input.currency ?? "HUF").reduce((sum, item) => sum + item.amounts.grossAmount, 0);
176
+ return {
177
+ key,
178
+ status: "previewed",
179
+ kind: "receipt",
180
+ grossTotal: Math.round(gross * 100) / 100
181
+ };
182
+ }
183
+ async function issueDocument(api, document, key, options) {
184
+ const onceOptions = {
185
+ signal: options.signal,
186
+ recoveryDelayMs: options.recoveryDelayMs
187
+ };
188
+ if (document.kind === "invoice") {
189
+ const input = withOrderNumber(document.input, key);
190
+ const run = () => api.invoices.createOnce(input, onceOptions);
191
+ const result = options.journal ? await options.journal.trackInvoice(key, run) : await run();
192
+ return {
193
+ key,
194
+ status: result.created ? "created" : "existing",
195
+ kind: "invoice",
196
+ number: result.number,
197
+ grossTotal: invoiceGross(result)
198
+ };
199
+ }
200
+ const input = withOrderNumber(document.input, key);
201
+ const run = () => api.receipts.createOnce(input, onceOptions);
202
+ const result = options.journal ? await options.journal.trackReceipt(key, run) : await run();
203
+ return {
204
+ key,
205
+ status: result.created ? "created" : "existing",
206
+ kind: "receipt",
207
+ number: result.receipt.number,
208
+ grossTotal: result.receipt.totals.grossAmount
209
+ };
210
+ }
211
+ function stopReason(error) {
212
+ if (!require_errors.isSzamlazzError(error) || !STOPPING_CATEGORIES.has(error.category)) return void 0;
213
+ return `A(z) ${error.category} hiba miatt a batch leállt, a hátralévő tételek nem futottak le: ${error.message}`;
214
+ }
215
+ async function runBatch(api, options) {
216
+ const concurrency = resolveConcurrency(options.concurrency);
217
+ const intervalMs = 6e4 / resolveRate(options.ratePerMinute);
218
+ assertKeys(options.items);
219
+ const dryRun = options.dryRun === true;
220
+ const results = new Array(options.items.length);
221
+ const counts = {
222
+ created: 0,
223
+ existing: 0,
224
+ previewed: 0,
225
+ failed: 0,
226
+ skipped: 0
227
+ };
228
+ let done = 0;
229
+ let cursor = 0;
230
+ let nextStart = Date.now();
231
+ const control = { stopped: void 0 };
232
+ const currentStop = () => control.stopped;
233
+ const record = (index, result) => {
234
+ results[index] = result;
235
+ counts[result.status] += 1;
236
+ done += 1;
237
+ try {
238
+ options.onProgress?.({
239
+ total: options.items.length,
240
+ done,
241
+ ...counts,
242
+ key: result.key,
243
+ status: result.status
244
+ });
245
+ } catch {
246
+ return;
247
+ }
248
+ };
249
+ const processItem = async (index, item) => {
250
+ const key = item.key.trim();
251
+ try {
252
+ const document = await item.document();
253
+ const result = dryRun ? await previewDocument(api, document, key, options.signal) : await issueDocument(api, document, key, options);
254
+ record(index, result);
255
+ } catch (error) {
256
+ if (options.signal?.aborted && error === options.signal.reason) {
257
+ control.stopped ??= {
258
+ reason: "A batch futását megszakították.",
259
+ error
260
+ };
261
+ record(index, {
262
+ key,
263
+ status: "skipped",
264
+ reason: "Megszakítva."
265
+ });
266
+ return;
267
+ }
268
+ const reason = stopReason(error);
269
+ if (reason) control.stopped ??= {
270
+ reason,
271
+ error
272
+ };
273
+ record(index, {
274
+ key,
275
+ status: "failed",
276
+ error
277
+ });
278
+ }
279
+ };
280
+ const worker = async () => {
281
+ for (;;) {
282
+ if (control.stopped || options.signal?.aborted) return;
283
+ const index = cursor;
284
+ cursor += 1;
285
+ const item = options.items[index];
286
+ if (!item) return;
287
+ const delay = nextStart - Date.now();
288
+ nextStart = Math.max(nextStart, Date.now()) + intervalMs;
289
+ try {
290
+ await wait(delay, options.signal);
291
+ } catch (error) {
292
+ control.stopped ??= {
293
+ reason: "A batch futását megszakították.",
294
+ error
295
+ };
296
+ record(index, {
297
+ key: item.key.trim(),
298
+ status: "skipped",
299
+ reason: "Megszakítva."
300
+ });
301
+ return;
302
+ }
303
+ const stop = currentStop();
304
+ if (stop) {
305
+ record(index, {
306
+ key: item.key.trim(),
307
+ status: "skipped",
308
+ reason: stop.reason
309
+ });
310
+ return;
311
+ }
312
+ await processItem(index, item);
313
+ }
314
+ };
315
+ await Promise.all(Array.from({ length: Math.min(concurrency, options.items.length) }, worker));
316
+ if (options.signal?.aborted) control.stopped ??= { reason: "A batch futását megszakították." };
317
+ options.items.forEach((item, index) => {
318
+ if (results[index]) return;
319
+ record(index, {
320
+ key: item.key.trim(),
321
+ status: "skipped",
322
+ reason: control.stopped?.reason ?? "Nem futott le."
323
+ });
324
+ });
325
+ const by = (status) => results.filter((result) => result.status === status);
326
+ const grossTotal = results.filter((result) => result.status !== "failed" && result.status !== "skipped").reduce((sum, result) => sum + (result.grossTotal ?? 0), 0);
327
+ return {
328
+ dryRun,
329
+ items: results,
330
+ created: by("created"),
331
+ existing: by("existing"),
332
+ previewed: by("previewed"),
333
+ failed: by("failed"),
334
+ skipped: by("skipped"),
335
+ stopped: control.stopped,
336
+ grossTotal: Math.round(grossTotal * 100) / 100
337
+ };
338
+ }
339
+ //#endregion
340
+ exports.DEFAULT_BATCH_RATE_PER_MINUTE = DEFAULT_BATCH_RATE_PER_MINUTE;
341
+ exports.MAX_BATCH_CONCURRENCY = MAX_BATCH_CONCURRENCY;
342
+ exports.billingPeriod = billingPeriod;
343
+ exports.billingPeriodAt = billingPeriodAt;
344
+ exports.billingPeriodsBetween = billingPeriodsBetween;
345
+ exports.runBatch = runBatch;
346
+ exports.todayPeriod = todayPeriod;
@@ -0,0 +1,91 @@
1
+ import { t as DateInput } from "../dates-9zvESP0i.cjs";
2
+ import { M as CreateInvoiceInput } from "../context-CFNKzStE.cjs";
3
+ import { t as CreateReceiptInput } from "../types-lnKkQdot.cjs";
4
+ import { a as ReceiptsApi, t as InvoicesApi } from "../client-SVZu25MB.cjs";
5
+ import { n as Journal } from "../journal-DvI81Kt7.cjs";
6
+ //#region src/batch/recurring.d.ts
7
+ type BillingInterval = "week" | "month" | "quarter" | "year";
8
+ interface BillingPeriodOptions {
9
+ readonly interval: BillingInterval;
10
+ readonly anchor: DateInput;
11
+ readonly paymentDueInDays?: number | undefined;
12
+ }
13
+ interface BillingPeriod {
14
+ readonly index: number;
15
+ readonly interval: BillingInterval;
16
+ readonly start: string;
17
+ readonly end: string;
18
+ readonly dueDate?: string | undefined;
19
+ readonly key: string;
20
+ }
21
+ export declare function billingPeriod(options: BillingPeriodOptions, index: number): BillingPeriod;
22
+ export declare function billingPeriodAt(options: BillingPeriodOptions, date: DateInput): BillingPeriod;
23
+ export declare function billingPeriodsBetween(options: BillingPeriodOptions, from: DateInput, to: DateInput): BillingPeriod[];
24
+ export declare function todayPeriod(options: BillingPeriodOptions, now?: Date): BillingPeriod;
25
+ //#endregion
26
+ //#region src/batch/run.d.ts
27
+ export declare const MAX_BATCH_CONCURRENCY = 4;
28
+ export declare const DEFAULT_BATCH_RATE_PER_MINUTE = 30;
29
+ type BatchDocument = {
30
+ readonly kind: "invoice";
31
+ readonly input: CreateInvoiceInput;
32
+ } | {
33
+ readonly kind: "receipt";
34
+ readonly input: CreateReceiptInput;
35
+ };
36
+ interface BatchItem {
37
+ readonly key: string;
38
+ readonly document: () => BatchDocument | Promise<BatchDocument>;
39
+ }
40
+ interface BatchProgress {
41
+ readonly total: number;
42
+ readonly done: number;
43
+ readonly created: number;
44
+ readonly existing: number;
45
+ readonly previewed: number;
46
+ readonly failed: number;
47
+ readonly skipped: number;
48
+ readonly key: string;
49
+ readonly status: BatchItemStatus;
50
+ }
51
+ type BatchItemStatus = "created" | "existing" | "previewed" | "failed" | "skipped";
52
+ interface BatchItemResult {
53
+ readonly key: string;
54
+ readonly status: BatchItemStatus;
55
+ readonly kind?: "invoice" | "receipt" | undefined;
56
+ readonly number?: string | undefined;
57
+ readonly grossTotal?: number | undefined;
58
+ readonly error?: unknown;
59
+ readonly reason?: string | undefined;
60
+ }
61
+ interface BatchResult {
62
+ readonly dryRun: boolean;
63
+ readonly items: readonly BatchItemResult[];
64
+ readonly created: readonly BatchItemResult[];
65
+ readonly existing: readonly BatchItemResult[];
66
+ readonly previewed: readonly BatchItemResult[];
67
+ readonly failed: readonly BatchItemResult[];
68
+ readonly skipped: readonly BatchItemResult[];
69
+ readonly stopped?: {
70
+ readonly reason: string;
71
+ readonly error?: unknown;
72
+ } | undefined;
73
+ readonly grossTotal: number;
74
+ }
75
+ interface BatchApi {
76
+ readonly invoices: Pick<InvoicesApi, "createOnce" | "preview">;
77
+ readonly receipts: Pick<ReceiptsApi, "createOnce">;
78
+ }
79
+ interface RunBatchOptions {
80
+ readonly items: readonly BatchItem[];
81
+ readonly concurrency?: number | undefined;
82
+ readonly ratePerMinute?: number | undefined;
83
+ readonly dryRun?: boolean | undefined;
84
+ readonly journal?: Journal | undefined;
85
+ readonly signal?: AbortSignal | undefined;
86
+ readonly onProgress?: ((progress: BatchProgress) => void) | undefined;
87
+ readonly recoveryDelayMs?: number | undefined;
88
+ }
89
+ export declare function runBatch(api: BatchApi, options: RunBatchOptions): Promise<BatchResult>;
90
+ //#endregion
91
+ export type { BatchApi, BatchDocument, BatchItem, BatchItemResult, BatchItemStatus, BatchProgress, BatchResult, BillingInterval, BillingPeriod, BillingPeriodOptions, RunBatchOptions };
@@ -0,0 +1,91 @@
1
+ import { t as DateInput } from "../dates-9zvESP0i.js";
2
+ import { M as CreateInvoiceInput } from "../context-kb33TgzE.js";
3
+ import { t as CreateReceiptInput } from "../types-D1zDOH_F.js";
4
+ import { a as ReceiptsApi, t as InvoicesApi } from "../client-DeWWQ-RZ.js";
5
+ import { n as Journal } from "../journal-Y9qrIB8G.js";
6
+ //#region src/batch/recurring.d.ts
7
+ type BillingInterval = "week" | "month" | "quarter" | "year";
8
+ interface BillingPeriodOptions {
9
+ readonly interval: BillingInterval;
10
+ readonly anchor: DateInput;
11
+ readonly paymentDueInDays?: number | undefined;
12
+ }
13
+ interface BillingPeriod {
14
+ readonly index: number;
15
+ readonly interval: BillingInterval;
16
+ readonly start: string;
17
+ readonly end: string;
18
+ readonly dueDate?: string | undefined;
19
+ readonly key: string;
20
+ }
21
+ export declare function billingPeriod(options: BillingPeriodOptions, index: number): BillingPeriod;
22
+ export declare function billingPeriodAt(options: BillingPeriodOptions, date: DateInput): BillingPeriod;
23
+ export declare function billingPeriodsBetween(options: BillingPeriodOptions, from: DateInput, to: DateInput): BillingPeriod[];
24
+ export declare function todayPeriod(options: BillingPeriodOptions, now?: Date): BillingPeriod;
25
+ //#endregion
26
+ //#region src/batch/run.d.ts
27
+ export declare const MAX_BATCH_CONCURRENCY = 4;
28
+ export declare const DEFAULT_BATCH_RATE_PER_MINUTE = 30;
29
+ type BatchDocument = {
30
+ readonly kind: "invoice";
31
+ readonly input: CreateInvoiceInput;
32
+ } | {
33
+ readonly kind: "receipt";
34
+ readonly input: CreateReceiptInput;
35
+ };
36
+ interface BatchItem {
37
+ readonly key: string;
38
+ readonly document: () => BatchDocument | Promise<BatchDocument>;
39
+ }
40
+ interface BatchProgress {
41
+ readonly total: number;
42
+ readonly done: number;
43
+ readonly created: number;
44
+ readonly existing: number;
45
+ readonly previewed: number;
46
+ readonly failed: number;
47
+ readonly skipped: number;
48
+ readonly key: string;
49
+ readonly status: BatchItemStatus;
50
+ }
51
+ type BatchItemStatus = "created" | "existing" | "previewed" | "failed" | "skipped";
52
+ interface BatchItemResult {
53
+ readonly key: string;
54
+ readonly status: BatchItemStatus;
55
+ readonly kind?: "invoice" | "receipt" | undefined;
56
+ readonly number?: string | undefined;
57
+ readonly grossTotal?: number | undefined;
58
+ readonly error?: unknown;
59
+ readonly reason?: string | undefined;
60
+ }
61
+ interface BatchResult {
62
+ readonly dryRun: boolean;
63
+ readonly items: readonly BatchItemResult[];
64
+ readonly created: readonly BatchItemResult[];
65
+ readonly existing: readonly BatchItemResult[];
66
+ readonly previewed: readonly BatchItemResult[];
67
+ readonly failed: readonly BatchItemResult[];
68
+ readonly skipped: readonly BatchItemResult[];
69
+ readonly stopped?: {
70
+ readonly reason: string;
71
+ readonly error?: unknown;
72
+ } | undefined;
73
+ readonly grossTotal: number;
74
+ }
75
+ interface BatchApi {
76
+ readonly invoices: Pick<InvoicesApi, "createOnce" | "preview">;
77
+ readonly receipts: Pick<ReceiptsApi, "createOnce">;
78
+ }
79
+ interface RunBatchOptions {
80
+ readonly items: readonly BatchItem[];
81
+ readonly concurrency?: number | undefined;
82
+ readonly ratePerMinute?: number | undefined;
83
+ readonly dryRun?: boolean | undefined;
84
+ readonly journal?: Journal | undefined;
85
+ readonly signal?: AbortSignal | undefined;
86
+ readonly onProgress?: ((progress: BatchProgress) => void) | undefined;
87
+ readonly recoveryDelayMs?: number | undefined;
88
+ }
89
+ export declare function runBatch(api: BatchApi, options: RunBatchOptions): Promise<BatchResult>;
90
+ //#endregion
91
+ export type { BatchApi, BatchDocument, BatchItem, BatchItemResult, BatchItemStatus, BatchProgress, BatchResult, BillingInterval, BillingPeriod, BillingPeriodOptions, RunBatchOptions };