143 lines
3.4 KiB
TypeScript
143 lines
3.4 KiB
TypeScript
import { Result } from "@repo/rdx-utils";
|
|
import * as z from "zod/v4";
|
|
import { translateZodValidationError } from "../helpers";
|
|
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<QuantityProps> {
|
|
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(translateZodValidationError("Quantity creation failed", checkProps.error));
|
|
}
|
|
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<Quantity, Error> {
|
|
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<Quantity, Error> {
|
|
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<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.value) / oldFactor;
|
|
|
|
const newFactor = 10 ** newScale; // ** => Math.pow
|
|
const newValue = Math.round(value * newFactor);
|
|
|
|
return Quantity.create({ value: newValue, scale: newScale });
|
|
}
|
|
|
|
format(): string {
|
|
return this.toNumber().toLocaleString();
|
|
}
|
|
}
|