Add per-project socket limit and Revit socket device-type flag

- Project setting maxSocketsPerCircuit (migration 0018,
  project.update-settings schema 4, snapshot schema 11); older command
  and snapshot versions keep or upgrade the value to null.
- Revit configuration schema 2 marks device types as socket; version 1
  stays readable and derives the flag from socketCount family rules.
- Settings dialog field and device-type card on the Revit page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Julian Appel 2026-10-30 21:18:08 +01:00
parent 85cbdbf8e0
commit e48042bde3
35 changed files with 3781 additions and 41 deletions

View file

@ -56,6 +56,7 @@ function getSettings(context: DatabaseContext) {
workStage: projects.workStage,
projectLead: projects.projectLead,
projectEditor: projects.projectEditor,
maxSocketsPerCircuit: projects.maxSocketsPerCircuit,
currentRevision: projects.currentRevision,
})
.from(projects)
@ -91,6 +92,7 @@ function settings(
workStage: null,
projectLead: null,
projectEditor: null,
maxSocketsPerCircuit: null,
...overrides,
};
}
@ -231,6 +233,7 @@ describe("project settings project-command repository", () => {
workStage: null,
projectLead: null,
projectEditor: null,
maxSocketsPerCircuit: null,
},
},
]);
@ -307,6 +310,7 @@ describe("project settings project-command repository", () => {
workStage: null,
projectLead: null,
projectEditor: null,
maxSocketsPerCircuit: null,
});
const undoStep = history.getNextCommand("project-1", "undo");
@ -339,6 +343,7 @@ describe("project settings project-command repository", () => {
workStage: null,
projectLead: null,
projectEditor: null,
maxSocketsPerCircuit: null,
currentRevision: 2,
});
assert.deepEqual(getDerivedVoltages(context), {
@ -382,6 +387,7 @@ describe("project settings project-command repository", () => {
workStage: null,
projectLead: null,
projectEditor: null,
maxSocketsPerCircuit: null,
currentRevision: 3,
});
assert.deepEqual(getDerivedVoltages(context), {
@ -495,7 +501,7 @@ describe("project settings project-command repository", () => {
projectLead: "P. Leitung",
projectEditor: "B. Arbeiter",
});
assert.equal(forward.inverse.schemaVersion, 3);
assert.equal(forward.inverse.schemaVersion, 4);
const undoStep = new ProjectHistoryRepository(
context.db
@ -543,6 +549,7 @@ describe("project settings project-command repository", () => {
workStage: _workStage,
projectLead: _projectLead,
projectEditor: _projectEditor,
maxSocketsPerCircuit: _maxSocketsPerCircuit,
...versionTwoPayload
} = settings({ name: "Stand aus Version 2" });
const forward = repository.executeUpdate({
@ -570,6 +577,83 @@ describe("project settings project-command repository", () => {
}
});
it("persists the socket limit with undo and keeps it for version 3 commands", () => {
const context = createTestDatabase();
try {
const repository = new ProjectSettingsProjectCommandRepository(
context.db
);
const readLimit = () =>
context.db
.select({ maxSocketsPerCircuit: projects.maxSocketsPerCircuit })
.from(projects)
.where(eq(projects.id, "project-1"))
.get()?.maxSocketsPerCircuit;
const forward = repository.executeUpdate({
projectId: "project-1",
expectedRevision: 0,
source: "user",
command: createProjectSettingsUpdateProjectCommand(
settings({ maxSocketsPerCircuit: 8 })
),
});
assert.equal(readLimit(), 8);
assert.equal(forward.inverse.schemaVersion, 4);
assert.equal(
(forward.inverse.payload as { maxSocketsPerCircuit: unknown })
.maxSocketsPerCircuit,
null
);
const {
maxSocketsPerCircuit: _maxSocketsPerCircuit,
...versionThreePayload
} = settings({ name: "Stand aus Version 3" });
const versionThree = repository.executeUpdate({
projectId: "project-1",
expectedRevision: 1,
source: "user",
command: {
schemaVersion: 3,
type: "project.update-settings",
payload: versionThreePayload,
},
});
assert.equal(readLimit(), 8);
assert.equal(versionThree.inverse.schemaVersion, 3);
assert.equal(
"maxSocketsPerCircuit" in versionThree.inverse.payload,
false
);
const history = new ProjectHistoryRepository(context.db);
for (const expectedRevision of [2, 3]) {
const undoStep = history.getNextCommand("project-1", "undo");
assert.ok(undoStep);
repository.executeUpdate({
projectId: "project-1",
expectedRevision,
source: "undo",
historyTargetChangeSetId: undoStep.changeSetId,
command: undoStep.command as never,
});
}
assert.equal(readLimit(), null);
for (const invalid of [0, 2.5, 1001]) {
assert.throws(
() =>
createProjectSettingsUpdateProjectCommand(
settings({ maxSocketsPerCircuit: invalid })
),
/invalid values/
);
}
} finally {
context.close();
}
});
it("persists the project supply-type selection and rejects disabling an in-use type", () => {
const context = createTestDatabase();
try {