diff --git a/.ai/backend/agent.md b/.ai/backend/agent.md new file mode 100644 index 00000000..2d7a96cc --- /dev/null +++ b/.ai/backend/agent.md @@ -0,0 +1,15 @@ +Especialización backend. + +Contiene: + +- Clean Architecture +- DDD +- módulos +- Sequelize +- DTOs +- repositorios +- fiscalidad +- use cases +- Express +- snapshot builders + diff --git a/.ai/frontend/agent.md b/.ai/frontend/agent.md new file mode 100644 index 00000000..22533b3a --- /dev/null +++ b/.ai/frontend/agent.md @@ -0,0 +1,32 @@ +# Frontend ERP Agent + +## Objetivo + +Construir frontend ERP modular, tipado, mantenible y desacoplado. + +--- + +# Stack + +- React +- TypeScript +- Vite +- TanStack Query +- React Hook Form +- Zod +- Zustand (si aplica) +- Tailwind/Shadcn/etc + +--- + +# Principios + +- UI desacoplada de transporte +- SSR/CSR explícito +- Estado mínimo global +- Formularios tipados +- Server state != UI state +- Componentes pequeños +- Evitar smart components gigantes +- Accesibilidad obligatoria +- Diseño orientado a features \ No newline at end of file diff --git a/.ai/index.md b/.ai/index.md new file mode 100644 index 00000000..fb0f1dae --- /dev/null +++ b/.ai/index.md @@ -0,0 +1,10 @@ +# ERP AI Context + +## Shared => filosofía + convenciones +- ./shared/agent.md + +## Backend => dominio + arquitectura + persistencia +- ./backend/agent.md + +## Frontend => interacción + UX + estado + rendering +- ./frontend/agent.md \ No newline at end of file diff --git a/.ai/shared/convenciones.md b/.ai/shared/convenciones.md new file mode 100644 index 00000000..0607a81e --- /dev/null +++ b/.ai/shared/convenciones.md @@ -0,0 +1,5 @@ +- Mantener código en inglés. +- Comentarios solo si aportan, en español. +- No usar any. +- No crear abstracciones genéricas prematuras. + diff --git a/apps/server/src/register-modules.ts b/apps/server/src/register-modules.ts index c9d75daf..2d149a87 100644 --- a/apps/server/src/register-modules.ts +++ b/apps/server/src/register-modules.ts @@ -1,3 +1,4 @@ +import catalogsAPIModule from "@erp/catalogs/api"; import customerInvoicesAPIModule from "@erp/customer-invoices/api"; import customersAPIModule from "@erp/customers/api"; import factuGESAPIModule from "@erp/factuges/api"; @@ -8,6 +9,7 @@ import { registerModule } from "./lib"; export const registerModules = () => { //registerModule(authAPIModule); + registerModule(catalogsAPIModule); registerModule(customersAPIModule); registerModule(customerInvoicesAPIModule); registerModule(factuGESAPIModule); diff --git a/modules/catalogs/package.json b/modules/catalogs/package.json new file mode 100644 index 00000000..82e99b05 --- /dev/null +++ b/modules/catalogs/package.json @@ -0,0 +1,32 @@ +{ + "name": "@erp/catalogs", + "description": "Catalogs module", + "version": "0.1.0", + "private": true, + "type": "module", + "sideEffects": false, + "scripts": { + "typecheck": "tsc -p tsconfig.json --noEmit", + "check": "biome check .", + "lint": "biome lint .", + "clean": "rimraf .turbo node_modules dist" + }, + "exports": { + ".": "./src/api/index.ts", + "./api": "./src/api/index.ts" + }, + "dependencies": { + "@erp/core": "workspace:*", + "@erp/auth": "workspace:*", + "@repo/rdx-ddd": "workspace:*", + "@repo/rdx-utils": "workspace:*", + "@repo/rdx-criteria": "workspace:*", + "express": "^4.22.1", + "sequelize": "^6.37.8", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "typescript": "^6.0.2" + } +} \ No newline at end of file diff --git a/modules/catalogs/src/api/application/di/index.ts b/modules/catalogs/src/api/application/di/index.ts new file mode 100644 index 00000000..81449c81 --- /dev/null +++ b/modules/catalogs/src/api/application/di/index.ts @@ -0,0 +1,5 @@ +export * from "./payment-method-creator.di"; +export * from "./payment-method-finder.di"; +export * from "./payment-method-input-mappers.di"; +export * from "./payment-method-snapshot-builders.di"; +export * from "./payment-method-updater.di"; diff --git a/modules/catalogs/src/api/application/di/payment-method-creator.di.ts b/modules/catalogs/src/api/application/di/payment-method-creator.di.ts new file mode 100644 index 00000000..1e783fed --- /dev/null +++ b/modules/catalogs/src/api/application/di/payment-method-creator.di.ts @@ -0,0 +1,10 @@ +import type { IPaymentMethodRepository } from "../repositories"; +import { type IPaymentMethodCreator, PaymentMethodCreator } from "../services"; + +export const buildPaymentMethodCreator = (params: { + repository: IPaymentMethodRepository; +}): IPaymentMethodCreator => { + const { repository } = params; + + return new PaymentMethodCreator(repository); +}; diff --git a/modules/catalogs/src/api/application/di/payment-method-finder.di.ts b/modules/catalogs/src/api/application/di/payment-method-finder.di.ts new file mode 100644 index 00000000..fb70baa7 --- /dev/null +++ b/modules/catalogs/src/api/application/di/payment-method-finder.di.ts @@ -0,0 +1,8 @@ +import type { IPaymentMethodRepository } from "../repositories"; +import { type IPaymentMethodFinder, PaymentMethodFinder } from "../services"; + +export function buildPaymentMethodFinder( + repository: IPaymentMethodRepository +): IPaymentMethodFinder { + return new PaymentMethodFinder(repository); +} diff --git a/modules/catalogs/src/api/application/di/payment-method-input-mappers.di.ts b/modules/catalogs/src/api/application/di/payment-method-input-mappers.di.ts new file mode 100644 index 00000000..5147ae99 --- /dev/null +++ b/modules/catalogs/src/api/application/di/payment-method-input-mappers.di.ts @@ -0,0 +1,21 @@ +import { + CreatePaymentMethodInputMapper, + type ICreatePaymentMethodInputMapper, + UpdatePaymentMethodInputMapper, +} from "../mappers"; + +export interface IPaymentMethodInputMappers { + createInputMapper: ICreatePaymentMethodInputMapper; + updateInputMapper: UpdatePaymentMethodInputMapper; +} + +export const buildPaymentMethodInputMappers = (): IPaymentMethodInputMappers => { + // Mappers el DTO a las props validadas (PaymentMethodProps) y luego construir agregado + const createInputMapper = new CreatePaymentMethodInputMapper(); + const updateInputMapper = new UpdatePaymentMethodInputMapper(); + + return { + createInputMapper, + updateInputMapper, + }; +}; diff --git a/modules/catalogs/src/api/application/di/payment-method-snapshot-builders.di.ts b/modules/catalogs/src/api/application/di/payment-method-snapshot-builders.di.ts new file mode 100644 index 00000000..bdc433c9 --- /dev/null +++ b/modules/catalogs/src/api/application/di/payment-method-snapshot-builders.di.ts @@ -0,0 +1,16 @@ +// application/issued-invoices/di/snapshot-builders.di.ts + +import { + PaymentMethodFullSnapshotBuilder, + PaymentMethodSummarySnapshotBuilder, +} from "../snapshot-builders"; + +export function buildPaymentMethodSnapshotBuilders() { + const fullSnapshotBuilder = new PaymentMethodFullSnapshotBuilder(); + const summarySnapshotBuilder = new PaymentMethodSummarySnapshotBuilder(); + + return { + full: fullSnapshotBuilder, + summary: summarySnapshotBuilder, + }; +} diff --git a/modules/catalogs/src/api/application/di/payment-method-updater.di.ts b/modules/catalogs/src/api/application/di/payment-method-updater.di.ts new file mode 100644 index 00000000..87197ca7 --- /dev/null +++ b/modules/catalogs/src/api/application/di/payment-method-updater.di.ts @@ -0,0 +1,10 @@ +import type { IPaymentMethodRepository } from "../repositories"; +import { type IPaymentMethodUpdater, PaymentMethodUpdater } from "../services"; + +export const buildPaymentMethodUpdater = (params: { + repository: IPaymentMethodRepository; +}): IPaymentMethodUpdater => { + const { repository } = params; + + return new PaymentMethodUpdater(repository); +}; diff --git a/modules/catalogs/src/api/application/index.ts b/modules/catalogs/src/api/application/index.ts new file mode 100644 index 00000000..b68ea0dc --- /dev/null +++ b/modules/catalogs/src/api/application/index.ts @@ -0,0 +1,7 @@ +export * from "./di"; +export * from "./mappers"; +export * from "./models"; +export * from "./repositories"; +export * from "./services"; +export * from "./snapshot-builders"; +export * from "./use-cases"; diff --git a/modules/catalogs/src/api/application/mappers/create-payment-method-input.mapper.ts b/modules/catalogs/src/api/application/mappers/create-payment-method-input.mapper.ts new file mode 100644 index 00000000..17fdd89f --- /dev/null +++ b/modules/catalogs/src/api/application/mappers/create-payment-method-input.mapper.ts @@ -0,0 +1,67 @@ +import { + DomainError, + Name, + TextValue, + UniqueID, + ValidationErrorCollection, + type ValidationErrorDetail, + extractOrPushError, + maybeFromNullableResult, +} from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { CreatePaymentMethodRequestDTO } from "../../../common"; +import type { IPaymentMethodCreateProps } from "../../domain"; + +export interface ICreatePaymentMethodInputMapper { + map( + dto: CreatePaymentMethodRequestDTO, + params: { companyId: UniqueID } + ): Result<{ id: UniqueID; props: IPaymentMethodCreateProps }, Error>; +} + +export class CreatePaymentMethodInputMapper implements ICreatePaymentMethodInputMapper { + public map( + dto: CreatePaymentMethodRequestDTO, + params: { companyId: UniqueID } + ): Result<{ id: UniqueID; props: IPaymentMethodCreateProps }, Error> { + const errors: ValidationErrorDetail[] = []; + + try { + const paymentMethodId = extractOrPushError(UniqueID.create(dto.id), "id", errors); + + const name = extractOrPushError(Name.create(dto.name), "name", errors); + + const description = extractOrPushError( + maybeFromNullableResult(dto.description, (value) => TextValue.create(value)), + "description", + errors + ); + + const isActive = extractOrPushError(Result.ok(dto.is_active), "is_active", errors); + + this.throwIfValidationErrors(errors); + + const props = { + companyId: params.companyId, + name: name!, + description: description!, + isActive: isActive!, + isSystem: false, + }; + + return Result.ok({ + id: paymentMethodId!, + props, + }); + } catch (err: unknown) { + return Result.fail(new DomainError("Payment method props mapping failed", { cause: err })); + } + } + + private throwIfValidationErrors(errors: ValidationErrorDetail[]): void { + if (errors.length > 0) { + throw new ValidationErrorCollection("Payment method props mapping failed", errors); + } + } +} diff --git a/modules/catalogs/src/api/application/mappers/index.ts b/modules/catalogs/src/api/application/mappers/index.ts new file mode 100644 index 00000000..0d9201f1 --- /dev/null +++ b/modules/catalogs/src/api/application/mappers/index.ts @@ -0,0 +1,2 @@ +export * from "./create-payment-method-input.mapper"; +export * from "./update-payment-method-by-id-input.mapper"; diff --git a/modules/catalogs/src/api/application/mappers/update-payment-method-by-id-input.mapper.ts b/modules/catalogs/src/api/application/mappers/update-payment-method-by-id-input.mapper.ts new file mode 100644 index 00000000..ca804aea --- /dev/null +++ b/modules/catalogs/src/api/application/mappers/update-payment-method-by-id-input.mapper.ts @@ -0,0 +1,86 @@ +import { + DomainError, + Name, + TextValue, + type UniqueID, + ValidationErrorCollection, + type ValidationErrorDetail, + extractOrPushError, + maybeFromNullableResult, +} from "@repo/rdx-ddd"; +import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; + +import type { UpdatePaymentMethodByIdRequestDTO } from "../../../common"; +import type { PaymentMethodPatchProps } from "../../domain"; + +export interface IUpdatePaymentMethodInputMapper { + map( + dto: UpdatePaymentMethodByIdRequestDTO, + params: { companyId: UniqueID } + ): Result; +} + +/** + * @summary Convierte el DTO de update de forma de pago en props de dominio. + * @remarks + * Respeta semántica PATCH en cabecera: + * - omitido: no modificar + * - null: limpiar valor cuando el campo lo permite + * - valor: asignar nuevo valor + * + * Para `items`, no aplica patch granular: + * - undefined: no tocar líneas + * - []: borrar todas las líneas + * - [...]: reemplazar colección completa + */ + +export class UpdatePaymentMethodInputMapper implements IUpdatePaymentMethodInputMapper { + public map( + dto: UpdatePaymentMethodByIdRequestDTO, + _params: { companyId: UniqueID } + ): Result { + try { + const errors: ValidationErrorDetail[] = []; + const paymentmethodPatchProps: PaymentMethodPatchProps = {}; + + toPatchField(dto.name).ifSet((name) => { + if (isNullishOrEmpty(name)) { + errors.push({ + path: "name", + message: "Name cannot be empty", + }); + return; + } + + paymentmethodPatchProps.name = extractOrPushError(Name.create(name), "name", errors); + }); + + toPatchField(dto.description).ifSet((description) => { + paymentmethodPatchProps.description = extractOrPushError( + maybeFromNullableResult(description, (value) => TextValue.create(value)), + "description", + errors + ); + }); + + toPatchField(dto.is_active).ifSet((isActive) => { + paymentmethodPatchProps.isActive = isActive; + }); + + this.throwIfValidationErrors(errors); + + return Result.ok(paymentmethodPatchProps); + } catch (err: unknown) { + console.error(err); + return Result.fail( + new DomainError("PaymentMethod props mapping failed (update)", { cause: err }) + ); + } + } + + private throwIfValidationErrors(errors: ValidationErrorDetail[]): void { + if (errors.length > 0) { + throw new ValidationErrorCollection("PaymentMethod props mapping failed", errors); + } + } +} diff --git a/modules/catalogs/src/api/application/models/index.ts b/modules/catalogs/src/api/application/models/index.ts new file mode 100644 index 00000000..399eb510 --- /dev/null +++ b/modules/catalogs/src/api/application/models/index.ts @@ -0,0 +1,2 @@ +export * from "./payment-method-detail.model"; +export * from "./payment-method-summary.model"; diff --git a/modules/catalogs/src/api/application/models/payment-method-detail.model.ts b/modules/catalogs/src/api/application/models/payment-method-detail.model.ts new file mode 100644 index 00000000..ef161ad0 --- /dev/null +++ b/modules/catalogs/src/api/application/models/payment-method-detail.model.ts @@ -0,0 +1,8 @@ +export type PaymentMethodDetail = { + id: string; + name: string; + type: string; + is_active: boolean; + created_at: string; + updated_at: string; +}; diff --git a/modules/catalogs/src/api/application/models/payment-method-summary.model.ts b/modules/catalogs/src/api/application/models/payment-method-summary.model.ts new file mode 100644 index 00000000..1f8a3366 --- /dev/null +++ b/modules/catalogs/src/api/application/models/payment-method-summary.model.ts @@ -0,0 +1,9 @@ +import type { Name, UniqueID } from "@repo/rdx-ddd"; + +export type PaymentMethodSummary = { + id: UniqueID; + companyId: UniqueID; + name: Name; + isActive: boolean; + isSystem: boolean; +}; diff --git a/modules/catalogs/src/api/application/repositories/index.ts b/modules/catalogs/src/api/application/repositories/index.ts new file mode 100644 index 00000000..bb269b10 --- /dev/null +++ b/modules/catalogs/src/api/application/repositories/index.ts @@ -0,0 +1 @@ +export * from "./payment-method-repository.interface"; diff --git a/modules/catalogs/src/api/application/repositories/payment-method-repository.interface.ts b/modules/catalogs/src/api/application/repositories/payment-method-repository.interface.ts new file mode 100644 index 00000000..19375e4c --- /dev/null +++ b/modules/catalogs/src/api/application/repositories/payment-method-repository.interface.ts @@ -0,0 +1,31 @@ +import type { Criteria } from "@repo/rdx-criteria/server"; +import type { UniqueID } from "@repo/rdx-ddd"; +import type { Collection, Result } from "@repo/rdx-utils"; + +import type { PaymentMethod } from "../../domain"; +import type { PaymentMethodSummary } from "../models"; + +export interface IPaymentMethodRepository { + create(paymentMethod: PaymentMethod, transaction?: unknown): Promise>; + update(paymentMethod: PaymentMethod, transaction?: unknown): Promise>; + existsByIdInCompany( + companyId: UniqueID, + id: UniqueID, + transaction?: unknown + ): Promise>; + getByIdInCompany( + companyId: UniqueID, + id: UniqueID, + transaction?: unknown + ): Promise>; + findByCriteriaInCompany( + companyId: UniqueID, + criteria: Criteria, + transaction?: unknown + ): Promise, Error>>; + deleteByIdInCompany( + companyId: UniqueID, + id: UniqueID, + transaction?: unknown + ): Promise>; +} diff --git a/modules/catalogs/src/api/application/services/index.ts b/modules/catalogs/src/api/application/services/index.ts new file mode 100644 index 00000000..6bb972c0 --- /dev/null +++ b/modules/catalogs/src/api/application/services/index.ts @@ -0,0 +1,5 @@ +export * from "./payment-method-creator"; +export * from "./payment-method-disabler"; +export * from "./payment-method-finder"; +export * from "./payment-method-public-services"; +export * from "./payment-method-updater"; diff --git a/modules/catalogs/src/api/application/services/payment-method-creator.ts b/modules/catalogs/src/api/application/services/payment-method-creator.ts new file mode 100644 index 00000000..bbbd6487 --- /dev/null +++ b/modules/catalogs/src/api/application/services/payment-method-creator.ts @@ -0,0 +1,41 @@ +import type { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import { type IPaymentMethodCreateProps, PaymentMethod } from "../../domain"; +import type { IPaymentMethodRepository } from "../repositories"; + +export interface IPaymentMethodCreatorParams { + companyId: UniqueID; + id: UniqueID; + props: IPaymentMethodCreateProps; + transaction: unknown; +} + +export interface IPaymentMethodCreator { + create(params: IPaymentMethodCreatorParams): Promise>; +} + +export class PaymentMethodCreator implements IPaymentMethodCreator { + constructor(private readonly repository: IPaymentMethodRepository) {} + + async create(params: IPaymentMethodCreatorParams): Promise> { + const { companyId, id, props, transaction } = params; + + // 1. Crear agregado + const paymentMethodResult = PaymentMethod.create({ ...props, companyId }, id); + + if (paymentMethodResult.isFailure) { + return Result.fail(paymentMethodResult.error); + } + + const paymentMethod = paymentMethodResult.data; + + // 2. Persistir + const saveResult = await this.repository.create(paymentMethod, transaction); + if (saveResult.isFailure) { + return Result.fail(saveResult.error); + } + + return Result.ok(paymentMethod); + } +} diff --git a/modules/catalogs/src/api/application/services/payment-method-disabler.ts b/modules/catalogs/src/api/application/services/payment-method-disabler.ts new file mode 100644 index 00000000..5182f1f6 --- /dev/null +++ b/modules/catalogs/src/api/application/services/payment-method-disabler.ts @@ -0,0 +1,35 @@ +import { Result } from "@repo/rdx-utils"; +import type { Transaction } from "sequelize"; + +import type { PaymentMethod } from "../../domain"; +import type { IPaymentMethodRepository } from "../repositories"; + +export interface IPaymentMethodDisabler { + disable(params: { + paymentMethod: PaymentMethod; + transaction?: Transaction; + }): Promise>; +} + +export class PaymentMethodDisabler implements IPaymentMethodDisabler { + constructor(private readonly repository: IPaymentMethodRepository) {} + + async disable(params: { + paymentMethod: PaymentMethod; + transaction?: Transaction; + }): Promise> { + const { paymentMethod, transaction } = params; + + const disableResult = paymentMethod.disable(); + if (disableResult.isFailure) { + return Result.fail(disableResult.error); + } + + const persistenceResult = await this.repository.update(paymentMethod, transaction); + if (persistenceResult.isFailure) { + return Result.fail(persistenceResult.error); + } + + return Result.ok(paymentMethod); + } +} diff --git a/modules/catalogs/src/api/application/services/payment-method-finder.ts b/modules/catalogs/src/api/application/services/payment-method-finder.ts new file mode 100644 index 00000000..ee6433c4 --- /dev/null +++ b/modules/catalogs/src/api/application/services/payment-method-finder.ts @@ -0,0 +1,55 @@ +import type { Criteria } from "@repo/rdx-criteria/server"; +import type { UniqueID } from "@repo/rdx-ddd"; +import type { Collection, Result } from "@repo/rdx-utils"; + +import type { PaymentMethod } from "../../domain"; +import type { PaymentMethodSummary } from "../models"; +import type { IPaymentMethodRepository } from "../repositories"; + +export interface IPaymentMethodFinder { + findPaymentMethodById( + companyId: UniqueID, + invoiceId: UniqueID, + transaction?: unknown + ): Promise>; + + paymentmethodExists( + companyId: UniqueID, + invoiceId: UniqueID, + transaction?: unknown + ): Promise>; + + findPaymentMethodsByCriteria( + companyId: UniqueID, + criteria: Criteria, + transaction?: unknown + ): Promise, Error>>; +} + +export class PaymentMethodFinder implements IPaymentMethodFinder { + constructor(private readonly repository: IPaymentMethodRepository) {} + + async findPaymentMethodById( + companyId: UniqueID, + paymentmethodId: UniqueID, + transaction?: unknown + ): Promise> { + return this.repository.getByIdInCompany(companyId, paymentmethodId, transaction); + } + + async paymentmethodExists( + companyId: UniqueID, + paymentmethodId: UniqueID, + transaction?: unknown + ): Promise> { + return this.repository.existsByIdInCompany(companyId, paymentmethodId, transaction); + } + + async findPaymentMethodsByCriteria( + companyId: UniqueID, + criteria: Criteria, + transaction?: unknown + ): Promise, Error>> { + return this.repository.findByCriteriaInCompany(companyId, criteria, transaction); + } +} diff --git a/modules/catalogs/src/api/application/services/payment-method-public-services.ts b/modules/catalogs/src/api/application/services/payment-method-public-services.ts new file mode 100644 index 00000000..34c45cca --- /dev/null +++ b/modules/catalogs/src/api/application/services/payment-method-public-services.ts @@ -0,0 +1,5 @@ +import type { IPaymentMethodFinder } from "./payment-method-finder"; + +export type IPaymentMethodPublicServices = { + finder: IPaymentMethodFinder; +}; diff --git a/modules/catalogs/src/api/application/services/payment-method-updater.ts b/modules/catalogs/src/api/application/services/payment-method-updater.ts new file mode 100644 index 00000000..33aa3734 --- /dev/null +++ b/modules/catalogs/src/api/application/services/payment-method-updater.ts @@ -0,0 +1,52 @@ +import type { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { PaymentMethod, PaymentMethodPatchProps } from "../../domain"; +import type { IPaymentMethodRepository } from "../repositories"; + +export interface IPaymentMethodUpdater { + update(params: { + companyId: UniqueID; + id: UniqueID; + patchProps: PaymentMethodPatchProps; + transaction?: unknown; + }): Promise>; +} + +export class PaymentMethodUpdater implements IPaymentMethodUpdater { + constructor(private readonly repository: IPaymentMethodRepository) {} + + async update(params: { + companyId: UniqueID; + id: UniqueID; + patchProps: PaymentMethodPatchProps; + transaction?: unknown; + }): Promise> { + const { companyId, id, patchProps, transaction } = params; + + // Recuperar agregado existente + const existingResult = await this.repository.getByIdInCompany(companyId, id, transaction); + + if (existingResult.isFailure) { + return Result.fail(existingResult.error); + } + + const paymentMethod = existingResult.data; + + // Aplicar cambios en el agregado + const updateResult = paymentMethod.update(patchProps); + + if (updateResult.isFailure) { + return Result.fail(updateResult.error); + } + + // Persistir cambios + const saveResult = await this.repository.update(paymentMethod, transaction); + + if (saveResult.isFailure) { + return Result.fail(saveResult.error); + } + + return Result.ok(paymentMethod); + } +} diff --git a/modules/catalogs/src/api/application/snapshot-builders/full/index.ts b/modules/catalogs/src/api/application/snapshot-builders/full/index.ts new file mode 100644 index 00000000..d22b69f7 --- /dev/null +++ b/modules/catalogs/src/api/application/snapshot-builders/full/index.ts @@ -0,0 +1 @@ +export * from './payment-method-full-snapshot-builder'; diff --git a/modules/catalogs/src/api/application/snapshot-builders/full/payment-method-full-snapshot-builder.ts b/modules/catalogs/src/api/application/snapshot-builders/full/payment-method-full-snapshot-builder.ts new file mode 100644 index 00000000..0db00d6c --- /dev/null +++ b/modules/catalogs/src/api/application/snapshot-builders/full/payment-method-full-snapshot-builder.ts @@ -0,0 +1,20 @@ +import type { GetPaymentMethodByIdResponseDTO } from "@erp/catalogs/common"; +import type { ISnapshotBuilder } from "@erp/core/api"; +import { toNullable } from "@repo/rdx-ddd"; + +import type { PaymentMethod } from "../../../domain"; + +export interface IPaymentMethodFullSnapshotBuilder + extends ISnapshotBuilder {} + +export class PaymentMethodFullSnapshotBuilder implements IPaymentMethodFullSnapshotBuilder { + public toOutput(paymentMethod: PaymentMethod): GetPaymentMethodByIdResponseDTO { + return { + id: paymentMethod.id.toPrimitive(), + name: paymentMethod.name.toPrimitive(), + description: toNullable(paymentMethod.description, (value) => value.toPrimitive()), + is_active: paymentMethod.isActive, + is_system: paymentMethod.isSystem, + }; + } +} diff --git a/modules/catalogs/src/api/application/snapshot-builders/index.ts b/modules/catalogs/src/api/application/snapshot-builders/index.ts new file mode 100644 index 00000000..3b83e1ff --- /dev/null +++ b/modules/catalogs/src/api/application/snapshot-builders/index.ts @@ -0,0 +1,2 @@ +export * from "./full"; +export * from "./summary"; diff --git a/modules/catalogs/src/api/application/snapshot-builders/summary/index.ts b/modules/catalogs/src/api/application/snapshot-builders/summary/index.ts new file mode 100644 index 00000000..275b5446 --- /dev/null +++ b/modules/catalogs/src/api/application/snapshot-builders/summary/index.ts @@ -0,0 +1 @@ +export * from "./payment-method-summary-snapshot-builder"; diff --git a/modules/catalogs/src/api/application/snapshot-builders/summary/payment-method-summary-snapshot-builder.ts b/modules/catalogs/src/api/application/snapshot-builders/summary/payment-method-summary-snapshot-builder.ts new file mode 100644 index 00000000..cef5faad --- /dev/null +++ b/modules/catalogs/src/api/application/snapshot-builders/summary/payment-method-summary-snapshot-builder.ts @@ -0,0 +1,19 @@ +import type { ISnapshotBuilder } from "@erp/core/api"; + +import type { PaymentMethodSummaryDTO } from "../../../../common/"; +import type { PaymentMethodSummary } from "../../models"; + +export interface IPaymentMethodSummarySnapshotBuilder + extends ISnapshotBuilder {} + +export class PaymentMethodSummarySnapshotBuilder implements IPaymentMethodSummarySnapshotBuilder { + public toOutput(paymentMethod: PaymentMethodSummary): PaymentMethodSummaryDTO { + return { + id: paymentMethod.id.toString(), + company_id: paymentMethod.companyId.toString(), + name: paymentMethod.name.toString(), + is_system: paymentMethod.isSystem, + is_active: paymentMethod.isActive, + }; + } +} diff --git a/modules/catalogs/src/api/application/use-cases/create-payment-method.use-case.ts b/modules/catalogs/src/api/application/use-cases/create-payment-method.use-case.ts new file mode 100644 index 00000000..5a7b2762 --- /dev/null +++ b/modules/catalogs/src/api/application/use-cases/create-payment-method.use-case.ts @@ -0,0 +1,51 @@ +import type { CreatePaymentMethodRequestDTO } from "@erp/catalogs/common"; +import type { ITransactionManager } from "@erp/core/api"; +import type { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { ICreatePaymentMethodInputMapper } from "../mappers"; +import type { IPaymentMethodCreator } from "../services"; +import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders"; + +export type CreatePaymentMethodUseCaseInput = { + companyId: UniqueID; + dto: CreatePaymentMethodRequestDTO; +}; + +type CreatePaymentMethodUseCaseDeps = { + dtoMapper: ICreatePaymentMethodInputMapper; + creator: IPaymentMethodCreator; + fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder; + transactionManager: ITransactionManager; +}; + +export class CreatePaymentMethodUseCase { + constructor(private readonly deps: CreatePaymentMethodUseCaseDeps) {} + + public execute(params: CreatePaymentMethodUseCaseInput) { + const { dto, companyId } = params; + console.log("Executing CreatePaymentMethodUseCase with params:", params); + + const mappedPropsResult = this.deps.dtoMapper.map(dto, { companyId }); + if (mappedPropsResult.isFailure) { + return mappedPropsResult; + } + + const { props, id } = mappedPropsResult.data; + + return this.deps.transactionManager.complete(async (transaction: unknown) => { + try { + const createResult = await this.deps.creator.create({ companyId, id, props, transaction }); + + if (createResult.isFailure) { + return createResult; + } + + const snapshot = this.deps.fullSnapshotBuilder.toOutput(createResult.data); + return Result.ok(snapshot); + } catch (error: unknown) { + return Result.fail(error as Error); + } + }); + } +} diff --git a/modules/catalogs/src/api/application/use-cases/disable-payment-method-by-id.use-case.ts b/modules/catalogs/src/api/application/use-cases/disable-payment-method-by-id.use-case.ts new file mode 100644 index 00000000..394a13f2 --- /dev/null +++ b/modules/catalogs/src/api/application/use-cases/disable-payment-method-by-id.use-case.ts @@ -0,0 +1,47 @@ +import type { ITransactionManager } from "@erp/core/api"; +import { Result } from "@repo/rdx-utils"; +import type { Transaction } from "sequelize"; + +import type { IPaymentMethodDisabler, IPaymentMethodFinder } from "../services"; +import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders"; + +export type DisablePaymentMethodByIdUseCaseInput = { + id: string; +}; + +export class DisablePaymentMethodByIdUseCase { + constructor( + private readonly deps: { + finder: IPaymentMethodFinder; + disabler: IPaymentMethodDisabler; + fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder; + transactionManager: ITransactionManager; + } + ) {} + + public execute(params: DisablePaymentMethodByIdUseCaseInput) { + const { id } = params; + + return this.deps.transactionManager.complete(async (transaction: unknown) => { + const tx = transaction as Transaction; + try { + const findResult = await this.deps.finder.getById(id, tx); + if (findResult.isFailure) { + return Result.fail(findResult.error); + } + + const disableResult = await this.deps.disabler.disable({ + paymentMethod: findResult.data, + transaction: tx, + }); + if (disableResult.isFailure) { + return Result.fail(disableResult.error); + } + + return Result.ok(this.deps.fullSnapshotBuilder.toOutput(disableResult.data)); + } catch (error: unknown) { + return Result.fail(error as Error); + } + }); + } +} diff --git a/modules/catalogs/src/api/application/use-cases/get-payment-method-by-id.use-case.ts b/modules/catalogs/src/api/application/use-cases/get-payment-method-by-id.use-case.ts new file mode 100644 index 00000000..bb6b0f5c --- /dev/null +++ b/modules/catalogs/src/api/application/use-cases/get-payment-method-by-id.use-case.ts @@ -0,0 +1,47 @@ +import type { ITransactionManager } from "@erp/core/api"; +import { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { IPaymentMethodFinder } from "../services"; +import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders"; + +export type GetPaymentMethodByIdUseCaseInput = { + companyId: UniqueID; + payment_method_id: string; +}; + +export class GetPaymentMethodByIdUseCase { + constructor( + private readonly finder: IPaymentMethodFinder, + private readonly fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(params: GetPaymentMethodByIdUseCaseInput) { + const { payment_method_id, companyId } = params; + + const idOrError = UniqueID.create(payment_method_id); + if (idOrError.isFailure) { + return Result.fail(idOrError.error); + } + + const paymentMethodId = idOrError.data; + + return this.transactionManager.complete(async (transaction: unknown) => { + try { + const result = await this.finder.findPaymentMethodById( + companyId, + paymentMethodId, + transaction + ); + if (result.isFailure) { + return Result.fail(result.error); + } + + return Result.ok(this.fullSnapshotBuilder.toOutput(result.data)); + } catch (error: unknown) { + return Result.fail(error as Error); + } + }); + } +} diff --git a/modules/catalogs/src/api/application/use-cases/index.ts b/modules/catalogs/src/api/application/use-cases/index.ts new file mode 100644 index 00000000..7f37505a --- /dev/null +++ b/modules/catalogs/src/api/application/use-cases/index.ts @@ -0,0 +1,5 @@ +export * from "./create-payment-method.use-case"; +export * from "./disable-payment-method-by-id.use-case"; +export * from "./get-payment-method-by-id.use-case"; +export * from "./list-payment-methods.use-case"; +export * from "./update-payment-method-by-id.use-case"; diff --git a/modules/catalogs/src/api/application/use-cases/list-payment-methods.use-case.ts b/modules/catalogs/src/api/application/use-cases/list-payment-methods.use-case.ts new file mode 100644 index 00000000..7531e9ea --- /dev/null +++ b/modules/catalogs/src/api/application/use-cases/list-payment-methods.use-case.ts @@ -0,0 +1,59 @@ +import type { ITransactionManager } from "@erp/core/api"; +import type { Criteria } from "@repo/rdx-criteria/server"; +import type { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { IPaymentMethodFinder } from "../services"; +import type { IPaymentMethodSummarySnapshotBuilder } from "../snapshot-builders"; + +type ListPaymentMethodsUseCaseInput = { + companyId: UniqueID; + criteria: Criteria; +}; + +export class ListPaymentMethodsUseCase { + constructor( + private readonly finder: IPaymentMethodFinder, + private readonly summarySnapshotBuilder: IPaymentMethodSummarySnapshotBuilder, + private readonly transactionManager: ITransactionManager + ) {} + + public execute(params: ListPaymentMethodsUseCaseInput) { + const { criteria, companyId } = params; + + return this.transactionManager.complete(async (transaction: unknown) => { + try { + const result = await this.finder.findPaymentMethodsByCriteria( + companyId, + criteria, + transaction + ); + + if (result.isFailure) { + return Result.fail(result.error); + } + + const paymentMethods = result.data; + const totalItems = paymentMethods.total(); + + const items = paymentMethods.map((item) => this.summarySnapshotBuilder.toOutput(item)); + + const snapshot = { + page: criteria.pageNumber, + per_page: criteria.pageSize, + total_pages: Math.ceil(totalItems / criteria.pageSize), + total_items: totalItems, + items: items, + metadata: { + entity: "payment_methods", + criteria: criteria.toJSON(), + }, + }; + + return Result.ok(snapshot); + } catch (error: unknown) { + return Result.fail(error as Error); + } + }); + } +} diff --git a/modules/catalogs/src/api/application/use-cases/update-payment-method-by-id.use-case.ts b/modules/catalogs/src/api/application/use-cases/update-payment-method-by-id.use-case.ts new file mode 100644 index 00000000..94bf0b1c --- /dev/null +++ b/modules/catalogs/src/api/application/use-cases/update-payment-method-by-id.use-case.ts @@ -0,0 +1,64 @@ +import type { UpdatePaymentMethodByIdRequestDTO } from "@erp/catalogs/common"; +import type { ITransactionManager } from "@erp/core/api"; +import { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { IUpdatePaymentMethodInputMapper } from "../mappers"; +import type { IPaymentMethodFinder, IPaymentMethodUpdater } from "../services"; +import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders"; + +export type UpdatePaymentMethodByIdUseCaseInput = { + companyId: UniqueID; + payment_method_id: string; + dto: UpdatePaymentMethodByIdRequestDTO; +}; + +export class UpdatePaymentMethodByIdUseCase { + constructor( + private readonly deps: { + updater: IPaymentMethodUpdater; + finder: IPaymentMethodFinder; + dtoMapper: IUpdatePaymentMethodInputMapper; + fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder; + transactionManager: ITransactionManager; + } + ) {} + + public execute(params: UpdatePaymentMethodByIdUseCaseInput) { + const { companyId, payment_method_id, dto } = params; + + const idOrError = UniqueID.create(payment_method_id); + if (idOrError.isFailure) { + return Result.fail(idOrError.error); + } + + const paymentMethodId = idOrError.data; + + // Mapear DTO → props de dominio + const patchPropsResult = this.deps.dtoMapper.map(dto, { companyId }); + if (patchPropsResult.isFailure) { + return patchPropsResult; + } + + const patchProps = patchPropsResult.data; + + return this.deps.transactionManager.complete(async (transaction: unknown) => { + try { + const updateResult = await this.deps.updater.update({ + companyId, + id: paymentMethodId, + patchProps, + transaction, + }); + + if (updateResult.isFailure) { + return Result.fail(updateResult.error); + } + + return Result.ok(this.deps.fullSnapshotBuilder.toOutput(updateResult.data)); + } catch (error: unknown) { + return Result.fail(error as Error); + } + }); + } +} diff --git a/modules/catalogs/src/api/domain/index.ts b/modules/catalogs/src/api/domain/index.ts new file mode 100644 index 00000000..cd29a5d4 --- /dev/null +++ b/modules/catalogs/src/api/domain/index.ts @@ -0,0 +1 @@ +export * from "./payment-methods"; diff --git a/modules/catalogs/src/api/domain/payment-methods/errors.ts b/modules/catalogs/src/api/domain/payment-methods/errors.ts new file mode 100644 index 00000000..0cc1b93c --- /dev/null +++ b/modules/catalogs/src/api/domain/payment-methods/errors.ts @@ -0,0 +1,27 @@ +import { DomainError } from "@repo/rdx-ddd"; + +export class InvalidPaymentMethodIdError extends DomainError { + public readonly code = "PAYMENT_METHOD_INVALID_ID" as const; +} + +export const isInvalidPaymentMethodIdError = (e: unknown): e is InvalidPaymentMethodIdError => + e instanceof InvalidPaymentMethodIdError; + +export class InvalidPaymentMethodNameError extends DomainError { + public readonly code = "PAYMENT_METHOD_NAME" as const; +} + +export const isInvalidPaymentMethodNameError = (e: unknown): e is InvalidPaymentMethodNameError => + e instanceof InvalidPaymentMethodNameError; + +export class PaymentMethodNotFoundError extends DomainError { + public readonly code = "PAYMENT_METHOD_NOT_FOUND" as const; +} + +export class PaymentMethodCannotBeDeletedError extends DomainError { + public readonly code = "PAYMENT_METHOD_CANNOT_BE_DELETED" as const; +} + +export const isPaymentMethodCannotBeDeletedError = ( + e: unknown +): e is PaymentMethodCannotBeDeletedError => e instanceof PaymentMethodCannotBeDeletedError; diff --git a/modules/catalogs/src/api/domain/payment-methods/index.ts b/modules/catalogs/src/api/domain/payment-methods/index.ts new file mode 100644 index 00000000..f1da9c9d --- /dev/null +++ b/modules/catalogs/src/api/domain/payment-methods/index.ts @@ -0,0 +1,4 @@ +export * from "./errors"; +export * from "./payment-method.aggregate"; +export * from "./payment-method-name"; +export * from "./payment-method-type"; diff --git a/modules/catalogs/src/api/domain/payment-methods/payment-method-name.ts b/modules/catalogs/src/api/domain/payment-methods/payment-method-name.ts new file mode 100644 index 00000000..55b46b3a --- /dev/null +++ b/modules/catalogs/src/api/domain/payment-methods/payment-method-name.ts @@ -0,0 +1,28 @@ +import { Result } from "@repo/rdx-utils"; + +import { InvalidPaymentMethodNameError } from "./errors"; + +export class PaymentMethodName { + private constructor(private readonly value: string) {} + + public static create(name: string): Result { + const trimmed = name?.trim() ?? ""; + if (trimmed.length === 0) { + return Result.fail(new InvalidPaymentMethodNameError("Payment method name cannot be empty")); + } + + return Result.ok(new PaymentMethodName(trimmed)); + } + + public static fromPersistence(name: string): PaymentMethodName { + return new PaymentMethodName(name); + } + + public toString(): string { + return this.value; + } + + public toPrimitive(): string { + return this.value; + } +} diff --git a/modules/catalogs/src/api/domain/payment-methods/payment-method-type.ts b/modules/catalogs/src/api/domain/payment-methods/payment-method-type.ts new file mode 100644 index 00000000..8ea739de --- /dev/null +++ b/modules/catalogs/src/api/domain/payment-methods/payment-method-type.ts @@ -0,0 +1,43 @@ +import { Result } from "@repo/rdx-utils"; + +import { InvalidPaymentMethodTypeError } from "./errors"; + +export const PAYMENT_METHOD_TYPES = [ + "cash", + "bank_transfer", + "card", + "direct_debit", + "other", +] as const; + +export type PaymentMethodTypeValue = (typeof PAYMENT_METHOD_TYPES)[number]; + +export class PaymentMethodType { + private constructor(private readonly value: PaymentMethodTypeValue) {} + + public static create(type: string): Result { + const normalized = String(type).trim() as PaymentMethodTypeValue; + + if (!PAYMENT_METHOD_TYPES.includes(normalized)) { + return Result.fail( + new InvalidPaymentMethodTypeError( + `Payment method type must be one of: ${PAYMENT_METHOD_TYPES.join(", ")}` + ) + ); + } + + return Result.ok(new PaymentMethodType(normalized)); + } + + public static fromPersistence(type: PaymentMethodTypeValue): PaymentMethodType { + return new PaymentMethodType(type); + } + + public toString(): PaymentMethodTypeValue { + return this.value; + } + + public toPrimitive(): PaymentMethodTypeValue { + return this.value; + } +} diff --git a/modules/catalogs/src/api/domain/payment-methods/payment-method.aggregate.ts b/modules/catalogs/src/api/domain/payment-methods/payment-method.aggregate.ts new file mode 100644 index 00000000..8f2f5b2e --- /dev/null +++ b/modules/catalogs/src/api/domain/payment-methods/payment-method.aggregate.ts @@ -0,0 +1,107 @@ +import { AggregateRoot, type Name, type TextValue, type UniqueID } from "@repo/rdx-ddd"; +import { type Maybe, Result } from "@repo/rdx-utils"; + +export interface IPaymentMethodCreateProps { + companyId: UniqueID; + name: Name; + description: Maybe; + isActive: boolean; + isSystem: boolean; +} + +export type PaymentMethodPatchProps = Partial< + Omit +>; + +export type PaymentMethodInternalProps = IPaymentMethodCreateProps; + +export type PaymentMethodProps = PaymentMethodPatchProps; + +export class PaymentMethod extends AggregateRoot { + protected constructor(props: PaymentMethodInternalProps, id?: UniqueID) { + super(props, id); // eslint-disable-line @typescript-eslint/no-unused-vars + } + + public static create( + props: IPaymentMethodCreateProps, + id?: UniqueID + ): Result { + const validationResult = PaymentMethod.validateCreateProps(props); + + if (validationResult.isFailure) { + return Result.fail(validationResult.error); + } + + // Crear instancia + const paymentMethod = new PaymentMethod(props, id); + + // Disparar eventos de dominio + // ... + // ... + + return Result.ok(paymentMethod); + } + + public static rehydrate(props: PaymentMethodInternalProps, id: UniqueID): PaymentMethod { + return new PaymentMethod(props, id); + } + + private static validateCreateProps(_props: IPaymentMethodCreateProps): Result { + return Result.ok(); + } + + public get companyId(): UniqueID { + return this.props.companyId; + } + + public get name(): Name { + return this.props.name; + } + + public get description(): Maybe { + return this.props.description; + } + + public get isActive(): boolean { + return this.props.isActive; + } + + public get isSystem(): boolean { + return this.props.isSystem; + } + + public update(props: Partial): Result { + if (props.name !== undefined) { + this.props.name = props.name; + } + + if (props.description !== undefined) { + this.props.description = props.description; + } + + if (props.isActive !== undefined) { + this.props.isActive = props.isActive; + } + + return Result.ok(); + } + + public disable(): Result { + if (!this.isActive) { + return Result.ok(); + } + + this.props.isActive = false; + return Result.ok(); + } + + public toJSON() { + return { + id: this.id.toPrimitive(), + name: this.props.name.toPrimitive(), + description: this.props.description, + is_active: this.isActive, + is_system: this.isSystem, + }; + } +} diff --git a/modules/catalogs/src/api/index.ts b/modules/catalogs/src/api/index.ts new file mode 100644 index 00000000..edd3a254 --- /dev/null +++ b/modules/catalogs/src/api/index.ts @@ -0,0 +1,44 @@ +import type { IModuleServer } from "@erp/core/api"; + +import { models, paymentMethodsRouter } from "./infrastructure"; +import { + buildCatalogsDependencies, + buildCatalogsPublicServices, +} from "./infrastructure/di/catalogs.di"; + +export * from "./infrastructure/persistence/sequelize"; + +export const catalogsAPIModule: IModuleServer = { + name: "catalogs", + version: "1.0.0", + dependencies: [], + + async setup(params) { + const internal = buildCatalogsDependencies(params); + const publicServices = buildCatalogsPublicServices(params, internal); + + params.logger.info("🚀 Catalogs module dependencies registered", { + label: this.name, + }); + + return { + models, + services: { + paymentMethod: publicServices, + }, + internal, + }; + }, + + async start(params) { + const { logger } = params; + + paymentMethodsRouter(params); + + logger.info("🚀 Catalogs module started", { + label: this.name, + }); + }, +}; + +export default catalogsAPIModule; diff --git a/modules/catalogs/src/api/infrastructure/di/catalogs.di.ts b/modules/catalogs/src/api/infrastructure/di/catalogs.di.ts new file mode 100644 index 00000000..2aa75e2f --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/di/catalogs.di.ts @@ -0,0 +1,23 @@ +import type { ModuleParams, SetupParams } from "@erp/core/api"; + +import { + type PaymentMethodsInternalDeps, + buildPaymentMethodsDependencies, + buildPaymentMethodsPublicServices, +} from "./payment-methods.di"; + +export type CatalogsInternalDeps = { + paymentMethods: PaymentMethodsInternalDeps; +}; + +export const buildCatalogsDependencies = (params: ModuleParams): CatalogsInternalDeps => { + return { + paymentMethods: buildPaymentMethodsDependencies(params), + }; +}; + +export const buildCatalogsPublicServices = (params: SetupParams, deps: CatalogsInternalDeps) => { + return { + paymentMethods: buildPaymentMethodsPublicServices(params, deps.paymentMethods), + }; +}; diff --git a/modules/catalogs/src/api/infrastructure/di/index.ts b/modules/catalogs/src/api/infrastructure/di/index.ts new file mode 100644 index 00000000..500d6cd3 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/di/index.ts @@ -0,0 +1 @@ +export * from "./catalogs.di"; diff --git a/modules/catalogs/src/api/infrastructure/di/payment-method-persistence-mappers.di.ts b/modules/catalogs/src/api/infrastructure/di/payment-method-persistence-mappers.di.ts new file mode 100644 index 00000000..03499699 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/di/payment-method-persistence-mappers.di.ts @@ -0,0 +1,27 @@ +import { + SequelizePaymentMethodDomainMapper, + SequelizePaymentMethodSummaryMapper, +} from "../persistence/index"; + +export interface IPaymentMethodPersistenceMappers { + domainMapper: SequelizePaymentMethodDomainMapper; + listMapper: SequelizePaymentMethodSummaryMapper; + + //createMapper: CreatePaymentMethodRequestMapper; +} + +export const buildPaymentMethodPersistenceMappers = (): IPaymentMethodPersistenceMappers => { + // Mappers para el repositorio + const domainMapper = new SequelizePaymentMethodDomainMapper(); + const listMapper = new SequelizePaymentMethodSummaryMapper(); + + // Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado + //const createMapper = new CreatePaymentMethodRequestMapper(catalogs); + + return { + domainMapper, + listMapper, + + //createMapper, + }; +}; diff --git a/modules/catalogs/src/api/infrastructure/di/payment-method-repositories.di.ts b/modules/catalogs/src/api/infrastructure/di/payment-method-repositories.di.ts new file mode 100644 index 00000000..b13b3efb --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/di/payment-method-repositories.di.ts @@ -0,0 +1,14 @@ +import type { Sequelize } from "sequelize"; + +import { SequelizePaymentMethodRepository } from "../persistence"; + +import type { IPaymentMethodPersistenceMappers } from "./payment-method-persistence-mappers.di"; + +export const buildPaymentMethodRepository = (params: { + database: Sequelize; + mappers: IPaymentMethodPersistenceMappers; +}) => { + const { database, mappers } = params; + + return new SequelizePaymentMethodRepository(mappers.domainMapper, mappers.listMapper, database); +}; diff --git a/modules/catalogs/src/api/infrastructure/di/payment-methods.di.ts b/modules/catalogs/src/api/infrastructure/di/payment-methods.di.ts new file mode 100644 index 00000000..e3b54d52 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/di/payment-methods.di.ts @@ -0,0 +1,96 @@ +import { type ModuleParams, type SetupParams, buildTransactionManager } from "@erp/core/api"; +import type { Sequelize } from "sequelize"; + +import { + buildPaymentMethodCreator, + buildPaymentMethodFinder, + buildPaymentMethodInputMappers, + buildPaymentMethodSnapshotBuilders, +} from "../../application"; +import type { IPaymentMethodRepository } from "../../application/repositories"; +import type { IPaymentMethodFinder } from "../../application/services"; +import { PaymentMethodFinder, PaymentMethodUpdater } from "../../application/services"; +import { + CreatePaymentMethodUseCase, + type DisablePaymentMethodByIdUseCase, + GetPaymentMethodByIdUseCase, + ListPaymentMethodsUseCase, + UpdatePaymentMethodByIdUseCase, +} from "../../application/use-cases"; + +import { buildPaymentMethodPersistenceMappers } from "./payment-method-persistence-mappers.di"; +import { buildPaymentMethodRepository } from "./payment-method-repositories.di"; + +export type PaymentMethodsInternalDeps = { + repository: IPaymentMethodRepository; + useCases: { + listPaymentMethods: () => ListPaymentMethodsUseCase; + getPaymentMethodById: () => GetPaymentMethodByIdUseCase; + createPaymentMethod: () => CreatePaymentMethodUseCase; + updatePaymentMethodById: () => UpdatePaymentMethodByIdUseCase; + disablePaymentMethodById: () => DisablePaymentMethodByIdUseCase; + }; +}; + +export const buildPaymentMethodsDependencies = ( + params: ModuleParams +): PaymentMethodsInternalDeps => { + const { database } = params; + + // Infrastructure + const transactionManager = buildTransactionManager(database as Sequelize); + const persistenceMappers = buildPaymentMethodPersistenceMappers(); + + const repository = buildPaymentMethodRepository({ database, mappers: persistenceMappers }); + + // Application helpers + const inputMappers = buildPaymentMethodInputMappers(); + const finder = buildPaymentMethodFinder(repository); + const creator = buildPaymentMethodCreator({ repository }); + const updater = new PaymentMethodUpdater(repository); + //const disabler = new PaymentMethodDisabler(repository); + + const snapshotBuilders = buildPaymentMethodSnapshotBuilders(); + + return { + repository, + useCases: { + listPaymentMethods: () => + new ListPaymentMethodsUseCase(finder, snapshotBuilders.summary, transactionManager), + getPaymentMethodById: () => + new GetPaymentMethodByIdUseCase(finder, snapshotBuilders.full, transactionManager), + createPaymentMethod: () => + new CreatePaymentMethodUseCase({ + dtoMapper: inputMappers.createInputMapper, + creator, + fullSnapshotBuilder: snapshotBuilders.full, + transactionManager, + }), + + updatePaymentMethodById: () => + new UpdatePaymentMethodByIdUseCase({ + updater, + finder, + dtoMapper: inputMappers.updateInputMapper, + fullSnapshotBuilder: snapshotBuilders.full, + transactionManager, + }), + /*disablePaymentMethodById: () => + new DisablePaymentMethodByIdUseCase({ + finder, + disabler, + fullSnapshotBuilder, + transactionManager, + }),*/ + }, + }; +}; + +export const buildPaymentMethodsPublicServices = ( + _params: SetupParams, + deps: PaymentMethodsInternalDeps +): { finder: IPaymentMethodFinder } => { + return { + finder: new PaymentMethodFinder(deps.repository), + }; +}; diff --git a/modules/catalogs/src/api/infrastructure/express/index.ts b/modules/catalogs/src/api/infrastructure/express/index.ts new file mode 100644 index 00000000..1c7eb085 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/index.ts @@ -0,0 +1 @@ +export * from "./payment-methods/payment-methods.routes"; diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/create-payment-method.controller.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/create-payment-method.controller.ts new file mode 100644 index 00000000..23070994 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/create-payment-method.controller.ts @@ -0,0 +1,40 @@ +import { + ExpressController, + forbidQueryFieldGuard, + requireAuthenticatedGuard, + requireCompanyContextGuard, +} from "@erp/core/api"; + +import type { CreatePaymentMethodRequestDTO } from "../../../../../common"; +import type { CreatePaymentMethodUseCase } from "../../../../application"; +import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper"; + +export class CreatePaymentMethodController extends ExpressController { + constructor(private readonly useCase: CreatePaymentMethodUseCase) { + super(); + + this.errorMapper = paymentmethodsApiErrorMapper; + + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query + this.registerGuards( + requireAuthenticatedGuard(), + requireCompanyContextGuard(), + forbidQueryFieldGuard("companyId") + ); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) { + return this.forbiddenError("Tenant ID not found"); + } + + const dto = this.req.body satisfies CreatePaymentMethodRequestDTO; + const result = await this.useCase.execute({ dto, companyId }); + + return result.match( + (data) => this.created(data), + (err) => this.handleError(err) + ); + } +} diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/disable-payment-method-by-id.controller.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/disable-payment-method-by-id.controller.ts new file mode 100644 index 00000000..b7ecb04c --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/disable-payment-method-by-id.controller.ts @@ -0,0 +1,19 @@ +import { ExpressController } from "@erp/core/api"; + +import type { DisablePaymentMethodByIdUseCase } from "../../../../application"; + +export class DisablePaymentMethodByIdController extends ExpressController { + constructor(private readonly useCase: DisablePaymentMethodByIdUseCase) { + super(); + } + + protected async executeImpl() { + const id = this.req.params.payment_method_id; + const result = await this.useCase.execute({ id }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/get-payment-method-by-id.controller.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/get-payment-method-by-id.controller.ts new file mode 100644 index 00000000..a921b5d2 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/get-payment-method-by-id.controller.ts @@ -0,0 +1,39 @@ +import { + ExpressController, + forbidQueryFieldGuard, + requireAuthenticatedGuard, + requireCompanyContextGuard, +} from "@erp/core/api"; + +import type { GetPaymentMethodByIdUseCase } from "../../../../application"; +import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper"; + +export class GetPaymentMethodByIdController extends ExpressController { + constructor(private readonly useCase: GetPaymentMethodByIdUseCase) { + super(); + + this.errorMapper = paymentmethodsApiErrorMapper; + + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query + this.registerGuards( + requireAuthenticatedGuard(), + requireCompanyContextGuard(), + forbidQueryFieldGuard("companyId") + ); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) { + return this.forbiddenError("Tenant ID not found"); + } + + const { payment_method_id } = this.req.params; + const result = await this.useCase.execute({ payment_method_id, companyId }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/index.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/index.ts new file mode 100644 index 00000000..542c3595 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/index.ts @@ -0,0 +1,5 @@ +export * from "./create-payment-method.controller"; +export * from "./disable-payment-method-by-id.controller"; +export * from "./get-payment-method-by-id.controller"; +export * from "./list-payment-methods.controller"; +export * from "./update-payment-method-by-id.controller"; diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/list-payment-methods.controller.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/list-payment-methods.controller.ts new file mode 100644 index 00000000..a66f514a --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/list-payment-methods.controller.ts @@ -0,0 +1,54 @@ +import { + ExpressController, + forbidQueryFieldGuard, + requireAuthenticatedGuard, + requireCompanyContextGuard, +} from "@erp/core/api"; +import { Criteria } from "@repo/rdx-criteria/server"; + +import type { ListPaymentMethodsUseCase } from "../../../../application"; +import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper"; + +export class ListPaymentMethodsController extends ExpressController { + constructor(private readonly useCase: ListPaymentMethodsUseCase) { + super(); + this.errorMapper = paymentmethodsApiErrorMapper; + + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query + this.registerGuards( + requireAuthenticatedGuard(), + requireCompanyContextGuard(), + forbidQueryFieldGuard("companyId") + ); + } + + private getCriteriaWithDefaultOrder() { + if (this.criteria.hasOrder()) { + return this.criteria; + } + + const { q: quicksearch, filters, pageSize, pageNumber } = this.criteria.toPrimitives(); + return Criteria.fromPrimitives(filters, "name", "ASC", pageSize, pageNumber, quicksearch); + } + + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) { + return this.forbiddenError("Tenant ID not found"); + } + + const criteria = this.getCriteriaWithDefaultOrder(); + const result = await this.useCase.execute({ criteria, companyId }); + + return result.match( + (data) => + this.ok(data, { + "X-Total-Count": String(data.total_items), + "Pagination-Count": String(data.total_pages), + "Pagination-Page": String(data.page), + "Pagination-Limit": String(data.per_page), + }), + (err) => this.handleError(err) + ); + } +} diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/update-payment-method-by-id.controller.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/update-payment-method-by-id.controller.ts new file mode 100644 index 00000000..3f14c95b --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/controllers/update-payment-method-by-id.controller.ts @@ -0,0 +1,45 @@ +import type { UpdatePaymentMethodByIdRequestDTO } from "@erp/catalogs/common"; +import { + ExpressController, + forbidQueryFieldGuard, + requireAuthenticatedGuard, + requireCompanyContextGuard, +} from "@erp/core/api"; + +import type { UpdatePaymentMethodByIdUseCase } from "../../../../application"; +import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper"; + +export class UpdatePaymentMethodByIdController extends ExpressController { + constructor(private readonly useCase: UpdatePaymentMethodByIdUseCase) { + super(); + + this.errorMapper = paymentmethodsApiErrorMapper; + + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query + this.registerGuards( + requireAuthenticatedGuard(), + requireCompanyContextGuard(), + forbidQueryFieldGuard("companyId") + ); + } + protected async executeImpl() { + const companyId = this.getTenantId(); + if (!companyId) { + return this.forbiddenError("Tenant ID not found"); + } + + const { payment_method_id } = this.req.params; + if (!payment_method_id) { + return this.invalidInputError("Payment method ID missing"); + } + + const dto = this.req.body as UpdatePaymentMethodByIdRequestDTO; + + const result = await this.useCase.execute({ payment_method_id, companyId, dto }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/payment-methods-api-error-mapper.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/payment-methods-api-error-mapper.ts new file mode 100644 index 00000000..287b9629 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/payment-methods-api-error-mapper.ts @@ -0,0 +1,38 @@ +import { + ApiErrorMapper, + ConflictApiError, + type ErrorToApiRule, + ValidationApiError, +} from "@erp/core/api"; + +import { + type InvalidPaymentMethodIdError, + type PaymentMethodCannotBeDeletedError, + isInvalidPaymentMethodIdError, + isPaymentMethodCannotBeDeletedError, +} from "../../../domain/payment-methods"; + +// Crea una regla específica (prioridad alta para sobreescribir mensajes) +const paymentmethodDuplicateRule: ErrorToApiRule = { + priority: 120, + matches: (e) => isInvalidPaymentMethodIdError(e), + build: (e) => + new ConflictApiError( + (e as InvalidPaymentMethodIdError).message || + "Payment method with the provided id already exists." + ), +}; + +const paymentmethodCannotBeDeletedRule: ErrorToApiRule = { + priority: 120, + matches: (e) => isPaymentMethodCannotBeDeletedError(e), + build: (e) => + new ValidationApiError( + (e as PaymentMethodCannotBeDeletedError).message || "Payment method cannot be deleted." + ), +}; + +// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra +export const paymentmethodsApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default() + .register(paymentmethodDuplicateRule) + .register(paymentmethodCannotBeDeletedRule); diff --git a/modules/catalogs/src/api/infrastructure/express/payment-methods/payment-methods.routes.ts b/modules/catalogs/src/api/infrastructure/express/payment-methods/payment-methods.routes.ts new file mode 100644 index 00000000..8d19d894 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/express/payment-methods/payment-methods.routes.ts @@ -0,0 +1,91 @@ +import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api"; +import { type RequestWithAuth, type StartParams, validateRequest } from "@erp/core/api"; +import { type NextFunction, type Request, type Response, Router } from "express"; + +import { + CreatePaymentMethodRequestSchema, + GetPaymentMethodByIdRequestSchema, + ListPaymentMethodsRequestSchema, + UpdatePaymentMethodByIdParamsRequestSchema, + UpdatePaymentMethodByIdRequestSchema, +} from "../../../../common/dto/payment-methods/request"; +import type { CatalogsInternalDeps } from "../../di/catalogs.di"; + +import { + CreatePaymentMethodController, + DisablePaymentMethodByIdController, + GetPaymentMethodByIdController, + ListPaymentMethodsController, + UpdatePaymentMethodByIdController, +} from "./controllers"; + +export const paymentMethodsRouter = (params: StartParams) => { + const { app, config, getInternal } = params; + const deps = getInternal("catalogs").paymentMethods; + + const router = Router({ mergeParams: true }); + + // ---------------------------------------------- + + if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") { + // 🔐 Autenticación + Tenancy para TODO el router + router.use( + (req: Request, res: Response, next: NextFunction) => + mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas + ); + } + + router.use([ + (req: Request, res: Response, next: NextFunction) => + requireAuthenticated()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas + + (req: Request, res: Response, next: NextFunction) => + requireCompanyContext()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas + ]); + + // ---------------------------------------------- + + router.get("/", validateRequest(ListPaymentMethodsRequestSchema, "query"), (req, res, next) => { + const controller = new ListPaymentMethodsController(deps.useCases.listPaymentMethods()); + return controller.execute(req, res, next); + }); + + router.post("/", validateRequest(CreatePaymentMethodRequestSchema, "body"), (req, res, next) => { + const controller = new CreatePaymentMethodController(deps.useCases.createPaymentMethod()); + return controller.execute(req, res, next); + }); + + router.get( + "/:payment_method_id", + validateRequest(GetPaymentMethodByIdRequestSchema, "params"), + (req, res, next) => { + const controller = new GetPaymentMethodByIdController(deps.useCases.getPaymentMethodById()); + return controller.execute(req, res, next); + } + ); + + router.patch( + "/:payment_method_id", + validateRequest(UpdatePaymentMethodByIdParamsRequestSchema, "params"), + validateRequest(UpdatePaymentMethodByIdRequestSchema, "body"), + (req, res, next) => { + const controller = new UpdatePaymentMethodByIdController( + deps.useCases.updatePaymentMethodById() + ); + return controller.execute(req, res, next); + } + ); + + router.patch( + "/:payment_method_id/disable", + validateRequest(GetPaymentMethodByIdRequestSchema, "params"), + (req, res, next) => { + const controller = new DisablePaymentMethodByIdController( + deps.useCases.disablePaymentMethodById() + ); + return controller.execute(req, res, next); + } + ); + + app.use(`${config.server.apiBasePath}/catalogs/payment-methods`, router); +}; diff --git a/modules/catalogs/src/api/infrastructure/index.ts b/modules/catalogs/src/api/infrastructure/index.ts new file mode 100644 index 00000000..4263e0ee --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/index.ts @@ -0,0 +1,2 @@ +export * from "./express"; +export * from "./persistence"; diff --git a/modules/catalogs/src/api/infrastructure/persistence/index.ts b/modules/catalogs/src/api/infrastructure/persistence/index.ts new file mode 100644 index 00000000..62f8ac11 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/index.ts @@ -0,0 +1 @@ +export * from "./sequelize"; diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/index.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/index.ts new file mode 100644 index 00000000..e0e3ca2e --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/index.ts @@ -0,0 +1,7 @@ +export * from "./mappers"; +export * from "./models"; +export * from "./repositories"; + +import paymentMethodModelInit from "./models/sequelize-payment-method.model"; + +export const models = [paymentMethodModelInit]; diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/index.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/index.ts new file mode 100644 index 00000000..0abb77d5 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/index.ts @@ -0,0 +1,2 @@ +export * from "./sequelize-payment-method-domain.mapper"; +export * from "./sequelize-payment-method-summary.mapper"; diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/sequelize-payment-method-domain.mapper.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/sequelize-payment-method-domain.mapper.ts new file mode 100644 index 00000000..9d044a93 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/sequelize-payment-method-domain.mapper.ts @@ -0,0 +1,85 @@ +import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api"; +import { + Name, + TextValue, + UniqueID, + ValidationErrorCollection, + type ValidationErrorDetail, + extractOrPushError, + maybeFromNullableResult, + maybeToNullable, +} from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import { PaymentMethod } from "../../../../domain"; +import type { PaymentMethodCreationAttributes, PaymentMethodModel } from "../models"; + +export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper< + PaymentMethodModel, + PaymentMethodCreationAttributes, + PaymentMethod +> { + public mapToDomain( + source: PaymentMethodModel, + params?: MapperParamsType + ): Result { + try { + const errors: ValidationErrorDetail[] = []; + + const idResult = UniqueID.create(source.id, true); + if (idResult.isFailure) { + return Result.fail(idResult.error); + } + + const companyId = extractOrPushError( + UniqueID.create(source.company_id), + "company_id", + errors + ); + + const name = extractOrPushError(Name.create(source.name), "name", errors); + + const description = extractOrPushError( + maybeFromNullableResult(source.description, (value) => TextValue.create(value)), + "description", + errors + ); + + // Si hubo errores de mapeo, devolvemos colección de validación + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Payment method props mapping failed", errors) + ); + } + + const paymentMethod = PaymentMethod.rehydrate( + { + companyId: companyId!, + name: name!, + description: description!, + isActive: source.is_active, + isSystem: source.is_system, + }, + idResult.data + ); + + return Result.ok(paymentMethod); + } catch (error: unknown) { + return Result.fail(error as Error); + } + } + + public mapToPersistence( + source: PaymentMethod, + params?: MapperParamsType + ): Result { + return Result.ok({ + id: source.id.toPrimitive(), + company_id: source.companyId.toPrimitive(), + name: source.name.toPrimitive(), + description: maybeToNullable(source.description, (description) => description.toPrimitive()), + is_active: source.isActive, + is_system: source.isSystem, + }); + } +} diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/sequelize-payment-method-summary.mapper.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/sequelize-payment-method-summary.mapper.ts new file mode 100644 index 00000000..78e62b1d --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/mappers/sequelize-payment-method-summary.mapper.ts @@ -0,0 +1,46 @@ +import { type MapperParamsType, SequelizeQueryMapper } from "@erp/core/api"; +import { + Name, + UniqueID, + ValidationErrorCollection, + type ValidationErrorDetail, + extractOrPushError, +} from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import type { PaymentMethodSummary } from "../../../../application/models"; +import type { PaymentMethodModel } from "../models"; + +export class SequelizePaymentMethodSummaryMapper extends SequelizeQueryMapper< + PaymentMethodModel, + PaymentMethodSummary +> { + public mapToReadModel( + raw: PaymentMethodModel, + params?: MapperParamsType + ): Result { + const errors: ValidationErrorDetail[] = []; + + // 1) Valores escalares (atributos generales) + const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors); + const id = extractOrPushError(UniqueID.create(raw.id), "id", errors); + const name = extractOrPushError(Name.create(raw.name), "name", errors); + const isActive = raw.is_active; + const isSystem = raw.is_system; + + // Si hubo errores de mapeo, devolvemos colección de validación + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("PaymentMethod mapping failed [mapToDTO]", errors) + ); + } + + return Result.ok({ + id: id!, + companyId: companyId!, + name: name!, + isActive: isActive, + isSystem: isSystem, + }); + } +} diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/models/index.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/models/index.ts new file mode 100644 index 00000000..5d4adfc8 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/models/index.ts @@ -0,0 +1 @@ +export * from "./sequelize-payment-method.model"; diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/models/sequelize-payment-method.model.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/models/sequelize-payment-method.model.ts new file mode 100644 index 00000000..e8099e95 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/models/sequelize-payment-method.model.ts @@ -0,0 +1,87 @@ +import { + type CreationOptional, + DataTypes, + type InferAttributes, + type InferCreationAttributes, + Model, + type Sequelize, +} from "sequelize"; + +export type PaymentMethodCreationAttributes = InferCreationAttributes & {}; + +export class PaymentMethodModel extends Model< + InferAttributes, + InferCreationAttributes +> { + declare id: string; + declare company_id: string; + declare name: string; + declare description: CreationOptional; + declare is_active: boolean; + declare is_system: boolean; + + static associate(_database: Sequelize) {} + + static hooks(_database: Sequelize) {} +} + +export default (database: Sequelize) => { + PaymentMethodModel.init( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + }, + company_id: { + type: DataTypes.UUID, + allowNull: false, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.STRING, + allowNull: true, + }, + is_active: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + is_system: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + }, + { + sequelize: database, + modelName: "PaymentMethodModel", + tableName: "payment_methods", + + underscored: true, + paranoid: true, // softs deletes + timestamps: true, + + createdAt: "created_at", + updatedAt: "updated_at", + deletedAt: "deleted_at", + + indexes: [ + { + name: "idx_payment_methods_company", + fields: ["company_id", "deleted_at", "name"], + }, + ], + + whereMergeStrategy: "and", // <- cómo tratar el merge de un scope + + defaultScope: {}, + + scopes: {}, + } + ); + + return PaymentMethodModel; +}; diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/repositories/index.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/repositories/index.ts new file mode 100644 index 00000000..c360ac48 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/repositories/index.ts @@ -0,0 +1 @@ +export * from "./sequelize-payment-method.repository"; diff --git a/modules/catalogs/src/api/infrastructure/persistence/sequelize/repositories/sequelize-payment-method.repository.ts b/modules/catalogs/src/api/infrastructure/persistence/sequelize/repositories/sequelize-payment-method.repository.ts new file mode 100644 index 00000000..74e27843 --- /dev/null +++ b/modules/catalogs/src/api/infrastructure/persistence/sequelize/repositories/sequelize-payment-method.repository.ts @@ -0,0 +1,226 @@ +import { + EntityNotFoundError, + InfrastructureRepositoryError, + SequelizeRepository, + translateSequelizeError, +} from "@erp/core/api"; +import { type Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server"; +import type { UniqueID } from "@repo/rdx-ddd"; +import { type Collection, Result } from "@repo/rdx-utils"; +import type { Sequelize, Transaction } from "sequelize"; + +import type { IPaymentMethodRepository } from "../../../../application"; +import type { PaymentMethodSummary } from "../../../../application/models"; +import type { PaymentMethod } from "../../../../domain"; +import type { SequelizePaymentMethodSummaryMapper } from "../mappers"; +import type { SequelizePaymentMethodDomainMapper } from "../mappers/sequelize-payment-method-domain.mapper"; +import { PaymentMethodModel } from "../models"; + +export class SequelizePaymentMethodRepository + extends SequelizeRepository + implements IPaymentMethodRepository +{ + constructor( + private readonly domainMapper: SequelizePaymentMethodDomainMapper, + private readonly summaryMapper: SequelizePaymentMethodSummaryMapper, + database: Sequelize + ) { + super({ database }); + } + + /** + * + * Crea un nuevo método de pago + * + * @param paymentMethod - El método de pago nuevo a guardar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + async create( + paymentMethod: PaymentMethod, + transaction?: Transaction + ): Promise> { + try { + const dtoResult = this.domainMapper.mapToPersistence(paymentMethod); + if (dtoResult.isFailure) { + return Result.fail(dtoResult.error); + } + + await PaymentMethodModel.create(dtoResult.data, { transaction }); + return Result.ok(); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } + + /** + * Actualiza un método de pago existente. + * + * @param paymentMethod - El método de pago a actualizar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + async update( + paymentMethod: PaymentMethod, + transaction?: Transaction + ): Promise> { + try { + const dtoResult = this.domainMapper.mapToPersistence(paymentMethod); + if (dtoResult.isFailure) { + return Result.fail(dtoResult.error); + } + + const { id, ...payload } = dtoResult.data; + const [affected] = await PaymentMethodModel.update(payload, { + where: { id }, + transaction, + individualHooks: true, + }); + + if (affected === 0) { + return Result.fail( + new InfrastructureRepositoryError("Concurrency conflict or payment method not found") + ); + } + + return Result.ok(); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } + + /** + * Comprueba si existe un PaymentMethod con un `id` dentro de una `company`. + * + * @param companyId - Identificador UUID de la empresa a la que pertenece el método de pago. + * @param id - Identificador UUID del método de pago. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + async existsByIdInCompany( + companyId: UniqueID, + id: UniqueID, + transaction?: Transaction + ): Promise> { + try { + const count = await PaymentMethodModel.count({ + where: { id: id.toString(), company_id: companyId.toString() }, + transaction, + }); + return Result.ok(Boolean(count > 0)); + } catch (error: unknown) { + return Result.fail(translateSequelizeError(error)); + } + } + + /** + * Recupera un método de pago por su ID y companyId. + * + * @param companyId - Identificador UUID de la empresa a la que pertenece el método de pago. + * @param id - Identificador UUID del método de pago. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + + async getByIdInCompany( + companyId: UniqueID, + id: UniqueID, + transaction?: Transaction + ): Promise> { + try { + const row = await PaymentMethodModel.findOne({ + where: { + id: id.toString(), + company_id: companyId.toString(), + }, + transaction, + }); + if (!row) { + return Result.fail(new EntityNotFoundError("PaymentMethod", "id", id.toString())); + } + + return this.domainMapper.mapToDomain(row); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } + + /** + * Recupera múltiples customers dentro de una empresa según un criterio dinámico (búsqueda, paginación, etc.). + * + * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. + * @param criteria - Criterios de búsqueda. + * @param transaction - Transacción activa para la operación. + * @returns Result, Error> + * + * @see Criteria + */ + async findByCriteriaInCompany( + companyId: UniqueID, + criteria: Criteria, + transaction?: Transaction + ): Promise, Error>> { + try { + const criteriaConverter = new CriteriaToSequelizeConverter(); + const query = criteriaConverter.convert(criteria, { + searchableFields: [], + sortableFields: ["name"], + enableFullText: true, + database: this.database, + strictMode: true, // fuerza error si ORDER BY no permitido + }); + + query.where = { + ...query.where, + company_id: companyId.toString(), + deleted_at: null, + }; + + const [rows, count] = await Promise.all([ + PaymentMethodModel.findAll({ + ...query, + transaction, + }), + PaymentMethodModel.count({ + where: query.where, + distinct: true, // evita duplicados por LEFT JOIN + transaction, + }), + ]); + + return this.summaryMapper.mapToReadModelCollection(rows, count); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } + + /** + * + * Elimina o marca como eliminado una forma de pago. + * + * @param companyId - Identificador UUID de la empresa a la que pertenece la forma de pago. + * @param id - UUID de la forma de pago a eliminar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + async deleteByIdInCompany( + companyId: UniqueID, + id: UniqueID, + transaction: Transaction + ): Promise> { + try { + const deleted = await PaymentMethodModel.destroy({ + where: { id: id.toString(), company_id: companyId.toString() }, + transaction, + }); + + if (deleted === 0) { + return Result.fail(new EntityNotFoundError("PaymentMethod", "id", id.toString())); + } + + return Result.ok(true); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } +} diff --git a/modules/catalogs/src/common/dto/index.ts b/modules/catalogs/src/common/dto/index.ts new file mode 100644 index 00000000..cd29a5d4 --- /dev/null +++ b/modules/catalogs/src/common/dto/index.ts @@ -0,0 +1 @@ +export * from "./payment-methods"; diff --git a/modules/catalogs/src/common/dto/payment-methods/index.ts b/modules/catalogs/src/common/dto/payment-methods/index.ts new file mode 100644 index 00000000..346dac3b --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/index.ts @@ -0,0 +1,2 @@ +export * from "./request"; +export * from "./response"; diff --git a/modules/catalogs/src/common/dto/payment-methods/request/create-payment-method.request.dto.ts b/modules/catalogs/src/common/dto/payment-methods/request/create-payment-method.request.dto.ts new file mode 100644 index 00000000..e59c6bbb --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/request/create-payment-method.request.dto.ts @@ -0,0 +1,12 @@ +import { z } from "zod/v4"; + +export const CreatePaymentMethodRequestSchema = z.object({ + id: z.uuid(), + + name: z.string(), + description: z.string().nullable().optional(), + + is_active: z.boolean(), +}); + +export type CreatePaymentMethodRequestDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/request/get-payment-method-by-id.request.dto.ts b/modules/catalogs/src/common/dto/payment-methods/request/get-payment-method-by-id.request.dto.ts new file mode 100644 index 00000000..a415f3d3 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/request/get-payment-method-by-id.request.dto.ts @@ -0,0 +1,7 @@ +import { z } from "zod/v4"; + +export const GetPaymentMethodByIdRequestSchema = z.object({ + payment_method_id: z.uuid(), +}); + +export type GetPaymentMethodByIdRequestDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/request/index.ts b/modules/catalogs/src/common/dto/payment-methods/request/index.ts new file mode 100644 index 00000000..a73f5ad7 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/request/index.ts @@ -0,0 +1,4 @@ +export * from "./create-payment-method.request.dto"; +export * from "./get-payment-method-by-id.request.dto"; +export * from "./list-payment-methods.request.dto"; +export * from "./update-payment-method-by-id.request.dto"; diff --git a/modules/catalogs/src/common/dto/payment-methods/request/list-payment-methods.request.dto.ts b/modules/catalogs/src/common/dto/payment-methods/request/list-payment-methods.request.dto.ts new file mode 100644 index 00000000..e996b7a0 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/request/list-payment-methods.request.dto.ts @@ -0,0 +1,5 @@ +import { CriteriaSchema } from "@erp/core"; +import type { z } from "zod/v4"; + +export const ListPaymentMethodsRequestSchema = CriteriaSchema; +export type ListPaymentMethodsRequestDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/request/update-payment-method-by-id.request.dto.ts b/modules/catalogs/src/common/dto/payment-methods/request/update-payment-method-by-id.request.dto.ts new file mode 100644 index 00000000..b73c99ea --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/request/update-payment-method-by-id.request.dto.ts @@ -0,0 +1,18 @@ +import { z } from "zod/v4"; + +export const UpdatePaymentMethodByIdParamsRequestSchema = z.object({ + payment_method_id: z.uuid(), +}); + +export const UpdatePaymentMethodByIdRequestSchema = z.object({ + name: z.string().optional(), + description: z.string().nullable().optional(), + is_active: z.boolean().optional(), +}); + +export type UpdatePaymentMethodByIdParamsRequestDTO = z.infer< + typeof UpdatePaymentMethodByIdParamsRequestSchema +>; +export type UpdatePaymentMethodByIdRequestDTO = z.infer< + typeof UpdatePaymentMethodByIdRequestSchema +>; diff --git a/modules/catalogs/src/common/dto/payment-methods/response/create-payment-method.response.dto.ts b/modules/catalogs/src/common/dto/payment-methods/response/create-payment-method.response.dto.ts new file mode 100644 index 00000000..78542bea --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/response/create-payment-method.response.dto.ts @@ -0,0 +1,6 @@ +import type { z } from "zod/v4"; + +import { PaymentMethodDetailSchema } from "../shared"; + +export const CreatePaymentMethodResponseSchema = PaymentMethodDetailSchema; +export type CreatePaymentMethodResponseDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/response/disable-payment-method-by-id.response.dto.ts b/modules/catalogs/src/common/dto/payment-methods/response/disable-payment-method-by-id.response.dto.ts new file mode 100644 index 00000000..0512def0 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/response/disable-payment-method-by-id.response.dto.ts @@ -0,0 +1,8 @@ +import type { z } from "zod/v4"; + +import { PaymentMethodDetailSchema } from "../shared"; + +export const DisablePaymentMethodByIdResponseSchema = PaymentMethodDetailSchema; +export type DisablePaymentMethodByIdResponseDTO = z.infer< + typeof DisablePaymentMethodByIdResponseSchema +>; diff --git a/modules/catalogs/src/common/dto/payment-methods/response/get-payment-method-by-id.response.dto.ts b/modules/catalogs/src/common/dto/payment-methods/response/get-payment-method-by-id.response.dto.ts new file mode 100644 index 00000000..84d6be9e --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/response/get-payment-method-by-id.response.dto.ts @@ -0,0 +1,6 @@ +import type { z } from "zod/v4"; + +import { PaymentMethodDetailSchema } from "../shared"; + +export const GetPaymentMethodByIdResponseSchema = PaymentMethodDetailSchema; +export type GetPaymentMethodByIdResponseDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/response/index.ts b/modules/catalogs/src/common/dto/payment-methods/response/index.ts new file mode 100644 index 00000000..82bc3306 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/response/index.ts @@ -0,0 +1,5 @@ +export * from "./create-payment-method.response.dto"; +export * from "./disable-payment-method-by-id.response.dto"; +export * from "./get-payment-method-by-id.response.dto"; +export * from "./list-payment-methods.response.dto"; +export * from "./update-payment-method-by-id.response.dto"; diff --git a/modules/catalogs/src/common/dto/payment-methods/response/list-payment-methods.response.dto.ts b/modules/catalogs/src/common/dto/payment-methods/response/list-payment-methods.response.dto.ts new file mode 100644 index 00000000..9c4b1187 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/response/list-payment-methods.response.dto.ts @@ -0,0 +1,6 @@ +import { z } from "zod/v4"; + +import { PaymentMethodSummarySchema } from "../shared"; + +export const ListPaymentMethodsResponseSchema = z.array(PaymentMethodSummarySchema); +export type ListPaymentMethodsResponseDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/response/update-payment-method-by-id.response.dto.ts b/modules/catalogs/src/common/dto/payment-methods/response/update-payment-method-by-id.response.dto.ts new file mode 100644 index 00000000..327cc6ff --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/response/update-payment-method-by-id.response.dto.ts @@ -0,0 +1,8 @@ +import type { z } from "zod/v4"; + +import { PaymentMethodDetailSchema } from "../shared"; + +export const UpdatePaymentMethodByIdResponseSchema = PaymentMethodDetailSchema; +export type UpdatePaymentMethodByIdResponseDTO = z.infer< + typeof UpdatePaymentMethodByIdResponseSchema +>; diff --git a/modules/catalogs/src/common/dto/payment-methods/shared/index.ts b/modules/catalogs/src/common/dto/payment-methods/shared/index.ts new file mode 100644 index 00000000..e1ee5aec --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/shared/index.ts @@ -0,0 +1,2 @@ +export * from "./payment-method-detail.dto"; +export * from "./payment-method-summary.dto"; diff --git a/modules/catalogs/src/common/dto/payment-methods/shared/payment-method-detail.dto.ts b/modules/catalogs/src/common/dto/payment-methods/shared/payment-method-detail.dto.ts new file mode 100644 index 00000000..f73cb408 --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/shared/payment-method-detail.dto.ts @@ -0,0 +1,13 @@ +import { z } from "zod/v4"; + +export const PaymentMethodDetailSchema = z.object({ + id: z.uuid(), + company_id: z.uuid(), + + name: z.string(), + description: z.string().nullable(), + is_active: z.boolean(), + is_system: z.boolean(), +}); + +export type PaymentMethodDetailDTO = z.infer; diff --git a/modules/catalogs/src/common/dto/payment-methods/shared/payment-method-summary.dto.ts b/modules/catalogs/src/common/dto/payment-methods/shared/payment-method-summary.dto.ts new file mode 100644 index 00000000..179a41bf --- /dev/null +++ b/modules/catalogs/src/common/dto/payment-methods/shared/payment-method-summary.dto.ts @@ -0,0 +1,12 @@ +import { z } from "zod/v4"; + +export const PaymentMethodSummarySchema = z.object({ + id: z.uuid(), + company_id: z.uuid(), + + name: z.string(), + is_active: z.boolean(), + is_system: z.boolean(), +}); + +export type PaymentMethodSummaryDTO = z.infer; diff --git a/modules/catalogs/src/common/index.ts b/modules/catalogs/src/common/index.ts new file mode 100644 index 00000000..0392b1b4 --- /dev/null +++ b/modules/catalogs/src/common/index.ts @@ -0,0 +1 @@ +export * from "./dto"; diff --git a/modules/catalogs/tsconfig.json b/modules/catalogs/tsconfig.json new file mode 100644 index 00000000..5a69e561 --- /dev/null +++ b/modules/catalogs/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "paths": { + "@erp/catalogs/*": ["./src/*"] + }, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts b/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts index 19f92eae..b583071d 100644 --- a/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts +++ b/modules/customer-invoices/src/api/application/proformas/di/proforma-use-cases.di.ts @@ -21,7 +21,7 @@ import { ListProformasUseCase, ReportProformaUseCase, } from "../use-cases"; -import { UpdateProformaUseCase } from "../use-cases/update-proforma.use-case"; +import { UpdateProformaByIdUseCase } from "../use-cases/update-proforma-by-id.use-case"; export function buildGetProformaByIdUseCase(deps: { finder: IProformaFinder; @@ -101,7 +101,7 @@ export function buildUpdateProformaUseCase(deps: { fullSnapshotBuilder: IProformaFullSnapshotBuilder; transactionManager: ITransactionManager; }) { - return new UpdateProformaUseCase({ + return new UpdateProformaByIdUseCase({ dtoMapper: deps.dtoMapper, updater: deps.updater, fullSnapshotBuilder: deps.fullSnapshotBuilder, diff --git a/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts b/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts index 71215eb1..98a50f10 100644 --- a/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts +++ b/modules/customer-invoices/src/api/application/proformas/use-cases/index.ts @@ -5,4 +5,4 @@ export * from "./get-proforma-by-id.use-case"; export * from "./issue-proforma.use-case"; export * from "./list-proformas.use-case"; export * from "./report-proforma.use-case"; -export * from "./update-proforma.use-case"; +export * from "./update-proforma-by-id.use-case"; diff --git a/modules/customer-invoices/src/api/application/proformas/use-cases/update-proforma.use-case.ts b/modules/customer-invoices/src/api/application/proformas/use-cases/update-proforma-by-id.use-case.ts similarity index 98% rename from modules/customer-invoices/src/api/application/proformas/use-cases/update-proforma.use-case.ts rename to modules/customer-invoices/src/api/application/proformas/use-cases/update-proforma-by-id.use-case.ts index 5b2f5b3b..c11eec39 100644 --- a/modules/customer-invoices/src/api/application/proformas/use-cases/update-proforma.use-case.ts +++ b/modules/customer-invoices/src/api/application/proformas/use-cases/update-proforma-by-id.use-case.ts @@ -21,7 +21,7 @@ type UpdateProformaUseCaseDeps = { transactionManager: ITransactionManager; }; -export class UpdateProformaUseCase { +export class UpdateProformaByIdUseCase { private readonly dtoMapper: IUpdateProformaInputMapper; private readonly updater: IProformaUpdater; private readonly fullSnapshotBuilder: IProformaFullSnapshotBuilder; diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts index 7c421da5..b505f3a8 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts @@ -7,7 +7,7 @@ import { type IssueProformaUseCase, type ListProformasUseCase, type ReportProformaUseCase, - type UpdateProformaUseCase, + type UpdateProformaByIdUseCase, buildCreateProformaUseCase, buildGetProformaByIdUseCase, buildIssueProformaUseCase, @@ -37,7 +37,7 @@ export type ProformasInternalDeps = { issueProforma: (publicServices: { issuedInvoiceServices: IIssuedInvoicePublicServices; }) => IssueProformaUseCase; - updateProforma: () => UpdateProformaUseCase; + updateProforma: () => UpdateProformaByIdUseCase; /* deleteProforma: () => DeleteProformaUseCase; diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts index af5940a2..c5e10c8f 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/create-proforma.controller.ts @@ -27,7 +27,7 @@ export class CreateProformaController extends ExpressController { if (!companyId) { return this.forbiddenError("Tenant ID not found"); } - const dto = this.req.body as CreateProformaRequestDTO; + const dto = this.req.body satisfies CreateProformaRequestDTO; const result = await this.useCase.execute({ dto, companyId }); diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts index 98f79577..570339f9 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/express/controllers/update-proforma.controller.ts @@ -6,11 +6,11 @@ import { } from "@erp/core/api"; import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto/index.ts"; -import type { UpdateProformaUseCase } from "../../../../application/index.ts"; +import type { UpdateProformaByIdUseCase } from "../../../../application/index.ts"; import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts"; export class UpdateProformaController extends ExpressController { - public constructor(private readonly useCase: UpdateProformaUseCase) { + public constructor(private readonly useCase: UpdateProformaByIdUseCase) { super(); this.errorMapper = proformasApiErrorMapper; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cb41c7b..18c8c1b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@erp/auth': specifier: workspace:* version: link:../../modules/auth + '@erp/catalogs': + specifier: workspace:* + version: link:../../modules/catalogs '@erp/core': specifier: workspace:* version: link:../../modules/core @@ -375,6 +378,40 @@ importers: specifier: ^6.0.2 version: 6.0.2 + modules/catalogs: + dependencies: + '@erp/auth': + specifier: workspace:* + version: link:../auth + '@erp/core': + specifier: workspace:* + version: link:../core + '@repo/rdx-criteria': + specifier: workspace:* + version: link:../../packages/rdx-criteria + '@repo/rdx-ddd': + specifier: workspace:* + version: link:../../packages/rdx-ddd + '@repo/rdx-utils': + specifier: workspace:* + version: link:../../packages/rdx-utils + express: + specifier: ^4.22.1 + version: 4.22.1 + sequelize: + specifier: ^6.37.8 + version: 6.37.8(mysql2@3.22.0(@types/node@25.6.0))(pg-hstore@2.3.4) + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.25 + typescript: + specifier: ^6.0.2 + version: 6.0.2 + modules/core: dependencies: '@hookform/resolvers': diff --git a/uecko-erp.code-workspace b/uecko-erp.code-workspace index fb529bd6..465135b0 100644 --- a/uecko-erp.code-workspace +++ b/uecko-erp.code-workspace @@ -7,7 +7,11 @@ "settings": { "chatgpt.openOnStartup": true, "chat.tools.terminal.autoApprove": { - "pnpm": true + "pnpm": true, + "/^cd /home/rodax/Documentos/uecko-erp && ls -l node_modules/@repo/typescript-config/root\\.json && node -p \"require\\.resolve\\('@repo/typescript-config/root\\.json'\\)\"$/": { + "approve": true, + "matchCommandLine": true + } } } }