Persist circuit reorders

This commit is contained in:
2026-07-25 21:22:10 +02:00
parent 08a2775a88
commit abcb468807
26 changed files with 914 additions and 372 deletions
@@ -0,0 +1,94 @@
import {
assertCircuitSectionReorderProjectCommand,
circuitSectionReorderCommandSchemaVersion,
type CircuitSectionReorderAssignment,
} from "./circuit-section-reorder-project-command.model.js";
import type { SerializedProjectCommand } from "./project-command.model.js";
export const circuitSectionsReorderCommandType =
"circuit.reorder-sections" as const;
export const circuitSectionsReorderCommandSchemaVersion =
circuitSectionReorderCommandSchemaVersion;
export interface CircuitSectionsReorderSection {
sectionId: string;
assignments: CircuitSectionReorderAssignment[];
}
export interface CircuitSectionsReorderCommandPayload {
sections: CircuitSectionsReorderSection[];
}
export interface CircuitSectionsReorderProjectCommand
extends SerializedProjectCommand<CircuitSectionsReorderCommandPayload> {
schemaVersion: typeof circuitSectionsReorderCommandSchemaVersion;
type: typeof circuitSectionsReorderCommandType;
}
export function createCircuitSectionsReorderProjectCommand(
sections: CircuitSectionsReorderSection[]
): CircuitSectionsReorderProjectCommand {
const command: CircuitSectionsReorderProjectCommand = {
schemaVersion: circuitSectionsReorderCommandSchemaVersion,
type: circuitSectionsReorderCommandType,
payload: { sections },
};
assertCircuitSectionsReorderProjectCommand(command);
return command;
}
export function assertCircuitSectionsReorderProjectCommand(
command: SerializedProjectCommand<unknown>
): asserts command is CircuitSectionsReorderProjectCommand {
if (
command.schemaVersion !==
circuitSectionsReorderCommandSchemaVersion ||
command.type !== circuitSectionsReorderCommandType
) {
throw new Error("Unsupported circuit sections reorder command.");
}
if (!isPlainObject(command.payload)) {
throw new Error(
"Circuit sections reorder payload must be an object."
);
}
const sections = command.payload.sections;
if (!Array.isArray(sections) || sections.length === 0) {
throw new Error(
"Circuit sections reorder command requires sections."
);
}
const sectionIds = new Set<string>();
for (const section of sections) {
if (!isPlainObject(section)) {
throw new Error(
"Circuit sections reorder command contains an invalid section."
);
}
if (
typeof section.sectionId !== "string" ||
!section.sectionId.trim()
) {
throw new Error("sectionId must be a non-empty string.");
}
if (sectionIds.has(section.sectionId)) {
throw new Error(
"Circuit sections reorder command contains duplicate section ids."
);
}
assertCircuitSectionReorderProjectCommand({
schemaVersion: circuitSectionReorderCommandSchemaVersion,
type: "circuit.reorder-section",
payload: {
sectionId: section.sectionId,
assignments: section.assignments,
},
});
sectionIds.add(section.sectionId);
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
@@ -1,4 +1,5 @@
import type { CircuitSectionReorderProjectCommand } from "../models/circuit-section-reorder-project-command.model.js";
import type { CircuitSectionsReorderProjectCommand } from "../models/circuit-sections-reorder-project-command.model.js";
import type {
AppendedProjectRevision,
ProjectRevisionSource,
@@ -11,12 +12,16 @@ export interface ExecuteCircuitSectionReorderCommandInput {
description?: string;
actorId?: string;
historyTargetChangeSetId?: string;
command: CircuitSectionReorderProjectCommand;
command:
| CircuitSectionReorderProjectCommand
| CircuitSectionsReorderProjectCommand;
}
export interface ExecutedCircuitSectionReorderCommand {
revision: AppendedProjectRevision;
inverse: CircuitSectionReorderProjectCommand;
inverse:
| CircuitSectionReorderProjectCommand
| CircuitSectionsReorderProjectCommand;
}
export interface CircuitSectionReorderProjectCommandStore {
@@ -4,5 +4,4 @@ export interface CircuitSectionTransactionStore {
updates: Array<{ id: string; equipmentIdentifier: string }>,
tempNamespace: string
): void;
updateSortOrders(sectionId: string, orderedCircuitIds: string[]): void;
}
@@ -2,7 +2,6 @@ import type { CircuitSectionTransactionStore } from "../ports/circuit-section-tr
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";
@@ -97,27 +96,4 @@ export class CircuitWriteService {
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);
}
}
@@ -23,6 +23,10 @@ import {
assertCircuitSectionReorderProjectCommand,
circuitSectionReorderCommandType,
} from "../models/circuit-section-reorder-project-command.model.js";
import {
assertCircuitSectionsReorderProjectCommand,
circuitSectionsReorderCommandType,
} from "../models/circuit-sections-reorder-project-command.model.js";
import {
assertCircuitSectionRenumberProjectCommand,
circuitSectionRenumberCommandType,
@@ -225,6 +229,13 @@ export class ProjectCommandService implements ProjectCommandExecutor {
command: input.command,
}).revision;
}
case circuitSectionsReorderCommandType: {
assertCircuitSectionsReorderProjectCommand(input.command);
return this.circuitSectionReorderStore.execute({
...input,
command: input.command,
}).revision;
}
case circuitSectionRenumberCommandType: {
assertCircuitSectionRenumberProjectCommand(input.command);
return this.circuitSectionRenumberStore.execute({