276 lines
9.5 KiB
TypeScript
276 lines
9.5 KiB
TypeScript
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<string, string>,
|
|
resourceNames: ReadonlyMap<string, string>
|
|
) {
|
|
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<Row>(
|
|
`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<string, string>,
|
|
resourceNames: ReadonlyMap<string, string>
|
|
) {
|
|
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<string, string>,
|
|
resourceNames: ReadonlyMap<string, string>
|
|
) {
|
|
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<ReturnType<IProcessAssetInventoryService["list"]>>,
|
|
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;
|
|
}
|
|
}
|