Integrate rough estimate balance
This commit is contained in:
commit
573487666c
45 changed files with 9378 additions and 58 deletions
|
|
@ -11,7 +11,13 @@ import {
|
|||
import { DistributionBoardFixtureRepository } from "./support/distribution-board-fixture.js";
|
||||
import { ProjectSnapshotRepository } from "../src/db/repositories/project-snapshot.repository.js";
|
||||
import { ProjectTransferRepository } from "../src/db/repositories/project-transfer.repository.js";
|
||||
import { ProjectStateRestoreCommandRepository } from "../src/db/repositories/project-state-restore-command.repository.js";
|
||||
import { readProjectStateSnapshot } from "../src/db/repositories/project-state-snapshot.persistence.js";
|
||||
import {
|
||||
deleteEstimateBalance,
|
||||
insertEstimateBalance,
|
||||
readEstimateBalances,
|
||||
} from "../src/db/repositories/estimate-balance.persistence.js";
|
||||
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../src/db/schema/circuit-lists.js";
|
||||
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
|
||||
|
|
@ -28,8 +34,48 @@ import { projects } from "../src/db/schema/projects.js";
|
|||
import { rooms } from "../src/db/schema/rooms.js";
|
||||
import { deserializeProjectStateSnapshot } from "../src/domain/models/project-state-snapshot.model.js";
|
||||
import { projectStateSnapshotSchemaVersion } from "../src/domain/models/project-state-snapshot.model.js";
|
||||
import { createProjectStateRestoreCommand } from "../src/domain/models/project-state-restore-command.model.js";
|
||||
import { ProjectRevisionConflictError } from "../src/domain/errors/project-revision-conflict.error.js";
|
||||
import { ProjectSnapshotNameConflictError } from "../src/domain/errors/project-snapshot-name-conflict.error.js";
|
||||
import { parseEstimateBalanceSnapshot } from "../src/estimate-balance/domain/estimate-balance-command.model.js";
|
||||
|
||||
function estimateBalance(projectId: string, suffix = "") {
|
||||
return parseEstimateBalanceSnapshot({
|
||||
id: `estimate${suffix}`,
|
||||
projectId,
|
||||
name: `Vorplanung${suffix}`,
|
||||
workStage: "LPH 2",
|
||||
dateIso: "2026-09-27",
|
||||
reserveAvPercent: 10,
|
||||
reserveSvPercent: 5,
|
||||
sortOrder: 0,
|
||||
groups: [
|
||||
{
|
||||
id: `estimate-group${suffix}`,
|
||||
costGroup: "440",
|
||||
title: "Starkstromanlagen",
|
||||
editor: "Planung",
|
||||
sortOrder: 0,
|
||||
rows: [
|
||||
{
|
||||
id: `estimate-row${suffix}`,
|
||||
sortOrder: 0,
|
||||
supply: "ELT",
|
||||
name: "Beleuchtung",
|
||||
quantity: 100,
|
||||
unit: "area",
|
||||
connection: "fixed",
|
||||
powerPerUnitW: 12,
|
||||
simultaneityFactor: 0.8,
|
||||
cosPhi: 0.95,
|
||||
safetySupply: false,
|
||||
remark: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function createTestDatabase(): DatabaseContext {
|
||||
const context = createDatabaseContext(":memory:");
|
||||
|
|
@ -463,14 +509,41 @@ describe("project snapshot repository", () => {
|
|||
});
|
||||
|
||||
describe("portable project transfer", () => {
|
||||
it("keeps estimate balances outside normal snapshot restore", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
insertEstimateBalance(context.db, estimateBalance("project-1"));
|
||||
const current = readProjectStateSnapshot(context.db, "project-1");
|
||||
assert.ok(current);
|
||||
new ProjectStateRestoreCommandRepository(context.db).execute({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 0,
|
||||
source: "restore",
|
||||
command: createProjectStateRestoreCommand(current.payloadSha256, {
|
||||
...current.state,
|
||||
project: { ...current.state.project, name: "Restored project" },
|
||||
}),
|
||||
});
|
||||
assert.deepEqual(
|
||||
readEstimateBalances(context.db, "project-1").map((entry) => entry.id),
|
||||
["estimate"]
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("exports and atomically duplicates a complete project with new ids", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
context.db.update(projects).set({ siteAddress: "Musterstraße 1", workStage: "LPH 5", projectLead: "Planung", projectEditor: "Test" }).where(eq(projects.id, "project-1")).run();
|
||||
context.db.update(distributionBoards).set({ boardNumber: "UV-01", location: "Technik", fedFrom: "HAK", remark: "Prüfstand" }).where(eq(distributionBoards.projectId, "project-1")).run();
|
||||
insertEstimateBalance(context.db, estimateBalance("project-1"));
|
||||
const repository = new ProjectTransferRepository(context.db);
|
||||
const transfer = repository.exportProject("project-1");
|
||||
assert.ok(transfer);
|
||||
assert.equal(transfer.formatVersion, 2);
|
||||
assert.equal(transfer.estimateBalances.length, 1);
|
||||
const duplicate = repository.importDuplicate(transfer);
|
||||
assert.notEqual(duplicate.projectId, "project-1");
|
||||
assert.equal(duplicate.name, "Snapshot-Projekt (Kopie)");
|
||||
|
|
@ -541,6 +614,18 @@ describe("portable project transfer", () => {
|
|||
copied.state.distributionBoards[0].simultaneityFactor,
|
||||
0.65
|
||||
);
|
||||
const copiedBalances = readEstimateBalances(context.db, duplicate.projectId);
|
||||
assert.equal(copiedBalances.length, 1);
|
||||
assert.notEqual(copiedBalances[0].id, transfer.estimateBalances[0].id);
|
||||
assert.notEqual(
|
||||
copiedBalances[0].groups[0].id,
|
||||
transfer.estimateBalances[0].groups[0].id
|
||||
);
|
||||
assert.notEqual(
|
||||
copiedBalances[0].groups[0].rows[0].id,
|
||||
transfer.estimateBalances[0].groups[0].rows[0].id
|
||||
);
|
||||
assert.equal(copiedBalances[0].groups[0].rows[0].name, "Beleuchtung");
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
|
|
@ -566,6 +651,14 @@ describe("portable project transfer", () => {
|
|||
}),
|
||||
/checksum verification failed/
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
repository.importDuplicate({
|
||||
...transfer,
|
||||
estimateBalances: [estimateBalance("project-1", "-tampered")],
|
||||
}),
|
||||
/checksum verification failed/
|
||||
);
|
||||
assert.equal(
|
||||
context.db.select().from(projects).all().length,
|
||||
1
|
||||
|
|
@ -575,15 +668,79 @@ describe("portable project transfer", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("imports legacy transfer version 1 with no estimate balances", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
const snapshot = readProjectStateSnapshot(context.db, "project-1");
|
||||
assert.ok(snapshot);
|
||||
const legacyTransfer = {
|
||||
format: "leistungsbilanz.project",
|
||||
formatVersion: 1,
|
||||
exportedAtIso: "2026-09-27T12:00:00.000Z",
|
||||
payloadSha256: snapshot.payloadSha256,
|
||||
projectState: snapshot.state,
|
||||
};
|
||||
const duplicate = new ProjectTransferRepository(context.db).importDuplicate(
|
||||
legacyTransfer
|
||||
);
|
||||
assert.deepEqual(
|
||||
readEstimateBalances(context.db, duplicate.projectId),
|
||||
[]
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("replaces exported estimate balances atomically and restores them on undo", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
insertEstimateBalance(context.db, estimateBalance("project-1"));
|
||||
const transferRepository = new ProjectTransferRepository(context.db);
|
||||
const transfer = transferRepository.exportProject("project-1");
|
||||
assert.ok(transfer);
|
||||
|
||||
deleteEstimateBalance(context.db, "project-1", "estimate");
|
||||
insertEstimateBalance(context.db, estimateBalance("project-1", "-changed"));
|
||||
const prepared = transferRepository.prepareReplace("project-1", transfer);
|
||||
assert.ok(prepared);
|
||||
const restored = new ProjectStateRestoreCommandRepository(context.db).execute({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 0,
|
||||
source: "restore",
|
||||
command: prepared.command,
|
||||
});
|
||||
assert.deepEqual(
|
||||
readEstimateBalances(context.db, "project-1").map((entry) => entry.id),
|
||||
["estimate"]
|
||||
);
|
||||
|
||||
new ProjectStateRestoreCommandRepository(context.db).execute({
|
||||
projectId: "project-1",
|
||||
expectedRevision: 1,
|
||||
source: "undo",
|
||||
historyTargetChangeSetId: restored.revision.changeSetId,
|
||||
command: restored.inverse,
|
||||
});
|
||||
assert.deepEqual(
|
||||
readEstimateBalances(context.db, "project-1").map((entry) => entry.id),
|
||||
["estimate-changed"]
|
||||
);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rolls back a duplicate when a late project-state insert fails", () => {
|
||||
const context = createTestDatabase();
|
||||
try {
|
||||
insertEstimateBalance(context.db, estimateBalance("project-1"));
|
||||
const repository = new ProjectTransferRepository(context.db);
|
||||
const transfer = repository.exportProject("project-1");
|
||||
assert.ok(transfer);
|
||||
context.sqlite.exec(`
|
||||
CREATE TRIGGER fail_imported_device_row
|
||||
BEFORE INSERT ON circuit_device_rows
|
||||
CREATE TRIGGER fail_imported_estimate_row
|
||||
BEFORE INSERT ON estimate_rows
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'forced project transfer failure');
|
||||
END;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue