ceylon-devkit 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,570 @@
1
+ // src/currency/index.ts
2
+ var EN_ONES = [
3
+ "",
4
+ "One",
5
+ "Two",
6
+ "Three",
7
+ "Four",
8
+ "Five",
9
+ "Six",
10
+ "Seven",
11
+ "Eight",
12
+ "Nine",
13
+ "Ten",
14
+ "Eleven",
15
+ "Twelve",
16
+ "Thirteen",
17
+ "Fourteen",
18
+ "Fifteen",
19
+ "Sixteen",
20
+ "Seventeen",
21
+ "Eighteen",
22
+ "Nineteen"
23
+ ];
24
+ var EN_TENS = [
25
+ "",
26
+ "",
27
+ "Twenty",
28
+ "Thirty",
29
+ "Forty",
30
+ "Fifty",
31
+ "Sixty",
32
+ "Seventy",
33
+ "Eighty",
34
+ "Ninety"
35
+ ];
36
+ function convertThreeDigitEnglish(num) {
37
+ let str = "";
38
+ const hundred = Math.floor(num / 100);
39
+ const remainder = num % 100;
40
+ if (hundred > 0) {
41
+ str += `${EN_ONES[hundred]} Hundred`;
42
+ if (remainder > 0) str += " ";
43
+ }
44
+ if (remainder > 0) {
45
+ if (remainder < 20) {
46
+ str += EN_ONES[remainder];
47
+ } else {
48
+ const ten = Math.floor(remainder / 10);
49
+ const one = remainder % 10;
50
+ str += EN_TENS[ten];
51
+ if (one > 0) str += ` ${EN_ONES[one]}`;
52
+ }
53
+ }
54
+ return str.trim();
55
+ }
56
+ function integerToEnglishWords(num) {
57
+ if (num === 0) return "Zero";
58
+ const parts = [];
59
+ const crores = Math.floor(num / 1e7);
60
+ num %= 1e7;
61
+ if (crores > 0) parts.push(`${integerToEnglishWords(crores)} Crore`);
62
+ const lakhs = Math.floor(num / 1e5);
63
+ num %= 1e5;
64
+ if (lakhs > 0) parts.push(`${convertThreeDigitEnglish(lakhs)} Lakh`);
65
+ const thousands = Math.floor(num / 1e3);
66
+ num %= 1e3;
67
+ if (thousands > 0) parts.push(`${convertThreeDigitEnglish(thousands)} Thousand`);
68
+ if (num > 0) parts.push(convertThreeDigitEnglish(num));
69
+ return parts.join(" ");
70
+ }
71
+ var SI_ONES = [
72
+ "",
73
+ "\u0D91\u0D9A",
74
+ "\u0DAF\u0DD9\u0D9A",
75
+ "\u0DAD\u0DD4\u0DB1",
76
+ "\u0DC4\u0DAD\u0DBB",
77
+ "\u0DB4\u0DC4",
78
+ "\u0DC4\u0DBA",
79
+ "\u0DC4\u0DAD",
80
+ "\u0D85\u0DA7",
81
+ "\u0DB1\u0DC0\u0DBA",
82
+ "\u0DAF\u0DC4\u0DBA",
83
+ "\u0D91\u0D9A\u0DDC\u0DC5\u0DC4",
84
+ "\u0DAF\u0DDC\u0DC5\u0DC4",
85
+ "\u0DAF\u0DC4\u0DAD\u0DD4\u0DB1",
86
+ "\u0DAF\u0DC4\u0DC4\u0DAD\u0DBB",
87
+ "\u0DB4\u0DC4\u0DC5\u0DDC\u0DC0",
88
+ "\u0DAF\u0DC4\u0DC3\u0DBA",
89
+ "\u0DAF\u0DCF\u0DC4\u0DAD",
90
+ "\u0DAF\u0DC4\u0D85\u0DA7",
91
+ "\u0DAF\u0DC4\u0DB1\u0DC0\u0DBA"
92
+ ];
93
+ var SI_ONES_SUFFIX = [
94
+ "",
95
+ "\u0D91\u0D9A\u0DBA\u0DD2",
96
+ "\u0DAF\u0DD9\u0D9A\u0DBA\u0DD2",
97
+ "\u0DAD\u0DD4\u0DB1\u0DBA\u0DD2",
98
+ "\u0DC4\u0DAD\u0DBB\u0DBA\u0DD2",
99
+ "\u0DB4\u0DC4\u0DBA\u0DD2",
100
+ "\u0DC4\u0DBA\u0DBA\u0DD2",
101
+ "\u0DC4\u0DAD\u0DBA\u0DD2",
102
+ "\u0D85\u0DA7\u0DBA\u0DD2",
103
+ "\u0DB1\u0DC0\u0DBA\u0DBA\u0DD2",
104
+ "\u0DAF\u0DC4\u0DBA\u0DBA\u0DD2",
105
+ "\u0D91\u0D9A\u0DDC\u0DC5\u0DC4\u0DBA\u0DD2",
106
+ "\u0DAF\u0DDC\u0DC5\u0DC4\u0DBA\u0DD2",
107
+ "\u0DAF\u0DC4\u0DAD\u0DD4\u0DB1\u0DBA\u0DD2",
108
+ "\u0DAF\u0DC4\u0DC4\u0DAD\u0DBB\u0DBA\u0DD2",
109
+ "\u0DB4\u0DC4\u0DC5\u0DDC\u0DC0\u0DBA\u0DD2",
110
+ "\u0DAF\u0DC4\u0DC3\u0DBA\u0DBA\u0DD2",
111
+ "\u0DAF\u0DCF\u0DC4\u0DAD\u0DBA\u0DD2",
112
+ "\u0DAF\u0DC4\u0D85\u0DA7\u0DBA\u0DD2",
113
+ "\u0DAF\u0DC4\u0DB1\u0DC0\u0DBA\u0DBA\u0DD2"
114
+ ];
115
+ var SI_TENS_PREFIX = [
116
+ "",
117
+ "",
118
+ "\u0DC0\u0DD2\u0DC3\u0DD2",
119
+ "\u0DAD\u0DD2\u0DC3\u0DCA",
120
+ "\u0DC4\u0DAD\u0DC5\u0DD2\u0DC3\u0DCA",
121
+ "\u0DB4\u0DB1\u0DC3\u0DCA",
122
+ "\u0DC4\u0DD0\u0DA7",
123
+ "\u0DC4\u0DD0\u0DAD\u0DCA\u0DAD\u0DD1",
124
+ "\u0D85\u0DC3\u0DD6",
125
+ "\u0D85\u0DB1\u0DD6"
126
+ ];
127
+ var SI_TENS_SUFFIX = [
128
+ "",
129
+ "",
130
+ "\u0DC0\u0DD2\u0DC3\u0DCA\u0DC3\u0DBA\u0DD2",
131
+ "\u0DAD\u0DD2\u0DC4\u0DBA\u0DD2",
132
+ "\u0DC4\u0DAD\u0DC5\u0DD2\u0DC4\u0DBA\u0DD2",
133
+ "\u0DB4\u0DB1\u0DC4\u0DBA\u0DD2",
134
+ "\u0DC4\u0DD0\u0DA7\u0DBA\u0DD2",
135
+ "\u0DC4\u0DD0\u0DAD\u0DCA\u0DAD\u0DD1\u0DC0\u0DBA\u0DD2",
136
+ "\u0D85\u0DC3\u0DD6\u0DC0\u0DBA\u0DD2",
137
+ "\u0D85\u0DB1\u0DD6\u0DC0\u0DBA\u0DD2"
138
+ ];
139
+ var SI_HUNDREDS = [
140
+ "",
141
+ "\u0D91\u0D9A\u0DCA\u0DC3\u0DD2\u0DBA",
142
+ "\u0DAF\u0DD9\u0DC3\u0DD2\u0DBA",
143
+ "\u0DAD\u0DD4\u0DB1\u0DCA\u0DC3\u0DD2\u0DBA",
144
+ "\u0DC4\u0DCF\u0DBB\u0DC3\u0DD2\u0DBA",
145
+ "\u0DB4\u0DB1\u0DCA\u0DC3\u0DD2\u0DBA",
146
+ "\u0DC4\u0DBA\u0DC3\u0DD2\u0DBA",
147
+ "\u0DC4\u0DAD\u0DCA\u0DC3\u0DD2\u0DBA",
148
+ "\u0D85\u0DA7\u0DC3\u0DD2\u0DBA",
149
+ "\u0DB1\u0DB8\u0DC3\u0DD2\u0DBA"
150
+ ];
151
+ function convertThreeDigitSinhala(num, isEnding) {
152
+ let str = "";
153
+ const hundred = Math.floor(num / 100);
154
+ const remainder = num % 100;
155
+ if (hundred > 0) {
156
+ if (remainder === 0 && isEnding) {
157
+ return hundred === 1 ? "\u0DC3\u0DD2\u0DBA\u0DBA\u0DBA\u0DD2" : `${SI_HUNDREDS[hundred]}\u0DBA\u0DD2`;
158
+ } else {
159
+ str += SI_HUNDREDS[hundred];
160
+ if (remainder > 0) str += " ";
161
+ }
162
+ }
163
+ if (remainder > 0) {
164
+ if (remainder < 20) {
165
+ str += isEnding ? SI_ONES_SUFFIX[remainder] : SI_ONES[remainder];
166
+ } else {
167
+ const ten = Math.floor(remainder / 10);
168
+ const one = remainder % 10;
169
+ if (one === 0) {
170
+ str += isEnding ? SI_TENS_SUFFIX[ten] : SI_TENS_PREFIX[ten];
171
+ } else {
172
+ str += `${SI_TENS_PREFIX[ten]} ${isEnding ? SI_ONES_SUFFIX[one] : SI_ONES[one]}`;
173
+ }
174
+ }
175
+ }
176
+ return str.trim();
177
+ }
178
+ function integerToSinhalaWords(num) {
179
+ if (num === 0) return "\u0DB6\u0DD2\u0D82\u0DAF\u0DD4\u0DC0\u0DBA\u0DD2";
180
+ const parts = [];
181
+ const crores = Math.floor(num / 1e7);
182
+ num %= 1e7;
183
+ if (crores > 0) {
184
+ const isEnd = num === 0;
185
+ parts.push(`${convertThreeDigitSinhala(crores, false)} ${isEnd ? "\u0D9A\u0DDD\u0DA7\u0DD2\u0DBA\u0DBA\u0DD2" : "\u0D9A\u0DDD\u0DA7\u0DD2"}`);
186
+ }
187
+ const lakhs = Math.floor(num / 1e5);
188
+ num %= 1e5;
189
+ if (lakhs > 0) {
190
+ const isEnd = num === 0;
191
+ parts.push(`${convertThreeDigitSinhala(lakhs, false)} ${isEnd ? "\u0DBD\u0D9A\u0DCA\u0DC2\u0DBA\u0DBA\u0DD2" : "\u0DBD\u0D9A\u0DCA\u0DC2"}`);
192
+ }
193
+ const thousands = Math.floor(num / 1e3);
194
+ num %= 1e3;
195
+ if (thousands > 0) {
196
+ const isEnd = num === 0;
197
+ if (thousands === 1) {
198
+ parts.push(isEnd ? "\u0D91\u0D9A\u0DCA\u0DAF\u0DC4\u0DC3\u0DBA\u0DD2" : "\u0D91\u0D9A\u0DCA\u0DAF\u0DC4\u0DC3\u0DCA");
199
+ } else {
200
+ parts.push(`${convertThreeDigitSinhala(thousands, false)} ${isEnd ? "\u0DAF\u0DC4\u0DC3\u0DBA\u0DD2" : "\u0DAF\u0DC4\u0DC3\u0DCA"}`);
201
+ }
202
+ }
203
+ if (num > 0) {
204
+ parts.push(convertThreeDigitSinhala(num, true));
205
+ }
206
+ return parts.join(" ").trim();
207
+ }
208
+ function lkrToWords(amount, options) {
209
+ if (isNaN(amount) || amount === null || amount === void 0) {
210
+ return "";
211
+ }
212
+ const lang = (options == null ? void 0 : options.lang) || "en";
213
+ const showOnly = (options == null ? void 0 : options.showOnly) ?? true;
214
+ const chequeFormat = (options == null ? void 0 : options.chequeFormat) ?? false;
215
+ const isNegative = amount < 0;
216
+ const absAmount = Math.abs(amount);
217
+ const rupees = Math.floor(absAmount);
218
+ const cents = Math.round((absAmount - rupees) * 100);
219
+ let result = "";
220
+ if (lang === "en") {
221
+ let rupeesText = "";
222
+ if (rupees === 0 && cents > 0) {
223
+ rupeesText = "";
224
+ } else if (rupees === 1) {
225
+ rupeesText = "One Rupee";
226
+ } else {
227
+ rupeesText = `${integerToEnglishWords(rupees)} Rupees`;
228
+ }
229
+ let centsText = "";
230
+ if (cents > 0) {
231
+ centsText = `${convertThreeDigitEnglish(cents)} Cent${cents === 1 ? "" : "s"}`;
232
+ }
233
+ if (rupeesText && centsText) {
234
+ result = `${rupeesText} and ${centsText}`;
235
+ } else if (rupeesText) {
236
+ result = rupeesText;
237
+ } else if (centsText) {
238
+ result = centsText;
239
+ } else {
240
+ result = "Zero Rupees";
241
+ }
242
+ if (showOnly) result += " Only";
243
+ if (isNegative) result = `Minus ${result}`;
244
+ } else {
245
+ let rupeesText = "";
246
+ if (rupees === 0 && cents > 0) {
247
+ rupeesText = "";
248
+ } else {
249
+ rupeesText = `\u0DBB\u0DD4\u0DB4\u0DD2\u0DBA\u0DBD\u0DCA ${integerToSinhalaWords(rupees)}`;
250
+ }
251
+ let centsText = "";
252
+ if (cents > 0) {
253
+ centsText = `\u0DC1\u0DAD ${convertThreeDigitSinhala(cents, true)}`;
254
+ }
255
+ if (rupeesText && centsText) {
256
+ result = `${rupeesText} ${centsText}`;
257
+ } else if (rupeesText) {
258
+ result = rupeesText;
259
+ } else if (centsText) {
260
+ result = centsText;
261
+ } else {
262
+ result = "\u0DBB\u0DD4\u0DB4\u0DD2\u0DBA\u0DBD\u0DCA \u0DB6\u0DD2\u0D82\u0DAF\u0DD4\u0DC0\u0DBA\u0DD2";
263
+ }
264
+ if (showOnly) result += " \u0DB4\u0DB8\u0DAB\u0DD2";
265
+ if (isNegative) result = `\u0D8D\u0DAB ${result}`;
266
+ }
267
+ if (chequeFormat) {
268
+ result = `*** ${result} ***`;
269
+ }
270
+ return result;
271
+ }
272
+ function formatLKR(amount, options) {
273
+ if (isNaN(amount) || amount === null || amount === void 0) {
274
+ return "";
275
+ }
276
+ const compactLang = (options == null ? void 0 : options.compactLang) ?? "en";
277
+ const defaultPrefix = compactLang === "si" ? "\u0DBB\u0DD4. " : "Rs. ";
278
+ const prefix = (options == null ? void 0 : options.prefix) ?? defaultPrefix;
279
+ const decimals = (options == null ? void 0 : options.decimals) ?? 2;
280
+ const compact = (options == null ? void 0 : options.compact) ?? false;
281
+ const isNegative = amount < 0;
282
+ const absAmount = Math.abs(amount);
283
+ if (compact) {
284
+ if (absAmount >= 1e7) {
285
+ const val = (absAmount / 1e7).toFixed(decimals);
286
+ const unit = compactLang === "si" ? "\u0D9A\u0DDD\u0DA7\u0DD2" : "Crores";
287
+ return `${isNegative ? "-" : ""}${prefix}${val} ${unit}`;
288
+ } else if (absAmount >= 1e5) {
289
+ const val = (absAmount / 1e5).toFixed(decimals);
290
+ const unit = compactLang === "si" ? "\u0DBD\u0D9A\u0DCA\u0DC2" : "Lakhs";
291
+ return `${isNegative ? "-" : ""}${prefix}${val} ${unit}`;
292
+ }
293
+ }
294
+ const formatted = absAmount.toLocaleString("en-US", {
295
+ minimumFractionDigits: decimals,
296
+ maximumFractionDigits: decimals
297
+ });
298
+ return `${isNegative ? "-" : ""}${prefix}${formatted}`;
299
+ }
300
+
301
+ // src/bank/index.ts
302
+ var SRI_LANKAN_BANKS = [
303
+ { code: "7010", name: "Bank of Ceylon", shortName: "BOC", swiftCode: "BCEYLKLX" },
304
+ { code: "7056", name: "Commercial Bank of Ceylon PLC", shortName: "Commercial Bank", swiftCode: "CCEYLKLX" },
305
+ { code: "7083", name: "Hatton National Bank PLC", shortName: "HNB", swiftCode: "HBLILKLX" },
306
+ { code: "7135", name: "People's Bank", shortName: "People's Bank", swiftCode: "PSBKLKLX" },
307
+ { code: "7162", name: "Nations Trust Bank PLC", shortName: "NTB", swiftCode: "NTBLLKLX" },
308
+ { code: "7214", name: "National Development Bank PLC", shortName: "NDB", swiftCode: "NDBLLKLX" },
309
+ { code: "7278", name: "Sampath Bank PLC", shortName: "Sampath Bank", swiftCode: "BSAMLKLX" },
310
+ { code: "7287", name: "Seylan Bank PLC", shortName: "Seylan Bank", swiftCode: "SEYBLKLX" },
311
+ { code: "7302", name: "Pan Asia Banking Corporation PLC", shortName: "Pan Asia Bank", swiftCode: "PABCLLLX" },
312
+ { code: "7311", name: "Union Bank of Colombo PLC", shortName: "Union Bank", swiftCode: "UBCLKLX" },
313
+ { code: "7463", name: "DFCC Bank PLC", shortName: "DFCC", swiftCode: "DFCCLKLX" },
314
+ { code: "7472", name: "Amana Bank PLC", shortName: "Amana Bank", swiftCode: "AMNLKLX" },
315
+ { code: "7481", name: "Cargills Bank Limited", shortName: "Cargills Bank", swiftCode: "CBKLLKLX" },
316
+ { code: "7092", name: "The Hongkong and Shanghai Banking Corporation Ltd", shortName: "HSBC", swiftCode: "HSBCLKLX" },
317
+ { code: "7108", name: "Standard Chartered Bank", shortName: "Standard Chartered", swiftCode: "SCBLLKLX" },
318
+ { code: "7719", name: "National Savings Bank", shortName: "NSB", swiftCode: "NSBLLKLX" },
319
+ { code: "7728", name: "Sanasa Development Bank PLC", shortName: "SDB Bank", swiftCode: "SDBLLKLX" },
320
+ { code: "7746", name: "Regional Development Bank", shortName: "RDB", swiftCode: "RDBLLKLX" }
321
+ ];
322
+ var SRI_LANKAN_BRANCHES = [
323
+ // Commercial Bank
324
+ { code: "001", name: "Colombo Fort / Head Office", bankCode: "7056", district: "Colombo" },
325
+ { code: "002", name: "Kollupitiya", bankCode: "7056", district: "Colombo" },
326
+ { code: "005", name: "Bambalapitiya", bankCode: "7056", district: "Colombo" },
327
+ { code: "010", name: "Kandy", bankCode: "7056", district: "Kandy" },
328
+ { code: "015", name: "Galle", bankCode: "7056", district: "Galle" },
329
+ { code: "020", name: "Kurunegala", bankCode: "7056", district: "Kurunegala" },
330
+ { code: "025", name: "Gampaha", bankCode: "7056", district: "Gampaha" },
331
+ { code: "030", name: "Negombo", bankCode: "7056", district: "Gampaha" },
332
+ { code: "035", name: "Jaffna", bankCode: "7056", district: "Jaffna" },
333
+ { code: "040", name: "Matara", bankCode: "7056", district: "Matara" },
334
+ // Bank of Ceylon
335
+ { code: "001", name: "City Office (Fort)", bankCode: "7010", district: "Colombo" },
336
+ { code: "002", name: "Kollupitiya", bankCode: "7010", district: "Colombo" },
337
+ { code: "010", name: "Kandy Main", bankCode: "7010", district: "Kandy" },
338
+ { code: "015", name: "Galle Fort", bankCode: "7010", district: "Galle" },
339
+ { code: "025", name: "Gampaha Super Grade", bankCode: "7010", district: "Gampaha" },
340
+ // Sampath Bank
341
+ { code: "001", name: "Colombo Fort / Head Office", bankCode: "7278", district: "Colombo" },
342
+ { code: "002", name: "Kollupitiya", bankCode: "7278", district: "Colombo" },
343
+ { code: "003", name: "Kandy", bankCode: "7278", district: "Kandy" },
344
+ { code: "004", name: "Galle", bankCode: "7278", district: "Galle" },
345
+ { code: "008", name: "Negombo", bankCode: "7278", district: "Gampaha" },
346
+ // People's Bank
347
+ { code: "001", name: "Corporate Banking Division (Colombo)", bankCode: "7135", district: "Colombo" },
348
+ { code: "010", name: "Kandy City", bankCode: "7135", district: "Kandy" },
349
+ { code: "015", name: "Galle Fort", bankCode: "7135", district: "Galle" },
350
+ // HNB
351
+ { code: "001", name: "HNB Towers (Head Office)", bankCode: "7083", district: "Colombo" },
352
+ { code: "010", name: "Kandy", bankCode: "7083", district: "Kandy" },
353
+ { code: "015", name: "Galle", bankCode: "7083", district: "Galle" }
354
+ ];
355
+ function getBanks() {
356
+ return SRI_LANKAN_BANKS;
357
+ }
358
+ function findBank(query) {
359
+ if (!query) return void 0;
360
+ const q = query.trim().toLowerCase();
361
+ return SRI_LANKAN_BANKS.find(
362
+ (b) => b.code === q || b.shortName.toLowerCase().includes(q) || b.name.toLowerCase().includes(q)
363
+ );
364
+ }
365
+ function getBranches(bankCode) {
366
+ if (!bankCode) return [];
367
+ const cleanCode = bankCode.trim();
368
+ return SRI_LANKAN_BRANCHES.filter((branch) => branch.bankCode === cleanCode);
369
+ }
370
+ function findBranch(bankCode, query) {
371
+ if (!bankCode || !query) return void 0;
372
+ const cleanBankCode = bankCode.trim();
373
+ const q = query.trim().toLowerCase();
374
+ return SRI_LANKAN_BRANCHES.find(
375
+ (branch) => branch.bankCode === cleanBankCode && (branch.code === q || branch.name.toLowerCase().includes(q))
376
+ );
377
+ }
378
+
379
+ // src/tax/index.ts
380
+ function validateTIN(input) {
381
+ if (input === null || input === void 0) {
382
+ return { isValid: false, error: "TIN cannot be empty." };
383
+ }
384
+ const tinStr = String(input).trim().replace(/[-\s]/g, "");
385
+ if (!/^\d{9}$/.test(tinStr)) {
386
+ return {
387
+ isValid: false,
388
+ error: "Invalid TIN. A Sri Lankan TIN must be exactly 9 numeric digits."
389
+ };
390
+ }
391
+ const formatted = `${tinStr.slice(0, 3)}-${tinStr.slice(3, 6)}-${tinStr.slice(6, 9)}`;
392
+ return {
393
+ isValid: true,
394
+ tin: tinStr,
395
+ formatted
396
+ };
397
+ }
398
+ function validateVAT(input) {
399
+ if (!input || typeof input !== "string") {
400
+ return { isValid: false, error: "VAT number must be a valid string." };
401
+ }
402
+ const clean = input.trim().replace(/\s/g, "");
403
+ const matchWithBranch = clean.match(/^(\d{9})-(\d{4})$/);
404
+ if (matchWithBranch) {
405
+ return {
406
+ isValid: true,
407
+ vatNumber: clean,
408
+ baseTin: matchWithBranch[1],
409
+ branchCode: matchWithBranch[2]
410
+ };
411
+ }
412
+ if (/^\d{9}$/.test(clean)) {
413
+ return {
414
+ isValid: true,
415
+ vatNumber: `${clean}-7000`,
416
+ baseTin: clean,
417
+ branchCode: "7000"
418
+ };
419
+ }
420
+ if (/^\d{13}$/.test(clean)) {
421
+ return {
422
+ isValid: true,
423
+ vatNumber: `${clean.slice(0, 9)}-${clean.slice(9)}`,
424
+ baseTin: clean.slice(0, 9),
425
+ branchCode: clean.slice(9)
426
+ };
427
+ }
428
+ return {
429
+ isValid: false,
430
+ error: "Invalid VAT format. Expected 9 digits with optional 4-digit branch suffix (e.g. 123456789-7000)."
431
+ };
432
+ }
433
+ function calculateTax(options) {
434
+ const {
435
+ amount,
436
+ isInclusive = false,
437
+ vatRate = 18,
438
+ includeSSCL = false,
439
+ ssclRate = 2.5
440
+ } = options;
441
+ const vRate = vatRate / 100;
442
+ const sRate = includeSSCL ? ssclRate / 100 : 0;
443
+ let baseAmount = 0;
444
+ let ssclAmount = 0;
445
+ let vatAmount = 0;
446
+ let totalAmount = 0;
447
+ if (isInclusive) {
448
+ const multiplier = (1 + sRate) * (1 + vRate);
449
+ baseAmount = Number((amount / multiplier).toFixed(2));
450
+ ssclAmount = Number((baseAmount * sRate).toFixed(2));
451
+ vatAmount = Number(((baseAmount + ssclAmount) * vRate).toFixed(2));
452
+ totalAmount = Number(amount.toFixed(2));
453
+ } else {
454
+ baseAmount = Number(amount.toFixed(2));
455
+ ssclAmount = Number((baseAmount * sRate).toFixed(2));
456
+ vatAmount = Number(((baseAmount + ssclAmount) * vRate).toFixed(2));
457
+ totalAmount = Number((baseAmount + ssclAmount + vatAmount).toFixed(2));
458
+ }
459
+ const totalTax = Number((ssclAmount + vatAmount).toFixed(2));
460
+ return {
461
+ baseAmount,
462
+ ssclAmount,
463
+ vatAmount,
464
+ totalTax,
465
+ totalAmount
466
+ };
467
+ }
468
+
469
+ // src/calendar/index.ts
470
+ var SRI_LANKAN_HOLIDAYS = [
471
+ // Fixed annual national holidays
472
+ { date: "2025-01-14", name: "Tamil Thai Pongal Day", categories: ["public", "bank", "mercantile"] },
473
+ { date: "2025-01-13", name: "Duruthu Full Moon Poya Day", categories: ["public", "bank"] },
474
+ { date: "2025-02-04", name: "National Day (Independence Day)", categories: ["public", "bank", "mercantile"] },
475
+ { date: "2025-02-12", name: "Navam Full Moon Poya Day", categories: ["public", "bank"] },
476
+ { date: "2025-02-26", name: "Mahasivarathri Day", categories: ["public", "bank"] },
477
+ { date: "2025-03-13", name: "Medin Full Moon Poya Day", categories: ["public", "bank"] },
478
+ { date: "2025-03-31", name: "Id-Ul-Fitr (Ramazan Festival Day)", categories: ["public", "bank"] },
479
+ { date: "2025-04-12", name: "Bak Full Moon Poya Day", categories: ["public", "bank"] },
480
+ { date: "2025-04-13", name: "Day prior to Sinhala & Tamil New Year Day", categories: ["public", "bank", "mercantile"] },
481
+ { date: "2025-04-14", name: "Sinhala & Tamil New Year Day", categories: ["public", "bank", "mercantile"] },
482
+ { date: "2025-04-18", name: "Good Friday", categories: ["public", "bank"] },
483
+ { date: "2025-05-01", name: "May Day (International Workers' Day)", categories: ["public", "bank", "mercantile"] },
484
+ { date: "2025-05-12", name: "Vesak Full Moon Poya Day", categories: ["public", "bank", "mercantile"] },
485
+ { date: "2025-05-13", name: "Day following Vesak Full Moon Poya Day", categories: ["public", "bank", "mercantile"] },
486
+ { date: "2025-06-10", name: "Poson Full Moon Poya Day", categories: ["public", "bank"] },
487
+ { date: "2025-07-10", name: "Esala Full Moon Poya Day", categories: ["public", "bank"] },
488
+ { date: "2025-08-08", name: "Nikini Full Moon Poya Day", categories: ["public", "bank"] },
489
+ { date: "2025-09-05", name: "Milad-Un-Nabi (Holy Prophet's Birthday)", categories: ["public", "bank", "mercantile"] },
490
+ { date: "2025-09-07", name: "Binara Full Moon Poya Day", categories: ["public", "bank"] },
491
+ { date: "2025-10-06", name: "Vap Full Moon Poya Day", categories: ["public", "bank"] },
492
+ { date: "2025-10-20", name: "Deepavali Festival Day", categories: ["public", "bank"] },
493
+ { date: "2025-11-05", name: "Ill Full Moon Poya Day", categories: ["public", "bank"] },
494
+ { date: "2025-12-04", name: "Unduvap Full Moon Poya Day", categories: ["public", "bank"] },
495
+ { date: "2025-12-25", name: "Christmas Day", categories: ["public", "bank", "mercantile"] }
496
+ ];
497
+ function formatDateString(d) {
498
+ const yyyy = d.getFullYear();
499
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
500
+ const dd = String(d.getDate()).padStart(2, "0");
501
+ return `${yyyy}-${mm}-${dd}`;
502
+ }
503
+ function isWeekend(date, saturdayWorking = false) {
504
+ const day = date.getDay();
505
+ if (day === 0) return true;
506
+ if (day === 6) return !saturdayWorking;
507
+ return false;
508
+ }
509
+ function getHoliday(date) {
510
+ const dateStr = formatDateString(date);
511
+ return SRI_LANKAN_HOLIDAYS.find((h) => h.date === dateStr);
512
+ }
513
+ function isWorkingDay(date, options) {
514
+ const satWorking = (options == null ? void 0 : options.saturdayWorking) ?? false;
515
+ if (isWeekend(date, satWorking)) {
516
+ return false;
517
+ }
518
+ const holiday = getHoliday(date);
519
+ if (!holiday) {
520
+ return true;
521
+ }
522
+ if (options == null ? void 0 : options.mercantileOnly) {
523
+ return !holiday.categories.includes("mercantile");
524
+ }
525
+ return false;
526
+ }
527
+ function getWorkingDays(startDate, endDate, options) {
528
+ if (startDate > endDate) {
529
+ return 0;
530
+ }
531
+ let count = 0;
532
+ const current = new Date(startDate.getTime());
533
+ while (current <= endDate) {
534
+ if (isWorkingDay(current, options)) {
535
+ count++;
536
+ }
537
+ current.setDate(current.getDate() + 1);
538
+ }
539
+ return count;
540
+ }
541
+ function addWorkingDays(startDate, daysToAdd, options) {
542
+ const result = new Date(startDate.getTime());
543
+ let added = 0;
544
+ while (added < daysToAdd) {
545
+ result.setDate(result.getDate() + 1);
546
+ if (isWorkingDay(result, options)) {
547
+ added++;
548
+ }
549
+ }
550
+ return result;
551
+ }
552
+ export {
553
+ SRI_LANKAN_BANKS,
554
+ SRI_LANKAN_BRANCHES,
555
+ SRI_LANKAN_HOLIDAYS,
556
+ addWorkingDays,
557
+ calculateTax,
558
+ findBank,
559
+ findBranch,
560
+ formatLKR,
561
+ getBanks,
562
+ getBranches,
563
+ getHoliday,
564
+ getWorkingDays,
565
+ isWeekend,
566
+ isWorkingDay,
567
+ lkrToWords,
568
+ validateTIN,
569
+ validateVAT
570
+ };
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "ceylon-devkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "CeylonDevKit - The ultimate Sri Lankan developer toolkit: LKR currency to words, Bank & Branch codes, TIN & VAT validator with Tax calculator, and CBSL working days calculator.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
20
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
+ "demo": "npx tsx demo.ts",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "prepublishOnly": "npm run test && npm run build"
25
+ },
26
+ "keywords": [
27
+ "ceylon",
28
+ "ceylon-devkit",
29
+ "sri-lanka",
30
+ "lka",
31
+ "lkr",
32
+ "currency-to-words",
33
+ "bank-codes",
34
+ "tin-validator",
35
+ "vat-calculator",
36
+ "cbsl-holidays",
37
+ "working-days"
38
+ ],
39
+ "author": "",
40
+ "license": "MIT",
41
+ "devDependencies": {
42
+ "tsup": "^8.0.2",
43
+ "typescript": "^5.4.5",
44
+ "vitest": "^1.6.0"
45
+ }
46
+ }