216 lines
5.2 KiB
TypeScript
216 lines
5.2 KiB
TypeScript
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<number | string>;
|
|
precision?: number;
|
|
}
|
|
|
|
interface IQuantity {
|
|
amount: NullOr<number>;
|
|
precision: number;
|
|
}
|
|
|
|
export interface QuantityObject {
|
|
amount: number;
|
|
precision: number;
|
|
}
|
|
|
|
const defaultQuantityProps = {
|
|
amount: 0,
|
|
precision: 0,
|
|
};
|
|
|
|
export class Quantity extends NullableValueObject<IQuantity> {
|
|
public static readonly DEFAULT_PRECISION = defaultQuantityProps.precision;
|
|
|
|
private readonly _isNull: boolean;
|
|
private readonly _options: IQuantityOptions;
|
|
|
|
protected static validate(value: NullOr<number | string>, 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<NullOr<number>>(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<number> = Quantity.sanitize(validationResult.object);
|
|
const _props = {
|
|
amount: isNull(_amount) ? 0 : _amount,
|
|
precision,
|
|
};
|
|
|
|
return Result.ok<Quantity>(new this(_props, isNull(_amount), options));
|
|
}
|
|
|
|
private static sanitize(value: NullOr<number | string>): NullOr<number> {
|
|
let _value: NullOr<number> = 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<number> {
|
|
return this.isNull() ? null : Number(this.props?.amount);
|
|
}
|
|
|
|
get precision(): number {
|
|
return this.isNull() ? 0 : Number(this.props?.precision);
|
|
}
|
|
|
|
public getAmount(): NullOr<number> {
|
|
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
|
|
);
|
|
}
|
|
}
|