import type { Sequelize, Transaction } from "sequelize"; import { DataTypes, QueryTypes } from "sequelize"; import { WorkstationInputSchema } from "../../domain/workstation-values"; import { readAssetRows, writeAssetRow } from "./asset-relational-sql"; import type { WorkstationPersistenceMapper } from "./workstation-persistence.mapper"; type Values = ReturnType; export type WorkstationRow = { id: string; companyId: string; siteId: string | null; personId: string | null; values: Values; }; export type WorkstationRevisionRow = { id: string; workstationId: string; actorId: string; recordedAt: string; position: number; siteId: string | null; personId: string | null; values: Values; }; type StoredRow = Record & { id: string; name: string; active: boolean; siteId: string | null; personId: string | null; }; type Member = { ownerId: string; assetId: string; kind: string; position: number }; const tables = [ { root: "workstations", members: "workstation_members", owner: "workstationId" }, { root: "workstation_revisions", members: "workstation_revision_members", owner: "revisionId" }, ] as const; function reconstruct(row: StoredRow, members: readonly Member[]): Values { const ordered = members .filter((member) => member.ownerId === row.id) .sort((a, b) => a.position - b.position); if (ordered.some((member) => !["exclusive", "shared"].includes(member.kind))) { throw new Error("Tipo de asignación de activo inconsistente."); } return WorkstationInputSchema.parse({ name: row.name, active: Boolean(row.active), siteId: row.siteId, personId: row.personId, assetIds: ordered .filter((member) => member.kind === "exclusive") .map((member) => member.assetId), sharedAssetIds: ordered .filter((member) => member.kind === "shared") .map((member) => member.assetId), }); } export async function readWorkstations( database: Sequelize, companyId: string, transaction: Transaction ) { const roots = await readAssetRows>( database, "workstations", "companyId", [companyId], transaction ); const revisions = await readAssetRows>( database, "workstation_revisions", "workstationId", roots.map((row) => row.id), transaction ); const rootMembers = await database.query( "SELECT workstationId AS ownerId,assetId,kind,position FROM workstation_members WHERE workstationId IN (:ids)", { replacements: { ids: roots.map((row) => row.id).concat(["00000000-0000-0000-0000-000000000000"]), }, type: QueryTypes.SELECT, transaction, } ); const revisionMembers = await database.query( "SELECT revisionId AS ownerId,assetId,kind,position FROM workstation_revision_members WHERE revisionId IN (:ids)", { replacements: { ids: revisions.map((row) => row.id).concat(["00000000-0000-0000-0000-000000000000"]), }, type: QueryTypes.SELECT, transaction, } ); return { rows: roots.map((row) => ({ id: row.id, companyId: row.companyId, siteId: row.siteId, personId: row.personId, values: reconstruct(row, rootMembers), })), revisions: revisions.map((row) => ({ id: row.id, workstationId: row.workstationId, actorId: row.actorId, recordedAt: row.recordedAt, position: row.position, siteId: row.siteId, personId: row.personId, values: reconstruct(row, revisionMembers), })), }; } async function writeMembers( database: Sequelize, table: string, owner: string, id: string, values: Values, transaction: Transaction ) { await database.query(`DELETE FROM ${table} WHERE ${owner}=:id`, { replacements: { id }, transaction, }); for (const [kind, ids] of [ ["exclusive", values.assetIds], ["shared", values.sharedAssetIds], ] as const) { for (const [position, assetId] of ids.entries()) { await writeAssetRow(database, table, { [owner]: id, assetId, kind, position }, transaction); } } } async function writeSnapshot( database: Sequelize, definition: (typeof tables)[number], row: Record, values: Values, transaction: Transaction ) { await writeAssetRow( database, definition.root, { ...row, name: values.name, active: values.active }, transaction ); await writeMembers( database, definition.members, definition.owner, String(row.id), values, transaction ); } export async function writeWorkstation( database: Sequelize, data: ReturnType, existing: ReadonlySet, transaction: Transaction ) { const { values, ...root } = data.workstation; await writeSnapshot(database, tables[0], root, values, transaction); for (const { values, ...revision } of data.revisions) { if (!existing.has(revision.id)) { await writeSnapshot(database, tables[1], revision, values, transaction); } } } export async function initializeWorkstationMembers(database: Sequelize) { const query = database.getQueryInterface(); const existing = await query.showAllTables(); for (const definition of tables) { if (!existing.includes(definition.members)) { await query.createTable(definition.members, { [definition.owner]: { type: DataTypes.UUID, primaryKey: true, references: { model: definition.root, key: "id" }, onDelete: "CASCADE", }, assetId: { type: DataTypes.UUID, primaryKey: true, references: { model: "assets", key: "id" }, onDelete: "RESTRICT", }, kind: { type: DataTypes.STRING(10), allowNull: false }, position: { type: DataTypes.INTEGER, allowNull: false }, }); } } }