Facturas de cliente
This commit is contained in:
parent
4807e51d82
commit
d5c6079d26
@ -1,64 +0,0 @@
|
|||||||
/* import {
|
|
||||||
ApplicationServiceError,
|
|
||||||
type IApplicationServiceError,
|
|
||||||
} from "@/contexts/common/application/services/ApplicationServiceError";
|
|
||||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
|
||||||
import { Result, UniqueID } from "@shared/contexts";
|
|
||||||
import { NullOr } from "@shared/utilities";
|
|
||||||
import { ICustomerInvoiceParticipantAddress, ICustomerInvoiceParticipantAddressRepository } from "../../domain";
|
|
||||||
|
|
||||||
export const participantAddressFinder = async (
|
|
||||||
addressId: UniqueID,
|
|
||||||
adapter: IAdapter,
|
|
||||||
repository: RepositoryBuilder<ICustomerInvoiceParticipantAddressRepository>
|
|
||||||
) => {
|
|
||||||
if (addressId.isNull()) {
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(
|
|
||||||
ApplicationServiceError.INVALID_REQUEST_PARAM,
|
|
||||||
`Participant address ID required`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const transaction = adapter.startTransaction();
|
|
||||||
let address: NullOr<ICustomerInvoiceParticipantAddress> = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await transaction.complete(async (t) => {
|
|
||||||
address = await repository({ transaction: t }).getById(addressId);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (address === null) {
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(ApplicationServiceError.NOT_FOUND_ERROR, "", {
|
|
||||||
id: addressId.toString(),
|
|
||||||
entity: "participant address",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok<ICustomerInvoiceParticipantAddress>(address);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const _error = error as Error;
|
|
||||||
|
|
||||||
if (repository().isRepositoryError(_error)) {
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(
|
|
||||||
ApplicationServiceError.REPOSITORY_ERROR,
|
|
||||||
_error.message,
|
|
||||||
_error
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(
|
|
||||||
ApplicationServiceError.UNEXCEPTED_ERROR,
|
|
||||||
_error.message,
|
|
||||||
_error
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/* import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
|
||||||
import { UniqueID } from "@shared/contexts";
|
|
||||||
import { ICustomerInvoiceParticipantRepository } from "../../domain";
|
|
||||||
import { CustomerInvoiceCustomer } from "../../domain/entities/customer-invoice-customer/customer-invoice-customer";
|
|
||||||
|
|
||||||
export const participantFinder = async (
|
|
||||||
participantId: UniqueID,
|
|
||||||
adapter: IAdapter,
|
|
||||||
repository: RepositoryBuilder<ICustomerInvoiceParticipantRepository>
|
|
||||||
): Promise<CustomerInvoiceCustomer | undefined> => {
|
|
||||||
if (!participantId || (participantId && participantId.isNull())) {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
const participant = await adapter
|
|
||||||
.startTransaction()
|
|
||||||
.complete((t) => repository({ transaction: t }).getById(participantId));
|
|
||||||
|
|
||||||
return Promise.resolve(participant ? participant : undefined);
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { DuplicateEntityError, ITransactionManager } from "@erp/core/api";
|
import { DuplicateEntityError, IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto";
|
import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto";
|
||||||
import { CustomerInvoiceService } from "../../../domain";
|
import { CustomerInvoiceService } from "../../../domain";
|
||||||
import { CreateCustomerInvoiceAssembler } from "./assembler";
|
import { CustomerInvoiceFullPresenter } from "../../presenters";
|
||||||
import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props";
|
import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props";
|
||||||
|
|
||||||
type CreateCustomerInvoiceUseCaseInput = {
|
type CreateCustomerInvoiceUseCaseInput = {
|
||||||
@ -17,15 +17,19 @@ export class CreateCustomerInvoiceUseCase {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly service: CustomerInvoiceService,
|
private readonly service: CustomerInvoiceService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly assembler: CreateCustomerInvoiceAssembler,
|
private readonly presenterRegistry: IPresenterRegistry,
|
||||||
private readonly taxCatalog: JsonTaxCatalogProvider
|
private readonly taxCatalog: JsonTaxCatalogProvider
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: CreateCustomerInvoiceUseCaseInput) {
|
public execute(params: CreateCustomerInvoiceUseCaseInput) {
|
||||||
const { dto, companyId } = params;
|
const { dto, companyId } = params;
|
||||||
const dtoMapper = new CreateCustomerInvoicePropsMapper({ taxCatalog: this.taxCatalog });
|
const presenter = this.presenterRegistry.getPresenter({
|
||||||
|
resource: "customer-invoice",
|
||||||
|
projection: "FULL",
|
||||||
|
}) as CustomerInvoiceFullPresenter;
|
||||||
|
|
||||||
// 1) Mapear DTO → props de dominio
|
// 1) Mapear DTO → props de dominio
|
||||||
|
const dtoMapper = new CreateCustomerInvoicePropsMapper({ taxCatalog: this.taxCatalog });
|
||||||
const dtoResult = dtoMapper.map(dto);
|
const dtoResult = dtoMapper.map(dto);
|
||||||
if (dtoResult.isFailure) {
|
if (dtoResult.isFailure) {
|
||||||
return Result.fail(dtoResult.error);
|
return Result.fail(dtoResult.error);
|
||||||
@ -43,6 +47,7 @@ export class CreateCustomerInvoiceUseCase {
|
|||||||
|
|
||||||
// 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
// 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||||
|
try {
|
||||||
const existsGuard = await this.ensureNotExists(companyId, id, transaction);
|
const existsGuard = await this.ensureNotExists(companyId, id, transaction);
|
||||||
if (existsGuard.isFailure) {
|
if (existsGuard.isFailure) {
|
||||||
return Result.fail(existsGuard.error);
|
return Result.fail(existsGuard.error);
|
||||||
@ -53,9 +58,13 @@ export class CreateCustomerInvoiceUseCase {
|
|||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewDTO = this.assembler.toDTO(saveResult.data);
|
const invoice = saveResult.data;
|
||||||
|
const dto = presenter.toOutput(invoice);
|
||||||
|
|
||||||
return Result.ok(viewDTO);
|
return Result.ok(dto);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
|||||||
"/",
|
"/",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
validateRequest(CreateCustomerInvoiceRequestSchema),
|
validateRequest(CreateCustomerInvoiceRequestSchema, "body"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.build.create();
|
const useCase = deps.build.create();
|
||||||
const controller = new CreateCustomerInvoiceController(useCase);
|
const controller = new CreateCustomerInvoiceController(useCase);
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { CompositeSpecification, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { CustomerService } from "../../domain";
|
||||||
|
|
||||||
|
export class CustomerNotExistsInCompanySpecification extends CompositeSpecification<UniqueID> {
|
||||||
|
constructor(
|
||||||
|
private readonly service: CustomerService,
|
||||||
|
private readonly companyId: UniqueID,
|
||||||
|
private readonly transaction?: any
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isSatisfiedBy(customerId: UniqueID): Promise<boolean> {
|
||||||
|
const exists = await this.service.existsByIdInCompany(
|
||||||
|
customerId,
|
||||||
|
this.companyId,
|
||||||
|
this.transaction
|
||||||
|
);
|
||||||
|
return !exists;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
modules/customers/src/api/application/specs/index.ts
Normal file
1
modules/customers/src/api/application/specs/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./customer-not-exists.spec";
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { DuplicateEntityError, ITransactionManager } from "@erp/core/api";
|
import { DuplicateEntityError, IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { CreateCustomerRequestDTO } from "../../../common";
|
import { CreateCustomerRequestDTO } from "../../../../common";
|
||||||
import { CustomerService } from "../../domain";
|
import { CustomerService } from "../../../domain";
|
||||||
import { CreateCustomersAssembler } from "./assembler";
|
import { CustomerFullPresenter } from "../../presenters";
|
||||||
|
import { CustomerNotExistsInCompanySpecification } from "../../specs";
|
||||||
import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props";
|
import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props";
|
||||||
|
|
||||||
type CreateCustomerUseCaseInput = {
|
type CreateCustomerUseCaseInput = {
|
||||||
@ -16,11 +17,15 @@ export class CreateCustomerUseCase {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly service: CustomerService,
|
private readonly service: CustomerService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly assembler: CreateCustomersAssembler
|
private readonly presenterRegistry: IPresenterRegistry
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: CreateCustomerUseCaseInput) {
|
public execute(params: CreateCustomerUseCaseInput) {
|
||||||
const { dto, companyId } = params;
|
const { dto, companyId } = params;
|
||||||
|
const presenter = this.presenterRegistry.getPresenter({
|
||||||
|
resource: "customer",
|
||||||
|
projection: "FULL",
|
||||||
|
}) as CustomerFullPresenter;
|
||||||
|
|
||||||
// 1) Mapear DTO → props de dominio
|
// 1) Mapear DTO → props de dominio
|
||||||
const dtoResult = mapDTOToCreateCustomerProps(dto);
|
const dtoResult = mapDTOToCreateCustomerProps(dto);
|
||||||
@ -40,9 +45,17 @@ export class CreateCustomerUseCase {
|
|||||||
|
|
||||||
// 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
// 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||||
const existsGuard = await this.ensureNotExists(companyId, id, transaction);
|
try {
|
||||||
if (existsGuard.isFailure) {
|
// Verificar que no exista ya un cliente con el mismo id en la companyId
|
||||||
return Result.fail(existsGuard.error);
|
const spec = new CustomerNotExistsInCompanySpecification(
|
||||||
|
this.service,
|
||||||
|
companyId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
const isNew = await spec.isSatisfiedBy(newCustomer.id);
|
||||||
|
|
||||||
|
if (!isNew) {
|
||||||
|
return Result.fail(new DuplicateEntityError("Customer", "id", String(newCustomer.id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveResult = await this.service.saveCustomer(newCustomer, transaction);
|
const saveResult = await this.service.saveCustomer(newCustomer, transaction);
|
||||||
@ -50,29 +63,13 @@ export class CreateCustomerUseCase {
|
|||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewDTO = this.assembler.toDTO(saveResult.data);
|
const customer = saveResult.data;
|
||||||
|
const dto = presenter.toOutput(customer);
|
||||||
|
|
||||||
return Result.ok(viewDTO);
|
return Result.ok(dto);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Verifica que no exista un Customer con el mismo id en la companyId.
|
|
||||||
*/
|
|
||||||
private async ensureNotExists(
|
|
||||||
companyId: UniqueID,
|
|
||||||
id: UniqueID,
|
|
||||||
transaction: Transaction
|
|
||||||
): Promise<Result<void, Error>> {
|
|
||||||
const existsResult = await this.service.existsByIdInCompany(companyId, id, transaction);
|
|
||||||
if (existsResult.isFailure) {
|
|
||||||
return Result.fail(existsResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existsResult.data) {
|
|
||||||
return Result.fail(new DuplicateEntityError("Customer", "id", String(id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok<void>(undefined);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
SequelizeTransactionManager,
|
SequelizeTransactionManager,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
|
CreateCustomerUseCase,
|
||||||
CustomerFullPresenter,
|
CustomerFullPresenter,
|
||||||
ListCustomersPresenter,
|
ListCustomersPresenter,
|
||||||
ListCustomersUseCase,
|
ListCustomersUseCase,
|
||||||
@ -23,8 +24,8 @@ export type CustomerDeps = {
|
|||||||
build: {
|
build: {
|
||||||
list: () => ListCustomersUseCase;
|
list: () => ListCustomersUseCase;
|
||||||
get: () => GetCustomerUseCase;
|
get: () => GetCustomerUseCase;
|
||||||
/*create: () => CreateCustomerUseCase;
|
create: () => CreateCustomerUseCase;
|
||||||
update: () => UpdateCustomerUseCase;
|
/*update: () => UpdateCustomerUseCase;
|
||||||
delete: () => DeleteCustomerUseCase;*/
|
delete: () => DeleteCustomerUseCase;*/
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -64,9 +65,9 @@ export function buildCustomerDependencies(params: ModuleParams): CustomerDeps {
|
|||||||
service,
|
service,
|
||||||
build: {
|
build: {
|
||||||
list: () => new ListCustomersUseCase(service, transactionManager, presenterRegistry),
|
list: () => new ListCustomersUseCase(service, transactionManager, presenterRegistry),
|
||||||
/*get: () => new GetCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
get: () => new GetCustomerUseCase(service, transactionManager, presenterRegistry),
|
||||||
create: () => new CreateCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
create: () => new CreateCustomerUseCase(service, transactionManager, presenterRegistry),
|
||||||
update: () => new UpdateCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
/*update: () => new UpdateCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
||||||
delete: () => new DeleteCustomerUseCase(_service!, transactionManager!),*/
|
delete: () => new DeleteCustomerUseCase(_service!, transactionManager!),*/
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,9 +2,17 @@ import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth
|
|||||||
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
|
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
|
||||||
import { Application, NextFunction, Request, Response, Router } from "express";
|
import { Application, NextFunction, Request, Response, Router } from "express";
|
||||||
import { Sequelize } from "sequelize";
|
import { Sequelize } from "sequelize";
|
||||||
import { CustomerListRequestSchema, GetCustomerByIdRequestSchema } from "../../../common/dto";
|
import {
|
||||||
|
CreateCustomerRequestSchema,
|
||||||
|
CustomerListRequestSchema,
|
||||||
|
GetCustomerByIdRequestSchema,
|
||||||
|
} from "../../../common/dto";
|
||||||
import { buildCustomerDependencies } from "../dependencies";
|
import { buildCustomerDependencies } from "../dependencies";
|
||||||
import { GetCustomerController, ListCustomersController } from "./controllers";
|
import {
|
||||||
|
CreateCustomerController,
|
||||||
|
GetCustomerController,
|
||||||
|
ListCustomersController,
|
||||||
|
} from "./controllers";
|
||||||
|
|
||||||
export const customersRouter = (params: ModuleParams) => {
|
export const customersRouter = (params: ModuleParams) => {
|
||||||
const { app, database, baseRoutePath, logger } = params as {
|
const { app, database, baseRoutePath, logger } = params as {
|
||||||
@ -61,7 +69,7 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
@ -73,7 +81,7 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.put(
|
/*router.put(
|
||||||
"/:customer_id",
|
"/:customer_id",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(UpdateCustomerByIdParamsRequestSchema, "params"),
|
validateRequest(UpdateCustomerByIdParamsRequestSchema, "params"),
|
||||||
|
|||||||
@ -3,4 +3,5 @@ export * from "./aggregate-root-repository.interface";
|
|||||||
export * from "./domain-entity";
|
export * from "./domain-entity";
|
||||||
export * from "./events/domain-event.interface";
|
export * from "./events/domain-event.interface";
|
||||||
export * from "./helpers";
|
export * from "./helpers";
|
||||||
|
export * from "./specification";
|
||||||
export * from "./value-objects";
|
export * from "./value-objects";
|
||||||
|
|||||||
132
packages/rdx-ddd/src/specification.ts
Normal file
132
packages/rdx-ddd/src/specification.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
export interface IBaseSpecification<T> {
|
||||||
|
isSatisfiedBy(candidate: T): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICompositeSpecification<T> extends IBaseSpecification<T> {
|
||||||
|
and(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
||||||
|
andNot(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
||||||
|
or(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
||||||
|
orNot(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
||||||
|
not(): ICompositeSpecification<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class CompositeSpecification<T> implements ICompositeSpecification<T> {
|
||||||
|
abstract isSatisfiedBy(candidate: T): Promise<boolean>;
|
||||||
|
|
||||||
|
public and(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
||||||
|
return new AndSpecification<T>(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public andNot(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
||||||
|
return new AndNotSpecification<T>(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public or(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
||||||
|
return new OrSpecification<T>(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public orNot(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
||||||
|
return new OrNotSpecification<T>(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public not(): ICompositeSpecification<T> {
|
||||||
|
return new NotSpecification<T>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AndSpecification<T> extends CompositeSpecification<T> {
|
||||||
|
constructor(
|
||||||
|
private readonly left: ICompositeSpecification<T>,
|
||||||
|
private readonly right: ICompositeSpecification<T>
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
||||||
|
const leftResult = await this.left.isSatisfiedBy(candidate);
|
||||||
|
if (!leftResult) return false;
|
||||||
|
|
||||||
|
const rightResult = await this.right.isSatisfiedBy(candidate);
|
||||||
|
return rightResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `(${this.left.toString()} and ${this.right.toString()})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AndNotSpecification<T> extends AndSpecification<T> {
|
||||||
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
||||||
|
return (await super.isSatisfiedBy(candidate)) !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `not ${super.toString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrSpecification<T> extends CompositeSpecification<T> {
|
||||||
|
constructor(
|
||||||
|
private readonly left: ICompositeSpecification<T>,
|
||||||
|
private readonly right: ICompositeSpecification<T>
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
||||||
|
const leftResult = await this.left.isSatisfiedBy(candidate);
|
||||||
|
if (leftResult) return true;
|
||||||
|
|
||||||
|
const rightResult = await this.right.isSatisfiedBy(candidate);
|
||||||
|
return rightResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `(${this.left.toString()} or ${this.right.toString()})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrNotSpecification<T> extends OrSpecification<T> {
|
||||||
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
||||||
|
return (await super.isSatisfiedBy(candidate)) !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `not ${super.toString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotSpecification<T> extends CompositeSpecification<T> {
|
||||||
|
constructor(private readonly spec: ICompositeSpecification<T>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
||||||
|
return !this.spec.isSatisfiedBy(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `(not ${this.spec.toString()})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RangeSpecification<T> extends CompositeSpecification<T> {
|
||||||
|
private readonly min: T;
|
||||||
|
private readonly max: T;
|
||||||
|
private readonly compareFn: (a: T, b: T) => number;
|
||||||
|
|
||||||
|
constructor(min: T, max: T, compareFn: (a: T, b: T) => number) {
|
||||||
|
super();
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
this.compareFn = compareFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
||||||
|
return this.compareFn(candidate, this.min) >= 0 && this.compareFn(candidate, this.max) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `range [${this.min}, ${this.max}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user