Formas de pago
This commit is contained in:
parent
cd4b8975c3
commit
32dbd7d31f
@ -50,7 +50,10 @@
|
|||||||
"noInferrableTypes": "error",
|
"noInferrableTypes": "error",
|
||||||
"noNamespace": "error",
|
"noNamespace": "error",
|
||||||
"noNegationElse": "warn",
|
"noNegationElse": "warn",
|
||||||
"noNonNullAssertion": "info",
|
"noNonNullAssertion": {
|
||||||
|
"level": "info",
|
||||||
|
"fix": "none"
|
||||||
|
},
|
||||||
"noParameterAssign": "error",
|
"noParameterAssign": "error",
|
||||||
"noUnusedTemplateLiteral": "error",
|
"noUnusedTemplateLiteral": "error",
|
||||||
"noUselessElse": "warn",
|
"noUselessElse": "warn",
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
export * from "./payment-method-creator.di";
|
export * from "./payment-method-creator.di";
|
||||||
|
export * from "./payment-method-deleter.di";
|
||||||
export * from "./payment-method-finder.di";
|
export * from "./payment-method-finder.di";
|
||||||
export * from "./payment-method-input-mappers.di";
|
export * from "./payment-method-input-mappers.di";
|
||||||
export * from "./payment-method-snapshot-builders.di";
|
export * from "./payment-method-snapshot-builders.di";
|
||||||
|
export * from "./payment-method-status-changer.di";
|
||||||
export * from "./payment-method-updater.di";
|
export * from "./payment-method-updater.di";
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
import { type IPaymentMethodDeleter, PaymentMethodDeleter } from "../services";
|
||||||
|
|
||||||
|
export const buildPaymentMethodDeleter = (params: {
|
||||||
|
repository: IPaymentMethodRepository;
|
||||||
|
}): IPaymentMethodDeleter => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentMethodDeleter(repository);
|
||||||
|
};
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
import { type IPaymentMethodFinder, PaymentMethodFinder } from "../services";
|
import { type IPaymentMethodFinder, PaymentMethodFinder } from "../services";
|
||||||
|
|
||||||
export function buildPaymentMethodFinder(
|
export function buildPaymentMethodFinder(params: {
|
||||||
repository: IPaymentMethodRepository
|
repository: IPaymentMethodRepository;
|
||||||
): IPaymentMethodFinder {
|
}): IPaymentMethodFinder {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
return new PaymentMethodFinder(repository);
|
return new PaymentMethodFinder(repository);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
import { type IPaymentMethodStatusChanger, PaymentMethodStatusChanger } from "../services";
|
||||||
|
|
||||||
|
export const buildPaymentMethodStatusChanger = (params: {
|
||||||
|
repository: IPaymentMethodRepository;
|
||||||
|
}): IPaymentMethodStatusChanger => {
|
||||||
|
const { repository } = params;
|
||||||
|
|
||||||
|
return new PaymentMethodStatusChanger(repository);
|
||||||
|
};
|
||||||
@ -1,5 +1,8 @@
|
|||||||
export * from "./payment-method-creator";
|
export * from "./payment-method-creator";
|
||||||
|
export * from "./payment-method-deleter";
|
||||||
export * from "./payment-method-disabler";
|
export * from "./payment-method-disabler";
|
||||||
|
export * from "./payment-method-enabler";
|
||||||
export * from "./payment-method-finder";
|
export * from "./payment-method-finder";
|
||||||
export * from "./payment-method-public-services";
|
export * from "./payment-method-public-services";
|
||||||
|
export * from "./payment-method-status-changer";
|
||||||
export * from "./payment-method-updater";
|
export * from "./payment-method-updater";
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentMethod } from "../../domain";
|
||||||
|
import { PaymentMethodCannotBeDeletedError } from "../../domain";
|
||||||
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface IPaymentMethodDeleter {
|
||||||
|
delete(params: {
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentMethod, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentMethodDeleter implements IPaymentMethodDeleter {
|
||||||
|
public constructor(private readonly repository: IPaymentMethodRepository) {}
|
||||||
|
|
||||||
|
public async delete(params: {
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentMethod, Error>> {
|
||||||
|
const { paymentMethod, transaction } = params;
|
||||||
|
|
||||||
|
if (paymentMethod.isSystem) {
|
||||||
|
return Result.fail(
|
||||||
|
new PaymentMethodCannotBeDeletedError("System payment methods cannot be deleted.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteResult = await this.repository.deleteByIdInCompany(
|
||||||
|
paymentMethod.companyId,
|
||||||
|
paymentMethod.id,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deleteResult.isFailure) {
|
||||||
|
return Result.fail(deleteResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { PaymentMethod } from "../../domain";
|
import type { PaymentMethod } from "../../domain";
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
@ -7,7 +6,7 @@ import type { IPaymentMethodRepository } from "../repositories";
|
|||||||
export interface IPaymentMethodDisabler {
|
export interface IPaymentMethodDisabler {
|
||||||
disable(params: {
|
disable(params: {
|
||||||
paymentMethod: PaymentMethod;
|
paymentMethod: PaymentMethod;
|
||||||
transaction?: Transaction;
|
transaction?: unknown;
|
||||||
}): Promise<Result<PaymentMethod, Error>>;
|
}): Promise<Result<PaymentMethod, Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,16 +15,22 @@ export class PaymentMethodDisabler implements IPaymentMethodDisabler {
|
|||||||
|
|
||||||
async disable(params: {
|
async disable(params: {
|
||||||
paymentMethod: PaymentMethod;
|
paymentMethod: PaymentMethod;
|
||||||
transaction?: Transaction;
|
transaction?: unknown;
|
||||||
}): Promise<Result<PaymentMethod, Error>> {
|
}): Promise<Result<PaymentMethod, Error>> {
|
||||||
const { paymentMethod, transaction } = params;
|
const { paymentMethod, transaction } = params;
|
||||||
|
|
||||||
const disableResult = paymentMethod.disable();
|
const disableResult = paymentMethod.disable();
|
||||||
|
|
||||||
if (disableResult.isFailure) {
|
if (disableResult.isFailure) {
|
||||||
return Result.fail(disableResult.error);
|
return Result.fail(disableResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!disableResult.data) {
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
const persistenceResult = await this.repository.update(paymentMethod, transaction);
|
const persistenceResult = await this.repository.update(paymentMethod, transaction);
|
||||||
|
|
||||||
if (persistenceResult.isFailure) {
|
if (persistenceResult.isFailure) {
|
||||||
return Result.fail(persistenceResult.error);
|
return Result.fail(persistenceResult.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentMethod } from "../../domain";
|
||||||
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface IPaymentMethodEnabler {
|
||||||
|
enable(params: {
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentMethod, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentMethodEnabler implements IPaymentMethodEnabler {
|
||||||
|
constructor(private readonly repository: IPaymentMethodRepository) {}
|
||||||
|
|
||||||
|
async enable(params: {
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentMethod, Error>> {
|
||||||
|
const { paymentMethod, transaction } = params;
|
||||||
|
|
||||||
|
const enableResult = paymentMethod.enable();
|
||||||
|
|
||||||
|
if (enableResult.isFailure) {
|
||||||
|
return Result.fail(enableResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enableResult.data) {
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistenceResult = await this.repository.update(paymentMethod, transaction);
|
||||||
|
|
||||||
|
if (persistenceResult.isFailure) {
|
||||||
|
return Result.fail(persistenceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { PaymentMethod } from "../../domain";
|
||||||
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
|
export type PaymentMethodStatusChangeAction = "enable" | "disable";
|
||||||
|
|
||||||
|
export interface IPaymentMethodStatusChanger {
|
||||||
|
changeStatus(params: {
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
action: PaymentMethodStatusChangeAction;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentMethod, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentMethodStatusChanger implements IPaymentMethodStatusChanger {
|
||||||
|
public constructor(private readonly repository: IPaymentMethodRepository) {}
|
||||||
|
|
||||||
|
public async changeStatus(params: {
|
||||||
|
paymentMethod: PaymentMethod;
|
||||||
|
action: PaymentMethodStatusChangeAction;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<PaymentMethod, Error>> {
|
||||||
|
const { paymentMethod, action, transaction } = params;
|
||||||
|
|
||||||
|
const statusResult = action === "enable" ? paymentMethod.enable() : paymentMethod.disable();
|
||||||
|
|
||||||
|
if (statusResult.isFailure) {
|
||||||
|
return Result.fail(statusResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statusResult.data) {
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistenceResult = await this.repository.update(paymentMethod, transaction);
|
||||||
|
|
||||||
|
if (persistenceResult.isFailure) {
|
||||||
|
return Result.fail(persistenceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,6 +40,13 @@ export class PaymentMethodUpdater implements IPaymentMethodUpdater {
|
|||||||
return Result.fail(updateResult.error);
|
return Result.fail(updateResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasChanges = updateResult.data;
|
||||||
|
|
||||||
|
if (!hasChanges) {
|
||||||
|
// No hay cambios, retornar el agregado original
|
||||||
|
return Result.ok(paymentMethod);
|
||||||
|
}
|
||||||
|
|
||||||
// Persistir cambios
|
// Persistir cambios
|
||||||
const saveResult = await this.repository.update(paymentMethod, transaction);
|
const saveResult = await this.repository.update(paymentMethod, transaction);
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export class PaymentMethodFullSnapshotBuilder implements IPaymentMethodFullSnaps
|
|||||||
public toOutput(paymentMethod: PaymentMethod): GetPaymentMethodByIdResponseDTO {
|
public toOutput(paymentMethod: PaymentMethod): GetPaymentMethodByIdResponseDTO {
|
||||||
return {
|
return {
|
||||||
id: paymentMethod.id.toPrimitive(),
|
id: paymentMethod.id.toPrimitive(),
|
||||||
|
company_id: paymentMethod.companyId.toPrimitive(),
|
||||||
name: paymentMethod.name.toPrimitive(),
|
name: paymentMethod.name.toPrimitive(),
|
||||||
description: toNullable(paymentMethod.description, (value) => value.toPrimitive()),
|
description: toNullable(paymentMethod.description, (value) => value.toPrimitive()),
|
||||||
is_active: paymentMethod.isActive,
|
is_active: paymentMethod.isActive,
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IPaymentMethodDeleter, IPaymentMethodFinder } from "../services";
|
||||||
|
import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type DeletePaymentMethodByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_method_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DeletePaymentMethodByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
finder: IPaymentMethodFinder;
|
||||||
|
deleter: IPaymentMethodDeleter;
|
||||||
|
fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: DeletePaymentMethodByIdUseCaseInput) {
|
||||||
|
const { payment_method_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_method_id);
|
||||||
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
|
|
||||||
|
const paymentMethodId = idOrError.data;
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const findResult = await this.deps.finder.findPaymentMethodById(
|
||||||
|
companyId,
|
||||||
|
paymentMethodId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
if (findResult.isFailure) {
|
||||||
|
return Result.fail(findResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteResult = await this.deps.deleter.delete({
|
||||||
|
paymentMethod: findResult.data,
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deleteResult.isFailure) {
|
||||||
|
return Result.fail(deleteResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(deleteResult.data));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,39 +1,50 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { IPaymentMethodDisabler, IPaymentMethodFinder } from "../services";
|
import type { IPaymentMethodFinder, IPaymentMethodStatusChanger } from "../services";
|
||||||
import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
|
import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
export type DisablePaymentMethodByIdUseCaseInput = {
|
export type DisablePaymentMethodByIdUseCaseInput = {
|
||||||
id: string;
|
companyId: UniqueID;
|
||||||
|
payment_method_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DisablePaymentMethodByIdUseCase {
|
export class DisablePaymentMethodByIdUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly deps: {
|
private readonly deps: {
|
||||||
finder: IPaymentMethodFinder;
|
finder: IPaymentMethodFinder;
|
||||||
disabler: IPaymentMethodDisabler;
|
changer: IPaymentMethodStatusChanger;
|
||||||
fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder;
|
fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder;
|
||||||
transactionManager: ITransactionManager;
|
transactionManager: ITransactionManager;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: DisablePaymentMethodByIdUseCaseInput) {
|
public execute(params: DisablePaymentMethodByIdUseCaseInput) {
|
||||||
const { id } = params;
|
const { payment_method_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_method_id);
|
||||||
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
|
|
||||||
|
const paymentMethodId = idOrError.data;
|
||||||
|
|
||||||
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
||||||
const tx = transaction as Transaction;
|
|
||||||
try {
|
try {
|
||||||
const findResult = await this.deps.finder.getById(id, tx);
|
const findResult = await this.deps.finder.findPaymentMethodById(
|
||||||
|
companyId,
|
||||||
|
paymentMethodId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
if (findResult.isFailure) {
|
if (findResult.isFailure) {
|
||||||
return Result.fail(findResult.error);
|
return Result.fail(findResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableResult = await this.deps.disabler.disable({
|
const disableResult = await this.deps.changer.changeStatus({
|
||||||
paymentMethod: findResult.data,
|
paymentMethod: findResult.data,
|
||||||
transaction: tx,
|
action: "disable",
|
||||||
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (disableResult.isFailure) {
|
if (disableResult.isFailure) {
|
||||||
return Result.fail(disableResult.error);
|
return Result.fail(disableResult.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,58 @@
|
|||||||
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IPaymentMethodFinder, IPaymentMethodStatusChanger } from "../services";
|
||||||
|
import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
|
export type EnablePaymentMethodByIdUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
payment_method_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EnablePaymentMethodByIdUseCase {
|
||||||
|
constructor(
|
||||||
|
private readonly deps: {
|
||||||
|
finder: IPaymentMethodFinder;
|
||||||
|
changer: IPaymentMethodStatusChanger;
|
||||||
|
fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public execute(params: EnablePaymentMethodByIdUseCaseInput) {
|
||||||
|
const { payment_method_id, companyId } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(payment_method_id);
|
||||||
|
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
||||||
|
|
||||||
|
const paymentMethodId = idOrError.data;
|
||||||
|
|
||||||
|
return this.deps.transactionManager.complete(async (transaction: unknown) => {
|
||||||
|
try {
|
||||||
|
const findResult = await this.deps.finder.findPaymentMethodById(
|
||||||
|
companyId,
|
||||||
|
paymentMethodId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
if (findResult.isFailure) {
|
||||||
|
return Result.fail(findResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableResult = await this.deps.changer.changeStatus({
|
||||||
|
paymentMethod: findResult.data,
|
||||||
|
action: "enable",
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (enableResult.isFailure) {
|
||||||
|
return Result.fail(enableResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(this.deps.fullSnapshotBuilder.toOutput(enableResult.data));
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
export * from "./create-payment-method.use-case";
|
export * from "./create-payment-method.use-case";
|
||||||
|
export * from "./delete-payment-method-by-id.use-case";
|
||||||
export * from "./disable-payment-method-by-id.use-case";
|
export * from "./disable-payment-method-by-id.use-case";
|
||||||
|
export * from "./enable-payment-method-by-id.use-case";
|
||||||
export * from "./get-payment-method-by-id.use-case";
|
export * from "./get-payment-method-by-id.use-case";
|
||||||
export * from "./list-payment-methods.use-case";
|
export * from "./list-payment-methods.use-case";
|
||||||
export * from "./update-payment-method-by-id.use-case";
|
export * from "./update-payment-method-by-id.use-case";
|
||||||
|
|||||||
@ -7,6 +7,8 @@ export class InvalidPaymentMethodIdError extends DomainError {
|
|||||||
export const isInvalidPaymentMethodIdError = (e: unknown): e is InvalidPaymentMethodIdError =>
|
export const isInvalidPaymentMethodIdError = (e: unknown): e is InvalidPaymentMethodIdError =>
|
||||||
e instanceof InvalidPaymentMethodIdError;
|
e instanceof InvalidPaymentMethodIdError;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
export class InvalidPaymentMethodNameError extends DomainError {
|
export class InvalidPaymentMethodNameError extends DomainError {
|
||||||
public readonly code = "PAYMENT_METHOD_NAME" as const;
|
public readonly code = "PAYMENT_METHOD_NAME" as const;
|
||||||
}
|
}
|
||||||
@ -14,10 +16,17 @@ export class InvalidPaymentMethodNameError extends DomainError {
|
|||||||
export const isInvalidPaymentMethodNameError = (e: unknown): e is InvalidPaymentMethodNameError =>
|
export const isInvalidPaymentMethodNameError = (e: unknown): e is InvalidPaymentMethodNameError =>
|
||||||
e instanceof InvalidPaymentMethodNameError;
|
e instanceof InvalidPaymentMethodNameError;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
export class PaymentMethodNotFoundError extends DomainError {
|
export class PaymentMethodNotFoundError extends DomainError {
|
||||||
public readonly code = "PAYMENT_METHOD_NOT_FOUND" as const;
|
public readonly code = "PAYMENT_METHOD_NOT_FOUND" as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isPaymentMethodNotFoundError = (e: unknown): e is PaymentMethodNotFoundError =>
|
||||||
|
e instanceof PaymentMethodNotFoundError;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
export class PaymentMethodCannotBeDeletedError extends DomainError {
|
export class PaymentMethodCannotBeDeletedError extends DomainError {
|
||||||
public readonly code = "PAYMENT_METHOD_CANNOT_BE_DELETED" as const;
|
public readonly code = "PAYMENT_METHOD_CANNOT_BE_DELETED" as const;
|
||||||
}
|
}
|
||||||
@ -25,3 +34,33 @@ export class PaymentMethodCannotBeDeletedError extends DomainError {
|
|||||||
export const isPaymentMethodCannotBeDeletedError = (
|
export const isPaymentMethodCannotBeDeletedError = (
|
||||||
e: unknown
|
e: unknown
|
||||||
): e is PaymentMethodCannotBeDeletedError => e instanceof PaymentMethodCannotBeDeletedError;
|
): e is PaymentMethodCannotBeDeletedError => e instanceof PaymentMethodCannotBeDeletedError;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
export class PaymentMethodCannotBeDisabledError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_METHOD_CANNOT_BE_DISABLED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentMethodCannotBeDisabledError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentMethodCannotBeDisabledError => e instanceof PaymentMethodCannotBeDisabledError;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
export class PaymentMethodCannotBeEnabledError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_METHOD_CANNOT_BE_ENABLED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentMethodCannotBeEnabledError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentMethodCannotBeEnabledError => e instanceof PaymentMethodCannotBeEnabledError;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
export class PaymentMethodCannotBeUpdatedError extends DomainError {
|
||||||
|
public readonly code = "PAYMENT_METHOD_CANNOT_BE_UPDATED" as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPaymentMethodCannotBeUpdatedError = (
|
||||||
|
e: unknown
|
||||||
|
): e is PaymentMethodCannotBeUpdatedError => e instanceof PaymentMethodCannotBeUpdatedError;
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { AggregateRoot, type Name, type TextValue, type UniqueID } from "@repo/rdx-ddd";
|
import { AggregateRoot, type Name, type TextValue, type UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { PaymentMethodCannotBeDisabledError, PaymentMethodCannotBeUpdatedError } from "./errors";
|
||||||
|
|
||||||
export interface IPaymentMethodCreateProps {
|
export interface IPaymentMethodCreateProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
name: Name;
|
name: Name;
|
||||||
@ -9,14 +11,14 @@ export interface IPaymentMethodCreateProps {
|
|||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaymentMethodPatchProps = Partial<
|
export type PaymentMethodPatchProps = Partial<{
|
||||||
Omit<IPaymentMethodCreateProps, "companyId" | "isSystem">
|
name: Name;
|
||||||
>;
|
description: Maybe<TextValue>;
|
||||||
|
isActive: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type PaymentMethodInternalProps = IPaymentMethodCreateProps;
|
export type PaymentMethodInternalProps = IPaymentMethodCreateProps;
|
||||||
|
|
||||||
export type PaymentMethodProps = PaymentMethodPatchProps;
|
|
||||||
|
|
||||||
export class PaymentMethod extends AggregateRoot<PaymentMethodInternalProps> {
|
export class PaymentMethod extends AggregateRoot<PaymentMethodInternalProps> {
|
||||||
protected constructor(props: PaymentMethodInternalProps, id?: UniqueID) {
|
protected constructor(props: PaymentMethodInternalProps, id?: UniqueID) {
|
||||||
super(props, id); // eslint-disable-line @typescript-eslint/no-unused-vars
|
super(props, id); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
@ -46,7 +48,23 @@ export class PaymentMethod extends AggregateRoot<PaymentMethodInternalProps> {
|
|||||||
return new PaymentMethod(props, id);
|
return new PaymentMethod(props, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static validateCreateProps(_props: IPaymentMethodCreateProps): Result<void, Error> {
|
private static validateCreateProps(props: IPaymentMethodCreateProps): Result<void, Error> {
|
||||||
|
if (!props.companyId) {
|
||||||
|
return Result.fail(new Error("Payment method company ID is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.name) {
|
||||||
|
return Result.fail(new Error("Payment method name is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validatePatchProps(patchProps: PaymentMethodPatchProps): Result<void, Error> {
|
||||||
|
if (Object.keys(patchProps).length === 0) {
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,38 +88,96 @@ export class PaymentMethod extends AggregateRoot<PaymentMethodInternalProps> {
|
|||||||
return this.props.isSystem;
|
return this.props.isSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(props: Partial<PaymentMethodPatchProps>): Result<void, Error> {
|
public update(patchProps: PaymentMethodPatchProps): Result<boolean, Error> {
|
||||||
if (props.name !== undefined) {
|
if (this.isSystem) {
|
||||||
this.props.name = props.name;
|
return Result.fail(
|
||||||
|
new PaymentMethodCannotBeUpdatedError("System payment methods cannot be updated.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const validationResult = PaymentMethod.validatePatchProps(patchProps);
|
||||||
|
|
||||||
|
if (validationResult.isFailure) {
|
||||||
|
return Result.fail(validationResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.description !== undefined) {
|
let hasChanges = false;
|
||||||
this.props.description = props.description;
|
|
||||||
|
if (
|
||||||
|
patchProps.name !== undefined &&
|
||||||
|
this.props.name.toPrimitive() !== patchProps.name.toPrimitive()
|
||||||
|
) {
|
||||||
|
this.props.name = patchProps.name;
|
||||||
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.isActive !== undefined) {
|
if (
|
||||||
this.props.isActive = props.isActive;
|
patchProps.description !== undefined &&
|
||||||
|
!PaymentMethod.sameDescription(this.props.description, patchProps.description)
|
||||||
|
) {
|
||||||
|
this.props.description = patchProps.description;
|
||||||
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok();
|
if (patchProps.isActive !== undefined && this.props.isActive !== patchProps.isActive) {
|
||||||
|
this.props.isActive = patchProps.isActive;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(hasChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
public disable(): Result<void, Error> {
|
public disable(): Result<boolean, Error> {
|
||||||
|
if (this.isSystem) {
|
||||||
|
return Result.fail(
|
||||||
|
new PaymentMethodCannotBeDisabledError("System payment methods cannot be disabled.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isActive) {
|
if (!this.isActive) {
|
||||||
return Result.ok();
|
return Result.ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.isActive = false;
|
this.props.isActive = false;
|
||||||
return Result.ok();
|
|
||||||
|
return Result.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enable(): Result<boolean, Error> {
|
||||||
|
if (this.isSystem) {
|
||||||
|
return Result.ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isActive) {
|
||||||
|
return Result.ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.isActive = true;
|
||||||
|
|
||||||
|
return Result.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public toJSON() {
|
public toJSON() {
|
||||||
return {
|
return {
|
||||||
id: this.id.toPrimitive(),
|
id: this.id.toPrimitive(),
|
||||||
name: this.props.name.toPrimitive(),
|
company_id: this.companyId.toPrimitive(),
|
||||||
description: this.props.description,
|
name: this.name.toPrimitive(),
|
||||||
|
description: this.description.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
is_active: this.isActive,
|
is_active: this.isActive,
|
||||||
is_system: this.isSystem,
|
is_system: this.isSystem,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static sameDescription(current: Maybe<TextValue>, next: Maybe<TextValue>): boolean {
|
||||||
|
return current.match(
|
||||||
|
(currentValue) =>
|
||||||
|
next.match(
|
||||||
|
(nextValue) => currentValue.toPrimitive() === nextValue.toPrimitive(),
|
||||||
|
() => false
|
||||||
|
),
|
||||||
|
() => next.isNone()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,16 +3,21 @@ import type { Sequelize } from "sequelize";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
buildPaymentMethodCreator,
|
buildPaymentMethodCreator,
|
||||||
|
buildPaymentMethodDeleter,
|
||||||
buildPaymentMethodFinder,
|
buildPaymentMethodFinder,
|
||||||
buildPaymentMethodInputMappers,
|
buildPaymentMethodInputMappers,
|
||||||
buildPaymentMethodSnapshotBuilders,
|
buildPaymentMethodSnapshotBuilders,
|
||||||
|
buildPaymentMethodStatusChanger,
|
||||||
|
buildPaymentMethodUpdater,
|
||||||
} from "../../application";
|
} from "../../application";
|
||||||
import type { IPaymentMethodRepository } from "../../application/repositories";
|
import type { IPaymentMethodRepository } from "../../application/repositories";
|
||||||
import type { IPaymentMethodFinder } from "../../application/services";
|
import type { IPaymentMethodFinder } from "../../application/services";
|
||||||
import { PaymentMethodFinder, PaymentMethodUpdater } from "../../application/services";
|
import { PaymentMethodFinder } from "../../application/services";
|
||||||
import {
|
import {
|
||||||
CreatePaymentMethodUseCase,
|
CreatePaymentMethodUseCase,
|
||||||
type DisablePaymentMethodByIdUseCase,
|
DeletePaymentMethodByIdUseCase,
|
||||||
|
DisablePaymentMethodByIdUseCase,
|
||||||
|
EnablePaymentMethodByIdUseCase,
|
||||||
GetPaymentMethodByIdUseCase,
|
GetPaymentMethodByIdUseCase,
|
||||||
ListPaymentMethodsUseCase,
|
ListPaymentMethodsUseCase,
|
||||||
UpdatePaymentMethodByIdUseCase,
|
UpdatePaymentMethodByIdUseCase,
|
||||||
@ -28,7 +33,9 @@ export type PaymentMethodsInternalDeps = {
|
|||||||
getPaymentMethodById: () => GetPaymentMethodByIdUseCase;
|
getPaymentMethodById: () => GetPaymentMethodByIdUseCase;
|
||||||
createPaymentMethod: () => CreatePaymentMethodUseCase;
|
createPaymentMethod: () => CreatePaymentMethodUseCase;
|
||||||
updatePaymentMethodById: () => UpdatePaymentMethodByIdUseCase;
|
updatePaymentMethodById: () => UpdatePaymentMethodByIdUseCase;
|
||||||
|
deletePaymentMethodById: () => DeletePaymentMethodByIdUseCase;
|
||||||
disablePaymentMethodById: () => DisablePaymentMethodByIdUseCase;
|
disablePaymentMethodById: () => DisablePaymentMethodByIdUseCase;
|
||||||
|
enablePaymentMethodById: () => EnablePaymentMethodByIdUseCase;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,10 +52,11 @@ export const buildPaymentMethodsDependencies = (
|
|||||||
|
|
||||||
// Application helpers
|
// Application helpers
|
||||||
const inputMappers = buildPaymentMethodInputMappers();
|
const inputMappers = buildPaymentMethodInputMappers();
|
||||||
const finder = buildPaymentMethodFinder(repository);
|
const finder = buildPaymentMethodFinder({ repository });
|
||||||
const creator = buildPaymentMethodCreator({ repository });
|
const creator = buildPaymentMethodCreator({ repository });
|
||||||
const updater = new PaymentMethodUpdater(repository);
|
const updater = buildPaymentMethodUpdater({ repository });
|
||||||
//const disabler = new PaymentMethodDisabler(repository);
|
const deleter = buildPaymentMethodDeleter({ repository });
|
||||||
|
const statusChanger = buildPaymentMethodStatusChanger({ repository });
|
||||||
|
|
||||||
const snapshotBuilders = buildPaymentMethodSnapshotBuilders();
|
const snapshotBuilders = buildPaymentMethodSnapshotBuilders();
|
||||||
|
|
||||||
@ -57,8 +65,10 @@ export const buildPaymentMethodsDependencies = (
|
|||||||
useCases: {
|
useCases: {
|
||||||
listPaymentMethods: () =>
|
listPaymentMethods: () =>
|
||||||
new ListPaymentMethodsUseCase(finder, snapshotBuilders.summary, transactionManager),
|
new ListPaymentMethodsUseCase(finder, snapshotBuilders.summary, transactionManager),
|
||||||
|
|
||||||
getPaymentMethodById: () =>
|
getPaymentMethodById: () =>
|
||||||
new GetPaymentMethodByIdUseCase(finder, snapshotBuilders.full, transactionManager),
|
new GetPaymentMethodByIdUseCase(finder, snapshotBuilders.full, transactionManager),
|
||||||
|
|
||||||
createPaymentMethod: () =>
|
createPaymentMethod: () =>
|
||||||
new CreatePaymentMethodUseCase({
|
new CreatePaymentMethodUseCase({
|
||||||
dtoMapper: inputMappers.createInputMapper,
|
dtoMapper: inputMappers.createInputMapper,
|
||||||
@ -75,13 +85,30 @@ export const buildPaymentMethodsDependencies = (
|
|||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
/*disablePaymentMethodById: () =>
|
|
||||||
|
deletePaymentMethodById: () =>
|
||||||
|
new DeletePaymentMethodByIdUseCase({
|
||||||
|
deleter,
|
||||||
|
finder,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
|
|
||||||
|
disablePaymentMethodById: () =>
|
||||||
new DisablePaymentMethodByIdUseCase({
|
new DisablePaymentMethodByIdUseCase({
|
||||||
finder,
|
finder,
|
||||||
disabler,
|
changer: statusChanger,
|
||||||
fullSnapshotBuilder,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),*/
|
}),
|
||||||
|
|
||||||
|
enablePaymentMethodById: () =>
|
||||||
|
new EnablePaymentMethodByIdUseCase({
|
||||||
|
finder,
|
||||||
|
changer: statusChanger,
|
||||||
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,13 +7,13 @@ import {
|
|||||||
|
|
||||||
import type { CreatePaymentMethodRequestDTO } from "../../../../../common";
|
import type { CreatePaymentMethodRequestDTO } from "../../../../../common";
|
||||||
import type { CreatePaymentMethodUseCase } from "../../../../application";
|
import type { CreatePaymentMethodUseCase } from "../../../../application";
|
||||||
import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class CreatePaymentMethodController extends ExpressController {
|
export class CreatePaymentMethodController extends ExpressController {
|
||||||
constructor(private readonly useCase: CreatePaymentMethodUseCase) {
|
constructor(private readonly useCase: CreatePaymentMethodUseCase) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.errorMapper = paymentmethodsApiErrorMapper;
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.registerGuards(
|
this.registerGuards(
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { DeletePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
|
export class DeletePaymentMethodByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: DeletePaymentMethodByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_method_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_method_id, companyId });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.ok(data),
|
||||||
|
(err) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,35 @@
|
|||||||
import { ExpressController } from "@erp/core/api";
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { DisablePaymentMethodByIdUseCase } from "../../../../application";
|
import type { DisablePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class DisablePaymentMethodByIdController extends ExpressController {
|
export class DisablePaymentMethodByIdController extends ExpressController {
|
||||||
constructor(private readonly useCase: DisablePaymentMethodByIdUseCase) {
|
constructor(private readonly useCase: DisablePaymentMethodByIdUseCase) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async executeImpl() {
|
protected async executeImpl() {
|
||||||
const id = this.req.params.payment_method_id;
|
const companyId = this.getTenantId();
|
||||||
const result = await this.useCase.execute({ id });
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_method_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_method_id, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
(data) => this.ok(data),
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
ExpressController,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
requireAuthenticatedGuard,
|
||||||
|
requireCompanyContextGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { EnablePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
|
export class EnablePaymentMethodByIdController extends ExpressController {
|
||||||
|
constructor(private readonly useCase: EnablePaymentMethodByIdUseCase) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
|
this.registerGuards(
|
||||||
|
requireAuthenticatedGuard(),
|
||||||
|
requireCompanyContextGuard(),
|
||||||
|
forbidQueryFieldGuard("companyId")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId();
|
||||||
|
if (!companyId) {
|
||||||
|
return this.forbiddenError("Tenant ID not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { payment_method_id } = this.req.params;
|
||||||
|
const result = await this.useCase.execute({ payment_method_id, companyId });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.ok(data),
|
||||||
|
(err) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,13 +6,13 @@ import {
|
|||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { GetPaymentMethodByIdUseCase } from "../../../../application";
|
import type { GetPaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class GetPaymentMethodByIdController extends ExpressController {
|
export class GetPaymentMethodByIdController extends ExpressController {
|
||||||
constructor(private readonly useCase: GetPaymentMethodByIdUseCase) {
|
constructor(private readonly useCase: GetPaymentMethodByIdUseCase) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.errorMapper = paymentmethodsApiErrorMapper;
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.registerGuards(
|
this.registerGuards(
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
export * from "./create-payment-method.controller";
|
export * from "./create-payment-method.controller";
|
||||||
|
export * from "./delete-payment-method-by-id.controller";
|
||||||
export * from "./disable-payment-method-by-id.controller";
|
export * from "./disable-payment-method-by-id.controller";
|
||||||
|
export * from "./enable-payment-method-by-id.controller";
|
||||||
export * from "./get-payment-method-by-id.controller";
|
export * from "./get-payment-method-by-id.controller";
|
||||||
export * from "./list-payment-methods.controller";
|
export * from "./list-payment-methods.controller";
|
||||||
export * from "./update-payment-method-by-id.controller";
|
export * from "./update-payment-method-by-id.controller";
|
||||||
|
|||||||
@ -7,12 +7,12 @@ import {
|
|||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
|
||||||
import type { ListPaymentMethodsUseCase } from "../../../../application";
|
import type { ListPaymentMethodsUseCase } from "../../../../application";
|
||||||
import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class ListPaymentMethodsController extends ExpressController {
|
export class ListPaymentMethodsController extends ExpressController {
|
||||||
constructor(private readonly useCase: ListPaymentMethodsUseCase) {
|
constructor(private readonly useCase: ListPaymentMethodsUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = paymentmethodsApiErrorMapper;
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.registerGuards(
|
this.registerGuards(
|
||||||
|
|||||||
@ -7,13 +7,13 @@ import {
|
|||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { UpdatePaymentMethodByIdUseCase } from "../../../../application";
|
import type { UpdatePaymentMethodByIdUseCase } from "../../../../application";
|
||||||
import { paymentmethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
|
||||||
|
|
||||||
export class UpdatePaymentMethodByIdController extends ExpressController {
|
export class UpdatePaymentMethodByIdController extends ExpressController {
|
||||||
constructor(private readonly useCase: UpdatePaymentMethodByIdUseCase) {
|
constructor(private readonly useCase: UpdatePaymentMethodByIdUseCase) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.errorMapper = paymentmethodsApiErrorMapper;
|
this.errorMapper = paymentMethodsApiErrorMapper;
|
||||||
|
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.registerGuards(
|
this.registerGuards(
|
||||||
|
|||||||
@ -2,37 +2,96 @@ import {
|
|||||||
ApiErrorMapper,
|
ApiErrorMapper,
|
||||||
ConflictApiError,
|
ConflictApiError,
|
||||||
type ErrorToApiRule,
|
type ErrorToApiRule,
|
||||||
|
NotFoundApiError,
|
||||||
ValidationApiError,
|
ValidationApiError,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type InvalidPaymentMethodIdError,
|
type InvalidPaymentMethodIdError,
|
||||||
|
type InvalidPaymentMethodNameError,
|
||||||
type PaymentMethodCannotBeDeletedError,
|
type PaymentMethodCannotBeDeletedError,
|
||||||
|
type PaymentMethodCannotBeDisabledError,
|
||||||
|
type PaymentMethodCannotBeEnabledError,
|
||||||
|
type PaymentMethodCannotBeUpdatedError,
|
||||||
|
type PaymentMethodNotFoundError,
|
||||||
isInvalidPaymentMethodIdError,
|
isInvalidPaymentMethodIdError,
|
||||||
|
isInvalidPaymentMethodNameError,
|
||||||
isPaymentMethodCannotBeDeletedError,
|
isPaymentMethodCannotBeDeletedError,
|
||||||
|
isPaymentMethodCannotBeDisabledError,
|
||||||
|
isPaymentMethodCannotBeEnabledError,
|
||||||
|
isPaymentMethodCannotBeUpdatedError,
|
||||||
|
isPaymentMethodNotFoundError,
|
||||||
} from "../../../domain/payment-methods";
|
} from "../../../domain/payment-methods";
|
||||||
|
|
||||||
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
const invalidPaymentMethodIdRule: ErrorToApiRule = {
|
||||||
const paymentmethodDuplicateRule: ErrorToApiRule = {
|
|
||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isInvalidPaymentMethodIdError(e),
|
matches: isInvalidPaymentMethodIdError,
|
||||||
build: (e) =>
|
build: (error) =>
|
||||||
new ConflictApiError(
|
new ConflictApiError(
|
||||||
(e as InvalidPaymentMethodIdError).message ||
|
(error as InvalidPaymentMethodIdError).message ||
|
||||||
"Payment method with the provided id already exists."
|
"Payment method with the provided id already exists."
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const paymentmethodCannotBeDeletedRule: ErrorToApiRule = {
|
const invalidPaymentMethodNameRule: ErrorToApiRule = {
|
||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isPaymentMethodCannotBeDeletedError(e),
|
matches: isInvalidPaymentMethodNameError,
|
||||||
build: (e) =>
|
build: (error) =>
|
||||||
new ValidationApiError(
|
new ValidationApiError(
|
||||||
(e as PaymentMethodCannotBeDeletedError).message || "Payment method cannot be deleted."
|
(error as InvalidPaymentMethodNameError).message || "Payment method name is invalid."
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
const paymentMethodNotFoundRule: ErrorToApiRule = {
|
||||||
export const paymentmethodsApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
priority: 120,
|
||||||
.register(paymentmethodDuplicateRule)
|
matches: isPaymentMethodNotFoundError,
|
||||||
.register(paymentmethodCannotBeDeletedRule);
|
build: (error) =>
|
||||||
|
new NotFoundApiError(
|
||||||
|
(error as PaymentMethodNotFoundError).message || "Payment method not found."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentMethodCannotBeDeletedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentMethodCannotBeDeletedError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentMethodCannotBeDeletedError).message || "Payment method cannot be deleted."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentMethodCannotBeDisabledRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentMethodCannotBeDisabledError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentMethodCannotBeDisabledError).message || "Payment method cannot be disabled."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentMethodCannotBeEnabledRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentMethodCannotBeEnabledError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentMethodCannotBeEnabledError).message || "Payment method cannot be enabled."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentMethodCannotBeUpdatedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: isPaymentMethodCannotBeUpdatedError,
|
||||||
|
build: (error) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(error as PaymentMethodCannotBeUpdatedError).message || "Payment method cannot be updated."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const paymentMethodsApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
||||||
|
.register(invalidPaymentMethodIdRule)
|
||||||
|
.register(invalidPaymentMethodNameRule)
|
||||||
|
.register(paymentMethodNotFoundRule)
|
||||||
|
.register(paymentMethodCannotBeDeletedRule)
|
||||||
|
.register(paymentMethodCannotBeDisabledRule)
|
||||||
|
.register(paymentMethodCannotBeEnabledRule)
|
||||||
|
.register(paymentMethodCannotBeUpdatedRule);
|
||||||
|
|||||||
@ -4,16 +4,19 @@ import { type NextFunction, type Request, type Response, Router } from "express"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CreatePaymentMethodRequestSchema,
|
CreatePaymentMethodRequestSchema,
|
||||||
|
DeletePaymentMethodByIdRequestSchema,
|
||||||
GetPaymentMethodByIdRequestSchema,
|
GetPaymentMethodByIdRequestSchema,
|
||||||
ListPaymentMethodsRequestSchema,
|
ListPaymentMethodsRequestSchema,
|
||||||
UpdatePaymentMethodByIdParamsRequestSchema,
|
UpdatePaymentMethodByIdParamsRequestSchema,
|
||||||
UpdatePaymentMethodByIdRequestSchema,
|
UpdatePaymentMethodByIdRequestSchema,
|
||||||
} from "../../../../common/dto/payment-methods/request";
|
} from "../../../../common";
|
||||||
import type { CatalogsInternalDeps } from "../../di/catalogs.di";
|
import type { CatalogsInternalDeps } from "../../di/catalogs.di";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreatePaymentMethodController,
|
CreatePaymentMethodController,
|
||||||
|
DeletePaymentMethodByIdController,
|
||||||
DisablePaymentMethodByIdController,
|
DisablePaymentMethodByIdController,
|
||||||
|
EnablePaymentMethodByIdController,
|
||||||
GetPaymentMethodByIdController,
|
GetPaymentMethodByIdController,
|
||||||
ListPaymentMethodsController,
|
ListPaymentMethodsController,
|
||||||
UpdatePaymentMethodByIdController,
|
UpdatePaymentMethodByIdController,
|
||||||
@ -64,7 +67,18 @@ export const paymentMethodsRouter = (params: StartParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.delete(
|
||||||
|
"/:payment_method_id",
|
||||||
|
validateRequest(DeletePaymentMethodByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new DeletePaymentMethodByIdController(
|
||||||
|
deps.useCases.deletePaymentMethodById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
"/:payment_method_id",
|
"/:payment_method_id",
|
||||||
validateRequest(UpdatePaymentMethodByIdParamsRequestSchema, "params"),
|
validateRequest(UpdatePaymentMethodByIdParamsRequestSchema, "params"),
|
||||||
validateRequest(UpdatePaymentMethodByIdRequestSchema, "body"),
|
validateRequest(UpdatePaymentMethodByIdRequestSchema, "body"),
|
||||||
@ -87,5 +101,16 @@ export const paymentMethodsRouter = (params: StartParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:payment_method_id/enable",
|
||||||
|
validateRequest(GetPaymentMethodByIdRequestSchema, "params"),
|
||||||
|
(req, res, next) => {
|
||||||
|
const controller = new EnablePaymentMethodByIdController(
|
||||||
|
deps.useCases.enablePaymentMethodById()
|
||||||
|
);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
app.use(`${config.server.apiBasePath}/catalogs/payment-methods`, router);
|
app.use(`${config.server.apiBasePath}/catalogs/payment-methods`, router);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper<
|
|||||||
> {
|
> {
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
source: PaymentMethodModel,
|
source: PaymentMethodModel,
|
||||||
params?: MapperParamsType
|
_params?: MapperParamsType
|
||||||
): Result<PaymentMethod, Error> {
|
): Result<PaymentMethod, Error> {
|
||||||
try {
|
try {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
@ -71,7 +71,7 @@ export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
public mapToPersistence(
|
public mapToPersistence(
|
||||||
source: PaymentMethod,
|
source: PaymentMethod,
|
||||||
params?: MapperParamsType
|
_params?: MapperParamsType
|
||||||
): Result<PaymentMethodCreationAttributes, Error> {
|
): Result<PaymentMethodCreationAttributes, Error> {
|
||||||
return Result.ok<PaymentMethodCreationAttributes>({
|
return Result.ok<PaymentMethodCreationAttributes>({
|
||||||
id: source.id.toPrimitive(),
|
id: source.id.toPrimitive(),
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export class SequelizePaymentMethodSummaryMapper extends SequelizeQueryMapper<
|
|||||||
> {
|
> {
|
||||||
public mapToReadModel(
|
public mapToReadModel(
|
||||||
raw: PaymentMethodModel,
|
raw: PaymentMethodModel,
|
||||||
params?: MapperParamsType
|
_params?: MapperParamsType
|
||||||
): Result<PaymentMethodSummary, Error> {
|
): Result<PaymentMethodSummary, Error> {
|
||||||
const errors: ValidationErrorDetail[] = [];
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
|||||||
@ -71,12 +71,14 @@ export class SequelizePaymentMethodRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { id, ...payload } = dtoResult.data;
|
const { id, ...payload } = dtoResult.data;
|
||||||
const [affected] = await PaymentMethodModel.update(payload, {
|
const [affected, updated] = await PaymentMethodModel.update(payload, {
|
||||||
where: { id },
|
where: { id },
|
||||||
transaction,
|
transaction,
|
||||||
individualHooks: true,
|
individualHooks: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("Update result:", { affected, updated });
|
||||||
|
|
||||||
if (affected === 0) {
|
if (affected === 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new InfrastructureRepositoryError("Concurrency conflict or payment method not found")
|
new InfrastructureRepositoryError("Concurrency conflict or payment method not found")
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const DeletePaymentMethodByIdRequestSchema = z.object({
|
||||||
|
payment_method_id: z.uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DeletePaymentMethodByIdRequestDTO = z.infer<
|
||||||
|
typeof DeletePaymentMethodByIdRequestSchema
|
||||||
|
>;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./create-payment-method.request.dto";
|
export * from "./create-payment-method.request.dto";
|
||||||
|
export * from "./delete-payment-method-by-id.request.dto";
|
||||||
export * from "./get-payment-method-by-id.request.dto";
|
export * from "./get-payment-method-by-id.request.dto";
|
||||||
export * from "./list-payment-methods.request.dto";
|
export * from "./list-payment-methods.request.dto";
|
||||||
export * from "./update-payment-method-by-id.request.dto";
|
export * from "./update-payment-method-by-id.request.dto";
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { translateZodValidationError } from "../helpers";
|
import { translateZodValidationError } from "../helpers";
|
||||||
|
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
interface NameProps {
|
interface NameProps {
|
||||||
@ -24,7 +26,7 @@ export class Name extends ValueObject<NameProps> {
|
|||||||
if (!valueIsValid.success) {
|
if (!valueIsValid.success) {
|
||||||
return Result.fail(translateZodValidationError("Name creation failed", valueIsValid.error));
|
return Result.fail(translateZodValidationError("Name creation failed", valueIsValid.error));
|
||||||
}
|
}
|
||||||
return Result.ok(new Name({ value }));
|
return Result.ok(new Name({ value: valueIsValid.data }));
|
||||||
}
|
}
|
||||||
|
|
||||||
static generateAcronym(name: string): string {
|
static generateAcronym(name: string): string {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { translateZodValidationError } from "../helpers";
|
import { translateZodValidationError } from "../helpers";
|
||||||
|
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
interface TextValueProps {
|
interface TextValueProps {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export interface IMaybe<T> {
|
|||||||
getOrUndefined(): T | undefined;
|
getOrUndefined(): T | undefined;
|
||||||
map<U>(fn: (value: T) => U): IMaybe<U>;
|
map<U>(fn: (value: T) => U): IMaybe<U>;
|
||||||
match<U>(someFn: (value: T) => U, noneFn: () => U): U;
|
match<U>(someFn: (value: T) => U, noneFn: () => U): U;
|
||||||
|
equals(other: IMaybe<T>): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMaybe<T = unknown>(input: unknown): input is Maybe<T> {
|
export function isMaybe<T = unknown>(input: unknown): input is Maybe<T> {
|
||||||
@ -40,6 +41,18 @@ export class Maybe<T> implements IMaybe<T> {
|
|||||||
return new Maybe<T>();
|
return new Maybe<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
equals(other: IMaybe<T>): boolean {
|
||||||
|
if (this.isNone() && other.isNone()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSome() && other.isSome()) {
|
||||||
|
return this.unwrap() === other.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
isSome(): boolean {
|
isSome(): boolean {
|
||||||
return this.value !== undefined;
|
return this.value !== undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user