metis-desk/modules/companies/src/api/infrastructure/persistence/company-persistence.mapper.ts
David 18324f92bd feat: add person and department management features
- Implemented Person entity with validation and update methods.
- Created CalendarDate value object for date validation.
- Developed migration script to transition company_contacts to person and department structures.
- Established Sequelize models for departments and assignments.
- Introduced request DTOs for person and department management.
- Added shared DTO schemas for validation.
- Built assignment and department forms with validation and submission handling.
- Created people section to manage and display company personnel.
- Implemented person form for adding and editing personnel details.
2026-10-03 13:13:15 +02:00

176 lines
6.6 KiB
TypeScript

import type { Result as DomainResult } from "@repo/rdx-utils";
import type { InferAttributes } from "sequelize";
import { z } from "zod/v4";
import { CompanyInputMapper } from "../../application/company-input.mapper";
import { Company } from "../../domain/company.aggregate";
import { Department } from "../../domain/department.entity";
import {
AssignmentRevision,
DepartmentAssignment,
} from "../../domain/department-assignment.entity";
import { Person } from "../../domain/person.entity";
import { Site } from "../../domain/site.entity";
import type { CompanyModel, PersonModel, SiteModel } from "./company.model";
import type { AssignmentModel, AssignmentRevisionModel, DepartmentModel } from "./people.model";
export type CompanyRecord = {
company: InferAttributes<CompanyModel>;
people: InferAttributes<PersonModel>[];
sites: InferAttributes<SiteModel>[];
departments: InferAttributes<DepartmentModel>[];
assignments: InferAttributes<AssignmentModel>[];
revisions: InferAttributes<AssignmentRevisionModel>[];
};
function persisted<T>(result: DomainResult<T, Error>): T {
if (!result.isSuccess) {
throw new Error("Persistencia de compañía inconsistente.");
}
return result.data;
}
export class CompanyPersistenceMapper {
private readonly input = new CompanyInputMapper();
toDomain(record: CompanyRecord): Company {
const id = persisted(this.input.id(record.company.id));
const props = persisted(this.input.company(record.company));
const companyId = (value: string) => {
const parent = persisted(this.input.id(value));
if (!parent.equals(id)) {
throw new Error("Pertenencia persistida inconsistente.");
}
return parent;
};
const people = record.people.map((row) =>
Person.rehydrate(
{ ...persisted(this.input.person(row)), companyId: companyId(row.companyId) },
persisted(this.input.id(row.id))
)
);
const sites = record.sites.map((row) =>
Site.rehydrate(
{ ...persisted(this.input.site(row)), companyId: companyId(row.companyId) },
persisted(this.input.id(row.id))
)
);
const departments = record.departments.map((row) =>
Department.rehydrate(
{ ...persisted(this.input.department(row)), companyId: companyId(row.companyId) },
persisted(this.input.id(row.id))
)
);
const assignments = record.assignments.map((row) => {
const personId = persisted(this.input.id(row.personId));
const departmentId = persisted(this.input.id(row.departmentId));
if (
!(
people.some((item) => item.id.equals(personId)) &&
departments.some((item) => item.id.equals(departmentId))
)
) {
throw new Error("Asignación persistida inconsistente.");
}
const revisions = record.revisions
.filter((revision) => revision.assignmentId === row.id)
.sort((a, b) => a.sequence - b.sequence);
if (revisions.length === 0) {
throw new Error("Historial persistido inconsistente.");
}
const values = persisted(this.input.assignment(row));
if (!DepartmentAssignment.isValidValues(values)) {
throw new Error("Asignación persistida inconsistente.");
}
const history = revisions.map((revision, index) => {
if (
revision.sequence !== index ||
!z.iso.datetime().safeParse(revision.recordedAt).success
) {
throw new Error("Historial persistido inconsistente.");
}
const revisionValues = persisted(this.input.assignment(revision));
if (!DepartmentAssignment.isValidValues(revisionValues)) {
throw new Error("Historial persistido inconsistente.");
}
return AssignmentRevision.rehydrate(
{ ...revisionValues, recordedAt: revision.recordedAt },
persisted(this.input.id(revision.id))
);
});
const latest = revisions.at(-1);
if (
!latest ||
latest.position !== row.position ||
latest.startDate !== row.startDate ||
latest.endDate !== row.endDate ||
latest.isPrimary !== row.isPrimary
) {
throw new Error("Historial persistido inconsistente.");
}
return DepartmentAssignment.rehydrate(
{
...values,
companyId: companyId(row.companyId),
personId,
departmentId,
history,
},
persisted(this.input.id(row.id))
);
});
return Company.rehydrate({ ...props, people, sites, departments, assignments }, id);
}
toPersistence(company: Company): CompanyRecord {
const values = (item: DepartmentAssignment["values"]) => ({
position: item.position.getOrUndefined()?.toPrimitive() ?? null,
startDate: item.startDate.toPrimitive(),
endDate: item.endDate.getOrUndefined()?.toPrimitive() ?? null,
isPrimary: item.isPrimary,
});
return {
company: {
id: company.id.toPrimitive(),
name: company.name.toPrimitive(),
active: company.active,
},
people: company.people.map((person) => ({
id: person.id.toPrimitive(),
companyId: person.companyId.toPrimitive(),
name: person.name.toPrimitive(),
position: person.position.getOrUndefined()?.toPrimitive() ?? null,
email: person.email.getOrUndefined()?.toPrimitive() ?? null,
phone: person.phone.getOrUndefined()?.toPrimitive() ?? null,
active: person.active,
isContact: person.isContact,
})),
sites: company.sites.map((site) => ({
id: site.id.toPrimitive(),
companyId: site.companyId.toPrimitive(),
name: site.name.toPrimitive(),
address: site.address.getOrUndefined()?.toPrimitive() ?? null,
})),
departments: company.departments.map((item) => ({
id: item.id.toPrimitive(),
companyId: item.companyId.toPrimitive(),
name: item.name.toPrimitive(),
nameKey: item.name.toPrimitive().normalize("NFC").toLowerCase(),
active: item.active,
})),
assignments: company.assignments.map((item) => ({
id: item.id.toPrimitive(),
companyId: item.companyId.toPrimitive(),
personId: item.personId.toPrimitive(),
departmentId: item.departmentId.toPrimitive(),
...values(item.values),
})),
revisions: company.assignments.flatMap((item) =>
item.history.map((revision, sequence) => ({
id: revision.id.toPrimitive(),
assignmentId: item.id.toPrimitive(),
sequence,
recordedAt: revision.values.recordedAt,
...values(revision.values),
}))
),
};
}
}