diff --git a/docs/prompt-customer-invoices.md b/docs/prompt-customer-invoices.md index 040faa03..96b193db 100644 --- a/docs/prompt-customer-invoices.md +++ b/docs/prompt-customer-invoices.md @@ -8,7 +8,7 @@ Este módulo es para **facturas de cliente (Customer Invoices)** y debe cumplir - Las **líneas de factura** (`InvoiceLine`) se modelan como entidades o value objects dentro del agregado. - Se usará un `Mapper` para convertir entre dominio y persistencia. - Repositorios (`ICustomerInvoiceRepository`) solo manejan agregados. - - Operaciones como `createInvoice`, `updateInvoice`, `getInvoiceById` serán gestionadas en `CustomerInvoiceService`. + - Operaciones como `createInvoice`, `updateInvoice`, `getInvoiceById` serán gestionadas en `CustomerInvoiceApplicationService`. ✅ **SOLID** - Usar SRP: cada clase con una responsabilidad clara. @@ -74,7 +74,7 @@ La entidad `CustomerInvoice` tendrá: ✅ Los repositorios deben capturar errores de Sequelize (`UniqueConstraintError`, etc.) y convertirlos a errores de dominio con mensajes claros y específicos (mediante `errorMapper`). 📌 TESTING: -✅ Los servicios (`CustomerInvoiceService`) serán testeados con mocks de repositorio. +✅ Los servicios (`CustomerInvoiceApplicationService`) serán testeados con mocks de repositorio. ✅ Las rutas serán testeadas con `supertest`. ✅ Las validaciones de ValueObjects tendrán pruebas unitarias. diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts b/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts index f995aa6e..e274f083 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts +++ b/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts @@ -46,6 +46,7 @@ export function translateSequelizeError(err: unknown): Error { path: e.path ?? "unknown", message: e.message, // Algunas props útiles: e.validatorKey / e.validatorName + // biome-ignore lint/suspicious/noExplicitAny: rule: (e as any).validatorKey ?? undefined, })); @@ -55,7 +56,7 @@ export function translateSequelizeError(err: unknown): Error { return DomainValidationError.invalidFormat(d.path, d.message, { cause: err }); } - return new ValidationErrorCollection("Invalid data provided", details, { cause: err }); + return new ValidationErrorCollection(details, { cause: err }); } // 4) Conectividad / indisponibilidad (transitorio) @@ -71,5 +72,5 @@ export function translateSequelizeError(err: unknown): Error { // 6) Fallback: deja pasar si ya es un Error tipado de tu app, si no wrap if (err instanceof Error) return err; - return new InfrastructureRepositoryError("Unknown persistence error", { cause: err as any }); + return new InfrastructureRepositoryError("Unknown persistence error", { cause: err as unknown }); } diff --git a/modules/core/tsconfig.json b/modules/core/tsconfig.json index d197d298..00dfcb5e 100644 --- a/modules/core/tsconfig.json +++ b/modules/core/tsconfig.json @@ -28,6 +28,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src", "../../packages/rdx-ddd/src/helpers/extract-or-push-error.ts"], + "include": ["src"], "exclude": ["node_modules"] } diff --git a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts b/modules/customer-invoices/src/api/application/customer-invoice-application.service.ts similarity index 88% rename from modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts rename to modules/customer-invoices/src/api/application/customer-invoice-application.service.ts index 499a6cba..0f18a1e3 100644 --- a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts +++ b/modules/customer-invoices/src/api/application/customer-invoice-application.service.ts @@ -2,11 +2,15 @@ import { Criteria } from "@repo/rdx-criteria/server"; import { UniqueID } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; -import { CustomerInvoiceListDTO } from "../../infrastructure"; -import { CustomerInvoice, CustomerInvoicePatchProps, CustomerInvoiceProps } from "../aggregates"; -import { ICustomerInvoiceRepository } from "../repositories"; +import { + CustomerInvoice, + CustomerInvoicePatchProps, + CustomerInvoiceProps, +} from "../domain/aggregates"; +import { ICustomerInvoiceRepository } from "../domain/repositories"; +import { CustomerInvoiceListDTO } from "../infrastructure"; -export class CustomerInvoiceService { +export class CustomerInvoiceApplicationService { constructor(private readonly repository: ICustomerInvoiceRepository) {} /** @@ -33,7 +37,7 @@ export class CustomerInvoiceService { * @param transaction - Transacción activa para la operación. * @returns Result - El agregado guardado o un error si falla la operación. */ - async createInvoice( + async createInvoiceInCompany( companyId: UniqueID, invoice: CustomerInvoice, transaction: Transaction @@ -47,14 +51,14 @@ export class CustomerInvoiceService { } /** - * Actualiza una nueva factura y devuelve la factura actualizada. + * Actualiza una factura existente y devuelve la factura actualizada. * * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. * @param invoice - El agregado a guardar. * @param transaction - Transacción activa para la operación. * @returns Result - El agregado guardado o un error si falla la operación. */ - async updateInvoice( + async updateInvoiceInCompany( companyId: UniqueID, invoice: CustomerInvoice, transaction: Transaction @@ -126,27 +130,25 @@ export class CustomerInvoiceService { * @param transaction - Transacción activa para la operación. * @returns Result - Factura actualizada o error. */ - async updateInvoiceByIdInCompany( + async patchInvoiceByIdInCompany( companyId: UniqueID, invoiceId: UniqueID, changes: CustomerInvoicePatchProps, transaction?: Transaction ): Promise> { - // Verificar si la factura existe const invoiceResult = await this.getInvoiceByIdInCompany(companyId, invoiceId, transaction); if (invoiceResult.isFailure) { return Result.fail(invoiceResult.error); } - const invoice = invoiceResult.data; - const updatedInvoice = invoice.update(changes); + const updated = invoiceResult.data.update(changes); - if (updatedInvoice.isFailure) { - return Result.fail(updatedInvoice.error); + if (updated.isFailure) { + return Result.fail(updated.error); } - return Result.ok(updatedInvoice.data); + return Result.ok(updated.data); } /** @@ -161,7 +163,7 @@ export class CustomerInvoiceService { companyId: UniqueID, invoiceId: UniqueID, transaction?: Transaction - ): Promise> { + ): Promise> { return this.repository.deleteByIdInCompany(companyId, invoiceId, transaction); } } diff --git a/modules/customer-invoices/src/api/application/use-cases/create/create-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/create/create-customer-invoice.use-case.ts index 5f4c25f0..1ee79819 100644 --- a/modules/customer-invoices/src/api/application/use-cases/create/create-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/create/create-customer-invoice.use-case.ts @@ -4,7 +4,7 @@ import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto"; -import { CustomerInvoiceService } from "../../../domain"; +import { CustomerInvoiceApplicationService } from "../../../domain"; import { CustomerInvoiceFullPresenter } from "../../presenters"; import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props"; @@ -15,7 +15,7 @@ type CreateCustomerInvoiceUseCaseInput = { export class CreateCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry, private readonly taxCatalog: JsonTaxCatalogProvider diff --git a/modules/customer-invoices/src/api/application/use-cases/delete-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/delete-customer-invoice.use-case.ts index f7d2ea3c..56dcc4bb 100644 --- a/modules/customer-invoices/src/api/application/use-cases/delete-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/delete-customer-invoice.use-case.ts @@ -1,7 +1,7 @@ import { EntityNotFoundError, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerInvoiceService } from "../../domain"; +import { CustomerInvoiceApplicationService } from "../../application"; type DeleteCustomerInvoiceUseCaseInput = { companyId: UniqueID; @@ -10,7 +10,7 @@ type DeleteCustomerInvoiceUseCaseInput = { export class DeleteCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager ) {} diff --git a/modules/customer-invoices/src/api/application/use-cases/get-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/get-customer-invoice.use-case.ts index 8867fc12..b1313034 100644 --- a/modules/customer-invoices/src/api/application/use-cases/get-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/get-customer-invoice.use-case.ts @@ -1,7 +1,7 @@ import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerInvoiceService } from "../../domain"; +import { CustomerInvoiceApplicationService } from "../../application"; import { CustomerInvoiceFullPresenter } from "../presenters/domain"; type GetCustomerInvoiceUseCaseInput = { @@ -11,7 +11,7 @@ type GetCustomerInvoiceUseCaseInput = { export class GetCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} diff --git a/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts index af7827b2..f642fe75 100644 --- a/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/issue-customer-invoice.use-case.ts @@ -1,7 +1,7 @@ import { EntityNotFoundError, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerInvoiceNumber, CustomerInvoiceService } from "../../domain"; +import { CustomerInvoiceApplicationService, CustomerInvoiceNumber } from "../../domain"; import { StatusInvoiceIsApprovedSpecification } from "../specs"; type IssueCustomerInvoiceUseCaseInput = { @@ -11,7 +11,7 @@ type IssueCustomerInvoiceUseCaseInput = { export class IssueCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager ) {} diff --git a/modules/customer-invoices/src/api/application/use-cases/list-customer-invoices.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/list-customer-invoices.use-case.ts index 9a5fcc99..e82c320b 100644 --- a/modules/customer-invoices/src/api/application/use-cases/list-customer-invoices.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/list-customer-invoices.use-case.ts @@ -4,7 +4,7 @@ import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { ListCustomerInvoicesResponseDTO } from "../../../common/dto"; -import { CustomerInvoiceService } from "../../domain"; +import { CustomerInvoiceApplicationService } from "../../application"; import { ListCustomerInvoicesPresenter } from "../presenters"; type ListCustomerInvoicesUseCaseInput = { @@ -14,7 +14,7 @@ type ListCustomerInvoicesUseCaseInput = { export class ListCustomerInvoicesUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} diff --git a/modules/customer-invoices/src/api/application/use-cases/report/delete-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/report/delete-customer-invoice.use-case.ts index 1b823daa..332924fb 100644 --- a/modules/customer-invoices/src/api/application/use-cases/report/delete-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/report/delete-customer-invoice.use-case.ts @@ -1,7 +1,7 @@ import { EntityNotFoundError, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerInvoiceService } from "../../../domain"; +import { CustomerInvoiceApplicationService } from "../../../domain"; type DeleteCustomerInvoiceUseCaseInput = { companyId: UniqueID; @@ -10,7 +10,7 @@ type DeleteCustomerInvoiceUseCaseInput = { export class DeleteCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager ) {} diff --git a/modules/customer-invoices/src/api/application/use-cases/report/report-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/report/report-customer-invoice.use-case.ts index 337d771f..7c8b4384 100644 --- a/modules/customer-invoices/src/api/application/use-cases/report/report-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/report/report-customer-invoice.use-case.ts @@ -1,7 +1,7 @@ import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerInvoiceService } from "../../../domain"; +import { CustomerInvoiceApplicationService } from "../../../domain"; import { CustomerInvoiceReportPDFPresenter } from "./reporter"; type ReportCustomerInvoiceUseCaseInput = { @@ -11,7 +11,7 @@ type ReportCustomerInvoiceUseCaseInput = { export class ReportCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} diff --git a/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts index 78ddd793..eeccee75 100644 --- a/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts @@ -3,7 +3,8 @@ import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { UpdateCustomerInvoiceByIdRequestDTO } from "../../../../common"; -import { CustomerInvoicePatchProps, CustomerInvoiceService } from "../../../domain"; +import { CustomerInvoicePatchProps } from "../../../domain"; +import { CustomerInvoiceApplicationService } from "../../customer-invoice-application.service"; import { CustomerInvoiceFullPresenter } from "../../presenters"; import { mapDTOToUpdateCustomerInvoicePatchProps } from "./map-dto-to-update-customer-invoice-props"; @@ -15,7 +16,7 @@ type UpdateCustomerInvoiceUseCaseInput = { export class UpdateCustomerInvoiceUseCase { constructor( - private readonly service: CustomerInvoiceService, + private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} @@ -44,7 +45,7 @@ export class UpdateCustomerInvoiceUseCase { return this.transactionManager.complete(async (transaction: Transaction) => { try { - const updatedInvoice = await this.service.updateInvoiceByIdInCompany( + const updatedInvoice = await this.service.patchInvoiceByIdInCompany( companyId, invoiceId, patchProps, @@ -55,7 +56,7 @@ export class UpdateCustomerInvoiceUseCase { return Result.fail(updatedInvoice.error); } - const invoiceOrError = await this.service.updateInvoice( + const invoiceOrError = await this.service.updateInvoiceInCompany( companyId, updatedInvoice.data, transaction diff --git a/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts b/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts index f744a99a..1626b538 100644 --- a/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts +++ b/modules/customer-invoices/src/api/domain/errors/customer-invoice-id-already-exits-error.ts @@ -7,3 +7,7 @@ import { DomainError } from "@repo/rdx-ddd"; export class CustomerInvoiceIdAlreadyExistsError extends DomainError { public readonly code = "DUPLICATE_INVOICE_ID" as const; } + +export const isCustomerInvoiceIdAlreadyExistsError = ( + e: unknown +): e is CustomerInvoiceIdAlreadyExistsError => e instanceof CustomerInvoiceIdAlreadyExistsError; diff --git a/modules/customer-invoices/src/api/domain/index.ts b/modules/customer-invoices/src/api/domain/index.ts index ed8d70d5..62b47e36 100644 --- a/modules/customer-invoices/src/api/domain/index.ts +++ b/modules/customer-invoices/src/api/domain/index.ts @@ -2,5 +2,4 @@ export * from "./aggregates"; export * from "./entities"; export * from "./errors"; export * from "./repositories"; -export * from "./services"; export * from "./value-objects"; diff --git a/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts b/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts index 303928ab..59283190 100644 --- a/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts +++ b/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts @@ -79,5 +79,5 @@ export interface ICustomerInvoiceRepository { companyId: UniqueID, id: UniqueID, transaction: unknown - ): Promise>; + ): Promise>; } diff --git a/modules/customer-invoices/src/api/domain/services/index.ts b/modules/customer-invoices/src/api/domain/services/index.ts deleted file mode 100644 index 3b0a9973..00000000 --- a/modules/customer-invoices/src/api/domain/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./customer-invoice.service"; diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index 7bd673c7..dcf12b52 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -24,8 +24,10 @@ import { } from "../application"; import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core"; -import { CustomerInvoiceItemsReportPersenter } from "../application/presenters/queries/customer-invoice-items.report.presenter"; -import { CustomerInvoiceService } from "../domain"; +import { + CustomerInvoiceApplicationService, + CustomerInvoiceItemsReportPersenter, +} from "../application"; import { CustomerInvoiceDomainMapper, CustomerInvoiceListMapper } from "./mappers"; import { CustomerInvoiceRepository } from "./sequelize"; @@ -34,7 +36,7 @@ export type CustomerInvoiceDeps = { mapperRegistry: IMapperRegistry; presenterRegistry: IPresenterRegistry; repo: CustomerInvoiceRepository; - service: CustomerInvoiceService; + service: CustomerInvoiceApplicationService; catalogs: { taxes: JsonTaxCatalogProvider; }; @@ -71,7 +73,7 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer // Repository & Services const repo = new CustomerInvoiceRepository({ mapperRegistry, database }); - const service = new CustomerInvoiceService(repo); + const service = new CustomerInvoiceApplicationService(repo); // Presenter Registry const presenterRegistry = new InMemoryPresenterRegistry(); diff --git a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts index d633cda3..6c379faf 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts @@ -2,13 +2,15 @@ // (si defines un error más ubicuo dentro del BC con su propia clase) import { ApiErrorMapper, ConflictApiError, ErrorToApiRule } from "@erp/core/api"; -import { CustomerInvoiceIdAlreadyExistsError } from "../../domain"; +import { + CustomerInvoiceIdAlreadyExistsError, + isCustomerInvoiceIdAlreadyExistsError, +} from "../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) const invoiceDuplicateRule: ErrorToApiRule = { priority: 120, - matches: (e): e is CustomerInvoiceIdAlreadyExistsError => - e instanceof CustomerInvoiceIdAlreadyExistsError, + matches: (e) => isCustomerInvoiceIdAlreadyExistsError(e), build: (e) => new ConflictApiError( (e as CustomerInvoiceIdAlreadyExistsError).message || diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts index 42fc1d9e..d0530885 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts @@ -162,13 +162,13 @@ export class CustomerInvoiceRepository id: UniqueID, transaction: Transaction ): Promise> { + const { CustomerModel } = this._database.models; + try { const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({ resource: "customer-invoice", }); - const { CustomerModel } = this._database.models; - const row = await CustomerInvoiceModel.findOne({ where: { id: id.toString(), company_id: companyId.toString() }, order: [[{ model: CustomerInvoiceItemModel, as: "items" }, "position", "ASC"]], @@ -203,8 +203,8 @@ export class CustomerInvoiceRepository return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString())); } - const customer = mapper.mapToDomain(row); - return customer; + const invoice = mapper.mapToDomain(row); + return invoice; } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } @@ -226,12 +226,14 @@ export class CustomerInvoiceRepository criteria: Criteria, transaction: Transaction ): Promise, Error>> { + const { CustomerModel } = this._database.models; + try { const mapper: ICustomerInvoiceListMapper = this._registry.getQueryMapper({ resource: "customer-invoice", query: "LIST", }); - const { CustomerModel } = this._database.models; + const converter = new CriteriaToSequelizeConverter(); const query = converter.convert(criteria); @@ -272,20 +274,24 @@ export class CustomerInvoiceRepository * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. * @param id - UUID de la factura a eliminar. * @param transaction - Transacción activa para la operación. - * @returns Result + * @returns Result */ async deleteByIdInCompany( companyId: UniqueID, id: UniqueID, transaction: Transaction - ): Promise> { + ): Promise> { try { const deleted = await CustomerInvoiceModel.destroy({ where: { id: id.toString(), company_id: companyId.toString() }, transaction, }); - return Result.ok(); + if (deleted === 0) { + return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString())); + } + + return Result.ok(true); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } diff --git a/modules/customer-invoices/src/web/context/customer-invoices-context.tsx b/modules/customer-invoices/src/web/context/customer-invoices-context.tsx index 356c850c..6a2b64aa 100644 --- a/modules/customer-invoices/src/web/context/customer-invoices-context.tsx +++ b/modules/customer-invoices/src/web/context/customer-invoices-context.tsx @@ -45,7 +45,7 @@ import { PropsWithChildren, createContext } from "react"; export type CustomerInvoicesContextType = {}; export type CustomerInvoicesContextParamsType = { - //service: CustomerInvoiceService; + //service: CustomerInvoiceApplicationService; }; export const CustomerInvoicesContext = createContext({}); diff --git a/modules/customers/src/api/domain/services/customer.service.ts b/modules/customers/src/api/application/customer-application.service.ts similarity index 60% rename from modules/customers/src/api/domain/services/customer.service.ts rename to modules/customers/src/api/application/customer-application.service.ts index 101b2d94..89699bda 100644 --- a/modules/customers/src/api/domain/services/customer.service.ts +++ b/modules/customers/src/api/application/customer-application.service.ts @@ -1,93 +1,55 @@ +// application/customer-application-service.ts import { Criteria } from "@repo/rdx-criteria/server"; import { UniqueID } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; -import { CustomerListDTO } from "../../infrastructure"; -import { Customer, CustomerPatchProps, CustomerProps } from "../aggregates"; -import { ICustomerRepository } from "../repositories"; +import { Transaction } from "sequelize"; +import { Customer, CustomerPatchProps, ICustomerRepository } from "../domain"; +import { CustomerListDTO } from "../infrastructure"; -export class CustomerService { +export class CustomerApplicationService { constructor(private readonly repository: ICustomerRepository) {} /** - * Construye un nuevo agregado Customer a partir de props validadas. - * - * @param companyId - Identificador de la empresa a la que pertenece el cliente. - * @param props - Las propiedades ya validadas para crear el cliente. - * @param customerId - Identificador UUID del cliente (opcional). - * @returns Result - El agregado construido o un error si falla la creación. - */ - buildCustomerInCompany( - companyId: UniqueID, - props: Omit, - customerId?: UniqueID - ): Result { - return Customer.create({ ...props, companyId }, customerId); - } - - /** - * Guarda una instancia de Customer en persistencia. - * - * @param customer - El agregado a guardar (con el companyId ya asignado) - * @param transaction - Transacción activa para la operación. - * @returns Result - El agregado guardado o un error si falla la operación. - */ - async saveCustomer(customer: Customer, transaction: unknown): Promise> { - return this.repository.save(customer, transaction); - } - - /** - * - * Comprueba si existe o no en persistencia un cliente con el ID proporcionado + * Guarda un nuevo cliente y devuelve el cliente guardado. * * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. - * @param customerId - Identificador UUID del cliente + * @param customer - El cliente a guardar. * @param transaction - Transacción activa para la operación. - * @returns Result - Existe el cliente o no. + * @returns Result - El cliente guardado o un error si falla la operación. */ - - existsByIdInCompany( + async createCustomerInCompany( companyId: UniqueID, - customerId: UniqueID, - transaction?: unknown - ): Promise> { - return this.repository.existsByIdInCompany(companyId, customerId, transaction); + customer: Customer, + transaction?: Transaction + ): Promise> { + const result = await this.repository.create(customer, transaction); + if (result.isFailure) return Result.fail(result.error); + + return this.getCustomerByIdInCompany(companyId, customer.id, transaction); } /** - * Obtiene una colección de clientes que cumplen con los filtros definidos en un objeto Criteria. + * Actualiza un cliente existente y devuelve el cliente actualizado. * * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. - * @param criteria - Objeto con condiciones de filtro, paginación y orden. + * @param customer - El cliente a guardar. * @param transaction - Transacción activa para la operación. - * @returns Result, Error> - Colección de clientes o error. + * @returns Result - El cliente guardado o un error si falla la operación. */ - async findCustomerByCriteriaInCompany( + async updateCustomerInCompany( companyId: UniqueID, - criteria: Criteria, - transaction?: unknown - ): Promise, Error>> { - return this.repository.findByCriteriaInCompany(companyId, criteria, transaction); - } + customer: Customer, + transaction?: Transaction + ): Promise> { + const result = await this.repository.update(customer, transaction); + if (result.isFailure) return Result.fail(result.error); - /** - * Recupera un cliente por su identificador único. - * - * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. - * @param customerId - Identificador UUID del cliente. - * @param transaction - Transacción activa para la operación. - * @returns Result - Cliente encontradoF o error. - */ - async getCustomerByIdInCompany( - companyId: UniqueID, - customerId: UniqueID, - transaction?: unknown - ): Promise> { - return this.repository.getByIdInCompany(companyId, customerId, transaction); + return this.getCustomerByIdInCompany(companyId, customer.id, transaction); } /** * Actualiza parcialmente un cliente existente con nuevos datos. - * No lo guarda en el repositorio. + * Solo en memoria. No lo guarda en el repositorio. * * @param companyId - Identificador de la empresa a la que pertenece el cliente. * @param customerId - Identificador del cliente a actualizar. @@ -95,26 +57,23 @@ export class CustomerService { * @param transaction - Transacción activa para la operación. * @returns Result - Cliente actualizado o error. */ - async updateCustomerByIdInCompany( + async patchCustomerByIdInCompany( companyId: UniqueID, customerId: UniqueID, changes: CustomerPatchProps, - transaction?: unknown + transaction?: Transaction ): Promise> { const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction); - if (customerResult.isFailure) { return Result.fail(customerResult.error); } - const customer = customerResult.data; - const updatedCustomer = customer.update(changes); - - if (updatedCustomer.isFailure) { - return Result.fail(updatedCustomer.error); + const updated = customerResult.data.update(changes); + if (updated.isFailure) { + return Result.fail(updated.error); } - return Result.ok(updatedCustomer.data); + return Result.ok(updated.data); } /** @@ -128,8 +87,57 @@ export class CustomerService { async deleteCustomerByIdInCompany( companyId: UniqueID, customerId: UniqueID, - transaction?: unknown - ): Promise> { + transaction?: Transaction + ): Promise> { return this.repository.deleteByIdInCompany(companyId, customerId, transaction); } + + /** + * + * Comprueba si existe o no en persistencia un cliente con el ID proporcionado + * + * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. + * @param customerId - Identificador UUID del cliente + * @param transaction - Transacción activa para la operación. + * @returns Result - Existe el cliente o no. + */ + async existsByIdInCompany( + companyId: UniqueID, + customerId: UniqueID, + transaction?: Transaction + ): Promise> { + return this.repository.existsByIdInCompany(companyId, customerId, transaction); + } + + /** + * Recupera un cliente por su identificador único. + * + * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. + * @param customerId - Identificador UUID del cliente. + * @param transaction - Transacción activa para la operación. + * @returns Result - Cliente encontrado o error. + */ + async getCustomerByIdInCompany( + companyId: UniqueID, + customerId: UniqueID, + transaction?: Transaction + ): Promise> { + return this.repository.getByIdInCompany(companyId, customerId, transaction); + } + + /** + * Obtiene una colección de clientes que cumplen con los filtros definidos en un objeto Criteria. + * + * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. + * @param criteria - Objeto con condiciones de filtro, paginación y orden. + * @param transaction - Transacción activa para la operación. + * @returns Result, Error> - Colección de clientes o error. + */ + async findCustomerByCriteriaInCompany( + companyId: UniqueID, + criteria: Criteria, + transaction?: Transaction + ): Promise, Error>> { + return this.repository.findByCriteriaInCompany(companyId, criteria, transaction); + } } diff --git a/modules/customers/src/api/application/index.ts b/modules/customers/src/api/application/index.ts index 8643bc2a..b17e4cff 100644 --- a/modules/customers/src/api/application/index.ts +++ b/modules/customers/src/api/application/index.ts @@ -1,2 +1,3 @@ +export * from "./customer-application.service"; export * from "./presenters"; export * from "./use-cases"; diff --git a/modules/customers/src/api/application/specs/customer-not-exists.spec.ts b/modules/customers/src/api/application/specs/customer-not-exists.spec.ts index d23e630d..3d42d2ec 100644 --- a/modules/customers/src/api/application/specs/customer-not-exists.spec.ts +++ b/modules/customers/src/api/application/specs/customer-not-exists.spec.ts @@ -1,10 +1,10 @@ import { CompositeSpecification, UniqueID } from "@repo/rdx-ddd"; -import { CustomerService } from "../../domain"; +import { CustomerApplicationService } from "../../application"; import { logger } from "../../helpers"; export class CustomerNotExistsInCompanySpecification extends CompositeSpecification { constructor( - private readonly service: CustomerService, + private readonly service: CustomerApplicationService, private readonly companyId: UniqueID, private readonly transaction?: unknown ) { diff --git a/modules/customers/src/api/application/use-cases/create/create-customer.use-case.ts b/modules/customers/src/api/application/use-cases/create/create-customer.use-case.ts index 9415fab3..c70d4c94 100644 --- a/modules/customers/src/api/application/use-cases/create/create-customer.use-case.ts +++ b/modules/customers/src/api/application/use-cases/create/create-customer.use-case.ts @@ -4,7 +4,7 @@ import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { CreateCustomerRequestDTO } from "../../../../common"; import { logger } from "../../..//helpers"; -import { CustomerService } from "../../../domain"; +import { CustomerApplicationService } from "../../../domain"; import { CustomerFullPresenter } from "../../presenters"; import { CustomerNotExistsInCompanySpecification } from "../../specs"; import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props"; @@ -16,7 +16,7 @@ type CreateCustomerUseCaseInput = { export class CreateCustomerUseCase { constructor( - private readonly service: CustomerService, + private readonly service: CustomerApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} diff --git a/modules/customers/src/api/application/use-cases/delete-customer.use-case.ts b/modules/customers/src/api/application/use-cases/delete-customer.use-case.ts index 2853d586..9f2a90c9 100644 --- a/modules/customers/src/api/application/use-cases/delete-customer.use-case.ts +++ b/modules/customers/src/api/application/use-cases/delete-customer.use-case.ts @@ -1,7 +1,7 @@ import { EntityNotFoundError, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerService } from "../../domain"; +import { CustomerApplicationService } from "../../application"; type DeleteCustomerUseCaseInput = { companyId: UniqueID; @@ -10,7 +10,7 @@ type DeleteCustomerUseCaseInput = { export class DeleteCustomerUseCase { constructor( - private readonly service: CustomerService, + private readonly service: CustomerApplicationService, private readonly transactionManager: ITransactionManager ) {} diff --git a/modules/customers/src/api/application/use-cases/get-customer.use-case.ts b/modules/customers/src/api/application/use-cases/get-customer.use-case.ts index 31227d2d..a7123fec 100644 --- a/modules/customers/src/api/application/use-cases/get-customer.use-case.ts +++ b/modules/customers/src/api/application/use-cases/get-customer.use-case.ts @@ -1,7 +1,7 @@ import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; -import { CustomerService } from "../../domain"; +import { CustomerApplicationService } from "../../application"; import { CustomerFullPresenter } from "../presenters"; type GetCustomerUseCaseInput = { @@ -11,7 +11,7 @@ type GetCustomerUseCaseInput = { export class GetCustomerUseCase { constructor( - private readonly service: CustomerService, + private readonly service: CustomerApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} diff --git a/modules/customers/src/api/application/use-cases/list-customers.use-case.ts b/modules/customers/src/api/application/use-cases/list-customers.use-case.ts index 66f81aba..178033a7 100644 --- a/modules/customers/src/api/application/use-cases/list-customers.use-case.ts +++ b/modules/customers/src/api/application/use-cases/list-customers.use-case.ts @@ -4,7 +4,7 @@ import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { ListCustomersResponseDTO } from "../../../common/dto"; -import { CustomerService } from "../../domain"; +import { CustomerApplicationService } from "../../application"; import { ListCustomersPresenter } from "../presenters"; type ListCustomersUseCaseInput = { @@ -14,7 +14,7 @@ type ListCustomersUseCaseInput = { export class ListCustomersUseCase { constructor( - private readonly service: CustomerService, + private readonly service: CustomerApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} diff --git a/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts b/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts index bbbda8cc..238a06f2 100644 --- a/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts +++ b/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts @@ -3,7 +3,8 @@ import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { Transaction } from "sequelize"; import { UpdateCustomerByIdRequestDTO } from "../../../../common/dto"; -import { CustomerPatchProps, CustomerService } from "../../../domain"; +import { CustomerPatchProps } from "../../../domain"; +import { CustomerApplicationService } from "../../customer-application.service"; import { CustomerFullPresenter } from "../../presenters"; import { mapDTOToUpdateCustomerPatchProps } from "./map-dto-to-update-customer-props"; @@ -15,7 +16,7 @@ type UpdateCustomerUseCaseInput = { export class UpdateCustomerUseCase { constructor( - private readonly service: CustomerService, + private readonly service: CustomerApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} @@ -44,7 +45,7 @@ export class UpdateCustomerUseCase { return this.transactionManager.complete(async (transaction: Transaction) => { try { - const updatedCustomer = await this.service.updateCustomerByIdInCompany( + const updatedCustomer = await this.service.patchCustomerByIdInCompany( companyId, customerId, patchProps, @@ -55,7 +56,11 @@ export class UpdateCustomerUseCase { return Result.fail(updatedCustomer.error); } - const customerOrError = await this.service.saveCustomer(updatedCustomer.data, transaction); + const customerOrError = await this.service.updateCustomerInCompany( + companyId, + updatedCustomer.data, + transaction + ); const customer = customerOrError.data; const dto = presenter.toOutput(customer); return Result.ok(dto); diff --git a/modules/customers/src/api/domain/errors/customer-not-found-error.ts b/modules/customers/src/api/domain/errors/customer-not-found-error.ts new file mode 100644 index 00000000..e85cbd0d --- /dev/null +++ b/modules/customers/src/api/domain/errors/customer-not-found-error.ts @@ -0,0 +1,8 @@ +import { DomainError } from "@repo/rdx-ddd"; + +export class CustomerNotFoundError extends DomainError { + public readonly code = "CUSTOMER_ID" as const; +} + +export const isCustomerNotFoundError = (e: unknown): e is CustomerNotFoundError => + e instanceof CustomerNotFoundError; diff --git a/modules/customers/src/api/domain/errors/index.ts b/modules/customers/src/api/domain/errors/index.ts new file mode 100644 index 00000000..afcc3812 --- /dev/null +++ b/modules/customers/src/api/domain/errors/index.ts @@ -0,0 +1 @@ +export * from "./customer-not-found-error"; diff --git a/modules/customers/src/api/domain/index.ts b/modules/customers/src/api/domain/index.ts index 5dcd597d..ccbba3a8 100644 --- a/modules/customers/src/api/domain/index.ts +++ b/modules/customers/src/api/domain/index.ts @@ -1,4 +1,4 @@ export * from "./aggregates"; +export * from "./errors"; export * from "./repositories"; -export * from "./services"; export * from "./value-objects"; diff --git a/modules/customers/src/api/domain/repositories/customer-repository.interface.ts b/modules/customers/src/api/domain/repositories/customer-repository.interface.ts index cb95a393..671b51a1 100644 --- a/modules/customers/src/api/domain/repositories/customer-repository.interface.ts +++ b/modules/customers/src/api/domain/repositories/customer-repository.interface.ts @@ -10,10 +10,23 @@ import { Customer } from "../aggregates"; */ export interface ICustomerRepository { /** - * Guarda (crea o actualiza) un Customer en la base de datos. - * Retorna el objeto actualizado tras la operación. + * + * Crea un nuevo cliente + * + * @param customer - El cliente nuevo a guardar. + * @param transaction - Transacción activa para la operación. + * @returns Result */ - save(customer: Customer, transaction: unknown): Promise>; + create(customer: Customer, transaction: unknown): Promise>; + + /** + * Actualiza un cliente existente. + * + * @param customer - El cliente a actualizar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + update(customer: Customer, transaction: unknown): Promise>; /** * Comprueba si existe un Customer con un `id` dentro de una `company`. @@ -21,7 +34,7 @@ export interface ICustomerRepository { existsByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: unknown + transaction: unknown ): Promise>; /** @@ -31,7 +44,7 @@ export interface ICustomerRepository { getByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: unknown + transaction: unknown ): Promise>; /** @@ -42,7 +55,7 @@ export interface ICustomerRepository { findByCriteriaInCompany( companyId: UniqueID, criteria: Criteria, - transaction?: unknown + transaction: unknown ): Promise, Error>>; /** @@ -54,5 +67,5 @@ export interface ICustomerRepository { companyId: UniqueID, id: UniqueID, transaction: unknown - ): Promise>; + ): Promise>; } diff --git a/modules/customers/src/api/domain/services/index.ts b/modules/customers/src/api/domain/services/index.ts deleted file mode 100644 index 09df0d7c..00000000 --- a/modules/customers/src/api/domain/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./customer.service"; diff --git a/modules/customers/src/api/infrastructure/dependencies.ts b/modules/customers/src/api/infrastructure/dependencies.ts index 1841ee41..2dd605d4 100644 --- a/modules/customers/src/api/infrastructure/dependencies.ts +++ b/modules/customers/src/api/infrastructure/dependencies.ts @@ -6,13 +6,13 @@ import { } from "@erp/core/api"; import { CreateCustomerUseCase, + CustomerApplicationService, CustomerFullPresenter, + GetCustomerUseCase, ListCustomersPresenter, ListCustomersUseCase, UpdateCustomerUseCase, } from "../application"; -import { GetCustomerUseCase } from "../application/use-cases/get-customer.use-case"; -import { CustomerService } from "../domain"; import { CustomerDomainMapper, CustomerListMapper } from "./mappers"; import { CustomerRepository } from "./sequelize"; @@ -21,7 +21,7 @@ export type CustomerDeps = { mapperRegistry: IMapperRegistry; presenterRegistry: IPresenterRegistry; repo: CustomerRepository; - service: CustomerService; + service: CustomerApplicationService; build: { list: () => ListCustomersUseCase; get: () => GetCustomerUseCase; @@ -32,7 +32,7 @@ export type CustomerDeps = { }; export function buildCustomerDependencies(params: ModuleParams): CustomerDeps { - const { database, logger } = params; + const { database } = params; const transactionManager = new SequelizeTransactionManager(database); // Mapper Registry @@ -43,7 +43,7 @@ export function buildCustomerDependencies(params: ModuleParams): CustomerDeps { // Repository & Services const repo = new CustomerRepository({ mapperRegistry, database }); - const service = new CustomerService(repo); + const service = new CustomerApplicationService(repo); // Presenter Registry const presenterRegistry = new InMemoryPresenterRegistry(); diff --git a/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts b/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts new file mode 100644 index 00000000..a895f66a --- /dev/null +++ b/modules/customers/src/api/infrastructure/express/customer-api-error-mapper.ts @@ -0,0 +1,16 @@ +import { ApiErrorMapper, ConflictApiError, ErrorToApiRule } from "@erp/core/api"; +import { CustomerNotFoundError, isCustomerNotFoundError } from "../../domain"; + +// Crea una regla específica (prioridad alta para sobreescribir mensajes) +const customerNotFoundRule: ErrorToApiRule = { + priority: 120, + matches: (e) => isCustomerNotFoundError(e), + build: (e) => + new ConflictApiError( + (e as CustomerNotFoundError).message || "Customer with the provided id not exists." + ), +}; + +// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra +export const customersApiErrorMapper: ApiErrorMapper = + ApiErrorMapper.default().register(customerNotFoundRule); diff --git a/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts b/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts index f86a46de..94450561 100644 --- a/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts +++ b/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts @@ -1,4 +1,9 @@ -import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api"; +import { + EntityNotFoundError, + InfrastructureRepositoryError, + SequelizeRepository, + translateSequelizeError, +} from "@erp/core/api"; import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server"; import { UniqueID } from "@repo/rdx-ddd"; import { Collection, Result } from "@repo/rdx-utils"; @@ -13,34 +18,69 @@ export class CustomerRepository { /** * - * Guarda un nuevo cliente o actualiza uno existente. + * Crea un nuevo cliente * - * @param customer - El cliente a guardar. + * @param customer - El cliente nuevo a guardar. * @param transaction - Transacción activa para la operación. - * @returns Result + * @returns Result */ - async save(customer: Customer, transaction: Transaction): Promise> { + async create(customer: Customer, transaction?: Transaction): Promise> { try { const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({ resource: "customer", }); + const dto = mapper.mapToPersistence(customer); - const mapperData = mapper.mapToPersistence(customer); - - if (mapperData.isFailure) { - return Result.fail(mapperData.error); + if (dto.isFailure) { + return Result.fail(dto.error); } - const { data } = mapperData; + const { data } = dto; - const [instance] = await CustomerModel.upsert(data, { transaction, returning: true }); - const savedCustomer = mapper.mapToDomain(instance); - return savedCustomer; + await CustomerModel.create(data, { + include: [{ all: true }], + transaction, + }); + + return Result.ok(); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } } + /** + * Actualiza un cliente existente. + * + * @param customer - El cliente a actualizar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + async update(customer: Customer, transaction?: Transaction): Promise> { + try { + const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({ + resource: "customer-invoice", + }); + const dto = mapper.mapToPersistence(customer); + + const { id, ...updatePayload } = dto.data; + const [affected] = await CustomerModel.update(updatePayload, { + where: { id /*, version */ }, + //fields: Object.keys(updatePayload), + transaction, + individualHooks: true, + }); + + if (affected === 0) { + return Result.fail( + new InfrastructureRepositoryError("Concurrency conflict or not found update customer") + ); + } + + return Result.ok(); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } /** * Comprueba si existe un Customer con un `id` dentro de una `company`. * @@ -60,7 +100,7 @@ export class CustomerRepository transaction, }); return Result.ok(Boolean(count > 0)); - } catch (error: any) { + } catch (error: unknown) { return Result.fail(translateSequelizeError(error)); } } @@ -94,7 +134,7 @@ export class CustomerRepository const customer = mapper.mapToDomain(row); return customer; - } catch (error: any) { + } catch (error: unknown) { return Result.fail(translateSequelizeError(error)); } } @@ -138,8 +178,6 @@ export class CustomerRepository company_id: companyId.toString(), }; - console.log(query); - const { rows, count } = await CustomerModel.findAndCountAll({ ...query, transaction, @@ -158,20 +196,24 @@ export class CustomerRepository * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. * @param id - UUID del cliente a eliminar. * @param transaction - Transacción activa para la operación. - * @returns Result + * @returns Result */ async deleteByIdInCompany( companyId: UniqueID, id: UniqueID, transaction: Transaction - ): Promise> { + ): Promise> { try { const deleted = await CustomerModel.destroy({ where: { id: id.toString(), company_id: companyId.toString() }, transaction, }); - return Result.ok(); + if (deleted === 0) { + return Result.fail(new EntityNotFoundError("Customer", "id", id.toString())); + } + + return Result.ok(true); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } diff --git a/modules/customers/src/web/context/customers-context.tsx b/modules/customers/src/web/context/customers-context.tsx index 9d452f05..aea978bc 100644 --- a/modules/customers/src/web/context/customers-context.tsx +++ b/modules/customers/src/web/context/customers-context.tsx @@ -45,7 +45,7 @@ import { PropsWithChildren, createContext } from "react"; export type CustomersContextType = {}; export type CustomersContextParamsType = { - //service: CustomerService; + //service: CustomerApplicationService; }; export const CustomersContext = createContext({}); diff --git a/packages/rdx-ddd/src/errors/application-error.ts b/packages/rdx-ddd/src/errors/application-error.ts index 981e2dde..0393e334 100644 --- a/packages/rdx-ddd/src/errors/application-error.ts +++ b/packages/rdx-ddd/src/errors/application-error.ts @@ -11,3 +11,6 @@ export class ApplicationError extends BaseError<"application"> { super("ApplicationError", message, code, options); } } + +export const isApplicationError = (e: unknown): e is ApplicationError => + e instanceof ApplicationError; diff --git a/packages/rdx-ddd/src/errors/domain-error.ts b/packages/rdx-ddd/src/errors/domain-error.ts index e95ce83b..75f7e873 100644 --- a/packages/rdx-ddd/src/errors/domain-error.ts +++ b/packages/rdx-ddd/src/errors/domain-error.ts @@ -19,3 +19,5 @@ export class DomainError extends BaseError<"domain"> { super("DomainError", message, code, options); } } + +export const isDomainError = (e: unknown): e is DomainError => e instanceof DomainError; diff --git a/packages/rdx-ddd/src/errors/infrastructure-error.ts b/packages/rdx-ddd/src/errors/infrastructure-error.ts index d40c53fb..a7f332fb 100644 --- a/packages/rdx-ddd/src/errors/infrastructure-error.ts +++ b/packages/rdx-ddd/src/errors/infrastructure-error.ts @@ -3,7 +3,14 @@ import { BaseError } from "./base-error"; /** Errores de infraestructura: DB, red, serialización, proveedores externos */ export class InfrastructureError extends BaseError<"infrastructure"> { public readonly layer = "infrastructure" as const; - constructor(message: string, code = "INFRASTRUCTURE_ERROR", options?: ErrorOptions & { metadata?: Record }) { + constructor( + message: string, + code = "INFRASTRUCTURE_ERROR", + options?: ErrorOptions & { metadata?: Record } + ) { super("InfrastructureError", message, code, options); } } + +export const isInfrastructureError = (e: unknown): e is InfrastructureError => + e instanceof InfrastructureError;