Refactor payment methods: Remove system_key and related logic

- Removed `system_key` from payment method model and related files.
- Updated payment method repository and services to eliminate references to `system_key`.
- Deleted the BootstrapSystemPaymentMethodsService and its use case, as they are no longer needed.
- Adjusted tests to reflect the removal of system payment methods and their associated logic.
- Added SQL script to drop the `system_key` column and its unique index from the database.
This commit is contained in:
David Arranz 2026-08-20 19:37:39 +02:00
parent de204fbcc9
commit f1f329fa0d
26 changed files with 92 additions and 656 deletions

View File

@ -15,7 +15,6 @@ Campos persistidos y expuestos para administración:
- `is_active`
- `is_system`
- `is_default`
- `system_key`
- `created_at`
- `updated_at`
- `deleted_at`
@ -24,17 +23,8 @@ Campos persistidos y expuestos para administración:
Reglas:
- `is_system = true` implica `system_key != null`
- `is_system = false` implica `system_key = null`
- `is_system = true` no se puede editar, desactivar ni eliminar
Identidad estable de métodos del sistema:
- `cash`
- `bank_transfer`
- `card`
- `direct_debit`
## Active and default
Reglas:
@ -46,26 +36,6 @@ Reglas:
La unicidad del default se garantiza mediante servicio transaccional y bloqueo por empresa. No se fuerza con constraint SQL adicional para evitar una solución opaca en MariaDB.
## Bootstrap
Endpoint explícito e idempotente:
- `POST /catalogs/payment-methods/bootstrap-system`
Comportamiento:
- crea los cuatro métodos del sistema que falten
- restaura un system method soft-deleted si ya existía por `system_key`
- resincroniza `name`, `is_system`, `is_active`, `is_default = false` y `system_key`
- si la empresa no tiene default, establece `cash` como default inicial
Métodos creados:
- `Efectivo`
- `Transferencia bancaria`
- `Tarjeta`
- `Domiciliación bancaria`
## Delete policy
Un método solo puede eliminarse si:

View File

@ -0,0 +1,35 @@
SET @index_exists := (
SELECT COUNT(1)
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'payment_methods'
AND index_name = 'uq_payment_methods_company_system_key'
);
SET @drop_index_sql := IF(
@index_exists > 0,
'DROP INDEX `uq_payment_methods_company_system_key` ON `payment_methods`',
'SELECT 1'
);
PREPARE stmt FROM @drop_index_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists := (
SELECT COUNT(1)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'payment_methods'
AND column_name = 'system_key'
);
SET @drop_column_sql := IF(
@column_exists > 0,
'ALTER TABLE `payment_methods` DROP COLUMN `system_key`',
'SELECT 1'
);
PREPARE stmt FROM @drop_column_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -8,7 +8,7 @@ import {
extractOrPushError,
maybeFromNullableResult,
} from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { Result } from "@repo/rdx-utils";
import type { CreatePaymentMethodRequestDTO } from "../../../../common";
import type { IPaymentMethodCreateProps } from "../../../domain";
@ -49,7 +49,6 @@ export class CreatePaymentMethodInputMapper implements ICreatePaymentMethodInput
isActive: isActive!,
isSystem: false,
isDefault: false,
systemKey: Maybe.none(),
};
return Result.ok({

View File

@ -2,7 +2,7 @@ import type { Criteria } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import type { Collection, Maybe, Result } from "@repo/rdx-utils";
import type { PaymentMethod, PaymentMethodSystemKey } from "../../../domain";
import type { PaymentMethod } from "../../../domain";
import type { PaymentMethodSummary } from "../models";
export interface IPaymentMethodRepository {
@ -32,29 +32,7 @@ export interface IPaymentMethodRepository {
companyId: UniqueID,
transaction?: unknown
): Promise<Result<Maybe<PaymentMethod>, Error>>;
findBySystemKeyInCompany(
companyId: UniqueID,
systemKey: PaymentMethodSystemKey,
transaction?: unknown,
options?: { includeDeleted?: boolean }
): Promise<Result<Maybe<PaymentMethod>, Error>>;
restoreByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction?: unknown
): Promise<Result<boolean, Error>>;
syncSystemPaymentMethodState(params: {
companyId: UniqueID;
id: UniqueID;
name: string;
isDefault: boolean;
systemKey: PaymentMethodSystemKey;
transaction?: unknown;
}): Promise<Result<void, Error>>;
lockByCompanyId(
companyId: UniqueID,
transaction?: unknown
): Promise<Result<void, Error>>;
lockByCompanyId(companyId: UniqueID, transaction?: unknown): Promise<Result<void, Error>>;
deleteByIdInCompany(
companyId: UniqueID,
id: UniqueID,

View File

@ -1,13 +1,11 @@
import { describe, test } from "node:test";
import assert from "node:assert/strict";
import type { Criteria } from "@repo/rdx-criteria/server";
import { EntityNotFoundError } from "@erp/core/api";
import { Name, UniqueID } from "@repo/rdx-ddd";
import { Collection, Maybe, Result } from "@repo/rdx-utils";
import { PaymentMethod } from "../../../../domain/payment-methods/payment-method.aggregate";
import { BootstrapSystemPaymentMethodsService } from "../bootstrap-system-payment-methods.service";
import { PaymentMethodDeleter } from "../payment-method-deleter";
import { SetDefaultPaymentMethodService } from "../set-default-payment-method.service";
import type { IPaymentMethodRepository } from "../../repositories";
@ -66,7 +64,7 @@ class InMemoryPaymentMethodRepository implements IPaymentMethodRepository {
}
async findByCriteriaInCompany(): Promise<Result<Collection<PaymentMethodSummary>, Error>> {
return Result.ok(Collection.create<PaymentMethodSummary>([]));
return Result.ok(new Collection<PaymentMethodSummary>([]));
}
async findDefaultInCompany(companyId: UniqueID): Promise<Result<Maybe<PaymentMethod>, Error>> {
@ -81,80 +79,6 @@ class InMemoryPaymentMethodRepository implements IPaymentMethodRepository {
return paymentMethod ? Result.ok(Maybe.some(paymentMethod)) : Result.ok(Maybe.none());
}
async findBySystemKeyInCompany(
companyId: UniqueID,
systemKey: "cash" | "bank_transfer" | "card" | "direct_debit",
_transaction?: unknown,
options?: { includeDeleted?: boolean }
): Promise<Result<Maybe<PaymentMethod>, Error>> {
const paymentMethod =
[...this.items.values()].find((item) => {
const matchesCompany = item.companyId.toString() === companyId.toString();
const matchesKey = item.systemKey.match(
(value) => value === systemKey,
() => false
);
const includeDeleted = options?.includeDeleted === true;
return (
matchesCompany &&
matchesKey &&
(includeDeleted || !this.deleted.has(item.id.toString()))
);
}) ?? null;
return paymentMethod ? Result.ok(Maybe.some(paymentMethod)) : Result.ok(Maybe.none());
}
async restoreByIdInCompany(companyId: UniqueID, id: UniqueID): Promise<Result<boolean, Error>> {
const result = await this.existsByIdInCompany(companyId, id);
if (result.isFailure) {
return Result.fail(result.error);
}
if (!result.data) {
return Result.ok(false);
}
this.deleted.delete(id.toString());
return Result.ok(true);
}
async syncSystemPaymentMethodState(params: {
companyId: UniqueID;
id: UniqueID;
name: string;
isDefault: boolean;
systemKey: "cash" | "bank_transfer" | "card" | "direct_debit";
}): Promise<Result<void, Error>> {
const paymentMethodResult = await this.getByIdInCompany(params.companyId, params.id);
if (paymentMethodResult.isFailure) {
return Result.fail(paymentMethodResult.error);
}
const current = paymentMethodResult.data;
const nameResult = Name.create(params.name);
assert.equal(nameResult.isSuccess, true);
const updatedResult = PaymentMethod.create(
{
companyId: current.companyId,
name: nameResult.data,
description: current.description,
isActive: true,
isSystem: true,
isDefault: params.isDefault,
systemKey: Maybe.some(params.systemKey),
},
current.id
);
assert.equal(updatedResult.isSuccess, true);
this.items.set(current.id.toString(), updatedResult.data);
return Result.ok();
}
async lockByCompanyId(): Promise<Result<void, Error>> {
return Result.ok();
}
@ -180,7 +104,6 @@ const buildPaymentMethod = (params: {
isActive?: boolean;
isSystem?: boolean;
isDefault?: boolean;
systemKey?: "cash" | "bank_transfer" | "card" | "direct_debit";
}) => {
const nameResult = Name.create(params.name);
assert.equal(nameResult.isSuccess, true);
@ -193,7 +116,6 @@ const buildPaymentMethod = (params: {
isActive: params.isActive ?? true,
isSystem: params.isSystem ?? false,
isDefault: params.isDefault ?? false,
systemKey: params.systemKey ? Maybe.some(params.systemKey) : Maybe.none(),
},
UniqueID.generateNewID()
);
@ -302,46 +224,12 @@ describe("Payment method services", () => {
assert.equal(result.isFailure, true);
});
test("bootstrap creates four system methods and exactly one default", async () => {
const repository = new InMemoryPaymentMethodRepository();
const setDefaultService = new SetDefaultPaymentMethodService(repository);
const service = new BootstrapSystemPaymentMethodsService({
repository,
setDefaultService,
});
const companyId = UniqueID.generateNewID();
const result = await service.bootstrap({ companyId });
assert.equal(result.isSuccess, true);
assert.equal(result.data.length, 4);
assert.equal(result.data.filter((item) => item.isDefault).length, 1);
assert.equal(result.data.some((item) => item.systemKey.match((key) => key === "cash", () => false) && item.isDefault), true);
});
test("bootstrap is idempotent", async () => {
const repository = new InMemoryPaymentMethodRepository();
const setDefaultService = new SetDefaultPaymentMethodService(repository);
const service = new BootstrapSystemPaymentMethodsService({
repository,
setDefaultService,
});
const companyId = UniqueID.generateNewID();
const first = await service.bootstrap({ companyId });
const second = await service.bootstrap({ companyId });
assert.equal(first.isSuccess, true);
assert.equal(second.isSuccess, true);
assert.equal(second.data.length, 4);
});
test("delete blocks referenced payment methods", async () => {
const repository = new InMemoryPaymentMethodRepository();
const deleter = new PaymentMethodDeleter({
repository,
usageChecker: {
findUsage: async () => Result.ok(["customers"]),
findUsage: async () => Result.ok([{ source: "customers" }]),
},
});
const companyId = UniqueID.generateNewID();
@ -352,9 +240,32 @@ describe("Payment method services", () => {
await repository.create(paymentMethod);
const result = await deleter.deleteById({
const result = await deleter.delete({
paymentMethod,
});
assert.equal(result.isFailure, true);
});
test("delete blocks system payment methods", async () => {
const repository = new InMemoryPaymentMethodRepository();
const deleter = new PaymentMethodDeleter({
repository,
usageChecker: {
findUsage: async () => Result.ok([]),
},
});
const companyId = UniqueID.generateNewID();
const paymentMethod = buildPaymentMethod({
companyId,
id: paymentMethod.id,
name: "Transferencia",
isSystem: true,
});
await repository.create(paymentMethod);
const result = await deleter.delete({
paymentMethod,
});
assert.equal(result.isFailure, true);

View File

@ -1,208 +0,0 @@
import { Name, UniqueID } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { PaymentMethod, type PaymentMethodSystemKey } from "../../../domain";
import type { IPaymentMethodRepository } from "../repositories";
import type { ISetDefaultPaymentMethodService } from "./set-default-payment-method.service";
type SystemPaymentMethodDefinition = {
systemKey: PaymentMethodSystemKey;
name: string;
isDefault: boolean;
};
const SYSTEM_PAYMENT_METHOD_DEFINITIONS: SystemPaymentMethodDefinition[] = [
{
systemKey: "cash",
name: "Efectivo",
isDefault: true,
},
{
systemKey: "bank_transfer",
name: "Transferencia bancaria",
isDefault: false,
},
{
systemKey: "card",
name: "Tarjeta",
isDefault: false,
},
{
systemKey: "direct_debit",
name: "Domiciliación bancaria",
isDefault: false,
},
];
export interface IBootstrapSystemPaymentMethodsService {
bootstrap(params: {
companyId: UniqueID;
transaction?: unknown;
}): Promise<Result<PaymentMethod[], Error>>;
}
export class BootstrapSystemPaymentMethodsService implements IBootstrapSystemPaymentMethodsService {
public constructor(
private readonly deps: {
repository: IPaymentMethodRepository;
setDefaultService: ISetDefaultPaymentMethodService;
}
) {}
public async bootstrap(params: {
companyId: UniqueID;
transaction?: unknown;
}): Promise<Result<PaymentMethod[], Error>> {
const { companyId, transaction } = params;
const lockResult = await this.deps.repository.lockByCompanyId(companyId, transaction);
if (lockResult.isFailure) {
return Result.fail(lockResult.error);
}
const bootstrappedPaymentMethods: PaymentMethod[] = [];
for (const definition of SYSTEM_PAYMENT_METHOD_DEFINITIONS) {
const paymentMethodResult = await this.ensureSystemPaymentMethod({
companyId,
definition,
transaction,
});
if (paymentMethodResult.isFailure) {
return Result.fail(paymentMethodResult.error);
}
bootstrappedPaymentMethods.push(paymentMethodResult.data);
}
const currentDefaultResult = await this.deps.repository.findDefaultInCompany(
companyId,
transaction
);
if (currentDefaultResult.isFailure) {
return Result.fail(currentDefaultResult.error);
}
if (currentDefaultResult.data.isNone()) {
const cashPaymentMethod = bootstrappedPaymentMethods.find(
(paymentMethod) =>
paymentMethod.systemKey.match(
(systemKey) => systemKey === "cash",
() => false
)
);
if (!cashPaymentMethod) {
return Result.fail(new Error("Cash system payment method bootstrap failed."));
}
const setDefaultResult = await this.deps.setDefaultService.setDefault({
companyId,
paymentMethodId: cashPaymentMethod.id,
transaction,
});
if (setDefaultResult.isFailure) {
return Result.fail(setDefaultResult.error);
}
const updatedItems = bootstrappedPaymentMethods.map((paymentMethod) =>
paymentMethod.id.toString() === setDefaultResult.data.id.toString()
? setDefaultResult.data
: paymentMethod
);
return Result.ok(updatedItems);
}
return Result.ok(bootstrappedPaymentMethods);
}
private async ensureSystemPaymentMethod(params: {
companyId: UniqueID;
definition: SystemPaymentMethodDefinition;
transaction?: unknown;
}): Promise<Result<PaymentMethod, Error>> {
const { companyId, definition, transaction } = params;
const existingResult = await this.deps.repository.findBySystemKeyInCompany(
companyId,
definition.systemKey,
transaction,
{ includeDeleted: true }
);
if (existingResult.isFailure) {
return Result.fail(existingResult.error);
}
if (existingResult.data.isSome()) {
const existingPaymentMethod = existingResult.data.unwrap();
const restoreResult = await this.deps.repository.restoreByIdInCompany(
companyId,
existingPaymentMethod.id,
transaction
);
if (restoreResult.isFailure) {
return Result.fail(restoreResult.error);
}
const syncResult = await this.deps.repository.syncSystemPaymentMethodState({
companyId,
id: existingPaymentMethod.id,
name: definition.name,
isDefault: false,
systemKey: definition.systemKey,
transaction,
});
if (syncResult.isFailure) {
return Result.fail(syncResult.error);
}
const refreshedResult = await this.deps.repository.getByIdInCompany(
companyId,
existingPaymentMethod.id,
transaction
);
if (refreshedResult.isFailure) {
return Result.fail(refreshedResult.error);
}
return Result.ok(refreshedResult.data);
}
const nameResult = Name.create(definition.name);
if (nameResult.isFailure) {
return Result.fail(nameResult.error);
}
const paymentMethodResult = PaymentMethod.create(
{
companyId,
name: nameResult.data,
description: Maybe.none(),
isActive: true,
isSystem: true,
isDefault: false,
systemKey: Maybe.some(definition.systemKey),
},
UniqueID.generateNewID()
);
if (paymentMethodResult.isFailure) {
return Result.fail(paymentMethodResult.error);
}
const createResult = await this.deps.repository.create(paymentMethodResult.data, transaction);
if (createResult.isFailure) {
return Result.fail(createResult.error);
}
return Result.ok(paymentMethodResult.data);
}
}

View File

@ -1,5 +1,4 @@
export * from "./payment-method-creator";
export * from "./bootstrap-system-payment-methods.service";
export * from "./payment-method-deleter";
export * from "./payment-method-disabler";
export * from "./payment-method-enabler";

View File

@ -1,46 +0,0 @@
import type { BootstrapSystemPaymentMethodsResponseDTO } from "@erp/catalogs/common";
import type { ITransactionManager } from "@erp/core/api";
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { IBootstrapSystemPaymentMethodsService } from "../services";
import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
export type BootstrapSystemPaymentMethodsUseCaseInput = {
companyId: UniqueID;
};
export class BootstrapSystemPaymentMethodsUseCase {
public constructor(
private readonly deps: {
bootstrapService: IBootstrapSystemPaymentMethodsService;
fullSnapshotBuilder: IPaymentMethodFullSnapshotBuilder;
transactionManager: ITransactionManager;
}
) {}
public execute(
params: BootstrapSystemPaymentMethodsUseCaseInput
): Promise<Result<BootstrapSystemPaymentMethodsResponseDTO, Error>> {
const { companyId } = params;
return this.deps.transactionManager.complete(async (transaction: unknown) => {
try {
const result = await this.deps.bootstrapService.bootstrap({
companyId,
transaction,
});
if (result.isFailure) {
return Result.fail(result.error);
}
return Result.ok({
items: result.data.map((paymentMethod) => this.deps.fullSnapshotBuilder.toOutput(paymentMethod)),
});
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -1,4 +1,3 @@
export * from "./bootstrap-system-payment-methods.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";

View File

@ -22,7 +22,6 @@ const createPaymentMethod = (params?: {
isActive: params?.isActive ?? true,
isSystem: params?.isSystem ?? false,
isDefault: params?.isDefault ?? false,
systemKey: Maybe.none(),
},
UniqueID.generateNewID()
);
@ -45,7 +44,6 @@ describe("PaymentMethod aggregate", () => {
isActive: false,
isSystem: false,
isDefault: true,
systemKey: Maybe.none(),
},
UniqueID.generateNewID()
);
@ -81,7 +79,6 @@ describe("PaymentMethod aggregate", () => {
isActive: true,
isSystem: true,
isDefault: false,
systemKey: Maybe.some("cash"),
},
UniqueID.generateNewID()
);
@ -105,4 +102,29 @@ describe("PaymentMethod aggregate", () => {
assert.equal(result.isSuccess, true);
});
test("blocks deleting system payment methods", () => {
const paymentMethod = createPaymentMethod({ isSystem: true });
const result = paymentMethod.ensureCanBeDeleted();
assert.equal(result.isFailure, true);
});
test("blocks disabling system payment methods", () => {
const paymentMethod = createPaymentMethod({ isSystem: true });
const result = paymentMethod.disable();
assert.equal(result.isFailure, true);
});
test("keeps enable idempotent for system payment methods", () => {
const paymentMethod = createPaymentMethod({ isSystem: true });
const result = paymentMethod.enable();
assert.equal(result.isSuccess, true);
assert.equal(result.data, false);
});
});

View File

@ -1,4 +1,3 @@
export * from "./errors";
export * from "./payment-method.aggregate";
export * from "./payment-method-name";
export * from "./payment-method-system-key";

View File

@ -1,12 +0,0 @@
export const PAYMENT_METHOD_SYSTEM_KEYS = [
"cash",
"bank_transfer",
"card",
"direct_debit",
] as const;
export type PaymentMethodSystemKey = (typeof PAYMENT_METHOD_SYSTEM_KEYS)[number];
export const isPaymentMethodSystemKey = (value: string): value is PaymentMethodSystemKey => {
return PAYMENT_METHOD_SYSTEM_KEYS.includes(value as PaymentMethodSystemKey);
};

View File

@ -7,7 +7,6 @@ import {
PaymentMethodCannotBeDeletedError,
PaymentMethodCannotBeUpdatedError,
} from "./errors";
import type { PaymentMethodSystemKey } from "./payment-method-system-key";
export interface IPaymentMethodCreateProps {
companyId: UniqueID;
@ -16,7 +15,6 @@ export interface IPaymentMethodCreateProps {
isActive: boolean;
isSystem: boolean;
isDefault: boolean;
systemKey: Maybe<PaymentMethodSystemKey>;
}
export type PaymentMethodPatchProps = Partial<{
@ -71,14 +69,6 @@ export class PaymentMethod extends AggregateRoot<PaymentMethodInternalProps> {
);
}
if (props.isSystem && props.systemKey.isNone()) {
return Result.fail(new Error("System payment methods require a system key."));
}
if (!props.isSystem && props.systemKey.isSome()) {
return Result.fail(new Error("Custom payment methods cannot define a system key."));
}
return Result.ok();
}
@ -114,10 +104,6 @@ export class PaymentMethod extends AggregateRoot<PaymentMethodInternalProps> {
return this.props.isDefault;
}
public get systemKey(): Maybe<PaymentMethodSystemKey> {
return this.props.systemKey;
}
public update(patchProps: PaymentMethodPatchProps): Result<boolean, Error> {
if (this.isSystem) {
return Result.fail(

View File

@ -3,8 +3,6 @@ import type { Sequelize } from "sequelize";
import {
type IPaymentMethodPublicFinder,
BootstrapSystemPaymentMethodsService,
BootstrapSystemPaymentMethodsUseCase,
PaymentMethodPublicFinder,
PaymentMethodPublicModelMapper,
SetDefaultPaymentMethodByIdUseCase,
@ -43,7 +41,6 @@ export type PaymentMethodsInternalDeps = {
disablePaymentMethodById: () => DisablePaymentMethodByIdUseCase;
enablePaymentMethodById: () => EnablePaymentMethodByIdUseCase;
setDefaultPaymentMethodById: () => SetDefaultPaymentMethodByIdUseCase;
bootstrapSystemPaymentMethods: () => BootstrapSystemPaymentMethodsUseCase;
};
};
@ -67,10 +64,6 @@ export const buildPaymentMethodsDependencies = (
const deleter = buildPaymentMethodDeleter({ repository, usageChecker });
const statusChanger = buildPaymentMethodStatusChanger({ repository });
const setDefaultService = new SetDefaultPaymentMethodService(repository);
const bootstrapService = new BootstrapSystemPaymentMethodsService({
repository,
setDefaultService,
});
const snapshotBuilders = buildPaymentMethodSnapshotBuilders();
@ -130,13 +123,6 @@ export const buildPaymentMethodsDependencies = (
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
bootstrapSystemPaymentMethods: () =>
new BootstrapSystemPaymentMethodsUseCase({
bootstrapService,
fullSnapshotBuilder: snapshotBuilders.full,
transactionManager,
}),
},
};
};

View File

@ -1,37 +0,0 @@
import {
ExpressController,
forbidQueryFieldGuard,
requireAuthenticatedGuard,
requireCompanyContextGuard,
} from "@erp/core/api";
import type { BootstrapSystemPaymentMethodsUseCase } from "../../../../application";
import { paymentMethodsApiErrorMapper } from "../payment-methods-api-error-mapper";
export class BootstrapSystemPaymentMethodsController extends ExpressController {
constructor(private readonly useCase: BootstrapSystemPaymentMethodsUseCase) {
super();
this.errorMapper = paymentMethodsApiErrorMapper;
this.registerGuards(
requireAuthenticatedGuard(),
requireCompanyContextGuard(),
forbidQueryFieldGuard("companyId")
);
}
protected async executeImpl() {
const companyId = this.getTenantId();
if (!companyId) {
return this.forbiddenError("Tenant ID not found");
}
const result = await this.useCase.execute({ companyId });
return result.match(
(data) => this.ok(data),
(err) => this.handleError(err)
);
}
}

View File

@ -4,6 +4,5 @@ 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 "./list-payment-methods.controller";
export * from "./bootstrap-system-payment-methods.controller";
export * from "./set-default-payment-method-by-id.controller";
export * from "./update-payment-method-by-id.controller";

View File

@ -3,7 +3,6 @@ import { requireIdentityTenantMembership } from "@erp/identity/api";
import { type NextFunction, type Request, type Response, Router } from "express";
import {
BootstrapSystemPaymentMethodsRequestSchema,
CreatePaymentMethodRequestSchema,
DeletePaymentMethodByIdRequestSchema,
GetPaymentMethodByIdRequestSchema,
@ -15,7 +14,6 @@ import {
import type { CatalogsInternalDeps } from "../../di/catalogs.di";
import {
BootstrapSystemPaymentMethodsController,
CreatePaymentMethodController,
DeletePaymentMethodByIdController,
DisablePaymentMethodByIdController,
@ -47,17 +45,6 @@ export const paymentMethodsRouter = (params: StartParams) => {
}
);
router.post(
"/bootstrap-system",
validateRequest(BootstrapSystemPaymentMethodsRequestSchema, "body"),
(req: Request, res: Response, next: NextFunction) => {
const controller = new BootstrapSystemPaymentMethodsController(
deps.useCases.bootstrapSystemPaymentMethods()
);
return controller.execute(req, res, next);
}
);
router.post(
"/",
validateRequest(CreatePaymentMethodRequestSchema, "body"),

View File

@ -11,7 +11,7 @@ import {
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { PaymentMethod, isPaymentMethodSystemKey } from "../../../../../domain";
import { PaymentMethod } from "../../../../../domain";
import type { PaymentMethodCreationAttributes, PaymentMethodModel } from "../models";
export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper<
@ -44,15 +44,6 @@ export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper<
"description",
errors
);
const systemKey = extractOrPushError(
maybeFromNullableResult(source.system_key, (value) =>
isPaymentMethodSystemKey(value)
? Result.ok(value)
: Result.fail(new Error(`Invalid payment method system key: ${value}`))
),
"system_key",
errors
);
// Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
@ -69,7 +60,6 @@ export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper<
isActive: source.is_active,
isSystem: source.is_system,
isDefault: source.is_default,
systemKey: systemKey!,
},
idResult.data
);
@ -92,10 +82,6 @@ export class SequelizePaymentMethodDomainMapper extends SequelizeDomainMapper<
is_active: source.isActive,
is_system: source.isSystem,
is_default: source.isDefault,
system_key: source.systemKey.match(
(systemKey) => systemKey,
() => null
),
});
}
}

View File

@ -20,7 +20,6 @@ export class PaymentMethodModel extends Model<
declare is_active: boolean;
declare is_system: boolean;
declare is_default: boolean;
declare system_key: CreationOptional<string | null>;
static associate(_database: Sequelize) {}
@ -65,11 +64,6 @@ export default (database: Sequelize) => {
allowNull: false,
defaultValue: false,
},
system_key: {
type: DataTypes.STRING(40),
allowNull: true,
defaultValue: null,
},
},
{
sequelize: database,
@ -91,13 +85,6 @@ export default (database: Sequelize) => {
defaultScope: {},
scopes: {},
indexes: [
{
name: "uq_payment_methods_company_system_key",
fields: ["company_id", "system_key"],
unique: true,
},
],
}
);

View File

@ -11,7 +11,7 @@ import type { Sequelize, Transaction } from "sequelize";
import type { IPaymentMethodRepository } from "../../../../../application";
import type { PaymentMethodSummary } from "../../../../../application/payment-methods/models";
import type { PaymentMethod, PaymentMethodSystemKey } from "../../../../../domain";
import type { PaymentMethod } from "../../../../../domain";
import type { SequelizePaymentMethodSummaryMapper } from "../mappers";
import type { SequelizePaymentMethodDomainMapper } from "../mappers/sequelize-payment-method-domain.mapper";
import { PaymentMethodModel } from "../models";
@ -202,90 +202,6 @@ export class SequelizePaymentMethodRepository
}
}
public async findBySystemKeyInCompany(
companyId: UniqueID,
systemKey: PaymentMethodSystemKey,
transaction?: Transaction,
options?: { includeDeleted?: boolean }
): Promise<Result<Maybe<PaymentMethod>, Error>> {
try {
const row = await PaymentMethodModel.findOne({
where: {
company_id: companyId.toString(),
system_key: systemKey,
},
paranoid: !(options?.includeDeleted ?? false),
transaction,
});
if (!row) {
return Result.ok(Maybe.none<PaymentMethod>());
}
const mappedResult = this.domainMapper.mapToDomain(row);
if (mappedResult.isFailure) {
return Result.fail(mappedResult.error);
}
return Result.ok(Maybe.some(mappedResult.data));
} catch (error: unknown) {
return Result.fail(translateSequelizeError(error));
}
}
public async restoreByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction?: Transaction
): Promise<Result<boolean, Error>> {
try {
const restored = await PaymentMethodModel.restore({
where: {
id: id.toString(),
company_id: companyId.toString(),
},
transaction,
});
return Result.ok(restored > 0);
} catch (error: unknown) {
return Result.fail(translateSequelizeError(error));
}
}
public async syncSystemPaymentMethodState(params: {
companyId: UniqueID;
id: UniqueID;
name: string;
isDefault: boolean;
systemKey: PaymentMethodSystemKey;
transaction?: Transaction;
}): Promise<Result<void, Error>> {
try {
await PaymentMethodModel.update(
{
name: params.name,
is_active: true,
is_system: true,
is_default: params.isDefault,
system_key: params.systemKey,
},
{
where: {
id: params.id.toString(),
company_id: params.companyId.toString(),
},
paranoid: false,
transaction: params.transaction,
}
);
return Result.ok();
} catch (error: unknown) {
return Result.fail(translateSequelizeError(error));
}
}
public async lockByCompanyId(
companyId: UniqueID,
transaction?: Transaction

View File

@ -1,7 +0,0 @@
import { z } from "zod/v4";
export const BootstrapSystemPaymentMethodsRequestSchema = z.object({});
export type BootstrapSystemPaymentMethodsRequestDTO = z.infer<
typeof BootstrapSystemPaymentMethodsRequestSchema
>;

View File

@ -1,5 +1,4 @@
export * from "./create-payment-method.request.dto";
export * from "./bootstrap-system-payment-methods.request.dto";
export * from "./delete-payment-method-by-id.request.dto";
export * from "./get-payment-method-by-id.request.dto";
export * from "./list-payment-methods.request.dto";

View File

@ -1,11 +0,0 @@
import { z } from "zod/v4";
import { PaymentMethodDetailSchema } from "../shared";
export const BootstrapSystemPaymentMethodsResponseSchema = z.object({
items: z.array(PaymentMethodDetailSchema),
});
export type BootstrapSystemPaymentMethodsResponseDTO = z.infer<
typeof BootstrapSystemPaymentMethodsResponseSchema
>;

View File

@ -1,5 +1,4 @@
export * from "./create-payment-method.response.dto";
export * from "./bootstrap-system-payment-methods.response.dto";
export * from "./disable-payment-method-by-id.response.dto";
export * from "./get-payment-method-by-id.response.dto";
export * from "./list-payment-methods.response.dto";