.
This commit is contained in:
parent
c86684812c
commit
ab36d6e084
@ -179,7 +179,7 @@ export class Percentage extends NullableValueObject<IPercentage> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public toNumber(): number {
|
public toNumber(): number {
|
||||||
return Percentage._toNumber(this.scale, this.scale);
|
return Percentage._toNumber(this.amount, this.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public toString(): string {
|
public toString(): string {
|
||||||
|
|||||||
@ -6,14 +6,14 @@ describe("Quantity Value Object", () => {
|
|||||||
const validQuantity = Quantity.create({ amount: 5 });
|
const validQuantity = Quantity.create({ amount: 5 });
|
||||||
|
|
||||||
expect(validQuantity.isSuccess).toBe(true);
|
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)", () => {
|
it("Should create a valid quantity (string)", () => {
|
||||||
const validQuantity = Quantity.create({ amount: "99" });
|
const validQuantity = Quantity.create({ amount: "99" });
|
||||||
|
|
||||||
expect(validQuantity.isSuccess).toBe(true);
|
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)", () => {
|
it("Should create a valid quantity (null)", () => {
|
||||||
@ -28,21 +28,21 @@ describe("Quantity Value Object", () => {
|
|||||||
const nullQuantity = Quantity.create();
|
const nullQuantity = Quantity.create();
|
||||||
|
|
||||||
expect(nullQuantity.isSuccess).toBe(true);
|
expect(nullQuantity.isSuccess).toBe(true);
|
||||||
expect(nullQuantity.object.amount).toBe(1);
|
expect(nullQuantity.object.amount).toBe(0);
|
||||||
expect(nullQuantity.object.precision).toBe(0);
|
expect(nullQuantity.object.scale).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prueba la creación de una cantidad válida a partir de una cadena.
|
// Prueba la creación de una cantidad válida a partir de una cadena.
|
||||||
it("Should create a valid quantity from string", () => {
|
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.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.
|
// Prueba la creación de una cantidad válida a partir de una cadena con decimales.
|
||||||
it("Should create a valid quantity from string", () => {
|
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.isSuccess).toBe(true);
|
||||||
expect(validQuantityFromString.object.toNumber()).toBe(1234.56);
|
expect(validQuantityFromString.object.toNumber()).toBe(1234.56);
|
||||||
@ -57,7 +57,7 @@ describe("Quantity Value Object", () => {
|
|||||||
|
|
||||||
// Prueba la conversión a número.
|
// Prueba la conversión a número.
|
||||||
it("Should convert to number", () => {
|
it("Should convert to number", () => {
|
||||||
const quantity = Quantity.create({ amount: 7 }).object;
|
const quantity = Quantity.create({ amount: 700 }).object;
|
||||||
const result = quantity.toNumber();
|
const result = quantity.toNumber();
|
||||||
|
|
||||||
expect(result).toBe(7);
|
expect(result).toBe(7);
|
||||||
@ -65,7 +65,7 @@ describe("Quantity Value Object", () => {
|
|||||||
|
|
||||||
// Prueba la conversión a cadena.
|
// Prueba la conversión a cadena.
|
||||||
it("Should convert to string", () => {
|
it("Should convert to string", () => {
|
||||||
const quantity = Quantity.create({ amount: 15 }).object;
|
const quantity = Quantity.create({ amount: 1500 }).object;
|
||||||
const result = quantity.toString();
|
const result = quantity.toString();
|
||||||
|
|
||||||
expect(result).toBe("15");
|
expect(result).toBe("15");
|
||||||
@ -73,10 +73,12 @@ describe("Quantity Value Object", () => {
|
|||||||
|
|
||||||
// Prueba la operación de incremento.
|
// Prueba la operación de incremento.
|
||||||
it("Should increment", () => {
|
it("Should increment", () => {
|
||||||
const quantity = Quantity.create({ amount: 5 }).object;
|
const quantity = Quantity.create({ amount: 5, scale: 2 }).object;
|
||||||
const incrementedQuantity = quantity.increment().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", () => {
|
it("Should increment quantity", () => {
|
||||||
@ -84,12 +86,12 @@ describe("Quantity Value Object", () => {
|
|||||||
const secountQ = Quantity.create({ amount: 105 }).object;
|
const secountQ = Quantity.create({ amount: 105 }).object;
|
||||||
const incrementedQuantity = firstQ.increment(secountQ).object;
|
const incrementedQuantity = firstQ.increment(secountQ).object;
|
||||||
|
|
||||||
expect(incrementedQuantity.toNumber()).toBe(100);
|
expect(incrementedQuantity.toNumber()).toBe(1.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prueba la operación de decremento.
|
// Prueba la operación de decremento.
|
||||||
it("Should decrement", () => {
|
it("Should decrement", () => {
|
||||||
const quantity = Quantity.create({ amount: 0 }).object;
|
const quantity = Quantity.create({ amount: 0, scale: 0 }).object;
|
||||||
const decrementedQuantity = quantity.decrement().object;
|
const decrementedQuantity = quantity.decrement().object;
|
||||||
|
|
||||||
expect(decrementedQuantity.toNumber()).toBe(-1);
|
expect(decrementedQuantity.toNumber()).toBe(-1);
|
||||||
@ -97,8 +99,10 @@ describe("Quantity Value Object", () => {
|
|||||||
|
|
||||||
// Prueba la operación de decremento.
|
// Prueba la operación de decremento.
|
||||||
it("Should decrement quantity", () => {
|
it("Should decrement quantity", () => {
|
||||||
const quantity = Quantity.create({ amount: 10 }).object;
|
const quantity = Quantity.create({ amount: 100, scale: 1 }).object;
|
||||||
const decrementedQuantity = quantity.decrement(Quantity.create({ amount: 110 }).object).object;
|
const decrementedQuantity = quantity.decrement(
|
||||||
|
Quantity.create({ amount: 1100, scale: 1 }).object
|
||||||
|
).object;
|
||||||
|
|
||||||
expect(decrementedQuantity.toNumber()).toBe(-100);
|
expect(decrementedQuantity.toNumber()).toBe(-100);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,36 +4,45 @@ import { NullOr } from "../../../../utilities";
|
|||||||
import { RuleValidator } from "../RuleValidator";
|
import { RuleValidator } from "../RuleValidator";
|
||||||
import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject";
|
import { INullableValueObjectOptions, NullableValueObject } from "./NullableValueObject";
|
||||||
import { Result } from "./Result";
|
import { Result } from "./Result";
|
||||||
|
import { ResultCollection } from "./ResultCollection";
|
||||||
|
|
||||||
|
const DEFAULT_SCALE = 2;
|
||||||
|
|
||||||
export interface IQuantityOptions extends INullableValueObjectOptions {}
|
export interface IQuantityOptions extends INullableValueObjectOptions {}
|
||||||
|
|
||||||
export interface IQuantityProps {
|
export interface IQuantityProps {
|
||||||
amount: NullOr<number | string>;
|
amount: NullOr<number | string>;
|
||||||
precision?: number;
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IQuantity {
|
interface IQuantity {
|
||||||
amount: NullOr<number>;
|
amount: NullOr<number>;
|
||||||
precision: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuantityObject {
|
export interface QuantityObject {
|
||||||
amount: number;
|
amount: number;
|
||||||
precision: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultQuantityProps = {
|
const defaultQuantityProps = {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
precision: 0,
|
scale: DEFAULT_SCALE,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Quantity extends NullableValueObject<IQuantity> {
|
export class Quantity extends NullableValueObject<IQuantity> {
|
||||||
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 _isNull: boolean;
|
||||||
private readonly _options: IQuantityOptions;
|
private readonly _options: IQuantityOptions;
|
||||||
|
|
||||||
protected static validate(value: NullOr<number | string>, options: IQuantityOptions = {}) {
|
protected static validate(
|
||||||
|
value: NullOr<number | string>,
|
||||||
|
scale: NullOr<number>,
|
||||||
|
options: IQuantityOptions = {}
|
||||||
|
) {
|
||||||
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(
|
const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(
|
||||||
defaultQuantityProps.amount
|
defaultQuantityProps.amount
|
||||||
);
|
);
|
||||||
@ -46,9 +55,38 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
options.label ? options.label : "quantity"
|
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);
|
const rules = Joi.alternatives(ruleNull, ruleNumber, ruleString);
|
||||||
|
|
||||||
return RuleValidator.validate<NullOr<number>>(rules, value);
|
const validationResults = new ResultCollection([
|
||||||
|
RuleValidator.validate<NullOr<number>>(
|
||||||
|
Joi.alternatives(ruleNull, ruleNumber, ruleString),
|
||||||
|
value
|
||||||
|
),
|
||||||
|
RuleValidator.validate<NullOr<number>>(
|
||||||
|
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(
|
public static create(
|
||||||
@ -59,30 +97,30 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
throw new Error(`InvalidParams: props params is missing`);
|
throw new Error(`InvalidParams: props params is missing`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { amount = defaultQuantityProps.amount, precision = defaultQuantityProps.precision } =
|
const { amount = defaultQuantityProps.amount, scale = defaultQuantityProps.scale } = props;
|
||||||
props;
|
|
||||||
|
|
||||||
const _options = {
|
const _options = {
|
||||||
label: "quantity",
|
label: "quantity",
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationResult = Quantity.validate(amount, _options);
|
const validationResult = Quantity.validate(amount, scale, _options);
|
||||||
|
|
||||||
if (validationResult.isFailure) {
|
if (validationResult.isFailure) {
|
||||||
return Result.fail(validationResult.error);
|
return Result.fail(validationResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _amount: NullOr<number> = Quantity.sanitize(validationResult.object);
|
let _amount: NullOr<number> = Quantity._sanitize(amount);
|
||||||
|
|
||||||
const _props = {
|
const _props = {
|
||||||
amount: isNull(_amount) ? 0 : _amount,
|
amount: isNull(_amount) ? 0 : _amount,
|
||||||
precision,
|
scale,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Result.ok<Quantity>(new this(_props, isNull(_amount), options));
|
return Result.ok<Quantity>(new this(_props, isNull(_amount), options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static sanitize(value: NullOr<number | string>): NullOr<number> {
|
private static _sanitize(value: NullOr<number | string>): NullOr<number> {
|
||||||
let _value: NullOr<number> = null;
|
let _value: NullOr<number> = null;
|
||||||
|
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
@ -94,6 +132,16 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
return _value;
|
return _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static _toNumber(value: NullOr<number>, 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) {
|
constructor(quantity: IQuantity, isNull: boolean, options: IQuantityOptions) {
|
||||||
super(quantity);
|
super(quantity);
|
||||||
this._isNull = Object.freeze(isNull);
|
this._isNull = Object.freeze(isNull);
|
||||||
@ -104,16 +152,16 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
return this.isNull() ? null : Number(this.props?.amount);
|
return this.isNull() ? null : Number(this.props?.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
get precision(): number {
|
get scale(): number {
|
||||||
return this.isNull() ? 0 : Number(this.props?.precision);
|
return this.isNull() ? 0 : Number(this.props?.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAmount(): NullOr<number> {
|
public getAmount(): NullOr<number> {
|
||||||
return this.isNull() ? null : Number(this.props?.amount);
|
return this.isNull() ? null : Number(this.props?.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPrecision(): number {
|
public getScale(): number {
|
||||||
return this.isNull() ? 0 : Number(this.props?.precision);
|
return this.isNull() ? 0 : Number(this.props?.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isEmpty = (): boolean => {
|
public isEmpty = (): boolean => {
|
||||||
@ -125,13 +173,7 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public toNumber(): number {
|
public toNumber(): number {
|
||||||
if (this.isNull()) {
|
return Quantity._toNumber(this.amount, this.scale);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const factor = Math.pow(10, this.precision);
|
|
||||||
const amount = Number(this.amount) / factor;
|
|
||||||
return Number(amount.toFixed(this.precision));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public toString(): string {
|
public toString(): string {
|
||||||
@ -149,65 +191,83 @@ export class Quantity extends NullableValueObject<IQuantity> {
|
|||||||
public toObject(): QuantityObject {
|
public toObject(): QuantityObject {
|
||||||
return {
|
return {
|
||||||
amount: this.amount ? this.amount : 0,
|
amount: this.amount ? this.amount : 0,
|
||||||
precision: this.precision,
|
scale: this.scale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasSamePrecision(quantity: Quantity) {
|
public convertScale(newScale: number): Quantity {
|
||||||
return this.precision === quantity.precision;
|
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) {
|
public increment(anotherQuantity?: Quantity) {
|
||||||
|
if (this.isNull()) {
|
||||||
|
return anotherQuantity ? Quantity.create(anotherQuantity.toObject()) : Quantity.create();
|
||||||
|
}
|
||||||
|
|
||||||
if (!anotherQuantity) {
|
if (!anotherQuantity) {
|
||||||
return Quantity.create(
|
return Quantity.create(
|
||||||
{
|
{
|
||||||
amount: this.toNumber() + 1,
|
amount: Number(this.amount) + 1,
|
||||||
precision: this.precision,
|
scale: this.scale,
|
||||||
},
|
},
|
||||||
this._options
|
this._options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.hasSamePrecision(anotherQuantity)) {
|
if (!this.hasSameScale(anotherQuantity)) {
|
||||||
return Result.fail(Error("No se pueden sumar cantidades con diferentes precisiones."));
|
return Result.fail(Error("No se pueden sumar cantidades con diferentes escalas."));
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isNull()) {
|
|
||||||
return Quantity.create(anotherQuantity.toObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Quantity.create(
|
return Quantity.create(
|
||||||
{
|
{
|
||||||
amount: this.toNumber() + anotherQuantity.toNumber(),
|
amount: Number(this.amount) + Number(anotherQuantity.amount),
|
||||||
precision: this.precision,
|
scale: this.scale,
|
||||||
},
|
},
|
||||||
this._options
|
this._options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public decrement(anotherQuantity?: Quantity) {
|
public decrement(anotherQuantity?: Quantity) {
|
||||||
|
if (this.isNull()) {
|
||||||
|
return anotherQuantity ? Quantity.create(anotherQuantity.toObject()) : Quantity.create();
|
||||||
|
}
|
||||||
|
|
||||||
if (!anotherQuantity) {
|
if (!anotherQuantity) {
|
||||||
return Quantity.create(
|
return Quantity.create(
|
||||||
{
|
{
|
||||||
amount: this.toNumber() - 1,
|
amount: Number(this.amount) - 1,
|
||||||
precision: this.precision,
|
scale: this.scale,
|
||||||
},
|
},
|
||||||
this._options
|
this._options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.hasSamePrecision(anotherQuantity)) {
|
if (!this.hasSameScale(anotherQuantity)) {
|
||||||
return Result.fail(Error("No se pueden restar cantidades con diferentes precisiones."));
|
return Result.fail(Error("No se pueden restar cantidades con diferentes escalas."));
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isNull()) {
|
|
||||||
return Quantity.create(anotherQuantity.toObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Quantity.create(
|
return Quantity.create(
|
||||||
{
|
{
|
||||||
amount: this.toNumber() - anotherQuantity.toNumber(),
|
amount: Number(this.amount) - Number(anotherQuantity.amount),
|
||||||
precision: this.precision,
|
scale: this.scale,
|
||||||
},
|
},
|
||||||
this._options
|
this._options
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user