Presupuestador_web/shared/lib/contexts/common/domain/entities/Quantity.ts
2024-07-09 18:21:21 +02:00

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
);
}
}