2026-10-05 11:13:51 +00:00
|
|
|
import { UniqueID } from "@repo/rdx-ddd";
|
|
|
|
|
import { z } from "zod/v4";
|
|
|
|
|
|
|
|
|
|
import { Resource } from "../../domain/resource.aggregate";
|
|
|
|
|
import { ResourceRevision } from "../../domain/resource-revision.entity";
|
|
|
|
|
import { ResourceInputSchema, ResourceSpecificationSchema } from "../../domain/resource-values";
|
2026-10-09 08:38:19 +00:00
|
|
|
import {
|
|
|
|
|
ResourceSpecification,
|
|
|
|
|
ResourceValues,
|
|
|
|
|
} from "../../domain/value-objects/resource-values.vo";
|
2026-10-05 11:13:51 +00:00
|
|
|
|
2026-10-10 11:32:40 +00:00
|
|
|
import type { InventoryRow, RevisionRow } from "./resource-inventory-relational";
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
export function inventoryIdentity(value: string) {
|
|
|
|
|
const parsed = UniqueID.create(value);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
if (!parsed.isSuccess) {
|
|
|
|
|
throw new Error("Identidad de recurso persistida inconsistente.");
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
return parsed.data;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
export class ResourcePersistenceMapper {
|
2026-10-10 11:32:40 +00:00
|
|
|
toDomain(row: InventoryRow, revisions: readonly RevisionRow[]) {
|
2026-10-05 11:13:51 +00:00
|
|
|
const specification = ResourceSpecification.create(
|
|
|
|
|
ResourceSpecificationSchema.parse(row.specification)
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
if (!specification.isSuccess) {
|
|
|
|
|
throw new Error("Especificación de recurso persistida inconsistente.");
|
|
|
|
|
}
|
2026-10-19 09:45:09 +00:00
|
|
|
const values = ResourceValues.reconstruct(
|
|
|
|
|
ResourceInputSchema.parse(row.values),
|
|
|
|
|
specification.data
|
|
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
if (!values.isSuccess) {
|
|
|
|
|
throw new Error("Valores de recurso persistidos inconsistentes.");
|
|
|
|
|
}
|
|
|
|
|
const current = values.data.toPrimitive();
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
if (row.templateId !== current.templateId) {
|
|
|
|
|
throw new Error("Referencia de recurso persistida inconsistente.");
|
|
|
|
|
}
|
|
|
|
|
const ordered = [...revisions].sort((a, b) => a.position - b.position);
|
|
|
|
|
const history = ordered.map((revision, position) => {
|
2026-10-19 09:45:09 +00:00
|
|
|
const revisionSpecification = ResourceSpecification.create(
|
|
|
|
|
ResourceSpecificationSchema.parse(revision.specification ?? row.specification)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!revisionSpecification.isSuccess) {
|
|
|
|
|
throw new Error("Especificación histórica inconsistente.");
|
|
|
|
|
}
|
|
|
|
|
const previous = ResourceValues.reconstruct(
|
2026-10-05 11:13:51 +00:00
|
|
|
ResourceInputSchema.parse(revision.values),
|
2026-10-19 09:45:09 +00:00
|
|
|
revisionSpecification.data
|
2026-10-05 11:13:51 +00:00
|
|
|
);
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
if (
|
|
|
|
|
!previous.isSuccess ||
|
|
|
|
|
revision.position !== position ||
|
|
|
|
|
(position > 0 && revision.recordedAt < (ordered[position - 1]?.recordedAt ?? "")) ||
|
|
|
|
|
!z.iso.datetime().safeParse(revision.recordedAt).success
|
|
|
|
|
) {
|
|
|
|
|
throw new Error("Historial de recurso persistido inconsistente.");
|
|
|
|
|
}
|
|
|
|
|
if (revision.resourceId !== row.id) {
|
|
|
|
|
throw new Error("Revisión fuera del recurso.");
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
return ResourceRevision.rehydrate(
|
|
|
|
|
{
|
|
|
|
|
values: previous.data,
|
2026-10-19 09:45:09 +00:00
|
|
|
specification: revisionSpecification.data,
|
2026-10-05 11:13:51 +00:00
|
|
|
actorId: inventoryIdentity(revision.actorId),
|
|
|
|
|
recordedAt: revision.recordedAt,
|
|
|
|
|
},
|
|
|
|
|
inventoryIdentity(revision.id)
|
|
|
|
|
);
|
|
|
|
|
});
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
if (!(history.length && history.at(-1)?.values.equals(values.data))) {
|
|
|
|
|
throw new Error("Última revisión de recurso inconsistente.");
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
return Resource.rehydrate(
|
|
|
|
|
{
|
|
|
|
|
companyId: inventoryIdentity(row.companyId),
|
|
|
|
|
specification: specification.data,
|
|
|
|
|
values: values.data,
|
|
|
|
|
history,
|
|
|
|
|
},
|
|
|
|
|
inventoryIdentity(row.id)
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
toPersistence(resource: Resource) {
|
|
|
|
|
const values = resource.values.toPrimitive();
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-05 11:13:51 +00:00
|
|
|
return {
|
|
|
|
|
resource: {
|
|
|
|
|
id: resource.id.toPrimitive(),
|
|
|
|
|
companyId: resource.companyId.toPrimitive(),
|
|
|
|
|
templateId: values.templateId,
|
|
|
|
|
specification: resource.specification.toPrimitive(),
|
|
|
|
|
values,
|
|
|
|
|
},
|
|
|
|
|
revisions: resource.history.map((revision, position) => ({
|
|
|
|
|
id: revision.id.toPrimitive(),
|
|
|
|
|
resourceId: resource.id.toPrimitive(),
|
|
|
|
|
actorId: revision.actorId.toPrimitive(),
|
|
|
|
|
recordedAt: revision.recordedAt,
|
|
|
|
|
values: revision.values.toPrimitive(),
|
2026-10-19 09:45:09 +00:00
|
|
|
specification: revision.specification.toPrimitive(),
|
2026-10-05 11:13:51 +00:00
|
|
|
position,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|