import Joi from "joi"; import { isNull } from "lodash"; import { NullOr } from "../../../../utilities"; import { RuleValidator } from "../RuleValidator"; import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject"; import { Result } from "./Result"; export interface IQuantityOptions extends INullableValueObjectOptions {} export interface IQuantityProps { amount: NullOr; precision?: number; } interface IQuantity { amount: NullOr; precision: number; } export interface QuantityObject { amount: number; precision: number; } const defaultQuantityProps = { amount: 0, precision: 0, }; export class Quantity extends NullableValueObject { public static readonly DEFAULT_PRECISION = defaultQuantityProps.precision; private readonly _isNull: boolean; private readonly _options: IQuantityOptions; protected static validate(value: NullOr, options: IQuantityOptions = {}) { const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default( defaultQuantityProps.amount ); const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label( options.label ? options.label : "quantity" ); const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label( options.label ? options.label : "quantity" ); const rules = Joi.alternatives(ruleNull, ruleNumber, ruleString); return RuleValidator.validate>(rules, value); } public static create( props: IQuantityProps = defaultQuantityProps, options: IQuantityOptions = {} ) { if (props === null) { throw new Error(`InvalidParams: props params is missing`); } const { amount = defaultQuantityProps.amount, precision = defaultQuantityProps.precision } = props; const _options = { label: "quantity", ...options, }; const validationResult = Quantity.validate(amount, _options); if (validationResult.isFailure) { return Result.fail(validationResult.error); } let _amount: NullOr = Quantity.sanitize(validationResult.object); const _props = { amount: isNull(_amount) ? 0 : _amount, precision, }; return Result.ok(new this(_props, isNull(_amount), options)); } private static sanitize(value: NullOr): NullOr { let _value: NullOr = null; if (typeof value === "string") { _value = parseInt(value, 10); } else { _value = value; } return _value; } constructor(quantity: IQuantity, isNull: boolean, options: IQuantityOptions) { super(quantity); this._isNull = Object.freeze(isNull); this._options = Object.freeze(options); } get amount(): NullOr { return this.isNull() ? null : Number(this.props?.amount); } get precision(): number { return this.isNull() ? 0 : Number(this.props?.precision); } public getAmount(): NullOr { return this.isNull() ? null : Number(this.props?.amount); } public getPrecision(): number { return this.isNull() ? 0 : Number(this.props?.precision); } public isEmpty = (): boolean => { return this.isNull(); }; public isNull = (): boolean => { return this._isNull; }; public toNumber(): number { if (this.isNull()) { return 0; } const factor = Math.pow(10, this.precision); const amount = Number(this.amount) / factor; return Number(amount.toFixed(this.precision)); } public toString(): string { return this.isNull() ? "" : String(this.toNumber()); } public toPrimitive(): number { return this.toNumber(); } public toPrimitives() { return this.toObject(); } public toObject(): QuantityObject { return { amount: this.amount ? this.amount : 0, precision: this.precision, }; } public hasSamePrecision(quantity: Quantity) { return this.precision === quantity.precision; } public increment(anotherQuantity?: Quantity) { if (!anotherQuantity) { return Quantity.create( { amount: this.toNumber() + 1, precision: this.precision, }, this._options ); } if (!this.hasSamePrecision(anotherQuantity)) { return Result.fail(Error("No se pueden sumar cantidades con diferentes precisiones.")); } if (this.isNull()) { return Quantity.create(anotherQuantity.toObject()); } return Quantity.create( { amount: this.toNumber() + anotherQuantity.toNumber(), precision: this.precision, }, this._options ); } public decrement(anotherQuantity?: Quantity) { if (!anotherQuantity) { return Quantity.create( { amount: this.toNumber() - 1, precision: this.precision, }, this._options ); } if (!this.hasSamePrecision(anotherQuantity)) { return Result.fail(Error("No se pueden restar cantidades con diferentes precisiones.")); } if (this.isNull()) { return Quantity.create(anotherQuantity.toObject()); } return Quantity.create( { amount: this.toNumber() - anotherQuantity.toNumber(), precision: this.precision, }, this._options ); } }