128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
|
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||
|
|
import { Result, synchronizeTemplateStructure } from "@repo/rdx-utils";
|
||
|
|
import type { Sequelize, Transaction } from "sequelize";
|
||
|
|
import { QueryTypes } from "sequelize";
|
||
|
|
|
||
|
|
import { captureResourceSpecification } from "../../application/resource-input.mapper";
|
||
|
|
import type { Resource } from "../../domain/resource.aggregate";
|
||
|
|
import type { ResourceCatalog } from "../../domain/resource-catalog.aggregate";
|
||
|
|
import { ResourceSpecification } from "../../domain/value-objects/resource-values.vo";
|
||
|
|
|
||
|
|
import { readInventory } from "./resource-inventory-relational";
|
||
|
|
import { ResourcePersistenceMapper } from "./resource-persistence.mapper";
|
||
|
|
|
||
|
|
/** Prepara todas las compañías antes de escribir: un fallo de dominio no deja el catálogo a medio propagar. */
|
||
|
|
export async function prepareResourceTemplateUpdates(
|
||
|
|
database: Sequelize,
|
||
|
|
previous: ResourceCatalog,
|
||
|
|
next: ResourceCatalog,
|
||
|
|
actorId: UniqueID | undefined,
|
||
|
|
transaction: Transaction
|
||
|
|
) {
|
||
|
|
const updates: { previous: Resource; next: Resource }[] = [];
|
||
|
|
|
||
|
|
if (!actorId) {
|
||
|
|
return Result.ok(updates);
|
||
|
|
}
|
||
|
|
const templateIds = next.definitions
|
||
|
|
.filter((definition) => {
|
||
|
|
if (definition.values.data.kind !== "template") {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const old = previous.definitions.find((item) => item.id.equals(definition.id));
|
||
|
|
|
||
|
|
return (
|
||
|
|
old &&
|
||
|
|
(!old.values.equals(definition.values) ||
|
||
|
|
definition.values.data.fields.some((binding) => {
|
||
|
|
const field = next.definitions.find(
|
||
|
|
(item) => item.id.toPrimitive() === binding.fieldId
|
||
|
|
);
|
||
|
|
const oldField = previous.definitions.find(
|
||
|
|
(item) => item.id.toPrimitive() === binding.fieldId
|
||
|
|
);
|
||
|
|
|
||
|
|
return field && oldField && !field.values.equals(oldField.values);
|
||
|
|
}))
|
||
|
|
);
|
||
|
|
})
|
||
|
|
.map((item) => item.id.toPrimitive());
|
||
|
|
|
||
|
|
if (!templateIds.length) {
|
||
|
|
return Result.ok(updates);
|
||
|
|
}
|
||
|
|
const companies = await database.query<{ companyId: string }>(
|
||
|
|
"SELECT DISTINCT companyId FROM resources WHERE templateId IN (:templateIds) ORDER BY companyId",
|
||
|
|
{ replacements: { templateIds }, type: QueryTypes.SELECT, transaction }
|
||
|
|
);
|
||
|
|
const now = new Date().toISOString();
|
||
|
|
const mapper = new ResourcePersistenceMapper();
|
||
|
|
|
||
|
|
for (const company of companies) {
|
||
|
|
await database.query("SELECT id FROM companies WHERE id=:id FOR UPDATE", {
|
||
|
|
replacements: { id: company.companyId },
|
||
|
|
type: QueryTypes.SELECT,
|
||
|
|
transaction,
|
||
|
|
});
|
||
|
|
const loaded = await readInventory(database, company.companyId, transaction);
|
||
|
|
|
||
|
|
for (const row of loaded.rows.filter((item) => templateIds.includes(item.templateId))) {
|
||
|
|
const item = mapper.toDomain(
|
||
|
|
row,
|
||
|
|
loaded.revisions.filter((revision) => revision.resourceId === row.id)
|
||
|
|
);
|
||
|
|
const changed = synchronizeResourceTemplate(item, next, actorId, now);
|
||
|
|
|
||
|
|
if (!changed.isSuccess) {
|
||
|
|
return Result.fail(changed.error);
|
||
|
|
}
|
||
|
|
if (changed.data !== item) {
|
||
|
|
updates.push({ previous: item, next: changed.data });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return Result.ok(updates);
|
||
|
|
}
|
||
|
|
|
||
|
|
function synchronizeResourceTemplate(
|
||
|
|
item: Resource,
|
||
|
|
next: ResourceCatalog,
|
||
|
|
actorId: UniqueID,
|
||
|
|
now: string
|
||
|
|
) {
|
||
|
|
const captured = captureResourceSpecification(
|
||
|
|
{ templateId: item.values.toPrimitive().templateId },
|
||
|
|
item.companyId,
|
||
|
|
{ catalog: next },
|
||
|
|
{ existing: true }
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!captured.isSuccess) {
|
||
|
|
return Result.fail(captured.error);
|
||
|
|
}
|
||
|
|
const integration = item.values.toPrimitive().integration;
|
||
|
|
const protectedFieldIds =
|
||
|
|
integration?.kind === "mailbox"
|
||
|
|
? [integration.addressFieldId, integration.contextFieldId]
|
||
|
|
: integration?.kind === "access"
|
||
|
|
? [integration.referenceFieldId]
|
||
|
|
: [];
|
||
|
|
const structure = synchronizeTemplateStructure({
|
||
|
|
previous: item.specification.toPrimitive(),
|
||
|
|
next: captured.data.toPrimitive(),
|
||
|
|
values: item.values.toPrimitive().characteristics,
|
||
|
|
protectedFieldIds,
|
||
|
|
});
|
||
|
|
const specification = ResourceSpecification.create({
|
||
|
|
...captured.data.toPrimitive(),
|
||
|
|
...structure,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!specification.isSuccess) {
|
||
|
|
return Result.fail(specification.error);
|
||
|
|
}
|
||
|
|
|
||
|
|
return item.synchronizeSpecification(specification.data, actorId, now);
|
||
|
|
}
|