UNPKG

@easymoney/money

Version:
273 lines 11.1 kB
import { bind } from "@easymoney/core"; import { RoundingModes } from "@easymoney/core"; import { assert } from "@easymoney/core"; import { isNumeric } from "../assert"; import { fromString, fromNumber } from "../number"; function construct(amount, currency) { let newAmount = null; if (!Number.isInteger(Number(amount))) { let numberFromString = fromString(amount); if (!numberFromString.isInteger()) { throw new Error("Amount must be an integer(ish) value"); } newAmount = numberFromString.getIntegerPart(); } return { amount: newAmount ? newAmount : fromString(String(amount)).toString(), currency }; } const createMoneyFactory = (calculator) => ({ amount, currency }) => { const money = construct(amount, currency); const privateInstance = { calculator, instanceMoney: money }; privateInstance.round = bind(round, privateInstance); const publicInstance = {}; const instance = { privateInstance, publicInstance }; publicInstance.add = bind(add, instance); publicInstance.isSameCurrency = bind(isSameCurrency, publicInstance); publicInstance.getAmount = bind(getAmount, privateInstance); publicInstance.getCurrency = bind(getCurrency, privateInstance); publicInstance.equals = bind(equals, instance); publicInstance.compare = bind(compare, instance); publicInstance.greaterThan = bind(greaterThan, publicInstance); publicInstance.greaterThanOrEqual = bind(greaterThanOrEqual, publicInstance); publicInstance.lessThan = bind(lessThan, publicInstance); publicInstance.lessThanOrEqual = bind(lessThanOrEqual, publicInstance); publicInstance.subtract = bind(subtract, instance); publicInstance.multiply = bind(multiply, instance); publicInstance.divide = bind(divide, instance); publicInstance.allocate = bind(allocate, instance); publicInstance.allocateTo = bind(allocateTo, instance); publicInstance.mod = bind(mod, instance); publicInstance.absolute = bind(absolute, instance); publicInstance.negative = bind(negative, instance); publicInstance.isZero = bind(isZero, instance); publicInstance.isPositive = bind(isPositive, instance); publicInstance.isNegative = bind(isNegative, instance); publicInstance.ratioOf = bind(ratioOf, instance); return publicInstance; }; export function createMoneyUnit(calculator) { return createMoneyFactory(calculator); } function round(privateInstance, amount, roundingMode) { assertRoundingMode(roundingMode); const { calculator } = privateInstance; if (roundingMode === RoundingModes.CEILING) { return calculator.ceil(amount); } if (roundingMode === RoundingModes.FLOOR) { return calculator.floor(amount); } return calculator.round(amount, roundingMode); } function add(instance, money) { const { publicInstance, privateInstance } = instance; const { calculator, instanceMoney } = privateInstance; assertSameCurrency(publicInstance, money); const newAmount = calculator.add(money.getAmount(), instanceMoney.amount); return createMoneyFactory(privateInstance.calculator)({ amount: newAmount, currency: money.getCurrency() }); } function subtract(instance, money) { const { publicInstance, privateInstance } = instance; const { calculator } = privateInstance; assertSameCurrency(publicInstance, money); const newAmount = calculator.subtract(publicInstance.getAmount(), money.getAmount()); return createMoneyFactory(privateInstance.calculator)({ amount: newAmount, currency: money.getCurrency() }); } function getAmount(privateInstance) { return privateInstance.instanceMoney.amount; } function getCurrency(privateInstance) { return privateInstance.instanceMoney.currency; } function isSameCurrency(moneyInstance, money) { return moneyInstance.getCurrency() === money.getCurrency(); } function assertSameCurrency(moneyInstance, money) { assert(isSameCurrency(moneyInstance, money), new TypeError("Currencies must be identical")); } function equals(moneyInstance, money) { return (moneyInstance.publicInstance.isSameCurrency(money) && moneyInstance.privateInstance.instanceMoney.amount === money.getAmount()); } function compare(instance, money) { const { publicInstance, privateInstance } = instance; assertSameCurrency(publicInstance, money); return privateInstance.calculator.compare(privateInstance.instanceMoney.amount, money.getAmount()); } function greaterThan(publicInstance, money) { return publicInstance.compare(money) > 0; } function greaterThanOrEqual(publicInstance, money) { return publicInstance.compare(money) >= 0; } function lessThan(publicInstance, money) { return publicInstance.compare(money) < 0; } function lessThanOrEqual(publicInstance, money) { return publicInstance.compare(money) <= 0; } function multiply(instance, multiplier, roundingMode = RoundingModes.HALF_EVEN) { assertOperand(multiplier); assertRoundingMode(roundingMode); const { publicInstance, privateInstance } = instance; const { calculator } = privateInstance; const newAmount = calculator.multiply(publicInstance.getAmount(), multiplier); const roundedAmount = privateInstance.round(newAmount, roundingMode); return createMoneyFactory(privateInstance.calculator)({ amount: roundedAmount, currency: publicInstance.getCurrency() }); } function assertOperand(operand) { assert(isNumeric(operand), new TypeError("Operand should be a numeric value.")); } function assertRoundingMode(roundingMode) { assert([ RoundingModes.CEILING, RoundingModes.DOWN, RoundingModes.FLOOR, RoundingModes.HALF_DOWN, RoundingModes.HALF_EVEN, RoundingModes.HALF_UP, RoundingModes.UP ].includes(roundingMode), new TypeError("rounding mode should be one of RoundingModes.CEILING RoundingModes.DOWN RoundingModes.FLOOR RoundingModes.HALF_DOWN RoundingModes.HALF_EVEN RoundingModes.HALF_UP RoundingModes.UP")); } function divide(instance, divisor, roundingMode = RoundingModes.HALF_EVEN) { assertOperand(divisor); assertRoundingMode(roundingMode); const { privateInstance, publicInstance } = instance; const { round, calculator } = privateInstance; const result = fromNumber(divisor).toString(); if (calculator.compare(result, "0") === 0) { throw new TypeError("Division by zero"); } const quotient = round(calculator.divide(publicInstance.getAmount(), result), roundingMode); return createMoneyFactory(calculator)({ amount: quotient, currency: publicInstance.getCurrency() }); } function allocate(instance, ratios) { if (ratios.length === 0) { throw new TypeError("Cannot allocate to none, ratios cannot be an empty array"); } const { privateInstance, publicInstance } = instance; const { calculator } = privateInstance; let reminder = publicInstance.getAmount(); const results = []; const total = ratios.reduce((acc, val) => acc + val, 0); if (total <= 0) { throw new TypeError("Cannot allocate to none, sum of ratios must be greater than zero"); } for (let i = 0; i < ratios.length; i++) { const ratio = ratios[i]; if (ratio < 0) { throw new TypeError("Cannot allocate to none, ratio must be zero or positive"); } const share = calculator.share(publicInstance.getAmount(), ratio, total); results[i] = share; reminder = calculator.subtract(reminder, share); } if (calculator.compare(reminder, "0") === 0) { return results.map(result => createMoneyFactory(calculator)({ amount: result, currency: publicInstance.getCurrency() })); } const fractions = ratios.map(ratio => { const share = (ratio / total) * Number(publicInstance.getAmount()); return share - Math.floor(share); }); while (calculator.compare(reminder, "0") > 0) { let index; if (fractions.length !== 0) { let array = []; fractions.forEach((fraction, fractIndex) => { if (fraction === Math.max(...fractions)) { array.push(fractIndex); } }); index = array[0]; } else { index = 0; } results[index] = calculator.add(results[index], "1"); reminder = calculator.subtract(reminder, "1"); fractions[index] = 0; } return results.map(result => createMoneyFactory(calculator)({ amount: result, currency: publicInstance.getCurrency() })); } function allocateTo(instance, n) { if (!Number.isInteger(n)) { throw new TypeError("Number of targets must be an integer"); } if (n <= 0) { throw new TypeError("Cannot allocate to none, target must be greater than zero"); } return instance.publicInstance.allocate(Array(n).fill(1)); } function mod(instance, divisor) { assertSameCurrency(instance.publicInstance, divisor); const { publicInstance, privateInstance } = instance; const { calculator } = privateInstance; const newAmount = calculator.mod(publicInstance.getAmount(), divisor.getAmount()); return createMoneyFactory(privateInstance.calculator)({ amount: newAmount, currency: publicInstance.getCurrency() }); } function absolute(instance) { const { privateInstance, publicInstance } = instance; return createMoneyFactory(privateInstance.calculator)({ amount: privateInstance.calculator.absolute(publicInstance.getAmount()), currency: publicInstance.getCurrency() }); } function negative(instance) { const { privateInstance, publicInstance } = instance; const { calculator } = privateInstance; const newAmount = calculator.subtract("0", publicInstance.getAmount()); return createMoneyFactory(calculator)({ amount: newAmount, currency: publicInstance.getCurrency() }); } function isZero(instance) { const { privateInstance, publicInstance } = instance; const { calculator } = privateInstance; return calculator.compare(publicInstance.getAmount(), "0") === 0; } function isPositive(instance) { const { privateInstance, publicInstance } = instance; const { calculator } = privateInstance; return calculator.compare(publicInstance.getAmount(), "0") > 0; } function isNegative(instance) { const { privateInstance, publicInstance } = instance; const { calculator } = privateInstance; return calculator.compare(publicInstance.getAmount(), "0") < 0; } function ratioOf(instance, money) { if (money.isZero()) { throw new TypeError("Cannot calculate a ratio of zero"); } const { privateInstance, publicInstance } = instance; const { calculator } = privateInstance; return calculator.divide(publicInstance.getAmount(), money.getAmount()); } //# sourceMappingURL=money.js.map