diff --git a/modules/core/src/api/application/presenters/presenter.ts b/modules/core/src/api/application/presenters/presenter.ts index de570619..1c0abafe 100644 --- a/modules/core/src/api/application/presenters/presenter.ts +++ b/modules/core/src/api/application/presenters/presenter.ts @@ -1,5 +1,5 @@ -import { IPresenterRegistry } from "./presenter-registry.interface"; -import { IPresenter, IPresenterOutputParams } from "./presenter.interface"; +import type { IPresenter, IPresenterOutputParams } from "./presenter.interface"; +import type { IPresenterRegistry } from "./presenter-registry.interface"; export abstract class Presenter implements IPresenter diff --git a/modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts b/modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts index 6830da9f..ba561568 100644 --- a/modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts +++ b/modules/core/src/common/catalogs/taxes/json-tax-catalog.provider.ts @@ -1,8 +1,9 @@ // --- Adaptador que carga el catálogo JSON en memoria e indexa por code --- import { Maybe } from "@repo/rdx-utils"; -import { TaxCatalogProvider } from "./tax-catalog.provider"; -import { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types"; + +import type { TaxCatalogProvider } from "./tax-catalog.provider"; +import type { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types"; export class JsonTaxCatalogProvider implements TaxCatalogProvider { // Índice por código normalizado diff --git a/modules/core/src/common/catalogs/taxes/tax-catalog.provider.ts b/modules/core/src/common/catalogs/taxes/tax-catalog.provider.ts index 902048e9..949c6fb5 100644 --- a/modules/core/src/common/catalogs/taxes/tax-catalog.provider.ts +++ b/modules/core/src/common/catalogs/taxes/tax-catalog.provider.ts @@ -1,7 +1,6 @@ -// --- Puerto (interfaz) para resolver tasas desde un catálogo --- +import type { Maybe } from "@repo/rdx-utils"; // Usa tu implementación real de Maybe -import { Maybe } from "@repo/rdx-utils"; // Usa tu implementación real de Maybe -import { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types"; +import type { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types"; export interface TaxCatalogProvider { /** diff --git a/modules/customer-invoices/src/api/application/services/customer-invoice-application.service.ts b/modules/customer-invoices/src/api/application/services/customer-invoice-application.service.ts index 81f334cc..4c972141 100644 --- a/modules/customer-invoices/src/api/application/services/customer-invoice-application.service.ts +++ b/modules/customer-invoices/src/api/application/services/customer-invoice-application.service.ts @@ -3,11 +3,14 @@ import type { UniqueID } from "@repo/rdx-ddd"; import { type Collection, Maybe, Result } from "@repo/rdx-utils"; import type { Transaction } from "sequelize"; -import type { - CustomerInvoiceNumber, - CustomerInvoiceSerie, - CustomerInvoiceStatus, - ICustomerInvoiceNumberGenerator, +import { + CustomerInvoiceIsProformaSpecification, + type CustomerInvoiceNumber, + type CustomerInvoiceSerie, + type CustomerInvoiceStatus, + type ICustomerInvoiceNumberGenerator, + ProformaCannotBeDeletedError, + StatusInvoiceIsDraftSpecification, } from "../../domain"; import { CustomerInvoice, @@ -69,6 +72,27 @@ export class CustomerInvoiceApplicationService { return CustomerInvoice.create({ ...props, companyId }, proformaId); } + /** + * Guarda una nueva factura y devuelve la factura guardada. + * + * @param companyId - Identificador de la empresa a la que pertenece la factura. + * @param proforma - La factura a guardar. + * @param transaction - Transacción activa para la operación. + * @returns Result - La factura guardada o un error si falla la operación. + */ + async createIssueInvoiceInCompany( + companyId: UniqueID, + invoice: CustomerInvoice, + transaction: Transaction + ): Promise> { + const result = await this.repository.create(invoice, transaction); + if (result.isFailure) { + return Result.fail(result.error); + } + + return this.getIssueInvoiceByIdInCompany(companyId, invoice.id, transaction); + } + /** * Guarda una nueva proforma y devuelve la proforma guardada. * @@ -77,7 +101,7 @@ export class CustomerInvoiceApplicationService { * @param transaction - Transacción activa para la operación. * @returns Result - La proforma guardada o un error si falla la operación. */ - async createInvoiceInCompany( + async createProformaInCompany( companyId: UniqueID, proforma: CustomerInvoice, transaction: Transaction @@ -113,7 +137,27 @@ export class CustomerInvoiceApplicationService { /** * - * Comprueba si existe o no en persistencia una factura con el ID proporcionado + * Comprueba si existe o no en persistencia una proforma con el ID proporcionado + * + * @param companyId - Identificador de la empresa a la que pertenece la factura. + * @param proformaId - Identificador UUID de la factura. + * @param transaction - Transacción activa para la operación. + * @returns Result - Existe la factura o no. + */ + + async existsProformaByIdInCompany( + companyId: UniqueID, + proformaId: UniqueID, + transaction?: Transaction + ): Promise> { + return this.repository.existsByIdInCompany(companyId, proformaId, transaction, { + is_proforma: true, + }); + } + + /** + * + * Comprueba si existe o no en persistencia una proforma con el ID proporcionado * * @param companyId - Identificador de la empresa a la que pertenece la factura. * @param invoiceId - Identificador UUID de la factura. @@ -121,12 +165,14 @@ export class CustomerInvoiceApplicationService { * @returns Result - Existe la factura o no. */ - async existsByIdInCompany( + async existsIssueInvoiceByIdInCompany( companyId: UniqueID, invoiceId: UniqueID, transaction?: Transaction ): Promise> { - return this.repository.existsByIdInCompany(companyId, invoiceId, transaction); + return this.repository.existsByIdInCompany(companyId, invoiceId, transaction, { + is_proforma: false, + }); } /** @@ -199,13 +245,13 @@ export class CustomerInvoiceApplicationService { changes: CustomerInvoicePatchProps, transaction?: Transaction ): Promise> { - const invoiceResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction); + const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction); - if (invoiceResult.isFailure) { - return Result.fail(invoiceResult.error); + if (proformaResult.isFailure) { + return Result.fail(proformaResult.error); } - const updated = invoiceResult.data.update(changes); + const updated = proformaResult.data.update(changes); if (updated.isFailure) { return Result.fail(updated.error); @@ -215,27 +261,48 @@ export class CustomerInvoiceApplicationService { } /** - * Elimina (o marca como eliminada) una factura según su ID. + * Elimina (o marca como eliminada) una proforma según su ID. * - * @param companyId - Identificador de la empresa a la que pertenece la factura. - * @param invoiceId - Identificador UUID de la factura. + * @param companyId - Identificador de la empresa a la que pertenece la proforma. + * @param proformaId - Identificador UUID de la proforma. * @param transaction - Transacción activa para la operación. * @returns Result - Resultado de la operación. */ - async deleteInvoiceByIdInCompany( + async deleteProformaByIdInCompany( companyId: UniqueID, - invoiceId: UniqueID, + proformaId: UniqueID, transaction?: Transaction ): Promise> { - return this.repository.deleteByIdInCompany(companyId, invoiceId, transaction); + // 1) Buscar la proforma + const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction); + + if (proformaResult.isFailure) return Result.fail(proformaResult.error); + const proforma = proformaResult.data; + + // 2) Validar: es proforma + const isProforma = new CustomerInvoiceIsProformaSpecification(); + if (!(await isProforma.isSatisfiedBy(proforma))) { + return Result.fail(new ProformaCannotBeDeletedError(proformaId.toString(), "not a proforma")); + } + + // 3) Validar: estado draft + const isDraft = new StatusInvoiceIsDraftSpecification(); + if (!(await isDraft.isSatisfiedBy(proforma))) { + return Result.fail( + new ProformaCannotBeDeletedError(proformaId.toString(), "status is not 'draft'") + ); + } + + // 4) Borrar la proforma (baja lógica) + return this.repository.deleteProformaByIdInCompany(companyId, proformaId, transaction); } /** * * Actualiza el "status" de una proforma * - * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. - * @param proformaId - UUID de la factura a eliminar. + * @param companyId - Identificador UUID de la empresa a la que pertenece la proforma. + * @param proformaId - UUID de la proforma a actualizar. * @param newStatus - nuevo estado * @param transaction - Transacción activa para la operación. * @returns Result diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/change-status-proforma.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/proformas/change-status-proforma.use-case.ts index 22227546..f3229c39 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/change-status-proforma.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/change-status-proforma.use-case.ts @@ -37,7 +37,7 @@ export class ChangeStatusProformaUseCase { return this.transactionManager.complete(async (transaction) => { try { /** 1. Recuperamos la proforma */ - const proformaResult = await this.service.getInvoiceByIdInCompany( + const proformaResult = await this.service.getProformaByIdInCompany( companyId, proformaId, transaction diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/delete-proforma.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/proformas/delete-proforma.use-case.ts index 7b6b3f59..cfe4e17e 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/delete-proforma.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/delete-proforma.use-case.ts @@ -1,4 +1,4 @@ -import { EntityNotFoundError, type ITransactionManager } from "@erp/core/api"; +import type { ITransactionManager } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; @@ -19,32 +19,15 @@ export class DeleteProformaUseCase { const { proforma_id, companyId } = params; const idOrError = UniqueID.create(proforma_id); - if (idOrError.isFailure) { return Result.fail(idOrError.error); } - const invoiceId = idOrError.data; + const proformaId = idOrError.data; return this.transactionManager.complete(async (transaction) => { try { - const existsCheck = await this.service.existsByIdInCompany( - companyId, - invoiceId, - transaction - ); - - if (existsCheck.isFailure) { - return Result.fail(existsCheck.error); - } - - const invoiceExists = existsCheck.data; - - if (!invoiceExists) { - return Result.fail(new EntityNotFoundError("Proforma", "id", invoiceId.toString())); - } - - return await this.service.deleteInvoiceByIdInCompany(companyId, invoiceId, transaction); + return await this.service.deleteProformaByIdInCompany(companyId, proformaId, transaction); } catch (error: unknown) { return Result.fail(error as Error); } diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/issue-proforma.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/proformas/issue-proforma.use-case.ts index 98e57f4d..9bb5780f 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/issue-proforma.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/issue-proforma.use-case.ts @@ -50,7 +50,7 @@ export class IssueProformaInvoiceUseCase { return this.transactionManager.complete(async (transaction) => { try { /** 1. Recuperamos la proforma */ - const proformaResult = await this.service.getInvoiceByIdInCompany( + const proformaResult = await this.service.getProformaByIdInCompany( companyId, proformaId, transaction @@ -74,7 +74,7 @@ export class IssueProformaInvoiceUseCase { if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error); /** 5. Guardar la nueva factura */ - const saveInvoiceResult = await this.service.createInvoiceInCompany( + const saveInvoiceResult = await this.service.createIssueInvoiceInCompany( companyId, issuedInvoiceOrError.data, transaction diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo_acana.jpg b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo_acana.jpg new file mode 100644 index 00000000..c21c8b29 Binary files /dev/null and b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo_acana.jpg differ diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo1.jpg b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo_rodax.jpg similarity index 100% rename from modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo1.jpg rename to modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/logo_rodax.jpg diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/template.hbs b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/template.hbs index c4f9ff1f..63aad43e 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/template.hbs +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/report-proforma/reporter/templates/customer-invoice/template.hbs @@ -8,7 +8,7 @@ Factura F26200 + + + + +
+ +
+ +
+
+ +
+ +
+ +
+ + TOTAL: {{total_amount}} +
+
+
+ + + + + + + + + + + + + + {{#each items}} + + + + + + + + {{/each}} + +
ConceptoCantidadPrecio unidadImporte total
{{description}}{{#if quantity}}{{quantity}}{{else}} {{/if}}{{#if unit_amount}}{{unit_amount}}{{else}} {{/if}}{{#if taxable_amount}}{{taxable_amount}}{{else}} {{/if}}
+
+ +
+ +
+ {{#if payment_method}} +
+

Forma de pago: {{payment_method}}

+
+ {{else}} + + {{/if}} + {{#if notes}} +
+

Notas: {{notes}}

+
+ {{else}} + + {{/if}} + +
+ +
+ + + {{#if discount_percentage}} + + + + + + + + + + + {{else}} + + {{/if}} + + + + + + {{#each taxes}} + + + + + + {{/each}} + + + + + + +
Importe neto {{subtotal_amount}}
Descuento {{discount_percentage}} {{discount_amount.value}}
Base imponible {{taxable_amount}}
{{tax_name}} {{taxes_amount}}
+ Total factura +   + {{total_amount}}
+
+
+
+ + +
+ +
+ + + + \ No newline at end of file diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/index.ts b/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/index.ts index 002aceac..5af0ad37 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/index.ts +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/index.ts @@ -1 +1 @@ -export * from "./update-customer-invoice.use-case"; +export * from "./update-proforma.use-case"; diff --git a/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/update-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/update-proforma.use-case.ts similarity index 94% rename from modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/update-customer-invoice.use-case.ts rename to modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/update-proforma.use-case.ts index 077fe38b..6bd13536 100644 --- a/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/update-customer-invoice.use-case.ts +++ b/modules/customer-invoices/src/api/application/use-cases/proformas/update-proforma/update-proforma.use-case.ts @@ -10,20 +10,20 @@ import type { CustomerInvoiceApplicationService } from "../../../services/custom import { mapDTOToUpdateCustomerInvoicePatchProps } from "./map-dto-to-update-customer-invoice-props"; -type UpdateCustomerInvoiceUseCaseInput = { +type UpdateProformaUseCaseInput = { companyId: UniqueID; proforma_id: string; dto: UpdateProformaByIdRequestDTO; }; -export class UpdateCustomerInvoiceUseCase { +export class UpdateProformaUseCase { constructor( private readonly service: CustomerInvoiceApplicationService, private readonly transactionManager: ITransactionManager, private readonly presenterRegistry: IPresenterRegistry ) {} - public execute(params: UpdateCustomerInvoiceUseCaseInput) { + public execute(params: UpdateProformaUseCaseInput) { const { companyId, proforma_id, dto } = params; const idOrError = UniqueID.create(proforma_id); diff --git a/modules/customer-invoices/src/api/domain/errors/index.ts b/modules/customer-invoices/src/api/domain/errors/index.ts index 14e406c5..5d141a19 100644 --- a/modules/customer-invoices/src/api/domain/errors/index.ts +++ b/modules/customer-invoices/src/api/domain/errors/index.ts @@ -2,3 +2,4 @@ export * from "./customer-invoice-id-already-exits-error"; export * from "./entity-is-not-proforma-error"; export * from "./invalid-proforma-transition-error"; export * from "./proforma-cannot-be-converted-to-invoice-error"; +export * from "./proforma-cannot-be-deleted-error"; diff --git a/modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-deleted-error.ts b/modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-deleted-error.ts new file mode 100644 index 00000000..4bbc3655 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/errors/proforma-cannot-be-deleted-error.ts @@ -0,0 +1,12 @@ +// domain/errors/proforma-cannot-be-deleted.error.ts +import { DomainError } from "@repo/rdx-ddd"; + +/** Proforma solo se puede borrar si está en 'draft' */ +export class ProformaCannotBeDeletedError extends DomainError { + constructor(id: string, reason?: string, options?: ErrorOptions) { + super(`Proforma '${id}' cannot be deleted${reason ? `: ${reason}` : ""}.`, options); + this.name = "ProformaCannotBeDeletedError"; + } +} +export const isProformaCannotBeDeletedError = (e: unknown): e is ProformaCannotBeDeletedError => + e instanceof ProformaCannotBeDeletedError; diff --git a/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts b/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts index e0887634..8462f5f1 100644 --- a/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts +++ b/modules/customer-invoices/src/api/domain/repositories/customer-invoice-repository.interface.ts @@ -36,7 +36,8 @@ export interface ICustomerInvoiceRepository { existsByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: unknown + transaction: unknown, + options: unknown ): Promise>; /** @@ -71,14 +72,14 @@ export interface ICustomerInvoiceRepository { /** * - * Elimina o marca como eliminada una factura dentro de una empresa. + * Elimina o marca como eliminada una proforma dentro de una empresa. * * @param companyId - ID de la empresa. - * @param id - UUID de la factura a eliminar. + * @param id - UUID de la proforma a eliminar. * @param transaction - Transacción activa para la operación. * @returns Result */ - deleteByIdInCompany( + deleteProformaByIdInCompany( companyId: UniqueID, id: UniqueID, transaction: unknown diff --git a/modules/customer-invoices/src/api/domain/services/issue-customer-invoice-domain-service.ts b/modules/customer-invoices/src/api/domain/services/issue-customer-invoice-domain-service.ts index 0ce7f88c..7eaec3ea 100644 --- a/modules/customer-invoices/src/api/domain/services/issue-customer-invoice-domain-service.ts +++ b/modules/customer-invoices/src/api/domain/services/issue-customer-invoice-domain-service.ts @@ -1,12 +1,13 @@ -import { UtcDate } from "@repo/rdx-ddd"; +import type { UtcDate } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; + import { CustomerInvoice } from "../aggregates"; import { EntityIsNotProformaError, ProformaCannotBeConvertedToInvoiceError } from "../errors"; import { CustomerInvoiceIsProformaSpecification, ProformaCanTranstionToIssuedSpecification, } from "../specs"; -import { CustomerInvoiceNumber, CustomerInvoiceStatus } from "../value-objects"; +import { type CustomerInvoiceNumber, CustomerInvoiceStatus } from "../value-objects"; /** * Servicio de dominio que encapsula la lógica de emisión de factura definitiva desde una proforma. @@ -15,11 +16,6 @@ export class IssueCustomerInvoiceDomainService { private readonly isProformaSpec = new CustomerInvoiceIsProformaSpecification(); private readonly isApprovedSpec = new ProformaCanTranstionToIssuedSpecification(); - public linkWithProforma( - invoice: CustomerInvoice, - proforma: CustomerInvoice - ): Result {} - /** * Convierte una proforma en factura definitiva. * diff --git a/modules/customer-invoices/src/api/domain/services/proforma-customer-invoice-domain-service.ts b/modules/customer-invoices/src/api/domain/services/proforma-customer-invoice-domain-service.ts index 9eb0a25b..a92927ca 100644 --- a/modules/customer-invoices/src/api/domain/services/proforma-customer-invoice-domain-service.ts +++ b/modules/customer-invoices/src/api/domain/services/proforma-customer-invoice-domain-service.ts @@ -1,4 +1,5 @@ import { Result } from "@repo/rdx-utils"; + import { CustomerInvoice } from "../aggregates"; import { EntityIsNotProformaError, InvalidProformaTransitionError } from "../errors"; import { CustomerInvoiceIsProformaSpecification } from "../specs"; diff --git a/modules/customer-invoices/src/api/domain/specs/customer-invoice-is-proforma.specification.ts b/modules/customer-invoices/src/api/domain/specs/customer-invoice-is-proforma.specification.ts index 640854b5..3c169d57 100644 --- a/modules/customer-invoices/src/api/domain/specs/customer-invoice-is-proforma.specification.ts +++ b/modules/customer-invoices/src/api/domain/specs/customer-invoice-is-proforma.specification.ts @@ -1,5 +1,6 @@ import { CompositeSpecification } from "@repo/rdx-ddd"; -import { CustomerInvoice } from "../aggregates"; + +import type { CustomerInvoice } from "../aggregates"; export class CustomerInvoiceIsProformaSpecification extends CompositeSpecification { public async isSatisfiedBy(proforma: CustomerInvoice): Promise { diff --git a/modules/customer-invoices/src/api/domain/specs/index.ts b/modules/customer-invoices/src/api/domain/specs/index.ts index 59eb6146..550b9ec5 100644 --- a/modules/customer-invoices/src/api/domain/specs/index.ts +++ b/modules/customer-invoices/src/api/domain/specs/index.ts @@ -1,2 +1,3 @@ export * from "./customer-invoice-is-proforma.specification"; export * from "./proforma-can-transtion-to-issued.specification"; +export * from "./status-invoice-is-draft.specification"; diff --git a/modules/customer-invoices/src/api/domain/specs/status-invoice-is-draft.specification.ts b/modules/customer-invoices/src/api/domain/specs/status-invoice-is-draft.specification.ts new file mode 100644 index 00000000..f2701af8 --- /dev/null +++ b/modules/customer-invoices/src/api/domain/specs/status-invoice-is-draft.specification.ts @@ -0,0 +1,11 @@ +// domain/specifications/status-invoice-is-draft.specification.ts +import { CompositeSpecification } from "@repo/rdx-ddd"; + +import type { CustomerInvoice } from "../aggregates"; + +/** Verifica que el estado es borrador */ +export class StatusInvoiceIsDraftSpecification extends CompositeSpecification { + public async isSatisfiedBy(invoice: CustomerInvoice): Promise { + return invoice.status.isDraft(); + } +} diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index f11fdf3a..8dd35659 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -19,13 +19,14 @@ import { CustomerInvoiceReportPDFPresenter, CustomerInvoiceReportPresenter, CustomerInvoiceTaxesReportPresenter, + DeleteProformaUseCase, GetProformaUseCase, IssueProformaInvoiceUseCase, ListCustomerInvoicesPresenter, ListProformasUseCase, RecipientInvoiceFullPresenter, ReportProformaUseCase, - UpdateCustomerInvoiceUseCase, + UpdateProformaUseCase, } from "../application"; import { CustomerInvoiceDomainMapper, CustomerInvoiceListMapper } from "./mappers"; @@ -42,14 +43,14 @@ export type CustomerInvoiceDeps = { taxes: JsonTaxCatalogProvider; }; useCases: { - list: () => ListProformasUseCase; - get: () => GetProformaUseCase; - create: () => CreateProformaUseCase; - update: () => UpdateCustomerInvoiceUseCase; - //delete: () => DeleteCustomerInvoiceUseCase; - report: () => ReportProformaUseCase; - issue: () => IssueProformaInvoiceUseCase; - changeStatus: () => ChangeStatusProformaUseCase; + list_proformas: () => ListProformasUseCase; + get_proforma: () => GetProformaUseCase; + create_proforma: () => CreateProformaUseCase; + update_proforma: () => UpdateProformaUseCase; + delete_proforma: () => DeleteProformaUseCase; + report_proforma: () => ReportProformaUseCase; + issue_proforma: () => IssueProformaInvoiceUseCase; + changeStatus_proforma: () => ChangeStatusProformaUseCase; }; }; @@ -123,16 +124,20 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer }, ]); - const useCases = { - list: () => new ListProformasUseCase(appService, transactionManager, presenterRegistry), - get: () => new GetProformaUseCase(appService, transactionManager, presenterRegistry), - create: () => + const useCases: CustomerInvoiceDeps["useCases"] = { + list_proformas: () => + new ListProformasUseCase(appService, transactionManager, presenterRegistry), + get_proforma: () => new GetProformaUseCase(appService, transactionManager, presenterRegistry), + create_proforma: () => new CreateProformaUseCase(appService, transactionManager, presenterRegistry, catalogs.taxes), - update: () => - new UpdateCustomerInvoiceUseCase(appService, transactionManager, presenterRegistry), - report: () => new ReportProformaUseCase(appService, transactionManager, presenterRegistry), - issue: () => new IssueProformaInvoiceUseCase(appService, transactionManager, presenterRegistry), - changeStatus: () => new ChangeStatusProformaUseCase(appService, transactionManager), + update_proforma: () => + new UpdateProformaUseCase(appService, transactionManager, presenterRegistry), + delete_proforma: () => new DeleteProformaUseCase(appService, transactionManager), + report_proforma: () => + new ReportProformaUseCase(appService, transactionManager, presenterRegistry), + issue_proforma: () => + new IssueProformaInvoiceUseCase(appService, transactionManager, presenterRegistry), + changeStatus_proforma: () => new ChangeStatusProformaUseCase(appService, transactionManager), }; return { diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/delete-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/delete-proforma.controller.ts index 5f345906..f92f3a0c 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/delete-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/delete-proforma.controller.ts @@ -4,10 +4,7 @@ import type { DeleteProformaUseCase } from "../../../../application"; import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper"; export class DeleteProformaController extends ExpressController { - public constructor( - private readonly useCase: DeleteProformaUseCase - /* private readonly presenter: any */ - ) { + public constructor(private readonly useCase: DeleteProformaUseCase) { super(); this.errorMapper = customerInvoicesApiErrorMapper; @@ -22,6 +19,9 @@ export class DeleteProformaController extends ExpressController { } const { proforma_id } = this.req.params; + if (!proforma_id) { + return this.invalidInputError("Proforma ID missing"); + } const result = await this.useCase.execute({ proforma_id, companyId }); diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/update-proforma.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/update-proforma.controller.ts index 33985b29..eb25ea71 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/update-proforma.controller.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/proformas/update-proforma.controller.ts @@ -1,11 +1,11 @@ import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto"; -import type { UpdateCustomerInvoiceUseCase } from "../../../../application"; +import type { UpdateProformaUseCase } from "../../../../application"; import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper"; export class UpdateProformaController extends ExpressController { - public constructor(private readonly useCase: UpdateCustomerInvoiceUseCase) { + public constructor(private readonly useCase: UpdateProformaUseCase) { super(); this.errorMapper = customerInvoicesApiErrorMapper; diff --git a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts index 721175fd..718c1aa6 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices-api-error-mapper.ts @@ -15,6 +15,7 @@ import { isCustomerInvoiceIdAlreadyExistsError, isEntityIsNotProformaError, isProformaCannotBeConvertedToInvoiceError, + isProformaCannotBeDeletedError, } from "../../domain"; // Crea una regla específica (prioridad alta para sobreescribir mensajes) @@ -47,8 +48,18 @@ const proformaConversionRule: ErrorToApiRule = { ), }; +const proformaCannotBeDeletedRule: ErrorToApiRule = { + priority: 120, + matches: (e) => isProformaCannotBeDeletedError(e), + build: (e) => + new ValidationApiError( + (e as ProformaCannotBeConvertedToInvoiceError).message || "Proforma cannot be deleted." + ), +}; + // Cómo aplicarla: crea una nueva instancia del mapper con la regla extra export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default() .register(invoiceDuplicateRule) .register(entityIsNotProformaError) - .register(proformaConversionRule); + .register(proformaConversionRule) + .register(proformaCannotBeDeletedRule); diff --git a/modules/customer-invoices/src/api/infrastructure/express/proformas.routes.ts b/modules/customer-invoices/src/api/infrastructure/express/proformas.routes.ts index 9a22d31f..add75c8f 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/proformas.routes.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/proformas.routes.ts @@ -4,7 +4,9 @@ import { ChangeStatusProformaByIdParamsRequestSchema, ChangeStatusProformaByIdRequestSchema, CreateProformaRequestSchema, + DeleteProformaByIdParamsRequestSchema, GetProformaByIdRequestSchema, + IssueProformaByIdParamsRequestSchema, ListProformasRequestSchema, ReportProformaByIdRequestSchema, UpdateProformaByIdParamsRequestSchema, @@ -19,12 +21,13 @@ import { buildCustomerInvoiceDependencies } from "../dependencies"; import { ChangeStatusProformaController, CreateProformaController, + DeleteProformaController, GetProformaController, + IssueProformaController, ListProformasController, ReportProformaController, UpdateProformaController, } from "./controllers/proformas"; -import { IssueProformaController } from "./controllers/proformas/issue-proforma.controller"; export const proformasRouter = (params: ModuleParams) => { const { app, baseRoutePath, logger } = params as { @@ -60,7 +63,7 @@ export const proformasRouter = (params: ModuleParams) => { //checkTabContext, validateRequest(ListProformasRequestSchema, "params"), async (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.list(); + const useCase = deps.useCases.list_proformas(); const controller = new ListProformasController(useCase /*, deps.presenters.list */); return controller.execute(req, res, next); } @@ -71,7 +74,7 @@ export const proformasRouter = (params: ModuleParams) => { //checkTabContext, validateRequest(GetProformaByIdRequestSchema, "params"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.get(); + const useCase = deps.useCases.get_proforma(); const controller = new GetProformaController(useCase); return controller.execute(req, res, next); } @@ -83,7 +86,7 @@ export const proformasRouter = (params: ModuleParams) => { validateRequest(CreateProformaRequestSchema, "body"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.create(); + const useCase = deps.useCases.create_proforma(); const controller = new CreateProformaController(useCase); return controller.execute(req, res, next); } @@ -96,30 +99,30 @@ export const proformasRouter = (params: ModuleParams) => { validateRequest(UpdateProformaByIdParamsRequestSchema, "params"), validateRequest(UpdateProformaByIdRequestSchema, "body"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.update(); + const useCase = deps.useCases.update_proforma(); const controller = new UpdateProformaController(useCase); return controller.execute(req, res, next); } ); - /*router.delete( + router.delete( "/:proforma_id", //checkTabContext, - validateRequest(DeleteCustomerInvoiceByIdRequestSchema, "params"), + validateRequest(DeleteProformaByIdParamsRequestSchema, "params"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.delete(); - const controller = new DeleteCustomerInvoiceController(useCase); + const useCase = deps.useCases.delete_proforma(); + const controller = new DeleteProformaController(useCase); return controller.execute(req, res, next); } - );*/ + ); router.get( "/:proforma_id/report", //checkTabContext, validateRequest(ReportProformaByIdRequestSchema, "params"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.report(); + const useCase = deps.useCases.report_proforma(); const controller = new ReportProformaController(useCase); return controller.execute(req, res, next); } @@ -133,7 +136,7 @@ export const proformasRouter = (params: ModuleParams) => { validateRequest(ChangeStatusProformaByIdRequestSchema, "body"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.changeStatus(); + const useCase = deps.useCases.changeStatus_proforma(); const controller = new ChangeStatusProformaController(useCase); return controller.execute(req, res, next); } @@ -143,11 +146,10 @@ export const proformasRouter = (params: ModuleParams) => { "/:proforma_id/issue", //checkTabContext, - /*validateRequest(XXX, "params"), - validateRequest(XXX, "body"),*/ + validateRequest(IssueProformaByIdParamsRequestSchema, "params"), (req: Request, res: Response, next: NextFunction) => { - const useCase = deps.useCases.issue(); + const useCase = deps.useCases.issue_proforma(); const controller = new IssueProformaController(useCase); return controller.execute(req, res, next); } diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts index f3f1776e..9c4421ab 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/customer-invoice.repository.ts @@ -166,16 +166,23 @@ export class CustomerInvoiceRepository * @param companyId - Identificador UUID de la empresa a la que pertenece la factura. * @param id - Identificador UUID de la factura. * @param transaction - Transacción activa para la operación. + * @param options - Opciones adicionales para la consulta (Sequelize FindOptions) * @returns Result */ async existsByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: Transaction + transaction: Transaction, + options: FindOptions> = {} ): Promise> { try { const count = await CustomerInvoiceModel.count({ - where: { id: id.toString(), company_id: companyId.toString() }, + ...options, + where: { + id: id.toString(), + company_id: companyId.toString(), + ...(options.where ?? {}), + }, transaction, }); return Result.ok(Boolean(count > 0)); @@ -191,7 +198,7 @@ export class CustomerInvoiceRepository * @param companyId - Identificador UUID de la empresa a la que pertenece la factura. * @param id - UUID de la factura. * @param transaction - Transacción activa para la operación. - * @params options - Opciones adicionales para la consulta (Sequelize FindOptions) + * @param options - Opciones adicionales para la consulta (Sequelize FindOptions) * @returns Result */ async getByIdInCompany( @@ -367,21 +374,25 @@ export class CustomerInvoiceRepository /** * - * Elimina o marca como eliminada una factura. + * Elimina o marca como eliminada una proforma dentro de una empresa. * - * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente. - * @param id - UUID de la factura a eliminar. + * @param companyId - ID de la empresa. + * @param id - UUID de la proforma a eliminar. * @param transaction - Transacción activa para la operación. - * @returns Result + * @returns Result */ - async deleteByIdInCompany( + async deleteProformaByIdInCompany( companyId: UniqueID, id: UniqueID, transaction: Transaction ): Promise> { try { const deleted = await CustomerInvoiceModel.destroy({ - where: { id: id.toString(), company_id: companyId.toString() }, + where: { + id: id.toString(), + company_id: companyId.toString(), + is_proforma: true, + }, transaction, }); @@ -424,8 +435,6 @@ export class CustomerInvoiceRepository } ); - console.log(affected); - if (affected === 0) { return Result.fail( new InfrastructureRepositoryError( diff --git a/modules/customer-invoices/src/common/dto/request/proformas/delete-proforma-by-id.request.dto.ts b/modules/customer-invoices/src/common/dto/request/proformas/delete-proforma-by-id.request.dto.ts index eba27fec..445f1fa6 100644 --- a/modules/customer-invoices/src/common/dto/request/proformas/delete-proforma-by-id.request.dto.ts +++ b/modules/customer-invoices/src/common/dto/request/proformas/delete-proforma-by-id.request.dto.ts @@ -6,8 +6,8 @@ import { z } from "zod/v4"; * */ -export const DeleteProformaByIdRequestSchema = z.object({ - id: z.string(), +export const DeleteProformaByIdParamsRequestSchema = z.object({ + proforma_id: z.string(), }); -export type DeleteProformaByIdRequestDTO = z.infer; +export type DeleteProformaByIdRequestDTO = z.infer; diff --git a/modules/customer-invoices/src/common/dto/request/proformas/index.ts b/modules/customer-invoices/src/common/dto/request/proformas/index.ts index 626ccdf1..d39b3dda 100644 --- a/modules/customer-invoices/src/common/dto/request/proformas/index.ts +++ b/modules/customer-invoices/src/common/dto/request/proformas/index.ts @@ -2,6 +2,7 @@ export * from "./change-status-proforma-by-id.request.dto"; export * from "./create-proforma.request.dto"; export * from "./delete-proforma-by-id.request.dto"; export * from "./get-proforma-by-id.request.dto"; +export * from "./issue-proforma-by-id.request.dto"; export * from "./list-proformas.request.dto"; export * from "./report-proforma-by-id.request.dto"; export * from "./update-proforma-by-id.request.dto"; diff --git a/modules/customer-invoices/src/common/dto/request/proformas/issue-proforma-by-id.request.dto.ts b/modules/customer-invoices/src/common/dto/request/proformas/issue-proforma-by-id.request.dto.ts new file mode 100644 index 00000000..fb754316 --- /dev/null +++ b/modules/customer-invoices/src/common/dto/request/proformas/issue-proforma-by-id.request.dto.ts @@ -0,0 +1,5 @@ +import { z } from "zod/v4"; + +export const IssueProformaByIdParamsRequestSchema = z.object({ + proforma_id: z.string(), +});