From ab36d6e084164130a86feb721e8e7dcf5b762d35 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Mon, 15 Jul 2024 15:58:12 +0200 Subject: [PATCH] . --- .../common/domain/entities/Percentage.ts | 2 +- .../common/domain/entities/Quantity.test.ts | 36 ++-- .../common/domain/entities/Quantity.ts | 154 ++++++++++++------ 3 files changed, 128 insertions(+), 64 deletions(-) diff --git a/shared/lib/contexts/common/domain/entities/Percentage.ts b/shared/lib/contexts/common/domain/entities/Percentage.ts index 9ab9b5b..1969b5a 100644 --- a/shared/lib/contexts/common/domain/entities/Percentage.ts +++ b/shared/lib/contexts/common/domain/entities/Percentage.ts @@ -179,7 +179,7 @@ export class Percentage extends NullableValueObject { }; public toNumber(): number { - return Percentage._toNumber(this.scale, this.scale); + return Percentage._toNumber(this.amount, this.scale); } public toString(): string { diff --git a/shared/lib/contexts/common/domain/entities/Quantity.test.ts b/shared/lib/contexts/common/domain/entities/Quantity.test.ts index 6ccc369..f65f109 100644 --- a/shared/lib/contexts/common/domain/entities/Quantity.test.ts +++ b/shared/lib/contexts/common/domain/entities/Quantity.test.ts @@ -6,14 +6,14 @@ describe("Quantity Value Object", () => { const validQuantity = Quantity.create({ amount: 5 }); expect(validQuantity.isSuccess).toBe(true); - expect(validQuantity.object.toNumber()).toBe(5); + expect(validQuantity.object.toNumber()).toBe(0.05); }); it("Should create a valid quantity (string)", () => { const validQuantity = Quantity.create({ amount: "99" }); expect(validQuantity.isSuccess).toBe(true); - expect(validQuantity.object.toNumber()).toBe(99); + expect(validQuantity.object.toNumber()).toBe(0.99); }); it("Should create a valid quantity (null)", () => { @@ -28,21 +28,21 @@ describe("Quantity Value Object", () => { const nullQuantity = Quantity.create(); expect(nullQuantity.isSuccess).toBe(true); - expect(nullQuantity.object.amount).toBe(1); - expect(nullQuantity.object.precision).toBe(0); + expect(nullQuantity.object.amount).toBe(0); + expect(nullQuantity.object.scale).toBe(2); }); // Prueba la creación de una cantidad válida a partir de una cadena. it("Should create a valid quantity from string", () => { - const validQuantityFromString = Quantity.create({ amount: "10" }); + const validQuantityFromString = Quantity.create({ amount: "318" }); expect(validQuantityFromString.isSuccess).toBe(true); - expect(validQuantityFromString.object.toNumber()).toBe(10); + expect(validQuantityFromString.object.toNumber()).toBe(3.18); }); // Prueba la creación de una cantidad válida a partir de una cadena con decimales. it("Should create a valid quantity from string", () => { - const validQuantityFromString = Quantity.create({ amount: "123456", precision: 2 }); + const validQuantityFromString = Quantity.create({ amount: "123456", scale: 2 }); expect(validQuantityFromString.isSuccess).toBe(true); expect(validQuantityFromString.object.toNumber()).toBe(1234.56); @@ -57,7 +57,7 @@ describe("Quantity Value Object", () => { // Prueba la conversión a número. it("Should convert to number", () => { - const quantity = Quantity.create({ amount: 7 }).object; + const quantity = Quantity.create({ amount: 700 }).object; const result = quantity.toNumber(); expect(result).toBe(7); @@ -65,7 +65,7 @@ describe("Quantity Value Object", () => { // Prueba la conversión a cadena. it("Should convert to string", () => { - const quantity = Quantity.create({ amount: 15 }).object; + const quantity = Quantity.create({ amount: 1500 }).object; const result = quantity.toString(); expect(result).toBe("15"); @@ -73,10 +73,12 @@ describe("Quantity Value Object", () => { // Prueba la operación de incremento. it("Should increment", () => { - const quantity = Quantity.create({ amount: 5 }).object; - const incrementedQuantity = quantity.increment().object; + const quantity = Quantity.create({ amount: 5, scale: 2 }).object; - expect(incrementedQuantity.toNumber()).toBe(6); + const incrementedQuantity = quantity.increment().object; + console.log(quantity.toNumber()); + + expect(incrementedQuantity.toNumber()).toBe(0.06); }); it("Should increment quantity", () => { @@ -84,12 +86,12 @@ describe("Quantity Value Object", () => { const secountQ = Quantity.create({ amount: 105 }).object; const incrementedQuantity = firstQ.increment(secountQ).object; - expect(incrementedQuantity.toNumber()).toBe(100); + expect(incrementedQuantity.toNumber()).toBe(1.0); }); // Prueba la operación de decremento. it("Should decrement", () => { - const quantity = Quantity.create({ amount: 0 }).object; + const quantity = Quantity.create({ amount: 0, scale: 0 }).object; const decrementedQuantity = quantity.decrement().object; expect(decrementedQuantity.toNumber()).toBe(-1); @@ -97,8 +99,10 @@ describe("Quantity Value Object", () => { // Prueba la operación de decremento. it("Should decrement quantity", () => { - const quantity = Quantity.create({ amount: 10 }).object; - const decrementedQuantity = quantity.decrement(Quantity.create({ amount: 110 }).object).object; + const quantity = Quantity.create({ amount: 100, scale: 1 }).object; + const decrementedQuantity = quantity.decrement( + Quantity.create({ amount: 1100, scale: 1 }).object + ).object; expect(decrementedQuantity.toNumber()).toBe(-100); }); diff --git a/shared/lib/contexts/common/domain/entities/Quantity.ts b/shared/lib/contexts/common/domain/entities/Quantity.ts index bfe4b4b..728917b 100644 --- a/shared/lib/contexts/common/domain/entities/Quantity.ts +++ b/shared/lib/contexts/common/domain/entities/Quantity.ts @@ -4,36 +4,45 @@ import { NullOr } from "../../../../utilities"; import { RuleValidator } from "../RuleValidator"; import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject"; import { Result } from "./Result"; +import { ResultCollection } from "./ResultCollection"; + +const DEFAULT_SCALE = 2; export interface IQuantityOptions extends INullableValueObjectOptions {} export interface IQuantityProps { amount: NullOr; - precision?: number; + scale?: number; } interface IQuantity { amount: NullOr; - precision: number; + scale: number; } export interface QuantityObject { amount: number; - precision: number; + scale: number; } const defaultQuantityProps = { amount: 0, - precision: 0, + scale: DEFAULT_SCALE, }; export class Quantity extends NullableValueObject { - public static readonly DEFAULT_PRECISION = defaultQuantityProps.precision; + public static readonly DEFAULT_SCALE = DEFAULT_SCALE; + public static readonly MIN_SCALE = 0; + public static readonly MAX_SCALE = 2; private readonly _isNull: boolean; private readonly _options: IQuantityOptions; - protected static validate(value: NullOr, options: IQuantityOptions = {}) { + protected static validate( + value: NullOr, + scale: NullOr, + options: IQuantityOptions = {} + ) { const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default( defaultQuantityProps.amount ); @@ -46,9 +55,38 @@ export class Quantity extends NullableValueObject { options.label ? options.label : "quantity" ); + const ruleScale = Joi.number() + .min(Quantity.MIN_SCALE) + .max(Quantity.MAX_SCALE) + .label(options.label ? options.label : "quantity"); + const rules = Joi.alternatives(ruleNull, ruleNumber, ruleString); - return RuleValidator.validate>(rules, value); + const validationResults = new ResultCollection([ + RuleValidator.validate>( + Joi.alternatives(ruleNull, ruleNumber, ruleString), + value + ), + RuleValidator.validate>( + Joi.alternatives(ruleNull, RuleValidator.RULE_IS_TYPE_NUMBER, ruleScale), + scale + ), + ]); + + if (validationResults.hasSomeFaultyResult()) { + return validationResults.getFirstFaultyResult(); + } + + // Convert the value to a number if it's a string + let numericValue = typeof value === "string" ? parseInt(value, 10) : Number(value); + + // Check if scale is null, and set to default if so + let numericScale = isNull(scale) ? Quantity.DEFAULT_SCALE : Number(scale); + + // Calculate the adjusted value + const adjustedValue = numericValue / Math.pow(10, numericScale); + + return Result.ok(); } public static create( @@ -59,30 +97,30 @@ export class Quantity extends NullableValueObject { throw new Error(`InvalidParams: props params is missing`); } - const { amount = defaultQuantityProps.amount, precision = defaultQuantityProps.precision } = - props; + const { amount = defaultQuantityProps.amount, scale = defaultQuantityProps.scale } = props; const _options = { label: "quantity", ...options, }; - const validationResult = Quantity.validate(amount, _options); + const validationResult = Quantity.validate(amount, scale, _options); if (validationResult.isFailure) { return Result.fail(validationResult.error); } - let _amount: NullOr = Quantity.sanitize(validationResult.object); + let _amount: NullOr = Quantity._sanitize(amount); + const _props = { amount: isNull(_amount) ? 0 : _amount, - precision, + scale, }; return Result.ok(new this(_props, isNull(_amount), options)); } - private static sanitize(value: NullOr): NullOr { + private static _sanitize(value: NullOr): NullOr { let _value: NullOr = null; if (typeof value === "string") { @@ -94,6 +132,16 @@ export class Quantity extends NullableValueObject { return _value; } + private static _toNumber(value: NullOr, scale: number): number { + if (isNull(value)) { + return 0; + } + + const factor = Math.pow(10, scale); + const amount = Number(value) / factor; + return Number(amount.toFixed(scale)); + } + constructor(quantity: IQuantity, isNull: boolean, options: IQuantityOptions) { super(quantity); this._isNull = Object.freeze(isNull); @@ -104,16 +152,16 @@ export class Quantity extends NullableValueObject { return this.isNull() ? null : Number(this.props?.amount); } - get precision(): number { - return this.isNull() ? 0 : Number(this.props?.precision); + get scale(): number { + return this.isNull() ? 0 : Number(this.props?.scale); } public getAmount(): NullOr { return this.isNull() ? null : Number(this.props?.amount); } - public getPrecision(): number { - return this.isNull() ? 0 : Number(this.props?.precision); + public getScale(): number { + return this.isNull() ? 0 : Number(this.props?.scale); } public isEmpty = (): boolean => { @@ -125,13 +173,7 @@ export class Quantity extends NullableValueObject { }; public toNumber(): number { - if (this.isNull()) { - return 0; - } - - const factor = Math.pow(10, this.precision); - const amount = Number(this.amount) / factor; - return Number(amount.toFixed(this.precision)); + return Quantity._toNumber(this.amount, this.scale); } public toString(): string { @@ -149,65 +191,83 @@ export class Quantity extends NullableValueObject { public toObject(): QuantityObject { return { amount: this.amount ? this.amount : 0, - precision: this.precision, + scale: this.scale, }; } - public hasSamePrecision(quantity: Quantity) { - return this.precision === quantity.precision; + public convertScale(newScale: number): Quantity { + if (newScale < Quantity.MIN_SCALE || newScale > Quantity.MAX_SCALE) { + throw new Error(`Scale out of range: ${newScale}`); + } + + if (this.isNull()) { + return new Quantity({ amount: null, scale: newScale }, true, this._options); + } + + 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 new Quantity({ amount: newValue, scale: newScale }, false, this._options); + } + + public hasSameScale(quantity: Quantity) { + return this.scale === quantity.scale; } public increment(anotherQuantity?: Quantity) { + if (this.isNull()) { + return anotherQuantity ? Quantity.create(anotherQuantity.toObject()) : Quantity.create(); + } + if (!anotherQuantity) { return Quantity.create( { - amount: this.toNumber() + 1, - precision: this.precision, + amount: Number(this.amount) + 1, + scale: this.scale, }, this._options ); } - if (!this.hasSamePrecision(anotherQuantity)) { - return Result.fail(Error("No se pueden sumar cantidades con diferentes precisiones.")); - } - - if (this.isNull()) { - return Quantity.create(anotherQuantity.toObject()); + if (!this.hasSameScale(anotherQuantity)) { + return Result.fail(Error("No se pueden sumar cantidades con diferentes escalas.")); } return Quantity.create( { - amount: this.toNumber() + anotherQuantity.toNumber(), - precision: this.precision, + amount: Number(this.amount) + Number(anotherQuantity.amount), + scale: this.scale, }, this._options ); } public decrement(anotherQuantity?: Quantity) { + if (this.isNull()) { + return anotherQuantity ? Quantity.create(anotherQuantity.toObject()) : Quantity.create(); + } + if (!anotherQuantity) { return Quantity.create( { - amount: this.toNumber() - 1, - precision: this.precision, + amount: Number(this.amount) - 1, + scale: this.scale, }, this._options ); } - if (!this.hasSamePrecision(anotherQuantity)) { - return Result.fail(Error("No se pueden restar cantidades con diferentes precisiones.")); - } - - if (this.isNull()) { - return Quantity.create(anotherQuantity.toObject()); + if (!this.hasSameScale(anotherQuantity)) { + return Result.fail(Error("No se pueden restar cantidades con diferentes escalas.")); } return Quantity.create( { - amount: this.toNumber() - anotherQuantity.toNumber(), - precision: this.precision, + amount: Number(this.amount) - Number(anotherQuantity.amount), + scale: this.scale, }, this._options );