kassza 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/agents/README.md +59 -0
  4. package/agents/api.md +206 -0
  5. package/agents/pitfalls.md +52 -0
  6. package/agents/recipes.md +217 -0
  7. package/agents/skills/kassza/SKILL.md +29 -0
  8. package/assets/logo-wordmark.svg +25 -0
  9. package/assets/logo.svg +19 -0
  10. package/dist/binary-B89HM03_.cjs +63 -0
  11. package/dist/binary-D0M9U2MD.js +34 -0
  12. package/dist/client-B9kbKKBf.d.cts +748 -0
  13. package/dist/client-zv4enyq7.d.ts +748 -0
  14. package/dist/cookie-stores/index.cjs +153 -0
  15. package/dist/cookie-stores/index.d.cts +63 -0
  16. package/dist/cookie-stores/index.d.ts +63 -0
  17. package/dist/cookie-stores/index.js +144 -0
  18. package/dist/create-BZd6CUO7.cjs +1160 -0
  19. package/dist/create-DDZaNlOz.js +939 -0
  20. package/dist/dates-D2XebOIg.js +34 -0
  21. package/dist/dates-DtHzwNU6.d.cts +7 -0
  22. package/dist/dates-DtHzwNU6.d.ts +7 -0
  23. package/dist/dates-PPxH0n5H.cjs +57 -0
  24. package/dist/errors-B0QJhUW1.cjs +302 -0
  25. package/dist/errors-DapdV5DK.js +273 -0
  26. package/dist/index-CYAGCdKJ.d.cts +56 -0
  27. package/dist/index-CYAGCdKJ.d.ts +56 -0
  28. package/dist/index.cjs +1406 -0
  29. package/dist/index.d.cts +8 -0
  30. package/dist/index.d.ts +8 -0
  31. package/dist/index.js +1394 -0
  32. package/dist/ipn/index.cjs +108 -0
  33. package/dist/ipn/index.d.cts +32 -0
  34. package/dist/ipn/index.d.ts +32 -0
  35. package/dist/ipn/index.js +101 -0
  36. package/dist/money/index.cjs +15 -0
  37. package/dist/money/index.d.cts +2 -0
  38. package/dist/money/index.d.ts +2 -0
  39. package/dist/money/index.js +2 -0
  40. package/dist/money-C9j7nBNP.js +258 -0
  41. package/dist/money-DkiGukAw.cjs +335 -0
  42. package/dist/session-CRGOuz-R.cjs +100 -0
  43. package/dist/session-Dm_45x8K.d.cts +11 -0
  44. package/dist/session-Dm_45x8K.d.ts +11 -0
  45. package/dist/session-OJMLGufT.js +77 -0
  46. package/dist/shared-CrxlxI8n.cjs +207 -0
  47. package/dist/shared-D6DLa4b7.js +100 -0
  48. package/dist/storage/fs.cjs +88 -0
  49. package/dist/storage/fs.d.cts +13 -0
  50. package/dist/storage/fs.d.ts +13 -0
  51. package/dist/storage/fs.js +87 -0
  52. package/dist/storage/index.cjs +643 -0
  53. package/dist/storage/index.d.cts +288 -0
  54. package/dist/storage/index.d.ts +288 -0
  55. package/dist/storage/index.js +619 -0
  56. package/dist/testing/index.cjs +409 -0
  57. package/dist/testing/index.d.cts +35 -0
  58. package/dist/testing/index.d.ts +35 -0
  59. package/dist/testing/index.js +407 -0
  60. package/dist/types-DVRgwcqg.d.cts +26 -0
  61. package/dist/types-DVRgwcqg.d.ts +26 -0
  62. package/dist/validators/index.cjs +296 -0
  63. package/dist/validators/index.d.cts +51 -0
  64. package/dist/validators/index.d.ts +51 -0
  65. package/dist/validators/index.js +278 -0
  66. package/llms.txt +16 -0
  67. package/package.json +151 -0
@@ -0,0 +1,108 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors-B0QJhUW1.cjs");
3
+ //#region src/ipn/ip.ts
4
+ const SZAMLAZZ_OUTBOUND_IPS = [
5
+ "3.73.214.98",
6
+ "3.76.149.232",
7
+ "18.153.156.51"
8
+ ];
9
+ const OUTBOUND_IP_SET = new Set(SZAMLAZZ_OUTBOUND_IPS);
10
+ function normalizeIp(value) {
11
+ const comma = value.indexOf(",");
12
+ let ip = (comma === -1 ? value : value.slice(0, comma)).trim();
13
+ const bracketed = /^\[([^\]]+)\](?::\d+)?$/.exec(ip);
14
+ if (bracketed?.[1]) ip = bracketed[1];
15
+ const ipv4WithPort = /^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/.exec(ip);
16
+ if (ipv4WithPort?.[1]) ip = ipv4WithPort[1];
17
+ return ip.replace(/^::ffff:/i, "");
18
+ }
19
+ function isSzamlazzIp(ip) {
20
+ if (typeof ip !== "string") return false;
21
+ return OUTBOUND_IP_SET.has(normalizeIp(ip));
22
+ }
23
+ //#endregion
24
+ //#region src/ipn/notification.ts
25
+ const IPN_FIELDS = {
26
+ invoiceNumber: "szlahu_szamlaszam",
27
+ proformaNumber: "szlahu_dijbekero_szama",
28
+ orderNumber: "szlahu_rendelesszam",
29
+ grossTotal: "szlahu_bruttovegosszeg",
30
+ paidAmount: "szlahu_kifizetettbrutto",
31
+ paymentMethod: "szlahu_fizetesmod",
32
+ paymentDate: "szlahu_kifizdat"
33
+ };
34
+ const AMOUNT_TOLERANCE = .005;
35
+ function ipnError(message) {
36
+ return new require_errors.SzamlazzError(message, { category: "validation" });
37
+ }
38
+ function toRecord(input) {
39
+ if (typeof input === "string") return Object.fromEntries(new URLSearchParams(input.trim().replace(/^\?/, "")));
40
+ if (input instanceof URLSearchParams) return Object.fromEntries(input);
41
+ if (typeof FormData !== "undefined" && input instanceof FormData) return Object.fromEntries([...input.entries()].filter((entry) => typeof entry[1] === "string"));
42
+ if (input !== null && typeof input === "object") return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => [key, String(value)]));
43
+ throw ipnError("Az IPN értesítés nem értelmezhető: szöveget, URLSearchParams-t, FormData-t vagy objektumot várunk.");
44
+ }
45
+ function optionalField(raw, name) {
46
+ const value = raw[name]?.trim();
47
+ return value === void 0 || value === "" ? void 0 : value;
48
+ }
49
+ function requiredField(raw, name) {
50
+ const value = optionalField(raw, name);
51
+ if (value === void 0) throw ipnError(`Hiányzik a kötelező IPN mező: ${name}`);
52
+ return value;
53
+ }
54
+ function parseIpnAmount(value) {
55
+ let text = value.replace(/\s+/g, "");
56
+ const lastComma = text.lastIndexOf(",");
57
+ const lastDot = text.lastIndexOf(".");
58
+ if (lastComma !== -1 && lastDot !== -1) {
59
+ const decimal = lastComma > lastDot ? "," : ".";
60
+ const thousands = decimal === "," ? "." : ",";
61
+ text = text.split(thousands).join("").replace(decimal, ".");
62
+ } else if (lastComma !== -1) text = text.replace(",", ".");
63
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) return void 0;
64
+ return Number(text);
65
+ }
66
+ function requiredAmount(raw, name) {
67
+ const value = requiredField(raw, name);
68
+ const amount = parseIpnAmount(value);
69
+ if (amount === void 0) throw ipnError(`Az IPN mező nem értelmezhető összeg: ${name}="${value}"`);
70
+ return amount;
71
+ }
72
+ function parseIpnNotification(input) {
73
+ const raw = toRecord(input);
74
+ const invoiceNumber = requiredField(raw, IPN_FIELDS.invoiceNumber);
75
+ const grossTotal = requiredAmount(raw, IPN_FIELDS.grossTotal);
76
+ const paidAmount = requiredAmount(raw, IPN_FIELDS.paidAmount);
77
+ return {
78
+ invoiceNumber,
79
+ proformaNumber: optionalField(raw, IPN_FIELDS.proformaNumber),
80
+ orderNumber: optionalField(raw, IPN_FIELDS.orderNumber),
81
+ grossTotal,
82
+ paidAmount,
83
+ paymentMethod: optionalField(raw, IPN_FIELDS.paymentMethod),
84
+ paymentDate: optionalField(raw, IPN_FIELDS.paymentDate),
85
+ isFullyPaid: Math.abs(paidAmount) + AMOUNT_TOLERANCE >= Math.abs(grossTotal),
86
+ raw
87
+ };
88
+ }
89
+ async function readIpnNotification(request) {
90
+ if ((request.headers.get("content-type")?.toLowerCase() ?? "").includes("multipart/form-data")) return parseIpnNotification(await request.formData());
91
+ const body = await request.text();
92
+ if (body.trim() === "") return parseIpnNotification(new URL(request.url).searchParams);
93
+ return parseIpnNotification(body);
94
+ }
95
+ function ipnOkResponse() {
96
+ return new Response("OK", {
97
+ status: 200,
98
+ headers: { "content-type": "text/plain; charset=utf-8" }
99
+ });
100
+ }
101
+ //#endregion
102
+ exports.IPN_FIELDS = IPN_FIELDS;
103
+ exports.SZAMLAZZ_OUTBOUND_IPS = SZAMLAZZ_OUTBOUND_IPS;
104
+ exports.ipnOkResponse = ipnOkResponse;
105
+ exports.isSzamlazzIp = isSzamlazzIp;
106
+ exports.parseIpnAmount = parseIpnAmount;
107
+ exports.parseIpnNotification = parseIpnNotification;
108
+ exports.readIpnNotification = readIpnNotification;
@@ -0,0 +1,32 @@
1
+ //#region src/ipn/ip.d.ts
2
+ export declare const SZAMLAZZ_OUTBOUND_IPS: readonly string[];
3
+ export declare function isSzamlazzIp(ip: string): boolean;
4
+ //#endregion
5
+ //#region src/ipn/notification.d.ts
6
+ type IpnInput = string | URLSearchParams | FormData | Readonly<Record<string, string>>;
7
+ interface IpnNotification {
8
+ readonly invoiceNumber: string;
9
+ readonly proformaNumber?: string | undefined;
10
+ readonly orderNumber?: string | undefined;
11
+ readonly grossTotal: number;
12
+ readonly paidAmount: number;
13
+ readonly paymentMethod?: string | undefined;
14
+ readonly paymentDate?: string | undefined;
15
+ readonly isFullyPaid: boolean;
16
+ readonly raw: Readonly<Record<string, string>>;
17
+ }
18
+ export declare const IPN_FIELDS: {
19
+ readonly invoiceNumber: "szlahu_szamlaszam";
20
+ readonly proformaNumber: "szlahu_dijbekero_szama";
21
+ readonly orderNumber: "szlahu_rendelesszam";
22
+ readonly grossTotal: "szlahu_bruttovegosszeg";
23
+ readonly paidAmount: "szlahu_kifizetettbrutto";
24
+ readonly paymentMethod: "szlahu_fizetesmod";
25
+ readonly paymentDate: "szlahu_kifizdat";
26
+ };
27
+ export declare function parseIpnAmount(value: string): number | undefined;
28
+ export declare function parseIpnNotification(input: IpnInput): IpnNotification;
29
+ export declare function readIpnNotification(request: Request): Promise<IpnNotification>;
30
+ export declare function ipnOkResponse(): Response;
31
+ //#endregion
32
+ export type { IpnInput, IpnNotification };
@@ -0,0 +1,32 @@
1
+ //#region src/ipn/ip.d.ts
2
+ export declare const SZAMLAZZ_OUTBOUND_IPS: readonly string[];
3
+ export declare function isSzamlazzIp(ip: string): boolean;
4
+ //#endregion
5
+ //#region src/ipn/notification.d.ts
6
+ type IpnInput = string | URLSearchParams | FormData | Readonly<Record<string, string>>;
7
+ interface IpnNotification {
8
+ readonly invoiceNumber: string;
9
+ readonly proformaNumber?: string | undefined;
10
+ readonly orderNumber?: string | undefined;
11
+ readonly grossTotal: number;
12
+ readonly paidAmount: number;
13
+ readonly paymentMethod?: string | undefined;
14
+ readonly paymentDate?: string | undefined;
15
+ readonly isFullyPaid: boolean;
16
+ readonly raw: Readonly<Record<string, string>>;
17
+ }
18
+ export declare const IPN_FIELDS: {
19
+ readonly invoiceNumber: "szlahu_szamlaszam";
20
+ readonly proformaNumber: "szlahu_dijbekero_szama";
21
+ readonly orderNumber: "szlahu_rendelesszam";
22
+ readonly grossTotal: "szlahu_bruttovegosszeg";
23
+ readonly paidAmount: "szlahu_kifizetettbrutto";
24
+ readonly paymentMethod: "szlahu_fizetesmod";
25
+ readonly paymentDate: "szlahu_kifizdat";
26
+ };
27
+ export declare function parseIpnAmount(value: string): number | undefined;
28
+ export declare function parseIpnNotification(input: IpnInput): IpnNotification;
29
+ export declare function readIpnNotification(request: Request): Promise<IpnNotification>;
30
+ export declare function ipnOkResponse(): Response;
31
+ //#endregion
32
+ export type { IpnInput, IpnNotification };
@@ -0,0 +1,101 @@
1
+ import { n as SzamlazzError } from "../errors-DapdV5DK.js";
2
+ //#region src/ipn/ip.ts
3
+ const SZAMLAZZ_OUTBOUND_IPS = [
4
+ "3.73.214.98",
5
+ "3.76.149.232",
6
+ "18.153.156.51"
7
+ ];
8
+ const OUTBOUND_IP_SET = new Set(SZAMLAZZ_OUTBOUND_IPS);
9
+ function normalizeIp(value) {
10
+ const comma = value.indexOf(",");
11
+ let ip = (comma === -1 ? value : value.slice(0, comma)).trim();
12
+ const bracketed = /^\[([^\]]+)\](?::\d+)?$/.exec(ip);
13
+ if (bracketed?.[1]) ip = bracketed[1];
14
+ const ipv4WithPort = /^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/.exec(ip);
15
+ if (ipv4WithPort?.[1]) ip = ipv4WithPort[1];
16
+ return ip.replace(/^::ffff:/i, "");
17
+ }
18
+ function isSzamlazzIp(ip) {
19
+ if (typeof ip !== "string") return false;
20
+ return OUTBOUND_IP_SET.has(normalizeIp(ip));
21
+ }
22
+ //#endregion
23
+ //#region src/ipn/notification.ts
24
+ const IPN_FIELDS = {
25
+ invoiceNumber: "szlahu_szamlaszam",
26
+ proformaNumber: "szlahu_dijbekero_szama",
27
+ orderNumber: "szlahu_rendelesszam",
28
+ grossTotal: "szlahu_bruttovegosszeg",
29
+ paidAmount: "szlahu_kifizetettbrutto",
30
+ paymentMethod: "szlahu_fizetesmod",
31
+ paymentDate: "szlahu_kifizdat"
32
+ };
33
+ const AMOUNT_TOLERANCE = .005;
34
+ function ipnError(message) {
35
+ return new SzamlazzError(message, { category: "validation" });
36
+ }
37
+ function toRecord(input) {
38
+ if (typeof input === "string") return Object.fromEntries(new URLSearchParams(input.trim().replace(/^\?/, "")));
39
+ if (input instanceof URLSearchParams) return Object.fromEntries(input);
40
+ if (typeof FormData !== "undefined" && input instanceof FormData) return Object.fromEntries([...input.entries()].filter((entry) => typeof entry[1] === "string"));
41
+ if (input !== null && typeof input === "object") return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => [key, String(value)]));
42
+ throw ipnError("Az IPN értesítés nem értelmezhető: szöveget, URLSearchParams-t, FormData-t vagy objektumot várunk.");
43
+ }
44
+ function optionalField(raw, name) {
45
+ const value = raw[name]?.trim();
46
+ return value === void 0 || value === "" ? void 0 : value;
47
+ }
48
+ function requiredField(raw, name) {
49
+ const value = optionalField(raw, name);
50
+ if (value === void 0) throw ipnError(`Hiányzik a kötelező IPN mező: ${name}`);
51
+ return value;
52
+ }
53
+ function parseIpnAmount(value) {
54
+ let text = value.replace(/\s+/g, "");
55
+ const lastComma = text.lastIndexOf(",");
56
+ const lastDot = text.lastIndexOf(".");
57
+ if (lastComma !== -1 && lastDot !== -1) {
58
+ const decimal = lastComma > lastDot ? "," : ".";
59
+ const thousands = decimal === "," ? "." : ",";
60
+ text = text.split(thousands).join("").replace(decimal, ".");
61
+ } else if (lastComma !== -1) text = text.replace(",", ".");
62
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text)) return void 0;
63
+ return Number(text);
64
+ }
65
+ function requiredAmount(raw, name) {
66
+ const value = requiredField(raw, name);
67
+ const amount = parseIpnAmount(value);
68
+ if (amount === void 0) throw ipnError(`Az IPN mező nem értelmezhető összeg: ${name}="${value}"`);
69
+ return amount;
70
+ }
71
+ function parseIpnNotification(input) {
72
+ const raw = toRecord(input);
73
+ const invoiceNumber = requiredField(raw, IPN_FIELDS.invoiceNumber);
74
+ const grossTotal = requiredAmount(raw, IPN_FIELDS.grossTotal);
75
+ const paidAmount = requiredAmount(raw, IPN_FIELDS.paidAmount);
76
+ return {
77
+ invoiceNumber,
78
+ proformaNumber: optionalField(raw, IPN_FIELDS.proformaNumber),
79
+ orderNumber: optionalField(raw, IPN_FIELDS.orderNumber),
80
+ grossTotal,
81
+ paidAmount,
82
+ paymentMethod: optionalField(raw, IPN_FIELDS.paymentMethod),
83
+ paymentDate: optionalField(raw, IPN_FIELDS.paymentDate),
84
+ isFullyPaid: Math.abs(paidAmount) + AMOUNT_TOLERANCE >= Math.abs(grossTotal),
85
+ raw
86
+ };
87
+ }
88
+ async function readIpnNotification(request) {
89
+ if ((request.headers.get("content-type")?.toLowerCase() ?? "").includes("multipart/form-data")) return parseIpnNotification(await request.formData());
90
+ const body = await request.text();
91
+ if (body.trim() === "") return parseIpnNotification(new URL(request.url).searchParams);
92
+ return parseIpnNotification(body);
93
+ }
94
+ function ipnOkResponse() {
95
+ return new Response("OK", {
96
+ status: 200,
97
+ headers: { "content-type": "text/plain; charset=utf-8" }
98
+ });
99
+ }
100
+ //#endregion
101
+ export { IPN_FIELDS, SZAMLAZZ_OUTBOUND_IPS, ipnOkResponse, isSzamlazzIp, parseIpnAmount, parseIpnNotification, readIpnNotification };
@@ -0,0 +1,15 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_money = require("../money-DkiGukAw.cjs");
3
+ exports.NUMERIC_VAT_RATES = require_money.NUMERIC_VAT_RATES;
4
+ exports.SPECIAL_VAT_CODES = require_money.SPECIAL_VAT_CODES;
5
+ exports.addMoney = require_money.addMoney;
6
+ exports.calculateInvoiceItem = require_money.calculateInvoiceItem;
7
+ exports.calculateItemAmounts = require_money.calculateItemAmounts;
8
+ exports.calculateReceiptItem = require_money.calculateReceiptItem;
9
+ exports.decimalPlaces = require_money.decimalPlaces;
10
+ exports.formatVatRate = require_money.formatVatRate;
11
+ exports.isHuf = require_money.isHuf;
12
+ exports.isVatRate = require_money.isVatRate;
13
+ exports.roundMoney = require_money.roundMoney;
14
+ exports.summarizeItems = require_money.summarizeItems;
15
+ exports.vatPercentage = require_money.vatPercentage;
@@ -0,0 +1,2 @@
1
+ import { _ as VatRate, a as DocumentTotals, b as isVatRate, c as VatBreakdown, d as calculateReceiptItem, f as summarizeItems, g as SpecialVatCode, h as SPECIAL_VAT_CODES, i as DocumentKind, l as calculateInvoiceItem, m as NumericVatRate, n as decimalPlaces, o as ItemAmounts, p as NUMERIC_VAT_RATES, r as roundMoney, s as ItemPriceInput, t as addMoney, u as calculateItemAmounts, v as formatVatRate, x as vatPercentage, y as isHuf } from "../index-CYAGCdKJ.cjs";
2
+ export { type DocumentKind, type DocumentTotals, type ItemAmounts, type ItemPriceInput, NUMERIC_VAT_RATES, type NumericVatRate, SPECIAL_VAT_CODES, type SpecialVatCode, type VatBreakdown, type VatRate, addMoney, calculateInvoiceItem, calculateItemAmounts, calculateReceiptItem, decimalPlaces, formatVatRate, isHuf, isVatRate, roundMoney, summarizeItems, vatPercentage };
@@ -0,0 +1,2 @@
1
+ import { _ as VatRate, a as DocumentTotals, b as isVatRate, c as VatBreakdown, d as calculateReceiptItem, f as summarizeItems, g as SpecialVatCode, h as SPECIAL_VAT_CODES, i as DocumentKind, l as calculateInvoiceItem, m as NumericVatRate, n as decimalPlaces, o as ItemAmounts, p as NUMERIC_VAT_RATES, r as roundMoney, s as ItemPriceInput, t as addMoney, u as calculateItemAmounts, v as formatVatRate, x as vatPercentage, y as isHuf } from "../index-CYAGCdKJ.js";
2
+ export { type DocumentKind, type DocumentTotals, type ItemAmounts, type ItemPriceInput, NUMERIC_VAT_RATES, type NumericVatRate, SPECIAL_VAT_CODES, type SpecialVatCode, type VatBreakdown, type VatRate, addMoney, calculateInvoiceItem, calculateItemAmounts, calculateReceiptItem, decimalPlaces, formatVatRate, isHuf, isVatRate, roundMoney, summarizeItems, vatPercentage };
@@ -0,0 +1,2 @@
1
+ import { a as NUMERIC_VAT_RATES, c as isHuf, d as addMoney, f as decimalPlaces, i as summarizeItems, l as isVatRate, n as calculateItemAmounts, o as SPECIAL_VAT_CODES, p as roundMoney, r as calculateReceiptItem, s as formatVatRate, t as calculateInvoiceItem, u as vatPercentage } from "../money-C9j7nBNP.js";
2
+ export { NUMERIC_VAT_RATES, SPECIAL_VAT_CODES, addMoney, calculateInvoiceItem, calculateItemAmounts, calculateReceiptItem, decimalPlaces, formatVatRate, isHuf, isVatRate, roundMoney, summarizeItems, vatPercentage };
@@ -0,0 +1,258 @@
1
+ import { n as SzamlazzError } from "./errors-DapdV5DK.js";
2
+ //#region src/money/rounding.ts
3
+ function roundMoney(value, decimals = 0) {
4
+ if (!Number.isFinite(value)) throw new RangeError(`Nem véges összeg: ${value}`);
5
+ const factor = 10 ** decimals;
6
+ const scaled = Number((Math.abs(value) * factor).toPrecision(15));
7
+ const rounded = Math.sign(value) * Math.round(scaled) / factor;
8
+ return rounded === 0 ? 0 : rounded;
9
+ }
10
+ function decimalPlaces(value) {
11
+ if (!Number.isFinite(value) || Number.isInteger(value)) return 0;
12
+ const normalized = Number(value.toPrecision(15)).toString();
13
+ const exponentMatch = /e-(\d+)$/.exec(normalized);
14
+ if (exponentMatch?.[1]) return Number(exponentMatch[1]);
15
+ return normalized.split(".")[1]?.length ?? 0;
16
+ }
17
+ function addMoney(...values) {
18
+ return roundMoney(values.reduce((sum, value) => sum + value, 0), Math.max(0, ...values.map(decimalPlaces)));
19
+ }
20
+ //#endregion
21
+ //#region src/money/vat.ts
22
+ const NUMERIC_VAT_RATES = [
23
+ 0,
24
+ 1,
25
+ 2,
26
+ 2.1,
27
+ 3,
28
+ 4,
29
+ 4.8,
30
+ 5,
31
+ 5.5,
32
+ 6,
33
+ 7,
34
+ 7.7,
35
+ 8,
36
+ 8.1,
37
+ 9,
38
+ 9.5,
39
+ 10,
40
+ 11,
41
+ 12,
42
+ 13,
43
+ 13.5,
44
+ 14,
45
+ 15,
46
+ 16,
47
+ 17,
48
+ 18,
49
+ 19,
50
+ 20,
51
+ 21,
52
+ 22,
53
+ 23,
54
+ 24,
55
+ 25,
56
+ 25.5,
57
+ 26,
58
+ 27
59
+ ];
60
+ const SPECIAL_VAT_CODES = [
61
+ "TAHK",
62
+ "TAM",
63
+ "AAM",
64
+ "EUT",
65
+ "EUKT",
66
+ "F.AFA",
67
+ "K.AFA",
68
+ "HO",
69
+ "EUE",
70
+ "EUFADE",
71
+ "EUFAD37",
72
+ "ATK",
73
+ "NAM",
74
+ "EAM",
75
+ "KBAUK",
76
+ "KBAET"
77
+ ];
78
+ const numericRates = new Set(NUMERIC_VAT_RATES);
79
+ const specialCodes = new Set(SPECIAL_VAT_CODES);
80
+ function isVatRate(value) {
81
+ if (typeof value === "number") return numericRates.has(value);
82
+ return typeof value === "string" && specialCodes.has(value);
83
+ }
84
+ function vatPercentage(rate) {
85
+ return typeof rate === "number" ? rate : 0;
86
+ }
87
+ function formatVatRate(rate) {
88
+ return String(rate);
89
+ }
90
+ const HUF_CURRENCIES = /* @__PURE__ */ new Set([
91
+ "HUF",
92
+ "Ft",
93
+ "FT",
94
+ "huf",
95
+ "ft"
96
+ ]);
97
+ function isHuf(currency) {
98
+ return currency === void 0 || HUF_CURRENCIES.has(currency.trim());
99
+ }
100
+ //#endregion
101
+ //#region src/money/items.ts
102
+ const FOREIGN_CURRENCY_DECIMALS = 2;
103
+ const RECEIPT_HUF_DECIMALS = 2;
104
+ const MAX_UNIT_PRICE_DECIMALS = 6;
105
+ function validationError(message) {
106
+ return new SzamlazzError(message, { category: "validation" });
107
+ }
108
+ function assertFiniteNumber(value, field) {
109
+ if (value !== void 0 && !Number.isFinite(value)) throw validationError(`A(z) ${field} mező értéke nem érvényes szám: ${value}`);
110
+ }
111
+ function unitPriceFor(amount, quantity, decimals) {
112
+ const exact = amount / quantity;
113
+ for (let precision = 2; precision <= MAX_UNIT_PRICE_DECIMALS; precision++) {
114
+ const candidate = roundMoney(exact, precision);
115
+ if (roundMoney(candidate * quantity, decimals) === amount) return candidate;
116
+ }
117
+ return roundMoney(exact, MAX_UNIT_PRICE_DECIMALS);
118
+ }
119
+ function roundingRuleFor(kind, currency) {
120
+ if (!isHuf(currency)) return {
121
+ netDecimals: FOREIGN_CURRENCY_DECIMALS,
122
+ vatDecimals: FOREIGN_CURRENCY_DECIMALS,
123
+ grossDecimals: FOREIGN_CURRENCY_DECIMALS
124
+ };
125
+ if (kind === "receipt") return {
126
+ netDecimals: RECEIPT_HUF_DECIMALS,
127
+ vatDecimals: RECEIPT_HUF_DECIMALS,
128
+ grossDecimals: 0
129
+ };
130
+ return {
131
+ netDecimals: 0,
132
+ vatDecimals: 0,
133
+ grossDecimals: 0
134
+ };
135
+ }
136
+ function fromNet(input, quantity, rule) {
137
+ const percentage = vatPercentage(input.vat);
138
+ const netAmount = roundMoney(input.netUnitPrice * quantity, rule.netDecimals);
139
+ if (rule.grossDecimals < rule.netDecimals) {
140
+ const grossAmount = roundMoney(netAmount * (1 + percentage / 100), rule.grossDecimals);
141
+ const vatAmount = roundMoney(grossAmount - netAmount, rule.vatDecimals);
142
+ return {
143
+ quantity,
144
+ vat: input.vat,
145
+ netUnitPrice: input.netUnitPrice,
146
+ netAmount,
147
+ vatAmount,
148
+ grossAmount
149
+ };
150
+ }
151
+ const vatAmount = roundMoney(netAmount * percentage / 100, rule.vatDecimals);
152
+ const grossAmount = roundMoney(netAmount + vatAmount, rule.grossDecimals);
153
+ return {
154
+ quantity,
155
+ vat: input.vat,
156
+ netUnitPrice: input.netUnitPrice,
157
+ netAmount,
158
+ vatAmount,
159
+ grossAmount
160
+ };
161
+ }
162
+ function fromGross(input, quantity, rule) {
163
+ const percentage = vatPercentage(input.vat);
164
+ const grossAmount = roundMoney(input.grossUnitPrice * quantity, rule.grossDecimals);
165
+ const vatAmount = roundMoney(grossAmount * percentage / (100 + percentage), rule.vatDecimals);
166
+ const netAmount = roundMoney(grossAmount - vatAmount, rule.netDecimals);
167
+ return {
168
+ quantity,
169
+ vat: input.vat,
170
+ netUnitPrice: unitPriceFor(netAmount, quantity, rule.netDecimals),
171
+ netAmount,
172
+ vatAmount,
173
+ grossAmount
174
+ };
175
+ }
176
+ function fromExplicit(input, quantity) {
177
+ const { netAmount, vatAmount, grossAmount } = input;
178
+ if (netAmount === void 0 || vatAmount === void 0 || grossAmount === void 0) throw validationError("Explicit összegeknél a netAmount, vatAmount és grossAmount mezőt is meg kell adni.");
179
+ return {
180
+ quantity,
181
+ vat: input.vat,
182
+ netUnitPrice: input.netUnitPrice ?? unitPriceFor(netAmount, quantity, FOREIGN_CURRENCY_DECIMALS),
183
+ netAmount,
184
+ vatAmount,
185
+ grossAmount
186
+ };
187
+ }
188
+ function calculateItemAmounts(input, options) {
189
+ const quantity = input.quantity ?? 1;
190
+ assertFiniteNumber(quantity, "quantity");
191
+ assertFiniteNumber(input.netUnitPrice, "netUnitPrice");
192
+ assertFiniteNumber(input.grossUnitPrice, "grossUnitPrice");
193
+ assertFiniteNumber(input.netAmount, "netAmount");
194
+ assertFiniteNumber(input.vatAmount, "vatAmount");
195
+ assertFiniteNumber(input.grossAmount, "grossAmount");
196
+ if (quantity === 0) throw validationError("A tétel mennyisége nem lehet 0.");
197
+ if (!isVatRate(input.vat)) throw validationError(`Ismeretlen áfakulcs: ${String(input.vat)}`);
198
+ if (input.netAmount !== void 0 || input.vatAmount !== void 0 || input.grossAmount !== void 0) return fromExplicit(input, quantity);
199
+ if (input.netUnitPrice !== void 0 && input.grossUnitPrice !== void 0) throw validationError("A netUnitPrice és a grossUnitPrice közül csak az egyiket add meg.");
200
+ const rule = roundingRuleFor(options.kind, options.currency);
201
+ if (input.netUnitPrice !== void 0) return fromNet({
202
+ ...input,
203
+ netUnitPrice: input.netUnitPrice
204
+ }, quantity, rule);
205
+ if (input.grossUnitPrice !== void 0) return fromGross({
206
+ ...input,
207
+ grossUnitPrice: input.grossUnitPrice
208
+ }, quantity, rule);
209
+ throw validationError("A tételnél add meg a netUnitPrice vagy a grossUnitPrice mezőt.");
210
+ }
211
+ function calculateInvoiceItem(input, currency) {
212
+ return calculateItemAmounts(input, {
213
+ kind: "invoice",
214
+ currency
215
+ });
216
+ }
217
+ function calculateReceiptItem(input, currency) {
218
+ return calculateItemAmounts(input, {
219
+ kind: "receipt",
220
+ currency
221
+ });
222
+ }
223
+ function summarizeItems(items) {
224
+ const byVat = /* @__PURE__ */ new Map();
225
+ let netAmount = 0;
226
+ let vatAmount = 0;
227
+ let grossAmount = 0;
228
+ for (const item of items) {
229
+ netAmount += item.netAmount;
230
+ vatAmount += item.vatAmount;
231
+ grossAmount += item.grossAmount;
232
+ const key = String(item.vat);
233
+ const bucket = byVat.get(key) ?? {
234
+ vat: item.vat,
235
+ netAmount: 0,
236
+ vatAmount: 0,
237
+ grossAmount: 0
238
+ };
239
+ bucket.netAmount += item.netAmount;
240
+ bucket.vatAmount += item.vatAmount;
241
+ bucket.grossAmount += item.grossAmount;
242
+ byVat.set(key, bucket);
243
+ }
244
+ const round = (value) => roundMoney(value, 6);
245
+ return {
246
+ netAmount: round(netAmount),
247
+ vatAmount: round(vatAmount),
248
+ grossAmount: round(grossAmount),
249
+ byVat: [...byVat.values()].map((bucket) => ({
250
+ vat: bucket.vat,
251
+ netAmount: round(bucket.netAmount),
252
+ vatAmount: round(bucket.vatAmount),
253
+ grossAmount: round(bucket.grossAmount)
254
+ }))
255
+ };
256
+ }
257
+ //#endregion
258
+ export { NUMERIC_VAT_RATES as a, isHuf as c, addMoney as d, decimalPlaces as f, summarizeItems as i, isVatRate as l, calculateItemAmounts as n, SPECIAL_VAT_CODES as o, roundMoney as p, calculateReceiptItem as r, formatVatRate as s, calculateInvoiceItem as t, vatPercentage as u };