From 6e5b14305e0077f7afe5120a540fdecd30bbe965 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 3 Oct 2025 21:01:38 +0200 Subject: [PATCH] Clientes y facturas de cliente --- .../database/transaction-manager.ts | 4 +- .../src/api/application/use-cases/index.ts | 4 +- .../update-customer-invoice.use-case.ts | 401 ------------------ .../api/application/use-cases/update/index.ts | 1 + ...ap-dto-to-update-customer-invoice-props.ts | 133 ++++++ .../update-customer-invoice.use-case.ts | 71 ++++ .../api/domain/aggregates/customer-invoice.ts | 26 +- .../customer-invoice-repository.interface.ts | 24 +- .../services/customer-invoice.service.ts | 38 +- .../src/api/infrastructure/dependencies.ts | 6 +- .../express/controllers/index.ts | 4 +- .../update-customer-invoice.controller.ts | 27 ++ .../update-invoice.controller.ts.bak | 72 ---- .../express/customer-invoices.routes.ts | 8 +- .../mappers/domain/customer-invoice.mapper.ts | 6 +- .../domain/invoice-recipient.mapper.ts | 20 +- .../sequelize/customer-invoice.repository.ts | 71 +++- .../models/customer-invoice.model.ts | 17 + ...date-customer-invoice-by-id.request.dto.ts | 17 +- ...get-customer-invoice-by-id.response.dto.ts | 1 + .../recipient-modal-selector-field.tsx | 2 + .../use-update-customer-invoice-mutation.ts | 5 + .../update/customer-invoices-update-page.tsx | 2 + .../schemas/customer-invoices.form.schema.ts | 5 +- .../specs/customer-not-exists.spec.ts | 2 +- .../update/update-customer.use-case.ts | 3 +- .../customer-repository.interface.ts | 10 +- .../api/domain/services/customer.service.ts | 12 +- .../customer-modal-selector.tsx | 4 + .../doc-number-repository.interface.ts | 4 +- .../repositories/doc-number.repository.ts | 2 +- .../verifactu-repository.interface.ts | 7 +- .../services/verifactu-record.service.ts | 2 +- .../sequelize/verifactu-record.repository.ts | 4 +- .../{{kebabCase name}}-not-exists.spec.ts | 28 +- .../{{kebabCase name}}-unique-name.spec.ts | 28 +- ...{{kebabCase name}}-repository.interface.ts | 37 +- 37 files changed, 529 insertions(+), 579 deletions(-) delete mode 100644 modules/customer-invoices/src/api/application/use-cases/update-customer-invoice.use-case.ts create mode 100644 modules/customer-invoices/src/api/application/use-cases/update/index.ts create mode 100644 modules/customer-invoices/src/api/application/use-cases/update/map-dto-to-update-customer-invoice-props.ts create mode 100644 modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts create mode 100644 modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts delete mode 100644 modules/customer-invoices/src/api/infrastructure/express/controllers/update-invoice.controller.ts.bak diff --git a/modules/core/src/api/infrastructure/database/transaction-manager.ts b/modules/core/src/api/infrastructure/database/transaction-manager.ts index 1fb108da..98b85d49 100644 --- a/modules/core/src/api/infrastructure/database/transaction-manager.ts +++ b/modules/core/src/api/infrastructure/database/transaction-manager.ts @@ -3,7 +3,7 @@ import { ILogger } from "../../logger"; import { ITransactionManager } from "./transaction-manager.interface"; export abstract class TransactionManager implements ITransactionManager { - protected _transaction: any | null = null; + protected _transaction: unknown | null = null; protected _isCompleted = false; protected readonly logger!: ILogger; @@ -55,7 +55,7 @@ export abstract class TransactionManager implements ITransactionManager { /** * 🔹 Ejecuta una función dentro de una transacción */ - async complete(work: (transaction: any) => Promise): Promise { + async complete(work: (transaction: unknown) => Promise): Promise { if (this._transaction) { this.logger.error( "❌ Cannot start a new transaction inside another. Nested transactions are not allowed.", diff --git a/modules/customer-invoices/src/api/application/use-cases/index.ts b/modules/customer-invoices/src/api/application/use-cases/index.ts index 51261c63..95d3a470 100644 --- a/modules/customer-invoices/src/api/application/use-cases/index.ts +++ b/modules/customer-invoices/src/api/application/use-cases/index.ts @@ -1,6 +1,6 @@ export * from "./create"; export * from "./get-customer-invoice.use-case"; +export * from "./issue-customer-invoice.use-case"; export * from "./list-customer-invoices.use-case"; export * from "./report"; -//export * from "./update-customer-invoice.use-case"; -export * from "./issue-customer-invoice.use-case"; +export * from "./update"; diff --git a/modules/customer-invoices/src/api/application/use-cases/update-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/update-customer-invoice.use-case.ts deleted file mode 100644 index 9571481c..00000000 --- a/modules/customer-invoices/src/api/application/use-cases/update-customer-invoice.use-case.ts +++ /dev/null @@ -1,401 +0,0 @@ -import { UniqueID } from "@/core/common/domain"; -import { ITransactionManager } from "@/core/common/infrastructure/database"; -import { Result } from "@repo/rdx-utils"; -import { IUpdateCustomerInvoiceRequestDTO } from "../../common/dto"; -import { CustomerInvoice, ICustomerInvoiceService } from "../domain"; - -export class CreateCustomerInvoiceUseCase { - constructor( - private readonly customerInvoiceService: ICustomerInvoiceService, - private readonly transactionManager: ITransactionManager - ) {} - - public execute( - customerInvoiceID: UniqueID, - dto: Partial - ): Promise> { - return this.transactionManager.complete(async (transaction) => { - return Result.fail(new Error("No implementado")); - /* - try { - const validOrErrors = this.validateCustomerInvoiceData(dto); - if (validOrErrors.isFailure) { - return Result.fail(validOrErrors.error); - } - - const data = validOrErrors.data; - - // Update customerInvoice with dto - return await this.customerInvoiceService.updateCustomerInvoiceById(customerInvoiceID, data, transaction); - } catch (error: unknown) { - logger.error(error as Error); - return Result.fail(error as Error); - } - */ - }); - } - - /* private validateCustomerInvoiceData( - dto: Partial - ): Result, Error> { - const errors: Error[] = []; - const validatedData: Partial = {}; - - // Create customerInvoice - let customerInvoice_status = CustomerInvoiceStatus.create(customerInvoiceDTO.status).object; - if (customerInvoice_status.isEmpty()) { - customerInvoice_status = CustomerInvoiceStatus.createDraft(); - } - - let customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object; - if (customerInvoice_series.isEmpty()) { - customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object; - } - - let invoice_date = CustomerInvoiceDate.create(customerInvoiceDTO.invoice_date).object; - if (invoice_date.isEmpty()) { - invoice_date = CustomerInvoiceDate.createCurrentDate().object; - } - - let operation_date = CustomerInvoiceDate.create(customerInvoiceDTO.operation_date).object; - if (operation_date.isEmpty()) { - operation_date = CustomerInvoiceDate.createCurrentDate().object; - } - - let customerInvoiceCurrency = Currency.createFromCode(customerInvoiceDTO.currency).object; - - if (customerInvoiceCurrency.isEmpty()) { - customerInvoiceCurrency = Currency.createDefaultCode().object; - } - - let customerInvoiceLanguage = Language.createFromCode(customerInvoiceDTO.language_code).object; - - if (customerInvoiceLanguage.isEmpty()) { - customerInvoiceLanguage = Language.createDefaultCode().object; - } - - const items = new Collection( - customerInvoiceDTO.items?.map( - (item) => - CustomerInvoiceSimpleItem.create({ - description: Description.create(item.description).object, - quantity: Quantity.create(item.quantity).object, - unitPrice: UnitPrice.create({ - amount: item.unit_price.amount, - currencyCode: item.unit_price.currency, - precision: item.unit_price.precision, - }).object, - }).object - ) - ); - - if (!customerInvoice_status.isDraft()) { - throw Error("Error al crear una factura que no es borrador"); - } - - return DraftCustomerInvoice.create( - { - customerInvoiceSeries: customerInvoice_series, - invoiceDate: invoice_date, - operationDate: operation_date, - customerInvoiceCurrency, - language: customerInvoiceLanguage, - customerInvoiceNumber: CustomerInvoiceNumber.create(undefined).object, - //notes: Note.create(customerInvoiceDTO.notes).object, - - //senderId: UniqueID.create(null).object, - recipient, - - items, - }, - customerInvoiceId - ); - } */ -} - -/* export type UpdateCustomerInvoiceResponseOrError = - | Result // Misc errors (value objects) - | Result; // Success! - -export class UpdateCustomerInvoiceUseCase2 - implements - IUseCase<{ id: UniqueID; data: IUpdateCustomerInvoice_DTO }, Promise> -{ - private _context: IInvoicingContext; - private _adapter: ISequelizeAdapter; - private _repositoryManager: IRepositoryManager; - - constructor(context: IInvoicingContext) { - this._context = context; - this._adapter = context.adapter; - this._repositoryManager = context.repositoryManager; - } - - private getRepository(name: string) { - return this._repositoryManager.getRepository(name); - } - - private handleValidationFailure( - validationError: Error, - message?: string - ): Result { - return Result.fail( - UseCaseError.create( - UseCaseError.INVALID_INPUT_DATA, - message ? message : validationError.message, - validationError - ) - ); - } - - async execute(request: { - id: UniqueID; - data: IUpdateCustomerInvoice_DTO; - }): Promise { - const { id, data: customerInvoiceDTO } = request; - - // Validaciones - const customerInvoiceDTOOrError = ensureUpdateCustomerInvoice_DTOIsValid(customerInvoiceDTO); - if (customerInvoiceDTOOrError.isFailure) { - return this.handleValidationFailure(customerInvoiceDTOOrError.error); - } - - const transaction = this._adapter.startTransaction(); - - const customerInvoiceRepoBuilder = this.getRepository("CustomerInvoice"); - - let customerInvoice: CustomerInvoice | null = null; - - try { - await transaction.complete(async (t) => { - customerInvoice = await customerInvoiceRepoBuilder({ transaction: t }).getById(id); - }); - - if (customerInvoice === null) { - return Result.fail( - UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `CustomerInvoice not found`, { - id: request.id.toString(), - entity: "customerInvoice", - }) - ); - } - - return Result.ok(customerInvoice); - } catch (error: unknown) { - const _error = error as Error; - if (customerInvoiceRepoBuilder().isRepositoryError(_error)) { - return this.handleRepositoryError(error as BaseError, customerInvoiceRepoBuilder()); - } else { - return this.handleUnexceptedError(error); - } - } - - // Recipient validations - const recipientIdOrError = ensureParticipantIdIsValid( - customerInvoiceDTO?.recipient?.id, - ); - if (recipientIdOrError.isFailure) { - return this.handleValidationFailure( - recipientIdOrError.error, - "Recipient ID not valid", - ); - } - const recipientId = recipientIdOrError.object; - - const recipientBillingIdOrError = ensureParticipantAddressIdIsValid( - customerInvoiceDTO?.recipient?.billing_address_id, - ); - if (recipientBillingIdOrError.isFailure) { - return this.handleValidationFailure( - recipientBillingIdOrError.error, - "Recipient billing address ID not valid", - ); - } - const recipientBillingId = recipientBillingIdOrError.object; - - const recipientShippingIdOrError = ensureParticipantAddressIdIsValid( - customerInvoiceDTO?.recipient?.shipping_address_id, - ); - if (recipientShippingIdOrError.isFailure) { - return this.handleValidationFailure( - recipientShippingIdOrError.error, - "Recipient shipping address ID not valid", - ); - } - const recipientShippingId = recipientShippingIdOrError.object; - - const recipientContact = await this.findContact( - recipientId, - recipientBillingId, - recipientShippingId, - ); - - if (!recipientContact) { - return this.handleValidationFailure( - new Error(`Recipient with ID ${recipientId.toString()} does not exist`), - ); - } - - // Crear customerInvoice - const customerInvoiceOrError = await this.tryUpdateCustomerInvoiceInstance( - customerInvoiceDTO, - customerInvoiceIdOrError.object, - //senderId, - //senderBillingId, - //senderShippingId, - recipientContact, - ); - - if (customerInvoiceOrError.isFailure) { - const { error: domainError } = customerInvoiceOrError; - let errorCode = ""; - let message = ""; - - switch (domainError.code) { - case CustomerInvoice.ERROR_CUSTOMER_WITHOUT_NAME: - errorCode = UseCaseError.INVALID_INPUT_DATA; - message = - "El cliente debe ser una compañía o tener nombre y apellidos."; - break; - - default: - errorCode = UseCaseError.UNEXCEPTED_ERROR; - message = ""; - break; - } - - return Result.fail( - UseCaseError.create(errorCode, message, domainError), - ); - } - - return this.saveCustomerInvoice(customerInvoiceOrError.object); - - } - - private async tryUpdateCustomerInvoiceInstance(customerInvoiceDTO, customerInvoiceId, recipient) { - // Create customerInvoice - let customerInvoice_status = CustomerInvoiceStatus.create(customerInvoiceDTO.status).object; - if (customerInvoice_status.isEmpty()) { - customerInvoice_status = CustomerInvoiceStatus.createDraft(); - } - - let customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object; - if (customerInvoice_series.isEmpty()) { - customerInvoice_series = CustomerInvoiceSeries.create(customerInvoiceDTO.customerInvoice_series).object; - } - - let invoice_date = CustomerInvoiceDate.create(customerInvoiceDTO.invoice_date).object; - if (invoice_date.isEmpty()) { - invoice_date = CustomerInvoiceDate.createCurrentDate().object; - } - - let operation_date = CustomerInvoiceDate.create(customerInvoiceDTO.operation_date).object; - if (operation_date.isEmpty()) { - operation_date = CustomerInvoiceDate.createCurrentDate().object; - } - - let customerInvoiceCurrency = Currency.createFromCode(customerInvoiceDTO.currency).object; - - if (customerInvoiceCurrency.isEmpty()) { - customerInvoiceCurrency = Currency.createDefaultCode().object; - } - - let customerInvoiceLanguage = Language.createFromCode(customerInvoiceDTO.language_code).object; - - if (customerInvoiceLanguage.isEmpty()) { - customerInvoiceLanguage = Language.createDefaultCode().object; - } - - const items = new Collection( - customerInvoiceDTO.items?.map( - (item) => - CustomerInvoiceSimpleItem.create({ - description: Description.create(item.description).object, - quantity: Quantity.create(item.quantity).object, - unitPrice: UnitPrice.create({ - amount: item.unit_price.amount, - currencyCode: item.unit_price.currency, - precision: item.unit_price.precision, - }).object, - }).object - ) - ); - - if (!customerInvoice_status.isDraft()) { - throw Error("Error al crear una factura que no es borrador"); - } - - return DraftCustomerInvoice.create( - { - customerInvoiceSeries: customerInvoice_series, - invoiceDate: invoice_date, - operationDate: operation_date, - customerInvoiceCurrency, - language: customerInvoiceLanguage, - customerInvoiceNumber: CustomerInvoiceNumber.create(undefined).object, - //notes: Note.create(customerInvoiceDTO.notes).object, - - //senderId: UniqueID.create(null).object, - recipient, - - items, - }, - customerInvoiceId - ); - } - - private async findContact( - contactId: UniqueID, - billingAddressId: UniqueID, - shippingAddressId: UniqueID - ) { - const contactRepoBuilder = this.getRepository("Contact"); - - const contact = await contactRepoBuilder().getById2( - contactId, - billingAddressId, - shippingAddressId - ); - - return contact; - } - - private async saveCustomerInvoice(customerInvoice: DraftCustomerInvoice) { - const transaction = this._adapter.startTransaction(); - const customerInvoiceRepoBuilder = this.getRepository("CustomerInvoice"); - - try { - await transaction.complete(async (t) => { - const customerInvoiceRepo = customerInvoiceRepoBuilder({ transaction: t }); - await customerInvoiceRepo.save(customerInvoice); - }); - - return Result.ok(customerInvoice); - } catch (error: unknown) { - const _error = error as Error; - if (customerInvoiceRepoBuilder().isRepositoryError(_error)) { - return this.handleRepositoryError(error as BaseError, customerInvoiceRepoBuilder()); - } else { - return this.handleUnexceptedError(error); - } - } - } - - private handleUnexceptedError(error): Result { - return Result.fail( - UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error) - ); - } - - private handleRepositoryError( - error: BaseError, - repository: ICustomerInvoiceRepository - ): Result { - const { message, details } = repository.handleRepositoryError(error); - return Result.fail( - UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details) - ); - } -} - */ diff --git a/modules/customer-invoices/src/api/application/use-cases/update/index.ts b/modules/customer-invoices/src/api/application/use-cases/update/index.ts new file mode 100644 index 00000000..002aceac --- /dev/null +++ b/modules/customer-invoices/src/api/application/use-cases/update/index.ts @@ -0,0 +1 @@ +export * from "./update-customer-invoice.use-case"; diff --git a/modules/customer-invoices/src/api/application/use-cases/update/map-dto-to-update-customer-invoice-props.ts b/modules/customer-invoices/src/api/application/use-cases/update/map-dto-to-update-customer-invoice-props.ts new file mode 100644 index 00000000..551da2a1 --- /dev/null +++ b/modules/customer-invoices/src/api/application/use-cases/update/map-dto-to-update-customer-invoice-props.ts @@ -0,0 +1,133 @@ +import { + CurrencyCode, + DomainError, + LanguageCode, + TextValue, + UniqueID, + UtcDate, + ValidationErrorCollection, + ValidationErrorDetail, + extractOrPushError, + maybeFromNullableVO, +} from "@repo/rdx-ddd"; +import { Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils"; + +import { UpdateCustomerInvoiceByIdRequestDTO } from "../../../../common/dto"; +import { CustomerInvoicePatchProps, CustomerInvoiceSerie } from "../../../domain"; + +/** + * mapDTOToUpdateCustomerInvoicePatchProps + * Convierte el DTO a las props validadas (CustomerInvoiceProps). + * No construye directamente el agregado. + * Tri-estado: + * - campo omitido → no se cambia + * - campo con valor null/"" → se quita el valor -> set(None()), + * - campo con valor no-vacío → se pone el nuevo valor -> set(Some(VO)). + * + * @param dto - DTO con los datos a cambiar en la factura de cliente + * @returns Cambios en las propiedades de la factura de cliente + * + */ + +export function mapDTOToUpdateCustomerInvoicePatchProps(dto: UpdateCustomerInvoiceByIdRequestDTO) { + try { + const errors: ValidationErrorDetail[] = []; + const props: CustomerInvoicePatchProps = {}; + + toPatchField(dto.series).ifSet((series) => { + props.series = extractOrPushError( + maybeFromNullableVO(series, (value) => CustomerInvoiceSerie.create(value)), + "reference", + errors + ); + }); + + toPatchField(dto.invoice_date).ifSet((invoice_date) => { + if (isNullishOrEmpty(invoice_date)) { + errors.push({ path: "invoice_date", message: "Invoice date cannot be empty" }); + return; + } + props.invoiceDate = extractOrPushError( + UtcDate.createFromISO(invoice_date!), + "invoice_date", + errors + ); + }); + + toPatchField(dto.operation_date).ifSet((operation_date) => { + props.operationDate = extractOrPushError( + maybeFromNullableVO(operation_date, (value) => UtcDate.createFromISO(value)), + "operation_date", + errors + ); + }); + + toPatchField(dto.customer_id).ifSet((customer_id) => { + if (isNullishOrEmpty(customer_id)) { + errors.push({ path: "customer_id", message: "Customer cannot be empty" }); + return; + } + props.customerId = extractOrPushError(UniqueID.create(customer_id!), "customer_id", errors); + }); + + toPatchField(dto.reference).ifSet((reference) => { + props.reference = extractOrPushError( + maybeFromNullableVO(reference, (value) => Result.ok(String(value))), + "reference", + errors + ); + }); + + toPatchField(dto.description).ifSet((description) => { + props.description = extractOrPushError( + maybeFromNullableVO(description, (value) => Result.ok(String(value))), + "description", + errors + ); + }); + + toPatchField(dto.notes).ifSet((notes) => { + props.notes = extractOrPushError( + maybeFromNullableVO(notes, (value) => TextValue.create(value)), + "notes", + errors + ); + }); + + toPatchField(dto.language_code).ifSet((languageCode) => { + if (isNullishOrEmpty(languageCode)) { + errors.push({ path: "language_code", message: "Language code cannot be empty" }); + return; + } + + props.languageCode = extractOrPushError( + LanguageCode.create(languageCode!), + "language_code", + errors + ); + }); + + toPatchField(dto.currency_code).ifSet((currencyCode) => { + if (isNullishOrEmpty(currencyCode)) { + errors.push({ path: "currency_code", message: "Currency code cannot be empty" }); + return; + } + + props.currencyCode = extractOrPushError( + CurrencyCode.create(currencyCode!), + "currency_code", + errors + ); + }); + + if (errors.length > 0) { + return Result.fail( + new ValidationErrorCollection("Customer invoice props mapping failed (update)", errors) + ); + } + + return Result.ok(props); + } catch (err: unknown) { + return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err })); + } +} diff --git a/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts b/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts new file mode 100644 index 00000000..78ddd793 --- /dev/null +++ b/modules/customer-invoices/src/api/application/use-cases/update/update-customer-invoice.use-case.ts @@ -0,0 +1,71 @@ +import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; +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 { CustomerInvoiceFullPresenter } from "../../presenters"; +import { mapDTOToUpdateCustomerInvoicePatchProps } from "./map-dto-to-update-customer-invoice-props"; + +type UpdateCustomerInvoiceUseCaseInput = { + companyId: UniqueID; + invoice_id: string; + dto: UpdateCustomerInvoiceByIdRequestDTO; +}; + +export class UpdateCustomerInvoiceUseCase { + constructor( + private readonly service: CustomerInvoiceService, + private readonly transactionManager: ITransactionManager, + private readonly presenterRegistry: IPresenterRegistry + ) {} + + public execute(params: UpdateCustomerInvoiceUseCaseInput) { + const { companyId, invoice_id, dto } = params; + + const idOrError = UniqueID.create(invoice_id); + if (idOrError.isFailure) { + return Result.fail(idOrError.error); + } + + const invoiceId = idOrError.data; + const presenter = this.presenterRegistry.getPresenter({ + resource: "customer-invoice", + projection: "FULL", + }) as CustomerInvoiceFullPresenter; + + // Mapear DTO → props de dominio + const patchPropsResult = mapDTOToUpdateCustomerInvoicePatchProps(dto); + if (patchPropsResult.isFailure) { + return Result.fail(patchPropsResult.error); + } + + const patchProps: CustomerInvoicePatchProps = patchPropsResult.data; + + return this.transactionManager.complete(async (transaction: Transaction) => { + try { + const updatedInvoice = await this.service.updateInvoiceByIdInCompany( + companyId, + invoiceId, + patchProps, + transaction + ); + + if (updatedInvoice.isFailure) { + return Result.fail(updatedInvoice.error); + } + + const invoiceOrError = await this.service.updateInvoice( + companyId, + updatedInvoice.data, + transaction + ); + const invoice = invoiceOrError.data; + const dto = presenter.toOutput(invoice); + return Result.ok(dto); + } catch (error: unknown) { + return Result.fail(error as Error); + } + }); + } +} diff --git a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts index 75ba904b..e0f8d98b 100644 --- a/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts +++ b/modules/customer-invoices/src/api/domain/aggregates/customer-invoice.ts @@ -51,6 +51,12 @@ export interface CustomerInvoiceProps { verifactu_status: string;*/ } +export type CustomerInvoicePatchProps = Partial< + Omit +> & { + items?: CustomerInvoiceItems; +}; + export interface ICustomerInvoice { hasRecipient: boolean; hasPaymentMethod: boolean; @@ -65,8 +71,6 @@ export interface ICustomerInvoice { issueInvoice(newInvoiceNumber: CustomerInvoiceNumber): Result; } -export type CustomerInvoicePatchProps = Partial>; - export class CustomerInvoice extends AggregateRoot implements ICustomerInvoice @@ -106,7 +110,23 @@ export class CustomerInvoice } public update(partialInvoice: CustomerInvoicePatchProps): Result { - throw new Error("Not implemented"); + const { items, ...rest } = partialInvoice; + + const updatedProps = { + ...this.props, + ...rest, + } as CustomerInvoiceProps; + + /*if (partialAddress) { + const updatedAddressOrError = this.address.update(partialAddress); + if (updatedAddressOrError.isFailure) { + return Result.fail(updatedAddressOrError.error); + } + + updatedProps.address = updatedAddressOrError.data; + }*/ + + return CustomerInvoice.create(updatedProps, this.id); } public get companyId(): UniqueID { 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 a266bfbb..303928ab 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 @@ -11,14 +11,22 @@ import { CustomerInvoice } from "../aggregates"; export interface ICustomerInvoiceRepository { /** * - * Persiste una nueva factura o actualiza una existente. - * Retorna el objeto actualizado tras la operación. + * Crea una nueva factura. * * @param invoice - El agregado a guardar. * @param transaction - Transacción activa para la operación. - * @returns Result + * @returns Result */ - save(invoice: CustomerInvoice, transaction: any): Promise>; + create(invoice: CustomerInvoice, transaction?: unknown): Promise>; + + /** + * Actualiza una factura existente. + * + * @param invoice - El agregado a actualizar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + update(invoice: CustomerInvoice, transaction?: unknown): Promise>; /** * Comprueba si existe una factura con un `id` dentro de una `company`. @@ -26,7 +34,7 @@ export interface ICustomerInvoiceRepository { existsByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction?: unknown ): Promise>; /** @@ -36,7 +44,7 @@ export interface ICustomerInvoiceRepository { getByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction?: unknown ): Promise>; /** @@ -55,7 +63,7 @@ export interface ICustomerInvoiceRepository { findByCriteriaInCompany( companyId: UniqueID, criteria: Criteria, - transaction: any + transaction: unknown ): Promise, Error>>; /** @@ -70,6 +78,6 @@ export interface ICustomerInvoiceRepository { deleteByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction: any + transaction: unknown ): Promise>; } diff --git a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts b/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts index a0fa7535..499a6cba 100644 --- a/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts +++ b/modules/customer-invoices/src/api/domain/services/customer-invoice.service.ts @@ -26,17 +26,45 @@ export class CustomerInvoiceService { } /** - * Guarda una instancia de CustomerInvoice en persistencia. + * Guarda una nueva factura y devuelve la factura guardada. * + * @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 - El agregado guardado o un error si falla la operación. */ - async saveInvoice( + async createInvoice( + companyId: UniqueID, invoice: CustomerInvoice, - transaction: any + transaction: Transaction ): Promise> { - return this.repository.save(invoice, transaction); + const result = await this.repository.create(invoice, transaction); + if (result.isFailure) { + return Result.fail(result.error); + } + + return this.getInvoiceByIdInCompany(companyId, invoice.id, transaction); + } + + /** + * Actualiza una nueva factura 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 - El agregado guardado o un error si falla la operación. + */ + async updateInvoice( + companyId: UniqueID, + invoice: CustomerInvoice, + transaction: Transaction + ): Promise> { + const result = await this.repository.update(invoice, transaction); + if (result.isFailure) { + return Result.fail(result.error); + } + + return this.getInvoiceByIdInCompany(companyId, invoice.id, transaction); } /** @@ -52,7 +80,7 @@ export class CustomerInvoiceService { async existsByIdInCompany( companyId: UniqueID, invoiceId: UniqueID, - transaction?: any + transaction?: Transaction ): Promise> { return this.repository.existsByIdInCompany(companyId, invoiceId, transaction); } diff --git a/modules/customer-invoices/src/api/infrastructure/dependencies.ts b/modules/customer-invoices/src/api/infrastructure/dependencies.ts index 3ed0942a..7bd673c7 100644 --- a/modules/customer-invoices/src/api/infrastructure/dependencies.ts +++ b/modules/customer-invoices/src/api/infrastructure/dependencies.ts @@ -20,6 +20,7 @@ import { ListCustomerInvoicesUseCase, RecipientInvoiceFullPresenter, ReportCustomerInvoiceUseCase, + UpdateCustomerInvoiceUseCase, } from "../application"; import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core"; @@ -41,7 +42,7 @@ export type CustomerInvoiceDeps = { list: () => ListCustomerInvoicesUseCase; get: () => GetCustomerInvoiceUseCase; create: () => CreateCustomerInvoiceUseCase; - //update: () => UpdateCustomerInvoiceUseCase; + update: () => UpdateCustomerInvoiceUseCase; //delete: () => DeleteCustomerInvoiceUseCase; report: () => ReportCustomerInvoiceUseCase; }; @@ -154,7 +155,8 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer presenterRegistry, catalogs.taxes ), - // update: () => new UpdateCustomerInvoiceUseCase(service, transactionManager), + update: () => + new UpdateCustomerInvoiceUseCase(service, transactionManager, presenterRegistry), // delete: () => new DeleteCustomerInvoiceUseCase(service, transactionManager), report: () => new ReportCustomerInvoiceUseCase(service, transactionManager, presenterRegistry), diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/index.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/index.ts index e69f775f..52665bf1 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/index.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/index.ts @@ -1,7 +1,7 @@ export * from "./create-customer-invoice.controller"; export * from "./delete-customer-invoice.controller"; export * from "./get-customer-invoice.controller"; -export * from "./list-customer-invoices.controller"; -///export * from "./update-customer-invoice.controller"; export * from "./issue-customer-invoice.controller"; +export * from "./list-customer-invoices.controller"; export * from "./report-customer-invoice.controller"; +export * from "./update-customer-invoice.controller"; diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts b/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts new file mode 100644 index 00000000..913e9362 --- /dev/null +++ b/modules/customer-invoices/src/api/infrastructure/express/controllers/update-customer-invoice.controller.ts @@ -0,0 +1,27 @@ +import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; +import { UpdateCustomerInvoiceByIdRequestDTO } from "../../../../common/dto"; +import { UpdateCustomerInvoiceUseCase } from "../../../application"; + +export class UpdateCustomerInvoiceController extends ExpressController { + public constructor(private readonly useCase: UpdateCustomerInvoiceUseCase) { + super(); + // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query + this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); + } + + async executeImpl(): Promise { + const companyId = this.getTenantId(); + if (!companyId) { + return this.forbiddenError("Tenant ID not found"); + } + const { invoice_id } = this.req.params; + const dto = this.req.body as UpdateCustomerInvoiceByIdRequestDTO; + + const result = await this.useCase.execute({ invoice_id, companyId, dto }); + + return result.match( + (data) => this.ok(data), + (err) => this.handleError(err) + ); + } +} diff --git a/modules/customer-invoices/src/api/infrastructure/express/controllers/update-invoice.controller.ts.bak b/modules/customer-invoices/src/api/infrastructure/express/controllers/update-invoice.controller.ts.bak deleted file mode 100644 index c65e80c9..00000000 --- a/modules/customer-invoices/src/api/infrastructure/express/controllers/update-invoice.controller.ts.bak +++ /dev/null @@ -1,72 +0,0 @@ -import { IInvoicingContext } from "#/server/intrastructure"; -import { ExpressController } from "@rdx/core"; -import { IUpdateCustomerInvoicePresenter } from "./presenter"; - -export class UpdateCustomerInvoiceController extends ExpressController { - private useCase: UpdateCustomerInvoiceUseCase2; - private presenter: IUpdateCustomerInvoicePresenter; - private context: IInvoicingContext; - - constructor( - props: { - useCase: UpdateCustomerInvoiceUseCase; - presenter: IUpdateCustomerInvoicePresenter; - }, - context: IInvoicingContext - ) { - super(); - - const { useCase, presenter } = props; - this.useCase = useCase; - this.presenter = presenter; - this.context = context; - } - - async executeImpl(): Promise { - const { customerInvoiceId } = this.req.params; - const request: IUpdateCustomerInvoice_DTO = this.req.body; - - if (RuleValidator.validate(RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, customerInvoiceId).isFailure) { - return this.invalidInputError("CustomerInvoice Id param is required!"); - } - - const idOrError = UniqueID.create(customerInvoiceId); - if (idOrError.isFailure) { - return this.invalidInputError("Invalid customerInvoice Id param!"); - } - - try { - const result = await this.useCase.execute({ - id: idOrError.object, - data: request, - }); - - if (result.isFailure) { - const { error } = result; - - switch (error.code) { - case UseCaseError.NOT_FOUND_ERROR: - return this.notFoundError("CustomerInvoice not found", error); - - case UseCaseError.INVALID_INPUT_DATA: - return this.invalidInputError(error.message); - - case UseCaseError.UNEXCEPTED_ERROR: - return this.internalServerError(result.error.message, result.error); - - case UseCaseError.REPOSITORY_ERROR: - return this.conflictError(result.error, result.error.details); - - default: - return this.clientError(result.error.message); - } - } - - const customerInvoice = result.object; - - return this.ok(this.presenter.map(customerInvoice, this.context)); - } catch (e: unknown) { - return this.fail(e as IServerError); - } - } -} diff --git a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts index 0c268273..a5e55674 100644 --- a/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts +++ b/modules/customer-invoices/src/api/infrastructure/express/customer-invoices.routes.ts @@ -8,6 +8,8 @@ import { CustomerInvoiceListRequestSchema, GetCustomerInvoiceByIdRequestSchema, ReportCustomerInvoiceByIdRequestSchema, + UpdateCustomerInvoiceByIdParamsRequestSchema, + UpdateCustomerInvoiceByIdRequestSchema, } from "../../../common/dto"; import { buildCustomerInvoiceDependencies } from "../dependencies"; import { @@ -15,6 +17,7 @@ import { GetCustomerInvoiceController, ListCustomerInvoicesController, ReportCustomerInvoiceController, + UpdateCustomerInvoiceController, } from "./controllers"; export const customerInvoicesRouter = (params: ModuleParams) => { @@ -81,9 +84,10 @@ export const customerInvoicesRouter = (params: ModuleParams) => { } ); - /*router.put( + router.put( "/:invoice_id", //checkTabContext, + validateRequest(UpdateCustomerInvoiceByIdParamsRequestSchema, "params"), validateRequest(UpdateCustomerInvoiceByIdRequestSchema, "body"), (req: Request, res: Response, next: NextFunction) => { @@ -91,7 +95,7 @@ export const customerInvoicesRouter = (params: ModuleParams) => { const controller = new UpdateCustomerInvoiceController(useCase); return controller.execute(req, res, next); } - );*/ + ); /*router.delete( "/:invoice_id", diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts index 99dbf7f5..f3c32563 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/customer-invoice.mapper.ts @@ -374,7 +374,11 @@ export class CustomerInvoiceDomainMapper const allAmounts = source.getAllAmounts(); // 4) Cliente - const recipient = this._mapRecipientToPersistence(source); + const recipient = this._mapRecipientToPersistence(source, { + errors, + parent: source, + ...params, + }); // 7) Si hubo errores de mapeo, devolvemos colección de validación if (errors.length > 0) { diff --git a/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts b/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts index baa0f955..411b9c63 100644 --- a/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts +++ b/modules/customer-invoices/src/api/infrastructure/mappers/domain/invoice-recipient.mapper.ts @@ -41,21 +41,21 @@ export class InvoiceRecipientDomainMapper { }); } - const _name = isProforma ? source.current_customer.name : source.customer_name; - const _tin = isProforma ? source.current_customer.tin : source.customer_tin; - const _street = isProforma ? source.current_customer.street : source.customer_street; - const _street2 = isProforma ? source.current_customer.street2 : source.customer_street2; - const _city = isProforma ? source.current_customer.city : source.customer_city; + const _name = isProforma ? source.current_customer.name : source.customer_name!; + const _tin = isProforma ? source.current_customer.tin : source.customer_tin!; + const _street = isProforma ? source.current_customer.street : source.customer_street!; + const _street2 = isProforma ? source.current_customer.street2 : source.customer_street2!; + const _city = isProforma ? source.current_customer.city : source.customer_city!; const _postal_code = isProforma ? source.current_customer.postal_code - : source.customer_postal_code; - const _province = isProforma ? source.current_customer.province : source.customer_province; - const _country = isProforma ? source.current_customer.country : source.customer_country; + : source.customer_postal_code!; + const _province = isProforma ? source.current_customer.province : source.customer_province!; + const _country = isProforma ? source.current_customer.country : source.customer_country!; // Customer (snapshot) - const customerName = extractOrPushError(Name.create(_name), "customer_name", errors); + const customerName = extractOrPushError(Name.create(_name!), "customer_name", errors); - const customerTin = extractOrPushError(TINNumber.create(_tin), "customer_tin", errors); + const customerTin = extractOrPushError(TINNumber.create(_tin!), "customer_tin", errors); const customerStreet = extractOrPushError( maybeFromNullableVO(_street, (value) => Street.create(value)), 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 038bf453..42fc1d9e 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 @@ -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"; @@ -53,31 +58,67 @@ export class CustomerInvoiceRepository /** * - * Persiste una nueva factura o actualiza una existente. + * Crea una nueva factura. * * @param invoice - El agregado a guardar. * @param transaction - Transacción activa para la operación. - * @returns Result + * @returns Result */ - async save( - invoice: CustomerInvoice, - transaction: Transaction - ): Promise> { + async create(invoice: CustomerInvoice, transaction?: Transaction): Promise> { try { const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({ resource: "customer-invoice", }); - const mapperData = mapper.mapToPersistence(invoice); + const dto = mapper.mapToPersistence(invoice); - 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 CustomerInvoiceModel.upsert(data, { transaction, returning: true }); - const savedInvoice = mapper.mapToDomain(instance); - return savedInvoice; + await CustomerInvoiceModel.create(data, { + include: [{ all: true }], + transaction, + }); + + return Result.ok(); + } catch (err: unknown) { + return Result.fail(translateSequelizeError(err)); + } + } + + /** + * Actualiza una factura existente. + * + * @param invoice - El agregado a actualizar. + * @param transaction - Transacción activa para la operación. + * @returns Result + */ + async update(invoice: CustomerInvoice, transaction?: Transaction): Promise> { + try { + const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({ + resource: "customer-invoice", + }); + const dto = mapper.mapToPersistence(invoice); + + const { id, ...updatePayload } = dto.data; + const [affected] = await CustomerInvoiceModel.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 invoice" + ) + ); + } + + return Result.ok(); } catch (err: unknown) { return Result.fail(translateSequelizeError(err)); } @@ -236,7 +277,7 @@ export class CustomerInvoiceRepository async deleteByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction: any + transaction: Transaction ): Promise> { try { const deleted = await CustomerInvoiceModel.destroy({ diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts index 8a9c9bb9..709cb2d5 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts @@ -30,6 +30,9 @@ export class CustomerInvoiceModel extends Model< InferAttributes, InferCreationAttributes > { + // Version + // declare version: CreationOptional; + declare id: string; declare company_id: string; @@ -130,6 +133,12 @@ export class CustomerInvoiceModel extends Model< export default (database: Sequelize) => { CustomerInvoiceModel.init( { + /*version: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + defaultValue: 0, + },*/ + id: { type: DataTypes.UUID, primaryKey: true, @@ -359,6 +368,14 @@ export default (database: Sequelize) => { defaultScope: {}, scopes: {}, + + /*hooks: { + // Incrementa la versión en cada update exitoso (OCC). + beforeUpdate: (instance) => { + const current = instance.get("version") as number; + instance.set("version", current + 1); + }, + },*/ } ); diff --git a/modules/customer-invoices/src/common/dto/request/update-customer-invoice-by-id.request.dto.ts b/modules/customer-invoices/src/common/dto/request/update-customer-invoice-by-id.request.dto.ts index ee297e5a..c07cc5e8 100644 --- a/modules/customer-invoices/src/common/dto/request/update-customer-invoice-by-id.request.dto.ts +++ b/modules/customer-invoices/src/common/dto/request/update-customer-invoice-by-id.request.dto.ts @@ -5,18 +5,19 @@ export const UpdateCustomerInvoiceByIdParamsRequestSchema = z.object({ }); export const UpdateCustomerInvoiceByIdRequestSchema = z.object({ - invoice_number: z.string(), - series: z.string().default(""), + series: z.string().optional(), - invoice_date: z.string(), - operation_date: z.string().default(""), + invoice_date: z.string().optional(), + operation_date: z.string().optional(), - customer_id: z.uuid(), + customer_id: z.uuid().optional(), - notes: z.string().default(""), + reference: z.string().optional(), + description: z.string().optional(), + notes: z.string().optional(), - language_code: z.string().toLowerCase().default("es"), - currency_code: z.string().toUpperCase().default("EUR"), + language_code: z.string().optional(), + currency_code: z.string().optional(), }); export type UpdateCustomerInvoiceByIdRequestDTO = Partial< diff --git a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts index ba49a7e0..d247321c 100644 --- a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts +++ b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts @@ -12,6 +12,7 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({ invoice_date: z.string(), operation_date: z.string(), + description: z.string(), notes: z.string(), language_code: z.string(), diff --git a/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx b/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx index 43e5aecb..e9b7c672 100644 --- a/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx +++ b/modules/customer-invoices/src/web/components/editor/recipient/recipient-modal-selector-field.tsx @@ -38,6 +38,8 @@ export function RecipientModalSelectorField({ { const { id: invoiceId, data } = payload; + + console.log(payload); + if (!invoiceId) { throw new Error("customerInvoiceId is required"); } const result = schema.safeParse(data); if (!result.success) { + console.log(result); + // Construye errores detallados const validationErrors = result.error.issues.map((err) => ({ field: err.path.join("."), diff --git a/modules/customer-invoices/src/web/pages/update/customer-invoices-update-page.tsx b/modules/customer-invoices/src/web/pages/update/customer-invoices-update-page.tsx index 7e7948b4..3304e2fe 100644 --- a/modules/customer-invoices/src/web/pages/update/customer-invoices-update-page.tsx +++ b/modules/customer-invoices/src/web/pages/update/customer-invoices-update-page.tsx @@ -63,6 +63,8 @@ export const CustomerInvoiceUpdatePage = () => { } const patchData = pickFormDirtyValues(formData, dirtyFields); + console.log(patchData); + mutate( { id: invoiceId!, data: patchData }, { diff --git a/modules/customer-invoices/src/web/schemas/customer-invoices.form.schema.ts b/modules/customer-invoices/src/web/schemas/customer-invoices.form.schema.ts index e91a2424..3f65dfa4 100644 --- a/modules/customer-invoices/src/web/schemas/customer-invoices.form.schema.ts +++ b/modules/customer-invoices/src/web/schemas/customer-invoices.form.schema.ts @@ -1,4 +1,3 @@ -import { MoneySchema, PercentageSchema, QuantitySchema } from "@erp/core"; import { z } from "zod/v4"; export const CustomerInvoiceFormSchema = z.object({ @@ -14,7 +13,7 @@ export const CustomerInvoiceFormSchema = z.object({ description: z.string().optional(), notes: z.string().optional(), - language_code: z + /*language_code: z .string({ error: "El idioma es obligatorio", }) @@ -65,7 +64,7 @@ export const CustomerInvoiceFormSchema = z.object({ discount_amount: MoneySchema, taxable_amount: MoneySchema, taxes_amount: MoneySchema, - total_amount: MoneySchema, + total_amount: MoneySchema,*/ }); export type CustomerInvoiceFormData = z.infer; diff --git a/modules/customers/src/api/application/specs/customer-not-exists.spec.ts b/modules/customers/src/api/application/specs/customer-not-exists.spec.ts index 486d7a74..d23e630d 100644 --- a/modules/customers/src/api/application/specs/customer-not-exists.spec.ts +++ b/modules/customers/src/api/application/specs/customer-not-exists.spec.ts @@ -6,7 +6,7 @@ export class CustomerNotExistsInCompanySpecification extends CompositeSpecificat constructor( private readonly service: CustomerService, private readonly companyId: UniqueID, - private readonly transaction?: any + private readonly transaction?: unknown ) { super(); } diff --git a/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts b/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts index 04ec170e..bbbda8cc 100644 --- a/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts +++ b/modules/customers/src/api/application/use-cases/update/update-customer.use-case.ts @@ -1,6 +1,7 @@ import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; 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 { CustomerFullPresenter } from "../../presenters"; @@ -41,7 +42,7 @@ export class UpdateCustomerUseCase { const patchProps: CustomerPatchProps = patchPropsResult.data; - return this.transactionManager.complete(async (transaction) => { + return this.transactionManager.complete(async (transaction: Transaction) => { try { const updatedCustomer = await this.service.updateCustomerByIdInCompany( companyId, diff --git a/modules/customers/src/api/domain/repositories/customer-repository.interface.ts b/modules/customers/src/api/domain/repositories/customer-repository.interface.ts index c1270ff3..cb95a393 100644 --- a/modules/customers/src/api/domain/repositories/customer-repository.interface.ts +++ b/modules/customers/src/api/domain/repositories/customer-repository.interface.ts @@ -13,7 +13,7 @@ export interface ICustomerRepository { * Guarda (crea o actualiza) un Customer en la base de datos. * Retorna el objeto actualizado tras la operación. */ - save(customer: Customer, transaction: any): Promise>; + save(customer: Customer, transaction: unknown): Promise>; /** * Comprueba si existe un Customer con un `id` dentro de una `company`. @@ -21,7 +21,7 @@ export interface ICustomerRepository { existsByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction?: unknown ): Promise>; /** @@ -31,7 +31,7 @@ export interface ICustomerRepository { getByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction?: unknown ): Promise>; /** @@ -42,7 +42,7 @@ export interface ICustomerRepository { findByCriteriaInCompany( companyId: UniqueID, criteria: Criteria, - transaction?: any + transaction?: unknown ): Promise, Error>>; /** @@ -53,6 +53,6 @@ export interface ICustomerRepository { deleteByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction: any + transaction: unknown ): Promise>; } diff --git a/modules/customers/src/api/domain/services/customer.service.ts b/modules/customers/src/api/domain/services/customer.service.ts index c6fdec15..101b2d94 100644 --- a/modules/customers/src/api/domain/services/customer.service.ts +++ b/modules/customers/src/api/domain/services/customer.service.ts @@ -31,7 +31,7 @@ export class CustomerService { * @param transaction - Transacción activa para la operación. * @returns Result - El agregado guardado o un error si falla la operación. */ - async saveCustomer(customer: Customer, transaction: any): Promise> { + async saveCustomer(customer: Customer, transaction: unknown): Promise> { return this.repository.save(customer, transaction); } @@ -48,7 +48,7 @@ export class CustomerService { existsByIdInCompany( companyId: UniqueID, customerId: UniqueID, - transaction?: any + transaction?: unknown ): Promise> { return this.repository.existsByIdInCompany(companyId, customerId, transaction); } @@ -64,7 +64,7 @@ export class CustomerService { async findCustomerByCriteriaInCompany( companyId: UniqueID, criteria: Criteria, - transaction?: any + transaction?: unknown ): Promise, Error>> { return this.repository.findByCriteriaInCompany(companyId, criteria, transaction); } @@ -80,7 +80,7 @@ export class CustomerService { async getCustomerByIdInCompany( companyId: UniqueID, customerId: UniqueID, - transaction?: any + transaction?: unknown ): Promise> { return this.repository.getByIdInCompany(companyId, customerId, transaction); } @@ -99,7 +99,7 @@ export class CustomerService { companyId: UniqueID, customerId: UniqueID, changes: CustomerPatchProps, - transaction?: any + transaction?: unknown ): Promise> { const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction); @@ -128,7 +128,7 @@ export class CustomerService { async deleteCustomerByIdInCompany( companyId: UniqueID, customerId: UniqueID, - transaction?: any + transaction?: unknown ): Promise> { return this.repository.deleteByIdInCompany(companyId, customerId, transaction); } diff --git a/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx b/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx index 14d6ee3b..2558984d 100644 --- a/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx +++ b/modules/customers/src/web/components/customer-modal-selector/customer-modal-selector.tsx @@ -20,6 +20,8 @@ interface CustomerModalSelectorProps { value?: string; onValueChange?: (id: string) => void; initialCustomer?: CustomerSummary; + disabled?: boolean; + readOnly?: boolean; className: string; } @@ -27,6 +29,8 @@ export const CustomerModalSelector = ({ value, onValueChange, initialCustomer, + disabled = false, + readOnly = false, className, }: CustomerModalSelectorProps) => { // UI state diff --git a/modules/doc-numbering/src/api/domain/repositories/doc-number-repository.interface.ts b/modules/doc-numbering/src/api/domain/repositories/doc-number-repository.interface.ts index 8c68841d..cf2408df 100644 --- a/modules/doc-numbering/src/api/domain/repositories/doc-number-repository.interface.ts +++ b/modules/doc-numbering/src/api/domain/repositories/doc-number-repository.interface.ts @@ -6,7 +6,7 @@ export interface IDocNumberingRepository { getByReferenceInCompany( companyId: UniqueID, reference: string, - transaction?: any + transaction?: unknown ): Promise>; - save(reference: string, transaction?: any): Promise>; + save(reference: string, transaction?: unknown): Promise>; } diff --git a/modules/doc-numbering/src/api/infrastructure/sequelize/repositories/doc-number.repository.ts b/modules/doc-numbering/src/api/infrastructure/sequelize/repositories/doc-number.repository.ts index 9e00c244..f33665cc 100644 --- a/modules/doc-numbering/src/api/infrastructure/sequelize/repositories/doc-number.repository.ts +++ b/modules/doc-numbering/src/api/infrastructure/sequelize/repositories/doc-number.repository.ts @@ -13,7 +13,7 @@ export class DocNumberRepository async getByReferenceInCompany( companyId: UniqueID, reference: string, - transaction?: any + transaction?: unknown ): Promise> { try { const mapper: IDocNumberDomainMapper = this._registry.getDomainMapper({ diff --git a/modules/verifactu/src/api/domain/repositories/verifactu-repository.interface.ts b/modules/verifactu/src/api/domain/repositories/verifactu-repository.interface.ts index aedb18d1..9302d963 100644 --- a/modules/verifactu/src/api/domain/repositories/verifactu-repository.interface.ts +++ b/modules/verifactu/src/api/domain/repositories/verifactu-repository.interface.ts @@ -16,11 +16,14 @@ export interface IVerifactuRecordRepository { * @param transaction - Transacción activa para la operación. * @returns Result */ - save(verifactuRecord: VerifactuRecord, transaction: any): Promise>; + save( + verifactuRecord: VerifactuRecord, + transaction: unknown + ): Promise>; /** * Recupera un registro por su ID. * Devuelve un `NotFoundError` si no se encuentra. */ - getById(id: UniqueID, transaction?: any): Promise>; + getById(id: UniqueID, transaction?: unknown): Promise>; } diff --git a/modules/verifactu/src/api/domain/services/verifactu-record.service.ts b/modules/verifactu/src/api/domain/services/verifactu-record.service.ts index cbf74400..773aa3bf 100644 --- a/modules/verifactu/src/api/domain/services/verifactu-record.service.ts +++ b/modules/verifactu/src/api/domain/services/verifactu-record.service.ts @@ -16,7 +16,7 @@ export class VerifactuRecordService { */ async saveVerifactuRecord( verifactuRecord: VerifactuRecord, - transaction: any + transaction: unknown ): Promise> { return this.repository.save(verifactuRecord, transaction); } diff --git a/modules/verifactu/src/api/infrastructure/sequelize/verifactu-record.repository.ts b/modules/verifactu/src/api/infrastructure/sequelize/verifactu-record.repository.ts index 4cc9ff5b..8064e983 100644 --- a/modules/verifactu/src/api/infrastructure/sequelize/verifactu-record.repository.ts +++ b/modules/verifactu/src/api/infrastructure/sequelize/verifactu-record.repository.ts @@ -14,7 +14,7 @@ export class VerifactuRecordRepository extends SequelizeRepository implements IVerifactuRecordRepository { - getById(id: UniqueID, transaction?: any): Promise> { + getById(id: UniqueID, transaction?: unknown): Promise> { throw new Error("Method not implemented."); } // Listado por tenant con criteria saneada @@ -235,7 +235,7 @@ export class VerifactuRecordRepository async deleteByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction: any + transaction: unknown ): Promise> { try { const deleted = await VerifactuRecordModel.destroy({ diff --git a/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts index 197122c6..5584987d 100644 --- a/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts +++ b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-not-exists.spec.ts @@ -1,15 +1,29 @@ import { CompositeSpecification, UniqueID } from "@repo/rdx-ddd"; -import { I{{pascalCase name}}Repository } from "../../domain/repositories"; +import { I +{ + pascalCase; + name; +} +Repository; +} from "../../domain/repositories" -export class {{pascalCase name}}NotExistsSpecification extends CompositeSpecification { +export class { + { + pascalCase; + name; +} +}NotExistsSpecification extends CompositeSpecification +{ constructor( private readonly repo: I{{pascalCase name}}Repository, - private readonly transaction?: any - ) { - super(); - } + private readonly transaction?: unknown + ) + super(); - async isSatisfiedBy(entityId: UniqueID): Promise { + async; + isSatisfiedBy(entityId: UniqueID) + : Promise + { const existsOrError = await this.repo.existsById(entityId, this.transaction); if (existsOrError.isFailure) throw existsOrError.error; return existsOrError.data === false; diff --git a/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts index 918558b0..eadaff7b 100644 --- a/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts +++ b/templates/new-module/src/api/application/presenters/specs/{{kebabCase name}}-unique-name.spec.ts @@ -1,15 +1,29 @@ import { CompositeSpecification } from "@repo/rdx-ddd"; -import { I{{pascalCase name}}Repository } from "../../domain/repositories"; +import { I +{ + pascalCase; + name; +} +Repository; +} from "../../domain/repositories" -export class {{pascalCase name}}UniqueNameSpecification extends CompositeSpecification { +export class { + { + pascalCase; + name; +} +}UniqueNameSpecification extends CompositeSpecification +{ constructor( private readonly repo: I{{pascalCase name}}Repository, - private readonly transaction?: any - ) { - super(); - } + private readonly transaction?: unknown + ) + super(); - async isSatisfiedBy(name: string): Promise { + async; + isSatisfiedBy(name: string) + : Promise + { const criteria = { filters: { name } }; const resultOrError = await this.repo.findByCriteria(criteria as any, this.transaction); if (resultOrError.isFailure) throw resultOrError.error; diff --git a/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts b/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts index 2ce5000e..aafdeeaf 100644 --- a/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts +++ b/templates/new-module/src/api/domain/repositories/{{kebabCase name}}-repository.interface.ts @@ -1,12 +1,33 @@ import { UniqueID } from "@repo/rdx-ddd"; import { Result, Collection } from "@repo/rdx-utils"; import { Criteria } from "@repo/rdx-criteria/server"; -import { {{pascalCase name}} } from "../aggregates"; - -export interface I{{pascalCase name}}Repository { - save(entity: {{pascalCase name}}, transaction: any): Promise>; - existsById(id: UniqueID, transaction?: any): Promise>; - getById(id: UniqueID, transaction?: any): Promise>; - findByCriteria(criteria: Criteria, transaction?: any): Promise, Error>>; - deleteById(id: UniqueID, transaction: any): Promise>; +import { +{ + pascalCase; + name; +} +} from "../aggregates" + +export interface I{{pascalCase name} +}Repository +{ + save(entity: {{pascalCase name}}, transaction: unknown) + : Promise> + existsById(id: UniqueID, transaction?: unknown) + : Promise> + getById(id: UniqueID, transaction?: unknown) + : Promise> + findByCriteria(criteria: Criteria, transaction?: unknown) + : Promise, Error>> + deleteById(id: UniqueID, transaction: unknown) + : Promise> }