Presupuestador_web/shared/lib/contexts/common/domain/entities/Quantity.ts

330 lines
8.0 KiB
TypeScript
Raw Normal View History

2024-04-23 15:29:38 +00:00
import Joi from "joi";
import { NullOr } from "../../../../utilities";
2024-07-17 18:10:07 +00:00
import { DomainError, handleDomainError } from "../errors";
2024-04-23 15:29:38 +00:00
import { RuleValidator } from "../RuleValidator";
2024-06-19 16:54:25 +00:00
import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject";
2024-04-23 15:29:38 +00:00
import { Result } from "./Result";
2024-07-15 13:58:12 +00:00
import { ResultCollection } from "./ResultCollection";
const DEFAULT_SCALE = 2;
2024-04-23 15:29:38 +00:00
export interface IQuantityOptions extends INullableValueObjectOptions {}
2024-06-19 16:54:25 +00:00
export interface IQuantityProps {
amount: NullOr<number | string>;
2024-07-15 13:58:12 +00:00
scale?: number;
2024-06-19 16:54:25 +00:00
}
interface IQuantity {
amount: NullOr<number>;
2024-07-15 13:58:12 +00:00
scale: number;
2024-06-19 16:54:25 +00:00
}
2024-07-09 16:21:12 +00:00
export interface QuantityObject {
2024-07-16 15:55:14 +00:00
amount: number | null;
2024-07-15 13:58:12 +00:00
scale: number;
2024-07-09 16:21:12 +00:00
}
2024-06-19 16:54:25 +00:00
const defaultQuantityProps = {
2024-07-09 16:21:12 +00:00
amount: 0,
2024-07-15 13:58:12 +00:00
scale: DEFAULT_SCALE,
2024-06-19 16:54:25 +00:00
};
export class Quantity extends NullableValueObject<IQuantity> {
2024-07-15 13:58:12 +00:00
public static readonly DEFAULT_SCALE = DEFAULT_SCALE;
public static readonly MIN_SCALE = 0;
public static readonly MAX_SCALE = 2;
2024-07-09 16:21:12 +00:00
2024-06-19 16:54:25 +00:00
private readonly _isNull: boolean;
private readonly _options: IQuantityOptions;
2024-07-15 13:58:12 +00:00
protected static validate(
value: NullOr<number | string>,
scale: NullOr<number>,
options: IQuantityOptions = {}
) {
2024-07-16 15:55:14 +00:00
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED;
const ruleEmpty = RuleValidator.RULE_ALLOW_EMPTY;
2024-04-23 15:29:38 +00:00
const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
2024-07-16 17:17:52 +00:00
options.label ? options.label : "amount"
2024-04-23 15:29:38 +00:00
);
2024-06-19 16:54:25 +00:00
const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
2024-07-16 17:17:52 +00:00
options.label ? options.label : "amount"
2024-06-19 16:54:25 +00:00
);
2024-04-23 15:29:38 +00:00
2024-07-15 13:58:12 +00:00
const ruleScale = Joi.number()
.min(Quantity.MIN_SCALE)
.max(Quantity.MAX_SCALE)
2024-07-16 17:17:52 +00:00
.label(options.label ? options.label : "scale");
2024-04-23 15:29:38 +00:00
2024-07-15 13:58:12 +00:00
const validationResults = new ResultCollection([
RuleValidator.validate<NullOr<number>>(
2024-07-16 17:17:52 +00:00
Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString),
2024-07-15 13:58:12 +00:00
value
),
RuleValidator.validate<NullOr<number>>(
2024-07-16 17:17:52 +00:00
Joi.alternatives(
RuleValidator.RULE_IS_TYPE_NUMBER.label(options.label ? options.label : "scale"),
ruleScale
),
2024-07-15 13:58:12 +00:00
scale
),
]);
if (validationResults.hasSomeFaultyResult()) {
return validationResults.getFirstFaultyResult();
}
return Result.ok();
2024-04-23 15:29:38 +00:00
}
public static create(
2024-06-19 16:54:25 +00:00
props: IQuantityProps = defaultQuantityProps,
options: IQuantityOptions = {}
2024-04-23 15:29:38 +00:00
) {
2024-06-19 16:54:25 +00:00
if (props === null) {
throw new Error(`InvalidParams: props params is missing`);
}
2024-07-15 13:58:12 +00:00
const { amount = defaultQuantityProps.amount, scale = defaultQuantityProps.scale } = props;
2024-06-19 16:54:25 +00:00
2024-04-23 15:29:38 +00:00
const _options = {
label: "quantity",
...options,
};
2024-07-15 13:58:12 +00:00
const validationResult = Quantity.validate(amount, scale, _options);
2024-04-23 15:29:38 +00:00
if (validationResult.isFailure) {
2024-07-17 18:10:07 +00:00
return Result.fail(
handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
);
2024-04-23 15:29:38 +00:00
}
2024-07-15 13:58:12 +00:00
let _amount: NullOr<number> = Quantity._sanitize(amount);
2024-06-19 16:54:25 +00:00
const _props = {
2024-07-16 15:55:14 +00:00
amount: _amount === null ? 0 : _amount,
2024-07-15 13:58:12 +00:00
scale,
2024-06-19 16:54:25 +00:00
};
2024-07-16 15:55:14 +00:00
return Result.ok<Quantity>(new Quantity(_props, _amount === null, options));
}
public static createFromFormattedValue(
value: NullOr<number | string>,
_options: IQuantityOptions = {}
) {
if (value === null || value === "") {
return Quantity.create({
amount: null,
scale: Quantity.DEFAULT_SCALE,
});
}
const valueStr = String(value);
const [integerPart, decimalPart] = valueStr.split(",");
let _amount = integerPart;
let _scale = 2;
if (decimalPart === undefined) {
// 99
_scale = 0;
} else {
if (decimalPart === "") {
// 99,
_amount = integerPart + decimalPart.padEnd(1, "0");
_scale = 1;
}
if (decimalPart.length === 1) {
// 99,1
_amount = integerPart + decimalPart.padEnd(1, "0");
_scale = 1;
} else {
if (decimalPart.length === 2) {
// 99,12
_amount = integerPart + decimalPart.padEnd(2, "0");
_scale = 2;
}
}
}
return Quantity.create(
{
amount: _amount,
scale: _scale,
},
_options
);
2024-06-19 16:54:25 +00:00
}
2024-07-15 13:58:12 +00:00
private static _sanitize(value: NullOr<number | string>): NullOr<number> {
2024-04-23 15:29:38 +00:00
let _value: NullOr<number> = null;
2024-06-19 16:54:25 +00:00
if (typeof value === "string") {
_value = parseInt(value, 10);
2024-04-23 15:29:38 +00:00
} else {
2024-06-19 16:54:25 +00:00
_value = value;
2024-04-23 15:29:38 +00:00
}
2024-06-19 16:54:25 +00:00
return _value;
2024-04-23 15:29:38 +00:00
}
2024-07-16 15:55:14 +00:00
private static _toString(value: NullOr<number>, scale: number): string {
if (value === null) {
return "";
2024-07-15 13:58:12 +00:00
}
const factor = Math.pow(10, scale);
const amount = Number(value) / factor;
2024-07-16 16:05:52 +00:00
return amount.toFixed(scale);
2024-07-15 13:58:12 +00:00
}
2024-06-19 16:54:25 +00:00
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);
}
2024-07-15 13:58:12 +00:00
get scale(): number {
2024-07-18 10:02:59 +00:00
return Number(this.props?.scale);
2024-06-19 16:54:25 +00:00
}
2024-07-09 16:21:12 +00:00
public getAmount(): NullOr<number> {
return this.isNull() ? null : Number(this.props?.amount);
}
2024-07-15 13:58:12 +00:00
public getScale(): number {
return this.isNull() ? 0 : Number(this.props?.scale);
2024-07-09 16:21:12 +00:00
}
2024-06-19 16:54:25 +00:00
public isEmpty = (): boolean => {
return this.isNull();
};
public isNull = (): boolean => {
return this._isNull;
};
2024-07-16 15:55:14 +00:00
public toString(): string {
return Quantity._toString(this.amount, this.scale);
2024-04-23 15:29:38 +00:00
}
2024-07-16 15:55:14 +00:00
public toNumber(): number {
return this.isNull() ? 0 : Number(this.toString());
2024-04-23 15:29:38 +00:00
}
2024-07-24 16:01:31 +00:00
public toFormat(): string {
return this._isNull
? ""
: Intl.NumberFormat("es-ES", {
maximumFractionDigits: 2,
}).format(this.toNumber());
}
2024-07-15 16:53:33 +00:00
public toPrimitive(): NullOr<number> {
if (this.scale !== Quantity.DEFAULT_SCALE) {
return this.convertScale(Quantity.DEFAULT_SCALE).toPrimitive();
} else {
return this.amount;
}
2024-04-23 15:29:38 +00:00
}
2024-06-19 16:54:25 +00:00
public toPrimitives() {
return this.toObject();
}
2024-04-23 15:29:38 +00:00
2024-07-09 16:21:12 +00:00
public toObject(): QuantityObject {
2024-06-19 16:54:25 +00:00
return {
2024-07-16 15:55:14 +00:00
amount: this.amount,
2024-07-15 13:58:12 +00:00
scale: this.scale,
2024-06-19 16:54:25 +00:00
};
}
2024-07-15 13:58:12 +00:00
public convertScale(newScale: number): Quantity {
if (newScale < Quantity.MIN_SCALE || newScale > Quantity.MAX_SCALE) {
throw new Error(`Scale out of range: ${newScale}`);
}
if (this.isNull()) {
return new Quantity({ amount: null, scale: newScale }, true, this._options);
}
const oldFactor = Math.pow(10, this.scale);
const value = Number(this.amount) / oldFactor;
const newFactor = Math.pow(10, newScale);
const newValue = Math.round(value * newFactor);
return new Quantity({ amount: newValue, scale: newScale }, false, this._options);
}
public hasSameScale(quantity: Quantity) {
return this.scale === quantity.scale;
2024-06-19 16:54:25 +00:00
}
public increment(anotherQuantity?: Quantity) {
2024-07-15 13:58:12 +00:00
if (this.isNull()) {
return anotherQuantity ? Quantity.create(anotherQuantity.toObject()) : Quantity.create();
}
2024-06-19 16:54:25 +00:00
if (!anotherQuantity) {
return Quantity.create(
{
2024-07-15 13:58:12 +00:00
amount: Number(this.amount) + 1,
scale: this.scale,
2024-06-19 16:54:25 +00:00
},
this._options
);
}
2024-07-15 13:58:12 +00:00
if (!this.hasSameScale(anotherQuantity)) {
return Result.fail(Error("No se pueden sumar cantidades con diferentes escalas."));
2024-04-23 15:29:38 +00:00
}
2024-06-19 16:54:25 +00:00
return Quantity.create(
{
2024-07-15 13:58:12 +00:00
amount: Number(this.amount) + Number(anotherQuantity.amount),
scale: this.scale,
2024-06-19 16:54:25 +00:00
},
this._options
);
2024-04-23 15:29:38 +00:00
}
2024-06-19 16:54:25 +00:00
public decrement(anotherQuantity?: Quantity) {
2024-07-15 13:58:12 +00:00
if (this.isNull()) {
return anotherQuantity ? Quantity.create(anotherQuantity.toObject()) : Quantity.create();
}
2024-06-19 16:54:25 +00:00
if (!anotherQuantity) {
return Quantity.create(
{
2024-07-15 13:58:12 +00:00
amount: Number(this.amount) - 1,
scale: this.scale,
2024-06-19 16:54:25 +00:00
},
this._options
);
}
2024-04-23 15:29:38 +00:00
2024-07-15 13:58:12 +00:00
if (!this.hasSameScale(anotherQuantity)) {
return Result.fail(Error("No se pueden restar cantidades con diferentes escalas."));
2024-04-23 15:29:38 +00:00
}
2024-06-19 16:54:25 +00:00
return Quantity.create(
{
2024-07-15 13:58:12 +00:00
amount: Number(this.amount) - Number(anotherQuantity.amount),
scale: this.scale,
2024-06-19 16:54:25 +00:00
},
this._options
);
2024-04-23 15:29:38 +00:00
}
}