metis-desk/modules/companies/src/api/infrastructure/persistence/ticket-persistence.mapper.ts

118 lines
3.9 KiB
TypeScript
Raw Normal View History

2026-10-07 09:41:56 +00:00
import { Name, UniqueID } from "@repo/rdx-ddd";
import { z } from "zod/v4";
import { Ticket } from "../../domain/ticket.aggregate";
import { TicketRevision } from "../../domain/ticket-revision.entity";
import { TicketInputSchema } from "../../domain/ticket-values";
import { TicketValues } from "../../domain/value-objects/ticket-values";
const Root = z.strictObject({
id: z.uuid(),
companyId: z.uuid(),
reference: z.string().regex(/^T-[0-9]{6,}$/),
requestKey: z.uuid(),
requesterId: z.uuid().nullable(),
assigneeId: z.uuid().nullable(),
values: TicketInputSchema,
});
const Revision = z.strictObject({
id: z.uuid(),
ticketId: z.uuid(),
actorId: z.uuid(),
requesterId: z.uuid().nullable(),
assigneeId: z.uuid().nullable(),
values: TicketInputSchema,
recordedAt: z.iso.datetime(),
position: z.number().int().nonnegative(),
});
export function ticketIdentity(value: string) {
const id = UniqueID.create(value);
if (!id.isSuccess) {
throw new Error("Identidad de ticket inconsistente.");
}
return id.data;
}
function json(value: unknown): unknown {
return typeof value === "string" ? JSON.parse(value) : value;
}
function values(value: unknown) {
const result = TicketValues.create(TicketInputSchema.parse(json(value)));
if (!result.isSuccess) {
throw new Error("Valores de ticket inconsistentes.");
}
return result.data;
}
export class TicketPersistenceMapper {
toDomain(root: Record<string, unknown>, rows: readonly Record<string, unknown>[]) {
const current = Root.parse({ ...root, values: json(root.values) });
const history = rows
.map((row) => Revision.parse({ ...row, values: json(row.values) }))
.sort((a, b) => a.position - b.position);
const reference = Name.create(current.reference, 40);
if (
!(reference.isSuccess && history.length) ||
new Set(history.map((item) => item.id)).size !== history.length ||
history.some(
(item, index) =>
item.ticketId !== current.id ||
item.position !== index ||
item.requesterId !== item.values.requesterId ||
item.assigneeId !== item.values.assigneeId ||
(index > 0 && item.recordedAt < (history[index - 1]?.recordedAt ?? ""))
) ||
current.requesterId !== current.values.requesterId ||
current.assigneeId !== current.values.assigneeId
) {
throw new Error("Historial de ticket inconsistente.");
}
const revisions = history.map((item) =>
TicketRevision.rehydrate(
{
values: values(item.values),
actorId: ticketIdentity(item.actorId),
recordedAt: item.recordedAt,
},
ticketIdentity(item.id)
)
);
const currentValues = values(current.values);
if (!revisions.at(-1)?.values.equals(currentValues)) {
throw new Error("Instantánea de ticket inconsistente.");
}
return Ticket.rehydrate(
{
companyId: ticketIdentity(current.companyId),
reference: reference.data,
requestKey: ticketIdentity(current.requestKey),
values: currentValues,
history: revisions,
},
ticketIdentity(current.id)
);
}
toPersistence(ticket: Ticket) {
const input = ticket.values.toPrimitive();
return {
root: {
id: ticket.id.toPrimitive(),
companyId: ticket.companyId.toPrimitive(),
reference: ticket.reference.toPrimitive(),
requestKey: ticket.requestKey.toPrimitive(),
requesterId: input.requesterId,
assigneeId: input.assigneeId,
values: JSON.stringify(input),
},
revisions: ticket.history.map((item, position) => ({
id: item.id.toPrimitive(),
ticketId: ticket.id.toPrimitive(),
actorId: item.actorId.toPrimitive(),
requesterId: item.values.toPrimitive().requesterId,
assigneeId: item.values.toPrimitive().assigneeId,
values: JSON.stringify(item.values.toPrimitive()),
recordedAt: item.recordedAt,
position,
})),
};
}
}