metis-desk/modules/companies/src/api/infrastructure/persistence/sequelize-intervention.repository.ts

161 lines
6.3 KiB
TypeScript

import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { QueryTypes, type Sequelize, type Transaction } from "sequelize";
import { z } from "zod/v4";
import type {
IInterventionRepository,
InterventionContext,
InterventionLookups,
InterventionSaveContext,
} from "../../application/intervention.repository";
import type { IInterventionTemplateRepository } from "../../application/intervention-template.repository";
import type { ITicketRepository } from "../../application/ticket.repository";
import type { Intervention } from "../../domain/intervention.aggregate";
import { InterventionError } from "../../domain/intervention-values";
import { InterventionPersistenceMapper } from "./intervention-persistence.mapper";
export class SequelizeInterventionRepository implements IInterventionRepository {
private readonly mapper = new InterventionPersistenceMapper();
constructor(
private readonly database: Sequelize,
private readonly tickets: ITicketRepository,
private readonly templates: IInterventionTemplateRepository
) {}
private async read(
company: UniqueID | undefined,
transaction: Transaction
): Promise<Result<InterventionContext, InterventionError>> {
const context = await this.tickets.list(company);
if (!context.isSuccess) {
return Result.fail(new InterventionError(context.error.code));
}
const roots = await this.database.query<Record<string, unknown>>(
"SELECT id,companyId,reference,requestKey,ticketId,assigneeId,`values`,`procedure` FROM interventions" +
(company ? " WHERE companyId=:company" : ""),
{
transaction,
type: QueryTypes.SELECT,
replacements: { company: company?.toPrimitive() ?? null },
}
);
const revisions = await this.database.query<Record<string, unknown>>(
"SELECT r.id,r.interventionId,r.actorId,r.ticketId,r.assigneeId,r.`values`,r.recordedAt,r.position FROM intervention_revisions r JOIN interventions i ON i.id=r.interventionId" +
(company ? " WHERE i.companyId=:company" : ""),
{
transaction,
type: QueryTypes.SELECT,
replacements: { company: company?.toPrimitive() ?? null },
}
);
return Result.ok({
...context.data,
interventions: roots.map((row) =>
this.mapper.toDomain(
row,
revisions.filter((item) => item.interventionId === row.id)
)
),
});
}
list(company?: UniqueID) {
return this.database.transaction((transaction) => this.read(company, transaction));
}
async lookups(company: UniqueID): Promise<Result<InterventionLookups, InterventionError>> {
const [context, lookups, templates] = await Promise.all([
this.tickets.list(company),
this.tickets.lookups(company),
this.templates.list(company),
]);
if (!context.isSuccess) {
return Result.fail(new InterventionError(context.error.code));
}
if (!lookups.isSuccess) {
return Result.fail(new InterventionError(lookups.error.code));
}
if (!templates.isSuccess) {
return Result.fail(new InterventionError(templates.error.code));
}
return Result.ok({
context: context.data,
lookups: lookups.data,
templates: templates.data.templates,
templateActors: templates.data.actorNames,
});
}
save(
company: UniqueID,
requestKey: UniqueID,
action: (context: InterventionSaveContext) => Result<Intervention, InterventionError>
) {
return this.database.transaction(async (transaction) => {
await this.database.query("SELECT id FROM companies WHERE id=:id FOR UPDATE", {
replacements: { id: company.toPrimitive() },
transaction,
type: QueryTypes.SELECT,
});
await this.database.query("SELECT id FROM intervention_counter WHERE id=1 FOR UPDATE", {
transaction,
type: QueryTypes.SELECT,
});
const all = await this.read(undefined, transaction);
const lookups = await this.lookups(company);
if (!all.isSuccess) {
return Result.fail(all.error);
}
if (!lookups.isSuccess) {
return Result.fail(lookups.error);
}
const sequence = await this.database.query<{ nextNumber: number }>(
"SELECT nextNumber FROM intervention_counter WHERE id=1",
{ transaction, type: QueryTypes.SELECT }
);
const scoped = {
...all.data,
...lookups.data,
tickets: lookups.data.context.tickets,
interventions: all.data.interventions.filter((item) => item.companyId.equals(company)),
nextNumber: z.number().int().positive().parse(sequence[0]?.nextNumber),
requestIntervention: all.data.interventions.find((item) =>
item.requestKey.equals(requestKey)
),
};
const result = action(scoped);
if (!result.isSuccess) {
return Result.fail(result.error);
}
const previous = all.data.interventions.find((item) => item.id.equals(result.data.id));
await this.write(result.data, previous, transaction);
return Result.ok({ intervention: result.data, context: all.data });
});
}
private async write(
intervention: Intervention,
previous: Intervention | undefined,
transaction: Transaction
) {
if (previous === intervention) {
return;
}
const persisted = this.mapper.toPersistence(intervention);
await this.database.query(
previous
? "UPDATE interventions SET ticketId=:ticketId,assigneeId=:assigneeId,`values`=:values WHERE id=:id"
: "INSERT INTO interventions (id,companyId,reference,requestKey,ticketId,assigneeId,`values`,`procedure`) VALUES (:id,:companyId,:reference,:requestKey,:ticketId,:assigneeId,:values,:procedure)",
{ replacements: persisted.root, transaction }
);
for (const row of persisted.revisions.slice(previous?.history.length ?? 0)) {
await this.database.query(
"INSERT INTO intervention_revisions (id,interventionId,actorId,ticketId,assigneeId,`values`,recordedAt,position) VALUES (:id,:interventionId,:actorId,:ticketId,:assigneeId,:values,:recordedAt,:position)",
{ replacements: row, transaction }
);
}
if (!previous) {
await this.database.query(
"UPDATE intervention_counter SET nextNumber=nextNumber+1 WHERE id=1",
{ transaction }
);
}
}
}