import { Result } from "@repo/rdx-utils"; import * as z from "zod/v4"; import { ValueObject } from "./value-object"; const DEFAULT_SCALE = 2; const DEFAULT_MIN_SCALE = 0; const DEFAULT_MAX_SCALE = 2; export interface QuantityProps { value: number; scale: number; } export class Quantity extends ValueObject { protected static validate(values: QuantityProps) { const schema = z.object({ value: 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({ value, scale }: QuantityProps) { const props = { value: Number(value), scale: scale ?? Quantity.DEFAULT_SCALE, }; const checkProps = Quantity.validate(props); if (!checkProps.success) { return Result.fail(new Error(checkProps.error.issues[0].message)); } return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) })); } get value(): number { return this.props.value; } get scale(): number { return this.props.scale; } getProps(): QuantityProps { return this.props; } toPrimitive() { return this.getProps(); } toNumber(): number { return this.value / 10 ** this.scale; // ** => Math.pow } toString(): string { return this.toNumber().toFixed(this.scale); } toObjectString() { return { value: String(this.value), scale: String(this.scale), }; } isZero(): boolean { return this.value === 0; } isPositive(): boolean { return this.value > 0; } isNegative(): boolean { return this.value < 0; } increment(anotherQuantity?: Quantity): Result { if (!anotherQuantity) { return Quantity.create({ value: Number(this.value) + 1, scale: this.scale, }); } if (!this.hasSameScale(anotherQuantity)) { return Result.fail(Error("No se pueden sumar cantidades con diferentes escalas.")); } return Quantity.create({ value: Number(this.value) + Number(anotherQuantity.value), scale: this.scale, }); } decrement(anotherQuantity?: Quantity): Result { if (!anotherQuantity) { return Quantity.create({ value: Number(this.value) - 1, scale: this.scale, }); } if (!this.hasSameScale(anotherQuantity)) { return Result.fail(Error("No se pueden restar cantidades con diferentes escalas.")); } return Quantity.create({ value: Number(this.value) - Number(anotherQuantity.value), 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 = 10 ** this.scale; // ** => Math.pow const value = Number(this.value) / oldFactor; const newFactor = 10 ** newScale; // ** => Math.pow const newValue = Math.round(value * newFactor); return Quantity.create({ value: newValue, scale: newScale }); } }