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.
|
||||
Mutable group-protection and auxiliary distribution-board components use
|
||||
`distribution-board-component.insert` and
|
||||
`distribution-board-component.delete`. Their complete snapshots include the
|
||||
optional one-to-one protection-device state, and deletion rejects stale data.
|
||||
Fixed main-switch and surge-protection header roles are excluded from these
|
||||
general component commands.
|
||||
`distribution-board-component.delete`; edits and footer reordering use
|
||||
`distribution-board-component.update`. Their complete expected/target
|
||||
snapshots include the optional one-to-one protection-device state and reject
|
||||
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.update`; both values are snapshot/export fields and one
|
||||
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
|
||||
Legacy-Abschnitten und ohne nachträglich erfundene Komponenten ausführbar.
|
||||
`distribution-board-component.insert` und
|
||||
`distribution-board-component.delete` versionieren die Anlage und Entfernung
|
||||
veränderlicher Gruppen-Schutzgeräte und zusätzlicher Verteilergeräte. Der
|
||||
vollständige Snapshot enthält die stabile Komponenten-UUID und, für
|
||||
Gruppen-Schutzgeräte, die getrennte 1:1-Schutzgerätekonfiguration. Eigentum an
|
||||
Projekt, Stromkreisliste und Gruppe sowie der unveränderte Löschzustand 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-component.delete` sowie
|
||||
`distribution-board-component.update` versionieren Anlage, Entfernung,
|
||||
Bearbeitung und Sortierung veränderlicher Gruppen-Schutzgeräte und zusätzlicher
|
||||
Verteilergeräte. Die vollständigen Snapshots enthalten stabile
|
||||
Komponenten-UUIDs und, für Gruppen-Schutzgeräte, die getrennte
|
||||
1:1-Schutzgerätekonfiguration. Updates tauschen einen exakt erwarteten gegen
|
||||
einen vollständigen Zielzustand aus; ID, Eigentum, Rolle und Platzierungszone
|
||||
bleiben dabei unverändert. Projekt-, Listen- und Gruppenzugehörigkeit sowie
|
||||
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
|
||||
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
||||
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,
|
||||
stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und
|
||||
Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden
|
||||
Snapshot-Schema 7. Persistente Insert/Delete-Commands für veränderliche
|
||||
Verteilerkomponenten sind integriert; weitere Command- und UI-Schritte
|
||||
Snapshot-Schema 7. Persistente Insert/Delete/Update-Commands für
|
||||
veränderliche Verteilerkomponenten sind integriert; Gruppen- und UI-Schritte
|
||||
folgen in abgegrenzten Arbeitspaketen.
|
||||
|
||||
PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und
|
||||
|
||||
@@ -22,7 +22,7 @@ requirements and intended sequencing, not proof of implementation.
|
||||
components.
|
||||
- [x] Phase C2a1: persistent insert/delete commands for mutable group
|
||||
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 D: group numbering, moves and destructive operations.
|
||||
- [ ] Phase E: editor projection and editing.
|
||||
|
||||
@@ -593,9 +593,8 @@ Acceptance:
|
||||
|
||||
### C. Persistent Commands and Board Defaults
|
||||
|
||||
Status: In progress. New-board defaults C1 and component insert/delete commands
|
||||
C2a1 are complete. Component update/reorder and group management remain
|
||||
pending.
|
||||
Status: In progress. New-board defaults C1 and component management C2a are
|
||||
complete. Group management C2b remains pending.
|
||||
|
||||
- extend `distribution-board.insert` with fixed components and three groups
|
||||
- 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
|
||||
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:
|
||||
|
||||
- new boards contain the agreed fixed structure
|
||||
|
||||
+70
@@ -2,10 +2,13 @@ import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
assertDistributionBoardComponentDeleteProjectCommand,
|
||||
assertDistributionBoardComponentInsertProjectCommand,
|
||||
assertDistributionBoardComponentUpdateProjectCommand,
|
||||
createDistributionBoardComponentDeleteProjectCommand,
|
||||
createDistributionBoardComponentInsertProjectCommand,
|
||||
createDistributionBoardComponentUpdateProjectCommand,
|
||||
distributionBoardComponentDeleteCommandType,
|
||||
distributionBoardComponentInsertCommandType,
|
||||
distributionBoardComponentUpdateCommandType,
|
||||
type DistributionBoardComponentSnapshot,
|
||||
type DistributionBoardComponentStructureProjectCommand,
|
||||
} from "../../domain/models/distribution-board-component-structure-project-command.model.js";
|
||||
@@ -59,6 +62,19 @@ export class DistributionBoardComponentStructureProjectCommandRepository
|
||||
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(
|
||||
"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(
|
||||
database: AppDatabase,
|
||||
projectId: string,
|
||||
|
||||
@@ -13,6 +13,8 @@ export const distributionBoardComponentInsertCommandType =
|
||||
"distribution-board-component.insert" as const;
|
||||
export const distributionBoardComponentDeleteCommandType =
|
||||
"distribution-board-component.delete" as const;
|
||||
export const distributionBoardComponentUpdateCommandType =
|
||||
"distribution-board-component.update" as const;
|
||||
export const distributionBoardComponentStructureCommandSchemaVersion =
|
||||
1 as const;
|
||||
|
||||
@@ -47,6 +49,11 @@ interface DistributionBoardComponentStructurePayload {
|
||||
snapshot: DistributionBoardComponentSnapshot;
|
||||
}
|
||||
|
||||
interface DistributionBoardComponentUpdatePayload {
|
||||
expected: DistributionBoardComponentSnapshot;
|
||||
target: DistributionBoardComponentSnapshot;
|
||||
}
|
||||
|
||||
export interface DistributionBoardComponentInsertProjectCommand
|
||||
extends SerializedProjectCommand<DistributionBoardComponentStructurePayload> {
|
||||
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
|
||||
@@ -59,9 +66,16 @@ export interface DistributionBoardComponentDeleteProjectCommand
|
||||
type: typeof distributionBoardComponentDeleteCommandType;
|
||||
}
|
||||
|
||||
export interface DistributionBoardComponentUpdateProjectCommand
|
||||
extends SerializedProjectCommand<DistributionBoardComponentUpdatePayload> {
|
||||
schemaVersion: typeof distributionBoardComponentStructureCommandSchemaVersion;
|
||||
type: typeof distributionBoardComponentUpdateCommandType;
|
||||
}
|
||||
|
||||
export type DistributionBoardComponentStructureProjectCommand =
|
||||
| DistributionBoardComponentInsertProjectCommand
|
||||
| DistributionBoardComponentDeleteProjectCommand;
|
||||
| DistributionBoardComponentDeleteProjectCommand
|
||||
| DistributionBoardComponentUpdateProjectCommand;
|
||||
|
||||
export function createDistributionBoardComponentInsertProjectCommand(
|
||||
snapshot: DistributionBoardComponentSnapshot
|
||||
@@ -89,6 +103,20 @@ export function createDistributionBoardComponentDeleteProjectCommand(
|
||||
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(
|
||||
command: SerializedProjectCommand<unknown>
|
||||
): 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(
|
||||
command: SerializedProjectCommand<unknown>,
|
||||
type:
|
||||
@@ -127,6 +177,25 @@ function assertStructureCommand(
|
||||
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(
|
||||
value: unknown
|
||||
): asserts value is DistributionBoardComponentSnapshot {
|
||||
|
||||
@@ -44,8 +44,10 @@ import {
|
||||
import {
|
||||
assertDistributionBoardComponentDeleteProjectCommand,
|
||||
assertDistributionBoardComponentInsertProjectCommand,
|
||||
assertDistributionBoardComponentUpdateProjectCommand,
|
||||
distributionBoardComponentDeleteCommandType,
|
||||
distributionBoardComponentInsertCommandType,
|
||||
distributionBoardComponentUpdateCommandType,
|
||||
} from "../models/distribution-board-component-structure-project-command.model.js";
|
||||
import {
|
||||
assertDistributionBoardDeleteProjectCommand,
|
||||
@@ -322,6 +324,15 @@ export class ProjectCommandService implements ProjectCommandExecutor {
|
||||
command: input.command,
|
||||
}).revision;
|
||||
}
|
||||
case distributionBoardComponentUpdateCommandType: {
|
||||
assertDistributionBoardComponentUpdateProjectCommand(
|
||||
input.command
|
||||
);
|
||||
return this.distributionBoardComponentStructureStore.execute({
|
||||
...input,
|
||||
command: input.command,
|
||||
}).revision;
|
||||
}
|
||||
case projectFloorInsertCommandType: {
|
||||
assertProjectFloorInsertProjectCommand(input.command);
|
||||
return this.projectLocationStructureStore.execute({
|
||||
|
||||
@@ -35,6 +35,7 @@ const commandTypeLabels: Record<string, string> = {
|
||||
"distribution-board.delete": "Verteilung entfernt",
|
||||
"distribution-board-component.insert": "Verteilergerät angelegt",
|
||||
"distribution-board-component.delete": "Verteilergerät entfernt",
|
||||
"distribution-board-component.update": "Verteilergerät bearbeitet",
|
||||
"project-floor.insert": "Geschoss angelegt",
|
||||
"project-floor.delete": "Geschoss entfernt",
|
||||
"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 {
|
||||
createDistributionBoardComponentInsertProjectCommand,
|
||||
createDistributionBoardComponentUpdateProjectCommand,
|
||||
type DistributionBoardComponentSnapshot,
|
||||
} from "../src/domain/models/distribution-board-component-structure-project-command.model.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", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
|
||||
@@ -51,7 +51,10 @@ import {
|
||||
createDistributionBoardInsertProjectCommand,
|
||||
createDistributionBoardStructureSnapshot,
|
||||
} 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 {
|
||||
createProjectFloorInsertProjectCommand,
|
||||
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", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
|
||||
@@ -131,6 +131,14 @@ describe("project version history presentation", () => {
|
||||
),
|
||||
"Verteilergerät entfernt"
|
||||
);
|
||||
assert.equal(
|
||||
getProjectRevisionDescription(
|
||||
revision(7, {
|
||||
commandType: "distribution-board-component.update",
|
||||
})
|
||||
),
|
||||
"Verteilergerät bearbeitet"
|
||||
);
|
||||
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
||||
assert.equal(
|
||||
getProjectSnapshotKindLabel("automatic"),
|
||||
|
||||
Reference in New Issue
Block a user