Uecko_ERP/packages/rdx-ddd/src/value-objects/quantity.ts
2025-05-26 12:38:45 +02:00

149 lines
3.6 KiB
TypeScript

import { Result } from "@repo/rdx-utils";
import { z } from "zod";
import { ValueObject } from "./value-object";
const DEFAULT_SCALE = 2;
const DEFAULT_MIN_SCALE = 0;
const DEFAULT_MAX_SCALE = 2;
export interface IQuantityProps {
amount: number;
scale: number;
}
interface IQuantity {
amount: number;
scale: number;
getValue(): IQuantityProps;
toNumber(): number;
toString(): string;
isZero(): boolean;
isPositive(): boolean;
isNegative(): boolean;
increment(anotherQuantity?: Quantity): Result<Quantity, Error>;
decrement(anotherQuantity?: Quantity): Result<Quantity, Error>;
hasSameScale(otherQuantity: Quantity): boolean;
convertScale(newScale: number): Result<Quantity, Error>;
}
export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
protected static validate(values: IQuantityProps) {
const schema = z.object({
amount: z.number().int(),
scale: z.number().int().min(Quantity.MIN_SCALE).max(Quantity.MAX_SCALE),
});
return schema.safeParse(values);
}
static DEFAULT_SCALE = DEFAULT_SCALE;
static MIN_SCALE = DEFAULT_MIN_SCALE;
static MAX_SCALE = DEFAULT_MAX_SCALE;
static create({ amount, scale }: IQuantityProps) {
const props = {
amount: Number(amount),
scale: scale ?? Quantity.DEFAULT_SCALE,
};
const checkProps = Quantity.validate(props);
if (!checkProps.success) {
return Result.fail(new Error(checkProps.error.errors[0].message));
}
return Result.ok(new Quantity({ ...(checkProps.data as IQuantityProps) }));
}
get amount(): number {
return this.props.amount;
}
get scale(): number {
return this.props.scale;
}
getValue(): IQuantityProps {
return this.props;
}
toPrimitive() {
return this.getValue();
}
toNumber(): number {
return this.amount / 10 ** this.scale; // ** => Math.pow
}
toString(): string {
return this.toNumber().toFixed(this.scale);
}
isZero(): boolean {
return this.amount === 0;
}
isPositive(): boolean {
return this.amount > 0;
}
isNegative(): boolean {
return this.amount < 0;
}
increment(anotherQuantity?: Quantity): Result<Quantity, Error> {
if (!anotherQuantity) {
return Quantity.create({
amount: Number(this.amount) + 1,
scale: this.scale,
});
}
if (!this.hasSameScale(anotherQuantity)) {
return Result.fail(Error("No se pueden sumar cantidades con diferentes escalas."));
}
return Quantity.create({
amount: Number(this.amount) + Number(anotherQuantity.amount),
scale: this.scale,
});
}
decrement(anotherQuantity?: Quantity): Result<Quantity, Error> {
if (!anotherQuantity) {
return Quantity.create({
amount: Number(this.amount) - 1,
scale: this.scale,
});
}
if (!this.hasSameScale(anotherQuantity)) {
return Result.fail(Error("No se pueden restar cantidades con diferentes escalas."));
}
return Quantity.create({
amount: Number(this.amount) - Number(anotherQuantity.amount),
scale: this.scale,
});
}
hasSameScale(otherQuantity: Quantity): boolean {
return this.scale === otherQuantity.scale;
}
convertScale(newScale: number): Result<Quantity, Error> {
if (newScale < Quantity.MIN_SCALE || newScale > Quantity.MAX_SCALE) {
return Result.fail(new Error(`Scale out of range: ${newScale}`));
}
const oldFactor = 10 ** this.scale; // ** => Math.pow
const value = Number(this.amount) / oldFactor;
const newFactor = 10 ** newScale; // ** => Math.pow
const newValue = Math.round(value * newFactor);
return Quantity.create({ amount: newValue, scale: newScale });
}
}