184 lines
6.1 KiB
TypeScript
184 lines
6.1 KiB
TypeScript
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);
|
|
}
|