- Added `useAssetPickerController` for managing asset selection logic. - Created `AssetSelectProps` type for defining asset selection properties. - Implemented `AssetPickerDialog` for displaying the asset selection interface. - Developed `AssetPickerList` for rendering the list of selectable assets. - Introduced `AssetSelect` component for integrating the asset picker into forms. - Created `AssetList` component for shared asset listing functionality. - Added tests for workstation creation logic to ensure proper validation. - Implemented workstation asset fields and association fields for managing workstation assets. - Developed workstation lifecycle dialog for managing work profile states. - Added form progress component for visualizing multi-step forms.
218 lines
5.8 KiB
TypeScript
218 lines
5.8 KiB
TypeScript
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<typeof WorkstationInputSchema.parse>;
|
|
|
|
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<string, unknown> & {
|
|
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) => member.kind !== "exclusive")) {
|
|
throw new Error("Tipo de asignación de activo inconsistente.");
|
|
}
|
|
|
|
return WorkstationInputSchema.parse({
|
|
...(row.computerAssetId ? { computerAssetId: row.computerAssetId } : {}),
|
|
name: row.name,
|
|
active: Boolean(row.active),
|
|
siteId: row.siteId,
|
|
personId: row.personId,
|
|
assetIds: ordered
|
|
.filter((member) => member.kind === "exclusive")
|
|
.map((member) => member.assetId),
|
|
});
|
|
}
|
|
|
|
export async function readWorkstations(
|
|
database: Sequelize,
|
|
companyId: string,
|
|
transaction: Transaction
|
|
) {
|
|
const roots = await readAssetRows<StoredRow & Omit<WorkstationRow, "values">>(
|
|
database,
|
|
"workstations",
|
|
"companyId",
|
|
[companyId],
|
|
transaction
|
|
);
|
|
const revisions = await readAssetRows<StoredRow & Omit<WorkstationRevisionRow, "values">>(
|
|
database,
|
|
"workstation_revisions",
|
|
"workstationId",
|
|
roots.map((row) => row.id),
|
|
transaction
|
|
);
|
|
const rootMembers = await database.query<Member>(
|
|
"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<Member>(
|
|
"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 [position, assetId] of values.assetIds.entries()) {
|
|
await writeAssetRow(
|
|
database,
|
|
table,
|
|
{ [owner]: id, assetId, kind: "exclusive", position },
|
|
transaction
|
|
);
|
|
}
|
|
}
|
|
|
|
async function writeSnapshot(
|
|
database: Sequelize,
|
|
definition: (typeof tables)[number],
|
|
row: Record<string, string | number | boolean | null>,
|
|
values: Values,
|
|
transaction: Transaction
|
|
) {
|
|
await writeAssetRow(
|
|
database,
|
|
definition.root,
|
|
{
|
|
...row,
|
|
name: values.name,
|
|
active: values.active,
|
|
computerAssetId: values.computerAssetId ?? null,
|
|
},
|
|
transaction
|
|
);
|
|
await writeMembers(
|
|
database,
|
|
definition.members,
|
|
definition.owner,
|
|
String(row.id),
|
|
values,
|
|
transaction
|
|
);
|
|
}
|
|
|
|
export async function writeWorkstation(
|
|
database: Sequelize,
|
|
data: ReturnType<WorkstationPersistenceMapper["toPersistence"]>,
|
|
existing: ReadonlySet<string>,
|
|
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 },
|
|
});
|
|
}
|
|
}
|
|
}
|