Add external row quantity foundation
This commit is contained in:
@@ -128,6 +128,7 @@ function createTestDatabase(): DatabaseContext {
|
||||
name: "Leuchte",
|
||||
displayName: "Leuchte lokal",
|
||||
quantity: 1,
|
||||
manualQuantity: 1,
|
||||
powerPerUnit: 0.1,
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
@@ -138,6 +139,7 @@ function createTestDatabase(): DatabaseContext {
|
||||
name: "Fremdzeile",
|
||||
displayName: "Fremdzeile",
|
||||
quantity: 1,
|
||||
manualQuantity: 1,
|
||||
powerPerUnit: 0.2,
|
||||
simultaneityFactor: 1,
|
||||
},
|
||||
@@ -175,6 +177,7 @@ describe("circuit device-row project-command repository", () => {
|
||||
|
||||
const row = getRow(context);
|
||||
assert.equal(row.quantity, 2);
|
||||
assert.equal(row.manualQuantity, 2);
|
||||
assert.equal(row.roomId, "room-1");
|
||||
assert.equal(row.remark, null);
|
||||
assert.equal(row.overriddenFields, "[\"quantity\"]");
|
||||
@@ -185,6 +188,7 @@ describe("circuit device-row project-command repository", () => {
|
||||
{ field: "quantity", value: 1 },
|
||||
{ field: "roomId", value: null },
|
||||
{ field: "remark", value: null },
|
||||
{ field: "manualQuantity", value: 1 },
|
||||
{ field: "overriddenFields", value: null },
|
||||
],
|
||||
});
|
||||
@@ -203,6 +207,7 @@ describe("circuit device-row project-command repository", () => {
|
||||
{ field: "quantity", value: 2 },
|
||||
{ field: "roomId", value: "room-1" },
|
||||
{ field: "remark", value: null },
|
||||
{ field: "manualQuantity", value: 2 },
|
||||
{ field: "overriddenFields", value: "[\"quantity\"]" },
|
||||
],
|
||||
},
|
||||
@@ -234,6 +239,7 @@ describe("circuit device-row project-command repository", () => {
|
||||
|
||||
const row = getRow(context);
|
||||
assert.equal(row.quantity, 1);
|
||||
assert.equal(row.manualQuantity, 1);
|
||||
assert.equal(row.overriddenFields, null);
|
||||
assert.equal(undone.revision.revisionNumber, 2);
|
||||
assert.deepEqual(
|
||||
@@ -377,4 +383,22 @@ describe("circuit device-row project-command repository", () => {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a manual share above the materialized total", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const store = new CircuitDeviceRowProjectCommandRepository(context.db);
|
||||
assert.throws(() => store.executeUpdate({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 0,
|
||||
source: "user",
|
||||
command: createCircuitDeviceRowUpdateProjectCommand("row-1", {
|
||||
manualQuantity: 2,
|
||||
}),
|
||||
}), /manual quantity/);
|
||||
assert.equal(getRow(context).manualQuantity, 1);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import { describe, it } from "node:test";
|
||||
import Database from "better-sqlite3";
|
||||
import {
|
||||
assertCircuitDeviceRowQuantity,
|
||||
calculateCircuitDeviceRowQuantity,
|
||||
} from "../src/domain/calculations/circuit-device-row-quantity.js";
|
||||
|
||||
describe("circuit device-row quantity", () => {
|
||||
it("adds the manual share and every indivisible external quantity", () => {
|
||||
assert.equal(
|
||||
calculateCircuitDeviceRowQuantity(2, [
|
||||
{ effectiveQuantity: 2 },
|
||||
{ effectiveQuantity: 1 },
|
||||
]),
|
||||
5
|
||||
);
|
||||
assert.doesNotThrow(() => assertCircuitDeviceRowQuantity({
|
||||
quantity: 5,
|
||||
manualQuantity: 2,
|
||||
externalObjects: [{ effectiveQuantity: 2 }, { effectiveQuantity: 1 }],
|
||||
}));
|
||||
});
|
||||
|
||||
it("rejects invalid shares and a stale materialized total", () => {
|
||||
assert.throws(
|
||||
() => calculateCircuitDeviceRowQuantity(-1, []),
|
||||
/Manual quantity/
|
||||
);
|
||||
assert.throws(
|
||||
() => calculateCircuitDeviceRowQuantity(0, [{ effectiveQuantity: 0 }]),
|
||||
/External effective quantity/
|
||||
);
|
||||
assert.throws(
|
||||
() => assertCircuitDeviceRowQuantity({
|
||||
quantity: 2,
|
||||
manualQuantity: 1,
|
||||
externalObjects: [{ effectiveQuantity: 2 }],
|
||||
}),
|
||||
/does not match/
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves existing row quantities in migration 0005", () => {
|
||||
const database = new Database(":memory:");
|
||||
try {
|
||||
database.exec("CREATE TABLE circuit_device_rows (id text PRIMARY KEY, quantity integer NOT NULL)");
|
||||
database.exec("INSERT INTO circuit_device_rows (id, quantity) VALUES ('row-1', 7)");
|
||||
const migration = fs
|
||||
.readFileSync("src/db/migrations/0005_stale_gorilla_man.sql", "utf8")
|
||||
.replaceAll("--> statement-breakpoint", "");
|
||||
database.exec(migration);
|
||||
assert.deepEqual(
|
||||
database.prepare("SELECT quantity, manual_quantity FROM circuit_device_rows").get(),
|
||||
{ quantity: 7, manual_quantity: 7 }
|
||||
);
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -21,6 +21,7 @@ import { projectDevices } from "../src/db/schema/project-devices.js";
|
||||
import { projects } from "../src/db/schema/projects.js";
|
||||
import { rooms } from "../src/db/schema/rooms.js";
|
||||
import { createProjectStateRestoreCommand } from "../src/domain/models/project-state-restore-command.model.js";
|
||||
import { projectStateSnapshotSchemaVersion } from "../src/domain/models/project-state-snapshot.model.js";
|
||||
import { createExternalCsvPreview } from "../src/external-model/application/external-csv-preview.js";
|
||||
import { parseExternalCsv } from "../src/external-model/csv/external-csv-transport.js";
|
||||
import {
|
||||
@@ -211,7 +212,7 @@ describe("external model snapshot and transfer", () => {
|
||||
insertLinkedExternalFixture(context.db);
|
||||
const populated = readProjectStateSnapshot(context.db, "project-1");
|
||||
assert.ok(populated);
|
||||
assert.equal(populated.state.schemaVersion, 4);
|
||||
assert.equal(populated.state.schemaVersion, projectStateSnapshotSchemaVersion);
|
||||
assert.equal(populated.state.externalModel.objects[0].circuitDeviceRowId, "row-1");
|
||||
|
||||
context.db.delete(externalModelSources).run();
|
||||
|
||||
@@ -179,6 +179,7 @@ describe("circuit device-row update project commands", () => {
|
||||
{ field: "roomId", value: null },
|
||||
{ field: "quantity", value: 2 },
|
||||
{ field: "cosPhi", value: 0.9 },
|
||||
{ field: "manualQuantity", value: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -187,6 +188,13 @@ describe("circuit device-row update project commands", () => {
|
||||
() => createCircuitDeviceRowUpdateProjectCommand("row-1", {}),
|
||||
/at least one change/
|
||||
);
|
||||
assert.throws(
|
||||
() => createCircuitDeviceRowUpdateProjectCommand("row-1", {
|
||||
quantity: 1,
|
||||
manualQuantity: 2,
|
||||
}),
|
||||
/must not exceed/
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
createCircuitDeviceRowUpdateProjectCommand("row-1", {
|
||||
@@ -470,7 +478,7 @@ describe("circuit device-row structure project commands", () => {
|
||||
row.circuitId
|
||||
);
|
||||
|
||||
assert.deepEqual(insert.payload.row, row);
|
||||
assert.deepEqual(insert.payload.row, { ...row, manualQuantity: 1 });
|
||||
assert.deepEqual(remove.payload, {
|
||||
rowId: "row-1",
|
||||
expectedCircuitId: "circuit-1",
|
||||
|
||||
@@ -199,6 +199,39 @@ describe("project state snapshot model", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("upgrades snapshot version 4 with the existing quantity as manual share", () => {
|
||||
const previous = protectedSnapshot();
|
||||
previous.schemaVersion = 4 as typeof previous.schemaVersion;
|
||||
previous.circuits[0]!.isReserve = false;
|
||||
previous.circuits[0]!.deviceRows = [{
|
||||
id: "row-1",
|
||||
circuitId: "circuit-1",
|
||||
linkedProjectDeviceId: null,
|
||||
sortOrder: 10,
|
||||
name: "Leuchte",
|
||||
displayName: "Leuchte",
|
||||
phaseType: "single_phase",
|
||||
connectionKind: null,
|
||||
costGroup: null,
|
||||
category: "lighting",
|
||||
level: null,
|
||||
roomId: null,
|
||||
roomNumberSnapshot: null,
|
||||
roomNameSnapshot: null,
|
||||
quantity: 3,
|
||||
powerPerUnit: 0.1,
|
||||
simultaneityFactor: 1,
|
||||
cosPhi: 1,
|
||||
remark: null,
|
||||
overriddenFields: null,
|
||||
}];
|
||||
|
||||
const upgraded = parseProjectStateSnapshot(previous);
|
||||
|
||||
assert.equal(upgraded.schemaVersion, projectStateSnapshotSchemaVersion);
|
||||
assert.equal(upgraded.circuits[0]?.deviceRows[0]?.manualQuantity, 3);
|
||||
});
|
||||
|
||||
it("rejects malformed baseline and unknown snapshot shapes", () => {
|
||||
const outdated = structuredClone(minimalSnapshot());
|
||||
delete (outdated.project as Record<string, unknown>).description;
|
||||
|
||||
Reference in New Issue
Block a user