Mejorado VO Tax

This commit is contained in:
David Arranz 2025-11-04 12:24:14 +01:00
parent 54e47fee1b
commit b9bfdc02aa
7 changed files with 46 additions and 18 deletions

View File

@ -77,8 +77,17 @@ export class Tax extends ValueObject<TaxProps> {
*/ */
static createFromCode(code: string, provider: TaxCatalogProvider): Result<Tax, Error> { static createFromCode(code: string, provider: TaxCatalogProvider): Result<Tax, Error> {
const normalized = (code ?? "").trim().toLowerCase(); const normalized = (code ?? "").trim().toLowerCase();
if (!normalized || !Tax.CODE_REGEX.test(normalized)) {
return Result.fail(new Error(`Código de impuesto inválido: "${code}"`)); const schema = z
.string()
.min(1, "El código del impuesto es obligatorio.")
.max(40, "El código del impuesto no puede exceder 40 caracteres.")
.regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos.");
const validationResult = schema.safeParse(normalized);
if (!validationResult.success) {
return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", ")));
} }
const maybeItem = provider.findByCode(normalized); const maybeItem = provider.findByCode(normalized);

View File

@ -24,9 +24,16 @@ export abstract class SequelizeDomainMapper<TModel extends Model, TModelAttribut
return Result.ok(new Collection([], totalCount)); return Result.ok(new Collection([], totalCount));
} }
const items = _source.map( const items: TEntity[] = [];
(value, index) => this.mapToDomain(value as TModel, { index, ...params }).data for (let index = 0; index < _source.length; index++) {
); const value = _source[index];
const result = this.mapToDomain(value as TModel, { index, ...params });
if (result.isFailure) {
return Result.fail(result.error);
}
items.push(result.data);
}
return Result.ok(new Collection(items, totalCount)); return Result.ok(new Collection(items, totalCount));
} catch (error) { } catch (error) {
return Result.fail(error as Error); return Result.fail(error as Error);

View File

@ -1,6 +1,6 @@
{ {
"name": "@erp/customer-invoices", "name": "@erp/customer-invoices",
"version": "0.0.4", "version": "0.0.5",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,

View File

@ -1,4 +1,4 @@
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth/api"; import { enforceTenant, enforceUser, mockUser, RequestWithAuth } from "@erp/auth/api";
import { ModuleParams, validateRequest } from "@erp/core/api"; import { ModuleParams, validateRequest } from "@erp/core/api";
import { ILogger } from "@repo/rdx-logger"; import { ILogger } from "@repo/rdx-logger";
import { Application, NextFunction, Request, Response, Router } from "express"; import { Application, NextFunction, Request, Response, Router } from "express";
@ -31,9 +31,8 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
const deps = buildCustomerInvoiceDependencies(params); const deps = buildCustomerInvoiceDependencies(params);
const router: Router = Router({ mergeParams: true }); const router: Router = Router({ mergeParams: true });
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
// 🔐 Autenticación + Tenancy para TODO el router // 🔐 Autenticación + Tenancy para TODO el router
if (process.env.NODE_ENV === "development") {
router.use( router.use(
(req: Request, res: Response, next: NextFunction) => (req: Request, res: Response, next: NextFunction) =>
mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas

View File

@ -1,11 +1,11 @@
import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import { import {
UniqueID,
ValidationErrorCollection,
ValidationErrorDetail,
extractOrPushError, extractOrPushError,
maybeFromNullableVO, maybeFromNullableVO,
toNullable, toNullable,
UniqueID,
ValidationErrorCollection,
ValidationErrorDetail,
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { import {
@ -74,7 +74,7 @@ export class CustomerInvoiceItemDomainMapper
const unitAmount = extractOrPushError( const unitAmount = extractOrPushError(
maybeFromNullableVO(source.unit_amount_value, (value) => maybeFromNullableVO(source.unit_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode!.code }) ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
), ),
`items[${index}].unit_amount`, `items[${index}].unit_amount`,
errors errors
@ -130,6 +130,13 @@ export class CustomerInvoiceItemDomainMapper
}); });
} }
// Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice item mapping failed [mapToDomain]", errors)
);
}
const taxes = ItemTaxes.create(taxesResults.data.getAll()); const taxes = ItemTaxes.create(taxesResults.data.getAll());
// 3) Construcción del elemento de dominio // 3) Construcción del elemento de dominio

View File

@ -226,7 +226,7 @@ export class CustomerInvoiceDomainMapper
if (itemsResults.isFailure) { if (itemsResults.isFailure) {
errors.push({ errors.push({
path: "items", path: "items",
message: recipientResult.error.message, message: itemsResults.error.message,
}); });
} }

View File

@ -1,3 +1,4 @@
import { JsonTaxCatalogProvider } from "@erp/core";
import { import {
ISequelizeDomainMapper, ISequelizeDomainMapper,
MapperParamsType, MapperParamsType,
@ -5,13 +6,11 @@ import {
Tax, Tax,
} from "@erp/core/api"; } from "@erp/core/api";
import { JsonTaxCatalogProvider } from "@erp/core";
import { import {
extractOrPushError,
UniqueID, UniqueID,
ValidationErrorCollection, ValidationErrorCollection,
ValidationErrorDetail, ValidationErrorDetail,
extractOrPushError,
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { CustomerInvoiceItem } from "../../../domain"; import { CustomerInvoiceItem } from "../../../domain";
@ -65,6 +64,13 @@ export class ItemTaxesDomainMapper
errors errors
); );
// Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Invoice item tax mapping failed [mapToDomain]", errors)
);
}
// Creación del objeto de dominio // Creación del objeto de dominio
const createResult = Tax.create(tax!); const createResult = Tax.create(tax!);
if (createResult.isFailure) { if (createResult.isFailure) {