Facturas de cliente
This commit is contained in:
parent
6c472c21aa
commit
b87082754b
@ -27,7 +27,7 @@
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jsonwebtoken": "^9.0.8",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/luxon": "^3.6.2",
|
||||
"@types/node": "^22.15.12",
|
||||
"@types/passport": "^1.0.16",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
@ -54,7 +54,7 @@
|
||||
"helmet": "^8.0.0",
|
||||
"http": "0.0.1-security",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"luxon": "^3.5.0",
|
||||
"luxon": "^3.6.1",
|
||||
"module-alias": "^2.2.3",
|
||||
"mysql2": "^3.12.0",
|
||||
"passport": "^0.7.0",
|
||||
@ -75,14 +75,9 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/index.ts"
|
||||
],
|
||||
"entry": ["src/index.ts"],
|
||||
"outDir": "dist",
|
||||
"format": [
|
||||
"esm",
|
||||
"cjs"
|
||||
],
|
||||
"format": ["esm", "cjs"],
|
||||
"target": "es2020",
|
||||
"sourcemap": true,
|
||||
"clean": true,
|
||||
|
||||
@ -105,7 +105,8 @@ const server = http
|
||||
|
||||
// Manejo de promesas no capturadas
|
||||
process.on("unhandledRejection", (reason: any, promise: Promise<any>) => {
|
||||
logger.error(`❌ Unhandled rejection at:", ${promise}, "reason:", ${reason}`);
|
||||
const error = `❌ Unhandled rejection at:", ${promise}, "reason:", ${reason}`;
|
||||
logger.error(error);
|
||||
// Dependiendo de la aplicación, podrías desear una salida total o un cierre controlado
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
6
modules/core/src/api/errors/duplicate-entity-error.ts
Normal file
6
modules/core/src/api/errors/duplicate-entity-error.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export class DuplicateEntityError extends Error {
|
||||
constructor(entity: string, id: string) {
|
||||
super(`Entity '${entity}' with ID '${id}' already exists.`);
|
||||
this.name = "DuplicateEntityError";
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import { ApiError } from "./api-error";
|
||||
import { ConflictApiError } from "./conflict-api-error";
|
||||
import { DomainValidationError } from "./domain-validation-error";
|
||||
import { DuplicateEntityError } from "./duplicate-entity-error";
|
||||
import { ForbiddenApiError } from "./forbidden-api-error";
|
||||
import { InternalApiError } from "./internal-api-error";
|
||||
import { NotFoundApiError } from "./not-found-api-error";
|
||||
@ -74,6 +75,10 @@ export const errorMapper = {
|
||||
return new ValidationApiError(error.detail, [{ path: error.field, message: error.detail }]);
|
||||
}
|
||||
|
||||
if (error instanceof DuplicateEntityError) {
|
||||
return new ConflictApiError(error.message);
|
||||
}
|
||||
|
||||
// 3. 🔍 Errores individuales de validación
|
||||
if (
|
||||
message.includes("invalid") ||
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
export * from "./api-error";
|
||||
export * from "./conflict-api-error";
|
||||
export * from "./domain-validation-error";
|
||||
export * from "./duplicate-entity-error";
|
||||
export * from "./error-mapper";
|
||||
export * from "./forbidden-api-error";
|
||||
export * from "./internal-api-error";
|
||||
export * from "./not-found-api-error";
|
||||
export * from "./unauthorized-api-error";
|
||||
export * from "./unavailable-api-error";
|
||||
export * from "./validation-api-error";
|
||||
export * from "./validation-error-collection";
|
||||
|
||||
@ -41,6 +41,7 @@ export const validateRequest = <T extends "body" | "query" | "params">(
|
||||
): RequestHandler => {
|
||||
return async (req, res, next) => {
|
||||
console.debug(`Validating request ${source} with schema.`);
|
||||
console.debug(req[source]);
|
||||
const result = schema.safeParse(req[source]);
|
||||
|
||||
if (!result.success) {
|
||||
|
||||
@ -41,7 +41,8 @@ export abstract class SequelizeMapper<
|
||||
source: TModel[],
|
||||
params?: MapperParamsType
|
||||
): Result<Collection<TEntity>, Error> {
|
||||
return this.mapArrayAndCountToDomain(source, source.length, params);
|
||||
const items = source ?? [];
|
||||
return this.mapArrayAndCountToDomain(items, items.length, params);
|
||||
}
|
||||
|
||||
public mapArrayAndCountToDomain(
|
||||
@ -49,12 +50,14 @@ export abstract class SequelizeMapper<
|
||||
totalCount: number,
|
||||
params?: MapperParamsType
|
||||
): Result<Collection<TEntity>, Error> {
|
||||
const _source = source ?? [];
|
||||
|
||||
try {
|
||||
if (source.length === 0) {
|
||||
if (_source.length === 0) {
|
||||
return Result.ok(new Collection([], totalCount));
|
||||
}
|
||||
|
||||
const items = source.map(
|
||||
const items = _source.map(
|
||||
(value, index) => this.mapToDomain(value, { index, ...params }).data
|
||||
);
|
||||
return Result.ok(new Collection(items, totalCount));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { DuplicateEntityError, ITransactionManager } from "@erp/core/api";
|
||||
import { CreateCustomerInvoiceCommandDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
@ -8,19 +8,21 @@ import { CreateCustomerInvoicesPresenter } from "./presenter";
|
||||
|
||||
export class CreateCustomerInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly customerInvoiceService: ICustomerInvoiceService,
|
||||
private readonly service: ICustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly presenter: CreateCustomerInvoicesPresenter
|
||||
) {}
|
||||
|
||||
public execute(dto: CreateCustomerInvoiceCommandDTO) {
|
||||
const invoicePropOrError = mapDTOToCustomerInvoiceProps(dto);
|
||||
const invoicePropsOrError = mapDTOToCustomerInvoiceProps(dto);
|
||||
|
||||
if (invoicePropOrError.isFailure) {
|
||||
return Result.fail(invoicePropOrError.error);
|
||||
if (invoicePropsOrError.isFailure) {
|
||||
return Result.fail(invoicePropsOrError.error);
|
||||
}
|
||||
|
||||
const invoiceOrError = this.customerInvoiceService.build(invoicePropOrError.data);
|
||||
const { props, id } = invoicePropsOrError.data;
|
||||
|
||||
const invoiceOrError = this.service.build(props, id);
|
||||
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(invoiceOrError.error);
|
||||
@ -29,13 +31,27 @@ export class CreateCustomerInvoiceUseCase {
|
||||
const newInvoice = invoiceOrError.data;
|
||||
|
||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||
const result = await this.customerInvoiceService.save(newInvoice, transaction);
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
try {
|
||||
const duplicateCheck = await this.service.existsById(id, transaction);
|
||||
|
||||
const viewDTO = this.presenter.toDTO(newInvoice);
|
||||
return Result.ok(viewDTO);
|
||||
if (duplicateCheck.isFailure) {
|
||||
return Result.fail(duplicateCheck.error);
|
||||
}
|
||||
|
||||
if (duplicateCheck.data) {
|
||||
return Result.fail(new DuplicateEntityError("CustomerInvoice", id.toString()));
|
||||
}
|
||||
|
||||
const result = await this.service.save(newInvoice, transaction);
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
const viewDTO = this.presenter.toDTO(newInvoice);
|
||||
return Result.ok(viewDTO);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,34 @@
|
||||
import { UniqueID } from "@/core/common/domain";
|
||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { GetCustomerInvoiceByIdQueryDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice, ICustomerInvoiceService } from "../domain";
|
||||
import { ICustomerInvoiceService } from "../../domain";
|
||||
import { GetCustomerInvoicePresenter } from "./presenter";
|
||||
|
||||
export class GetCustomerInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly customerInvoiceService: ICustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
private readonly service: ICustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly presenter: GetCustomerInvoicePresenter
|
||||
) {}
|
||||
|
||||
public execute(customerInvoiceID: UniqueID): Promise<Result<CustomerInvoice, Error>> {
|
||||
public execute(dto: GetCustomerInvoiceByIdQueryDTO) {
|
||||
const idOrError = UniqueID.create(dto.id);
|
||||
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
return await this.customerInvoiceService.findCustomerInvoiceById(
|
||||
customerInvoiceID,
|
||||
transaction
|
||||
);
|
||||
const invoiceOrError = await this.service.getById(idOrError.data, transaction);
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(invoiceOrError.error);
|
||||
}
|
||||
|
||||
const getDTO = this.presenter.toDTO(invoiceOrError.data);
|
||||
return Result.ok(getDTO);
|
||||
} catch (error: unknown) {
|
||||
logger.error(error as Error);
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./get-customer-invoice.use-case";
|
||||
export * from "./presenter";
|
||||
|
||||
@ -1,25 +1,31 @@
|
||||
import { IGetCustomerInvoiceResponseDTO } from "../../../../common/dto";
|
||||
import { CustomerInvoice, CustomerInvoiceItem } from "../../../domain";
|
||||
import { GetCustomerInvoiceResultDTO } from "../../../../common/dto";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
|
||||
export interface IGetCustomerInvoicePresenter {
|
||||
toDTO: (customerInvoice: CustomerInvoice) => IGetCustomerInvoiceResponseDTO;
|
||||
export interface GetCustomerInvoicePresenter {
|
||||
toDTO: (customerInvoice: CustomerInvoice) => GetCustomerInvoiceResultDTO;
|
||||
}
|
||||
|
||||
export const getCustomerInvoicePresenter: IGetCustomerInvoicePresenter = {
|
||||
toDTO: (customerInvoice: CustomerInvoice): IGetCustomerInvoiceResponseDTO => ({
|
||||
export const getCustomerInvoicePresenter: GetCustomerInvoicePresenter = {
|
||||
toDTO: (customerInvoice: CustomerInvoice): GetCustomerInvoiceResultDTO => ({
|
||||
id: customerInvoice.id.toPrimitive(),
|
||||
|
||||
customerInvoice_status: customerInvoice.status.toString(),
|
||||
customerInvoice_number: customerInvoice.invoiceNumber.toString(),
|
||||
customerInvoice_series: customerInvoice.invoiceSeries.toString(),
|
||||
invoice_status: customerInvoice.status.toString(),
|
||||
invoice_number: customerInvoice.invoiceNumber.toString(),
|
||||
invoice_series: customerInvoice.invoiceSeries.toString(),
|
||||
issue_date: customerInvoice.issueDate.toDateString(),
|
||||
operation_date: customerInvoice.operationDate.toDateString(),
|
||||
language_code: "ES",
|
||||
currency: customerInvoice.customerInvoiceCurrency.toString(),
|
||||
subtotal: customerInvoice.calculateSubtotal().toPrimitive(),
|
||||
total: customerInvoice.calculateTotal().toPrimitive(),
|
||||
currency: customerInvoice.currency,
|
||||
|
||||
items:
|
||||
metadata: {
|
||||
entity: "customer-invoices",
|
||||
},
|
||||
|
||||
//subtotal: customerInvoice.calculateSubtotal().toPrimitive(),
|
||||
|
||||
//total: customerInvoice.calculateTotal().toPrimitive(),
|
||||
|
||||
/*items:
|
||||
customerInvoice.items.size() > 0
|
||||
? customerInvoice.items.map((item: CustomerInvoiceItem) => ({
|
||||
description: item.description.toString(),
|
||||
@ -30,7 +36,7 @@ export const getCustomerInvoicePresenter: IGetCustomerInvoicePresenter = {
|
||||
//tax_amount: item.calculateTaxAmount().toPrimitive(),
|
||||
total: item.calculateTotal().toPrimitive(),
|
||||
}))
|
||||
: [],
|
||||
: [],*/
|
||||
|
||||
//sender: {}, //await CustomerInvoiceParticipantPresenter(customerInvoice.senderId, context),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ValidationErrorCollection, ValidationErrorDetail } from "@erp/core/api";
|
||||
import { UtcDate } from "@repo/rdx-ddd";
|
||||
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CreateCustomerInvoiceCommandDTO } from "../../../common/dto";
|
||||
import {
|
||||
@ -16,16 +16,15 @@ import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice
|
||||
* No construye directamente el agregado.
|
||||
*
|
||||
* @param dto - DTO con los datos de la factura de cliente
|
||||
* @returns CustomerInvoiceProps - Las propiedades para crear una factura de cliente o error
|
||||
* @returns
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
export function mapDTOToCustomerInvoiceProps(
|
||||
dto: CreateCustomerInvoiceCommandDTO
|
||||
): Result<CustomerInvoiceProps, Error> {
|
||||
export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceCommandDTO) {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
//const invoiceId = extractOrPushError(UniqueID.create(dto.id), "invoice_id", errors);
|
||||
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||
|
||||
const invoiceNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(dto.invoice_number),
|
||||
@ -66,7 +65,7 @@ export function mapDTOToCustomerInvoiceProps(
|
||||
currency,
|
||||
};
|
||||
|
||||
return Result.ok(invoiceProps);
|
||||
return Result.ok({ id: invoiceId!, props: invoiceProps });
|
||||
|
||||
/*if (hasNoUndefinedFields(invoiceProps)) {
|
||||
const invoiceOrError = CustomerInvoice.create(invoiceProps, invoiceId);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export * from "./create-customer-invoice";
|
||||
//export * from "./delete-customer-invoice";
|
||||
//export * from "./get-customer-invoice";
|
||||
export * from "./get-customer-invoice";
|
||||
export * from "./list-customer-invoices";
|
||||
//export * from "./update-customer-invoice";
|
||||
|
||||
@ -16,10 +16,7 @@ export class ListCustomerInvoicesUseCase {
|
||||
public execute(criteria: Criteria): Promise<Result<ListCustomerInvoicesResultDTO, Error>> {
|
||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||
try {
|
||||
const result = await this.customerInvoiceService.findCustomerInvoices(
|
||||
criteria,
|
||||
transaction
|
||||
);
|
||||
const result = await this.customerInvoiceService.findByCriteria(criteria, transaction);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { ListCustomerInvoicesViewDTO } from "@erp/customer-invoices/common/dto";
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { ListCustomerInvoicesResultDTO } from "../../../../common/dto";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
|
||||
export interface ListCustomerInvoicesPresenter {
|
||||
toDTO: (
|
||||
customerInvoices: Collection<CustomerInvoice>,
|
||||
criteria: Criteria
|
||||
) => ListCustomerInvoicesViewDTO;
|
||||
) => ListCustomerInvoicesResultDTO;
|
||||
}
|
||||
|
||||
export const listCustomerInvoicesPresenter: ListCustomerInvoicesPresenter = {
|
||||
toDTO: (
|
||||
customerInvoices: Collection<CustomerInvoice>,
|
||||
criteria: Criteria
|
||||
): ListCustomerInvoicesViewDTO => {
|
||||
): ListCustomerInvoicesResultDTO => {
|
||||
const items = customerInvoices.map((invoice) => {
|
||||
return {
|
||||
id: invoice.id.toPrimitive(),
|
||||
@ -25,7 +25,7 @@ export const listCustomerInvoicesPresenter: ListCustomerInvoicesPresenter = {
|
||||
issue_date: invoice.issueDate.toISOString(),
|
||||
operation_date: invoice.operationDate.toISOString(),
|
||||
language_code: "ES",
|
||||
currency: invoice.customerInvoiceCurrency.toString(),
|
||||
currency: "EUR",
|
||||
|
||||
subtotal_price: invoice.calculateSubtotal().toPrimitive(),
|
||||
total_price: invoice.calculateTotal().toPrimitive(),
|
||||
|
||||
@ -2,12 +2,15 @@ import { SequelizeTransactionManager } from "@erp/core/api";
|
||||
import { Sequelize } from "sequelize";
|
||||
import { CreateCustomerInvoiceUseCase, CreateCustomerInvoicesPresenter } from "../../application/";
|
||||
import { CustomerInvoiceService } from "../../domain";
|
||||
import { CustomerInvoiceRepository, customerInvoiceMapper } from "../../infrastructure";
|
||||
import { CustomerInvoiceMapper, CustomerInvoiceRepository } from "../../infrastructure";
|
||||
import { CreateCustomerInvoiceController } from "./create-customer-invoice";
|
||||
|
||||
export const buildCreateCustomerInvoicesController = (database: Sequelize) => {
|
||||
const transactionManager = new SequelizeTransactionManager(database);
|
||||
const customerInvoiceRepository = new CustomerInvoiceRepository(database, customerInvoiceMapper);
|
||||
const customerInvoiceRepository = new CustomerInvoiceRepository(
|
||||
database,
|
||||
new CustomerInvoiceMapper()
|
||||
);
|
||||
const customerInvoiceService = new CustomerInvoiceService(customerInvoiceRepository);
|
||||
const presenter = new CreateCustomerInvoicesPresenter();
|
||||
|
||||
|
||||
@ -1,47 +1,30 @@
|
||||
import { ExpressController } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { ExpressController, errorMapper } from "@erp/core/api";
|
||||
import { GetCustomerInvoiceUseCase } from "../../application";
|
||||
import { IGetCustomerInvoicePresenter } from "./presenter";
|
||||
|
||||
export class GetCustomerInvoiceController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly getCustomerInvoice: GetCustomerInvoiceUseCase,
|
||||
private readonly presenter: IGetCustomerInvoicePresenter
|
||||
) {
|
||||
public constructor(private readonly getCustomerInvoice: GetCustomerInvoiceUseCase) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const { customerInvoiceId } = this.req.params;
|
||||
const { id } = this.req.params;
|
||||
|
||||
// Validar ID
|
||||
const customerInvoiceIdOrError = UniqueID.create(customerInvoiceId);
|
||||
if (customerInvoiceIdOrError.isFailure)
|
||||
return this.invalidInputError("CustomerInvoice ID not valid");
|
||||
/*
|
||||
const user = this.req.user; // asumimos middleware authenticateJWT inyecta user
|
||||
|
||||
const customerInvoiceOrError = await this.getCustomerInvoice.execute(
|
||||
customerInvoiceIdOrError.data
|
||||
);
|
||||
if (!user || !user.companyId) {
|
||||
this.unauthorized(res, "Unauthorized: user or company not found");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
if (customerInvoiceOrError.isFailure) {
|
||||
return this.handleError(customerInvoiceOrError.error);
|
||||
const result = await this.getCustomerInvoice.execute({ id });
|
||||
|
||||
if (result.isFailure) {
|
||||
const apiError = errorMapper.toApiError(result.error);
|
||||
return this.handleApiError(apiError);
|
||||
}
|
||||
|
||||
return this.ok(this.presenter.toDTO(customerInvoiceOrError.data));
|
||||
}
|
||||
|
||||
private handleError(error: Error) {
|
||||
const message = error.message;
|
||||
|
||||
if (
|
||||
message.includes("Database connection lost") ||
|
||||
message.includes("Database request timed out")
|
||||
) {
|
||||
return this.unavailableError(
|
||||
"Database service is currently unavailable. Please try again later."
|
||||
);
|
||||
}
|
||||
|
||||
return this.conflictError(message);
|
||||
return this.ok(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import { SequelizeTransactionManager } from "@erp/core/api";
|
||||
import { Sequelize } from "sequelize";
|
||||
import { GetCustomerInvoiceUseCase, getCustomerInvoicePresenter } from "../../application";
|
||||
import { CustomerInvoiceService } from "../../domain";
|
||||
import { CustomerInvoiceRepository, customerInvoiceMapper } from "../../infrastructure";
|
||||
|
||||
import { GetCustomerInvoiceUseCase } from "../../application";
|
||||
import { GetCustomerInvoiceController } from "./get-invoice.controller";
|
||||
import { getCustomerInvoicePresenter } from "./presenter";
|
||||
|
||||
export const buildGetCustomerInvoiceController = (database: Sequelize) => {
|
||||
const transactionManager = new SequelizeTransactionManager(database);
|
||||
const customerInvoiceRepository = new CustomerInvoiceRepository(database, customerInvoiceMapper);
|
||||
const customerInvoiceService = new CustomerInvoiceService(customerInvoiceRepository);
|
||||
|
||||
const useCase = new GetCustomerInvoiceUseCase(customerInvoiceService, transactionManager);
|
||||
const presenter = getCustomerInvoicePresenter;
|
||||
|
||||
return new GetCustomerInvoiceController(useCase, presenter);
|
||||
const useCase = new GetCustomerInvoiceUseCase(
|
||||
customerInvoiceService,
|
||||
transactionManager,
|
||||
presenter
|
||||
);
|
||||
|
||||
return new GetCustomerInvoiceController(useCase);
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
export * from "./aggregates";
|
||||
export * from "./entities";
|
||||
export * from "./errors";
|
||||
export * from "./repositories";
|
||||
export * from "./services";
|
||||
export * from "./value-objects";
|
||||
|
||||
@ -4,6 +4,8 @@ import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
|
||||
export interface ICustomerInvoiceRepository {
|
||||
existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Persiste una nueva factura o actualiza una existente.
|
||||
|
||||
@ -4,10 +4,12 @@ import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice, CustomerInvoiceProps } from "../aggregates";
|
||||
|
||||
export interface ICustomerInvoiceService {
|
||||
build(props: CustomerInvoiceProps): Result<CustomerInvoice, Error>;
|
||||
build(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error>;
|
||||
|
||||
save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
||||
|
||||
existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
|
||||
findByCriteria(
|
||||
criteria: Criteria,
|
||||
transaction?: any
|
||||
|
||||
@ -13,10 +13,11 @@ export class CustomerInvoiceService implements ICustomerInvoiceService {
|
||||
* Construye un nuevo agregado CustomerInvoice a partir de props validadas.
|
||||
*
|
||||
* @param props - Las propiedades ya validadas para crear la factura.
|
||||
* @param id - Identificador UUID de la factura (opcional).
|
||||
* @returns Result<CustomerInvoice, Error> - El agregado construido o un error si falla la creación.
|
||||
*/
|
||||
build(props: CustomerInvoiceProps): Result<CustomerInvoice, Error> {
|
||||
return CustomerInvoice.create(props);
|
||||
build(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||
return CustomerInvoice.create(props, id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,6 +32,19 @@ export class CustomerInvoiceService implements ICustomerInvoiceService {
|
||||
return saved.isSuccess ? Result.ok(invoice) : Result.fail(saved.error);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Comprueba si existe o no en persistencia una factura con el ID proporcionado
|
||||
*
|
||||
* @param id - Identificador UUID de la factura.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<Boolean, Error> - Existe la factura o no.
|
||||
*/
|
||||
|
||||
async existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
|
||||
return this.repository.existsById(id, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene una colección de facturas que cumplen con los filtros definidos en un objeto Criteria.
|
||||
*
|
||||
@ -62,7 +76,7 @@ export class CustomerInvoiceService implements ICustomerInvoiceService {
|
||||
* @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
|
||||
*/
|
||||
async getById(id: UniqueID, transaction?: Transaction): Promise<Result<CustomerInvoice>> {
|
||||
return await this.repository.getById(id, transaction);
|
||||
return await this.repository.findById(id, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,10 +3,12 @@ import { Application, NextFunction, Request, Response, Router } from "express";
|
||||
import { Sequelize } from "sequelize";
|
||||
import {
|
||||
CreateCustomerInvoiceCommandSchema,
|
||||
GetCustomerInvoiceByIdQuerySchema,
|
||||
ListCustomerInvoicesQuerySchema,
|
||||
} from "../../../common/dto";
|
||||
import {
|
||||
buildCreateCustomerInvoicesController,
|
||||
buildGetCustomerInvoiceController,
|
||||
buildListCustomerInvoicesController,
|
||||
} from "../../controllers";
|
||||
|
||||
@ -24,21 +26,21 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
||||
"/",
|
||||
//checkTabContext,
|
||||
//checkUser,
|
||||
validateRequest(ListCustomerInvoicesQuerySchema, "query"),
|
||||
validateRequest(ListCustomerInvoicesQuerySchema, "params"),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildListCustomerInvoicesController(database).execute(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
/*routes.get(
|
||||
"/:customerInvoiceId",
|
||||
routes.get(
|
||||
"/:id",
|
||||
//checkTabContext,
|
||||
//checkUser,
|
||||
validateRequest(GetCustomerInvoiceByIdQuerySchema, "query"),
|
||||
validateRequest(GetCustomerInvoiceByIdQuerySchema, "params"),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
buildGetCustomerInvoiceController(database).execute(req, res, next);
|
||||
}
|
||||
);*/
|
||||
);
|
||||
|
||||
routes.post(
|
||||
"/",
|
||||
|
||||
@ -67,11 +67,11 @@ export class CustomerInvoiceMapper
|
||||
return CustomerInvoice.create(
|
||||
{
|
||||
status: statusOrError.data,
|
||||
customerInvoiceSeries: customerInvoiceSeriesOrError.data,
|
||||
customerInvoiceNumber: customerInvoiceNumberOrError.data,
|
||||
invoiceSeries: customerInvoiceSeriesOrError.data,
|
||||
invoiceNumber: customerInvoiceNumberOrError.data,
|
||||
issueDate: issueDateOrError.data,
|
||||
operationDate: operationDateOrError.data,
|
||||
customerInvoiceCurrency,
|
||||
currency: customerInvoiceCurrency,
|
||||
items: itemsOrErrors.data,
|
||||
},
|
||||
idOrError.data
|
||||
@ -95,7 +95,7 @@ export class CustomerInvoiceMapper
|
||||
issue_date: source.issueDate.toPrimitive(),
|
||||
operation_date: source.operationDate.toPrimitive(),
|
||||
invoice_language: "es",
|
||||
invoice_currency: source.customerInvoiceCurrency || "EUR",
|
||||
invoice_currency: source.currency || "EUR",
|
||||
|
||||
subtotal_amount: subtotal.amount,
|
||||
subtotal_scale: subtotal.scale,
|
||||
|
||||
@ -11,16 +11,26 @@ export class CustomerInvoiceRepository
|
||||
extends SequelizeRepository<CustomerInvoice>
|
||||
implements ICustomerInvoiceRepository
|
||||
{
|
||||
private readonly model: typeof CustomerInvoiceModel;
|
||||
//private readonly model: typeof CustomerInvoiceModel;
|
||||
private readonly mapper!: ICustomerInvoiceMapper;
|
||||
|
||||
constructor(database: Sequelize, mapper: ICustomerInvoiceMapper) {
|
||||
super(database);
|
||||
|
||||
this.model = database.model("CustomerInvoice") as typeof CustomerInvoiceModel;
|
||||
//CustomerInvoice = database.model("CustomerInvoice") as typeof CustomerInvoiceModel;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
async existsById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const result = await this._exists(CustomerInvoiceModel, "id", id.toString(), transaction);
|
||||
|
||||
return Result.ok(Boolean(result));
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(errorMapper.toDomainError(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Persiste una nueva factura o actualiza una existente.
|
||||
@ -35,7 +45,7 @@ export class CustomerInvoiceRepository
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
try {
|
||||
const data = this.mapper.mapToPersistence(invoice);
|
||||
await this.model.upsert(data, { transaction });
|
||||
await CustomerInvoiceModel.upsert(data, { transaction });
|
||||
return Result.ok(invoice);
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(errorMapper.toDomainError(err));
|
||||
@ -51,7 +61,7 @@ export class CustomerInvoiceRepository
|
||||
*/
|
||||
async findById(id: UniqueID, transaction: Transaction): Promise<Result<CustomerInvoice, Error>> {
|
||||
try {
|
||||
const rawData = await this._findById(this.model, id.toString(), { transaction });
|
||||
const rawData = await this._findById(CustomerInvoiceModel, id.toString(), { transaction });
|
||||
|
||||
if (!rawData) {
|
||||
return Result.fail(new Error(`Invoice with id ${id} not found.`));
|
||||
@ -80,7 +90,7 @@ export class CustomerInvoiceRepository
|
||||
const converter = new CriteriaToSequelizeConverter();
|
||||
const query = converter.convert(criteria);
|
||||
|
||||
const instances = await this.model.findAll({
|
||||
const instances = await CustomerInvoiceModel.findAll({
|
||||
...query,
|
||||
transaction,
|
||||
});
|
||||
@ -100,7 +110,7 @@ export class CustomerInvoiceRepository
|
||||
*/
|
||||
async deleteById(id: UniqueID, transaction: any): Promise<Result<void, Error>> {
|
||||
try {
|
||||
await this._deleteById(this.model, id, false, transaction);
|
||||
await this._deleteById(CustomerInvoiceModel, id, false, transaction);
|
||||
return Result.ok<void>();
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(errorMapper.toDomainError(err));
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import * as z from "zod/v4";
|
||||
|
||||
/**
|
||||
* Este DTO es utilizado por el endpoint:
|
||||
* `GET /customer-invoices/:id` (consultar una factura por ID).
|
||||
*
|
||||
*/
|
||||
|
||||
export const GetCustomerInvoiceByIdQuerySchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export type GetCustomerInvoiceByIdQueryDTO = z.infer<typeof GetCustomerInvoiceByIdQuerySchema>;
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./create-customer-invoice.command.dto";
|
||||
export * from "./get-customer-invoice.query.dto";
|
||||
export * from "./list-customer-invoices.query.dto";
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { MetadataSchema } from "@erp/core";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const GetCustomerInvoiceResultSchema = z.object({
|
||||
id: z.uuid(),
|
||||
invoice_status: z.string(),
|
||||
invoice_number: z.string(),
|
||||
invoice_series: z.string(),
|
||||
issue_date: z.iso.datetime({ offset: true }),
|
||||
operation_date: z.iso.datetime({ offset: true }),
|
||||
language_code: z.string(),
|
||||
currency: z.string(),
|
||||
|
||||
metadata: MetadataSchema.optional(),
|
||||
});
|
||||
|
||||
export type GetCustomerInvoiceResultDTO = z.infer<typeof GetCustomerInvoiceResultSchema>;
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./customer-invoice-creation.result.dto";
|
||||
export * from "./get-customer-invoice.result.dto";
|
||||
export * from "./list-customer-invoices.result.dto";
|
||||
|
||||
@ -75,7 +75,7 @@ importers:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
luxon:
|
||||
specifier: ^3.5.0
|
||||
specifier: ^3.6.1
|
||||
version: 3.6.1
|
||||
module-alias:
|
||||
specifier: ^2.2.3
|
||||
@ -151,7 +151,7 @@ importers:
|
||||
specifier: ^9.0.8
|
||||
version: 9.0.10
|
||||
'@types/luxon':
|
||||
specifier: ^3.4.2
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
'@types/node':
|
||||
specifier: ^22.15.12
|
||||
|
||||
Loading…
Reference in New Issue
Block a user