ts-money
Version:
Typescript port of js-money. Implementation of the Money value object.
320 lines (319 loc) • 9.73 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const currencies_1 = require("./lib/currencies");
exports.Currencies = currencies_1.Currencies;
let isInt = function (n) {
return Number(n) === n && n % 1 === 0;
};
let decimalPlaces = function (num) {
let match = ('' + num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
if (!match)
return 0;
return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0));
};
let assertSameCurrency = function (left, right) {
if (left.currency !== right.currency)
throw new Error('Different currencies');
};
let assertType = function (other) {
if (!(other instanceof Money))
throw new TypeError('Instance of Money required');
};
let assertOperand = function (operand) {
if (lodash_1.isNaN(parseFloat(operand)) && !isFinite(operand))
throw new TypeError('Operand must be a number');
};
let getCurrencyObject = function (currency) {
let currencyObj = currencies_1.Currencies[currency];
if (currencyObj) {
return currencyObj;
}
else {
for (let key in currencies_1.Currencies) {
if (key.toUpperCase() === currency.toUpperCase())
return currencies_1.Currencies[key];
}
}
};
function isAmountObject(amount) {
return lodash_1.isObject(amount);
}
class Money {
/**
* Creates a new Money instance.
* The created Money instances is a value object thus it is immutable.
*
* @param {Number} amount
* @param {Object/String} currency
* @returns {Money}
* @constructor
*/
constructor(amount, currency) {
if (lodash_1.isString(currency))
currency = getCurrencyObject(currency);
if (!lodash_1.isPlainObject(currency))
throw new TypeError('Invalid currency');
if (!isInt(amount))
throw new TypeError('Amount must be an integer');
this.amount = amount;
this.currency = currency.code;
Object.freeze(this);
}
static fromInteger(amount, currency) {
if (isAmountObject(amount)) {
if (amount.amount === undefined || amount.currency === undefined)
throw new TypeError('Missing required parameters amount,currency');
currency = amount.currency;
amount = amount.amount;
}
if (!isInt(amount))
throw new TypeError('Amount must be an integer value');
return new Money(amount, currency);
}
static fromDecimal(amount, currency, rounder) {
if (isAmountObject(amount)) {
if (amount.amount === undefined || amount.currency === undefined)
throw new TypeError('Missing required parameters amount,currency');
rounder = currency;
currency = amount.currency;
amount = amount.amount;
}
if (lodash_1.isString(currency))
currency = getCurrencyObject(currency);
if (!lodash_1.isPlainObject(currency))
throw new TypeError('Invalid currency');
if (rounder === undefined) {
let decimals = decimalPlaces(amount);
if (decimals > currency.decimal_digits)
throw new Error(`The currency ${currency.code} supports only` +
` ${currency.decimal_digits} decimal digits`);
rounder = Math.round;
}
else {
if (['round', 'floor', 'ceil'].indexOf(rounder) === -1 && typeof rounder !== 'function')
throw new TypeError('Invalid parameter rounder');
if (lodash_1.isString(rounder))
rounder = Math[rounder];
}
let precisionMultiplier = Math.pow(10, currency.decimal_digits);
let resultAmount = amount * precisionMultiplier;
resultAmount = rounder(resultAmount);
return new Money(resultAmount, currency);
}
/**
* Returns true if the two instances of Money are equal, false otherwise.
*
* @param {Money} other
* @returns {Boolean}
*/
equals(other) {
let self = this;
assertType(other);
return self.amount === other.amount &&
self.currency === other.currency;
}
/**
* Adds the two objects together creating a new Money instance that holds the result of the operation.
*
* @param {Money} other
* @returns {Money}
*/
add(other) {
let self = this;
assertType(other);
assertSameCurrency(self, other);
return new Money(self.amount + other.amount, self.currency);
}
/**
* Subtracts the two objects creating a new Money instance that holds the result of the operation.
*
* @param {Money} other
* @returns {Money}
*/
subtract(other) {
let self = this;
assertType(other);
assertSameCurrency(self, other);
return new Money(self.amount - other.amount, self.currency);
}
/**
* Multiplies the object by the multiplier returning a new Money instance that holds the result of the operation.
*
* @param {Number} multiplier
* @param {Function} [fn=Math.round]
* @returns {Money}
*/
multiply(multiplier, fn) {
if (!lodash_1.isFunction(fn))
fn = Math.round;
assertOperand(multiplier);
let amount = fn(this.amount * multiplier);
return new Money(amount, this.currency);
}
/**
* Divides the object by the multiplier returning a new Money instance that holds the result of the operation.
*
* @param {Number} divisor
* @param {Function} [fn=Math.round]
* @returns {Money}
*/
divide(divisor, fn) {
if (!lodash_1.isFunction(fn))
fn = Math.round;
assertOperand(divisor);
let amount = fn(this.amount / divisor);
return new Money(amount, this.currency);
}
/**
* Allocates fund bases on the ratios provided returing an array of objects as a product of the allocation.
*
* @param {Array} other
* @returns {Array.Money}
*/
allocate(ratios) {
let self = this;
let remainder = self.amount;
let results = [];
let total = 0;
ratios.forEach(function (ratio) {
total += ratio;
});
ratios.forEach(function (ratio) {
let share = Math.floor(self.amount * ratio / total);
results.push(new Money(share, self.currency));
remainder -= share;
});
for (let i = 0; remainder > 0; i++) {
results[i] = new Money(results[i].amount + 1, results[i].currency);
remainder--;
}
return results;
}
/**
* Compares two instances of Money.
*
* @param {Money} other
* @returns {Number}
*/
compare(other) {
let self = this;
assertType(other);
assertSameCurrency(self, other);
if (self.amount === other.amount)
return 0;
return self.amount > other.amount ? 1 : -1;
}
/**
* Checks whether the value represented by this object is greater than the other.
*
* @param {Money} other
* @returns {boolean}
*/
greaterThan(other) {
return 1 === this.compare(other);
}
/**
* Checks whether the value represented by this object is greater or equal to the other.
*
* @param {Money} other
* @returns {boolean}
*/
greaterThanOrEqual(other) {
return 0 <= this.compare(other);
}
/**
* Checks whether the value represented by this object is less than the other.
*
* @param {Money} other
* @returns {boolean}
*/
lessThan(other) {
return -1 === this.compare(other);
}
/**
* Checks whether the value represented by this object is less than or equal to the other.
*
* @param {Money} other
* @returns {boolean}
*/
lessThanOrEqual(other) {
return 0 >= this.compare(other);
}
/**
* Returns true if the amount is zero.
*
* @returns {boolean}
*/
isZero() {
return this.amount === 0;
}
/**
* Returns true if the amount is positive.
*
* @returns {boolean}
*/
isPositive() {
return this.amount > 0;
}
/**
* Returns true if the amount is negative.
*
* @returns {boolean}
*/
isNegative() {
return this.amount < 0;
}
/**
* Returns the decimal value as a float.
*
* @returns {number}
*/
toDecimal() {
return Number(this.toString());
}
/**
* Returns the decimal value as a string.
*
* @returns {string}
*/
toString() {
let currency = getCurrencyObject(this.currency);
return (this.amount / Math.pow(10, currency.decimal_digits)).toFixed(currency.decimal_digits);
}
/**
* Returns a serialised version of the instance.
*
* @returns {{amount: number, currency: string}}
*/
toJSON() {
return {
amount: this.amount,
currency: this.currency
};
}
/**
* Returns the amount represented by this object.
*
* @returns {number}
*/
getAmount() {
return this.amount;
}
/**
* Returns the currency represented by this object.
*
* @returns {string}
*/
getCurrency() {
return this.currency;
}
/**
* Returns the full currency object
*/
getCurrencyInfo() {
return getCurrencyObject(this.currency);
}
}
exports.Money = Money;
Object.assign(Money, currencies_1.Currencies);