Persist circuit group renumbering
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import {
|
||||
circuitGroupCategories,
|
||||
type CircuitGroupCategory,
|
||||
} from "../../shared/constants/circuit-group.js";
|
||||
import {
|
||||
formatCircuitGroupPrefix,
|
||||
parseGroupedEquipmentIdentifier,
|
||||
} from "../services/circuit-group-numbering.js";
|
||||
import type { CircuitGroupRenumberPlan } from "../services/circuit-group-renumbering.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
|
||||
export const circuitGroupRenumberCommandType =
|
||||
"circuit-group.renumber" as const;
|
||||
export const circuitGroupRenumberCommandSchemaVersion = 1 as const;
|
||||
|
||||
export interface CircuitGroupRenumberCommandPayload
|
||||
extends CircuitGroupRenumberPlan {
|
||||
circuitListId: string;
|
||||
}
|
||||
|
||||
export interface CircuitGroupRenumberProjectCommand
|
||||
extends SerializedProjectCommand<CircuitGroupRenumberCommandPayload> {
|
||||
schemaVersion: typeof circuitGroupRenumberCommandSchemaVersion;
|
||||
type: typeof circuitGroupRenumberCommandType;
|
||||
}
|
||||
|
||||
export function createCircuitGroupRenumberProjectCommand(
|
||||
circuitListId: string,
|
||||
plan: CircuitGroupRenumberPlan
|
||||
): CircuitGroupRenumberProjectCommand {
|
||||
const command: CircuitGroupRenumberProjectCommand = {
|
||||
schemaVersion: circuitGroupRenumberCommandSchemaVersion,
|
||||
type: circuitGroupRenumberCommandType,
|
||||
payload: { circuitListId, groups: plan.groups },
|
||||
};
|
||||
assertCircuitGroupRenumberProjectCommand(command);
|
||||
return command;
|
||||
}
|
||||
|
||||
export function assertCircuitGroupRenumberProjectCommand(
|
||||
command: SerializedProjectCommand<unknown>
|
||||
): asserts command is CircuitGroupRenumberProjectCommand {
|
||||
if (
|
||||
command.schemaVersion !== circuitGroupRenumberCommandSchemaVersion ||
|
||||
command.type !== circuitGroupRenumberCommandType ||
|
||||
!isPlainObject(command.payload) ||
|
||||
Object.keys(command.payload).length !== 2 ||
|
||||
typeof command.payload.circuitListId !== "string" ||
|
||||
!command.payload.circuitListId.trim() ||
|
||||
!Array.isArray(command.payload.groups) ||
|
||||
command.payload.groups.length === 0
|
||||
) {
|
||||
throw new Error("Unsupported circuit-group renumber command.");
|
||||
}
|
||||
const groupIds = new Set<string>();
|
||||
const entityIds = new Set<string>();
|
||||
const targetGroupKeys = new Set<string>();
|
||||
const targetIdentifiers = new Set<string>();
|
||||
let hasChange = false;
|
||||
for (const group of command.payload.groups) {
|
||||
if (
|
||||
!isPlainObject(group) ||
|
||||
Object.keys(group).length !== 8 ||
|
||||
typeof group.groupId !== "string" ||
|
||||
!group.groupId.trim() ||
|
||||
groupIds.has(group.groupId) ||
|
||||
!circuitGroupCategories.includes(
|
||||
group.category as CircuitGroupCategory
|
||||
) ||
|
||||
!positiveInteger(group.expectedGroupNumber) ||
|
||||
!positiveInteger(group.targetGroupNumber) ||
|
||||
group.expectedPrefix !==
|
||||
formatCircuitGroupPrefix(
|
||||
group.category as CircuitGroupCategory,
|
||||
group.expectedGroupNumber as number
|
||||
) ||
|
||||
group.targetPrefix !==
|
||||
formatCircuitGroupPrefix(
|
||||
group.category as CircuitGroupCategory,
|
||||
group.targetGroupNumber as number
|
||||
) ||
|
||||
!Array.isArray(group.circuits) ||
|
||||
!Array.isArray(group.components)
|
||||
) {
|
||||
throw new Error("Circuit-group renumber assignment is invalid.");
|
||||
}
|
||||
const targetGroupKey = `${group.category}:${group.targetGroupNumber}`;
|
||||
if (targetGroupKeys.has(targetGroupKey)) {
|
||||
throw new Error(
|
||||
"Circuit-group renumber contains duplicate target group numbers."
|
||||
);
|
||||
}
|
||||
hasChange ||= group.expectedGroupNumber !== group.targetGroupNumber;
|
||||
groupIds.add(group.groupId);
|
||||
targetGroupKeys.add(targetGroupKey);
|
||||
for (const circuit of group.circuits) {
|
||||
assertEntityAssignment(
|
||||
circuit,
|
||||
"circuitId",
|
||||
"circuit",
|
||||
group.category as CircuitGroupCategory,
|
||||
group.expectedGroupNumber as number,
|
||||
group.targetGroupNumber as number,
|
||||
entityIds,
|
||||
targetIdentifiers
|
||||
);
|
||||
}
|
||||
for (const component of group.components) {
|
||||
assertEntityAssignment(
|
||||
component,
|
||||
"componentId",
|
||||
"component",
|
||||
group.category as CircuitGroupCategory,
|
||||
group.expectedGroupNumber as number,
|
||||
group.targetGroupNumber as number,
|
||||
entityIds,
|
||||
targetIdentifiers
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!hasChange) {
|
||||
throw new Error(
|
||||
"Circuit-group renumber must change at least one group number."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertEntityAssignment(
|
||||
value: unknown,
|
||||
idField: "circuitId" | "componentId",
|
||||
kind: "circuit" | "component",
|
||||
category: CircuitGroupCategory,
|
||||
expectedGroupNumber: number,
|
||||
targetGroupNumber: number,
|
||||
entityIds: Set<string>,
|
||||
targetIdentifiers: Set<string>
|
||||
) {
|
||||
if (
|
||||
!isPlainObject(value) ||
|
||||
Object.keys(value).length !== 3 ||
|
||||
typeof value[idField] !== "string" ||
|
||||
!(value[idField] as string).trim() ||
|
||||
typeof value.expectedEquipmentIdentifier !== "string" ||
|
||||
typeof value.targetEquipmentIdentifier !== "string" ||
|
||||
entityIds.has(value[idField] as string) ||
|
||||
targetIdentifiers.has(value.targetEquipmentIdentifier)
|
||||
) {
|
||||
throw new Error(`Circuit-group renumber ${kind} assignment is invalid.`);
|
||||
}
|
||||
const expected = parseGroupedEquipmentIdentifier(
|
||||
value.expectedEquipmentIdentifier
|
||||
);
|
||||
const target = parseGroupedEquipmentIdentifier(
|
||||
value.targetEquipmentIdentifier
|
||||
);
|
||||
if (
|
||||
!expected ||
|
||||
!target ||
|
||||
expected.kind !== target.kind ||
|
||||
expected.category !== category ||
|
||||
target.category !== category ||
|
||||
expected.groupNumber !== expectedGroupNumber ||
|
||||
target.groupNumber !== targetGroupNumber ||
|
||||
(kind === "circuit" &&
|
||||
(expected.kind !== "circuit" ||
|
||||
expected.circuitNumber !== target.circuitNumber)) ||
|
||||
(kind === "component" && expected.kind === "circuit")
|
||||
) {
|
||||
throw new Error(
|
||||
`Circuit-group renumber ${kind} identifiers are incompatible.`
|
||||
);
|
||||
}
|
||||
entityIds.add(value[idField] as string);
|
||||
targetIdentifiers.add(value.targetEquipmentIdentifier);
|
||||
}
|
||||
|
||||
function positiveInteger(value: unknown): value is number {
|
||||
return Number.isInteger(value) && (value as number) > 0;
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
Reference in New Issue
Block a user