metis-desk/modules/resources/src/api/infrastructure/persistence/prepare-resource-template-updates.ts

128 lines
4.2 KiB
TypeScript
Raw Normal View History

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);
}