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