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