- 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.
176 lines
6.6 KiB
TypeScript
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),
|
|
}))
|
|
),
|
|
};
|
|
}
|
|
}
|