forked from jappel/leistungsbilanz-ts
123 lines
5.1 KiB
TypeScript
123 lines
5.1 KiB
TypeScript
import type { CircuitSectionTransactionStore } from "../ports/circuit-section-transaction.store.js";
|
|
import { CircuitRepository } from "../../db/repositories/circuit.repository.js";
|
|
import { CircuitSectionRepository } from "../../db/repositories/circuit-section.repository.js";
|
|
import type {
|
|
ReorderSectionCircuitsInput,
|
|
UpdateSectionEquipmentIdentifiersInput,
|
|
} from "../../shared/validation/circuit.schemas.js";
|
|
import { CircuitNumberingService } from "./circuit-numbering.service.js";
|
|
|
|
export class CircuitWriteService {
|
|
private readonly circuitRepository: CircuitRepository;
|
|
private readonly circuitSectionRepository: CircuitSectionRepository;
|
|
private readonly circuitSectionTransactionStore?: CircuitSectionTransactionStore;
|
|
private readonly numberingService: CircuitNumberingService;
|
|
|
|
constructor(deps?: {
|
|
circuitRepository?: CircuitRepository;
|
|
circuitSectionRepository?: CircuitSectionRepository;
|
|
circuitSectionTransactionStore?: CircuitSectionTransactionStore;
|
|
numberingService?: CircuitNumberingService;
|
|
}) {
|
|
this.circuitRepository = deps?.circuitRepository ?? new CircuitRepository();
|
|
this.circuitSectionRepository = deps?.circuitSectionRepository ?? new CircuitSectionRepository();
|
|
this.circuitSectionTransactionStore = deps?.circuitSectionTransactionStore;
|
|
this.numberingService = deps?.numberingService ?? new CircuitNumberingService();
|
|
}
|
|
|
|
private getCircuitSectionTransactionStore() {
|
|
if (!this.circuitSectionTransactionStore) {
|
|
throw new Error("Circuit-section transactions are not configured.");
|
|
}
|
|
return this.circuitSectionTransactionStore;
|
|
}
|
|
|
|
async getNextIdentifier(sectionId: string) {
|
|
return this.numberingService.getNextIdentifier(sectionId);
|
|
}
|
|
|
|
async renumberSection(sectionId: string) {
|
|
// Explicit renumber operation for one section only.
|
|
// Never renumbers other sections and never runs implicitly during move/sort operations.
|
|
const section = await this.circuitSectionRepository.findById(sectionId);
|
|
if (!section) {
|
|
throw new Error("Invalid section id.");
|
|
}
|
|
const sectionCircuits = await this.circuitRepository.listBySection(sectionId);
|
|
const otherCircuits = (await this.circuitRepository.listByCircuitList(section.circuitListId)).filter(
|
|
(circuit) => circuit.sectionId !== sectionId
|
|
);
|
|
const otherIdentifiers = new Set(otherCircuits.map((circuit) => circuit.equipmentIdentifier));
|
|
|
|
const finalAssignments: Array<{ id: string; equipmentIdentifier: string }> = [];
|
|
let index = 1;
|
|
for (const circuit of sectionCircuits) {
|
|
let candidate = `${section.prefix}${index}`;
|
|
while (otherIdentifiers.has(candidate)) {
|
|
index += 1;
|
|
candidate = `${section.prefix}${index}`;
|
|
}
|
|
finalAssignments.push({ id: circuit.id, equipmentIdentifier: candidate });
|
|
index += 1;
|
|
}
|
|
// Uses safe two-phase identifier update to avoid UNIQUE collisions during swaps.
|
|
this.getCircuitSectionTransactionStore().updateEquipmentIdentifiers(
|
|
section.circuitListId,
|
|
finalAssignments,
|
|
sectionId
|
|
);
|
|
|
|
return this.circuitRepository.listBySection(sectionId);
|
|
}
|
|
|
|
async updateSectionEquipmentIdentifiers(
|
|
sectionId: string,
|
|
input: UpdateSectionEquipmentIdentifiersInput
|
|
) {
|
|
const section = await this.circuitSectionRepository.findById(sectionId);
|
|
if (!section) {
|
|
throw new Error("Invalid section id.");
|
|
}
|
|
const sectionCircuits = await this.circuitRepository.listBySection(sectionId);
|
|
const sectionIds = new Set(sectionCircuits.map((circuit) => circuit.id));
|
|
if (input.identifiers.length !== sectionCircuits.length) {
|
|
throw new Error("identifiers must include all circuits in the section.");
|
|
}
|
|
for (const entry of input.identifiers) {
|
|
if (!sectionIds.has(entry.circuitId)) {
|
|
throw new Error("Circuit id does not belong to section.");
|
|
}
|
|
}
|
|
|
|
this.getCircuitSectionTransactionStore().updateEquipmentIdentifiers(
|
|
section.circuitListId,
|
|
input.identifiers.map((entry) => ({ id: entry.circuitId, equipmentIdentifier: entry.equipmentIdentifier })),
|
|
sectionId
|
|
);
|
|
return this.circuitRepository.listBySection(sectionId);
|
|
}
|
|
|
|
async reorderCircuitsInSection(sectionId: string, input: ReorderSectionCircuitsInput) {
|
|
// Reorder updates sortOrder only. BMKs remain unchanged; users may renumber explicitly later.
|
|
const section = await this.circuitSectionRepository.findById(sectionId);
|
|
if (!section) {
|
|
throw new Error("Invalid section id.");
|
|
}
|
|
const sectionCircuits = await this.circuitRepository.listBySection(sectionId);
|
|
const sectionIds = new Set(sectionCircuits.map((circuit) => circuit.id));
|
|
if (sectionCircuits.length !== input.orderedCircuitIds.length) {
|
|
throw new Error("orderedCircuitIds must include all circuits of the section.");
|
|
}
|
|
for (const circuitId of input.orderedCircuitIds) {
|
|
if (!sectionIds.has(circuitId)) {
|
|
throw new Error("Circuit id does not belong to section.");
|
|
}
|
|
}
|
|
|
|
this.getCircuitSectionTransactionStore().updateSortOrders(
|
|
sectionId,
|
|
input.orderedCircuitIds
|
|
);
|
|
return this.circuitRepository.listBySection(sectionId);
|
|
}
|
|
}
|