@indodev/toolkit 0.3.3 → 0.3.4
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/LICENCE +21 -0
- package/dist/currency/index.cjs +158 -25
- package/dist/currency/index.cjs.map +1 -1
- package/dist/currency/index.d.cts +50 -354
- package/dist/currency/index.d.ts +50 -354
- package/dist/currency/index.js +154 -26
- package/dist/currency/index.js.map +1 -1
- package/dist/index.cjs +65 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +65 -25
- package/dist/index.js.map +1 -1
- package/dist/utils-OG1yMaAa.d.cts +485 -0
- package/dist/utils-OG1yMaAa.d.ts +485 -0
- package/package.json +18 -18
package/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Adamm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/currency/index.cjs
CHANGED
|
@@ -10,7 +10,7 @@ function formatRupiah(amount, options) {
|
|
|
10
10
|
spaceAfterSymbol = true
|
|
11
11
|
} = options || {};
|
|
12
12
|
const precision = options?.precision !== void 0 ? options.precision : decimal ? 2 : 0;
|
|
13
|
-
const isNegative = amount < 0;
|
|
13
|
+
const isNegative = amount < 0 && amount !== 0;
|
|
14
14
|
const absAmount = Math.abs(amount);
|
|
15
15
|
let result;
|
|
16
16
|
if (decimal) {
|
|
@@ -28,17 +28,21 @@ function formatRupiah(amount, options) {
|
|
|
28
28
|
const intAmount = Math.floor(absAmount);
|
|
29
29
|
result = intAmount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
30
30
|
}
|
|
31
|
-
if (isNegative) {
|
|
32
|
-
result = `-${result}`;
|
|
33
|
-
}
|
|
34
31
|
if (symbol) {
|
|
35
32
|
const space = spaceAfterSymbol ? " " : "";
|
|
36
|
-
|
|
33
|
+
if (isNegative) {
|
|
34
|
+
result = `-Rp${space}${result}`;
|
|
35
|
+
} else {
|
|
36
|
+
result = `Rp${space}${result}`;
|
|
37
|
+
}
|
|
38
|
+
} else if (isNegative) {
|
|
39
|
+
result = `-${result}`;
|
|
37
40
|
}
|
|
38
41
|
return result;
|
|
39
42
|
}
|
|
40
|
-
function formatCompact(amount) {
|
|
41
|
-
const
|
|
43
|
+
function formatCompact(amount, options) {
|
|
44
|
+
const { symbol = true, spaceAfterSymbol = true } = options || {};
|
|
45
|
+
const isNegative = amount < 0 && amount !== 0;
|
|
42
46
|
const abs = Math.abs(amount);
|
|
43
47
|
let result;
|
|
44
48
|
if (abs >= 1e12) {
|
|
@@ -54,10 +58,17 @@ function formatCompact(amount) {
|
|
|
54
58
|
} else {
|
|
55
59
|
result = abs.toString();
|
|
56
60
|
}
|
|
57
|
-
if (
|
|
61
|
+
if (symbol) {
|
|
62
|
+
const space = spaceAfterSymbol ? " " : "";
|
|
63
|
+
if (isNegative) {
|
|
64
|
+
result = `-Rp${space}${result}`;
|
|
65
|
+
} else {
|
|
66
|
+
result = `Rp${space}${result}`;
|
|
67
|
+
}
|
|
68
|
+
} else if (isNegative) {
|
|
58
69
|
result = `-${result}`;
|
|
59
70
|
}
|
|
60
|
-
return
|
|
71
|
+
return result;
|
|
61
72
|
}
|
|
62
73
|
function formatCompactValue(value, unit) {
|
|
63
74
|
const rounded = Math.round(value * 10) / 10;
|
|
@@ -154,20 +165,42 @@ var TENS = [
|
|
|
154
165
|
"sembilan puluh"
|
|
155
166
|
];
|
|
156
167
|
function toWords(amount, options) {
|
|
157
|
-
const {
|
|
168
|
+
const {
|
|
169
|
+
uppercase = false,
|
|
170
|
+
withCurrency = true,
|
|
171
|
+
withDecimals = false
|
|
172
|
+
} = options || {};
|
|
158
173
|
if (amount === 0) {
|
|
159
174
|
let result = "nol";
|
|
160
175
|
if (withCurrency) result += " rupiah";
|
|
161
176
|
return uppercase ? capitalize(result) : result;
|
|
162
177
|
}
|
|
163
178
|
const isNegative = amount < 0;
|
|
164
|
-
const absAmount = Math.
|
|
179
|
+
const absAmount = Math.abs(amount);
|
|
180
|
+
const intPart = Math.floor(absAmount);
|
|
181
|
+
let words = convertInteger(intPart);
|
|
182
|
+
if (isNegative) {
|
|
183
|
+
words = "minus " + words;
|
|
184
|
+
}
|
|
185
|
+
if (withCurrency) {
|
|
186
|
+
words += " rupiah";
|
|
187
|
+
}
|
|
188
|
+
if (withDecimals) {
|
|
189
|
+
const decimalPart = Math.round((absAmount - intPart) * 100);
|
|
190
|
+
if (decimalPart > 0) {
|
|
191
|
+
words += " koma " + convertDecimal(decimalPart);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return uppercase ? capitalize(words) : words;
|
|
195
|
+
}
|
|
196
|
+
function convertInteger(num) {
|
|
197
|
+
if (num === 0) return "nol";
|
|
165
198
|
let words = "";
|
|
166
|
-
const triliun = Math.floor(
|
|
167
|
-
const miliar = Math.floor(
|
|
168
|
-
const juta = Math.floor(
|
|
169
|
-
const ribu = Math.floor(
|
|
170
|
-
const sisa =
|
|
199
|
+
const triliun = Math.floor(num / 1e12);
|
|
200
|
+
const miliar = Math.floor(num % 1e12 / 1e9);
|
|
201
|
+
const juta = Math.floor(num % 1e9 / 1e6);
|
|
202
|
+
const ribu = Math.floor(num % 1e6 / 1e3);
|
|
203
|
+
const sisa = num % 1e3;
|
|
171
204
|
if (triliun > 0) {
|
|
172
205
|
words += convertGroup(triliun) + " triliun";
|
|
173
206
|
}
|
|
@@ -187,13 +220,19 @@ function toWords(amount, options) {
|
|
|
187
220
|
if (words) words += " ";
|
|
188
221
|
words += convertGroup(sisa);
|
|
189
222
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
223
|
+
return words;
|
|
224
|
+
}
|
|
225
|
+
function convertDecimal(num) {
|
|
226
|
+
if (num === 0) return "";
|
|
227
|
+
if (num < 10) return BASIC_NUMBERS[num];
|
|
228
|
+
if (num < 20) return TEENS[num - 10];
|
|
229
|
+
const tens = Math.floor(num / 10);
|
|
230
|
+
const ones = num % 10;
|
|
231
|
+
let result = TENS[tens];
|
|
232
|
+
if (ones > 0) {
|
|
233
|
+
result += " " + BASIC_NUMBERS[ones];
|
|
195
234
|
}
|
|
196
|
-
return
|
|
235
|
+
return result;
|
|
197
236
|
}
|
|
198
237
|
function convertGroup(num) {
|
|
199
238
|
if (num === 0) return "";
|
|
@@ -243,26 +282,120 @@ function formatAccounting(amount, options) {
|
|
|
243
282
|
}
|
|
244
283
|
return formatted;
|
|
245
284
|
}
|
|
246
|
-
function calculateTax(amount, rate
|
|
285
|
+
function calculateTax(amount, rate) {
|
|
247
286
|
return amount * rate;
|
|
248
287
|
}
|
|
249
288
|
function addRupiahSymbol(amount) {
|
|
250
289
|
if (typeof amount === "number") {
|
|
251
|
-
|
|
290
|
+
const formatted = amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
291
|
+
return `Rp ${formatted}`;
|
|
252
292
|
}
|
|
253
293
|
if (amount.trim().startsWith("Rp")) {
|
|
254
|
-
return amount;
|
|
294
|
+
return amount.trim();
|
|
255
295
|
}
|
|
256
296
|
return `Rp ${amount.trim()}`;
|
|
257
297
|
}
|
|
258
298
|
|
|
299
|
+
// src/currency/calc.ts
|
|
300
|
+
var InvalidSplitError = class extends Error {
|
|
301
|
+
constructor(message) {
|
|
302
|
+
super(message);
|
|
303
|
+
this.name = "InvalidSplitError";
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
function splitAmount(amount, parts, options) {
|
|
307
|
+
if (parts < 1) {
|
|
308
|
+
throw new InvalidSplitError("Parts must be at least 1");
|
|
309
|
+
}
|
|
310
|
+
if (parts === 1) {
|
|
311
|
+
return [amount];
|
|
312
|
+
}
|
|
313
|
+
const { ratios, roundTo } = options || {};
|
|
314
|
+
if (ratios) {
|
|
315
|
+
if (ratios.length !== parts) {
|
|
316
|
+
throw new InvalidSplitError(
|
|
317
|
+
`Ratios length (${ratios.length}) must match parts count (${parts})`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const sum = ratios.reduce((a, b) => a + b, 0);
|
|
321
|
+
if (Math.abs(sum - 100) > 0.01) {
|
|
322
|
+
throw new InvalidSplitError(`Ratios must sum to 100 (got ${sum})`);
|
|
323
|
+
}
|
|
324
|
+
let result2 = ratios.map((r) => amount * (r / 100));
|
|
325
|
+
if (roundTo) {
|
|
326
|
+
result2 = result2.map((v) => roundToClean2(v, roundTo));
|
|
327
|
+
}
|
|
328
|
+
return result2;
|
|
329
|
+
}
|
|
330
|
+
const base = Math.floor(amount / parts);
|
|
331
|
+
const remainder = amount - base * parts;
|
|
332
|
+
const result = [];
|
|
333
|
+
for (let i = 0; i < parts; i++) {
|
|
334
|
+
result.push(base + (i < remainder ? 1 : 0));
|
|
335
|
+
}
|
|
336
|
+
if (roundTo) {
|
|
337
|
+
return result.map((v) => roundToClean2(v, roundTo));
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
function percentageOf(part, total) {
|
|
342
|
+
if (total === 0) return 0;
|
|
343
|
+
return part / total * 100;
|
|
344
|
+
}
|
|
345
|
+
function difference(amount1, amount2) {
|
|
346
|
+
const absolute = amount1 - amount2;
|
|
347
|
+
let percentage;
|
|
348
|
+
if (amount2 === 0) {
|
|
349
|
+
percentage = amount1 === 0 ? 0 : null;
|
|
350
|
+
} else {
|
|
351
|
+
percentage = absolute / amount2 * 100;
|
|
352
|
+
}
|
|
353
|
+
const direction = absolute > 0 ? "increase" : absolute < 0 ? "decrease" : "same";
|
|
354
|
+
return { absolute, percentage, direction };
|
|
355
|
+
}
|
|
356
|
+
function roundToClean2(amount, unit) {
|
|
357
|
+
const divisors = {
|
|
358
|
+
ribu: 1e3,
|
|
359
|
+
"ratus-ribu": 1e5,
|
|
360
|
+
juta: 1e6
|
|
361
|
+
};
|
|
362
|
+
return Math.round(amount / divisors[unit]) * divisors[unit];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/currency/validate.ts
|
|
366
|
+
function validateRupiah(formatted) {
|
|
367
|
+
if (!formatted || typeof formatted !== "string") {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const trimmed = formatted.trim();
|
|
371
|
+
if (!trimmed) return false;
|
|
372
|
+
const compactUnits = ["triliun", "miliar", "juta", "ribu"];
|
|
373
|
+
for (const unit of compactUnits) {
|
|
374
|
+
if (trimmed.toLowerCase().includes(unit)) {
|
|
375
|
+
return /-?\d+[,.]?\d*\s*(ribu|juta|miliar|triliun)/i.test(trimmed);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
let cleaned = trimmed.replace(/^(-?\s*)?Rp\s*/i, "");
|
|
379
|
+
cleaned = cleaned.replace(/^\s*-/, "");
|
|
380
|
+
cleaned = cleaned.trim();
|
|
381
|
+
if (!cleaned) return false;
|
|
382
|
+
if (!/^[0-9.,]+$/.test(cleaned)) return false;
|
|
383
|
+
if (!/\d/.test(cleaned)) return false;
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
exports.InvalidSplitError = InvalidSplitError;
|
|
259
388
|
exports.addRupiahSymbol = addRupiahSymbol;
|
|
260
389
|
exports.calculateTax = calculateTax;
|
|
390
|
+
exports.difference = difference;
|
|
261
391
|
exports.formatAccounting = formatAccounting;
|
|
262
392
|
exports.formatCompact = formatCompact;
|
|
263
393
|
exports.formatRupiah = formatRupiah;
|
|
264
394
|
exports.parseRupiah = parseRupiah;
|
|
395
|
+
exports.percentageOf = percentageOf;
|
|
265
396
|
exports.roundToClean = roundToClean;
|
|
397
|
+
exports.splitAmount = splitAmount;
|
|
266
398
|
exports.toWords = toWords;
|
|
399
|
+
exports.validateRupiah = validateRupiah;
|
|
267
400
|
//# sourceMappingURL=index.cjs.map
|
|
268
401
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/currency/format.ts","../../src/currency/parse.ts","../../src/currency/words.ts","../../src/currency/utils.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Currency formatting utilities for Indonesian Rupiah.\n *\n * @module currency/format\n * @packageDocumentation\n */\n\nimport type { RupiahOptions } from './types';\n\n/**\n * Formats a number as Indonesian Rupiah currency.\n *\n * Provides flexible formatting options including symbol display,\n * decimal places, and custom separators.\n *\n * @param amount - The amount to format\n * @param options - Formatting options\n * @returns Formatted Rupiah string\n *\n * @example\n * Basic formatting:\n * ```typescript\n * formatRupiah(1500000); // 'Rp 1.500.000'\n * ```\n *\n * @example\n * With decimals:\n * ```typescript\n * formatRupiah(1500000.50, { decimal: true }); // 'Rp 1.500.000,50'\n * ```\n *\n * @example\n * Without symbol:\n * ```typescript\n * formatRupiah(1500000, { symbol: false }); // '1.500.000'\n * ```\n *\n * @example\n * Custom separators:\n * ```typescript\n * formatRupiah(1500000, { separator: ',' }); // 'Rp 1,500,000'\n * ```\n *\n * @public\n */\nexport function formatRupiah(amount: number, options?: RupiahOptions): string {\n const {\n symbol = true,\n decimal = false,\n separator = '.',\n decimalSeparator = ',',\n spaceAfterSymbol = true,\n } = options || {};\n\n // Default precision: 2 for decimals, 0 otherwise\n const precision =\n options?.precision !== undefined ? options.precision : decimal ? 2 : 0;\n\n const isNegative = amount < 0;\n const absAmount = Math.abs(amount);\n\n let result: string;\n\n if (decimal) {\n const factor = Math.pow(10, precision);\n const rounded = Math.round(absAmount * factor) / factor;\n\n if (precision > 0) {\n const [intPart, decPart] = rounded.toFixed(precision).split('.');\n const formattedInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator);\n result = `${formattedInt}${decimalSeparator}${decPart}`;\n } else {\n // Precision 0: no decimal separator needed\n const intPart = rounded.toString();\n result = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator);\n }\n } else {\n const intAmount = Math.floor(absAmount);\n result = intAmount.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator);\n }\n\n if (isNegative) {\n result = `-${result}`;\n }\n\n if (symbol) {\n const space = spaceAfterSymbol ? ' ' : '';\n result = `Rp${space}${result}`;\n }\n\n return result;\n}\n\n/**\n * Formats a number in compact Indonesian format.\n *\n * Uses Indonesian units: ribu, juta, miliar, triliun.\n * Follows Indonesian grammar rules (e.g., \"1 juta\" not \"1,0 juta\").\n *\n * @param amount - The amount to format\n * @returns Compact formatted string\n *\n * @example\n * Millions:\n * ```typescript\n * formatCompact(1500000); // 'Rp 1,5 juta'\n * formatCompact(1000000); // 'Rp 1 juta'\n * ```\n *\n * @example\n * Thousands:\n * ```typescript\n * formatCompact(500000); // 'Rp 500 ribu'\n * ```\n *\n * @example\n * Small numbers:\n * ```typescript\n * formatCompact(1500); // 'Rp 1.500'\n * ```\n *\n * @public\n */\nexport function formatCompact(amount: number): string {\n const isNegative = amount < 0;\n const abs = Math.abs(amount);\n\n let result: string;\n\n if (abs >= 1_000_000_000_000) {\n result = formatCompactValue(abs / 1_000_000_000_000, 'triliun');\n } else if (abs >= 1_000_000_000) {\n result = formatCompactValue(abs / 1_000_000_000, 'miliar');\n } else if (abs >= 1_000_000) {\n result = formatCompactValue(abs / 1_000_000, 'juta');\n } else if (abs >= 100_000) {\n result = formatCompactValue(abs / 1000, 'ribu');\n } else if (abs >= 1_000) {\n // Below 100k: use standard formatting instead of \"ribu\"\n result = abs.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n } else {\n result = abs.toString();\n }\n\n if (isNegative) {\n result = `-${result}`;\n }\n\n return `Rp ${result}`;\n}\n\n/**\n * Formats a value with Indonesian unit, applying grammar rules.\n *\n * Automatically removes trailing \".0\" to follow proper Indonesian grammar.\n * For example: \"1 juta\" instead of \"1,0 juta\".\n *\n * @param value - The numeric value to format\n * @param unit - The Indonesian unit (ribu, juta, miliar, triliun)\n * @returns Formatted string with unit\n * @internal\n */\nfunction formatCompactValue(value: number, unit: string): string {\n const rounded = Math.round(value * 10) / 10;\n\n if (rounded % 1 === 0) {\n return `${rounded.toFixed(0)} ${unit}`;\n }\n\n return `${rounded.toString().replace('.', ',')} ${unit}`;\n}\n","/**\n * Currency parsing utilities for Indonesian Rupiah.\n *\n * @module currency/parse\n * @packageDocumentation\n */\n\n/**\n * Parses a formatted Rupiah string back to a number.\n *\n * Handles multiple formats:\n * - Standard: \"Rp 1.500.000\"\n * - No symbol: \"1.500.000\"\n * - With decimals: \"Rp 1.500.000,50\"\n * - Compact: \"Rp 1,5 juta\", \"Rp 500 ribu\"\n *\n * @param formatted - The formatted Rupiah string to parse\n * @returns Parsed number, or null if invalid\n *\n * @example\n * Standard format:\n * ```typescript\n * parseRupiah('Rp 1.500.000'); // 1500000\n * ```\n *\n * @example\n * With decimals:\n * ```typescript\n * parseRupiah('Rp 1.500.000,50'); // 1500000.50\n * ```\n *\n * @example\n * Compact format:\n * ```typescript\n * parseRupiah('Rp 1,5 juta'); // 1500000\n * parseRupiah('Rp 500 ribu'); // 500000\n * ```\n *\n * @example\n * Invalid input:\n * ```typescript\n * parseRupiah('invalid'); // null\n * ```\n *\n * @public\n */\nexport function parseRupiah(formatted: string): number | null {\n if (!formatted || typeof formatted !== 'string') {\n return null;\n }\n\n const cleaned = formatted.trim().toLowerCase();\n\n // Check for compact units (juta, ribu, miliar, triliun)\n const compactUnits = {\n triliun: 1_000_000_000_000,\n miliar: 1_000_000_000,\n juta: 1_000_000,\n ribu: 1_000,\n };\n\n for (const [unit, multiplier] of Object.entries(compactUnits)) {\n if (cleaned.includes(unit)) {\n const match = cleaned.match(/(-?\\d+[,.]?\\d*)/);\n if (match) {\n const num = parseFloat(match[1].replace(',', '.'));\n return num * multiplier;\n }\n }\n }\n\n // Standard format: remove 'Rp' and spaces\n let numStr = cleaned.replace(/rp/gi, '').trim();\n\n const hasDot = numStr.includes('.');\n const hasComma = numStr.includes(',');\n\n if (hasDot && hasComma) {\n // Determine format based on last separator position\n // Indonesian: 1.500.000,50 vs International: 1,500,000.50\n const lastDot = numStr.lastIndexOf('.');\n const lastComma = numStr.lastIndexOf(',');\n\n if (lastComma > lastDot) {\n numStr = numStr.replace(/\\./g, '').replace(',', '.');\n } else {\n numStr = numStr.replace(/,/g, '');\n }\n } else if (hasComma) {\n const parts = numStr.split(',');\n // Decimal if only 1-2 digits after comma\n if (parts.length === 2 && parts[1].length <= 2) {\n numStr = numStr.replace(',', '.');\n } else {\n numStr = numStr.replace(/,/g, '');\n }\n } else if (hasDot) {\n const parts = numStr.split('.');\n // If not decimal format, remove dots (thousands separator)\n if (parts.length > 2 || (parts.length === 2 && parts[1].length > 2)) {\n numStr = numStr.replace(/\\./g, '');\n }\n }\n\n const parsed = parseFloat(numStr);\n return isNaN(parsed) ? null : parsed;\n}\n","/**\n * Convert numbers to Indonesian words (terbilang).\n *\n * @module currency/words\n * @packageDocumentation\n */\n\nimport type { WordOptions } from './types';\n\n/**\n * Basic Indonesian number words (0-9).\n * @internal\n */\nconst BASIC_NUMBERS = [\n '',\n 'satu',\n 'dua',\n 'tiga',\n 'empat',\n 'lima',\n 'enam',\n 'tujuh',\n 'delapan',\n 'sembilan',\n];\n\n/**\n * Indonesian words for 10-19.\n * @internal\n */\nconst TEENS = [\n 'sepuluh',\n 'sebelas',\n 'dua belas',\n 'tiga belas',\n 'empat belas',\n 'lima belas',\n 'enam belas',\n 'tujuh belas',\n 'delapan belas',\n 'sembilan belas',\n];\n\n/**\n * Indonesian words for tens (20, 30, 40, etc).\n * @internal\n */\nconst TENS = [\n '',\n '',\n 'dua puluh',\n 'tiga puluh',\n 'empat puluh',\n 'lima puluh',\n 'enam puluh',\n 'tujuh puluh',\n 'delapan puluh',\n 'sembilan puluh',\n];\n\n/**\n * Converts a number to Indonesian words (terbilang).\n *\n * Supports numbers up to trillions (triliun).\n * Follows Indonesian language rules for number pronunciation.\n *\n * Special rules:\n * - 1 = \"satu\" in most cases, but \"se-\" for 100, 1000\n * - 11 = \"sebelas\" (not \"satu belas\")\n * - 100 = \"seratus\" (not \"satu ratus\")\n * - 1000 = \"seribu\" (not \"satu ribu\")\n *\n * @param amount - The number to convert\n * @param options - Conversion options\n * @returns Indonesian words representation\n *\n * @example\n * Basic numbers:\n * ```typescript\n * toWords(123); // 'seratus dua puluh tiga rupiah'\n * ```\n *\n * @example\n * Large numbers:\n * ```typescript\n * toWords(1500000); // 'satu juta lima ratus ribu rupiah'\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * toWords(1500000, { uppercase: true });\n * // 'Satu juta lima ratus ribu rupiah'\n *\n * toWords(1500000, { withCurrency: false });\n * // 'satu juta lima ratus ribu'\n * ```\n *\n * @public\n */\nexport function toWords(amount: number, options?: WordOptions): string {\n const { uppercase = false, withCurrency = true } = options || {};\n\n if (amount === 0) {\n let result = 'nol';\n if (withCurrency) result += ' rupiah';\n return uppercase ? capitalize(result) : result;\n }\n\n const isNegative = amount < 0;\n const absAmount = Math.floor(Math.abs(amount));\n\n let words = '';\n\n // Break into groups: triliun, miliar, juta, ribu, sisa\n const triliun = Math.floor(absAmount / 1_000_000_000_000);\n const miliar = Math.floor((absAmount % 1_000_000_000_000) / 1_000_000_000);\n const juta = Math.floor((absAmount % 1_000_000_000) / 1_000_000);\n const ribu = Math.floor((absAmount % 1_000_000) / 1_000);\n const sisa = absAmount % 1_000;\n\n if (triliun > 0) {\n words += convertGroup(triliun) + ' triliun';\n }\n\n if (miliar > 0) {\n if (words) words += ' ';\n words += convertGroup(miliar) + ' miliar';\n }\n\n if (juta > 0) {\n if (words) words += ' ';\n words += convertGroup(juta) + ' juta';\n }\n\n if (ribu > 0) {\n if (words) words += ' ';\n // Special rule: 1000 = \"seribu\" not \"satu ribu\"\n words += ribu === 1 ? 'seribu' : convertGroup(ribu) + ' ribu';\n }\n\n if (sisa > 0) {\n if (words) words += ' ';\n words += convertGroup(sisa);\n }\n\n if (isNegative) {\n words = 'minus ' + words;\n }\n\n if (withCurrency) {\n words += ' rupiah';\n }\n\n return uppercase ? capitalize(words) : words;\n}\n\n/**\n * Converts a group of 1-3 digits (0-999) to Indonesian words.\n *\n * @param num - Number to convert (0-999)\n * @returns Indonesian words for the number\n * @internal\n */\nfunction convertGroup(num: number): string {\n if (num === 0) return '';\n\n let result = '';\n\n const hundreds = Math.floor(num / 100);\n if (hundreds > 0) {\n // Special rule: 100 = \"seratus\" not \"satu ratus\"\n result = hundreds === 1 ? 'seratus' : BASIC_NUMBERS[hundreds] + ' ratus';\n }\n\n const remainder = num % 100;\n if (remainder > 0) {\n if (result) result += ' ';\n result += convertTwoDigits(remainder);\n }\n\n return result;\n}\n\n/**\n * Converts numbers 1-99 to Indonesian words.\n *\n * @param num - Number to convert (1-99)\n * @returns Indonesian words for the number\n * @internal\n */\nfunction convertTwoDigits(num: number): string {\n if (num === 0) return '';\n if (num < 10) return BASIC_NUMBERS[num];\n if (num >= 10 && num < 20) return TEENS[num - 10];\n\n const tens = Math.floor(num / 10);\n const ones = num % 10;\n\n let result = TENS[tens];\n if (ones > 0) {\n result += ' ' + BASIC_NUMBERS[ones];\n }\n\n return result;\n}\n\n/**\n * Capitalizes the first letter of a string.\n *\n * @param str - String to capitalize\n * @returns String with first letter capitalized\n * @internal\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n","/**\n * Currency utility functions.\n *\n * @module currency/utils\n * @packageDocumentation\n */\n\nimport { formatRupiah } from './format';\nimport type { RoundUnit, RupiahOptions } from './types';\n\n/**\n * Rounds a number to a clean currency amount.\n *\n * Common use case: displaying approximate prices or budgets\n * in clean, rounded numbers.\n *\n * @param amount - The amount to round\n * @param unit - The unit to round to (default: 'ribu')\n * @returns Rounded amount\n *\n * @example\n * Round to thousands:\n * ```typescript\n * roundToClean(1234567, 'ribu'); // 1235000\n * ```\n *\n * @example\n * Round to hundred thousands:\n * ```typescript\n * roundToClean(1234567, 'ratus-ribu'); // 1200000\n * ```\n *\n * @example\n * Round to millions:\n * ```typescript\n * roundToClean(1234567, 'juta'); // 1000000\n * ```\n *\n * @public\n */\nexport function roundToClean(amount: number, unit: RoundUnit = 'ribu'): number {\n const divisors: Record<RoundUnit, number> = {\n ribu: 1000,\n 'ratus-ribu': 100000,\n juta: 1000000,\n };\n\n const divisor = divisors[unit];\n\n // Math.round handles both positive and negative numbers\n return Math.round(amount / divisor) * divisor;\n}\n\n/**\n * Formats a number as Indonesian Rupiah in accounting style.\n * Negative numbers are wrapped in parentheses.\n *\n * @param amount - The amount to format\n * @param options - Formatting options\n * @returns Formatted accounting string\n *\n * @example\n * ```typescript\n * formatAccounting(-1500000); // '(Rp 1.500.000)'\n * ```\n */\nexport function formatAccounting(\n amount: number,\n options?: RupiahOptions\n): string {\n const isNegative = amount < 0;\n const formatted = formatRupiah(Math.abs(amount), options);\n\n if (isNegative) {\n return `(${formatted})`;\n }\n\n return formatted;\n}\n\n/**\n * Calculates tax (PPN) for a given amount.\n *\n * @param amount - The base amount\n * @param rate - The tax rate (default: 0.11 for 11%)\n * @returns The calculated tax amount\n *\n * @example\n * ```typescript\n * calculateTax(1000000); // 110000\n * ```\n */\nexport function calculateTax(amount: number, rate: number = 0.11): number {\n return amount * rate;\n}\n\n/**\n * Helper to ensure a string or number has the 'Rp ' prefix.\n * If already prefixed, it returns the input as is.\n *\n * @param amount - The amount or formatted string\n * @returns String with Rupiah prefix\n */\nexport function addRupiahSymbol(amount: string | number): string {\n if (typeof amount === 'number') {\n return `Rp ${amount.toLocaleString('id-ID')}`;\n }\n\n if (amount.trim().startsWith('Rp')) {\n return amount;\n }\n\n return `Rp ${amount.trim()}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/currency/format.ts","../../src/currency/parse.ts","../../src/currency/words.ts","../../src/currency/utils.ts","../../src/currency/calc.ts","../../src/currency/validate.ts"],"names":["result","roundToClean"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Currency formatting utilities for Indonesian Rupiah.\n *\n * @module currency/format\n * @packageDocumentation\n */\n\nimport type { CompactOptions, RupiahOptions } from './types';\n\n/**\n * Formats a number as Indonesian Rupiah currency.\n *\n * Provides flexible formatting options including symbol display,\n * decimal places, and custom separators.\n *\n * @param amount - The amount to format\n * @param options - Formatting options\n * @returns Formatted Rupiah string\n *\n * @example\n * Basic formatting:\n * ```typescript\n * formatRupiah(1500000); // 'Rp 1.500.000'\n * ```\n *\n * @example\n * With decimals:\n * ```typescript\n * formatRupiah(1500000.50, { decimal: true }); // 'Rp 1.500.000,50'\n * ```\n *\n * @example\n * Without symbol:\n * ```typescript\n * formatRupiah(1500000, { symbol: false }); // '1.500.000'\n * ```\n *\n * @example\n * Custom separators:\n * ```typescript\n * formatRupiah(1500000, { separator: ',' }); // 'Rp 1,500,000'\n * ```\n *\n * @public\n */\nexport function formatRupiah(amount: number, options?: RupiahOptions): string {\n const {\n symbol = true,\n decimal = false,\n separator = '.',\n decimalSeparator = ',',\n spaceAfterSymbol = true,\n } = options || {};\n\n // Default precision: 2 for decimals, 0 otherwise\n const precision =\n options?.precision !== undefined ? options.precision : decimal ? 2 : 0;\n\n const isNegative = amount < 0 && amount !== 0;\n const absAmount = Math.abs(amount);\n\n let result: string;\n\n if (decimal) {\n const factor = Math.pow(10, precision);\n const rounded = Math.round(absAmount * factor) / factor;\n\n if (precision > 0) {\n const [intPart, decPart] = rounded.toFixed(precision).split('.');\n const formattedInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator);\n result = `${formattedInt}${decimalSeparator}${decPart}`;\n } else {\n const intPart = rounded.toString();\n result = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator);\n }\n } else {\n const intAmount = Math.floor(absAmount);\n result = intAmount.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator);\n }\n\n if (symbol) {\n const space = spaceAfterSymbol ? ' ' : '';\n if (isNegative) {\n result = `-Rp${space}${result}`;\n } else {\n result = `Rp${space}${result}`;\n }\n } else if (isNegative) {\n result = `-${result}`;\n }\n\n return result;\n}\n\n/**\n * Formats a number in compact Indonesian format.\n *\n * Uses Indonesian units: ribu, juta, miliar, triliun.\n * Follows Indonesian grammar rules (e.g., \"1 juta\" not \"1,0 juta\").\n *\n * @param amount - The amount to format\n * @param options - Compact formatting options\n * @returns Compact formatted string\n *\n * @example\n * Millions:\n * ```typescript\n * formatCompact(1500000); // 'Rp 1,5 juta'\n * formatCompact(1000000); // 'Rp 1 juta'\n * ```\n *\n * @example\n * Thousands:\n * ```typescript\n * formatCompact(500000); // 'Rp 500 ribu'\n * ```\n *\n * @example\n * Small numbers:\n * ```typescript\n * formatCompact(1500); // 'Rp 1.500'\n * ```\n *\n * @example\n * Without symbol:\n * ```typescript\n * formatCompact(1500000, { symbol: false }); // '1,5 juta'\n * ```\n *\n * @public\n */\nexport function formatCompact(\n amount: number,\n options?: CompactOptions\n): string {\n const { symbol = true, spaceAfterSymbol = true } = options || {};\n\n const isNegative = amount < 0 && amount !== 0;\n const abs = Math.abs(amount);\n\n let result: string;\n\n if (abs >= 1_000_000_000_000) {\n result = formatCompactValue(abs / 1_000_000_000_000, 'triliun');\n } else if (abs >= 1_000_000_000) {\n result = formatCompactValue(abs / 1_000_000_000, 'miliar');\n } else if (abs >= 1_000_000) {\n result = formatCompactValue(abs / 1_000_000, 'juta');\n } else if (abs >= 100_000) {\n result = formatCompactValue(abs / 1000, 'ribu');\n } else if (abs >= 1_000) {\n result = abs.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n } else {\n result = abs.toString();\n }\n\n if (symbol) {\n const space = spaceAfterSymbol ? ' ' : '';\n if (isNegative) {\n result = `-Rp${space}${result}`;\n } else {\n result = `Rp${space}${result}`;\n }\n } else if (isNegative) {\n result = `-${result}`;\n }\n\n return result;\n}\n\n/**\n * Formats a value with Indonesian unit, applying grammar rules.\n *\n * Automatically removes trailing \".0\" to follow proper Indonesian grammar.\n * For example: \"1 juta\" instead of \"1,0 juta\".\n *\n * @param value - The numeric value to format\n * @param unit - The Indonesian unit (ribu, juta, miliar, triliun)\n * @returns Formatted string with unit\n * @internal\n */\nfunction formatCompactValue(value: number, unit: string): string {\n const rounded = Math.round(value * 10) / 10;\n\n if (rounded % 1 === 0) {\n return `${rounded.toFixed(0)} ${unit}`;\n }\n\n return `${rounded.toString().replace('.', ',')} ${unit}`;\n}\n","/**\n * Currency parsing utilities for Indonesian Rupiah.\n *\n * @module currency/parse\n * @packageDocumentation\n */\n\n/**\n * Parses a formatted Rupiah string back to a number.\n *\n * Handles multiple formats:\n * - Standard: \"Rp 1.500.000\"\n * - No symbol: \"1.500.000\"\n * - With decimals: \"Rp 1.500.000,50\"\n * - Compact: \"Rp 1,5 juta\", \"Rp 500 ribu\"\n *\n * @param formatted - The formatted Rupiah string to parse\n * @returns Parsed number, or null if invalid\n *\n * @example\n * Standard format:\n * ```typescript\n * parseRupiah('Rp 1.500.000'); // 1500000\n * ```\n *\n * @example\n * With decimals:\n * ```typescript\n * parseRupiah('Rp 1.500.000,50'); // 1500000.50\n * ```\n *\n * @example\n * Compact format:\n * ```typescript\n * parseRupiah('Rp 1,5 juta'); // 1500000\n * parseRupiah('Rp 500 ribu'); // 500000\n * ```\n *\n * @example\n * Invalid input:\n * ```typescript\n * parseRupiah('invalid'); // null\n * ```\n *\n * @public\n */\nexport function parseRupiah(formatted: string): number | null {\n if (!formatted || typeof formatted !== 'string') {\n return null;\n }\n\n const cleaned = formatted.trim().toLowerCase();\n\n // Check for compact units (juta, ribu, miliar, triliun)\n const compactUnits = {\n triliun: 1_000_000_000_000,\n miliar: 1_000_000_000,\n juta: 1_000_000,\n ribu: 1_000,\n };\n\n for (const [unit, multiplier] of Object.entries(compactUnits)) {\n if (cleaned.includes(unit)) {\n const match = cleaned.match(/(-?\\d+[,.]?\\d*)/);\n if (match) {\n const num = parseFloat(match[1].replace(',', '.'));\n return num * multiplier;\n }\n }\n }\n\n // Standard format: remove 'Rp' and spaces\n let numStr = cleaned.replace(/rp/gi, '').trim();\n\n const hasDot = numStr.includes('.');\n const hasComma = numStr.includes(',');\n\n if (hasDot && hasComma) {\n // Determine format based on last separator position\n // Indonesian: 1.500.000,50 vs International: 1,500,000.50\n const lastDot = numStr.lastIndexOf('.');\n const lastComma = numStr.lastIndexOf(',');\n\n if (lastComma > lastDot) {\n numStr = numStr.replace(/\\./g, '').replace(',', '.');\n } else {\n numStr = numStr.replace(/,/g, '');\n }\n } else if (hasComma) {\n const parts = numStr.split(',');\n // Decimal if only 1-2 digits after comma\n if (parts.length === 2 && parts[1].length <= 2) {\n numStr = numStr.replace(',', '.');\n } else {\n numStr = numStr.replace(/,/g, '');\n }\n } else if (hasDot) {\n const parts = numStr.split('.');\n // If not decimal format, remove dots (thousands separator)\n if (parts.length > 2 || (parts.length === 2 && parts[1].length > 2)) {\n numStr = numStr.replace(/\\./g, '');\n }\n }\n\n const parsed = parseFloat(numStr);\n return isNaN(parsed) ? null : parsed;\n}\n","/**\n * Convert numbers to Indonesian words (terbilang).\n *\n * @module currency/words\n * @packageDocumentation\n */\n\nimport type { WordOptions } from './types';\n\n/**\n * Basic Indonesian number words (0-9).\n * @internal\n */\nconst BASIC_NUMBERS = [\n '',\n 'satu',\n 'dua',\n 'tiga',\n 'empat',\n 'lima',\n 'enam',\n 'tujuh',\n 'delapan',\n 'sembilan',\n];\n\n/**\n * Indonesian words for 10-19.\n * @internal\n */\nconst TEENS = [\n 'sepuluh',\n 'sebelas',\n 'dua belas',\n 'tiga belas',\n 'empat belas',\n 'lima belas',\n 'enam belas',\n 'tujuh belas',\n 'delapan belas',\n 'sembilan belas',\n];\n\n/**\n * Indonesian words for tens (20, 30, 40, etc).\n * @internal\n */\nconst TENS = [\n '',\n '',\n 'dua puluh',\n 'tiga puluh',\n 'empat puluh',\n 'lima puluh',\n 'enam puluh',\n 'tujuh puluh',\n 'delapan puluh',\n 'sembilan puluh',\n];\n\n/**\n * Converts a number to Indonesian words (terbilang).\n *\n * Supports numbers up to trillions (triliun).\n * Follows Indonesian language rules for number pronunciation.\n *\n * Special rules:\n * - 1 = \"satu\" in most cases, but \"se-\" for 100, 1000\n * - 11 = \"sebelas\" (not \"satu belas\")\n * - 100 = \"seratus\" (not \"satu ratus\")\n * - 1000 = \"seribu\" (not \"satu ribu\")\n *\n * @param amount - The number to convert\n * @param options - Conversion options\n * @returns Indonesian words representation\n *\n * @example\n * Basic numbers:\n * ```typescript\n * toWords(123); // 'seratus dua puluh tiga rupiah'\n * ```\n *\n * @example\n * Large numbers:\n * ```typescript\n * toWords(1500000); // 'satu juta lima ratus ribu rupiah'\n * ```\n *\n * @example\n * With options:\n * ```typescript\n * toWords(1500000, { uppercase: true });\n * // 'Satu juta lima ratus ribu rupiah'\n *\n * toWords(1500000, { withCurrency: false });\n * // 'satu juta lima ratus ribu'\n * ```\n *\n * @public\n */\nexport function toWords(amount: number, options?: WordOptions): string {\n const {\n uppercase = false,\n withCurrency = true,\n withDecimals = false,\n } = options || {};\n\n if (amount === 0) {\n let result = 'nol';\n if (withCurrency) result += ' rupiah';\n return uppercase ? capitalize(result) : result;\n }\n\n const isNegative = amount < 0;\n const absAmount = Math.abs(amount);\n const intPart = Math.floor(absAmount);\n\n let words = convertInteger(intPart);\n\n if (isNegative) {\n words = 'minus ' + words;\n }\n\n if (withCurrency) {\n words += ' rupiah';\n }\n\n if (withDecimals) {\n const decimalPart = Math.round((absAmount - intPart) * 100);\n if (decimalPart > 0) {\n words += ' koma ' + convertDecimal(decimalPart);\n }\n }\n\n return uppercase ? capitalize(words) : words;\n}\n\n/**\n * Converts the integer part to Indonesian words.\n */\nfunction convertInteger(num: number): string {\n if (num === 0) return 'nol';\n\n let words = '';\n\n const triliun = Math.floor(num / 1_000_000_000_000);\n const miliar = Math.floor((num % 1_000_000_000_000) / 1_000_000_000);\n const juta = Math.floor((num % 1_000_000_000) / 1_000_000);\n const ribu = Math.floor((num % 1_000_000) / 1_000);\n const sisa = num % 1_000;\n\n if (triliun > 0) {\n words += convertGroup(triliun) + ' triliun';\n }\n\n if (miliar > 0) {\n if (words) words += ' ';\n words += convertGroup(miliar) + ' miliar';\n }\n\n if (juta > 0) {\n if (words) words += ' ';\n words += convertGroup(juta) + ' juta';\n }\n\n if (ribu > 0) {\n if (words) words += ' ';\n words += ribu === 1 ? 'seribu' : convertGroup(ribu) + ' ribu';\n }\n\n if (sisa > 0) {\n if (words) words += ' ';\n words += convertGroup(sisa);\n }\n\n return words;\n}\n\n/**\n * Converts decimal part (0-99) to Indonesian words.\n */\nfunction convertDecimal(num: number): string {\n if (num === 0) return '';\n if (num < 10) return BASIC_NUMBERS[num];\n if (num < 20) return TEENS[num - 10];\n\n const tens = Math.floor(num / 10);\n const ones = num % 10;\n\n let result = TENS[tens];\n if (ones > 0) {\n result += ' ' + BASIC_NUMBERS[ones];\n }\n\n return result;\n}\n\n/**\n * Converts a group of 1-3 digits (0-999) to Indonesian words.\n *\n * @param num - Number to convert (0-999)\n * @returns Indonesian words for the number\n * @internal\n */\nfunction convertGroup(num: number): string {\n if (num === 0) return '';\n\n let result = '';\n\n const hundreds = Math.floor(num / 100);\n if (hundreds > 0) {\n // Special rule: 100 = \"seratus\" not \"satu ratus\"\n result = hundreds === 1 ? 'seratus' : BASIC_NUMBERS[hundreds] + ' ratus';\n }\n\n const remainder = num % 100;\n if (remainder > 0) {\n if (result) result += ' ';\n result += convertTwoDigits(remainder);\n }\n\n return result;\n}\n\n/**\n * Converts numbers 1-99 to Indonesian words.\n *\n * @param num - Number to convert (1-99)\n * @returns Indonesian words for the number\n * @internal\n */\nfunction convertTwoDigits(num: number): string {\n if (num === 0) return '';\n if (num < 10) return BASIC_NUMBERS[num];\n if (num >= 10 && num < 20) return TEENS[num - 10];\n\n const tens = Math.floor(num / 10);\n const ones = num % 10;\n\n let result = TENS[tens];\n if (ones > 0) {\n result += ' ' + BASIC_NUMBERS[ones];\n }\n\n return result;\n}\n\n/**\n * Capitalizes the first letter of a string.\n *\n * @param str - String to capitalize\n * @returns String with first letter capitalized\n * @internal\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n","/**\n * Currency utility functions.\n *\n * @module currency/utils\n * @packageDocumentation\n */\n\nimport { formatRupiah } from './format';\nimport type { RoundUnit, RupiahOptions } from './types';\n\n/**\n * Rounds a number to a clean currency amount.\n *\n * Common use case: displaying approximate prices or budgets\n * in clean, rounded numbers.\n *\n * @param amount - The amount to round\n * @param unit - The unit to round to (default: 'ribu')\n * @returns Rounded amount\n *\n * @example\n * Round to thousands:\n * ```typescript\n * roundToClean(1234567, 'ribu'); // 1235000\n * ```\n *\n * @example\n * Round to hundred thousands:\n * ```typescript\n * roundToClean(1234567, 'ratus-ribu'); // 1200000\n * ```\n *\n * @example\n * Round to millions:\n * ```typescript\n * roundToClean(1234567, 'juta'); // 1000000\n * ```\n *\n * @public\n */\nexport function roundToClean(amount: number, unit: RoundUnit = 'ribu'): number {\n const divisors: Record<RoundUnit, number> = {\n ribu: 1000,\n 'ratus-ribu': 100000,\n juta: 1000000,\n };\n\n const divisor = divisors[unit];\n\n // Math.round handles both positive and negative numbers\n return Math.round(amount / divisor) * divisor;\n}\n\n/**\n * Formats a number as Indonesian Rupiah in accounting style.\n * Negative numbers are wrapped in parentheses.\n *\n * @param amount - The amount to format\n * @param options - Formatting options\n * @returns Formatted accounting string\n *\n * @example\n * ```typescript\n * formatAccounting(-1500000); // '(Rp 1.500.000)'\n * ```\n */\nexport function formatAccounting(\n amount: number,\n options?: RupiahOptions\n): string {\n const isNegative = amount < 0;\n const formatted = formatRupiah(Math.abs(amount), options);\n\n if (isNegative) {\n return `(${formatted})`;\n }\n\n return formatted;\n}\n\n/**\n * Calculates tax (PPN) for a given amount.\n *\n * @param amount - The base amount\n * @param rate - The tax rate (e.g., 0.11 for 11% PPN)\n * @returns The calculated tax amount\n *\n * @example\n * ```typescript\n * calculateTax(1000000, 0.11); // 110000\n * ```\n */\nexport function calculateTax(amount: number, rate: number): number {\n return amount * rate;\n}\n\n/**\n * Helper to ensure a string or number has the 'Rp ' prefix.\n * If already prefixed, it returns the input as is.\n *\n * @param amount - The amount or formatted string\n * @returns String with Rupiah prefix\n */\nexport function addRupiahSymbol(amount: string | number): string {\n if (typeof amount === 'number') {\n const formatted = amount.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n return `Rp ${formatted}`;\n }\n\n if (amount.trim().startsWith('Rp')) {\n return amount.trim();\n }\n\n return `Rp ${amount.trim()}`;\n}\n","import type { RoundUnit, SplitOptions } from './types';\n\n/**\n * Invalid split error thrown when split parameters are invalid.\n *\n * @public\n */\nexport class InvalidSplitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidSplitError';\n }\n}\n\n/**\n * Splits an amount into equal or custom-ratio parts.\n *\n * @param amount - The amount to split\n * @param parts - Number of parts to split into\n * @param options - Split options (ratios, rounding)\n * @returns Array of split amounts\n *\n * @example\n * Equal split:\n * ```typescript\n * splitAmount(1500000, 3); // [500000, 500000, 500000]\n * ```\n *\n * @example\n * Custom ratios:\n * ```typescript\n * splitAmount(1000000, 2, { ratios: [70, 30] }); // [700000, 300000]\n * ```\n *\n * @example\n * With rounding:\n * ```typescript\n * splitAmount(1234567, 3, { roundTo: 'ribu' }); // [412000, 411000, 411000]\n * ```\n *\n * @public\n */\nexport function splitAmount(\n amount: number,\n parts: number,\n options?: SplitOptions\n): number[] {\n if (parts < 1) {\n throw new InvalidSplitError('Parts must be at least 1');\n }\n\n if (parts === 1) {\n return [amount];\n }\n\n const { ratios, roundTo } = options || {};\n\n if (ratios) {\n if (ratios.length !== parts) {\n throw new InvalidSplitError(\n `Ratios length (${ratios.length}) must match parts count (${parts})`\n );\n }\n\n const sum = ratios.reduce((a, b) => a + b, 0);\n if (Math.abs(sum - 100) > 0.01) {\n throw new InvalidSplitError(`Ratios must sum to 100 (got ${sum})`);\n }\n\n let result = ratios.map((r) => amount * (r / 100));\n\n if (roundTo) {\n result = result.map((v) => roundToClean(v, roundTo));\n }\n\n return result;\n }\n\n const base = Math.floor(amount / parts);\n const remainder = amount - base * parts;\n\n const result: number[] = [];\n for (let i = 0; i < parts; i++) {\n result.push(base + (i < remainder ? 1 : 0));\n }\n\n if (roundTo) {\n return result.map((v) => roundToClean(v, roundTo));\n }\n\n return result;\n}\n\n/**\n * Calculates what percentage a part is of a total.\n *\n * @param part - The part value\n * @param total - The total value\n * @returns Percentage as number (e.g., 15 for 15%)\n *\n * @example\n * ```typescript\n * percentageOf(150000, 1000000); // 15\n * percentageOf(0, 1000000); // 0\n * percentageOf(100, 0); // 0 (not NaN)\n * ```\n *\n * @public\n */\nexport function percentageOf(part: number, total: number): number {\n if (total === 0) return 0;\n return (part / total) * 100;\n}\n\n/**\n * Calculates absolute and percentage difference between two amounts.\n *\n * @param amount1 - The new/current amount\n * @param amount2 - The original/reference amount\n * @returns Object with absolute difference, percentage, and direction\n *\n * @example\n * ```typescript\n * difference(1200000, 1000000);\n * // { absolute: 200000, percentage: 20, direction: 'increase' }\n *\n * difference(0, 1000000);\n * // { absolute: -1000000, percentage: null, direction: 'decrease' }\n * ```\n *\n * @public\n */\nexport function difference(\n amount1: number,\n amount2: number\n): {\n absolute: number;\n percentage: number | null;\n direction: 'increase' | 'decrease' | 'same';\n} {\n const absolute = amount1 - amount2;\n\n let percentage: number | null;\n if (amount2 === 0) {\n percentage = amount1 === 0 ? 0 : null;\n } else {\n percentage = (absolute / amount2) * 100;\n }\n\n const direction: 'increase' | 'decrease' | 'same' =\n absolute > 0 ? 'increase' : absolute < 0 ? 'decrease' : 'same';\n\n return { absolute, percentage, direction };\n}\n\n/**\n * Rounds a number to a clean currency amount.\n * Internal helper — also exported from utils.ts for public API.\n */\nfunction roundToClean(amount: number, unit: RoundUnit): number {\n const divisors: Record<RoundUnit, number> = {\n ribu: 1000,\n 'ratus-ribu': 100000,\n juta: 1000000,\n };\n\n return Math.round(amount / divisors[unit]) * divisors[unit];\n}\n","/**\n * Validates whether a string is a valid Rupiah format.\n *\n * Accepts standard, compact, and negative formats.\n *\n * @param formatted - The string to validate\n * @returns `true` if valid Rupiah format, `false` otherwise\n *\n * @example\n * ```typescript\n * validateRupiah('Rp 1.500.000'); // true\n * validateRupiah('1.500.000'); // true\n * validateRupiah('Rp 1,5 juta'); // true\n * validateRupiah('abc'); // false\n * validateRupiah(''); // false\n * ```\n *\n * @public\n */\nexport function validateRupiah(formatted: string): boolean {\n if (!formatted || typeof formatted !== 'string') {\n return false;\n }\n\n const trimmed = formatted.trim();\n\n if (!trimmed) return false;\n\n const compactUnits = ['triliun', 'miliar', 'juta', 'ribu'];\n\n // Check for compact format\n for (const unit of compactUnits) {\n if (trimmed.toLowerCase().includes(unit)) {\n return /-?\\d+[,.]?\\d*\\s*(ribu|juta|miliar|triliun)/i.test(trimmed);\n }\n }\n\n // Standard format: remove optional Rp prefix and optional negative sign\n let cleaned = trimmed.replace(/^(-?\\s*)?Rp\\s*/i, '');\n\n // Remove negative sign if still present\n cleaned = cleaned.replace(/^\\s*-/, '');\n\n cleaned = cleaned.trim();\n\n if (!cleaned) return false;\n\n // Must be digits with dots and/or commas\n if (!/^[0-9.,]+$/.test(cleaned)) return false;\n\n // Must have at least one digit\n if (!/\\d/.test(cleaned)) return false;\n\n return true;\n}\n"]}
|