Uecko_ERP/packages/rdx-ddd/src/value-objects/quantity.ts

143 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-05-09 10:45:32 +00:00
import { Result } from "@repo/rdx-utils";
2025-06-24 18:38:57 +00:00
import * as z from "zod/v4";
2025-09-16 11:29:45 +00:00
import { translateZodValidationError } from "../helpers";
2025-02-24 19:00:28 +00:00
import { ValueObject } from "./value-object";
const DEFAULT_SCALE = 2;
2025-05-26 10:38:45 +00:00
const DEFAULT_MIN_SCALE = 0;
const DEFAULT_MAX_SCALE = 2;
2025-02-24 19:00:28 +00:00
2025-09-01 14:07:59 +00:00
export interface QuantityProps {
2025-09-04 10:02:24 +00:00
value: number;
2025-02-24 19:00:28 +00:00
scale: number;
}
2025-09-01 14:07:59 +00:00
export class Quantity extends ValueObject<QuantityProps> {
protected static validate(values: QuantityProps) {
2025-02-24 19:00:28 +00:00
const schema = z.object({
2025-09-04 10:02:24 +00:00
value: z.number().int(),
2025-02-25 10:16:34 +00:00
scale: z.number().int().min(Quantity.MIN_SCALE).max(Quantity.MAX_SCALE),
2025-02-24 19:00:28 +00:00
});
return schema.safeParse(values);
}
2025-05-26 10:38:45 +00:00
static DEFAULT_SCALE = DEFAULT_SCALE;
static MIN_SCALE = DEFAULT_MIN_SCALE;
static MAX_SCALE = DEFAULT_MAX_SCALE;
2025-02-24 19:00:28 +00:00
2025-09-04 10:02:24 +00:00
static create({ value, scale }: QuantityProps) {
2025-05-26 10:38:45 +00:00
const props = {
2025-09-04 10:02:24 +00:00
value: Number(value),
2025-05-26 10:38:45 +00:00
scale: scale ?? Quantity.DEFAULT_SCALE,
};
2025-02-24 19:00:28 +00:00
const checkProps = Quantity.validate(props);
if (!checkProps.success) {
2025-09-16 11:29:45 +00:00
return Result.fail(translateZodValidationError("Quantity creation failed", checkProps.error));
2025-02-24 19:00:28 +00:00
}
2025-09-01 14:07:59 +00:00
return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) }));
2025-02-24 19:00:28 +00:00
}
2025-09-04 10:02:24 +00:00
get value(): number {
return this.props.value;
2025-02-24 19:00:28 +00:00
}
get scale(): number {
return this.props.scale;
}
2025-09-04 10:02:24 +00:00
getProps(): QuantityProps {
2025-02-24 19:00:28 +00:00
return this.props;
}
2025-04-01 14:26:15 +00:00
toPrimitive() {
2025-09-04 10:02:24 +00:00
return this.getProps();
2025-04-01 14:26:15 +00:00
}
2025-02-24 19:00:28 +00:00
toNumber(): number {
2025-09-04 10:02:24 +00:00
return this.value / 10 ** this.scale; // ** => Math.pow
2025-02-24 19:00:28 +00:00
}
toString(): string {
return this.toNumber().toFixed(this.scale);
}
2025-09-12 16:23:36 +00:00
toObjectString() {
return {
value: String(this.value),
scale: String(this.scale),
};
}
2025-04-01 14:26:15 +00:00
isZero(): boolean {
2025-09-04 10:02:24 +00:00
return this.value === 0;
2025-04-01 14:26:15 +00:00
}
isPositive(): boolean {
2025-09-04 10:02:24 +00:00
return this.value > 0;
2025-04-01 14:26:15 +00:00
}
isNegative(): boolean {
2025-09-04 10:02:24 +00:00
return this.value < 0;
2025-04-01 14:26:15 +00:00
}
2025-02-24 19:00:28 +00:00
increment(anotherQuantity?: Quantity): Result<Quantity, Error> {
if (!anotherQuantity) {
return Quantity.create({
2025-09-04 10:02:24 +00:00
value: Number(this.value) + 1,
2025-02-24 19:00:28 +00:00
scale: this.scale,
});
}
if (!this.hasSameScale(anotherQuantity)) {
return Result.fail(Error("No se pueden sumar cantidades con diferentes escalas."));
}
return Quantity.create({
2025-09-04 10:02:24 +00:00
value: Number(this.value) + Number(anotherQuantity.value),
2025-02-24 19:00:28 +00:00
scale: this.scale,
});
}
decrement(anotherQuantity?: Quantity): Result<Quantity, Error> {
if (!anotherQuantity) {
return Quantity.create({
2025-09-04 10:02:24 +00:00
value: Number(this.value) - 1,
2025-02-24 19:00:28 +00:00
scale: this.scale,
});
}
if (!this.hasSameScale(anotherQuantity)) {
return Result.fail(Error("No se pueden restar cantidades con diferentes escalas."));
}
return Quantity.create({
2025-09-04 10:02:24 +00:00
value: Number(this.value) - Number(anotherQuantity.value),
2025-02-24 19:00:28 +00:00
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}`));
}
2025-05-04 20:06:57 +00:00
const oldFactor = 10 ** this.scale; // ** => Math.pow
2025-09-04 10:02:24 +00:00
const value = Number(this.value) / oldFactor;
2025-02-24 19:00:28 +00:00
2025-05-04 20:06:57 +00:00
const newFactor = 10 ** newScale; // ** => Math.pow
2025-02-24 19:00:28 +00:00
const newValue = Math.round(value * newFactor);
2025-09-04 10:02:24 +00:00
return Quantity.create({ value: newValue, scale: newScale });
2025-02-24 19:00:28 +00:00
}
2025-09-19 09:29:49 +00:00
format(): string {
return this.toNumber().toLocaleString();
}
2025-02-24 19:00:28 +00:00
}