Add component update commands
This commit is contained in:
@@ -303,10 +303,12 @@ Stored schema-version 1 and 2 setup commands remain executable with their four
|
|||||||
legacy sections and without invented components.
|
legacy sections and without invented components.
|
||||||
Mutable group-protection and auxiliary distribution-board components use
|
Mutable group-protection and auxiliary distribution-board components use
|
||||||
`distribution-board-component.insert` and
|
`distribution-board-component.insert` and
|
||||||
`distribution-board-component.delete`. Their complete snapshots include the
|
`distribution-board-component.delete`; edits and footer reordering use
|
||||||
optional one-to-one protection-device state, and deletion rejects stale data.
|
`distribution-board-component.update`. Their complete expected/target
|
||||||
Fixed main-switch and surge-protection header roles are excluded from these
|
snapshots include the optional one-to-one protection-device state and reject
|
||||||
general component commands.
|
stale data. Updates preserve id, ownership, role and placement. Fixed
|
||||||
|
main-switch and surge-protection header roles are excluded from these general
|
||||||
|
component commands.
|
||||||
Distribution-board floor assignment and a project-enabled supply type use
|
Distribution-board floor assignment and a project-enabled supply type use
|
||||||
`distribution-board.update`; both values are snapshot/export fields and one
|
`distribution-board.update`; both values are snapshot/export fields and one
|
||||||
persistent undo step.
|
persistent undo step.
|
||||||
|
|||||||
@@ -286,15 +286,18 @@ Freigabe der Netzart in den Projekteinstellungen.
|
|||||||
Gespeicherte Anlage-Commands der Schemas 1 und 2 bleiben mit ihren vier
|
Gespeicherte Anlage-Commands der Schemas 1 und 2 bleiben mit ihren vier
|
||||||
Legacy-Abschnitten und ohne nachträglich erfundene Komponenten ausführbar.
|
Legacy-Abschnitten und ohne nachträglich erfundene Komponenten ausführbar.
|
||||||
`distribution-board-component.insert` und
|
`distribution-board-component.insert` und
|
||||||
`distribution-board-component.delete` versionieren die Anlage und Entfernung
|
`distribution-board-component.delete` sowie
|
||||||
veränderlicher Gruppen-Schutzgeräte und zusätzlicher Verteilergeräte. Der
|
`distribution-board-component.update` versionieren Anlage, Entfernung,
|
||||||
vollständige Snapshot enthält die stabile Komponenten-UUID und, für
|
Bearbeitung und Sortierung veränderlicher Gruppen-Schutzgeräte und zusätzlicher
|
||||||
Gruppen-Schutzgeräte, die getrennte 1:1-Schutzgerätekonfiguration. Eigentum an
|
Verteilergeräte. Die vollständigen Snapshots enthalten stabile
|
||||||
Projekt, Stromkreisliste und Gruppe sowie der unveränderte Löschzustand werden
|
Komponenten-UUIDs und, für Gruppen-Schutzgeräte, die getrennte
|
||||||
innerhalb derselben Transaktion geprüft. Die festen Kopfkomponenten
|
1:1-Schutzgerätekonfiguration. Updates tauschen einen exakt erwarteten gegen
|
||||||
Hauptschalter und Überspannungsableiter sind von diesen allgemeinen Commands
|
einen vollständigen Zielzustand aus; ID, Eigentum, Rolle und Platzierungszone
|
||||||
ausgeschlossen. API- und Editorintegration folgen in den nächsten
|
bleiben dabei unverändert. Projekt-, Listen- und Gruppenzugehörigkeit sowie
|
||||||
Arbeitspaketen.
|
veraltete Zustände werden innerhalb derselben Transaktion geprüft. Die festen
|
||||||
|
Kopfkomponenten Hauptschalter und Überspannungsableiter sind von diesen
|
||||||
|
allgemeinen Commands ausgeschlossen. API- und Editorintegration folgen in den
|
||||||
|
nächsten Arbeitspaketen.
|
||||||
`distribution-board.update` versioniert Etage, Netzart und den
|
`distribution-board.update` versioniert Etage, Netzart und den
|
||||||
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
||||||
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
||||||
@@ -365,8 +368,8 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.
|
|||||||
Ein triggergeführtes Register erzwingt bereits eine normalisierte,
|
Ein triggergeführtes Register erzwingt bereits eine normalisierte,
|
||||||
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
||||||
Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden
|
Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden
|
||||||
Snapshot-Schema 7. Persistente Insert/Delete-Commands für veränderliche
|
Snapshot-Schema 7. Persistente Insert/Delete/Update-Commands für
|
||||||
Verteilerkomponenten sind integriert; weitere Command- und UI-Schritte
|
veränderliche Verteilerkomponenten sind integriert; Gruppen- und UI-Schritte
|
||||||
folgen in abgegrenzten Arbeitspaketen.
|
folgen in abgegrenzten Arbeitspaketen.
|
||||||
|
|
||||||
PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und
|
PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ requirements and intended sequencing, not proof of implementation.
|
|||||||
components.
|
components.
|
||||||
- [x] Phase C2a1: persistent insert/delete commands for mutable group
|
- [x] Phase C2a1: persistent insert/delete commands for mutable group
|
||||||
protection and auxiliary components.
|
protection and auxiliary components.
|
||||||
- [ ] Phase C2a2: component update and reorder commands.
|
- [x] Phase C2a2: component update and reorder commands.
|
||||||
- [ ] Phase C2b: group CRUD and reorder commands.
|
- [ ] Phase C2b: group CRUD and reorder commands.
|
||||||
- [ ] Phase D: group numbering, moves and destructive operations.
|
- [ ] Phase D: group numbering, moves and destructive operations.
|
||||||
- [ ] Phase E: editor projection and editing.
|
- [ ] Phase E: editor projection and editing.
|
||||||
|
|||||||
@@ -593,9 +593,8 @@ Acceptance:
|
|||||||
|
|
||||||
### C. Persistent Commands and Board Defaults
|
### C. Persistent Commands and Board Defaults
|
||||||
|
|
||||||
Status: In progress. New-board defaults C1 and component insert/delete commands
|
Status: In progress. New-board defaults C1 and component management C2a are
|
||||||
C2a1 are complete. Component update/reorder and group management remain
|
complete. Group management C2b remains pending.
|
||||||
pending.
|
|
||||||
|
|
||||||
- extend `distribution-board.insert` with fixed components and three groups
|
- extend `distribution-board.insert` with fixed components and three groups
|
||||||
- implement component and group CRUD commands
|
- implement component and group CRUD commands
|
||||||
@@ -626,6 +625,16 @@ Implemented in C2a1:
|
|||||||
component UUID and a late revision/history failure rolls back the complete
|
component UUID and a late revision/history failure rolls back the complete
|
||||||
write
|
write
|
||||||
|
|
||||||
|
Implemented in C2a2:
|
||||||
|
|
||||||
|
- `distribution-board-component.update` exchanges one complete expected
|
||||||
|
component/protection snapshot for one complete target snapshot
|
||||||
|
- name, BMK, protection configuration and `sortOrder` can change atomically
|
||||||
|
- component id, list/group ownership, role and placement zone remain immutable
|
||||||
|
through this general update command
|
||||||
|
- stale expected state and cross-entity BMK collisions are rejected; Undo/Redo
|
||||||
|
restores the exact previous snapshot
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
- new boards contain the agreed fixed structure
|
- new boards contain the agreed fixed structure
|
||||||
|
|||||||
+70
@@ -2,10 +2,13 @@ import { and, eq } from "drizzle-orm";
|
|||||||
import {
|
import {
|
||||||
assertDistributionBoardComponentDeleteProjectCommand,
|
assertDistributionBoardComponentDeleteProjectCommand,
|
||||||
assertDistributionBoardComponentInsertProjectCommand,
|
assertDistributionBoardComponentInsertProjectCommand,
|
||||||
|
assertDistributionBoardComponentUpdateProjectCommand,
|
||||||
createDistributionBoardComponentDeleteProjectCommand,
|
createDistributionBoardComponentDeleteProjectCommand,
|
||||||
createDistributionBoardComponentInsertProjectCommand,
|
createDistributionBoardComponentInsertProjectCommand,
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand,
|
||||||
distributionBoardComponentDeleteCommandType,
|
distributionBoardComponentDeleteCommandType,
|
||||||
distributionBoardComponentInsertCommandType,
|
distributionBoardComponentInsertCommandType,
|
||||||
|
distributionBoardComponentUpdateCommandType,
|
||||||
type DistributionBoardComponentSnapshot,
|
type DistributionBoardComponentSnapshot,
|
||||||
type DistributionBoardComponentStructureProjectCommand,
|
type DistributionBoardComponentStructureProjectCommand,
|
||||||
} from "../../domain/models/distribution-board-component-structure-project-command.model.js";
|
} from "../../domain/models/distribution-board-component-structure-project-command.model.js";
|
||||||
@@ -59,6 +62,19 @@ export class DistributionBoardComponentStructureProjectCommandRepository
|
|||||||
command.payload.snapshot
|
command.payload.snapshot
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (command.type === distributionBoardComponentUpdateCommandType) {
|
||||||
|
assertDistributionBoardComponentUpdateProjectCommand(command);
|
||||||
|
this.update(
|
||||||
|
database,
|
||||||
|
projectId,
|
||||||
|
command.payload.expected,
|
||||||
|
command.payload.target
|
||||||
|
);
|
||||||
|
return createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
command.payload.target,
|
||||||
|
command.payload.expected
|
||||||
|
);
|
||||||
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Unsupported distribution-board component structure command."
|
"Unsupported distribution-board component structure command."
|
||||||
);
|
);
|
||||||
@@ -139,6 +155,60 @@ export class DistributionBoardComponentStructureProjectCommandRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private update(
|
||||||
|
database: AppDatabase,
|
||||||
|
projectId: string,
|
||||||
|
expected: DistributionBoardComponentSnapshot,
|
||||||
|
target: DistributionBoardComponentSnapshot
|
||||||
|
) {
|
||||||
|
this.assertOwnership(database, projectId, expected);
|
||||||
|
this.assertOwnership(database, projectId, target);
|
||||||
|
const component = database
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
||||||
|
.get();
|
||||||
|
const protection = database
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponentProtectionDevices)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponentProtectionDevices.componentId,
|
||||||
|
expected.component.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
if (
|
||||||
|
!component ||
|
||||||
|
!sameRecord(expected.component, component) ||
|
||||||
|
!sameNullableRecord(expected.protectionDevice, protection)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Distribution-board component changed before update."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
database
|
||||||
|
.update(distributionBoardComponents)
|
||||||
|
.set(target.component)
|
||||||
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
||||||
|
.run();
|
||||||
|
if (
|
||||||
|
expected.protectionDevice !== null &&
|
||||||
|
target.protectionDevice !== null
|
||||||
|
) {
|
||||||
|
database
|
||||||
|
.update(distributionBoardComponentProtectionDevices)
|
||||||
|
.set(target.protectionDevice)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponentProtectionDevices.componentId,
|
||||||
|
expected.component.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private assertOwnership(
|
private assertOwnership(
|
||||||
database: AppDatabase,
|
database: AppDatabase,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export const distributionBoardComponentInsertCommandType =
|
|||||||
"distribution-board-component.insert" as const;
|
"distribution-board-component.insert" as const;
|
||||||
export const distributionBoardComponentDeleteCommandType =
|
export const distributionBoardComponentDeleteCommandType =
|
||||||
"distribution-board-component.delete" as const;
|
"distribution-board-component.delete" as const;
|
||||||
|
export const distributionBoardComponentUpdateCommandType =
|
||||||
|
"distribution-board-component.update" as const;
|
||||||
export const distributionBoardComponentStructureCommandSchemaVersion =
|
export const distributionBoardComponentStructureCommandSchemaVersion =
|
||||||
1 as const;
|
1 as const;
|
||||||
|
|
||||||
@@ -47,6 +49,11 @@ interface DistributionBoardComponentStructurePayload {
|
|||||||
snapshot: DistributionBoardComponentSnapshot;
|
snapshot: DistributionBoardComponentSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DistributionBoardComponentUpdatePayload {
|
||||||
|
expected: DistributionBoardComponentSnapshot;
|
||||||
|
target: DistributionBoardComponentSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DistributionBoardComponentInsertProjectCommand
|
export interface DistributionBoardComponentInsertProjectCommand
|
||||||
extends SerializedProjectCommand<DistributionBoardComponentStructurePayload> {
|
extends SerializedProjectCommand<DistributionBoardComponentStructurePayload> {
|
||||||
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
|
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
|
||||||
@@ -59,9 +66,16 @@ export interface DistributionBoardComponentDeleteProjectCommand
|
|||||||
type: typeof distributionBoardComponentDeleteCommandType;
|
type: typeof distributionBoardComponentDeleteCommandType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DistributionBoardComponentUpdateProjectCommand
|
||||||
|
extends SerializedProjectCommand<DistributionBoardComponentUpdatePayload> {
|
||||||
|
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
|
||||||
|
type: typeof distributionBoardComponentUpdateCommandType;
|
||||||
|
}
|
||||||
|
|
||||||
export type DistributionBoardComponentStructureProjectCommand =
|
export type DistributionBoardComponentStructureProjectCommand =
|
||||||
| DistributionBoardComponentInsertProjectCommand
|
| DistributionBoardComponentInsertProjectCommand
|
||||||
| DistributionBoardComponentDeleteProjectCommand;
|
| DistributionBoardComponentDeleteProjectCommand
|
||||||
|
| DistributionBoardComponentUpdateProjectCommand;
|
||||||
|
|
||||||
export function createDistributionBoardComponentInsertProjectCommand(
|
export function createDistributionBoardComponentInsertProjectCommand(
|
||||||
snapshot: DistributionBoardComponentSnapshot
|
snapshot: DistributionBoardComponentSnapshot
|
||||||
@@ -89,6 +103,20 @@ export function createDistributionBoardComponentDeleteProjectCommand(
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
expected: DistributionBoardComponentSnapshot,
|
||||||
|
target: DistributionBoardComponentSnapshot
|
||||||
|
): DistributionBoardComponentUpdateProjectCommand {
|
||||||
|
const command: DistributionBoardComponentUpdateProjectCommand = {
|
||||||
|
schemaVersion:
|
||||||
|
distributionBoardComponentStructureCommandSchemaVersion,
|
||||||
|
type: distributionBoardComponentUpdateCommandType,
|
||||||
|
payload: { expected, target },
|
||||||
|
};
|
||||||
|
assertDistributionBoardComponentUpdateProjectCommand(command);
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
export function assertDistributionBoardComponentInsertProjectCommand(
|
export function assertDistributionBoardComponentInsertProjectCommand(
|
||||||
command: SerializedProjectCommand<unknown>
|
command: SerializedProjectCommand<unknown>
|
||||||
): asserts command is DistributionBoardComponentInsertProjectCommand {
|
): asserts command is DistributionBoardComponentInsertProjectCommand {
|
||||||
@@ -107,6 +135,28 @@ export function assertDistributionBoardComponentDeleteProjectCommand(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function assertDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
command: SerializedProjectCommand<unknown>
|
||||||
|
): asserts command is DistributionBoardComponentUpdateProjectCommand {
|
||||||
|
if (
|
||||||
|
command.schemaVersion !==
|
||||||
|
distributionBoardComponentStructureCommandSchemaVersion ||
|
||||||
|
command.type !== distributionBoardComponentUpdateCommandType ||
|
||||||
|
!isPlainObject(command.payload) ||
|
||||||
|
Object.keys(command.payload).length !== 2
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Unsupported distribution-board component update command."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assertDistributionBoardComponentSnapshot(command.payload.expected);
|
||||||
|
assertDistributionBoardComponentSnapshot(command.payload.target);
|
||||||
|
assertSameComponentIdentity(
|
||||||
|
command.payload.expected,
|
||||||
|
command.payload.target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function assertStructureCommand(
|
function assertStructureCommand(
|
||||||
command: SerializedProjectCommand<unknown>,
|
command: SerializedProjectCommand<unknown>,
|
||||||
type:
|
type:
|
||||||
@@ -127,6 +177,25 @@ function assertStructureCommand(
|
|||||||
assertDistributionBoardComponentSnapshot(command.payload.snapshot);
|
assertDistributionBoardComponentSnapshot(command.payload.snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertSameComponentIdentity(
|
||||||
|
expected: DistributionBoardComponentSnapshot,
|
||||||
|
target: DistributionBoardComponentSnapshot
|
||||||
|
) {
|
||||||
|
for (const field of [
|
||||||
|
"id",
|
||||||
|
"circuitListId",
|
||||||
|
"sectionId",
|
||||||
|
"role",
|
||||||
|
"placement",
|
||||||
|
] as const) {
|
||||||
|
if (expected.component[field] !== target.component[field]) {
|
||||||
|
throw new Error(
|
||||||
|
"Distribution-board component updates cannot change ownership or role."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function assertDistributionBoardComponentSnapshot(
|
export function assertDistributionBoardComponentSnapshot(
|
||||||
value: unknown
|
value: unknown
|
||||||
): asserts value is DistributionBoardComponentSnapshot {
|
): asserts value is DistributionBoardComponentSnapshot {
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
assertDistributionBoardComponentDeleteProjectCommand,
|
assertDistributionBoardComponentDeleteProjectCommand,
|
||||||
assertDistributionBoardComponentInsertProjectCommand,
|
assertDistributionBoardComponentInsertProjectCommand,
|
||||||
|
assertDistributionBoardComponentUpdateProjectCommand,
|
||||||
distributionBoardComponentDeleteCommandType,
|
distributionBoardComponentDeleteCommandType,
|
||||||
distributionBoardComponentInsertCommandType,
|
distributionBoardComponentInsertCommandType,
|
||||||
|
distributionBoardComponentUpdateCommandType,
|
||||||
} from "../models/distribution-board-component-structure-project-command.model.js";
|
} from "../models/distribution-board-component-structure-project-command.model.js";
|
||||||
import {
|
import {
|
||||||
assertDistributionBoardDeleteProjectCommand,
|
assertDistributionBoardDeleteProjectCommand,
|
||||||
@@ -322,6 +324,15 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
|||||||
command: input.command,
|
command: input.command,
|
||||||
}).revision;
|
}).revision;
|
||||||
}
|
}
|
||||||
|
case distributionBoardComponentUpdateCommandType: {
|
||||||
|
assertDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
input.command
|
||||||
|
);
|
||||||
|
return this.distributionBoardComponentStructureStore.execute({
|
||||||
|
...input,
|
||||||
|
command: input.command,
|
||||||
|
}).revision;
|
||||||
|
}
|
||||||
case projectFloorInsertCommandType: {
|
case projectFloorInsertCommandType: {
|
||||||
assertProjectFloorInsertProjectCommand(input.command);
|
assertProjectFloorInsertProjectCommand(input.command);
|
||||||
return this.projectLocationStructureStore.execute({
|
return this.projectLocationStructureStore.execute({
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const commandTypeLabels: Record<string, string> = {
|
|||||||
"distribution-board.delete": "Verteilung entfernt",
|
"distribution-board.delete": "Verteilung entfernt",
|
||||||
"distribution-board-component.insert": "Verteilergerät angelegt",
|
"distribution-board-component.insert": "Verteilergerät angelegt",
|
||||||
"distribution-board-component.delete": "Verteilergerät entfernt",
|
"distribution-board-component.delete": "Verteilergerät entfernt",
|
||||||
|
"distribution-board-component.update": "Verteilergerät bearbeitet",
|
||||||
"project-floor.insert": "Geschoss angelegt",
|
"project-floor.insert": "Geschoss angelegt",
|
||||||
"project-floor.delete": "Geschoss entfernt",
|
"project-floor.delete": "Geschoss entfernt",
|
||||||
"project-room.insert": "Raum angelegt",
|
"project-room.insert": "Raum angelegt",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { projectRevisions } from "../src/db/schema/project-revisions.js";
|
|||||||
import { projects } from "../src/db/schema/projects.js";
|
import { projects } from "../src/db/schema/projects.js";
|
||||||
import {
|
import {
|
||||||
createDistributionBoardComponentInsertProjectCommand,
|
createDistributionBoardComponentInsertProjectCommand,
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand,
|
||||||
type DistributionBoardComponentSnapshot,
|
type DistributionBoardComponentSnapshot,
|
||||||
} from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
|
} from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
|
||||||
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
|
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
|
||||||
@@ -198,6 +199,272 @@ describe("distribution-board component structure project command", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updates an auxiliary component and restores its name, BMK and order", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const { list } = getListAndSection(context);
|
||||||
|
const expected: DistributionBoardComponentSnapshot = {
|
||||||
|
component: {
|
||||||
|
id: "component-update-aux",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: null,
|
||||||
|
equipmentIdentifier: "-K20",
|
||||||
|
name: "KNX Aktor",
|
||||||
|
role: "auxiliary",
|
||||||
|
placement: "footer",
|
||||||
|
sortOrder: 10,
|
||||||
|
},
|
||||||
|
protectionDevice: null,
|
||||||
|
};
|
||||||
|
const target: DistributionBoardComponentSnapshot = {
|
||||||
|
component: {
|
||||||
|
...expected.component,
|
||||||
|
equipmentIdentifier: "-K21",
|
||||||
|
name: "KNX Jalousieaktor",
|
||||||
|
sortOrder: 30,
|
||||||
|
},
|
||||||
|
protectionDevice: null,
|
||||||
|
};
|
||||||
|
const repository =
|
||||||
|
new DistributionBoardComponentStructureProjectCommandRepository(
|
||||||
|
context.db
|
||||||
|
);
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentInsertProjectCommand(
|
||||||
|
expected
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const updated = repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 1,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
expected,
|
||||||
|
target
|
||||||
|
),
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
||||||
|
.get(),
|
||||||
|
target.component
|
||||||
|
);
|
||||||
|
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 2,
|
||||||
|
source: "undo",
|
||||||
|
historyTargetChangeSetId: new ProjectHistoryRepository(
|
||||||
|
context.db
|
||||||
|
).getNextCommand("project-1", "undo")?.changeSetId,
|
||||||
|
command: updated.inverse,
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
||||||
|
.get(),
|
||||||
|
expected.component
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates group protection data atomically and rejects stale state", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const { list, section } = getListAndSection(context);
|
||||||
|
const expected: DistributionBoardComponentSnapshot = {
|
||||||
|
component: {
|
||||||
|
id: "component-update-protection",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: section.id,
|
||||||
|
equipmentIdentifier: "-1Q1.0",
|
||||||
|
name: "Gruppen-FI",
|
||||||
|
role: "group_residual_current_protection",
|
||||||
|
placement: "group",
|
||||||
|
sortOrder: 10,
|
||||||
|
},
|
||||||
|
protectionDevice: {
|
||||||
|
componentId: "component-update-protection",
|
||||||
|
type: "FI",
|
||||||
|
ratedCurrentA: 40,
|
||||||
|
fuseUtilizationCategory: null,
|
||||||
|
tripCharacteristic: null,
|
||||||
|
rcdType: "A",
|
||||||
|
ratedResidualCurrentMa: 30,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const target: DistributionBoardComponentSnapshot = {
|
||||||
|
component: {
|
||||||
|
...expected.component,
|
||||||
|
name: "Gruppen-FI 63 A",
|
||||||
|
},
|
||||||
|
protectionDevice: {
|
||||||
|
...expected.protectionDevice!,
|
||||||
|
ratedCurrentA: 63,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const repository =
|
||||||
|
new DistributionBoardComponentStructureProjectCommandRepository(
|
||||||
|
context.db
|
||||||
|
);
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentInsertProjectCommand(
|
||||||
|
expected
|
||||||
|
),
|
||||||
|
});
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 1,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
expected,
|
||||||
|
target
|
||||||
|
),
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponentProtectionDevices)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponentProtectionDevices.componentId,
|
||||||
|
expected.component.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get(),
|
||||||
|
target.protectionDevice
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 2,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
expected,
|
||||||
|
target
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
/changed before update/
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(projectRevisions).all().length,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
target,
|
||||||
|
{
|
||||||
|
...target,
|
||||||
|
component: {
|
||||||
|
...target.component,
|
||||||
|
sectionId: "another-section",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
/cannot change ownership or role/
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rolls back a component update when history persistence fails", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const { list } = getListAndSection(context);
|
||||||
|
const expected: DistributionBoardComponentSnapshot = {
|
||||||
|
component: {
|
||||||
|
id: "component-update-rollback",
|
||||||
|
circuitListId: list.id,
|
||||||
|
sectionId: null,
|
||||||
|
equipmentIdentifier: "-K30",
|
||||||
|
name: "Aktor",
|
||||||
|
role: "auxiliary",
|
||||||
|
placement: "footer",
|
||||||
|
sortOrder: 10,
|
||||||
|
},
|
||||||
|
protectionDevice: null,
|
||||||
|
};
|
||||||
|
const target: DistributionBoardComponentSnapshot = {
|
||||||
|
component: {
|
||||||
|
...expected.component,
|
||||||
|
name: "Geänderter Aktor",
|
||||||
|
sortOrder: 20,
|
||||||
|
},
|
||||||
|
protectionDevice: null,
|
||||||
|
};
|
||||||
|
const repository =
|
||||||
|
new DistributionBoardComponentStructureProjectCommandRepository(
|
||||||
|
context.db
|
||||||
|
);
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentInsertProjectCommand(
|
||||||
|
expected
|
||||||
|
),
|
||||||
|
});
|
||||||
|
context.sqlite.exec(`
|
||||||
|
CREATE TRIGGER fail_component_update_history
|
||||||
|
BEFORE INSERT ON project_history_stack_entries
|
||||||
|
BEGIN
|
||||||
|
SELECT RAISE(ABORT, 'forced component update history failure');
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
repository.execute({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 1,
|
||||||
|
source: "user",
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
expected,
|
||||||
|
target
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
/forced component update history failure/
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(eq(distributionBoardComponents.id, expected.component.id))
|
||||||
|
.get(),
|
||||||
|
expected.component
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
context.db.select().from(projectRevisions).all().length,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects fixed roles, foreign sections, duplicate BMKs and stale revisions", () => {
|
it("rejects fixed roles, foreign sections, duplicate BMKs and stale revisions", () => {
|
||||||
const context = createTestDatabase();
|
const context = createTestDatabase();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ import {
|
|||||||
createDistributionBoardInsertProjectCommand,
|
createDistributionBoardInsertProjectCommand,
|
||||||
createDistributionBoardStructureSnapshot,
|
createDistributionBoardStructureSnapshot,
|
||||||
} from "../src/domain/models/distribution-board-structure-project-command.model.js";
|
} from "../src/domain/models/distribution-board-structure-project-command.model.js";
|
||||||
import { createDistributionBoardComponentInsertProjectCommand } from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
|
import {
|
||||||
|
createDistributionBoardComponentInsertProjectCommand,
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand,
|
||||||
|
} from "../src/domain/models/distribution-board-component-structure-project-command.model.js";
|
||||||
import {
|
import {
|
||||||
createProjectFloorInsertProjectCommand,
|
createProjectFloorInsertProjectCommand,
|
||||||
createProjectFloorSnapshot,
|
createProjectFloorSnapshot,
|
||||||
@@ -1121,6 +1124,89 @@ describe("project command service", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("dispatches distribution-board component updates", () => {
|
||||||
|
const context = createTestDatabase();
|
||||||
|
try {
|
||||||
|
const service = createService(context);
|
||||||
|
const circuitListId = context.db
|
||||||
|
.select()
|
||||||
|
.from(circuitSections)
|
||||||
|
.get()!.circuitListId;
|
||||||
|
const expected = {
|
||||||
|
component: {
|
||||||
|
id: "component-service-update",
|
||||||
|
circuitListId,
|
||||||
|
sectionId: null,
|
||||||
|
equipmentIdentifier: "-K11",
|
||||||
|
name: "KNX Aktor",
|
||||||
|
role: "auxiliary" as const,
|
||||||
|
placement: "footer" as const,
|
||||||
|
sortOrder: 10,
|
||||||
|
},
|
||||||
|
protectionDevice: null,
|
||||||
|
};
|
||||||
|
const target = {
|
||||||
|
component: {
|
||||||
|
...expected.component,
|
||||||
|
name: "KNX Jalousieaktor",
|
||||||
|
sortOrder: 20,
|
||||||
|
},
|
||||||
|
protectionDevice: null,
|
||||||
|
};
|
||||||
|
service.executeUser({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 0,
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentInsertProjectCommand(
|
||||||
|
expected
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const updated = service.executeUser({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 1,
|
||||||
|
command:
|
||||||
|
createDistributionBoardComponentUpdateProjectCommand(
|
||||||
|
expected,
|
||||||
|
target
|
||||||
|
),
|
||||||
|
});
|
||||||
|
assert.equal(updated.history.currentRevision, 2);
|
||||||
|
assert.equal(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponents.id,
|
||||||
|
expected.component.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get()?.name,
|
||||||
|
target.component.name
|
||||||
|
);
|
||||||
|
|
||||||
|
createService(context).undo({
|
||||||
|
projectId: "project-1",
|
||||||
|
expectedRevision: 2,
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
context.db
|
||||||
|
.select()
|
||||||
|
.from(distributionBoardComponents)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
distributionBoardComponents.id,
|
||||||
|
expected.component.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.get()?.name,
|
||||||
|
expected.component.name
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("dispatches floor and room setup with their persisted inverses", () => {
|
it("dispatches floor and room setup with their persisted inverses", () => {
|
||||||
const context = createTestDatabase();
|
const context = createTestDatabase();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -131,6 +131,14 @@ describe("project version history presentation", () => {
|
|||||||
),
|
),
|
||||||
"Verteilergerät entfernt"
|
"Verteilergerät entfernt"
|
||||||
);
|
);
|
||||||
|
assert.equal(
|
||||||
|
getProjectRevisionDescription(
|
||||||
|
revision(7, {
|
||||||
|
commandType: "distribution-board-component.update",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
"Verteilergerät bearbeitet"
|
||||||
|
);
|
||||||
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getProjectSnapshotKindLabel("automatic"),
|
getProjectSnapshotKindLabel("automatic"),
|
||||||
|
|||||||
Reference in New Issue
Block a user