import type { UniqueID } from "@repo/rdx-ddd"; import type { Sequelize, Transaction } from "sequelize"; import { QueryTypes } from "sequelize"; import type { IProcessAssetInventoryService } from "../../application/process.repository"; import type { ProcessReviewSources } from "../../application/process-review.builder"; import { buildProcessReview } from "../../application/process-review.builder"; import { accessReviewRelationships } from "../../application/process-review-access"; import { AccessDefinitionSchema } from "../../domain/access/access-definition"; import { ApplicationConfigurationInputSchema } from "../../domain/application-configuration-values"; import { CertificateConfigurationInputSchema } from "../../domain/certificate-configuration-values"; import { FolderMappingInputSchema } from "../../domain/folder-mapping-values"; import { MailSnapshotSchema } from "../../domain/mail-configuration-values"; import { MailboxUseInputSchema } from "../../domain/mailbox-values"; import { PrinterConfigurationInputSchema } from "../../domain/printer-configuration-values"; import { RemoteConnectionInputSchema } from "../../domain/remote-connection-values"; import { TeleworkAuthorizationInputSchema } from "../../domain/telework-authorization-values"; import type { WorkProfileInput } from "../../domain/work-profile-values"; import { WorkProfileInputSchema } from "../../domain/work-profile-values"; import { configurationSnapshots } from "./configuration-snapshots"; import { snapshotValues } from "./scalar-snapshot-reader"; import { profileIdentity } from "./work-profile-persistence.mapper"; function configurationDetail( value: { profileId: string; path?: string; location?: string | null; version?: string | null; resourceId?: string; printerId?: string; }, assetNames: ReadonlyMap, resourceNames: ReadonlyMap ) { const target = value.resourceId ? resourceNames.get(value.resourceId) : value.printerId ? assetNames.get(value.printerId) : undefined; return [target, value.path ?? value.location ?? value.version].filter(Boolean).join(" · "); } type Profile = { id: string; values: WorkProfileInput }; import { readAccessItems } from "./access-relational"; type Row = { id: string; values: unknown }; type Table = | "work_profiles" | "mail_configurations" | "folder_mappings" | "printer_configurations" | "application_configurations" | "certificate_configurations" | "remote_connections" | "mailbox_uses" | "telework_authorizations" | "mailboxes"; function profileOf(profiles: readonly Profile[], id: string) { const p = profiles.find((p) => p.id === id); if (!p) { throw new Error("Perfil de revisión inconsistente."); } return p; } export class ProcessReviewReader { constructor(private readonly database: Sequelize) {} private async rows(table: Table, companyId: string, transaction: Transaction) { const rows = await this.database.query( `SELECT * FROM ${table} WHERE companyId=:companyId`, { replacements: { companyId }, transaction, type: QueryTypes.SELECT } ); const definition = configurationSnapshots.get(table); return rows.map((row) => ({ ...row, values: definition ? snapshotValues(definition, row) : row.values, })); } private mail(data: readonly Row[], profiles: readonly Profile[]) { const configurations: ProcessReviewSources["configurations"][number][] = []; for (const r of data) { const v = MailSnapshotSchema.parse(r.values); const personId = "profileId" in v ? profileOf(profiles, v.profileId).values.personId : v.personId; configurations.push({ id: profileIdentity(r.id).toPrimitive(), personId, kind: "mail", label: v.address, state: v.state, }); } return configurations; } private remote( data: readonly Row[], assetNames: ReadonlyMap, resourceNames: ReadonlyMap ) { const configurations: ProcessReviewSources["configurations"][number][] = []; for (const r of data) { const v = RemoteConnectionInputSchema.parse(r.values); configurations.push({ id: profileIdentity(r.id).toPrimitive(), personId: v.personId, kind: "remote", label: [ v.method, v.sourceAssetId ? assetNames.get(v.sourceAssetId) : null, v.targetKind === "asset" ? assetNames.get(v.targetId ?? "") : v.targetKind === "resource" ? resourceNames.get(v.targetId ?? "") : null, ] .filter(Boolean) .join(" · "), sourceKind: v.sourceKind, state: v.state, }); } return configurations; } private async profileConfigurations( companyId: string, transaction: Transaction, profiles: readonly Profile[], assetNames: ReadonlyMap, resourceNames: ReadonlyMap ) { const configurations: ProcessReviewSources["configurations"][number][] = []; const tables = [ ["folder_mappings", "folder", FolderMappingInputSchema], ["printer_configurations", "printer", PrinterConfigurationInputSchema], ["application_configurations", "application", ApplicationConfigurationInputSchema], ["certificate_configurations", "certificate", CertificateConfigurationInputSchema], ] as const; for (const [table, kind, schema] of tables) { for (const r of await this.rows(table, companyId, transaction)) { const v = schema.parse(r.values); const p = profileOf(profiles, v.profileId); const detail = configurationDetail(v, assetNames, resourceNames); configurations.push({ id: profileIdentity(r.id).toPrimitive(), personId: p.values.personId, kind, label: detail ? `${p.values.name} · ${detail}` : p.values.name, state: v.state, }); } } return configurations; } async load( companyId: string, people: readonly { id: string; name: string }[], transaction: Transaction, assets: Awaited>, resources: readonly { id: UniqueID; name: string }[] ) { const assetNames = new Map(assets.map((a) => [a.id.toPrimitive(), a.name])); const resourceNames = new Map(resources.map((r) => [r.id.toPrimitive(), r.name])); const rows = (table: Table) => this.rows(table, companyId, transaction); const profiles = (await rows("work_profiles")).map((r) => ({ id: profileIdentity(r.id).toPrimitive(), values: WorkProfileInputSchema.parse(r.values), })); const configurations = [ ...this.mail(await rows("mail_configurations"), profiles), ...(await this.profileConfigurations( companyId, transaction, profiles, assetNames, resourceNames )), ...this.remote(await rows("remote_connections"), assetNames, resourceNames), ]; const relationships: ProcessReviewSources["relationships"][number][] = []; const workspaces = await this.database.query<{ id: string }>( "SELECT id FROM access_workspaces WHERE companyId=:companyId", { replacements: { companyId }, transaction, type: QueryTypes.SELECT } ); const rawAccess = (await readAccessItems(this.database, transaction)).filter((row) => workspaces.some((workspace) => workspace.id === row.workspaceId) ); const access = rawAccess.map((r) => ({ id: profileIdentity(r.id).toPrimitive(), values: AccessDefinitionSchema.parse(r.values), })); relationships.push(...accessReviewRelationships(access)); const mailboxes = await this.database.query<{ id: string; address: string }>( "SELECT id,address FROM mailboxes WHERE companyId=:companyId", { replacements: { companyId }, transaction, type: QueryTypes.SELECT } ); for (const r of await rows("mailbox_uses")) { const v = MailboxUseInputSchema.parse(r.values); relationships.push({ id: profileIdentity(r.id).toPrimitive(), kind: "mailbox_use", personId: v.personId, label: mailboxes.find((m) => m.id === v.mailboxId)?.address ?? v.mailboxId, endDate: v.endDate, configuration: null, }); } const authorizations = (await rows("telework_authorizations")).map((r) => ({ id: profileIdentity(r.id).toPrimitive(), ...TeleworkAuthorizationInputSchema.parse(r.values), })); const sources = { assets: assets.map((a) => ({ ...a, id: a.id.toPrimitive() })), profiles: profiles.map((p) => ({ id: p.id, ...p.values })), configurations, relationships, authorizations, }; const knownPeople = new Set(people.map((p) => p.id)); const relatedPeople = [ ...sources.profiles, ...sources.configurations, ...sources.relationships, ...sources.authorizations, ...sources.assets, ]; if (relatedPeople.some((item) => item.personId && !knownPeople.has(item.personId))) { throw new Error("Ámbito de revisión inconsistente."); } const result = new Map( people.map((person) => [ person.id, buildProcessReview(person.id, sources, new Date().toISOString().slice(0, 10)).map((ref) => ref.kind === "telework" ? { ...ref, label: person.name } : ref ), ]) ); for (const refs of result.values()) { if (new Set(refs.map((r) => `${r.kind}:${r.id}`)).size !== refs.length) { throw new Error("Referencias de revisión duplicadas."); } } return result; } }