Facturas de cliente

This commit is contained in:
David Arranz 2025-06-26 20:25:38 +02:00
parent b87082754b
commit 91c00e7344
19 changed files with 122 additions and 55 deletions

View File

@ -0,0 +1,6 @@
export class EntityNotFoundError extends Error {
constructor(entity: string, id: string | number) {
super(`Entity '${entity}' with ID '${id}' was not found.`);
this.name = "EntityNotFoundError";
}
}

View File

@ -10,6 +10,7 @@ import { ApiError } from "./api-error";
import { ConflictApiError } from "./conflict-api-error";
import { DomainValidationError } from "./domain-validation-error";
import { DuplicateEntityError } from "./duplicate-entity-error";
import { EntityNotFoundError } from "./entity-not-found-error";
import { ForbiddenApiError } from "./forbidden-api-error";
import { InternalApiError } from "./internal-api-error";
import { NotFoundApiError } from "./not-found-api-error";
@ -79,6 +80,10 @@ export const errorMapper = {
return new ConflictApiError(error.message);
}
if (error instanceof EntityNotFoundError) {
return new NotFoundApiError(error.message);
}
// 3. 🔍 Errores individuales de validación
if (
message.includes("invalid") ||

View File

@ -2,6 +2,7 @@ export * from "./api-error";
export * from "./conflict-api-error";
export * from "./domain-validation-error";
export * from "./duplicate-entity-error";
export * from "./entity-not-found-error";
export * from "./error-mapper";
export * from "./forbidden-api-error";
export * from "./internal-api-error";

View File

@ -1,24 +1,38 @@
import { UniqueID } from "@/core/common/domain";
import { ITransactionManager } from "@/core/common/infrastructure/database";
import { logger } from "@/core/logger";
import { EntityNotFoundError, ITransactionManager } from "@erp/core/api";
import { DeleteCustomerInvoiceByIdQueryDTO } from "@erp/customer-invoices/common/dto";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { ICustomerInvoiceService } from "../domain";
import { ICustomerInvoiceService } from "../../domain";
export class DeleteCustomerInvoiceUseCase {
constructor(
private readonly customerInvoiceService: ICustomerInvoiceService,
private readonly service: ICustomerInvoiceService,
private readonly transactionManager: ITransactionManager
) {}
public execute(customerInvoiceID: UniqueID): Promise<Result<boolean, Error>> {
public execute(dto: DeleteCustomerInvoiceByIdQueryDTO) {
const idOrError = UniqueID.create(dto.id);
if (idOrError.isFailure) {
return Result.fail(idOrError.error);
}
const id = idOrError.data;
return this.transactionManager.complete(async (transaction) => {
try {
return await this.customerInvoiceService.deleteCustomerInvoiceById(
customerInvoiceID,
transaction
);
const existsCheck = await this.service.existsById(id, transaction);
if (existsCheck.isFailure) {
return Result.fail(existsCheck.error);
}
if (!existsCheck.data) {
return Result.fail(new EntityNotFoundError("CustomerInvoice", id.toString()));
}
return await this.service.deleteById(id, transaction);
} catch (error: unknown) {
logger.error(error as Error);
return Result.fail(error as Error);
}
});

View File

@ -1,12 +1,12 @@
import { GetCustomerInvoiceResultDTO } from "../../../../common/dto";
import { GetCustomerInvoiceByIdResultDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
export interface GetCustomerInvoicePresenter {
toDTO: (customerInvoice: CustomerInvoice) => GetCustomerInvoiceResultDTO;
toDTO: (customerInvoice: CustomerInvoice) => GetCustomerInvoiceByIdResultDTO;
}
export const getCustomerInvoicePresenter: GetCustomerInvoicePresenter = {
toDTO: (customerInvoice: CustomerInvoice): GetCustomerInvoiceResultDTO => ({
toDTO: (customerInvoice: CustomerInvoice): GetCustomerInvoiceByIdResultDTO => ({
id: customerInvoice.id.toPrimitive(),
invoice_status: customerInvoice.status.toString(),

View File

@ -1,5 +1,5 @@
export * from "./create-customer-invoice";
//export * from "./delete-customer-invoice";
export * from "./delete-customer-invoice";
export * from "./get-customer-invoice";
export * from "./list-customer-invoices";
//export * from "./update-customer-invoice";

View File

@ -0,0 +1,30 @@
import { ExpressController, errorMapper } from "@erp/core/api";
import { DeleteCustomerInvoiceUseCase } from "../../application";
export class DeleteCustomerInvoiceController extends ExpressController {
public constructor(private readonly deleteCustomerInvoice: DeleteCustomerInvoiceUseCase) {
super();
}
async executeImpl(): Promise<any> {
const { id } = this.req.params;
/*
const user = this.req.user; // asumimos middleware authenticateJWT inyecta user
if (!user || !user.companyId) {
this.unauthorized(res, "Unauthorized: user or company not found");
return;
}
*/
const result = await this.deleteCustomerInvoice.execute({ id });
if (result.isFailure) {
const apiError = errorMapper.toApiError(result.error);
return this.handleApiError(apiError);
}
return this.ok(result.data);
}
}

View File

@ -1,12 +0,0 @@
import { DeleteCustomerInvoiceUseCase } from "@/contexts/customer-invoices/application";
import { ExpressController } from "@/core/common/presentation";
export class DeleteCustomerInvoiceController extends ExpressController {
public constructor(private readonly deleteCustomerInvoice: DeleteCustomerInvoiceUseCase) {
super();
}
async executeImpl(): Promise<any> {
return this.noContent();
}
}

View File

@ -0,0 +1,19 @@
import { SequelizeTransactionManager } from "@erp/core/api";
import { Sequelize } from "sequelize";
import { DeleteCustomerInvoiceUseCase } from "../../application";
import { CustomerInvoiceService } from "../../domain";
import { CustomerInvoiceMapper, CustomerInvoiceRepository } from "../../infrastructure";
import { DeleteCustomerInvoiceController } from "./delete-invoice.controller";
export const buildDeleteCustomerInvoiceController = (database: Sequelize) => {
const transactionManager = new SequelizeTransactionManager(database);
const customerInvoiceRepository = new CustomerInvoiceRepository(
database,
new CustomerInvoiceMapper()
);
const customerInvoiceService = new CustomerInvoiceService(customerInvoiceRepository);
const useCase = new DeleteCustomerInvoiceUseCase(customerInvoiceService, transactionManager);
return new DeleteCustomerInvoiceController(useCase);
};

View File

@ -1,14 +0,0 @@
import { DeleteCustomerInvoiceUseCase } from "@/contexts/customer-invoices/application";
import { CustomerInvoiceService } from "@/contexts/customer-invoices/domain";
import { customerInvoiceRepository } from "@/contexts/customer-invoices/intrastructure";
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
import { DeleteCustomerInvoiceController } from "./delete-customer-invoice.controller";
export const buildDeleteCustomerInvoiceController = () => {
const transactionManager = new SequelizeTransactionManager();
const customerInvoiceService = new CustomerInvoiceService(customerInvoiceRepository);
const useCase = new DeleteCustomerInvoiceUseCase(customerInvoiceService, transactionManager);
return new DeleteCustomerInvoiceController(useCase);
};

View File

@ -1,5 +1,5 @@
export * from "./create-customer-invoice";
//export * from "./delete-customer-invoice";
export * from "./delete-customer-invoice";
export * from "./get-customer-invoice";
export * from "./list-customer-invoices";
///export * from "./update-customer-invoice";

View File

@ -29,5 +29,5 @@ export interface ICustomerInvoiceService {
transaction?: any
): Promise<Result<CustomerInvoice, Error>>;
deleteById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
deleteById(id: UniqueID, transaction?: any): Promise<Result<void, Error>>;
}

View File

@ -144,7 +144,7 @@ export class CustomerInvoiceService implements ICustomerInvoiceService {
* @param transaction - Transacción activa para la operación.
* @returns Result<boolean, Error> - Resultado de la operación.
*/
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<void, Error>> {
return this.repository.deleteById(id, transaction);
}
}

View File

@ -3,11 +3,13 @@ import { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize";
import {
CreateCustomerInvoiceCommandSchema,
GetCustomerInvoiceByIdQuerySchema,
DeleteCustomerInvoiceByIdQuerySchema,
DeleteCustomerInvoiceByIdQuerySchema as GetCustomerInvoiceByIdQuerySchema,
ListCustomerInvoicesQuerySchema,
} from "../../../common/dto";
import {
buildCreateCustomerInvoicesController,
buildDeleteCustomerInvoiceController,
buildGetCustomerInvoiceController,
buildListCustomerInvoicesController,
} from "../../controllers";
@ -60,17 +62,17 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
(req: Request, res: Response, next: NextFunction) => {
buildUpdateCustomerInvoiceController().execute(req, res, next);
}
);
);*/
routes.delete(
"/:customerInvoiceId",
validateAndParseBody(IDeleteCustomerInvoiceRequestSchema),
checkTabContext,
"/:id",
//checkTabContext,
//checkUser,
validateRequest(DeleteCustomerInvoiceByIdQuerySchema, "params"),
(req: Request, res: Response, next: NextFunction) => {
buildDeleteCustomerInvoiceController().execute(req, res, next);
buildDeleteCustomerInvoiceController(database).execute(req, res, next);
}
);*/
);
app.use(`${baseRoutePath}/customer-invoices`, routes);
};

View File

@ -0,0 +1,15 @@
import * as z from "zod/v4";
/**
* Este DTO es utilizado por el endpoint:
* `DELETE /customer-invoices/:id` (eliminar una factura por ID).
*
*/
export const DeleteCustomerInvoiceByIdQuerySchema = z.object({
id: z.string(),
});
export type DeleteCustomerInvoiceByIdQueryDTO = z.infer<
typeof DeleteCustomerInvoiceByIdQuerySchema
>;

View File

@ -1,3 +1,4 @@
export * from "./create-customer-invoice.command.dto";
export * from "./get-customer-invoice.query.dto";
export * from "./delete-customer-invoice-by-id.command.dto";
export * from "./get-customer-invoice-by-id.query.dto";
export * from "./list-customer-invoices.query.dto";

View File

@ -1,7 +1,7 @@
import { MetadataSchema } from "@erp/core";
import * as z from "zod/v4";
export const GetCustomerInvoiceResultSchema = z.object({
export const GetCustomerInvoiceByIdResultSchema = z.object({
id: z.uuid(),
invoice_status: z.string(),
invoice_number: z.string(),
@ -14,4 +14,4 @@ export const GetCustomerInvoiceResultSchema = z.object({
metadata: MetadataSchema.optional(),
});
export type GetCustomerInvoiceResultDTO = z.infer<typeof GetCustomerInvoiceResultSchema>;
export type GetCustomerInvoiceByIdResultDTO = z.infer<typeof GetCustomerInvoiceByIdResultSchema>;

View File

@ -1,3 +1,3 @@
export * from "./customer-invoice-creation.result.dto";
export * from "./get-customer-invoice.result.dto";
export * from "./get-customer-invoice-by-id.result.dto";
export * from "./list-customer-invoices.result.dto";