Clientes y facturas de cliente

This commit is contained in:
David Arranz 2025-10-03 21:01:38 +02:00
parent d6b2370cf1
commit 6e5b14305e
37 changed files with 529 additions and 579 deletions

View File

@ -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.",

View File

@ -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";

View File

@ -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)
);
}
}
*/

View File

@ -0,0 +1 @@
export * from "./update-customer-invoice.use-case";

View File

@ -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 }));
}
}

View File

@ -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);
}
});
}
}

View File

@ -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 {

View File

@ -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>>;
}

View File

@ -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);
}

View File

@ -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),

View File

@ -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";

View File

@ -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)
);
}
}

View File

@ -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);
}
}
}

View File

@ -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",

View File

@ -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) {

View File

@ -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)),

View File

@ -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({

View File

@ -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);
},
},*/
}
);

View File

@ -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<

View File

@ -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(),

View File

@ -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={{

View File

@ -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("."),

View File

@ -63,6 +63,8 @@ export const CustomerInvoiceUpdatePage = () => {
}
const patchData = pickFormDirtyValues(formData, dirtyFields);
console.log(patchData);
mutate(
{ id: invoiceId!, data: patchData },
{

View File

@ -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>;

View File

@ -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();
}

View File

@ -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,

View File

@ -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>>;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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>>;
}

View File

@ -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({

View File

@ -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>>;
}

View File

@ -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);
}

View File

@ -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({

View File

@ -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;

View File

@ -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;

View File

@ -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>>
}