import { UniqueID } from "core/common/domain"; import { Result } from "core/common/helpers"; import { ITransactionManager } from "core/common/infrastructure/database"; import { logger } from "core/common/infrastructure/logger"; import { IUpdateInvoiceRequestDTO } from "../presentation/dto"; export class CreateInvoiceUseCase { constructor( private readonly invoiceService: IInvoiceService, private readonly transactionManager: ITransactionManager ) {} public execute( invoiceID: UniqueID, dto: Partial ): Promise> { return this.transactionManager.complete(async (transaction) => { try { const validOrErrors = this.validateInvoiceData(dto); if (validOrErrors.isFailure) { return Result.fail(validOrErrors.error); } const data = validOrErrors.data; // Update invoice with dto return await this.invoiceService.updateInvoiceById(invoiceID, data, transaction); } catch (error: unknown) { logger.error(error as Error); return Result.fail(error as Error); } }); } private validateInvoiceData( dto: Partial ): Result, Error> { const errors: Error[] = []; const validatedData: Partial = {}; // Create invoice let invoice_status = InvoiceStatus.create(invoiceDTO.status).object; if (invoice_status.isEmpty()) { invoice_status = InvoiceStatus.createDraft(); } let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object; if (invoice_series.isEmpty()) { invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object; } let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object; if (issue_date.isEmpty()) { issue_date = InvoiceDate.createCurrentDate().object; } let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object; if (operation_date.isEmpty()) { operation_date = InvoiceDate.createCurrentDate().object; } let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object; if (invoiceCurrency.isEmpty()) { invoiceCurrency = Currency.createDefaultCode().object; } let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object; if (invoiceLanguage.isEmpty()) { invoiceLanguage = Language.createDefaultCode().object; } const items = new Collection( invoiceDTO.items?.map( (item) => InvoiceSimpleItem.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 (!invoice_status.isDraft()) { throw Error("Error al crear una factura que no es borrador"); } return DraftInvoice.create( { invoiceSeries: invoice_series, issueDate: issue_date, operationDate: operation_date, invoiceCurrency, language: invoiceLanguage, invoiceNumber: InvoiceNumber.create(undefined).object, //notes: Note.create(invoiceDTO.notes).object, //senderId: UniqueID.create(null).object, recipient, items, }, invoiceId ); } } export type UpdateInvoiceResponseOrError = | Result // Misc errors (value objects) | Result; // Success! export class UpdateInvoiceUseCase2 implements IUseCase<{ id: UniqueID; data: IUpdateInvoice_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: IUpdateInvoice_DTO; }): Promise { const { id, data: invoiceDTO } = request; // Validaciones const invoiceDTOOrError = ensureUpdateInvoice_DTOIsValid(invoiceDTO); if (invoiceDTOOrError.isFailure) { return this.handleValidationFailure(invoiceDTOOrError.error); } const transaction = this._adapter.startTransaction(); const invoiceRepoBuilder = this.getRepository("Invoice"); let invoice: Invoice | null = null; try { await transaction.complete(async (t) => { invoice = await invoiceRepoBuilder({ transaction: t }).getById(id); }); if (invoice === null) { return Result.fail( UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `Invoice not found`, { id: request.id.toString(), entity: "invoice", }) ); } return Result.ok(invoice); } catch (error: unknown) { const _error = error as Error; if (invoiceRepoBuilder().isRepositoryError(_error)) { return this.handleRepositoryError(error as BaseError, invoiceRepoBuilder()); } else { return this.handleUnexceptedError(error); } } // Recipient validations /*const recipientIdOrError = ensureParticipantIdIsValid( invoiceDTO?.recipient?.id, ); if (recipientIdOrError.isFailure) { return this.handleValidationFailure( recipientIdOrError.error, "Recipient ID not valid", ); } const recipientId = recipientIdOrError.object; const recipientBillingIdOrError = ensureParticipantAddressIdIsValid( invoiceDTO?.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( invoiceDTO?.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 invoice const invoiceOrError = await this.tryUpdateInvoiceInstance( invoiceDTO, invoiceIdOrError.object, //senderId, //senderBillingId, //senderShippingId, recipientContact, ); if (invoiceOrError.isFailure) { const { error: domainError } = invoiceOrError; let errorCode = ""; let message = ""; switch (domainError.code) { case Invoice.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.saveInvoice(invoiceOrError.object); */ } private async tryUpdateInvoiceInstance(invoiceDTO, invoiceId, recipient) { // Create invoice let invoice_status = InvoiceStatus.create(invoiceDTO.status).object; if (invoice_status.isEmpty()) { invoice_status = InvoiceStatus.createDraft(); } let invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object; if (invoice_series.isEmpty()) { invoice_series = InvoiceSeries.create(invoiceDTO.invoice_series).object; } let issue_date = InvoiceDate.create(invoiceDTO.issue_date).object; if (issue_date.isEmpty()) { issue_date = InvoiceDate.createCurrentDate().object; } let operation_date = InvoiceDate.create(invoiceDTO.operation_date).object; if (operation_date.isEmpty()) { operation_date = InvoiceDate.createCurrentDate().object; } let invoiceCurrency = Currency.createFromCode(invoiceDTO.currency).object; if (invoiceCurrency.isEmpty()) { invoiceCurrency = Currency.createDefaultCode().object; } let invoiceLanguage = Language.createFromCode(invoiceDTO.language_code).object; if (invoiceLanguage.isEmpty()) { invoiceLanguage = Language.createDefaultCode().object; } const items = new Collection( invoiceDTO.items?.map( (item) => InvoiceSimpleItem.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 (!invoice_status.isDraft()) { throw Error("Error al crear una factura que no es borrador"); } return DraftInvoice.create( { invoiceSeries: invoice_series, issueDate: issue_date, operationDate: operation_date, invoiceCurrency, language: invoiceLanguage, invoiceNumber: InvoiceNumber.create(undefined).object, //notes: Note.create(invoiceDTO.notes).object, //senderId: UniqueID.create(null).object, recipient, items, }, invoiceId ); } 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 saveInvoice(invoice: DraftInvoice) { const transaction = this._adapter.startTransaction(); const invoiceRepoBuilder = this.getRepository("Invoice"); try { await transaction.complete(async (t) => { const invoiceRepo = invoiceRepoBuilder({ transaction: t }); await invoiceRepo.save(invoice); }); return Result.ok(invoice); } catch (error: unknown) { const _error = error as Error; if (invoiceRepoBuilder().isRepositoryError(_error)) { return this.handleRepositoryError(error as BaseError, invoiceRepoBuilder()); } 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: IInvoiceRepository ): Result { const { message, details } = repository.handleRepositoryError(error); return Result.fail( UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details) ); } }