Add print view, board info card and document logo upload

- Seitenansicht (/projects/:projectId/print): tabular cover page, board
  overview with supply structure and power sums, one plan per board with a
  repeating title block; PDF via the browser print dialog with named @page
  rules (A4 portrait cover, A4/A3 landscape plans, page x of y).
- Distribution-board info card (board number, location, fed-from, remark)
  in the board edit dialog, persisted through the additively extended
  distribution-board.update command (migration 0008, snapshot schema 6,
  legacy subtree commands normalized).
- Global document logo upload (PNG/JPEG/SVG, max 2 MB) outside project
  revisions and snapshots.
- fed-from stays free text; the overview hierarchy and cumulated sums are
  preliminary until the feed link is modeled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-09-25 19:31:04 +02:00
parent 2742b26b87
commit 94688d7a8c
38 changed files with 5301 additions and 32 deletions

View file

@ -61,6 +61,10 @@ function protectedSnapshot() {
floorId: null,
supplyType: null,
simultaneityFactor: 1,
boardNumber: "UV 1.1" as string | null,
location: "Raum 003 Technik" as string | null,
fedFrom: "HV 0.1" as string | null,
remark: null as string | null,
},
],
circuitLists: [
@ -199,9 +203,52 @@ describe("project state snapshot model", () => {
});
});
it("upgrades snapshot version 5 with empty distribution-board info", () => {
const previous = protectedSnapshot();
previous.schemaVersion = 5 as typeof previous.schemaVersion;
for (const board of previous.distributionBoards) {
const record = board as Record<string, unknown>;
delete record.boardNumber;
delete record.location;
delete record.fedFrom;
delete record.remark;
}
const upgraded = parseProjectStateSnapshot(previous);
assert.equal(upgraded.schemaVersion, projectStateSnapshotSchemaVersion);
assert.deepEqual(
{
boardNumber: upgraded.distributionBoards[0]?.boardNumber,
location: upgraded.distributionBoards[0]?.location,
fedFrom: upgraded.distributionBoards[0]?.fedFrom,
remark: upgraded.distributionBoards[0]?.remark,
},
{ boardNumber: null, location: null, fedFrom: null, remark: null }
);
});
it("keeps distribution-board info values and rejects them in older versions", () => {
const parsed = parseProjectStateSnapshot(protectedSnapshot());
assert.equal(parsed.distributionBoards[0]?.boardNumber, "UV 1.1");
assert.equal(parsed.distributionBoards[0]?.location, "Raum 003 Technik");
assert.equal(parsed.distributionBoards[0]?.fedFrom, "HV 0.1");
const mislabeled = protectedSnapshot();
mislabeled.schemaVersion = 5 as typeof mislabeled.schemaVersion;
assert.throws(() => parseProjectStateSnapshot(mislabeled));
});
it("upgrades snapshot version 4 with the existing quantity as manual share", () => {
const previous = protectedSnapshot();
previous.schemaVersion = 4 as typeof previous.schemaVersion;
for (const board of previous.distributionBoards) {
const record = board as Record<string, unknown>;
delete record.boardNumber;
delete record.location;
delete record.fedFrom;
delete record.remark;
}
previous.circuits[0]!.isReserve = false;
previous.circuits[0]!.deviceRows = [{
id: "row-1",