feat(audit): integrate audit functionality for payment terms management
This commit is contained in:
parent
f1c0df685b
commit
71b79cc4b1
@ -0,0 +1,201 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { SequelizeTransactionManager } from "@erp/core/api";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { AuditChangeModel, AuditEventModel } from "@/infrastructure/audit/sequelize";
|
||||||
|
|
||||||
|
import { PaymentTermUpdater } from "../../../../../modules/catalogs/src/api/application/payment-terms/services/payment-term-updater.service";
|
||||||
|
import {
|
||||||
|
PaymentTermDueDays,
|
||||||
|
PaymentTermPercentage,
|
||||||
|
} from "../../../../../modules/catalogs/src/api/domain/payment-terms";
|
||||||
|
import { SequelizePaymentTermDomainMapper } from "../../../../../modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/mappers/sequelize-payment-term-domain.mapper";
|
||||||
|
import { SequelizePaymentTermSummaryMapper } from "../../../../../modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/mappers/sequelize-payment-term-summary.mapper";
|
||||||
|
import { PaymentTermDueRuleModel } from "../../../../../modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/models/sequelize-payment-term-due-rule.model";
|
||||||
|
import { SequelizePaymentTermRepository } from "../../../../../modules/catalogs/src/api/infrastructure/payment-terms/persistence/sequelize/repositories/sequelize-payment-term.repository";
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildCompanyHeaders,
|
||||||
|
createTestAccount,
|
||||||
|
createTestCompany,
|
||||||
|
createTestMembership,
|
||||||
|
loginByApi,
|
||||||
|
} from "./http-test-fixtures";
|
||||||
|
import { createHttpTestRuntime } from "./http-test-runtime";
|
||||||
|
|
||||||
|
test("payment terms registra dueRules atómicas y revierte junto con el agregado en MariaDB", async (t) => {
|
||||||
|
const runtime = await createHttpTestRuntime();
|
||||||
|
t.after(async () => runtime.stop());
|
||||||
|
|
||||||
|
const account = await createTestAccount(runtime.database, {
|
||||||
|
email: `payment-term-audit-${randomUUID()}@example.test`,
|
||||||
|
password: "Password123!",
|
||||||
|
});
|
||||||
|
const company = await createTestCompany(runtime.database, {
|
||||||
|
legalName: "Payment term 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 paymentTermId = randomUUID();
|
||||||
|
|
||||||
|
const createResponse = await runtime.request("/api/v1/catalogs/payment-terms", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
...buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: paymentTermId,
|
||||||
|
name: "Net 30",
|
||||||
|
is_active: true,
|
||||||
|
due_rules: [{ due_days: "30", percentage: { value: "10000", scale: "2" } }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(createResponse.status, 201);
|
||||||
|
|
||||||
|
const updateResponse = await runtime.request(`/api/v1/catalogs/payment-terms/${paymentTermId}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
...buildCompanyHeaders({ accessToken, companyId: company.id }),
|
||||||
|
"content-type": "application/json",
|
||||||
|
"x-correlation-id": "payment-term-due-rules-correlation",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
due_rules: [
|
||||||
|
{ due_days: "30", percentage: { value: "5000", scale: "2" } },
|
||||||
|
{ due_days: "60", percentage: { value: "5000", scale: "2" } },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(updateResponse.status, 200);
|
||||||
|
|
||||||
|
const auditEvent = await AuditEventModel.findOne({
|
||||||
|
where: { action: "payment_term.updated", target_id: paymentTermId },
|
||||||
|
});
|
||||||
|
assert.ok(auditEvent);
|
||||||
|
assert.equal(auditEvent.source_type, "application");
|
||||||
|
assert.equal(auditEvent.source_id, "uecko-erp");
|
||||||
|
assert.equal(auditEvent.origin, "http");
|
||||||
|
assert.equal(auditEvent.operation_id, updateResponse.headers.get("x-request-id"));
|
||||||
|
assert.equal(auditEvent.correlation_id, "payment-term-due-rules-correlation");
|
||||||
|
assert.equal(auditEvent.actor_type, "user");
|
||||||
|
assert.equal(auditEvent.actor_id, account.id);
|
||||||
|
assert.equal(auditEvent.scope_type, "company");
|
||||||
|
assert.equal(auditEvent.scope_id, company.id);
|
||||||
|
assert.equal(auditEvent.target_type, "payment_term");
|
||||||
|
assert.equal(auditEvent.target_label, "Net 30");
|
||||||
|
|
||||||
|
const auditChanges = await AuditChangeModel.findAll({
|
||||||
|
where: { audit_event_id: auditEvent.id },
|
||||||
|
order: [["position", "ASC"]],
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
auditChanges.map((change) => ({
|
||||||
|
path: change.path,
|
||||||
|
kind: change.kind,
|
||||||
|
before: change.before_value,
|
||||||
|
after: change.after_value,
|
||||||
|
})),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: "dueRules",
|
||||||
|
kind: "change",
|
||||||
|
before: [{ dueDays: 30, percentage: "100.00" }],
|
||||||
|
after: [
|
||||||
|
{ dueDays: 30, percentage: "50.00" },
|
||||||
|
{ dueDays: 60, percentage: "50.00" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
auditChanges.some((change) => change.path.startsWith("dueRules.")),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const dueDays30 = PaymentTermDueDays.create(30);
|
||||||
|
const dueDays60 = PaymentTermDueDays.create(60);
|
||||||
|
const dueDays90 = PaymentTermDueDays.create(90);
|
||||||
|
const percentage25 = PaymentTermPercentage.create({ value: 2500, scale: 2 });
|
||||||
|
const percentage50 = PaymentTermPercentage.create({ value: 5000, scale: 2 });
|
||||||
|
assert.equal(dueDays30.isSuccess, true);
|
||||||
|
assert.equal(dueDays60.isSuccess, true);
|
||||||
|
assert.equal(dueDays90.isSuccess, true);
|
||||||
|
assert.equal(percentage25.isSuccess, true);
|
||||||
|
assert.equal(percentage50.isSuccess, true);
|
||||||
|
|
||||||
|
const repository = new SequelizePaymentTermRepository(
|
||||||
|
new SequelizePaymentTermDomainMapper(),
|
||||||
|
new SequelizePaymentTermSummaryMapper(),
|
||||||
|
runtime.database
|
||||||
|
);
|
||||||
|
const updater = new PaymentTermUpdater(repository, runtime.audit);
|
||||||
|
const transactionManager = new SequelizeTransactionManager(
|
||||||
|
runtime.database,
|
||||||
|
runtime.transactionContext
|
||||||
|
);
|
||||||
|
const paymentTermIdResult = UniqueID.create(paymentTermId);
|
||||||
|
assert.equal(paymentTermIdResult.isSuccess, true);
|
||||||
|
const changesBeforeRollback = await AuditChangeModel.count();
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() =>
|
||||||
|
runtime.operationContext.run(
|
||||||
|
{
|
||||||
|
operationId: "payment-term-rollback-operation",
|
||||||
|
correlationId: "payment-term-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: paymentTermIdResult.data,
|
||||||
|
patchProps: {
|
||||||
|
dueRules: [
|
||||||
|
{ dueDays: dueDays30.data, percentage: percentage25.data },
|
||||||
|
{ dueDays: dueDays60.data, percentage: percentage25.data },
|
||||||
|
{ dueDays: dueDays90.data, percentage: percentage50.data },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
assert.equal(updateResult.isSuccess, true);
|
||||||
|
return Result.fail(new Error("forced payment term rollback"));
|
||||||
|
})
|
||||||
|
),
|
||||||
|
/forced payment term rollback/
|
||||||
|
);
|
||||||
|
|
||||||
|
const persistedRules = await PaymentTermDueRuleModel.findAll({
|
||||||
|
where: { payment_term_id: paymentTermId },
|
||||||
|
order: [["due_days", "ASC"]],
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
persistedRules.map((rule) => ({ dueDays: rule.due_days, percentage: rule.percentage_value })),
|
||||||
|
[
|
||||||
|
{ dueDays: 30, percentage: 5000 },
|
||||||
|
{ dueDays: 60, percentage: 5000 },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
await AuditEventModel.count({ where: { operation_id: "payment-term-rollback-operation" } }),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert.equal(await AuditChangeModel.count(), changesBeforeRollback);
|
||||||
|
});
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import type { AuditMutationTarget, AuditState } from "@repo/rdx-audit";
|
||||||
|
|
||||||
|
import type { PaymentTerm } from "../../../domain";
|
||||||
|
|
||||||
|
export const PAYMENT_TERM_AUDIT_ACTIONS = {
|
||||||
|
CREATED: "payment_term.created",
|
||||||
|
UPDATED: "payment_term.updated",
|
||||||
|
ENABLED: "payment_term.enabled",
|
||||||
|
DISABLED: "payment_term.disabled",
|
||||||
|
DELETED: "payment_term.deleted",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const toPercentageString = (params: { value: number; scale: number }): string => {
|
||||||
|
const divisor = 10 ** params.scale;
|
||||||
|
const integer = Math.floor(params.value / divisor);
|
||||||
|
const decimal = String(params.value % divisor).padStart(params.scale, "0");
|
||||||
|
|
||||||
|
return `${integer}.${decimal}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Proyección histórica estable; las reglas ya llegan ordenadas por dueDays desde el dominio. */
|
||||||
|
export const toPaymentTermAuditState = (paymentTerm: PaymentTerm) =>
|
||||||
|
({
|
||||||
|
name: paymentTerm.name.toPrimitive(),
|
||||||
|
description: paymentTerm.description.match(
|
||||||
|
(description) => description.toPrimitive(),
|
||||||
|
() => null
|
||||||
|
),
|
||||||
|
isActive: paymentTerm.isActive,
|
||||||
|
isSystem: paymentTerm.isSystem,
|
||||||
|
dueRules: paymentTerm.dueRules.getAll().map((rule) => ({
|
||||||
|
dueDays: rule.dueDays.toPrimitive(),
|
||||||
|
percentage: toPercentageString(rule.percentage),
|
||||||
|
})),
|
||||||
|
}) satisfies AuditState;
|
||||||
|
|
||||||
|
/** El nombre se conserva como label del recurso en el momento de la mutación. */
|
||||||
|
export const toPaymentTermAuditTarget = (paymentTerm: PaymentTerm): AuditMutationTarget => ({
|
||||||
|
type: "payment_term",
|
||||||
|
id: paymentTerm.id.toPrimitive(),
|
||||||
|
label: paymentTerm.name.toPrimitive(),
|
||||||
|
});
|
||||||
@ -1,10 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentTermRepository } from "../repositories";
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
import { type IPaymentTermCreator, PaymentTermCreator } from "../services";
|
import { type IPaymentTermCreator, PaymentTermCreator } from "../services";
|
||||||
|
|
||||||
export const buildPaymentTermCreator = (params: {
|
export const buildPaymentTermCreator = (params: {
|
||||||
repository: IPaymentTermRepository;
|
repository: IPaymentTermRepository;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentTermCreator => {
|
}): IPaymentTermCreator => {
|
||||||
const { repository } = params;
|
const { repository, audit } = params;
|
||||||
|
|
||||||
return new PaymentTermCreator(repository);
|
return new PaymentTermCreator(repository, audit);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentTermRepository } from "../repositories";
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
import type { IPaymentTermUsageChecker } from "../services";
|
import type { IPaymentTermUsageChecker } from "../services";
|
||||||
import { type IPaymentTermDeleter, PaymentTermDeleter } from "../services";
|
import { type IPaymentTermDeleter, PaymentTermDeleter } from "../services";
|
||||||
@ -5,8 +7,9 @@ import { type IPaymentTermDeleter, PaymentTermDeleter } from "../services";
|
|||||||
export const buildPaymentTermDeleter = (params: {
|
export const buildPaymentTermDeleter = (params: {
|
||||||
repository: IPaymentTermRepository;
|
repository: IPaymentTermRepository;
|
||||||
usageChecker: IPaymentTermUsageChecker;
|
usageChecker: IPaymentTermUsageChecker;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentTermDeleter => {
|
}): IPaymentTermDeleter => {
|
||||||
const { repository, usageChecker } = params;
|
const { repository, usageChecker, audit } = params;
|
||||||
|
|
||||||
return new PaymentTermDeleter({ repository, usageChecker });
|
return new PaymentTermDeleter({ repository, usageChecker, audit });
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Result } from "@repo/rdx-utils";
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ import { type IPaymentTermStatusChanger, PaymentTermStatusChanger } from "../ser
|
|||||||
|
|
||||||
export const buildPaymentTermStatusChanger = (params: {
|
export const buildPaymentTermStatusChanger = (params: {
|
||||||
repository: IPaymentTermRepository;
|
repository: IPaymentTermRepository;
|
||||||
|
audit: IAudit;
|
||||||
companyDefaultUsageChecker?: {
|
companyDefaultUsageChecker?: {
|
||||||
isUsedAsCompanyDefault(params: {
|
isUsedAsCompanyDefault(params: {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -14,7 +16,7 @@ export const buildPaymentTermStatusChanger = (params: {
|
|||||||
}): Promise<Result<boolean, Error>>;
|
}): Promise<Result<boolean, Error>>;
|
||||||
};
|
};
|
||||||
}): IPaymentTermStatusChanger => {
|
}): IPaymentTermStatusChanger => {
|
||||||
const { repository, companyDefaultUsageChecker } = params;
|
const { repository, audit, companyDefaultUsageChecker } = params;
|
||||||
|
|
||||||
return new PaymentTermStatusChanger({ repository, companyDefaultUsageChecker });
|
return new PaymentTermStatusChanger({ repository, companyDefaultUsageChecker, audit });
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
|
|
||||||
import type { IPaymentTermRepository } from "../repositories";
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
import { type IPaymentTermUpdater, PaymentTermUpdater } from "../services";
|
import { type IPaymentTermUpdater, PaymentTermUpdater } from "../services";
|
||||||
|
|
||||||
export const buildPaymentTermUpdater = (params: {
|
export const buildPaymentTermUpdater = (params: {
|
||||||
repository: IPaymentTermRepository;
|
repository: IPaymentTermRepository;
|
||||||
|
audit: IAudit;
|
||||||
}): IPaymentTermUpdater => {
|
}): IPaymentTermUpdater => {
|
||||||
const { repository } = params;
|
const { repository, audit } = params;
|
||||||
|
|
||||||
return new PaymentTermUpdater(repository);
|
return new PaymentTermUpdater(repository, audit);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|||||||
import { describe, test } from "node:test";
|
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 { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
@ -11,8 +12,29 @@ import { PaymentTermName } from "../../../../domain/payment-terms/payment-term-n
|
|||||||
import { PaymentTermPercentage } from "../../../../domain/payment-terms/payment-term-percentage";
|
import { PaymentTermPercentage } from "../../../../domain/payment-terms/payment-term-percentage";
|
||||||
import type { PaymentTermSummary } from "../../models/payment-term-summary.model";
|
import type { PaymentTermSummary } from "../../models/payment-term-summary.model";
|
||||||
import type { IPaymentTermRepository } from "../../repositories/payment-term-repository.interface";
|
import type { IPaymentTermRepository } from "../../repositories/payment-term-repository.interface";
|
||||||
|
import { PaymentTermCreator } from "../payment-term-creator.service";
|
||||||
import { PaymentTermDeleter } from "../payment-term-deleter.service";
|
import { PaymentTermDeleter } from "../payment-term-deleter.service";
|
||||||
import { PaymentTermStatusChanger } from "../payment-term-status-changer.service";
|
import { PaymentTermStatusChanger } from "../payment-term-status-changer.service";
|
||||||
|
import { PaymentTermUpdater } from "../payment-term-updater.service";
|
||||||
|
|
||||||
|
class AuditSpy implements IAudit {
|
||||||
|
public readonly mutations: RecordAuditMutationShortcutInput[] = [];
|
||||||
|
|
||||||
|
public constructor(private readonly failure?: Error) {}
|
||||||
|
|
||||||
|
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 this.failure ? Result.fail(this.failure) : Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class InMemoryPaymentTermRepository implements IPaymentTermRepository {
|
class InMemoryPaymentTermRepository implements IPaymentTermRepository {
|
||||||
private readonly items = new Map<string, PaymentTerm>();
|
private readonly items = new Map<string, PaymentTerm>();
|
||||||
@ -124,9 +146,174 @@ const createPaymentTerm = (companyId = UniqueID.generateNewID()) => {
|
|||||||
return paymentTermResult.data;
|
return paymentTermResult.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createSystemPaymentTerm = () => {
|
||||||
|
const companyId = UniqueID.generateNewID();
|
||||||
|
const nameResult = PaymentTermName.create("System term");
|
||||||
|
assert.equal(nameResult.isSuccess, true);
|
||||||
|
const paymentTermResult = PaymentTerm.create(
|
||||||
|
{
|
||||||
|
companyId,
|
||||||
|
name: nameResult.data,
|
||||||
|
description: Maybe.none(),
|
||||||
|
isActive: true,
|
||||||
|
isSystem: true,
|
||||||
|
dueRules: [createRuleProps({ dueDays: 0, percentageValue: 10000 })],
|
||||||
|
},
|
||||||
|
UniqueID.generateNewID()
|
||||||
|
);
|
||||||
|
assert.equal(paymentTermResult.isSuccess, true);
|
||||||
|
|
||||||
|
return paymentTermResult.data;
|
||||||
|
};
|
||||||
|
|
||||||
describe("Payment term services", () => {
|
describe("Payment term services", () => {
|
||||||
|
test("create registra el estado completo", async () => {
|
||||||
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const companyId = UniqueID.generateNewID();
|
||||||
|
const paymentTerm = createPaymentTerm(companyId);
|
||||||
|
|
||||||
|
const result = await new PaymentTermCreator(repository, audit).create({
|
||||||
|
companyId,
|
||||||
|
id: paymentTerm.id,
|
||||||
|
props: {
|
||||||
|
companyId,
|
||||||
|
name: paymentTerm.name,
|
||||||
|
description: paymentTerm.description,
|
||||||
|
isActive: paymentTerm.isActive,
|
||||||
|
isSystem: paymentTerm.isSystem,
|
||||||
|
dueRules: paymentTerm.dueRules.getAll().map((rule) => ({
|
||||||
|
dueDays: rule.dueDays,
|
||||||
|
percentage: rule.percentage,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
transaction: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.deepEqual(audit.mutations, [
|
||||||
|
{
|
||||||
|
action: "payment_term.created",
|
||||||
|
target: { type: "payment_term", id: paymentTerm.id.toPrimitive(), label: "30/60" },
|
||||||
|
before: {},
|
||||||
|
after: {
|
||||||
|
name: "30/60",
|
||||||
|
description: null,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: false,
|
||||||
|
dueRules: [
|
||||||
|
{ dueDays: 30, percentage: "50.00" },
|
||||||
|
{ dueDays: 60, percentage: "50.00" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update registra reglas de vencimiento como un único estado ordenado", async () => {
|
||||||
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentTerm = createPaymentTerm();
|
||||||
|
await repository.create(paymentTerm);
|
||||||
|
|
||||||
|
const result = await new PaymentTermUpdater(repository, audit).update({
|
||||||
|
companyId: paymentTerm.companyId,
|
||||||
|
id: paymentTerm.id,
|
||||||
|
patchProps: {
|
||||||
|
dueRules: [
|
||||||
|
createRuleProps({ dueDays: 60, percentageValue: 5000 }),
|
||||||
|
createRuleProps({ dueDays: 30, percentageValue: 5000 }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
|
||||||
|
const changedRulesResult = await new PaymentTermUpdater(repository, audit).update({
|
||||||
|
companyId: paymentTerm.companyId,
|
||||||
|
id: paymentTerm.id,
|
||||||
|
patchProps: {
|
||||||
|
dueRules: [
|
||||||
|
createRuleProps({ dueDays: 30, percentageValue: 5000 }),
|
||||||
|
createRuleProps({ dueDays: 60, percentageValue: 2500 }),
|
||||||
|
createRuleProps({ dueDays: 90, percentageValue: 2500 }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(changedRulesResult.isSuccess, true);
|
||||||
|
assert.deepEqual(audit.mutations, [
|
||||||
|
{
|
||||||
|
action: "payment_term.updated",
|
||||||
|
target: { type: "payment_term", id: paymentTerm.id.toPrimitive(), label: "30/60" },
|
||||||
|
before: {
|
||||||
|
name: "30/60",
|
||||||
|
description: null,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: false,
|
||||||
|
dueRules: [
|
||||||
|
{ dueDays: 30, percentage: "50.00" },
|
||||||
|
{ dueDays: 60, percentage: "50.00" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
name: "30/60",
|
||||||
|
description: null,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: false,
|
||||||
|
dueRules: [
|
||||||
|
{ dueDays: 30, percentage: "50.00" },
|
||||||
|
{ dueDays: 60, percentage: "25.00" },
|
||||||
|
{ dueDays: 90, percentage: "25.00" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("propaga el fallo de auditoría al actualizar", async () => {
|
||||||
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const paymentTerm = createPaymentTerm();
|
||||||
|
await repository.create(paymentTerm);
|
||||||
|
const auditFailure = new Error("audit unavailable");
|
||||||
|
|
||||||
|
const result = await new PaymentTermUpdater(repository, new AuditSpy(auditFailure)).update({
|
||||||
|
companyId: paymentTerm.companyId,
|
||||||
|
id: paymentTerm.id,
|
||||||
|
patchProps: { isActive: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(result.error, auditFailure);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update rechaza plazos de sistema y not found sin auditar", async () => {
|
||||||
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentTerm = createSystemPaymentTerm();
|
||||||
|
await repository.create(paymentTerm);
|
||||||
|
const updater = new PaymentTermUpdater(repository, audit);
|
||||||
|
|
||||||
|
const systemResult = await updater.update({
|
||||||
|
companyId: paymentTerm.companyId,
|
||||||
|
id: paymentTerm.id,
|
||||||
|
patchProps: { isActive: false },
|
||||||
|
});
|
||||||
|
const notFoundResult = await updater.update({
|
||||||
|
companyId: paymentTerm.companyId,
|
||||||
|
id: UniqueID.generateNewID(),
|
||||||
|
patchProps: { isActive: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(systemResult.isFailure, true);
|
||||||
|
assert.equal(notFoundResult.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test("delete allows removing unreferenced custom payment terms", async () => {
|
test("delete allows removing unreferenced custom payment terms", async () => {
|
||||||
const repository = new InMemoryPaymentTermRepository();
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
const paymentTerm = createPaymentTerm();
|
const paymentTerm = createPaymentTerm();
|
||||||
await repository.create(paymentTerm);
|
await repository.create(paymentTerm);
|
||||||
|
|
||||||
@ -135,6 +322,7 @@ describe("Payment term services", () => {
|
|||||||
usageChecker: {
|
usageChecker: {
|
||||||
findUsage: async () => Result.ok([]),
|
findUsage: async () => Result.ok([]),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await deleter.delete({
|
const result = await deleter.delete({
|
||||||
@ -142,10 +330,28 @@ describe("Payment term services", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.isSuccess, true);
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.deepEqual(audit.mutations, [
|
||||||
|
{
|
||||||
|
action: "payment_term.deleted",
|
||||||
|
target: { type: "payment_term", id: paymentTerm.id.toPrimitive(), label: "30/60" },
|
||||||
|
before: {
|
||||||
|
name: "30/60",
|
||||||
|
description: null,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: false,
|
||||||
|
dueRules: [
|
||||||
|
{ dueDays: 30, percentage: "50.00" },
|
||||||
|
{ dueDays: 60, percentage: "50.00" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
after: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("delete blocks referenced payment terms", async () => {
|
test("delete blocks referenced payment terms", async () => {
|
||||||
const repository = new InMemoryPaymentTermRepository();
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
const paymentTerm = createPaymentTerm();
|
const paymentTerm = createPaymentTerm();
|
||||||
await repository.create(paymentTerm);
|
await repository.create(paymentTerm);
|
||||||
|
|
||||||
@ -154,6 +360,7 @@ describe("Payment term services", () => {
|
|||||||
usageChecker: {
|
usageChecker: {
|
||||||
findUsage: async () => Result.ok([{ source: "customers" }]),
|
findUsage: async () => Result.ok([{ source: "customers" }]),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await deleter.delete({
|
const result = await deleter.delete({
|
||||||
@ -161,43 +368,34 @@ describe("Payment term services", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
assert.equal(
|
||||||
|
(await repository.getByIdInCompany(paymentTerm.companyId, paymentTerm.id)).isSuccess,
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("delete blocks system payment terms", async () => {
|
test("delete blocks system payment terms", async () => {
|
||||||
const companyId = UniqueID.generateNewID();
|
const audit = new AuditSpy();
|
||||||
const nameResult = PaymentTermName.create("Contado sistema");
|
|
||||||
assert.equal(nameResult.isSuccess, true);
|
|
||||||
|
|
||||||
const paymentTermResult = PaymentTerm.create(
|
|
||||||
{
|
|
||||||
companyId,
|
|
||||||
name: nameResult.data,
|
|
||||||
description: Maybe.none(),
|
|
||||||
isActive: true,
|
|
||||||
isSystem: true,
|
|
||||||
dueRules: [createRuleProps({ dueDays: 0, percentageValue: 10000 })],
|
|
||||||
},
|
|
||||||
UniqueID.generateNewID()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(paymentTermResult.isSuccess, true);
|
|
||||||
|
|
||||||
const deleter = new PaymentTermDeleter({
|
const deleter = new PaymentTermDeleter({
|
||||||
repository: new InMemoryPaymentTermRepository(),
|
repository: new InMemoryPaymentTermRepository(),
|
||||||
usageChecker: {
|
usageChecker: {
|
||||||
findUsage: async () => Result.ok([]),
|
findUsage: async () => Result.ok([]),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await deleter.delete({
|
const result = await deleter.delete({
|
||||||
paymentTerm: paymentTermResult.data,
|
paymentTerm: createSystemPaymentTerm(),
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("disable permite desactivar un payment term que no es default de company", async () => {
|
test("disable permite desactivar un payment term que no es default de company", async () => {
|
||||||
const repository = new InMemoryPaymentTermRepository();
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
const paymentTerm = createPaymentTerm();
|
const paymentTerm = createPaymentTerm();
|
||||||
await repository.create(paymentTerm);
|
await repository.create(paymentTerm);
|
||||||
|
|
||||||
@ -206,6 +404,7 @@ describe("Payment term services", () => {
|
|||||||
companyDefaultUsageChecker: {
|
companyDefaultUsageChecker: {
|
||||||
isUsedAsCompanyDefault: async () => Result.ok(false),
|
isUsedAsCompanyDefault: async () => Result.ok(false),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await changer.changeStatus({
|
const result = await changer.changeStatus({
|
||||||
@ -215,10 +414,14 @@ describe("Payment term services", () => {
|
|||||||
|
|
||||||
assert.equal(result.isSuccess, true);
|
assert.equal(result.isSuccess, true);
|
||||||
assert.equal(result.data.isActive, false);
|
assert.equal(result.data.isActive, false);
|
||||||
|
assert.equal(audit.mutations[0]?.action, "payment_term.disabled");
|
||||||
|
assert.equal(audit.mutations[0]?.before.isActive, true);
|
||||||
|
assert.equal(audit.mutations[0]?.after.isActive, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("disable rechaza desactivar un payment term usado como default de company", async () => {
|
test("disable rechaza desactivar un payment term usado como default de company", async () => {
|
||||||
const repository = new InMemoryPaymentTermRepository();
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
const paymentTerm = createPaymentTerm();
|
const paymentTerm = createPaymentTerm();
|
||||||
await repository.create(paymentTerm);
|
await repository.create(paymentTerm);
|
||||||
|
|
||||||
@ -227,6 +430,7 @@ describe("Payment term services", () => {
|
|||||||
companyDefaultUsageChecker: {
|
companyDefaultUsageChecker: {
|
||||||
isUsedAsCompanyDefault: async () => Result.ok(true),
|
isUsedAsCompanyDefault: async () => Result.ok(true),
|
||||||
},
|
},
|
||||||
|
audit,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await changer.changeStatus({
|
const result = await changer.changeStatus({
|
||||||
@ -236,5 +440,44 @@ describe("Payment term services", () => {
|
|||||||
|
|
||||||
assert.equal(result.isFailure, true);
|
assert.equal(result.isFailure, true);
|
||||||
assert.match(result.error.message, /plazo por defecto/i);
|
assert.match(result.error.message, /plazo por defecto/i);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("enable registra la transición activa", async () => {
|
||||||
|
const repository = new InMemoryPaymentTermRepository();
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentTerm = createPaymentTerm();
|
||||||
|
const disableResult = paymentTerm.disable();
|
||||||
|
assert.equal(disableResult.isSuccess, true);
|
||||||
|
await repository.create(paymentTerm);
|
||||||
|
|
||||||
|
const result = await new PaymentTermStatusChanger({ repository, audit }).changeStatus({
|
||||||
|
paymentTerm,
|
||||||
|
action: "enable",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.isSuccess, true);
|
||||||
|
assert.equal(audit.mutations[0]?.action, "payment_term.enabled");
|
||||||
|
assert.equal(audit.mutations[0]?.before.isActive, false);
|
||||||
|
assert.equal(audit.mutations[0]?.after.isActive, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("disable rechaza plazos de sistema sin auditar", async () => {
|
||||||
|
const audit = new AuditSpy();
|
||||||
|
const paymentTerm = createSystemPaymentTerm();
|
||||||
|
|
||||||
|
const result = await new PaymentTermStatusChanger({
|
||||||
|
repository: new InMemoryPaymentTermRepository(),
|
||||||
|
audit,
|
||||||
|
companyDefaultUsageChecker: {
|
||||||
|
isUsedAsCompanyDefault: async () => Result.ok(false),
|
||||||
|
},
|
||||||
|
}).changeStatus({
|
||||||
|
paymentTerm,
|
||||||
|
action: "disable",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.isFailure, true);
|
||||||
|
assert.equal(audit.mutations.length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 IPaymentTermCreateProps, PaymentTerm } from "../../../domain";
|
import { type IPaymentTermCreateProps, PaymentTerm } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_TERM_AUDIT_ACTIONS,
|
||||||
|
toPaymentTermAuditState,
|
||||||
|
toPaymentTermAuditTarget,
|
||||||
|
} from "../audit/payment-term-audit";
|
||||||
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
export interface IPaymentTermCreatorParams {
|
export interface IPaymentTermCreatorParams {
|
||||||
@ -16,7 +22,10 @@ export interface IPaymentTermCreator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PaymentTermCreator implements IPaymentTermCreator {
|
export class PaymentTermCreator implements IPaymentTermCreator {
|
||||||
constructor(private readonly repository: IPaymentTermRepository) {}
|
constructor(
|
||||||
|
private readonly repository: IPaymentTermRepository,
|
||||||
|
private readonly audit: IAudit
|
||||||
|
) {}
|
||||||
|
|
||||||
public async create(params: IPaymentTermCreatorParams): Promise<Result<PaymentTerm, Error>> {
|
public async create(params: IPaymentTermCreatorParams): Promise<Result<PaymentTerm, Error>> {
|
||||||
const { companyId, id, props, transaction } = params;
|
const { companyId, id, props, transaction } = params;
|
||||||
@ -32,6 +41,16 @@ export class PaymentTermCreator implements IPaymentTermCreator {
|
|||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action: PAYMENT_TERM_AUDIT_ACTIONS.CREATED,
|
||||||
|
target: toPaymentTermAuditTarget(paymentTerm),
|
||||||
|
before: {},
|
||||||
|
after: toPaymentTermAuditState(paymentTerm),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) {
|
||||||
|
return Result.fail(auditResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok(paymentTerm);
|
return Result.ok(paymentTerm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentTerm } from "../../../domain";
|
import type { PaymentTerm } from "../../../domain";
|
||||||
import { PaymentTermCannotBeDeletedError } from "../../../domain";
|
import { PaymentTermCannotBeDeletedError } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_TERM_AUDIT_ACTIONS,
|
||||||
|
toPaymentTermAuditState,
|
||||||
|
toPaymentTermAuditTarget,
|
||||||
|
} from "../audit/payment-term-audit";
|
||||||
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
import type { IPaymentTermUsageChecker } from "./payment-term-usage-checker";
|
import type { IPaymentTermUsageChecker } from "./payment-term-usage-checker";
|
||||||
@ -18,6 +24,7 @@ export class PaymentTermDeleter implements IPaymentTermDeleter {
|
|||||||
private readonly deps: {
|
private readonly deps: {
|
||||||
repository: IPaymentTermRepository;
|
repository: IPaymentTermRepository;
|
||||||
usageChecker: IPaymentTermUsageChecker;
|
usageChecker: IPaymentTermUsageChecker;
|
||||||
|
audit: IAudit;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -26,6 +33,8 @@ export class PaymentTermDeleter implements IPaymentTermDeleter {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}): Promise<Result<PaymentTerm, Error>> {
|
}): Promise<Result<PaymentTerm, Error>> {
|
||||||
const { paymentTerm, transaction } = params;
|
const { paymentTerm, transaction } = params;
|
||||||
|
const before = toPaymentTermAuditState(paymentTerm);
|
||||||
|
const target = toPaymentTermAuditTarget(paymentTerm);
|
||||||
|
|
||||||
const invariantResult = paymentTerm.ensureCanBeDeleted();
|
const invariantResult = paymentTerm.ensureCanBeDeleted();
|
||||||
if (invariantResult.isFailure) {
|
if (invariantResult.isFailure) {
|
||||||
@ -61,6 +70,16 @@ export class PaymentTermDeleter implements IPaymentTermDeleter {
|
|||||||
return Result.fail(deleteResult.error);
|
return Result.fail(deleteResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.deps.audit.recordMutation({
|
||||||
|
action: PAYMENT_TERM_AUDIT_ACTIONS.DELETED,
|
||||||
|
target,
|
||||||
|
before,
|
||||||
|
after: {},
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) {
|
||||||
|
return Result.fail(auditResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok(paymentTerm);
|
return Result.ok(paymentTerm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import type { IAudit } from "@repo/rdx-audit";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import type { PaymentTerm } from "../../../domain";
|
import type { PaymentTerm } from "../../../domain";
|
||||||
import { PaymentTermCannotBeDisabledError } from "../../../domain";
|
import { PaymentTermCannotBeDisabledError } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_TERM_AUDIT_ACTIONS,
|
||||||
|
toPaymentTermAuditState,
|
||||||
|
toPaymentTermAuditTarget,
|
||||||
|
} from "../audit/payment-term-audit";
|
||||||
import type { IPaymentTermRepository } from "../repositories";
|
import type { IPaymentTermRepository } from "../repositories";
|
||||||
|
|
||||||
export type PaymentTermStatusChangeAction = "enable" | "disable";
|
export type PaymentTermStatusChangeAction = "enable" | "disable";
|
||||||
@ -25,6 +31,7 @@ export class PaymentTermStatusChanger implements IPaymentTermStatusChanger {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}): Promise<Result<boolean, Error>>;
|
}): Promise<Result<boolean, Error>>;
|
||||||
};
|
};
|
||||||
|
audit: IAudit;
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -34,6 +41,7 @@ export class PaymentTermStatusChanger implements IPaymentTermStatusChanger {
|
|||||||
transaction?: unknown;
|
transaction?: unknown;
|
||||||
}): Promise<Result<PaymentTerm, Error>> {
|
}): Promise<Result<PaymentTerm, Error>> {
|
||||||
const { paymentTerm, action, transaction } = params;
|
const { paymentTerm, action, transaction } = params;
|
||||||
|
const before = toPaymentTermAuditState(paymentTerm);
|
||||||
|
|
||||||
if (action === "disable" && this.deps.companyDefaultUsageChecker) {
|
if (action === "disable" && this.deps.companyDefaultUsageChecker) {
|
||||||
const companyDefaultUsageResult =
|
const companyDefaultUsageResult =
|
||||||
@ -70,6 +78,19 @@ export class PaymentTermStatusChanger implements IPaymentTermStatusChanger {
|
|||||||
return Result.fail(persistenceResult.error);
|
return Result.fail(persistenceResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.deps.audit.recordMutation({
|
||||||
|
action:
|
||||||
|
action === "enable"
|
||||||
|
? PAYMENT_TERM_AUDIT_ACTIONS.ENABLED
|
||||||
|
: PAYMENT_TERM_AUDIT_ACTIONS.DISABLED,
|
||||||
|
target: toPaymentTermAuditTarget(paymentTerm),
|
||||||
|
before,
|
||||||
|
after: toPaymentTermAuditState(paymentTerm),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) {
|
||||||
|
return Result.fail(auditResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok(paymentTerm);
|
return Result.ok(paymentTerm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { PaymentTerm, PaymentTermPatchProps } from "../../../domain";
|
import type { PaymentTerm, PaymentTermPatchProps } from "../../../domain";
|
||||||
|
import {
|
||||||
|
PAYMENT_TERM_AUDIT_ACTIONS,
|
||||||
|
toPaymentTermAuditState,
|
||||||
|
toPaymentTermAuditTarget,
|
||||||
|
} from "../audit/payment-term-audit";
|
||||||
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
import type { IPaymentTermRepository } from "../repositories/payment-term-repository.interface";
|
||||||
|
|
||||||
export interface IPaymentTermUpdater {
|
export interface IPaymentTermUpdater {
|
||||||
@ -14,7 +20,10 @@ export interface IPaymentTermUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PaymentTermUpdater implements IPaymentTermUpdater {
|
export class PaymentTermUpdater implements IPaymentTermUpdater {
|
||||||
constructor(private readonly repository: IPaymentTermRepository) {}
|
constructor(
|
||||||
|
private readonly repository: IPaymentTermRepository,
|
||||||
|
private readonly audit: IAudit
|
||||||
|
) {}
|
||||||
|
|
||||||
public async update(params: {
|
public async update(params: {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
@ -30,8 +39,8 @@ export class PaymentTermUpdater implements IPaymentTermUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const paymentTerm = existingResult.data;
|
const paymentTerm = existingResult.data;
|
||||||
|
const before = toPaymentTermAuditState(paymentTerm);
|
||||||
|
|
||||||
// Aplicar cambios en el agregado
|
|
||||||
const updateResult = paymentTerm.update(patchProps);
|
const updateResult = paymentTerm.update(patchProps);
|
||||||
if (updateResult.isFailure) {
|
if (updateResult.isFailure) {
|
||||||
return Result.fail(updateResult.error);
|
return Result.fail(updateResult.error);
|
||||||
@ -41,12 +50,21 @@ export class PaymentTermUpdater implements IPaymentTermUpdater {
|
|||||||
return Result.ok(paymentTerm);
|
return Result.ok(paymentTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persistir cambios
|
|
||||||
const saveResult = await this.repository.update(paymentTerm, transaction);
|
const saveResult = await this.repository.update(paymentTerm, transaction);
|
||||||
if (saveResult.isFailure) {
|
if (saveResult.isFailure) {
|
||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auditResult = await this.audit.recordMutation({
|
||||||
|
action: PAYMENT_TERM_AUDIT_ACTIONS.UPDATED,
|
||||||
|
target: toPaymentTermAuditTarget(paymentTerm),
|
||||||
|
before,
|
||||||
|
after: toPaymentTermAuditState(paymentTerm),
|
||||||
|
});
|
||||||
|
if (auditResult.isFailure) {
|
||||||
|
return Result.fail(auditResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok(paymentTerm);
|
return Result.ok(paymentTerm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export type PaymentTermsInternalDeps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const buildPaymentTermsDependencies = (params: ModuleParams): PaymentTermsInternalDeps => {
|
export const buildPaymentTermsDependencies = (params: ModuleParams): PaymentTermsInternalDeps => {
|
||||||
const { database, transactionContext } = params;
|
const { audit, database, transactionContext } = params;
|
||||||
|
|
||||||
const transactionManager = buildTransactionManager(database as Sequelize, transactionContext);
|
const transactionManager = buildTransactionManager(database as Sequelize, transactionContext);
|
||||||
const persistenceMappers = buildPaymentTermPersistenceMappers();
|
const persistenceMappers = buildPaymentTermPersistenceMappers();
|
||||||
@ -57,11 +57,12 @@ export const buildPaymentTermsDependencies = (params: ModuleParams): PaymentTerm
|
|||||||
const inputMappers = buildPaymentTermInputMappers();
|
const inputMappers = buildPaymentTermInputMappers();
|
||||||
|
|
||||||
const finder = buildPaymentTermFinder({ repository });
|
const finder = buildPaymentTermFinder({ repository });
|
||||||
const creator = buildPaymentTermCreator({ repository });
|
const creator = buildPaymentTermCreator({ repository, audit });
|
||||||
const updater = buildPaymentTermUpdater({ repository });
|
const updater = buildPaymentTermUpdater({ repository, audit });
|
||||||
const deleter = buildPaymentTermDeleter({ repository, usageChecker });
|
const deleter = buildPaymentTermDeleter({ repository, usageChecker, audit });
|
||||||
const statusChanger = buildPaymentTermStatusChanger({
|
const statusChanger = buildPaymentTermStatusChanger({
|
||||||
repository,
|
repository,
|
||||||
|
audit,
|
||||||
companyDefaultUsageChecker,
|
companyDefaultUsageChecker,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user