feat(audit): integrate audit functionality for payment methods
- Added @repo/rdx-audit as a dependency in server and catalogs package.json. - Enhanced payment method services (creator, updater, deleter, status changer, and default setter) to include audit logging. - Implemented audit actions for create, update, enable, disable, delete, and default set/ unset operations. - Created payment-method-audit.ts for managing audit actions and state projections. - Developed integration tests for payment method audit logging in the HTTP context.
This commit is contained in:
parent
01d0ff67f8
commit
f1c0df685b
@ -16,6 +16,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.4.11",
|
"@biomejs/biome": "^2.4.11",
|
||||||
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
"@repo/typescript-config": "workspace:*",
|
"@repo/typescript-config": "workspace:*",
|
||||||
"@types/bcrypt": "^6.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|||||||
import {
|
import {
|
||||||
AsyncLocalStorageOperationContextStore,
|
AsyncLocalStorageOperationContextStore,
|
||||||
type IModuleServer,
|
type IModuleServer,
|
||||||
|
type IOperationContextStore,
|
||||||
type ISequelizeTransactionContext,
|
type ISequelizeTransactionContext,
|
||||||
OperationAuditContextProvider,
|
OperationAuditContextProvider,
|
||||||
SequelizeTransactionContext,
|
SequelizeTransactionContext,
|
||||||
@ -53,6 +54,7 @@ export interface IHttpTestRuntime {
|
|||||||
readonly config: ConfigType;
|
readonly config: ConfigType;
|
||||||
readonly database: Sequelize;
|
readonly database: Sequelize;
|
||||||
readonly transactionContext: ISequelizeTransactionContext;
|
readonly transactionContext: ISequelizeTransactionContext;
|
||||||
|
readonly operationContext: IOperationContextStore;
|
||||||
readonly audit: IAudit;
|
readonly audit: IAudit;
|
||||||
request(
|
request(
|
||||||
path: string,
|
path: string,
|
||||||
@ -136,6 +138,7 @@ export async function createHttpTestRuntime(
|
|||||||
baseUrl,
|
baseUrl,
|
||||||
config,
|
config,
|
||||||
database,
|
database,
|
||||||
|
operationContext,
|
||||||
transactionContext,
|
transactionContext,
|
||||||
audit,
|
audit,
|
||||||
request: async (path: string, init?: RequestInit) => {
|
request: async (path: string, init?: RequestInit) => {
|
||||||
|
|||||||
@ -0,0 +1,237 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { SequelizeTransactionManager } from "@erp/core/api";
|
||||||
|
import { Name, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { AuditChangeModel, AuditEventModel } from "@/infrastructure/audit/sequelize";
|
||||||
|
|
||||||
|
import { PaymentMethodUpdater } from "../../../../../modules/catalogs/src/api/application/payment-methods/services/payment-method-updater";
|
||||||
|
import { SequelizePaymentMethodDomainMapper } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/mappers/sequelize-payment-method-domain.mapper";
|
||||||
|
import { SequelizePaymentMethodSummaryMapper } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/mappers/sequelize-payment-method-summary.mapper";
|
||||||
|
import { PaymentMethodModel } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/models/sequelize-payment-method.model";
|
||||||
|
import { SequelizePaymentMethodRepository } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository";
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildCompanyHeaders,
|
||||||
|
createTestAccount,
|
||||||
|
createTestCompany,
|
||||||
|
createTestMembership,
|
||||||
|
loginByApi,
|
||||||
|
} from "./http-test-fixtures";
|
||||||
|
import { createHttpTestRuntime } from "./http-test-runtime";
|
||||||
|
|
||||||
|
test("payment methods registra create y update con el contexto HTTP en MariaDB", async (t) => {
|
||||||
|
const runtime = await createHttpTestRuntime();
|
||||||
|
t.after(async () => runtime.stop());
|
||||||
|
|
||||||
|
const account = await createTestAccount(runtime.database, {
|
||||||
|
email: `payment-method-audit-${randomUUID()}@example.test`,
|
||||||
|
password: "Password123!",
|
||||||
|
});
|
||||||
|
const company = await createTestCompany(runtime.database, {
|
||||||
|
legalName: "Payment method audit company",
|
||||||
|
});
|
||||||
|
await createTestMembership(runtime.database, {
|
||||||
|
accountId: account.id,
|
||||||
|
companyId: company.id,
|
||||||
|
});
|
||||||
|
const loginResponse = await loginByApi({
|
||||||
|
request: runtime.request,
|
||||||
|
email: account.email,
|
||||||
|
password: account.password,
|
||||||
|
});
|
||||||
|
assert.equal(loginResponse.status, 200);
|
||||||
|
const accessToken = (loginResponse.body as { access_token: string }).access_token;
|
||||||
|
const paymentMethodId = randomUUID();
|
||||||
|
|
||||||
|
const createResponse = await runtime.request("/api/v1/catalogs/payment-methods", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
...buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
"content-type": "application/json",
|
||||||
|
"x-correlation-id": "payment-method-create-correlation",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: paymentMethodId,
|
||||||
|
name: "Cash",
|
||||||
|
description: "Counter payment",
|
||||||
|
is_active: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(createResponse.status, 201);
|
||||||
|
|
||||||
|
const createdAudit = await AuditEventModel.findOne({
|
||||||
|
where: { action: "payment_method.created", target_id: paymentMethodId },
|
||||||
|
});
|
||||||
|
assert.ok(createdAudit);
|
||||||
|
assert.equal(createdAudit.source_type, "application");
|
||||||
|
assert.equal(createdAudit.source_id, "uecko-erp");
|
||||||
|
assert.equal(createdAudit.origin, "http");
|
||||||
|
assert.equal(createdAudit.operation_id, createResponse.headers.get("x-request-id"));
|
||||||
|
assert.equal(createdAudit.correlation_id, "payment-method-create-correlation");
|
||||||
|
assert.equal(createdAudit.actor_type, "user");
|
||||||
|
assert.equal(createdAudit.actor_id, account.id);
|
||||||
|
assert.equal(createdAudit.scope_type, "company");
|
||||||
|
assert.equal(createdAudit.scope_id, company.id);
|
||||||
|
assert.equal(createdAudit.target_type, "payment_method");
|
||||||
|
assert.equal(createdAudit.target_label, "Cash");
|
||||||
|
|
||||||
|
const updateResponse = await runtime.request(
|
||||||
|
`/api/v1/catalogs/payment-methods/${paymentMethodId}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
...buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
"content-type": "application/json",
|
||||||
|
"x-correlation-id": "payment-method-update-correlation",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name: "Cash updated" }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.equal(updateResponse.status, 200);
|
||||||
|
|
||||||
|
const updatedAudit = await AuditEventModel.findOne({
|
||||||
|
where: { action: "payment_method.updated", target_id: paymentMethodId },
|
||||||
|
});
|
||||||
|
assert.ok(updatedAudit);
|
||||||
|
assert.equal(updatedAudit.target_label, "Cash updated");
|
||||||
|
assert.equal(updatedAudit.actor_id, account.id);
|
||||||
|
assert.equal(updatedAudit.scope_id, company.id);
|
||||||
|
const changes = await AuditChangeModel.findAll({
|
||||||
|
where: { audit_event_id: updatedAudit.id },
|
||||||
|
order: [["position", "ASC"]],
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
changes.map((change) => ({
|
||||||
|
path: change.path,
|
||||||
|
kind: change.kind,
|
||||||
|
before: change.before_value,
|
||||||
|
after: change.after_value,
|
||||||
|
})),
|
||||||
|
[{ path: "name", kind: "change", before: "Cash", after: "Cash updated" }]
|
||||||
|
);
|
||||||
|
|
||||||
|
const secondPaymentMethodId = randomUUID();
|
||||||
|
const secondCreateResponse = await runtime.request("/api/v1/catalogs/payment-methods", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
...buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: secondPaymentMethodId,
|
||||||
|
name: "Card",
|
||||||
|
is_active: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(secondCreateResponse.status, 201);
|
||||||
|
|
||||||
|
const firstDefaultResponse = await runtime.request(
|
||||||
|
`/api/v1/catalogs/payment-methods/${paymentMethodId}/default`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.equal(firstDefaultResponse.status, 200);
|
||||||
|
|
||||||
|
const defaultResponse = await runtime.request(
|
||||||
|
`/api/v1/catalogs/payment-methods/${secondPaymentMethodId}/default`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
...buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
"x-correlation-id": "payment-method-default-correlation",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.equal(defaultResponse.status, 200);
|
||||||
|
|
||||||
|
const defaultEvents = await AuditEventModel.findAll({
|
||||||
|
where: {
|
||||||
|
action: ["payment_method.default_unset", "payment_method.default_set"],
|
||||||
|
operation_id: defaultResponse.headers.get("x-request-id") ?? "",
|
||||||
|
},
|
||||||
|
order: [["action", "ASC"]],
|
||||||
|
});
|
||||||
|
assert.equal(defaultEvents.length, 2);
|
||||||
|
const unsetEvent = defaultEvents.find((entry) => entry.action === "payment_method.default_unset");
|
||||||
|
const setEvent = defaultEvents.find((entry) => entry.action === "payment_method.default_set");
|
||||||
|
assert.equal(unsetEvent?.target_id, paymentMethodId);
|
||||||
|
assert.equal(setEvent?.target_id, secondPaymentMethodId);
|
||||||
|
assert.equal(unsetEvent?.correlation_id, "payment-method-default-correlation");
|
||||||
|
assert.equal(setEvent?.correlation_id, "payment-method-default-correlation");
|
||||||
|
assert.equal(unsetEvent?.actor_id, account.id);
|
||||||
|
assert.equal(setEvent?.scope_id, company.id);
|
||||||
|
|
||||||
|
const [unsetChange] = await AuditChangeModel.findAll({
|
||||||
|
where: { audit_event_id: unsetEvent?.id ?? "" },
|
||||||
|
});
|
||||||
|
const [setChange] = await AuditChangeModel.findAll({
|
||||||
|
where: { audit_event_id: setEvent?.id ?? "" },
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
{ path: unsetChange?.path, before: unsetChange?.before_value, after: unsetChange?.after_value },
|
||||||
|
{ path: "isDefault", before: true, after: false }
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
{ path: setChange?.path, before: setChange?.before_value, after: setChange?.after_value },
|
||||||
|
{ path: "isDefault", before: false, after: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const originalName = "Cash updated";
|
||||||
|
const rollbackName = "Cash rollback attempt";
|
||||||
|
const repository = new SequelizePaymentMethodRepository(
|
||||||
|
new SequelizePaymentMethodDomainMapper(),
|
||||||
|
new SequelizePaymentMethodSummaryMapper(),
|
||||||
|
runtime.database
|
||||||
|
);
|
||||||
|
const updater = new PaymentMethodUpdater(repository, runtime.audit);
|
||||||
|
const transactionManager = new SequelizeTransactionManager(
|
||||||
|
runtime.database,
|
||||||
|
runtime.transactionContext
|
||||||
|
);
|
||||||
|
const paymentMethodIdResult = UniqueID.create(paymentMethodId);
|
||||||
|
const rollbackNameResult = Name.create(rollbackName);
|
||||||
|
assert.equal(paymentMethodIdResult.isSuccess, true);
|
||||||
|
assert.equal(rollbackNameResult.isSuccess, true);
|
||||||
|
const changesBeforeRollback = await AuditChangeModel.count();
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() =>
|
||||||
|
runtime.operationContext.run(
|
||||||
|
{
|
||||||
|
operationId: "payment-method-rollback-operation",
|
||||||
|
correlationId: "payment-method-rollback-correlation",
|
||||||
|
origin: "http",
|
||||||
|
actor: { type: "user", id: account.id, label: account.email },
|
||||||
|
scope: { type: "company", id: company.id, label: null },
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
transactionManager.complete(async (transaction) => {
|
||||||
|
const updateResult = await updater.update({
|
||||||
|
companyId: UniqueID.create(company.id).data,
|
||||||
|
id: paymentMethodIdResult.data,
|
||||||
|
patchProps: { name: rollbackNameResult.data },
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
assert.equal(updateResult.isSuccess, true);
|
||||||
|
return Result.fail(new Error("forced payment method rollback"));
|
||||||
|
})
|
||||||
|
),
|
||||||
|
/forced payment method rollback/
|
||||||
|
);
|
||||||
|
|
||||||
|
const rolledBackPaymentMethod = await PaymentMethodModel.findByPk(paymentMethodId);
|
||||||
|
assert.equal(rolledBackPaymentMethod?.name, originalName);
|
||||||
|
assert.equal(
|
||||||
|
await AuditEventModel.count({
|
||||||
|
where: { operation_id: "payment-method-rollback-operation" },
|
||||||
|
}),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert.equal(await AuditChangeModel.count(), changesBeforeRollback);
|
||||||
|
});
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"@erp/identity": "workspace:*",
|
"@erp/identity": "workspace:*",
|
||||||
"@repo/i18next": "workspace:*",
|
"@repo/i18next": "workspace:*",
|
||||||
"@repo/rdx-criteria": "workspace:*",
|
"@repo/rdx-criteria": "workspace:*",
|
||||||
|
"@repo/rdx-audit": "workspace:*",
|
||||||
"@repo/rdx-ddd": "workspace:*",
|
"@repo/rdx-ddd": "workspace:*",
|
||||||
"@repo/rdx-logger": "workspace:*",
|
"@repo/rdx-logger": "workspace:*",
|
||||||
"@repo/rdx-ui": "workspace:*",
|
"@repo/rdx-ui": "workspace:*",
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
import type { AuditMutationTarget, AuditState } from "@repo/rdx-audit";
|
||||||
|
|
||||||
|
import type { PaymentMethod } from "../../../domain";
|
||||||
|
|
||||||
|
export const PAYMENT_METHOD_AUDIT_ACTIONS = {
|
||||||
|
CREATED: "payment_method.created",
|
||||||
|
UPDATED: "payment_method.updated",
|
||||||
|
ENABLED: "payment_method.enabled",
|
||||||
|
DISABLED: "payment_method.disabled",
|
||||||
|
DELETED: "payment_method.deleted",
|
||||||
|
DEFAULT_SET: "payment_method.default_set",
|
||||||
|
DEFAULT_UNSET: "payment_method.default_unset",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/** Proyección estable del estado funcional, sin detalles ORM ni de infraestructura. */
|
||||||
|
export const toPaymentMethodAuditState = (paymentMethod: PaymentMethod) =>
|
||||||
|
({
|
||||||
|
name: paymentMethod.name.toPrimitive(),
|
||||||
|
description: paymentMethod.description.match(
|
||||||
|
(description) => description.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
isActive: paymentMethod.isActive,
|
||||||
|
isSystem: paymentMethod.isSystem,
|
||||||
|
isDefault: paymentMethod.isDefault,
|
||||||
|
}) satisfies AuditState;
|
||||||
|
|
||||||
|
/** El label conserva el nombre visible del recurso en el momento de la mutación. */
|
||||||
|
export const toPaymentMethodAuditTarget = (paymentMethod: PaymentMethod): AuditMutationTarget => ({
|
||||||
|
type: "payment_method",
|
||||||
|
id: paymentMethod.id.toPrimitive(),
|
||||||
|
label: paymentMethod.name.toPrimitive(),
|
||||||
|
});
|
||||||
@ -1,10 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
import { type IPaymentMethodCreator, PaymentMethodCreator } from "../services";
|
import { type IPaymentMethodCreator, PaymentMethodCreator } from "../services";
|
||||||
|
|
||||||
export const buildPaymentMethodCreator = (params: {
|
export const buildPaymentMethodCreator = (params: {
|
||||||
repository: IPaymentMethodRepository;
|
repository: IPaymentMethodRepository;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentMethodCreator => {
|
}): IPaymentMethodCreator => {
|
||||||
const { repository } = params;
|
const { repository, audit } = params;
|
||||||
|
|
||||||
return new PaymentMethodCreator(repository);
|
return new PaymentMethodCreator(repository, audit);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
import { type IPaymentMethodDeleter, PaymentMethodDeleter } from "../services";
|
|
||||||
import type { IPaymentMethodUsageChecker } from "../services";
|
import type { IPaymentMethodUsageChecker } from "../services";
|
||||||
|
import { type IPaymentMethodDeleter, PaymentMethodDeleter } from "../services";
|
||||||
|
|
||||||
export const buildPaymentMethodDeleter = (params: {
|
export const buildPaymentMethodDeleter = (params: {
|
||||||
repository: IPaymentMethodRepository;
|
repository: IPaymentMethodRepository;
|
||||||
usageChecker: IPaymentMethodUsageChecker;
|
usageChecker: IPaymentMethodUsageChecker;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentMethodDeleter => {
|
}): IPaymentMethodDeleter => {
|
||||||
const { repository, usageChecker } = params;
|
const { repository, usageChecker, audit } = params;
|
||||||
|
|
||||||
return new PaymentMethodDeleter({
|
return new PaymentMethodDeleter({
|
||||||
repository,
|
repository,
|
||||||
usageChecker,
|
usageChecker,
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
import { type IPaymentMethodStatusChanger, PaymentMethodStatusChanger } from "../services";
|
import { type IPaymentMethodStatusChanger, PaymentMethodStatusChanger } from "../services";
|
||||||
|
|
||||||
export const buildPaymentMethodStatusChanger = (params: {
|
export const buildPaymentMethodStatusChanger = (params: {
|
||||||
repository: IPaymentMethodRepository;
|
repository: IPaymentMethodRepository;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentMethodStatusChanger => {
|
}): IPaymentMethodStatusChanger => {
|
||||||
const { repository } = params;
|
const { repository, audit } = params;
|
||||||
|
|
||||||
return new PaymentMethodStatusChanger(repository);
|
return new PaymentMethodStatusChanger(repository, audit);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
import { type IPaymentMethodUpdater, PaymentMethodUpdater } from "../services";
|
import { type IPaymentMethodUpdater, PaymentMethodUpdater } from "../services";
|
||||||
|
|
||||||
export const buildPaymentMethodUpdater = (params: {
|
export const buildPaymentMethodUpdater = (params: {
|
||||||
repository: IPaymentMethodRepository;
|
repository: IPaymentMethodRepository;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentMethodUpdater => {
|
}): IPaymentMethodUpdater => {
|
||||||
const { repository } = params;
|
const { repository, audit } = params;
|
||||||
|
|
||||||
return new PaymentMethodUpdater(repository);
|
return new PaymentMethodUpdater(repository, audit);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +1,37 @@
|
|||||||
import { describe, test } from "node:test";
|
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, test } from "node:test";
|
||||||
|
|
||||||
import { EntityNotFoundError } from "@erp/core/api";
|
import { EntityNotFoundError } from "@erp/core/api";
|
||||||
|
import type { AuditError, IAudit, RecordAuditMutationShortcutInput } from "@repo/rdx-audit";
|
||||||
import { Name, UniqueID } from "@repo/rdx-ddd";
|
import { Name, UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { PaymentMethod } from "../../../../domain/payment-methods/payment-method.aggregate";
|
import { PaymentMethod } from "../../../../domain/payment-methods/payment-method.aggregate";
|
||||||
import { PaymentMethodDeleter } from "../payment-method-deleter";
|
|
||||||
import { SetDefaultPaymentMethodService } from "../set-default-payment-method.service";
|
|
||||||
import type { IPaymentMethodRepository } from "../../repositories";
|
|
||||||
import type { PaymentMethodSummary } from "../../models";
|
import type { PaymentMethodSummary } from "../../models";
|
||||||
|
import type { IPaymentMethodRepository } from "../../repositories";
|
||||||
|
import { PaymentMethodDeleter } from "../payment-method-deleter";
|
||||||
|
import { PaymentMethodStatusChanger } from "../payment-method-status-changer";
|
||||||
|
import { PaymentMethodUpdater } from "../payment-method-updater";
|
||||||
|
import { SetDefaultPaymentMethodService } from "../set-default-payment-method.service";
|
||||||
|
|
||||||
|
class AuditSpy implements IAudit {
|
||||||
|
public readonly mutations: RecordAuditMutationShortcutInput[] = [];
|
||||||
|
|
||||||
|
async record(): Promise<Result<void, AuditError>> {
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
async recordEvent(): Promise<Result<void, AuditError>> {
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
async recordMutation(input: RecordAuditMutationShortcutInput): Promise<Result<void, AuditError>> {
|
||||||
|
this.mutations.push(input);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
|
||||||
class InMemoryPaymentMethodRepository implements IPaymentMethodRepository {
|
class InMemoryPaymentMethodRepository implements IPaymentMethodRepository {
|
||||||
private readonly items = new Map<string, PaymentMethod>();
|
private readonly items = new Map<string, PaymentMethod>();
|
||||||
@ -126,9 +148,113 @@ const buildPaymentMethod = (params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("Payment method services", () => {
|
describe("Payment method services", () => {
|
||||||
|
test("enable registra la transición activa", async () => {
|
||||||
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const companyId = UniqueID.generateNewID();
|
||||||
|
const paymentMethod = buildPaymentMethod({ companyId, name: "Card", isActive: false });
|
||||||
|
await repository.create(paymentMethod);
|
||||||
|
|
||||||
|
const result = await new PaymentMethodStatusChanger(repository, audit).changeStatus({
|
||||||
|
paymentMethod,
|
||||||
|
action: "enable",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.equal(audit.mutations.length, 1);
|
||||||
|
assert.deepEqual(audit.mutations[0], {
|
||||||
|
action: "payment_method.enabled",
|
||||||
|
target: { type: "payment_method", id: paymentMethod.id.toPrimitive(), label: "Card" },
|
||||||
|
before: {
|
||||||
|
name: "Card",
|
||||||
|
description: null,
|
||||||
|
isActive: false,
|
||||||
|
isSystem: false,
|
||||||
|
isDefault: false,
|
||||||
|
},
|
||||||
|
after: { name: "Card", description: null, isActive: true, isSystem: false, isDefault: false },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("disable rechaza métodos de sistema sin auditar", async () => {
|
||||||
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentMethod = buildPaymentMethod({
|
||||||
|
companyId: UniqueID.generateNewID(),
|
||||||
|
name: "System",
|
||||||
|
isSystem: true,
|
||||||
|
});
|
||||||
|
const result = await new PaymentMethodStatusChanger(repository, audit).changeStatus({
|
||||||
|
paymentMethod,
|
||||||
|
action: "disable",
|
||||||
|
});
|
||||||
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("disable registra la transición inactiva", async () => {
|
||||||
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentMethod = buildPaymentMethod({ companyId: UniqueID.generateNewID(), name: "Cash" });
|
||||||
|
const result = await new PaymentMethodStatusChanger(repository, audit).changeStatus({
|
||||||
|
paymentMethod,
|
||||||
|
action: "disable",
|
||||||
|
});
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.equal(audit.mutations.length, 1);
|
||||||
|
assert.equal(audit.mutations[0]?.action, "payment_method.disabled");
|
||||||
|
assert.equal(audit.mutations[0]?.before.isActive, true);
|
||||||
|
assert.equal(audit.mutations[0]?.after.isActive, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update rechaza métodos de sistema sin auditar", async () => {
|
||||||
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const companyId = UniqueID.generateNewID();
|
||||||
|
const paymentMethod = buildPaymentMethod({ companyId, name: "System", isSystem: true });
|
||||||
|
await repository.create(paymentMethod);
|
||||||
|
const name = Name.create("Changed");
|
||||||
|
assert.equal(name.isSuccess, true);
|
||||||
|
const result = await new PaymentMethodUpdater(repository, audit).update({
|
||||||
|
companyId,
|
||||||
|
id: paymentMethod.id,
|
||||||
|
patchProps: { name: name.data },
|
||||||
|
});
|
||||||
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delete registra estado completo y no audita rechazos", async () => {
|
||||||
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentMethod = buildPaymentMethod({ companyId: UniqueID.generateNewID(), name: "Cash" });
|
||||||
|
await repository.create(paymentMethod);
|
||||||
|
const deleter = new PaymentMethodDeleter({
|
||||||
|
repository,
|
||||||
|
audit,
|
||||||
|
usageChecker: { findUsage: async () => Result.ok([]) },
|
||||||
|
});
|
||||||
|
const result = await deleter.delete({ paymentMethod });
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.equal(audit.mutations.length, 1);
|
||||||
|
assert.deepEqual(audit.mutations[0]?.target, {
|
||||||
|
type: "payment_method",
|
||||||
|
id: paymentMethod.id.toPrimitive(),
|
||||||
|
label: "Cash",
|
||||||
|
});
|
||||||
|
assert.deepEqual(audit.mutations[0]?.before, {
|
||||||
|
name: "Cash",
|
||||||
|
description: null,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: false,
|
||||||
|
isDefault: false,
|
||||||
|
});
|
||||||
|
assert.deepEqual(audit.mutations[0]?.after, {});
|
||||||
|
assert.equal(audit.mutations[0]?.action, "payment_method.deleted");
|
||||||
|
});
|
||||||
test("set default swaps previous default atomically in repository state", async () => {
|
test("set default swaps previous default atomically in repository state", async () => {
|
||||||
const repository = new InMemoryPaymentMethodRepository();
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
const service = new SetDefaultPaymentMethodService(repository);
|
const service = new SetDefaultPaymentMethodService(repository, audit);
|
||||||
const companyId = UniqueID.generateNewID();
|
const companyId = UniqueID.generateNewID();
|
||||||
|
|
||||||
const cash = buildPaymentMethod({
|
const cash = buildPaymentMethod({
|
||||||
@ -162,7 +288,7 @@ describe("Payment method services", () => {
|
|||||||
|
|
||||||
test("set default is idempotent when method is already default", async () => {
|
test("set default is idempotent when method is already default", async () => {
|
||||||
const repository = new InMemoryPaymentMethodRepository();
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
const service = new SetDefaultPaymentMethodService(repository);
|
const service = new SetDefaultPaymentMethodService(repository, audit);
|
||||||
const companyId = UniqueID.generateNewID();
|
const companyId = UniqueID.generateNewID();
|
||||||
|
|
||||||
const cash = buildPaymentMethod({
|
const cash = buildPaymentMethod({
|
||||||
@ -184,7 +310,7 @@ describe("Payment method services", () => {
|
|||||||
|
|
||||||
test("set default rejects inactive methods", async () => {
|
test("set default rejects inactive methods", async () => {
|
||||||
const repository = new InMemoryPaymentMethodRepository();
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
const service = new SetDefaultPaymentMethodService(repository);
|
const service = new SetDefaultPaymentMethodService(repository, audit);
|
||||||
const companyId = UniqueID.generateNewID();
|
const companyId = UniqueID.generateNewID();
|
||||||
|
|
||||||
const inactive = buildPaymentMethod({
|
const inactive = buildPaymentMethod({
|
||||||
@ -205,7 +331,7 @@ describe("Payment method services", () => {
|
|||||||
|
|
||||||
test("set default preserves company isolation", async () => {
|
test("set default preserves company isolation", async () => {
|
||||||
const repository = new InMemoryPaymentMethodRepository();
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
const service = new SetDefaultPaymentMethodService(repository);
|
const service = new SetDefaultPaymentMethodService(repository, audit);
|
||||||
const companyA = UniqueID.generateNewID();
|
const companyA = UniqueID.generateNewID();
|
||||||
const companyB = UniqueID.generateNewID();
|
const companyB = UniqueID.generateNewID();
|
||||||
|
|
||||||
@ -226,11 +352,13 @@ describe("Payment method services", () => {
|
|||||||
|
|
||||||
test("delete blocks referenced payment methods", async () => {
|
test("delete blocks referenced payment methods", async () => {
|
||||||
const repository = new InMemoryPaymentMethodRepository();
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
const deleter = new PaymentMethodDeleter({
|
const deleter = new PaymentMethodDeleter({
|
||||||
repository,
|
repository,
|
||||||
usageChecker: {
|
usageChecker: {
|
||||||
findUsage: async () => Result.ok([{ source: "customers" }]),
|
findUsage: async () => Result.ok([{ source: "customers" }]),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
const companyId = UniqueID.generateNewID();
|
const companyId = UniqueID.generateNewID();
|
||||||
const paymentMethod = buildPaymentMethod({
|
const paymentMethod = buildPaymentMethod({
|
||||||
@ -245,15 +373,21 @@ describe("Payment method services", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
|
||||||
|
const persistedPaymentMethod = await repository.getByIdInCompany(companyId, paymentMethod.id);
|
||||||
|
assert.equal(persistedPaymentMethod.isSuccess, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("delete blocks system payment methods", async () => {
|
test("delete blocks system payment methods", async () => {
|
||||||
const repository = new InMemoryPaymentMethodRepository();
|
const repository = new InMemoryPaymentMethodRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
const deleter = new PaymentMethodDeleter({
|
const deleter = new PaymentMethodDeleter({
|
||||||
repository,
|
repository,
|
||||||
usageChecker: {
|
usageChecker: {
|
||||||
findUsage: async () => Result.ok([]),
|
findUsage: async () => Result.ok([]),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
const companyId = UniqueID.generateNewID();
|
const companyId = UniqueID.generateNewID();
|
||||||
const paymentMethod = buildPaymentMethod({
|
const paymentMethod = buildPaymentMethod({
|
||||||
@ -269,5 +403,9 @@ describe("Payment method services", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
|
||||||
|
const persistedPaymentMethod = await repository.getByIdInCompany(companyId, paymentMethod.id);
|
||||||
|
assert.equal(persistedPaymentMethod.isSuccess, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export * from "./payment-method-disabler";
|
|||||||
export * from "./payment-method-enabler";
|
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-usage-checker";
|
|
||||||
export * from "./payment-method-status-changer";
|
export * from "./payment-method-status-changer";
|
||||||
export * from "./payment-method-updater";
|
export * from "./payment-method-updater";
|
||||||
|
export * from "./payment-method-usage-checker";
|
||||||
export * from "./set-default-payment-method.service";
|
export * from "./set-default-payment-method.service";
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { type IPaymentMethodCreateProps, PaymentMethod } from "../../../domain";
|
import { type IPaymentMethodCreateProps, PaymentMethod } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_METHOD_AUDIT_ACTIONS,
|
||||||
|
toPaymentMethodAuditState,
|
||||||
|
toPaymentMethodAuditTarget,
|
||||||
|
} from "../audit/payment-method-audit";
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
export interface IPaymentMethodCreatorParams {
|
export interface IPaymentMethodCreatorParams {
|
||||||
@ -16,7 +22,10 @@ export interface IPaymentMethodCreator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PaymentMethodCreator implements IPaymentMethodCreator {
|
export class PaymentMethodCreator implements IPaymentMethodCreator {
|
||||||
constructor(private readonly repository: IPaymentMethodRepository) {}
|
constructor(
|
||||||
|
private readonly repository: IPaymentMethodRepository,
|
||||||
|
private readonly audit: IAudit
|
||||||
|
) {}
|
||||||
|
|
||||||
async create(params: IPaymentMethodCreatorParams): Promise<Result<PaymentMethod, Error>> {
|
async create(params: IPaymentMethodCreatorParams): Promise<Result<PaymentMethod, Error>> {
|
||||||
const { companyId, id, props, transaction } = params;
|
const { companyId, id, props, transaction } = params;
|
||||||
@ -36,6 +45,14 @@ export class PaymentMethodCreator implements IPaymentMethodCreator {
|
|||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action: PAYMENT_METHOD_AUDIT_ACTIONS.CREATED,
|
||||||
|
target: toPaymentMethodAuditTarget(paymentMethod),
|
||||||
|
before: {},
|
||||||
|
after: toPaymentMethodAuditState(paymentMethod),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||||
|
|
||||||
return Result.ok(paymentMethod);
|
return Result.ok(paymentMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,15 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentMethod } from "../../../domain";
|
import type { PaymentMethod } from "../../../domain";
|
||||||
import { PaymentMethodInUseError } from "../../../domain";
|
import { PaymentMethodInUseError } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_METHOD_AUDIT_ACTIONS,
|
||||||
|
toPaymentMethodAuditState,
|
||||||
|
toPaymentMethodAuditTarget,
|
||||||
|
} from "../audit/payment-method-audit";
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
import type { IPaymentMethodUsageChecker } from "./payment-method-usage-checker";
|
import type { IPaymentMethodUsageChecker } from "./payment-method-usage-checker";
|
||||||
|
|
||||||
export interface IPaymentMethodDeleter {
|
export interface IPaymentMethodDeleter {
|
||||||
@ -17,6 +24,7 @@ export class PaymentMethodDeleter implements IPaymentMethodDeleter {
|
|||||||
private readonly deps: {
|
private readonly deps: {
|
||||||
repository: IPaymentMethodRepository;
|
repository: IPaymentMethodRepository;
|
||||||
usageChecker: IPaymentMethodUsageChecker;
|
usageChecker: IPaymentMethodUsageChecker;
|
||||||
|
audit: IAudit;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -25,6 +33,8 @@ export class PaymentMethodDeleter implements IPaymentMethodDeleter {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}): Promise<Result<PaymentMethod, Error>> {
|
}): Promise<Result<PaymentMethod, Error>> {
|
||||||
const { paymentMethod, transaction } = params;
|
const { paymentMethod, transaction } = params;
|
||||||
|
const before = toPaymentMethodAuditState(paymentMethod);
|
||||||
|
const target = toPaymentMethodAuditTarget(paymentMethod);
|
||||||
|
|
||||||
const invariantResult = paymentMethod.ensureCanBeDeleted();
|
const invariantResult = paymentMethod.ensureCanBeDeleted();
|
||||||
if (invariantResult.isFailure) {
|
if (invariantResult.isFailure) {
|
||||||
@ -60,6 +70,14 @@ export class PaymentMethodDeleter implements IPaymentMethodDeleter {
|
|||||||
return Result.fail(deleteResult.error);
|
return Result.fail(deleteResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.deps.audit.recordMutation({
|
||||||
|
action: PAYMENT_METHOD_AUDIT_ACTIONS.DELETED,
|
||||||
|
target,
|
||||||
|
before,
|
||||||
|
after: {},
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||||
|
|
||||||
return Result.ok(paymentMethod);
|
return Result.ok(paymentMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentMethod } from "../../../domain";
|
import type { PaymentMethod } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_METHOD_AUDIT_ACTIONS,
|
||||||
|
toPaymentMethodAuditState,
|
||||||
|
toPaymentMethodAuditTarget,
|
||||||
|
} from "../audit/payment-method-audit";
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
export type PaymentMethodStatusChangeAction = "enable" | "disable";
|
export type PaymentMethodStatusChangeAction = "enable" | "disable";
|
||||||
@ -14,7 +20,10 @@ export interface IPaymentMethodStatusChanger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PaymentMethodStatusChanger implements IPaymentMethodStatusChanger {
|
export class PaymentMethodStatusChanger implements IPaymentMethodStatusChanger {
|
||||||
public constructor(private readonly repository: IPaymentMethodRepository) {}
|
public constructor(
|
||||||
|
private readonly repository: IPaymentMethodRepository,
|
||||||
|
private readonly audit: IAudit
|
||||||
|
) {}
|
||||||
|
|
||||||
public async changeStatus(params: {
|
public async changeStatus(params: {
|
||||||
paymentMethod: PaymentMethod;
|
paymentMethod: PaymentMethod;
|
||||||
@ -22,6 +31,7 @@ export class PaymentMethodStatusChanger implements IPaymentMethodStatusChanger {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}): Promise<Result<PaymentMethod, Error>> {
|
}): Promise<Result<PaymentMethod, Error>> {
|
||||||
const { paymentMethod, action, transaction } = params;
|
const { paymentMethod, action, transaction } = params;
|
||||||
|
const before = toPaymentMethodAuditState(paymentMethod);
|
||||||
|
|
||||||
const statusResult = action === "enable" ? paymentMethod.enable() : paymentMethod.disable();
|
const statusResult = action === "enable" ? paymentMethod.enable() : paymentMethod.disable();
|
||||||
|
|
||||||
@ -39,6 +49,17 @@ export class PaymentMethodStatusChanger implements IPaymentMethodStatusChanger {
|
|||||||
return Result.fail(persistenceResult.error);
|
return Result.fail(persistenceResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action:
|
||||||
|
action === "enable"
|
||||||
|
? PAYMENT_METHOD_AUDIT_ACTIONS.ENABLED
|
||||||
|
: PAYMENT_METHOD_AUDIT_ACTIONS.DISABLED,
|
||||||
|
target: toPaymentMethodAuditTarget(paymentMethod),
|
||||||
|
before,
|
||||||
|
after: toPaymentMethodAuditState(paymentMethod),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||||
|
|
||||||
return Result.ok(paymentMethod);
|
return Result.ok(paymentMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentMethod, PaymentMethodPatchProps } from "../../../domain";
|
import type { PaymentMethod, PaymentMethodPatchProps } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_METHOD_AUDIT_ACTIONS,
|
||||||
|
toPaymentMethodAuditState,
|
||||||
|
toPaymentMethodAuditTarget,
|
||||||
|
} from "../audit/payment-method-audit";
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
export interface IPaymentMethodUpdater {
|
export interface IPaymentMethodUpdater {
|
||||||
@ -14,7 +20,10 @@ export interface IPaymentMethodUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PaymentMethodUpdater implements IPaymentMethodUpdater {
|
export class PaymentMethodUpdater implements IPaymentMethodUpdater {
|
||||||
constructor(private readonly repository: IPaymentMethodRepository) {}
|
constructor(
|
||||||
|
private readonly repository: IPaymentMethodRepository,
|
||||||
|
private readonly audit: IAudit
|
||||||
|
) {}
|
||||||
|
|
||||||
async update(params: {
|
async update(params: {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -32,6 +41,7 @@ export class PaymentMethodUpdater implements IPaymentMethodUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const paymentMethod = existingResult.data;
|
const paymentMethod = existingResult.data;
|
||||||
|
const before = toPaymentMethodAuditState(paymentMethod);
|
||||||
|
|
||||||
// Aplicar cambios en el agregado
|
// Aplicar cambios en el agregado
|
||||||
const updateResult = paymentMethod.update(patchProps);
|
const updateResult = paymentMethod.update(patchProps);
|
||||||
@ -54,6 +64,14 @@ export class PaymentMethodUpdater implements IPaymentMethodUpdater {
|
|||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action: PAYMENT_METHOD_AUDIT_ACTIONS.UPDATED,
|
||||||
|
target: toPaymentMethodAuditTarget(paymentMethod),
|
||||||
|
before,
|
||||||
|
after: toPaymentMethodAuditState(paymentMethod),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||||
|
|
||||||
return Result.ok(paymentMethod);
|
return Result.ok(paymentMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { PaymentMethodCannotBeDefaultError, type PaymentMethod } from "../../../domain";
|
import { type PaymentMethod, PaymentMethodCannotBeDefaultError } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_METHOD_AUDIT_ACTIONS,
|
||||||
|
toPaymentMethodAuditState,
|
||||||
|
toPaymentMethodAuditTarget,
|
||||||
|
} from "../audit/payment-method-audit";
|
||||||
import type { IPaymentMethodRepository } from "../repositories";
|
import type { IPaymentMethodRepository } from "../repositories";
|
||||||
|
|
||||||
export interface ISetDefaultPaymentMethodService {
|
export interface ISetDefaultPaymentMethodService {
|
||||||
@ -13,7 +19,10 @@ export interface ISetDefaultPaymentMethodService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodService {
|
export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodService {
|
||||||
public constructor(private readonly repository: IPaymentMethodRepository) {}
|
public constructor(
|
||||||
|
private readonly repository: IPaymentMethodRepository,
|
||||||
|
private readonly audit: IAudit
|
||||||
|
) {}
|
||||||
|
|
||||||
public async setDefault(params: {
|
public async setDefault(params: {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -56,6 +65,7 @@ export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodS
|
|||||||
const currentDefault = currentDefaultResult.data.getOrUndefined() ?? null;
|
const currentDefault = currentDefaultResult.data.getOrUndefined() ?? null;
|
||||||
|
|
||||||
if (currentDefault && currentDefault.id.toString() !== paymentMethod.id.toString()) {
|
if (currentDefault && currentDefault.id.toString() !== paymentMethod.id.toString()) {
|
||||||
|
const previousDefaultState = toPaymentMethodAuditState(currentDefault);
|
||||||
const unmarkResult = currentDefault.unmarkAsDefault();
|
const unmarkResult = currentDefault.unmarkAsDefault();
|
||||||
if (unmarkResult.isFailure) {
|
if (unmarkResult.isFailure) {
|
||||||
return Result.fail(unmarkResult.error);
|
return Result.fail(unmarkResult.error);
|
||||||
@ -66,9 +76,17 @@ export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodS
|
|||||||
if (saveCurrentResult.isFailure) {
|
if (saveCurrentResult.isFailure) {
|
||||||
return Result.fail(saveCurrentResult.error);
|
return Result.fail(saveCurrentResult.error);
|
||||||
}
|
}
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action: PAYMENT_METHOD_AUDIT_ACTIONS.DEFAULT_UNSET,
|
||||||
|
target: toPaymentMethodAuditTarget(currentDefault),
|
||||||
|
before: previousDefaultState,
|
||||||
|
after: toPaymentMethodAuditState(currentDefault),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previousPaymentMethodState = toPaymentMethodAuditState(paymentMethod);
|
||||||
const markResult = paymentMethod.markAsDefault();
|
const markResult = paymentMethod.markAsDefault();
|
||||||
if (markResult.isFailure) {
|
if (markResult.isFailure) {
|
||||||
return Result.fail(markResult.error);
|
return Result.fail(markResult.error);
|
||||||
@ -79,6 +97,13 @@ export class SetDefaultPaymentMethodService implements ISetDefaultPaymentMethodS
|
|||||||
if (savePaymentMethodResult.isFailure) {
|
if (savePaymentMethodResult.isFailure) {
|
||||||
return Result.fail(savePaymentMethodResult.error);
|
return Result.fail(savePaymentMethodResult.error);
|
||||||
}
|
}
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action: PAYMENT_METHOD_AUDIT_ACTIONS.DEFAULT_SET,
|
||||||
|
target: toPaymentMethodAuditTarget(paymentMethod),
|
||||||
|
before: previousPaymentMethodState,
|
||||||
|
after: toPaymentMethodAuditState(paymentMethod),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) return Result.fail(auditResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(paymentMethod);
|
return Result.ok(paymentMethod);
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import type { 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 type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
|
|
||||||
import type { ISetDefaultPaymentMethodService } from "../services";
|
import type { ISetDefaultPaymentMethodService } from "../services";
|
||||||
|
import type { IPaymentMethodFullSnapshotBuilder } from "../snapshot-builders";
|
||||||
|
|
||||||
export type SetDefaultPaymentMethodByIdUseCaseInput = {
|
export type SetDefaultPaymentMethodByIdUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
|
|||||||
@ -25,10 +25,10 @@ import {
|
|||||||
ListPaymentMethodsUseCase,
|
ListPaymentMethodsUseCase,
|
||||||
UpdatePaymentMethodByIdUseCase,
|
UpdatePaymentMethodByIdUseCase,
|
||||||
} from "../../../application/payment-methods/use-cases";
|
} from "../../../application/payment-methods/use-cases";
|
||||||
|
import { buildPaymentMethodReferenceUsageChecker } from "../payment-method-reference-usage-checker";
|
||||||
|
|
||||||
import { buildPaymentMethodPersistenceMappers } from "./payment-method-persistence-mappers.di";
|
import { buildPaymentMethodPersistenceMappers } from "./payment-method-persistence-mappers.di";
|
||||||
import { buildPaymentMethodRepository } from "./payment-method-repositories.di";
|
import { buildPaymentMethodRepository } from "./payment-method-repositories.di";
|
||||||
import { buildPaymentMethodReferenceUsageChecker } from "../payment-method-reference-usage-checker";
|
|
||||||
|
|
||||||
export type PaymentMethodsInternalDeps = {
|
export type PaymentMethodsInternalDeps = {
|
||||||
repository: IPaymentMethodRepository;
|
repository: IPaymentMethodRepository;
|
||||||
@ -47,7 +47,7 @@ export type PaymentMethodsInternalDeps = {
|
|||||||
export const buildPaymentMethodsDependencies = (
|
export const buildPaymentMethodsDependencies = (
|
||||||
params: ModuleParams
|
params: ModuleParams
|
||||||
): PaymentMethodsInternalDeps => {
|
): PaymentMethodsInternalDeps => {
|
||||||
const { database, transactionContext } = params;
|
const { audit, database, transactionContext } = params;
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
const transactionManager = buildTransactionManager(database as Sequelize, transactionContext);
|
const transactionManager = buildTransactionManager(database as Sequelize, transactionContext);
|
||||||
@ -59,11 +59,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, audit });
|
||||||
const updater = buildPaymentMethodUpdater({ repository });
|
const updater = buildPaymentMethodUpdater({ repository, audit });
|
||||||
const deleter = buildPaymentMethodDeleter({ repository, usageChecker });
|
const deleter = buildPaymentMethodDeleter({ repository, usageChecker, audit });
|
||||||
const statusChanger = buildPaymentMethodStatusChanger({ repository });
|
const statusChanger = buildPaymentMethodStatusChanger({ repository, audit });
|
||||||
const setDefaultService = new SetDefaultPaymentMethodService(repository);
|
const setDefaultService = new SetDefaultPaymentMethodService(repository, audit);
|
||||||
|
|
||||||
const snapshotBuilders = buildPaymentMethodSnapshotBuilders();
|
const snapshotBuilders = buildPaymentMethodSnapshotBuilders();
|
||||||
|
|
||||||
|
|||||||
@ -180,6 +180,9 @@ importers:
|
|||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: ^2.4.11
|
specifier: ^2.4.11
|
||||||
version: 2.4.11
|
version: 2.4.11
|
||||||
|
'@repo/rdx-ddd':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-ddd
|
||||||
'@repo/typescript-config':
|
'@repo/typescript-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/typescript-config
|
version: link:../../packages/typescript-config
|
||||||
@ -428,6 +431,9 @@ importers:
|
|||||||
'@repo/i18next':
|
'@repo/i18next':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/i18n
|
version: link:../../packages/i18n
|
||||||
|
'@repo/rdx-audit':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/rdx-audit
|
||||||
'@repo/rdx-criteria':
|
'@repo/rdx-criteria':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/rdx-criteria
|
version: link:../../packages/rdx-criteria
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user