233 lines
7.1 KiB
TypeScript
233 lines
7.1 KiB
TypeScript
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<readonly Company[]> {
|
|
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<Maybe<Company>> {
|
|
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<Company> {
|
|
await CompanyModel.create(this.mapper.toPersistence(company).company);
|
|
|
|
return company;
|
|
}
|
|
|
|
async removePerson(
|
|
id: UniqueID,
|
|
personId: UniqueID
|
|
): Promise<DomainResult<Company, CompanyError>> {
|
|
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<DomainResult<Company, CompanyError>> {
|
|
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<Company, CompanyError>
|
|
): Promise<DomainResult<Company, CompanyError>> {
|
|
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;
|
|
});
|
|
}
|
|
}
|