118 lines
3.9 KiB
TypeScript
118 lines
3.9 KiB
TypeScript
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,
|
|
})),
|
|
};
|
|
}
|
|
}
|