diff --git a/AGENTS.md b/AGENTS.md index 5d285fe..894024e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -597,6 +597,13 @@ Critical target rules: remain canonical and are never silently changed. - Multi-socket objects remain indivisible. Row quantity stays `manualQuantity + sum(effectiveQuantity)`. +- A row with linked Revit objects locks "Anzahl" in the editor. Its manual + share is edited in the optional column "Anz. manuell"; "Anz. Revit" shows + the linked share read-only. `circuit-device-row.update` derives the total + from a manual-only edit and rejects any quantity change that breaks + `quantity = manualQuantity + sum(effectiveQuantity)`, for every source. +- Undo/redo of a row assignment restores an accepted state and does not ask + again for planning-value conflict confirmation. - The project configuration has no independent version counter. Preview and apply use `expectedRevision`; import batches store a configuration copy. - Apply retransmits and parses the workbook. Undo/Redo never reads Excel; they diff --git a/src/db/repositories/circuit-device-row-project-command.repository.ts b/src/db/repositories/circuit-device-row-project-command.repository.ts index cb34ada..6ffb46c 100644 --- a/src/db/repositories/circuit-device-row-project-command.repository.ts +++ b/src/db/repositories/circuit-device-row-project-command.repository.ts @@ -1,4 +1,8 @@ import { and, eq } from "drizzle-orm"; +import { + assertCircuitDeviceRowQuantity, + calculateCircuitDeviceRowQuantity, +} from "../../domain/calculations/circuit-device-row-quantity.js"; import { assertCircuitDeviceRowUpdateProjectCommand, createCircuitDeviceRowUpdateProjectCommand, @@ -20,6 +24,7 @@ import type { AppDatabase } from "../database-context.js"; import { circuitDeviceRows } from "../schema/circuit-device-rows.js"; import { circuitLists } from "../schema/circuit-lists.js"; import { circuits } from "../schema/circuits.js"; +import { externalModelObjects } from "../schema/external-model-objects.js"; import { projectDevices } from "../schema/project-devices.js"; import { rooms } from "../schema/rooms.js"; import { @@ -90,13 +95,8 @@ export class CircuitDeviceRowProjectCommandRepository change.value, ]) ) as CircuitDeviceRowPatchInput; - const targetQuantity = patch.quantity ?? current.quantity; - const targetManualQuantity = - patch.manualQuantity ?? current.manualQuantity; - if (targetManualQuantity > targetQuantity) { - throw new Error( - "Device-row manual quantity must not exceed total quantity." - ); + if (patch.quantity !== undefined || patch.manualQuantity !== undefined) { + this.applyQuantityPatch(tx, current, patch); } if ( input.source === "user" && @@ -166,6 +166,46 @@ export class CircuitDeviceRowProjectCommandRepository }; } + // The row quantity is always the manual share plus every linked Revit + // object. A manual-share edit derives the total; a total edit is only + // valid while no Revit object contributes to the row. + private applyQuantityPatch( + database: AppDatabase, + current: CircuitDeviceRow, + patch: CircuitDeviceRowPatchInput + ) { + const externalObjects = database + .select({ planningValues: externalModelObjects.planningValues }) + .from(externalModelObjects) + .where(eq(externalModelObjects.circuitDeviceRowId, current.id)) + .all() + .map((object) => ({ + effectiveQuantity: object.planningValues.effectiveQuantity, + })); + const manualQuantity = patch.manualQuantity ?? current.manualQuantity; + if (patch.quantity === undefined) { + patch.quantity = calculateCircuitDeviceRowQuantity( + manualQuantity, + externalObjects + ); + return; + } + if ( + externalObjects.length > 0 && + patch.quantity !== + calculateCircuitDeviceRowQuantity(manualQuantity, externalObjects) + ) { + throw new Error( + "Die Anzahl dieser Gerätezeile ist mit Revit-Objekten verknüpft. Bitte die manuelle Anzahl bearbeiten." + ); + } + assertCircuitDeviceRowQuantity({ + quantity: patch.quantity, + manualQuantity, + externalObjects, + }); + } + private assertLinkedProjectDevice( database: AppDatabase, projectId: string, diff --git a/src/db/repositories/external-object-row-assignment-project-command.repository.ts b/src/db/repositories/external-object-row-assignment-project-command.repository.ts index b6e8123..d0a525c 100644 --- a/src/db/repositories/external-object-row-assignment-project-command.repository.ts +++ b/src/db/repositories/external-object-row-assignment-project-command.repository.ts @@ -31,7 +31,7 @@ export class ExternalObjectRowAssignmentProjectCommandRepository execute(input: Parameters[0]) { assertExternalObjectRowAssignmentProjectCommand(input.command); return executeProjectCommandTransaction(this.database, input, (tx) => { - this.apply(tx, input.projectId, input.command); + this.apply(tx, input.projectId, input.command, input.source); return invertExternalObjectRowAssignmentProjectCommand(input.command); }); } @@ -39,7 +39,8 @@ export class ExternalObjectRowAssignmentProjectCommandRepository private apply( database: AppDatabase, projectId: string, - command: Parameters[0]["command"] + command: Parameters[0]["command"], + source: Parameters[0]["source"] ) { const rowTransitions = new Map( command.payload.rows.map((transition) => [transition.expected.id, transition]) @@ -95,7 +96,14 @@ export class ExternalObjectRowAssignmentProjectCommandRepository const targetObjects = allProjectObjects.map((object) => objectTransitions.get(object.id)?.target ?? object ); - const confirmedConflicts = new Set(command.payload.confirmedConflictObjectIds); + // Undo/redo restores an exact state the user already accepted, for example + // re-linking objects after "Revit-Objekte lösen". Only a new user + // assignment needs explicit confirmation of planning-value conflicts. + const confirmedConflicts = new Set( + source === "user" + ? command.payload.confirmedConflictObjectIds + : command.payload.objects.map(({ target }) => target.id) + ); for (const transition of command.payload.rows) { const linkedObjects = targetObjects.filter( diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 5d6a474..399b4c6 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -4641,7 +4641,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str : "" }`} title={ - isProtectionTrigger + cell.lockedReason + ? cell.lockedReason + : isProtectionTrigger ? "Schutzgerät bearbeiten" : column.key === "equipmentIdentifier" && row.circuit && diff --git a/src/frontend/utils/circuit-grid-model.ts b/src/frontend/utils/circuit-grid-model.ts index fafeca4..7a1dc0e 100644 --- a/src/frontend/utils/circuit-grid-model.ts +++ b/src/frontend/utils/circuit-grid-model.ts @@ -7,6 +7,8 @@ export type CellKey = | "equipmentIdentifier" | "displayName" | "quantity" + | "manualQuantity" + | "externalQuantity" | "powerPerUnit" | "simultaneityFactor" | "rowTotalPower" @@ -72,6 +74,18 @@ export const allColumns: ColumnDef[] = [ }, { key: "displayName", label: "Anzeigename", defaultVisible: true }, { key: "quantity", label: "Anzahl", numeric: true, defaultVisible: true }, + { + key: "manualQuantity", + label: "Anz. manuell", + fullLabel: "Anzahl manuell", + numeric: true, + }, + { + key: "externalQuantity", + label: "Anz. Revit", + fullLabel: "Anzahl aus verknüpften Revit-Objekten", + numeric: true, + }, { key: "powerPerUnit", label: "P/Stk. [kW]", @@ -166,10 +180,16 @@ export function getProjectColumnLayoutStorageKey( export function normalizeColumnOrder(keys: CellKey[]): CellKey[] { const unique = [...new Set(keys)]; const allKeys = allColumns.map((column) => column.key); - const merged = [ - ...unique, - ...allKeys.filter((key) => !unique.includes(key)), - ]; + // Columns missing from a stored layout follow their default predecessor. + const merged = [...unique]; + allKeys.forEach((key, index) => { + if (merged.includes(key)) return; + const predecessor = allKeys + .slice(0, index) + .reverse() + .find((candidate) => merged.includes(candidate)); + merged.splice(predecessor ? merged.indexOf(predecessor) + 1 : merged.length, 0, key); + }); return [ "equipmentIdentifier", ...merged.filter((key) => key !== "equipmentIdentifier"), @@ -228,6 +248,8 @@ export function parseStoredColumnLayout( const deviceOnlyColumns = new Set([ "quantity", + "manualQuantity", + "externalQuantity", "powerPerUnit", "simultaneityFactor", "rowTotalPower", @@ -274,6 +296,7 @@ export const deviceFieldKeys = new Set([ "roomNumberSnapshot", "roomNameSnapshot", "quantity", + "manualQuantity", "powerPerUnit", "simultaneityFactor", "cosPhi", @@ -357,7 +380,7 @@ export function formatCellValue(value: GridValue, key: CellKey): string { if (formatted === "-" || typeof value !== "number") { return formatted; } - if (key === "quantity") { + if (key === "quantity" || key === "manualQuantity" || key === "externalQuantity") { return `${formatted} St.`; } if ( @@ -442,6 +465,7 @@ export function buildDeviceRowEditPatch( ): CircuitDeviceRowUpdatePatch { if ( key === "quantity" || + key === "manualQuantity" || key === "powerPerUnit" || key === "simultaneityFactor" ) { @@ -495,6 +519,8 @@ export function getDeviceValue(device: CircuitTreeDeviceRowDto, key: CellKey): G case "roomNumberSnapshot": return device.roomNumberSnapshot; case "roomNameSnapshot": return device.roomNameSnapshot; case "quantity": return device.quantity; + case "manualQuantity": return device.manualQuantity; + case "externalQuantity": return device.quantity - device.manualQuantity; case "powerPerUnit": return device.powerPerUnit; case "simultaneityFactor": return device.simultaneityFactor; case "cosPhi": return device.cosPhi; @@ -600,6 +626,7 @@ export function compareSortValues(left: GridValue, right: GridValue): number { export function getCellKind(rowType: RowType, key: CellKey): CellKind { if ( + key === "externalQuantity" || key === "rowTotalPower" || key === "circuitTotalPower" || key === "protectionType" || @@ -609,6 +636,11 @@ export function getCellKind(rowType: RowType, key: CellKey): CellKind { ) return "computed"; if (key === "voltage") return "readonly"; if (rowType === "section") return "readonly"; + // The manual share belongs to an existing device row; new rows start from "Anzahl". + if ( + key === "manualQuantity" && + (rowType === "placeholder" || rowType === "reserveCircuit") + ) return "readonly"; if (rowType === "placeholder") { if (deviceFieldKeys.has(key)) return "deviceField"; if (circuitFieldKeys.has(key)) return "circuitField"; diff --git a/src/frontend/utils/circuit-grid-projection.ts b/src/frontend/utils/circuit-grid-projection.ts index 30e5ecf..e64b4f4 100644 --- a/src/frontend/utils/circuit-grid-projection.ts +++ b/src/frontend/utils/circuit-grid-projection.ts @@ -39,6 +39,8 @@ export interface VisibleGridCell { value: GridValue; /** Separate persisted value when the displayed value is derived. */ editValue?: GridValue; + /** Why an otherwise editable cell is locked, shown as the cell tooltip. */ + lockedReason?: string; } export interface VisibleGridRow { @@ -144,7 +146,15 @@ function makeVisibleGridRow( const cells = allColumns.map((column): VisibleGridCell => { const kind = getCellKind(rowType, column.key); - const editable = kind === "circuitField" || kind === "deviceField"; + // Linked Revit objects own part of the quantity; only the manual share stays editable. + const lockedReason = + column.key === "quantity" && + kind === "deviceField" && + (device?.externalObjectCount ?? 0) > 0 + ? "Mit Revit verknüpft – über „Anz. manuell“ bearbeiten." + : undefined; + const editable = + (kind === "circuitField" || kind === "deviceField") && !lockedReason; let value: GridValue; let editValue: GridValue | undefined; @@ -200,6 +210,8 @@ function makeVisibleGridRow( circuitOnlyColumns.has(column.key) ) { value = getCircuitValue(circuit, column.key); + } else if (column.key === "externalQuantity" && device) { + value = getDeviceValue(device, column.key); } else if (column.key === "rowTotalPower" && device) { value = device.rowTotalPower; } else if (column.key === "rowTotalPower" && circuit) { @@ -210,7 +222,7 @@ function makeVisibleGridRow( value = circuit.voltage; } - return { cellKey: column.key, editable, kind, value, editValue }; + return { cellKey: column.key, editable, kind, value, editValue, lockedReason }; }); return { diff --git a/tests/circuit-device-row-project-command.repository.test.ts b/tests/circuit-device-row-project-command.repository.test.ts index 5ab1e3a..97e774d 100644 --- a/tests/circuit-device-row-project-command.repository.test.ts +++ b/tests/circuit-device-row-project-command.repository.test.ts @@ -384,19 +384,37 @@ describe("circuit device-row project-command repository", () => { } }); - it("rejects a manual share above the materialized total", () => { + it("derives the total from a manual-share edit and rejects inconsistent quantity pairs", () => { const context = createTestDatabase(); try { const store = new CircuitDeviceRowProjectCommandRepository(context.db); + assert.throws( + () => createCircuitDeviceRowUpdateProjectCommand("row-1", { + quantity: 1, + manualQuantity: 2, + }), + /manualQuantity must not exceed quantity/ + ); assert.throws(() => store.executeUpdate({ + projectId: "project-1", + expectedRevision: 0, + source: "user", + command: createCircuitDeviceRowUpdateProjectCommand("row-1", { + quantity: 3, + manualQuantity: 2, + }), + }), /does not match manual and external quantity/); + assert.deepEqual([getRow(context).quantity, getRow(context).manualQuantity], [1, 1]); + + store.executeUpdate({ projectId: "project-1", expectedRevision: 0, source: "user", command: createCircuitDeviceRowUpdateProjectCommand("row-1", { manualQuantity: 2, }), - }), /manual quantity/); - assert.equal(getRow(context).manualQuantity, 1); + }); + assert.deepEqual([getRow(context).quantity, getRow(context).manualQuantity], [2, 2]); } finally { context.close(); } diff --git a/tests/circuit-grid-model.test.ts b/tests/circuit-grid-model.test.ts index 2cf0aa3..caf64fd 100644 --- a/tests/circuit-grid-model.test.ts +++ b/tests/circuit-grid-model.test.ts @@ -4,6 +4,7 @@ import { allColumns, buildCircuitEditPatch, buildDeviceRowEditPatch, + defaultVisibleColumnKeys, formatValue, formatCellValue, getCircuitSectionLabel, @@ -107,6 +108,34 @@ describe("circuit grid model", () => { assert.equal(parseStoredColumnLayout("{invalid"), null); }); + it("adds new quantity columns hidden and next to Anzahl in stored layouts", () => { + const layout = parseStoredColumnLayout( + JSON.stringify({ + order: ["equipmentIdentifier", "remark", "quantity", "displayName"], + visible: ["equipmentIdentifier", "quantity"], + }) + ); + assert.ok(layout); + const quantityIndex = layout.order.indexOf("quantity"); + assert.deepEqual( + layout.order.slice(quantityIndex, quantityIndex + 3), + ["quantity", "manualQuantity", "externalQuantity"] + ); + assert.deepEqual(layout.visible, ["equipmentIdentifier", "quantity"]); + assert.equal(defaultVisibleColumnKeys.includes("manualQuantity"), false); + assert.equal(defaultVisibleColumnKeys.includes("externalQuantity"), false); + }); + + it("edits the manual share only on existing device rows", () => { + assert.equal(getCellKind("deviceRow", "manualQuantity"), "deviceField"); + assert.equal(getCellKind("circuitCompact", "manualQuantity"), "deviceField"); + assert.equal(getCellKind("placeholder", "manualQuantity"), "readonly"); + assert.equal(getCellKind("reserveCircuit", "manualQuantity"), "readonly"); + assert.equal(getCellKind("deviceRow", "externalQuantity"), "computed"); + assert.deepEqual(buildDeviceRowEditPatch("manualQuantity", "2"), { manualQuantity: 2 }); + assert.equal(formatCellValue(2, "externalQuantity"), "2 St."); + }); + it("shows stable circuit sections with German labels", () => { assert.equal( getCircuitSectionLabel({ diff --git a/tests/circuit-grid-projection.test.ts b/tests/circuit-grid-projection.test.ts index 18cdba2..a789591 100644 --- a/tests/circuit-grid-projection.test.ts +++ b/tests/circuit-grid-projection.test.ts @@ -158,6 +158,27 @@ describe("circuit grid projection", () => { assert.deepEqual(result[0].circuits[1].deviceRows.map((row) => row.id), ["device-office", "device-store"]); }); + it("locks the total quantity of Revit-linked rows and exposes the manual and Revit shares", () => { + const linkedDevice = { ...makeDevice("device-linked", "Steckdose", "1.04", 10), quantity: 3, manualQuantity: 1, externalObjectCount: 1 }; + const linkedSections: CircuitTreeSectionDto[] = [{ + ...sections[0], + circuits: [makeCircuit("circuit-linked", "-1F4", "Steckdosen", [linkedDevice, makeDevice("device-manual", "Leuchte", "1.05", 20)], 10)], + }]; + const rows = buildVisibleGridRowsWithStructure(linkedSections, { headerComponents: [], footerComponents: [] }); + const cell = (deviceId: string, key: string) => + rows.find((row) => row.device?.id === deviceId)!.cells.find((entry) => entry.cellKey === key)!; + + assert.equal(cell("device-linked", "quantity").editable, false); + assert.match(cell("device-linked", "quantity").lockedReason ?? "", /Anz\. manuell/); + assert.equal(cell("device-linked", "manualQuantity").editable, true); + assert.equal(cell("device-linked", "manualQuantity").value, 1); + assert.equal(cell("device-linked", "externalQuantity").editable, false); + assert.equal(cell("device-linked", "externalQuantity").value, 2); + assert.equal(cell("device-manual", "quantity").editable, true); + assert.equal(cell("device-manual", "quantity").lockedReason, undefined); + assert.equal(cell("device-manual", "externalQuantity").value, 0); + }); + it("builds the compact, grouped, reserve and placeholder row shapes", () => { const rows = buildVisibleGridRowsWithStructure(sections, { headerComponents: [], footerComponents: [] }); diff --git a/tests/external-object-row-assignment-project-command.repository.test.ts b/tests/external-object-row-assignment-project-command.repository.test.ts index 547d60f..8a09e33 100644 --- a/tests/external-object-row-assignment-project-command.repository.test.ts +++ b/tests/external-object-row-assignment-project-command.repository.test.ts @@ -7,6 +7,7 @@ import { createDatabaseContext, type AppDatabase } from "../src/db/database-cont 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 { CircuitDeviceRowProjectCommandRepository } from "../src/db/repositories/circuit-device-row-project-command.repository.js"; import { CircuitDeviceRowStructureProjectCommandRepository } from "../src/db/repositories/circuit-device-row-structure-project-command.repository.js"; import { CircuitStructureProjectCommandRepository } from "../src/db/repositories/circuit-structure-project-command.repository.js"; import { CircuitGroupSubtreeProjectCommandRepository } from "../src/db/repositories/circuit-group-subtree-project-command.repository.js"; @@ -24,6 +25,7 @@ import { externalRoomMappings } from "../src/db/schema/external-room-mappings.js import { floors } from "../src/db/schema/floors.js"; import { projects } from "../src/db/schema/projects.js"; import { rooms } from "../src/db/schema/rooms.js"; +import { createCircuitDeviceRowUpdateProjectCommand } from "../src/domain/models/circuit-device-row-project-command.model.js"; import { createExternalObjectRowAssignmentProjectCommand } from "../src/domain/models/external-object-row-assignment-project-command.model.js"; import { createExternalObjectNewRowProjectCommand, @@ -223,6 +225,36 @@ describe("external object row assignment project command", () => { } }); + it("re-links a detached object on undo although its planning values differ from the row", () => { + const context = createFixture(); + try { + // The original assignment confirmed this planning-value conflict. + context.db.update(circuitDeviceRows).set({ displayName: "Abweichend" }).where(eq(circuitDeviceRows.id, "row-1")).run(); + const currentRow = row(context.db, "row-1"); + const object = objectSnapshot("row-1"); + const repository = new ExternalObjectRowAssignmentProjectCommandRepository(context.db); + const detached = repository.execute({ + projectId: "project-1", + expectedRevision: 0, + source: "user", + command: createExternalObjectRowAssignmentProjectCommand({ + rows: [{ expected: currentRow, target: { ...currentRow, quantity: 1 } }], + objects: [{ expected: object, target: { ...object, circuitDeviceRowId: null } }], + confirmedConflictObjectIds: [], + }), + }); + + const undone = repository.execute({ projectId: "project-1", expectedRevision: 1, source: "undo", historyTargetChangeSetId: detached.revision.changeSetId, command: detached.inverse }); + assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, "row-1"); + assert.deepEqual([row(context.db, "row-1").quantity, row(context.db, "row-1").manualQuantity], [3, 1]); + + repository.execute({ projectId: "project-1", expectedRevision: 2, source: "redo", historyTargetChangeSetId: detached.revision.changeSetId, command: undone.inverse }); + assert.equal(context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId, null); + } finally { + context.close(); + } + }); + it("rolls back row quantities and links when history persistence fails", () => { const context = createFixture(); try { @@ -245,6 +277,54 @@ describe("external object row assignment project command", () => { }); }); +describe("device-row quantity edits with linked Revit objects", () => { + const quantities = (database: AppDatabase, id: string) => { + const current = row(database, id); + return [current.quantity, current.manualQuantity]; + }; + + it("rejects a total edit on a linked row and keeps Revit and manual share consistent", () => { + const context = createFixture(); + try { + const store = new CircuitDeviceRowProjectCommandRepository(context.db); + assert.throws( + () => store.executeUpdate({ projectId: "project-1", expectedRevision: 0, source: "user", command: createCircuitDeviceRowUpdateProjectCommand("row-1", { quantity: 4 }) }), + /mit Revit-Objekten verknüpft/ + ); + assert.deepEqual(quantities(context.db, "row-1"), [3, 1]); + } finally { + context.close(); + } + }); + + it("derives the total from a manual-share edit through undo and redo", () => { + const context = createFixture(); + try { + const store = new CircuitDeviceRowProjectCommandRepository(context.db); + const edited = store.executeUpdate({ projectId: "project-1", expectedRevision: 0, source: "user", command: createCircuitDeviceRowUpdateProjectCommand("row-1", { manualQuantity: 2 }) }); + assert.deepEqual(quantities(context.db, "row-1"), [4, 2]); + + const undone = store.executeUpdate({ projectId: "project-1", expectedRevision: 1, source: "undo", historyTargetChangeSetId: edited.revision.changeSetId, command: edited.inverse }); + assert.deepEqual(quantities(context.db, "row-1"), [3, 1]); + + store.executeUpdate({ projectId: "project-1", expectedRevision: 2, source: "redo", historyTargetChangeSetId: edited.revision.changeSetId, command: undone.inverse }); + assert.deepEqual(quantities(context.db, "row-1"), [4, 2]); + } finally { + context.close(); + } + }); + + it("keeps editing the total of a row without Revit objects", () => { + const context = createFixture(null); + try { + new CircuitDeviceRowProjectCommandRepository(context.db).executeUpdate({ projectId: "project-1", expectedRevision: 0, source: "user", command: createCircuitDeviceRowUpdateProjectCommand("row-1", { quantity: 5 }) }); + assert.deepEqual(quantities(context.db, "row-1"), [5, 5]); + } finally { + context.close(); + } + }); +}); + describe("external object new-row project command", () => { it("creates an external-only row and supports exact persisted undo and redo", () => { const context = createFixture(null); diff --git a/tests/project-command.service.test.ts b/tests/project-command.service.test.ts index ac6d887..67106e7 100644 --- a/tests/project-command.service.test.ts +++ b/tests/project-command.service.test.ts @@ -132,6 +132,7 @@ function createTestDatabase(): DatabaseContext { name: "Leuchte", displayName: "Leuchte", quantity: 1, + manualQuantity: 1, powerPerUnit: 0.1, simultaneityFactor: 1, }) diff --git a/tests/project-history.repository.test.ts b/tests/project-history.repository.test.ts index 0c613f2..bcb59bc 100644 --- a/tests/project-history.repository.test.ts +++ b/tests/project-history.repository.test.ts @@ -59,6 +59,7 @@ function createTestDatabase(): DatabaseContext { name: "Leuchte", displayName: "Leuchte", quantity: 1, + manualQuantity: 1, powerPerUnit: 0.1, simultaneityFactor: 1, })