import { Result } from "@common/helpers"; import { z } from "zod"; import { ValueObject } from "./value-object"; const DEFAULT_SCALE = 2; interface IQuantityProps { amount: number; scale: number; } interface IQuantity { amount: number; scale: number; getValue(): IQuantityProps; toNumber(): number; toString(): string; increment(anotherQuantity?: Quantity): Result; decrement(anotherQuantity?: Quantity): Result; hasSameScale(otherQuantity: Quantity): boolean; convertScale(newScale: number): Result; } export class Quantity extends ValueObject 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); } public static readonly DEFAULT_SCALE = DEFAULT_SCALE; public static readonly MIN_SCALE = 0; public static readonly MAX_SCALE = 2; static create(props: IQuantityProps) { 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! })); } get amount(): number { return this.props.amount; } get scale(): number { return this.props.scale; } getValue(): IQuantityProps { return this.props; } toNumber(): number { return this.amount / Math.pow(10, this.scale); } toString(): string { return this.toNumber().toFixed(this.scale); } increment(anotherQuantity?: Quantity): Result { 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 { 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 { if (newScale < Quantity.MIN_SCALE || newScale > Quantity.MAX_SCALE) { return Result.fail(new Error(`Scale out of range: ${newScale}`)); } 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 Quantity.create({ amount: newValue, scale: newScale }); } }