import type { UniqueID } from "@repo/rdx-ddd"; import type { Result as DomainResult } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils"; import type { Sequelize, Transaction } from "sequelize"; import { Op } from "sequelize"; import type { ICompanyRepository } from "../../application/company.repository"; import type { Company } from "../../domain/company.aggregate"; import { CompanyError } from "../../domain/company-error"; import { CompanyModel, PersonModel, SiteModel } from "./company.model"; import type { CompanyPersistenceMapper } from "./company-persistence.mapper"; import { AssignmentModel, AssignmentRevisionModel, DepartmentModel } from "./people.model"; export class SequelizeCompanyRepository implements ICompanyRepository { constructor( private readonly database: Sequelize, private readonly mapper: CompanyPersistenceMapper ) {} private async children(ids: readonly string[], transaction?: Transaction) { const options = { where: { companyId: { [Op.in]: ids } }, ...(transaction ? { transaction } : {}), }; const [people, sites, departments, assignments] = await Promise.all([ PersonModel.findAll({ ...options, order: [ ["name", "ASC"], ["id", "ASC"], ], }), SiteModel.findAll({ ...options, order: [["name", "ASC"]] }), DepartmentModel.findAll({ ...options, order: [["name", "ASC"]] }), AssignmentModel.findAll({ ...options, order: [ ["startDate", "DESC"], ["id", "ASC"], ], }), ]); const revisions = assignments.length ? await AssignmentRevisionModel.findAll({ where: { assignmentId: { [Op.in]: assignments.map((item) => item.id) } }, ...(transaction ? { transaction } : {}), order: [["sequence", "ASC"]], }) : []; return { people, sites, departments, assignments, revisions }; } async list(): Promise { return this.database.transaction(async (transaction) => { const companies = await CompanyModel.findAll({ transaction, order: [ ["name", "ASC"], ["id", "ASC"], ], }); if (!companies.length) { return []; } const all = await this.children( companies.map((item) => item.id), transaction ); return companies.map((company) => { const assignments = all.assignments.filter((item) => item.companyId === company.id); return this.mapper.toDomain({ company, people: all.people.filter((item) => item.companyId === company.id), sites: all.sites.filter((item) => item.companyId === company.id), departments: all.departments.filter((item) => item.companyId === company.id), assignments, revisions: all.revisions.filter((item) => assignments.some((assignment) => assignment.id === item.assignmentId) ), }); }); }); } private async load( id: UniqueID, transaction: Transaction, lock = false ): Promise> { const company = await CompanyModel.findByPk(id.toPrimitive(), { transaction, ...(lock ? { lock: transaction.LOCK.UPDATE } : {}), }); if (!company) { return Maybe.none(); } return Maybe.some( this.mapper.toDomain({ company, ...(await this.children([company.id], transaction)) }) ); } findById(id: UniqueID) { return this.database.transaction((transaction) => this.load(id, transaction)); } async create(company: Company): Promise { await CompanyModel.create(this.mapper.toPersistence(company).company); return company; } async removePerson( id: UniqueID, personId: UniqueID ): Promise> { return this.database.transaction(async (transaction) => { const current = (await this.load(id, transaction, true)).getOrUndefined(); if (!current) { return Result.fail(new CompanyError("COMPANY_NOT_FOUND")); } const changed = current.removePerson(personId); if (!changed.isSuccess) { return changed; } try { await PersonModel.destroy({ where: { id: personId.toPrimitive(), companyId: id.toPrimitive() }, transaction, }); } catch { return Result.fail(new CompanyError("PERSON_IN_USE")); } return changed; }); } async removeAssignment( id: UniqueID, personId: UniqueID, assignmentId: UniqueID ): Promise> { return this.database.transaction(async (transaction) => { const current = (await this.load(id, transaction, true)).getOrUndefined(); if (!current) { return Result.fail(new CompanyError("COMPANY_NOT_FOUND")); } const changed = current.removeAssignment(personId, assignmentId); if (!changed.isSuccess) { return changed; } await AssignmentRevisionModel.destroy({ where: { assignmentId: assignmentId.toPrimitive() }, transaction, }); await AssignmentModel.destroy({ where: { id: assignmentId.toPrimitive(), companyId: id.toPrimitive() }, transaction, }); return changed; }); } async update( id: UniqueID, change: (company: Company) => DomainResult ): Promise> { return this.database.transaction(async (transaction) => { const current = (await this.load(id, transaction, true)).getOrUndefined(); if (!current) { return Result.fail(new CompanyError("COMPANY_NOT_FOUND")); } const changed = change(current); if (!changed.isSuccess || changed.data === current) { return changed; } const record = this.mapper.toPersistence(changed.data); await CompanyModel.update(record.company, { where: { id: id.toPrimitive() }, transaction }); if (record.people.length) { await PersonModel.bulkCreate(record.people, { transaction, updateOnDuplicate: ["name", "position", "email", "phone", "active", "isContact"], }); } if (record.sites.length) { await SiteModel.bulkCreate(record.sites, { transaction, updateOnDuplicate: ["name", "address", "phone"], }); } if (record.departments.length) { await DepartmentModel.bulkCreate(record.departments, { transaction, updateOnDuplicate: ["name", "nameKey", "active"], }); } if (record.assignments.length) { await AssignmentModel.bulkCreate(record.assignments, { transaction, updateOnDuplicate: ["position", "startDate", "endDate", "isPrimary"], }); } const persistedIds = new Set( current.assignments.flatMap((item) => item.history.map((revision) => revision.id.toPrimitive()) ) ); const newRevisions = record.revisions.filter((item) => !persistedIds.has(item.id)); if (newRevisions.length) { await AssignmentRevisionModel.bulkCreate(newRevisions, { transaction }); } return changed; }); } }