Add external object new circuit command

This commit is contained in:
2026-08-02 18:55:57 +02:00
parent 347b3717c3
commit a69b7b603f
13 changed files with 880 additions and 228 deletions
@@ -6,11 +6,13 @@ import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { createDatabaseContext, type AppDatabase } from "../src/db/database-context.js";
import { ExternalObjectRowAssignmentProjectCommandRepository } from "../src/db/repositories/external-object-row-assignment-project-command.repository.js";
import { ExternalObjectNewRowProjectCommandRepository } from "../src/db/repositories/external-object-new-row-project-command.repository.js";
import { ExternalObjectNewCircuitProjectCommandRepository } from "../src/db/repositories/external-object-new-circuit-project-command.repository.js";
import { toCircuitDeviceRowSnapshot } from "../src/db/repositories/circuit-device-row-structure.persistence.js";
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
import { circuitLists } from "../src/db/schema/circuit-lists.js";
import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
import { distributionBoards } from "../src/db/schema/distribution-boards.js";
import { externalImportBatches } from "../src/db/schema/external-import-batches.js";
import { externalModelObjects } from "../src/db/schema/external-model-objects.js";
@@ -24,6 +26,10 @@ import {
createExternalObjectNewRowProjectCommand,
externalObjectAssignToNewRowCommandType,
} from "../src/domain/models/external-object-new-row-project-command.model.js";
import {
createExternalObjectNewCircuitProjectCommand,
externalObjectAssignToNewCircuitCommandType,
} from "../src/domain/models/external-object-new-circuit-project-command.model.js";
import type { ExternalModelObjectSnapshot } from "../src/external-model/domain/external-model-contracts.js";
import { externalCsvTestConfiguration } from "./fixtures/revit-csv-fixtures.js";
@@ -376,3 +382,192 @@ describe("external object new-row project command", () => {
}
});
});
function newExternalCircuit(database: AppDatabase) {
const sourceRow = row(database, "row-2");
return {
id: "circuit-new",
circuitListId: "list-1",
sectionId: "section-1",
equipmentIdentifier: "-2F1.3",
displayName: "Steckdose",
sortOrder: 30,
cableType: null,
cableCrossSection: null,
cableLength: null,
rcdAssignment: null,
terminalDesignation: null,
voltage: 230,
controlRequirement: null,
status: null,
isReserve: false,
remark: null,
deviceRows: [{
...sourceRow,
id: "row-new",
circuitId: "circuit-new",
quantity: 2,
manualQuantity: 0,
}],
protectionDevice: {
circuitId: "circuit-new",
type: "FI_LS" as const,
ratedCurrentA: 16,
fuseUtilizationCategory: null,
tripCharacteristic: "B" as const,
rcdType: "A" as const,
ratedResidualCurrentMa: 30,
},
};
}
describe("external object new-circuit project command", () => {
it("creates circuit, protection, row and links as one undoable revision", () => {
const context = createFixture(null);
try {
const object = objectSnapshot(null);
const circuit = newExternalCircuit(context.db);
const command = createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit,
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
);
const repository = new ExternalObjectNewCircuitProjectCommandRepository(context.db);
const inserted = repository.execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command,
});
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get()!
.equipmentIdentifier,
"-2F1.3"
);
assert.equal(row(context.db, "row-new").manualQuantity, 0);
assert.equal(
context.db.select().from(circuitProtectionDevices)
.where(eq(circuitProtectionDevices.circuitId, "circuit-new")).get()!.type,
"FI_LS"
);
assert.equal(
context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId,
"row-new"
);
assert.throws(
() => repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command: inserted.inverse,
}),
/only be removed through project history/
);
const undone = repository.execute({
projectId: "project-1",
expectedRevision: 1,
source: "undo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: inserted.inverse,
});
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get(),
undefined
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
repository.execute({
projectId: "project-1",
expectedRevision: 2,
source: "redo",
historyTargetChangeSetId: inserted.revision.changeSetId,
command: undone.inverse,
});
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get()!
.equipmentIdentifier,
"-2F1.3"
);
} finally {
context.close();
}
});
it("rejects a preplanned BMK that does not belong to the target group", () => {
const context = createFixture(null);
try {
const object = objectSnapshot(null);
const circuit = {
...newExternalCircuit(context.db),
equipmentIdentifier: "-1F1.3",
};
assert.throws(
() => new ExternalObjectNewCircuitProjectCommandRepository(context.db).execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit,
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
),
}),
/equipment identifier does not match target group/
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
} finally {
context.close();
}
});
it("rolls back the complete circuit subtree when history persistence fails", () => {
const context = createFixture(null);
try {
context.sqlite.exec(`
CREATE TRIGGER fail_new_circuit_history
BEFORE INSERT ON project_change_sets
BEGIN SELECT RAISE(ABORT, 'late history failure'); END;
`);
const object = objectSnapshot(null);
assert.throws(
() => new ExternalObjectNewCircuitProjectCommandRepository(context.db).execute({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createExternalObjectNewCircuitProjectCommand(
externalObjectAssignToNewCircuitCommandType,
{
circuit: newExternalCircuit(context.db),
objects: [{
expected: object,
target: { ...object, circuitDeviceRowId: "row-new" },
}],
confirmedConflictObjectIds: [],
}
),
}),
/late history failure/
);
assert.equal(
context.db.select().from(circuits).where(eq(circuits.id, "circuit-new")).get(),
undefined
);
assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null);
} finally {
context.close();
}
});
});