iso-price 1.0.3
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/LICENSE +21 -0
- package/dist/contract/index.d.ts +25 -0
- package/dist/contract/index.js +60 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/domain.objects/IsoCurrency.d.ts +63 -0
- package/dist/domain.objects/IsoCurrency.js +70 -0
- package/dist/domain.objects/IsoCurrency.js.map +1 -0
- package/dist/domain.objects/IsoPrice.d.ts +16 -0
- package/dist/domain.objects/IsoPrice.js +3 -0
- package/dist/domain.objects/IsoPrice.js.map +1 -0
- package/dist/domain.objects/IsoPriceExponent.d.ts +28 -0
- package/dist/domain.objects/IsoPriceExponent.js +33 -0
- package/dist/domain.objects/IsoPriceExponent.js.map +1 -0
- package/dist/domain.objects/IsoPriceHuman.d.ts +19 -0
- package/dist/domain.objects/IsoPriceHuman.js +3 -0
- package/dist/domain.objects/IsoPriceHuman.js.map +1 -0
- package/dist/domain.objects/IsoPriceRoundMode.d.ts +23 -0
- package/dist/domain.objects/IsoPriceRoundMode.js +28 -0
- package/dist/domain.objects/IsoPriceRoundMode.js.map +1 -0
- package/dist/domain.objects/IsoPriceShape.d.ts +30 -0
- package/dist/domain.objects/IsoPriceShape.js +3 -0
- package/dist/domain.objects/IsoPriceShape.js.map +1 -0
- package/dist/domain.objects/IsoPriceWords.d.ts +20 -0
- package/dist/domain.objects/IsoPriceWords.js +3 -0
- package/dist/domain.objects/IsoPriceWords.js.map +1 -0
- package/dist/domain.operations/arithmetic/allocatePrice.d.ts +48 -0
- package/dist/domain.operations/arithmetic/allocatePrice.js +167 -0
- package/dist/domain.operations/arithmetic/allocatePrice.js.map +1 -0
- package/dist/domain.operations/arithmetic/dividePrice.d.ts +40 -0
- package/dist/domain.operations/arithmetic/dividePrice.js +127 -0
- package/dist/domain.operations/arithmetic/dividePrice.js.map +1 -0
- package/dist/domain.operations/arithmetic/multiplyPrice.d.ts +38 -0
- package/dist/domain.operations/arithmetic/multiplyPrice.js +89 -0
- package/dist/domain.operations/arithmetic/multiplyPrice.js.map +1 -0
- package/dist/domain.operations/arithmetic/subPrices.d.ts +28 -0
- package/dist/domain.operations/arithmetic/subPrices.js +62 -0
- package/dist/domain.operations/arithmetic/subPrices.js.map +1 -0
- package/dist/domain.operations/arithmetic/sumPrices.d.ts +44 -0
- package/dist/domain.operations/arithmetic/sumPrices.js +88 -0
- package/dist/domain.operations/arithmetic/sumPrices.js.map +1 -0
- package/dist/domain.operations/cast/asIsoPrice.d.ts +35 -0
- package/dist/domain.operations/cast/asIsoPrice.js +117 -0
- package/dist/domain.operations/cast/asIsoPrice.js.map +1 -0
- package/dist/domain.operations/cast/asIsoPriceHuman.d.ts +25 -0
- package/dist/domain.operations/cast/asIsoPriceHuman.js +106 -0
- package/dist/domain.operations/cast/asIsoPriceHuman.js.map +1 -0
- package/dist/domain.operations/cast/asIsoPriceShape.d.ts +25 -0
- package/dist/domain.operations/cast/asIsoPriceShape.js +164 -0
- package/dist/domain.operations/cast/asIsoPriceShape.js.map +1 -0
- package/dist/domain.operations/cast/asIsoPriceWords.d.ts +25 -0
- package/dist/domain.operations/cast/asIsoPriceWords.js +103 -0
- package/dist/domain.operations/cast/asIsoPriceWords.js.map +1 -0
- package/dist/domain.operations/guard/isIsoPrice.d.ts +18 -0
- package/dist/domain.operations/guard/isIsoPrice.js +29 -0
- package/dist/domain.operations/guard/isIsoPrice.js.map +1 -0
- package/dist/domain.operations/guard/isIsoPriceHuman.d.ts +24 -0
- package/dist/domain.operations/guard/isIsoPriceHuman.js +76 -0
- package/dist/domain.operations/guard/isIsoPriceHuman.js.map +1 -0
- package/dist/domain.operations/guard/isIsoPriceShape.d.ts +29 -0
- package/dist/domain.operations/guard/isIsoPriceShape.js +50 -0
- package/dist/domain.operations/guard/isIsoPriceShape.js.map +1 -0
- package/dist/domain.operations/guard/isIsoPriceWords.d.ts +24 -0
- package/dist/domain.operations/guard/isIsoPriceWords.js +48 -0
- package/dist/domain.operations/guard/isIsoPriceWords.js.map +1 -0
- package/dist/domain.operations/precision/getIsoPriceExponentByCurrency.d.ts +15 -0
- package/dist/domain.operations/precision/getIsoPriceExponentByCurrency.js +49 -0
- package/dist/domain.operations/precision/getIsoPriceExponentByCurrency.js.map +1 -0
- package/dist/domain.operations/precision/roundPrice.d.ts +25 -0
- package/dist/domain.operations/precision/roundPrice.js +24 -0
- package/dist/domain.operations/precision/roundPrice.js.map +1 -0
- package/dist/domain.operations/precision/setPricePrecision.d.ts +29 -0
- package/dist/domain.operations/precision/setPricePrecision.js +119 -0
- package/dist/domain.operations/precision/setPricePrecision.js.map +1 -0
- package/dist/domain.operations/statistics/calcPriceAvg.d.ts +21 -0
- package/dist/domain.operations/statistics/calcPriceAvg.js +92 -0
- package/dist/domain.operations/statistics/calcPriceAvg.js.map +1 -0
- package/dist/domain.operations/statistics/calcPriceStdev.d.ts +23 -0
- package/dist/domain.operations/statistics/calcPriceStdev.js +137 -0
- package/dist/domain.operations/statistics/calcPriceStdev.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/license.md +21 -0
- package/package.json +102 -0
- package/readme.md +373 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.priceAdd = exports.priceSum = exports.addPrices = exports.sumPrices = void 0;
|
|
4
|
+
const helpful_errors_1 = require("helpful-errors");
|
|
5
|
+
const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
|
|
6
|
+
const asIsoPriceShape_1 = require("../cast/asIsoPriceShape");
|
|
7
|
+
const asIsoPriceWords_1 = require("../cast/asIsoPriceWords");
|
|
8
|
+
/**
|
|
9
|
+
* .what = gets the numeric exponent value from exponent string
|
|
10
|
+
* .why = needed for precision comparison and normalization
|
|
11
|
+
*/
|
|
12
|
+
const getExponentValue = (exponent) => {
|
|
13
|
+
const match = exponent.match(/\^(-?\d+)$/);
|
|
14
|
+
if (!match)
|
|
15
|
+
return -2; // default to centi
|
|
16
|
+
return parseInt(match[1], 10);
|
|
17
|
+
};
|
|
18
|
+
function sumPrices(...args) {
|
|
19
|
+
// parse args: array syntax vs spread syntax
|
|
20
|
+
let prices;
|
|
21
|
+
let options;
|
|
22
|
+
if (Array.isArray(args[0])) {
|
|
23
|
+
prices = args[0];
|
|
24
|
+
options = args[1];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
prices = args;
|
|
28
|
+
options = undefined;
|
|
29
|
+
}
|
|
30
|
+
// handle empty array
|
|
31
|
+
if (prices.length === 0) {
|
|
32
|
+
throw new helpful_errors_1.BadRequestError('cannot sum empty price array', { prices });
|
|
33
|
+
}
|
|
34
|
+
// convert all to shapes
|
|
35
|
+
const shapes = prices.map((p) => (0, asIsoPriceShape_1.asIsoPriceShape)(p));
|
|
36
|
+
// verify all currencies match
|
|
37
|
+
const currency = shapes[0].currency;
|
|
38
|
+
const mismatch = shapes.find((s) => s.currency !== currency);
|
|
39
|
+
if (mismatch) {
|
|
40
|
+
throw new helpful_errors_1.BadRequestError('currency mismatch in price sum', {
|
|
41
|
+
expected: currency,
|
|
42
|
+
found: mismatch.currency,
|
|
43
|
+
prices,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// find the highest precision (lowest exponent value)
|
|
47
|
+
const exponents = shapes.map((s) => s.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI);
|
|
48
|
+
const exponentValues = exponents.map(getExponentValue);
|
|
49
|
+
const targetExponentValue = Math.min(...exponentValues);
|
|
50
|
+
const targetExponent = exponents[exponentValues.indexOf(targetExponentValue)];
|
|
51
|
+
// normalize all amounts to target precision and sum
|
|
52
|
+
let total = 0n;
|
|
53
|
+
for (let i = 0; i < shapes.length; i++) {
|
|
54
|
+
const shape = shapes[i];
|
|
55
|
+
const currentExp = getExponentValue(shape.exponent ?? IsoPriceExponent_1.IsoPriceExponent.CENTI);
|
|
56
|
+
const shift = currentExp - targetExponentValue;
|
|
57
|
+
const normalizedAmount = shape.amount * 10n ** BigInt(shift);
|
|
58
|
+
total += normalizedAmount;
|
|
59
|
+
}
|
|
60
|
+
// build result shape
|
|
61
|
+
const resultShape = {
|
|
62
|
+
amount: total,
|
|
63
|
+
currency,
|
|
64
|
+
exponent: targetExponent,
|
|
65
|
+
};
|
|
66
|
+
// return in requested format
|
|
67
|
+
if (options?.format === 'shape') {
|
|
68
|
+
return resultShape;
|
|
69
|
+
}
|
|
70
|
+
return (0, asIsoPriceWords_1.asIsoPriceWords)(resultShape);
|
|
71
|
+
}
|
|
72
|
+
exports.sumPrices = sumPrices;
|
|
73
|
+
/**
|
|
74
|
+
* .what = alias for sumPrices
|
|
75
|
+
* .why = provides semantic name for binary addition
|
|
76
|
+
*/
|
|
77
|
+
exports.addPrices = sumPrices;
|
|
78
|
+
/**
|
|
79
|
+
* .what = alias for sumPrices
|
|
80
|
+
* .why = provides price-prefixed variant
|
|
81
|
+
*/
|
|
82
|
+
exports.priceSum = sumPrices;
|
|
83
|
+
/**
|
|
84
|
+
* .what = alias for sumPrices
|
|
85
|
+
* .why = provides price-prefixed variant
|
|
86
|
+
*/
|
|
87
|
+
exports.priceAdd = sumPrices;
|
|
88
|
+
//# sourceMappingURL=sumPrices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sumPrices.js","sourceRoot":"","sources":["../../../src/domain.operations/arithmetic/sumPrices.ts"],"names":[],"mappings":";;;AAAA,mDAAiD;AAGjD,4EAAyE;AAGzE,6DAA0D;AAC1D,6DAA0D;AAE1D;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAmC,EAAU,EAAE;IACvE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,CAAC,CAAC,mBAAmB;IAC1C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC,CAAC;AAgCF,SAAgB,SAAS,CACvB,GAAG,IAEiC;IAEpC,4CAA4C;IAC5C,IAAI,MAA6B,CAAC;IAClC,IAAI,OAAmD,CAAC;IAExD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,MAAM,GAAG,IAAI,CAAC,CAAC,CAA0B,CAAC;QAC1C,OAAO,GAAG,IAAI,CAAC,CAAC,CAA+C,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,IAA6B,CAAC;QACvC,OAAO,GAAG,SAAS,CAAC;IACtB,CAAC;IAED,qBAAqB;IACrB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,gCAAe,CAAC,8BAA8B,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,wBAAwB;IACxB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,iCAAe,EAAC,CAAC,CAAC,CAAC,CAAC;IAErD,8BAA8B;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,QAAqB,CAAC;IAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;IAC7D,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,gCAAe,CAAC,gCAAgC,EAAE;YAC1D,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,QAAQ,CAAC,QAAQ;YACxB,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED,qDAAqD;IACrD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAC1B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CACtB,CAAC;IACxB,MAAM,cAAc,GAAG,SAAS,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACvD,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC;IACxD,MAAM,cAAc,GAClB,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAE,CAAC;IAE1D,oDAAoD;IACpD,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACzB,MAAM,UAAU,GAAG,gBAAgB,CACjC,KAAK,CAAC,QAAQ,IAAI,mCAAgB,CAAC,KAAK,CACzC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,GAAG,mBAAmB,CAAC;QAC/C,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7D,KAAK,IAAI,gBAAgB,CAAC;IAC5B,CAAC;IAED,qBAAqB;IACrB,MAAM,WAAW,GAA6B;QAC5C,MAAM,EAAE,KAAK;QACb,QAAQ;QACR,QAAQ,EAAE,cAAc;KACzB,CAAC;IAEF,6BAA6B;IAC7B,IAAI,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,IAAA,iCAAe,EAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AArED,8BAqEC;AAED;;;GAGG;AACU,QAAA,SAAS,GAAG,SAAS,CAAC;AAEnC;;;GAGG;AACU,QAAA,QAAQ,GAAG,SAAS,CAAC;AAElC;;;GAGG;AACU,QAAA,QAAQ,GAAG,SAAS,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IsoPrice } from '../../domain.objects/IsoPrice';
|
|
2
|
+
import type { IsoPriceWords } from '../../domain.objects/IsoPriceWords';
|
|
3
|
+
/**
|
|
4
|
+
* .what = normalizes any price input to IsoPriceWords format
|
|
5
|
+
* .why = provides a single entry point for price conversion
|
|
6
|
+
*
|
|
7
|
+
* this is the recommended entry point for all price conversion:
|
|
8
|
+
* - accepts human format: '$50.37', '€100.00'
|
|
9
|
+
* - accepts words format: 'USD 50.37', 'EUR 100.00'
|
|
10
|
+
* - accepts shape format: { amount: 5037n, currency: 'USD' }
|
|
11
|
+
* - normalizes commas to underscores in output
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* asIsoPrice('$50.37')
|
|
15
|
+
* // => 'USD 50.37'
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* asIsoPrice('$50.37', { currency: 'CAD' })
|
|
19
|
+
* // => 'CAD 50.37'
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* asIsoPrice('€50.37', { currency: 'USD' })
|
|
23
|
+
* // throws BadRequestError (symbol/currency mismatch)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* asIsoPrice('USD 1,000,000.00')
|
|
27
|
+
* // => 'USD 1_000_000.00'
|
|
28
|
+
*/
|
|
29
|
+
export declare const asIsoPrice: <TCurrency extends string = string>(input: IsoPrice<TCurrency> | string | {
|
|
30
|
+
amount: number | bigint;
|
|
31
|
+
currency: TCurrency;
|
|
32
|
+
exponent?: string;
|
|
33
|
+
}, options?: {
|
|
34
|
+
currency?: TCurrency;
|
|
35
|
+
}) => IsoPriceWords<TCurrency>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.asIsoPrice = void 0;
|
|
4
|
+
const helpful_errors_1 = require("helpful-errors");
|
|
5
|
+
const isIsoPriceHuman_1 = require("../guard/isIsoPriceHuman");
|
|
6
|
+
const asIsoPriceWords_1 = require("./asIsoPriceWords");
|
|
7
|
+
/**
|
|
8
|
+
* .what = currency symbol to code mappings
|
|
9
|
+
* .why = validates symbol/currency consistency
|
|
10
|
+
*/
|
|
11
|
+
const SYMBOL_TO_CODE = {
|
|
12
|
+
$: 'USD',
|
|
13
|
+
'€': 'EUR',
|
|
14
|
+
'£': 'GBP',
|
|
15
|
+
'¥': 'JPY',
|
|
16
|
+
'₹': 'INR',
|
|
17
|
+
'₽': 'RUB',
|
|
18
|
+
'₩': 'KRW',
|
|
19
|
+
'₪': 'ILS',
|
|
20
|
+
'฿': 'THB',
|
|
21
|
+
'₫': 'VND',
|
|
22
|
+
kr: 'SEK',
|
|
23
|
+
R: 'ZAR',
|
|
24
|
+
zł: 'PLN',
|
|
25
|
+
Kč: 'CZK',
|
|
26
|
+
CHF: 'CHF',
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* .what = currencies that have unique, unambiguous symbols
|
|
30
|
+
* .why = throw on currency override if the target has a different unique symbol
|
|
31
|
+
*
|
|
32
|
+
* when a currency has a unique symbol (like EUR → €), a different input
|
|
33
|
+
* symbol (like $) with that currency is a clear mismatch.
|
|
34
|
+
*
|
|
35
|
+
* see `define.currency-symbols-lossy.md` for why symbols are ambiguous
|
|
36
|
+
*/
|
|
37
|
+
const UNIQUE_SYMBOL_CURRENCIES = {
|
|
38
|
+
EUR: '€',
|
|
39
|
+
// note: most other currencies share symbols ($ used by USD/CAD/AUD, etc.)
|
|
40
|
+
// so we only list currencies with truly unique symbols here
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* .what = detects the symbol used in a human format string
|
|
44
|
+
* .why = enables symbol/currency mismatch detection
|
|
45
|
+
*/
|
|
46
|
+
const detectSymbol = (value) => {
|
|
47
|
+
for (const symbol of Object.keys(SYMBOL_TO_CODE)) {
|
|
48
|
+
if (value.startsWith(symbol) ||
|
|
49
|
+
value.endsWith(symbol) ||
|
|
50
|
+
value.endsWith(` ${symbol}`)) {
|
|
51
|
+
return symbol;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* .what = normalizes any price input to IsoPriceWords format
|
|
58
|
+
* .why = provides a single entry point for price conversion
|
|
59
|
+
*
|
|
60
|
+
* this is the recommended entry point for all price conversion:
|
|
61
|
+
* - accepts human format: '$50.37', '€100.00'
|
|
62
|
+
* - accepts words format: 'USD 50.37', 'EUR 100.00'
|
|
63
|
+
* - accepts shape format: { amount: 5037n, currency: 'USD' }
|
|
64
|
+
* - normalizes commas to underscores in output
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* asIsoPrice('$50.37')
|
|
68
|
+
* // => 'USD 50.37'
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* asIsoPrice('$50.37', { currency: 'CAD' })
|
|
72
|
+
* // => 'CAD 50.37'
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* asIsoPrice('€50.37', { currency: 'USD' })
|
|
76
|
+
* // throws BadRequestError (symbol/currency mismatch)
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* asIsoPrice('USD 1,000,000.00')
|
|
80
|
+
* // => 'USD 1_000_000.00'
|
|
81
|
+
*/
|
|
82
|
+
const asIsoPrice = (input, options) => {
|
|
83
|
+
// validate symbol/currency consistency for human format
|
|
84
|
+
if (typeof input === 'string' && (0, isIsoPriceHuman_1.isIsoPriceHuman)(input)) {
|
|
85
|
+
const symbol = detectSymbol(input);
|
|
86
|
+
if (symbol && options?.currency) {
|
|
87
|
+
const expectedCode = SYMBOL_TO_CODE[symbol];
|
|
88
|
+
// check if input symbol belongs to a unique-symbol currency
|
|
89
|
+
const expectedUniqueSymbol = expectedCode
|
|
90
|
+
? UNIQUE_SYMBOL_CURRENCIES[expectedCode]
|
|
91
|
+
: undefined;
|
|
92
|
+
// check if override currency has a unique symbol
|
|
93
|
+
const targetUniqueSymbol = UNIQUE_SYMBOL_CURRENCIES[options.currency];
|
|
94
|
+
// if input symbol is unique to a currency, override must match that currency
|
|
95
|
+
// e.g., €50.37 + USD → throw (€ uniquely identifies EUR)
|
|
96
|
+
if (expectedUniqueSymbol && expectedCode !== options.currency) {
|
|
97
|
+
throw new helpful_errors_1.BadRequestError('symbol/currency mismatch', {
|
|
98
|
+
symbol,
|
|
99
|
+
expectedCode,
|
|
100
|
+
providedCurrency: options.currency,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// if target currency has a unique symbol that differs from input, throw
|
|
104
|
+
// e.g., $50.37 + EUR → throw (EUR requires €, not $)
|
|
105
|
+
if (targetUniqueSymbol && targetUniqueSymbol !== symbol) {
|
|
106
|
+
throw new helpful_errors_1.BadRequestError('symbol/currency mismatch', {
|
|
107
|
+
symbol,
|
|
108
|
+
expectedCode,
|
|
109
|
+
providedCurrency: options.currency,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return (0, asIsoPriceWords_1.asIsoPriceWords)(input, options);
|
|
115
|
+
};
|
|
116
|
+
exports.asIsoPrice = asIsoPrice;
|
|
117
|
+
//# sourceMappingURL=asIsoPrice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asIsoPrice.js","sourceRoot":"","sources":["../../../src/domain.operations/cast/asIsoPrice.ts"],"names":[],"mappings":";;;AAAA,mDAAiD;AAIjD,8DAA2D;AAC3D,uDAAoD;AAEpD;;;GAGG;AACH,MAAM,cAAc,GAA2B;IAC7C,CAAC,EAAE,KAAK;IACR,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,EAAE,EAAE,KAAK;IACT,CAAC,EAAE,KAAK;IACR,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,GAAG,EAAE,KAAK;CACX,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,wBAAwB,GAA2B;IACvD,GAAG,EAAE,GAAG;IACR,0EAA0E;IAC1E,4DAA4D;CAC7D,CAAC;AAEF;;;GAGG;AACH,MAAM,YAAY,GAAG,CAAC,KAAa,EAAiB,EAAE;IACpD,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QACjD,IACE,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;YACxB,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YACtB,KAAK,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,EAC5B,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACI,MAAM,UAAU,GAAG,CACxB,KAGuE,EACvE,OAAkC,EACR,EAAE;IAC5B,wDAAwD;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAA,iCAAe,EAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,MAAM,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;YAChC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YAC5C,4DAA4D;YAC5D,MAAM,oBAAoB,GAAG,YAAY;gBACvC,CAAC,CAAC,wBAAwB,CAAC,YAAY,CAAC;gBACxC,CAAC,CAAC,SAAS,CAAC;YACd,iDAAiD;YACjD,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAEtE,6EAA6E;YAC7E,yDAAyD;YACzD,IAAI,oBAAoB,IAAI,YAAY,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAC9D,MAAM,IAAI,gCAAe,CAAC,0BAA0B,EAAE;oBACpD,MAAM;oBACN,YAAY;oBACZ,gBAAgB,EAAE,OAAO,CAAC,QAAQ;iBACnC,CAAC,CAAC;YACL,CAAC;YAED,wEAAwE;YACxE,qDAAqD;YACrD,IAAI,kBAAkB,IAAI,kBAAkB,KAAK,MAAM,EAAE,CAAC;gBACxD,MAAM,IAAI,gCAAe,CAAC,0BAA0B,EAAE;oBACpD,MAAM;oBACN,YAAY;oBACZ,gBAAgB,EAAE,OAAO,CAAC,QAAQ;iBACnC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAA,iCAAe,EAAC,KAA4B,EAAE,OAAO,CAAC,CAAC;AAChE,CAAC,CAAC;AA1CW,QAAA,UAAU,cA0CrB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IsoPrice } from '../../domain.objects/IsoPrice';
|
|
2
|
+
import type { IsoPriceHuman } from '../../domain.objects/IsoPriceHuman';
|
|
3
|
+
/**
|
|
4
|
+
* .what = converts any IsoPrice format to IsoPriceHuman (symbol format)
|
|
5
|
+
* .why = enables human-readable display with currency symbols
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* asIsoPriceHuman('USD 50.37')
|
|
9
|
+
* // => '$50.37'
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* asIsoPriceHuman({ amount: 5037n, currency: 'USD' })
|
|
13
|
+
* // => '$50.37'
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* asIsoPriceHuman('USD 1_000_000.00')
|
|
17
|
+
* // => '$1,000,000.00'
|
|
18
|
+
*/
|
|
19
|
+
export declare const asIsoPriceHuman: <TCurrency extends string = string>(input: IsoPrice<TCurrency> | {
|
|
20
|
+
amount: number | bigint;
|
|
21
|
+
currency: TCurrency;
|
|
22
|
+
exponent?: string;
|
|
23
|
+
} | string, options?: {
|
|
24
|
+
currency?: TCurrency;
|
|
25
|
+
}) => IsoPriceHuman;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.asIsoPriceHuman = void 0;
|
|
4
|
+
const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
|
|
5
|
+
const getIsoPriceExponentByCurrency_1 = require("../precision/getIsoPriceExponentByCurrency");
|
|
6
|
+
const asIsoPriceShape_1 = require("./asIsoPriceShape");
|
|
7
|
+
/**
|
|
8
|
+
* .what = currency code to symbol mappings
|
|
9
|
+
* .why = enables conversion from iso 4217 codes to display symbols
|
|
10
|
+
*/
|
|
11
|
+
const CODE_TO_SYMBOL = {
|
|
12
|
+
USD: '$',
|
|
13
|
+
EUR: '€',
|
|
14
|
+
GBP: '£',
|
|
15
|
+
JPY: '¥',
|
|
16
|
+
CNY: '¥',
|
|
17
|
+
INR: '₹',
|
|
18
|
+
RUB: '₽',
|
|
19
|
+
KRW: '₩',
|
|
20
|
+
ILS: '₪',
|
|
21
|
+
THB: '฿',
|
|
22
|
+
VND: '₫',
|
|
23
|
+
SEK: 'kr',
|
|
24
|
+
NOK: 'kr',
|
|
25
|
+
DKK: 'kr',
|
|
26
|
+
ZAR: 'R',
|
|
27
|
+
BRL: 'R$',
|
|
28
|
+
PLN: 'zł',
|
|
29
|
+
CZK: 'Kč',
|
|
30
|
+
CHF: 'CHF',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* .what = gets the decimal places for an exponent
|
|
34
|
+
* .why = determines how to format the amount string
|
|
35
|
+
*/
|
|
36
|
+
const getDecimalPlaces = (exponent) => {
|
|
37
|
+
const match = exponent.match(/\^(-?\d+)$/);
|
|
38
|
+
if (!match)
|
|
39
|
+
return 2;
|
|
40
|
+
return Math.abs(parseInt(match[1], 10));
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* .what = formats a bigint amount to human-readable string with commas
|
|
44
|
+
* .why = converts 5037n with exponent 2 to '50.37'
|
|
45
|
+
*/
|
|
46
|
+
const formatAmountHuman = (amount, exponent = IsoPriceExponent_1.IsoPriceExponent.CENTI) => {
|
|
47
|
+
const decimalPlaces = getDecimalPlaces(exponent);
|
|
48
|
+
const isNegative = amount < 0n;
|
|
49
|
+
const absAmount = isNegative ? -amount : amount;
|
|
50
|
+
// handle zero decimal places (whole units like JPY)
|
|
51
|
+
if (decimalPlaces === 0) {
|
|
52
|
+
const formattedInt = absAmount
|
|
53
|
+
.toString()
|
|
54
|
+
.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
55
|
+
return isNegative ? `-${formattedInt}` : formattedInt;
|
|
56
|
+
}
|
|
57
|
+
// convert to string and pad with zeros at start if needed
|
|
58
|
+
const amountStr = absAmount.toString().padStart(decimalPlaces + 1, '0');
|
|
59
|
+
// split into integer and decimal parts
|
|
60
|
+
const intPart = amountStr.slice(0, -decimalPlaces) || '0';
|
|
61
|
+
const decPart = amountStr.slice(-decimalPlaces);
|
|
62
|
+
// format integer part with comma thousands separators (human format)
|
|
63
|
+
const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
64
|
+
// build the result
|
|
65
|
+
const sign = isNegative ? '-' : '';
|
|
66
|
+
// for whole numbers at centi precision, show .00
|
|
67
|
+
if (decPart === '00' && decimalPlaces === 2) {
|
|
68
|
+
return `${sign}${formattedInt}.00`;
|
|
69
|
+
}
|
|
70
|
+
// omit zeros at end of decimal part for higher precision
|
|
71
|
+
const trimmedDec = decPart.replace(/0+$/, '');
|
|
72
|
+
if (trimmedDec.length === 0 && decimalPlaces > 2) {
|
|
73
|
+
return `${sign}${formattedInt}`;
|
|
74
|
+
}
|
|
75
|
+
return `${sign}${formattedInt}.${decPart}`;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* .what = converts any IsoPrice format to IsoPriceHuman (symbol format)
|
|
79
|
+
* .why = enables human-readable display with currency symbols
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* asIsoPriceHuman('USD 50.37')
|
|
83
|
+
* // => '$50.37'
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* asIsoPriceHuman({ amount: 5037n, currency: 'USD' })
|
|
87
|
+
* // => '$50.37'
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* asIsoPriceHuman('USD 1_000_000.00')
|
|
91
|
+
* // => '$1,000,000.00'
|
|
92
|
+
*/
|
|
93
|
+
const asIsoPriceHuman = (input, options) => {
|
|
94
|
+
// convert to shape first
|
|
95
|
+
const shape = (0, asIsoPriceShape_1.asIsoPriceShape)(input, options);
|
|
96
|
+
// get the symbol for this currency
|
|
97
|
+
const symbol = CODE_TO_SYMBOL[shape.currency] ?? shape.currency;
|
|
98
|
+
// use currency's standard exponent when shape.exponent is not set
|
|
99
|
+
const exponent = shape.exponent ?? (0, getIsoPriceExponentByCurrency_1.getIsoPriceExponentByCurrency)(shape.currency);
|
|
100
|
+
// format the amount with commas
|
|
101
|
+
const formatted = formatAmountHuman(shape.amount, exponent);
|
|
102
|
+
// build the human-readable string (symbol prefix)
|
|
103
|
+
return `${symbol}${formatted}`;
|
|
104
|
+
};
|
|
105
|
+
exports.asIsoPriceHuman = asIsoPriceHuman;
|
|
106
|
+
//# sourceMappingURL=asIsoPriceHuman.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asIsoPriceHuman.js","sourceRoot":"","sources":["../../../src/domain.operations/cast/asIsoPriceHuman.ts"],"names":[],"mappings":";;;AACA,4EAAyE;AAEzE,8FAA2F;AAC3F,uDAAoD;AAEpD;;;GAGG;AACH,MAAM,cAAc,GAA2B;IAC7C,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,KAAK;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAmC,EAAU,EAAE;IACvE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3C,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,iBAAiB,GAAG,CACxB,MAAc,EACd,WAAsC,mCAAgB,CAAC,KAAK,EACpD,EAAE;IACV,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,MAAM,GAAG,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAEhD,oDAAoD;IACpD,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,YAAY,GAAG,SAAS;aAC3B,QAAQ,EAAE;aACV,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;IACxD,CAAC;IAED,0DAA0D;IAC1D,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,aAAa,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAExE,uCAAuC;IACvC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC;IAC1D,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC;IAEhD,qEAAqE;IACrE,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAEnE,mBAAmB;IACnB,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnC,iDAAiD;IACjD,IAAI,OAAO,KAAK,IAAI,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;QAC5C,OAAO,GAAG,IAAI,GAAG,YAAY,KAAK,CAAC;IACrC,CAAC;IAED,yDAAyD;IACzD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,GAAG,IAAI,GAAG,YAAY,EAAE,CAAC;IAClC,CAAC;IAED,OAAO,GAAG,IAAI,GAAG,YAAY,IAAI,OAAO,EAAE,CAAC;AAC7C,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACI,MAAM,eAAe,GAAG,CAC7B,KAGU,EACV,OAAkC,EACnB,EAAE;IACjB,yBAAyB;IACzB,MAAM,KAAK,GAAG,IAAA,iCAAe,EAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAE9C,mCAAmC;IACnC,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC;IAEhE,kEAAkE;IAClE,MAAM,QAAQ,GACZ,KAAK,CAAC,QAAQ,IAAI,IAAA,6DAA6B,EAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAElE,gCAAgC;IAChC,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE5D,kDAAkD;IAClD,OAAO,GAAG,MAAM,GAAG,SAAS,EAAmB,CAAC;AAClD,CAAC,CAAC;AAtBW,QAAA,eAAe,mBAsB1B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IsoPrice } from '../../domain.objects/IsoPrice';
|
|
2
|
+
import type { IsoPriceShape } from '../../domain.objects/IsoPriceShape';
|
|
3
|
+
/**
|
|
4
|
+
* .what = converts any IsoPrice format to IsoPriceShape
|
|
5
|
+
* .why = enables uniform representation as shape for arithmetic operations
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* asIsoPriceShape('USD 50.37')
|
|
9
|
+
* // => { amount: 5037n, currency: 'USD' }
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* asIsoPriceShape({ amount: 5037, currency: 'USD' })
|
|
13
|
+
* // => { amount: 5037n, currency: 'USD' } // number → bigint
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* asIsoPriceShape('$50.37')
|
|
17
|
+
* // => { amount: 5037n, currency: 'USD' }
|
|
18
|
+
*/
|
|
19
|
+
export declare const asIsoPriceShape: <TCurrency extends string = string>(input: IsoPrice<TCurrency> | {
|
|
20
|
+
amount: number | bigint;
|
|
21
|
+
currency: TCurrency;
|
|
22
|
+
exponent?: string;
|
|
23
|
+
} | string, options?: {
|
|
24
|
+
currency?: TCurrency;
|
|
25
|
+
}) => IsoPriceShape<TCurrency>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.asIsoPriceShape = void 0;
|
|
4
|
+
const helpful_errors_1 = require("helpful-errors");
|
|
5
|
+
const IsoPriceExponent_1 = require("../../domain.objects/IsoPriceExponent");
|
|
6
|
+
const isIsoPriceHuman_1 = require("../guard/isIsoPriceHuman");
|
|
7
|
+
const isIsoPriceWords_1 = require("../guard/isIsoPriceWords");
|
|
8
|
+
const getIsoPriceExponentByCurrency_1 = require("../precision/getIsoPriceExponentByCurrency");
|
|
9
|
+
/**
|
|
10
|
+
* .what = currency symbol to code mappings
|
|
11
|
+
* .why = enables conversion from human format symbols to iso 4217 codes
|
|
12
|
+
*/
|
|
13
|
+
const SYMBOL_TO_CODE = {
|
|
14
|
+
$: 'USD',
|
|
15
|
+
'€': 'EUR',
|
|
16
|
+
'£': 'GBP',
|
|
17
|
+
'¥': 'JPY',
|
|
18
|
+
'₹': 'INR',
|
|
19
|
+
'₽': 'RUB',
|
|
20
|
+
'₩': 'KRW',
|
|
21
|
+
'₪': 'ILS',
|
|
22
|
+
'฿': 'THB',
|
|
23
|
+
'₫': 'VND',
|
|
24
|
+
kr: 'SEK',
|
|
25
|
+
R: 'ZAR',
|
|
26
|
+
zł: 'PLN',
|
|
27
|
+
Kč: 'CZK',
|
|
28
|
+
CHF: 'CHF',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* .what = parses amount string to bigint in minor units
|
|
32
|
+
* .why = converts decimal string like '50.37' to bigint 5037n
|
|
33
|
+
*/
|
|
34
|
+
const parseAmountToMinorUnits = (amountStr, exponent = IsoPriceExponent_1.IsoPriceExponent.CENTI) => {
|
|
35
|
+
// get the decimal places for this exponent
|
|
36
|
+
const decimalPlaces = Math.abs(parseInt(exponent.split('^')[1] ?? '-2', 10));
|
|
37
|
+
// clean the string: remove underscores and commas
|
|
38
|
+
const cleaned = amountStr.replace(/[_,]/g, '');
|
|
39
|
+
// split into integer and decimal parts
|
|
40
|
+
const [intPart, decPart = ''] = cleaned.split('.');
|
|
41
|
+
// pad or truncate decimal part to match exponent
|
|
42
|
+
const paddedDecimal = decPart.padEnd(decimalPlaces, '0');
|
|
43
|
+
const truncatedDecimal = paddedDecimal.slice(0, decimalPlaces);
|
|
44
|
+
// detect if we need higher precision
|
|
45
|
+
if (decPart.length > decimalPlaces) {
|
|
46
|
+
// use the actual decimal length to determine exponent
|
|
47
|
+
const actualDecPlaces = decPart.length;
|
|
48
|
+
const exponentMap = {
|
|
49
|
+
0: IsoPriceExponent_1.IsoPriceExponent.WHOLE,
|
|
50
|
+
2: IsoPriceExponent_1.IsoPriceExponent.CENTI,
|
|
51
|
+
3: IsoPriceExponent_1.IsoPriceExponent.MILLI,
|
|
52
|
+
6: IsoPriceExponent_1.IsoPriceExponent.MICRO,
|
|
53
|
+
9: IsoPriceExponent_1.IsoPriceExponent.NANO,
|
|
54
|
+
12: IsoPriceExponent_1.IsoPriceExponent.PICO,
|
|
55
|
+
};
|
|
56
|
+
// find the smallest exponent that fits
|
|
57
|
+
const newExponent = Object.entries(exponentMap)
|
|
58
|
+
.filter(([places]) => parseInt(places) >= actualDecPlaces)
|
|
59
|
+
.sort((a, b) => parseInt(a[0]) - parseInt(b[0]))[0]?.[1] ??
|
|
60
|
+
IsoPriceExponent_1.IsoPriceExponent.PICO;
|
|
61
|
+
const newDecPlaces = Math.abs(parseInt(newExponent.split('^')[1] ?? '-2', 10));
|
|
62
|
+
const fullDecimal = decPart.padEnd(newDecPlaces, '0');
|
|
63
|
+
return {
|
|
64
|
+
amount: BigInt(`${intPart ?? '0'}${fullDecimal}`),
|
|
65
|
+
exponent: newExponent,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// combine integer and decimal parts
|
|
69
|
+
const combined = `${intPart ?? '0'}${truncatedDecimal}`;
|
|
70
|
+
return {
|
|
71
|
+
amount: BigInt(combined),
|
|
72
|
+
exponent,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* .what = extracts currency code and amount from words format
|
|
77
|
+
* .why = parses 'USD 50.37' into { currency: 'USD', amount: '50.37' }
|
|
78
|
+
*/
|
|
79
|
+
const parseWordsFormat = (value) => {
|
|
80
|
+
const match = value.match(/^([A-Z]{3}) (.+)$/);
|
|
81
|
+
if (!match)
|
|
82
|
+
throw new helpful_errors_1.BadRequestError('invalid words format', { value });
|
|
83
|
+
return { currency: match[1], amountStr: match[2] };
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* .what = extracts currency code and amount from human format
|
|
87
|
+
* .why = parses '$50.37' or '50.37 €' into { currency: 'USD', amount: '50.37' }
|
|
88
|
+
*/
|
|
89
|
+
const parseHumanFormat = (value, options) => {
|
|
90
|
+
// try prefix symbols
|
|
91
|
+
for (const [symbol, code] of Object.entries(SYMBOL_TO_CODE)) {
|
|
92
|
+
if (value.startsWith(symbol)) {
|
|
93
|
+
const amountStr = value.slice(symbol.length);
|
|
94
|
+
const currency = options?.currency ?? code;
|
|
95
|
+
return { currency, amountStr };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// try suffix symbols
|
|
99
|
+
for (const [symbol, code] of Object.entries(SYMBOL_TO_CODE)) {
|
|
100
|
+
if (value.endsWith(symbol) || value.endsWith(` ${symbol}`)) {
|
|
101
|
+
const amountStr = value.replace(new RegExp(`\\s*${symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`), '');
|
|
102
|
+
const currency = options?.currency ?? code;
|
|
103
|
+
return { currency, amountStr };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw new helpful_errors_1.BadRequestError('unable to parse human format', { value });
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* .what = converts any IsoPrice format to IsoPriceShape
|
|
110
|
+
* .why = enables uniform representation as shape for arithmetic operations
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* asIsoPriceShape('USD 50.37')
|
|
114
|
+
* // => { amount: 5037n, currency: 'USD' }
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* asIsoPriceShape({ amount: 5037, currency: 'USD' })
|
|
118
|
+
* // => { amount: 5037n, currency: 'USD' } // number → bigint
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* asIsoPriceShape('$50.37')
|
|
122
|
+
* // => { amount: 5037n, currency: 'USD' }
|
|
123
|
+
*/
|
|
124
|
+
const asIsoPriceShape = (input, options) => {
|
|
125
|
+
// handle shape format (may need number → bigint conversion)
|
|
126
|
+
if (typeof input === 'object' &&
|
|
127
|
+
input !== null &&
|
|
128
|
+
'amount' in input &&
|
|
129
|
+
'currency' in input) {
|
|
130
|
+
const amount = typeof input.amount === 'bigint' ? input.amount : BigInt(input.amount);
|
|
131
|
+
return {
|
|
132
|
+
amount,
|
|
133
|
+
currency: input.currency,
|
|
134
|
+
exponent: input.exponent,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// handle words format
|
|
138
|
+
if ((0, isIsoPriceWords_1.isIsoPriceWords)(input)) {
|
|
139
|
+
const { currency, amountStr } = parseWordsFormat(input);
|
|
140
|
+
const currencyExponent = (0, getIsoPriceExponentByCurrency_1.getIsoPriceExponentByCurrency)(currency);
|
|
141
|
+
const { amount, exponent } = parseAmountToMinorUnits(amountStr, currencyExponent);
|
|
142
|
+
// only include exponent if it differs from currency's standard exponent
|
|
143
|
+
return {
|
|
144
|
+
amount,
|
|
145
|
+
currency: currency,
|
|
146
|
+
...(exponent !== currencyExponent ? { exponent } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// handle human format
|
|
150
|
+
if ((0, isIsoPriceHuman_1.isIsoPriceHuman)(input)) {
|
|
151
|
+
const { currency, amountStr } = parseHumanFormat(input, options);
|
|
152
|
+
const currencyExponent = (0, getIsoPriceExponentByCurrency_1.getIsoPriceExponentByCurrency)(currency);
|
|
153
|
+
const { amount, exponent } = parseAmountToMinorUnits(amountStr, currencyExponent);
|
|
154
|
+
// only include exponent if it differs from currency's standard exponent
|
|
155
|
+
return {
|
|
156
|
+
amount,
|
|
157
|
+
currency: currency,
|
|
158
|
+
...(exponent !== currencyExponent ? { exponent } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
throw new helpful_errors_1.BadRequestError('unable to convert to IsoPriceShape', { input });
|
|
162
|
+
};
|
|
163
|
+
exports.asIsoPriceShape = asIsoPriceShape;
|
|
164
|
+
//# sourceMappingURL=asIsoPriceShape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asIsoPriceShape.js","sourceRoot":"","sources":["../../../src/domain.operations/cast/asIsoPriceShape.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IsoPrice } from '../../domain.objects/IsoPrice';
|
|
2
|
+
import type { IsoPriceWords } from '../../domain.objects/IsoPriceWords';
|
|
3
|
+
/**
|
|
4
|
+
* .what = converts any IsoPrice format to IsoPriceWords
|
|
5
|
+
* .why = enables uniform representation as words for serialization
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* asIsoPriceWords({ amount: 5037n, currency: 'USD' })
|
|
9
|
+
* // => 'USD 50.37'
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* asIsoPriceWords('$50.37')
|
|
13
|
+
* // => 'USD 50.37'
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* asIsoPriceWords({ amount: 1000000n, currency: 'USD' })
|
|
17
|
+
* // => 'USD 10_000.00'
|
|
18
|
+
*/
|
|
19
|
+
export declare const asIsoPriceWords: <TCurrency extends string = string>(input: IsoPrice<TCurrency> | {
|
|
20
|
+
amount: number | bigint;
|
|
21
|
+
currency: TCurrency;
|
|
22
|
+
exponent?: string;
|
|
23
|
+
} | string, options?: {
|
|
24
|
+
currency?: TCurrency;
|
|
25
|
+
}) => IsoPriceWords<TCurrency>;
|