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 { captureAssetSpecification } from "../../application/asset-input.mapper"; import type { Asset } from "../../domain/asset.aggregate"; import type { AssetCatalog } from "../../domain/asset-catalog.aggregate"; import { AssetSpecification } from "../../domain/value-objects/asset-values.vo"; import { readInventory } from "./asset-inventory-relational"; import { AssetPersistenceMapper } from "./asset-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 prepareAssetTemplateUpdates( database: Sequelize, previous: AssetCatalog, next: AssetCatalog, actorId: UniqueID | undefined, transaction: Transaction ) { const updates: { previous: Asset; next: Asset }[] = []; 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 assets WHERE templateId IN (:templateIds) ORDER BY companyId", { replacements: { templateIds }, type: QueryTypes.SELECT, transaction } ); const now = new Date().toISOString(); const mapper = new AssetPersistenceMapper(); 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.assetId === row.id) ); const changed = synchronizeAssetTemplate(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 synchronizeAssetTemplate(item: Asset, next: AssetCatalog, actorId: UniqueID, now: string) { const captured = captureAssetSpecification( { templateId: item.values.toPrimitive().templateId }, item.companyId, { catalog: next }, { existing: true } ); if (!captured.isSuccess) { return Result.fail(captured.error); } const structure = synchronizeTemplateStructure({ previous: item.specification.toPrimitive(), next: captured.data.toPrimitive(), values: item.values.toPrimitive().characteristics, }); const specification = AssetSpecification.create({ ...captured.data.toPrimitive(), ...structure, }); if (!specification.isSuccess) { return Result.fail(specification.error); } return item.synchronizeSpecification(specification.data, actorId, now); }