Clientes y facturas de cliente
This commit is contained in:
parent
d6b2370cf1
commit
6e5b14305e
@ -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<T>(work: (transaction: any) => Promise<T>): Promise<T> {
|
||||
async complete<T>(work: (transaction: unknown) => Promise<T>): Promise<T> {
|
||||
if (this._transaction) {
|
||||
this.logger.error(
|
||||
"❌ Cannot start a new transaction inside another. Nested transactions are not allowed.",
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<IUpdateCustomerInvoiceRequestDTO>
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
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<IUpdateCustomerInvoiceRequestDTO>
|
||||
): Result<Partial<ICustomerInvoiceProps>, Error> {
|
||||
const errors: Error[] = [];
|
||||
const validatedData: Partial<ICustomerInvoiceProps> = {};
|
||||
|
||||
// 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<CustomerInvoiceItem>(
|
||||
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<never, IUseCaseError> // Misc errors (value objects)
|
||||
| Result<CustomerInvoice, never>; // Success!
|
||||
|
||||
export class UpdateCustomerInvoiceUseCase2
|
||||
implements
|
||||
IUseCase<{ id: UniqueID; data: IUpdateCustomerInvoice_DTO }, Promise<UpdateCustomerInvoiceResponseOrError>>
|
||||
{
|
||||
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: IUpdateCustomerInvoice_DTO;
|
||||
}): Promise<UpdateCustomerInvoiceResponseOrError> {
|
||||
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<ICustomerInvoiceRepository>("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<IUseCaseError>(
|
||||
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `CustomerInvoice not found`, {
|
||||
id: request.id.toString(),
|
||||
entity: "customerInvoice",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Result.ok<CustomerInvoice>(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<IUseCaseError>(
|
||||
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<CustomerInvoiceItem>(
|
||||
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<IContactRepository>("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<ICustomerInvoiceRepository>("CustomerInvoice");
|
||||
|
||||
try {
|
||||
await transaction.complete(async (t) => {
|
||||
const customerInvoiceRepo = customerInvoiceRepoBuilder({ transaction: t });
|
||||
await customerInvoiceRepo.save(customerInvoice);
|
||||
});
|
||||
|
||||
return Result.ok<DraftCustomerInvoice>(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<never, IUseCaseError> {
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error)
|
||||
);
|
||||
}
|
||||
|
||||
private handleRepositoryError(
|
||||
error: BaseError,
|
||||
repository: ICustomerInvoiceRepository
|
||||
): Result<never, IUseCaseError> {
|
||||
const { message, details } = repository.handleRepositoryError(error);
|
||||
return Result.fail<IUseCaseError>(
|
||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details)
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -0,0 +1 @@
|
||||
export * from "./update-customer-invoice.use-case";
|
||||
@ -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 }));
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -51,6 +51,12 @@ export interface CustomerInvoiceProps {
|
||||
verifactu_status: string;*/
|
||||
}
|
||||
|
||||
export type CustomerInvoicePatchProps = Partial<
|
||||
Omit<CustomerInvoiceProps, "companyId" | "items">
|
||||
> & {
|
||||
items?: CustomerInvoiceItems;
|
||||
};
|
||||
|
||||
export interface ICustomerInvoice {
|
||||
hasRecipient: boolean;
|
||||
hasPaymentMethod: boolean;
|
||||
@ -65,8 +71,6 @@ export interface ICustomerInvoice {
|
||||
issueInvoice(newInvoiceNumber: CustomerInvoiceNumber): Result<CustomerInvoice, Error>;
|
||||
}
|
||||
|
||||
export type CustomerInvoicePatchProps = Partial<Omit<CustomerInvoiceProps, "companyId">>;
|
||||
|
||||
export class CustomerInvoice
|
||||
extends AggregateRoot<CustomerInvoiceProps>
|
||||
implements ICustomerInvoice
|
||||
@ -106,7 +110,23 @@ export class CustomerInvoice
|
||||
}
|
||||
|
||||
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
||||
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 {
|
||||
|
||||
@ -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<CustomerInvoice, Error>
|
||||
* @returns Result<void, Error>
|
||||
*/
|
||||
save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
||||
create(invoice: CustomerInvoice, transaction?: unknown): Promise<Result<void, Error>>;
|
||||
|
||||
/**
|
||||
* Actualiza una factura existente.
|
||||
*
|
||||
* @param invoice - El agregado a actualizar.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<void, Error>
|
||||
*/
|
||||
update(invoice: CustomerInvoice, transaction?: unknown): Promise<Result<void, Error>>;
|
||||
|
||||
/**
|
||||
* 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<Result<boolean, Error>>;
|
||||
|
||||
/**
|
||||
@ -36,7 +44,7 @@ export interface ICustomerInvoiceRepository {
|
||||
getByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction?: any
|
||||
transaction?: unknown
|
||||
): Promise<Result<CustomerInvoice, Error>>;
|
||||
|
||||
/**
|
||||
@ -55,7 +63,7 @@ export interface ICustomerInvoiceRepository {
|
||||
findByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction: any
|
||||
transaction: unknown
|
||||
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>>;
|
||||
|
||||
/**
|
||||
@ -70,6 +78,6 @@ export interface ICustomerInvoiceRepository {
|
||||
deleteByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: any
|
||||
transaction: unknown
|
||||
): Promise<Result<void, Error>>;
|
||||
}
|
||||
|
||||
@ -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<CustomerInvoice, Error> - El agregado guardado o un error si falla la operación.
|
||||
*/
|
||||
async saveInvoice(
|
||||
async createInvoice(
|
||||
companyId: UniqueID,
|
||||
invoice: CustomerInvoice,
|
||||
transaction: any
|
||||
transaction: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
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<CustomerInvoice, Error> - El agregado guardado o un error si falla la operación.
|
||||
*/
|
||||
async updateInvoice(
|
||||
companyId: UniqueID,
|
||||
invoice: CustomerInvoice,
|
||||
transaction: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
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<Result<boolean, Error>> {
|
||||
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<any> {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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<any> {
|
||||
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 = <CustomerInvoice>result.object;
|
||||
|
||||
return this.ok<IUpdateCustomerInvoice_Response_DTO>(this.presenter.map(customerInvoice, this.context));
|
||||
} catch (e: unknown) {
|
||||
return this.fail(e as IServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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<CustomerInvoice, Error>
|
||||
* @returns Result<void, Error>
|
||||
*/
|
||||
async save(
|
||||
invoice: CustomerInvoice,
|
||||
transaction: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
async create(invoice: CustomerInvoice, transaction?: Transaction): Promise<Result<void, Error>> {
|
||||
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<void, Error>
|
||||
*/
|
||||
async update(invoice: CustomerInvoice, transaction?: Transaction): Promise<Result<void, Error>> {
|
||||
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<Result<void, Error>> {
|
||||
try {
|
||||
const deleted = await CustomerInvoiceModel.destroy({
|
||||
|
||||
@ -30,6 +30,9 @@ export class CustomerInvoiceModel extends Model<
|
||||
InferAttributes<CustomerInvoiceModel>,
|
||||
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" | "taxes" }>
|
||||
> {
|
||||
// Version
|
||||
// declare version: CreationOptional<number>;
|
||||
|
||||
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);
|
||||
},
|
||||
},*/
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -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<
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -38,6 +38,8 @@ export function RecipientModalSelectorField<TFormValues extends FieldValues>({
|
||||
<FormItem className={className}>
|
||||
<CustomerModalSelector
|
||||
value={value}
|
||||
disabled={isDisabled}
|
||||
readOnly={isReadOnly}
|
||||
onValueChange={onChange}
|
||||
className='bg-fuchsia-200'
|
||||
initialCustomer={{
|
||||
|
||||
@ -32,12 +32,17 @@ export function useUpdateCustomerInvoice() {
|
||||
|
||||
mutationFn: async (payload) => {
|
||||
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("."),
|
||||
|
||||
@ -63,6 +63,8 @@ export const CustomerInvoiceUpdatePage = () => {
|
||||
}
|
||||
|
||||
const patchData = pickFormDirtyValues(formData, dirtyFields);
|
||||
console.log(patchData);
|
||||
|
||||
mutate(
|
||||
{ id: invoiceId!, data: patchData },
|
||||
{
|
||||
|
||||
@ -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<typeof CustomerInvoiceFormSchema>;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<Result<Customer, Error>>;
|
||||
save(customer: Customer, transaction: unknown): Promise<Result<Customer, Error>>;
|
||||
|
||||
/**
|
||||
* 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<Result<boolean, Error>>;
|
||||
|
||||
/**
|
||||
@ -31,7 +31,7 @@ export interface ICustomerRepository {
|
||||
getByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction?: any
|
||||
transaction?: unknown
|
||||
): Promise<Result<Customer, Error>>;
|
||||
|
||||
/**
|
||||
@ -42,7 +42,7 @@ export interface ICustomerRepository {
|
||||
findByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: any
|
||||
transaction?: unknown
|
||||
): Promise<Result<Collection<CustomerListDTO>, Error>>;
|
||||
|
||||
/**
|
||||
@ -53,6 +53,6 @@ export interface ICustomerRepository {
|
||||
deleteByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: any
|
||||
transaction: unknown
|
||||
): Promise<Result<void, Error>>;
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export class CustomerService {
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<Customer, Error> - El agregado guardado o un error si falla la operación.
|
||||
*/
|
||||
async saveCustomer(customer: Customer, transaction: any): Promise<Result<Customer, Error>> {
|
||||
async saveCustomer(customer: Customer, transaction: unknown): Promise<Result<Customer, Error>> {
|
||||
return this.repository.save(customer, transaction);
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ export class CustomerService {
|
||||
existsByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
customerId: UniqueID,
|
||||
transaction?: any
|
||||
transaction?: unknown
|
||||
): Promise<Result<boolean, Error>> {
|
||||
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<Result<Collection<CustomerListDTO>, 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<Result<Customer>> {
|
||||
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<Result<Customer, Error>> {
|
||||
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<Result<void>> {
|
||||
return this.repository.deleteByIdInCompany(companyId, customerId, transaction);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -6,7 +6,7 @@ export interface IDocNumberingRepository {
|
||||
getByReferenceInCompany(
|
||||
companyId: UniqueID,
|
||||
reference: string,
|
||||
transaction?: any
|
||||
transaction?: unknown
|
||||
): Promise<Result<DocNumber, Error>>;
|
||||
save(reference: string, transaction?: any): Promise<Result<DocNumber, Error>>;
|
||||
save(reference: string, transaction?: unknown): Promise<Result<DocNumber, Error>>;
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export class DocNumberRepository
|
||||
async getByReferenceInCompany(
|
||||
companyId: UniqueID,
|
||||
reference: string,
|
||||
transaction?: any
|
||||
transaction?: unknown
|
||||
): Promise<Result<DocNumber, Error>> {
|
||||
try {
|
||||
const mapper: IDocNumberDomainMapper = this._registry.getDomainMapper({
|
||||
|
||||
@ -16,11 +16,14 @@ export interface IVerifactuRecordRepository {
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<VerifactuRecord, Error>
|
||||
*/
|
||||
save(verifactuRecord: VerifactuRecord, transaction: any): Promise<Result<VerifactuRecord, Error>>;
|
||||
save(
|
||||
verifactuRecord: VerifactuRecord,
|
||||
transaction: unknown
|
||||
): Promise<Result<VerifactuRecord, Error>>;
|
||||
|
||||
/**
|
||||
* Recupera un registro por su ID.
|
||||
* Devuelve un `NotFoundError` si no se encuentra.
|
||||
*/
|
||||
getById(id: UniqueID, transaction?: any): Promise<Result<VerifactuRecord, Error>>;
|
||||
getById(id: UniqueID, transaction?: unknown): Promise<Result<VerifactuRecord, Error>>;
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export class VerifactuRecordService {
|
||||
*/
|
||||
async saveVerifactuRecord(
|
||||
verifactuRecord: VerifactuRecord,
|
||||
transaction: any
|
||||
transaction: unknown
|
||||
): Promise<Result<VerifactuRecord, Error>> {
|
||||
return this.repository.save(verifactuRecord, transaction);
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ export class VerifactuRecordRepository
|
||||
extends SequelizeRepository<VerifactuRecord>
|
||||
implements IVerifactuRecordRepository
|
||||
{
|
||||
getById(id: UniqueID, transaction?: any): Promise<Result<VerifactuRecord, Error>> {
|
||||
getById(id: UniqueID, transaction?: unknown): Promise<Result<VerifactuRecord, Error>> {
|
||||
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<Result<void, Error>> {
|
||||
try {
|
||||
const deleted = await VerifactuRecordModel.destroy({
|
||||
|
||||
@ -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<UniqueID> {
|
||||
export class {
|
||||
{
|
||||
pascalCase;
|
||||
name;
|
||||
}
|
||||
}NotExistsSpecification extends CompositeSpecification<UniqueID>
|
||||
{
|
||||
constructor(
|
||||
private readonly repo: I{{pascalCase name}}Repository,
|
||||
private readonly transaction?: any
|
||||
) {
|
||||
super();
|
||||
}
|
||||
private readonly transaction?: unknown
|
||||
)
|
||||
super();
|
||||
|
||||
async isSatisfiedBy(entityId: UniqueID): Promise<boolean> {
|
||||
async;
|
||||
isSatisfiedBy(entityId: UniqueID)
|
||||
: Promise<boolean>
|
||||
{
|
||||
const existsOrError = await this.repo.existsById(entityId, this.transaction);
|
||||
if (existsOrError.isFailure) throw existsOrError.error;
|
||||
return existsOrError.data === false;
|
||||
|
||||
@ -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<string> {
|
||||
export class {
|
||||
{
|
||||
pascalCase;
|
||||
name;
|
||||
}
|
||||
}UniqueNameSpecification extends CompositeSpecification<string>
|
||||
{
|
||||
constructor(
|
||||
private readonly repo: I{{pascalCase name}}Repository,
|
||||
private readonly transaction?: any
|
||||
) {
|
||||
super();
|
||||
}
|
||||
private readonly transaction?: unknown
|
||||
)
|
||||
super();
|
||||
|
||||
async isSatisfiedBy(name: string): Promise<boolean> {
|
||||
async;
|
||||
isSatisfiedBy(name: string)
|
||||
: Promise<boolean>
|
||||
{
|
||||
const criteria = { filters: { name } };
|
||||
const resultOrError = await this.repo.findByCriteria(criteria as any, this.transaction);
|
||||
if (resultOrError.isFailure) throw resultOrError.error;
|
||||
|
||||
@ -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<Result<{{pascalCase name}}, Error>>;
|
||||
existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
getById(id: UniqueID, transaction?: any): Promise<Result<{{pascalCase name}}, Error>>;
|
||||
findByCriteria(criteria: Criteria, transaction?: any): Promise<Result<Collection<{{pascalCase name}}>, Error>>;
|
||||
deleteById(id: UniqueID, transaction: any): Promise<Result<void, Error>>;
|
||||
import {
|
||||
{
|
||||
pascalCase;
|
||||
name;
|
||||
}
|
||||
} from "../aggregates"
|
||||
|
||||
export interface I{{pascalCase name}
|
||||
}Repository
|
||||
{
|
||||
save(entity: {{pascalCase name}}, transaction: unknown)
|
||||
: Promise<Result<
|
||||
pascalCase;
|
||||
name;
|
||||
, Error>>
|
||||
existsById(id: UniqueID, transaction?: unknown)
|
||||
: Promise<Result<boolean, Error>>
|
||||
getById(id: UniqueID, transaction?: unknown)
|
||||
: Promise<Result<
|
||||
pascalCase;
|
||||
name;
|
||||
, Error>>
|
||||
findByCriteria(criteria: Criteria, transaction?: unknown)
|
||||
: Promise<Result<Collection<
|
||||
pascalCase;
|
||||
name;
|
||||
>, Error>>
|
||||
deleteById(id: UniqueID, transaction: unknown)
|
||||
: Promise<Result<void, Error>>
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user