Uecko_ERP/apps/server/src/contexts/invoices/application/update-invoice.use-case.ts

399 lines
12 KiB
TypeScript
Raw Normal View History

2025-04-22 15:09:57 +00:00
import { UniqueID } from "core/common/domain";
2025-03-18 08:05:00 +00:00
2025-04-22 15:09:57 +00:00
import { Result } from "core/common/helpers";
import { ITransactionManager } from "core/common/infrastructure/database";
import { logger } from "core/common/infrastructure/logger";
2025-03-18 08:05:00 +00:00
import { IUpdateInvoiceRequestDTO } from "../presentation/dto";
export class CreateInvoiceUseCase {
constructor(
private readonly invoiceService: IInvoiceService,
private readonly transactionManager: ITransactionManager
) {}
public execute(
invoiceID: UniqueID,
dto: Partial<IUpdateInvoiceRequestDTO>
): Promise<Result<Invoice, Error>> {
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<IUpdateInvoiceRequestDTO>
): Result<Partial<IInvoiceProps>, Error> {
const errors: Error[] = [];
const validatedData: Partial<IInvoiceProps> = {};
// 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<InvoiceItem>(
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<never, IUseCaseError> // Misc errors (value objects)
| Result<Invoice, never>; // Success!
export class UpdateInvoiceUseCase2
implements
IUseCase<{ id: UniqueID; data: IUpdateInvoice_DTO }, Promise<UpdateInvoiceResponseOrError>>
{
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<T>(name: string) {
return this._repositoryManager.getRepository<T>(name);
}
private handleValidationFailure(
validationError: Error,
message?: string
): Result<never, IUseCaseError> {
return Result.fail<IUseCaseError>(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
message ? message : validationError.message,
validationError
)
);
}
async execute(request: {
id: UniqueID;
data: IUpdateInvoice_DTO;
}): Promise<UpdateInvoiceResponseOrError> {
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<IInvoiceRepository>("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<IUseCaseError>(
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `Invoice not found`, {
id: request.id.toString(),
entity: "invoice",
})
);
}
return Result.ok<Invoice>(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<IUseCaseError>(
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<InvoiceItem>(
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<IContactRepository>("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<IInvoiceRepository>("Invoice");
try {
await transaction.complete(async (t) => {
const invoiceRepo = invoiceRepoBuilder({ transaction: t });
await invoiceRepo.save(invoice);
});
return Result.ok<DraftInvoice>(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<never, IUseCaseError> {
return Result.fail<IUseCaseError>(
UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error)
);
}
private handleRepositoryError(
error: BaseError,
repository: IInvoiceRepository
): Result<never, IUseCaseError> {
const { message, details } = repository.handleRepositoryError(error);
return Result.fail<IUseCaseError>(
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details)
);
}
}