metis-desk/modules/companies/src/api/infrastructure/persistence/process-template-persistence.mapper.ts
David dffb4b2794 feat: add process management features including feedback, help, and templates
- Implemented ProcessFeedback component for error handling in process forms.
- Created ProcessHelp component to provide contextual assistance for users.
- Added internationalization support for process-related strings in Spanish and English.
- Developed ProcessStartForm for initiating new processes with person and template selection.
- Introduced ProcessTemplateForm for creating and editing process templates.
- Built ProcessTemplatesPage to display and manage process templates.
- Enhanced ProcessesPage to list current processes and allow for creation and detail viewing.
- Added unit tests for various components to ensure functionality and correctness.
2026-10-07 09:34:18 +02:00

91 lines
3.1 KiB
TypeScript

import { createHash } from "node:crypto";
import { UniqueID } from "@repo/rdx-ddd";
import { Maybe } from "@repo/rdx-utils";
import { z } from "zod/v4";
import { ProcessTemplate } from "../../domain/process-template.aggregate";
import { ProcessTemplateRevision } from "../../domain/process-template-revision.entity";
import { ProcessTemplateInputSchema } from "../../domain/process-values";
import { ProcessTemplateValues } from "../../domain/value-objects/process-template-values";
import { persistedJson } from "./work-profile-persistence.mapper";
export type ProcessTemplateRow = { id: string; nameKey: string; values: unknown };
export type ProcessTemplateRevisionRow = {
id: string;
itemId: string;
position: number;
actorId: string | null;
recordedAt: string;
values: unknown;
};
function identity(value: string) {
const parsed = UniqueID.create(value);
if (!parsed.isSuccess) {
throw new Error("Identidad de proceso inconsistente.");
}
return parsed.data;
}
function mapped(raw: unknown) {
const values = ProcessTemplateValues.create(ProcessTemplateInputSchema.parse(persistedJson(raw)));
if (!values.isSuccess) {
throw new Error("Valores de proceso inconsistentes.");
}
return values.data;
}
export class ProcessTemplatePersistenceMapper {
toDomain(row: ProcessTemplateRow, revisions: readonly ProcessTemplateRevisionRow[]) {
const values = mapped(row.values);
const ordered = [...revisions].sort((a, b) => a.position - b.position);
const history = ordered.map((r, index) => {
if (
r.itemId !== row.id ||
r.position !== index ||
!z.iso.datetime().safeParse(r.recordedAt).success ||
r.recordedAt < (ordered[index - 1]?.recordedAt ?? "") ||
(!r.actorId && index !== 0)
) {
throw new Error("Historial de proceso inconsistente.");
}
return ProcessTemplateRevision.rehydrate(
{
values: mapped(r.values),
actorId: r.actorId ? Maybe.some(identity(r.actorId)) : Maybe.none(),
recordedAt: r.recordedAt,
},
identity(r.id)
);
});
if (
!(history.length && history.at(-1)?.values.equals(values)) ||
row.nameKey !== processTemplateNameKey(values.name.toPrimitive())
) {
throw new Error("Raíz de proceso inconsistente.");
}
return ProcessTemplate.rehydrate({ values, history }, identity(row.id));
}
toPersistence(item: ProcessTemplate) {
const values = item.values.toPrimitive();
return {
root: {
id: item.id.toPrimitive(),
nameKey: processTemplateNameKey(values.name),
values: JSON.stringify(values),
},
revisions: item.history.map((r, position) => ({
id: r.id.toPrimitive(),
itemId: item.id.toPrimitive(),
position,
actorId: r.actorId.getOrUndefined()?.toPrimitive() ?? null,
recordedAt: r.recordedAt,
values: JSON.stringify(r.values.toPrimitive()),
})),
};
}
}
export function processTemplateNameKey(name: string) {
return createHash("sha256").update(name.toLowerCase()).digest("hex");
}