Files
leistungsbilanz-ts/docs/circuit-list-editor-api.md
T
2026-07-25 21:09:02 +02:00

9.2 KiB

Circuit List Editor API

Scope

The circuit-first editor uses tree, circuit, row and project-device endpoints. All paths below are mounted below /api. There is no Consumer application API.

Project responses from GET /projects, GET /projects/:projectId and the circuit-tree endpoint include currentRevision. Versioned project commands use this value for optimistic concurrency checks.

Project Commands and History

  • GET /projects/:projectId/history
    • returns currentRevision, undoDepth, redoDepth and the current top change-set id for both persistent stacks
  • POST /projects/:projectId/commands
    • executes a supported versioned command as a new user revision
    • body: { "expectedRevision": 0, "command": { ... } }
  • POST /projects/:projectId/history/undo
  • POST /projects/:projectId/history/redo
    • body: { "expectedRevision": 1 }
    • executes the eligible inverse or forward command as a new auditable revision

The public dispatcher currently supports circuit.update, circuit.insert, circuit.delete, circuit-device-row.update, circuit-device-row.insert, circuit-device-row.delete, circuit-device-row.move, circuit-device-row.move-with-new-circuit, circuit.reorder-section, circuit.renumber-section and project-device.update, project-device.insert, project-device.delete and project-device.sync-rows, all with schema version 1. Other command types are rejected. Insert commands contain the complete entity or circuit block with stable ids; delete commands include the expected parent identity. Circuit snapshots contain zero, one or multiple complete device rows. Move commands contain each row's expected and target circuit plus its exact expected and target sort order. This makes deletion and moves undoable without generating replacement identities or renumbering circuits. Existing Circuit and CircuitDeviceRow cell edits, standalone insert/delete actions and device-row moves in the editor consume these endpoints. Reorder and renumber UI paths are still being migrated.

circuit-device-row.move targets existing circuits in the same circuit list. circuit-device-row.move-with-new-circuit atomically creates exactly one explicitly identified empty target circuit and moves one or multiple existing rows into it. Its inverse restores every row to its exact prior position and deletes the generated circuit only if its fields and complete row set still match the recorded state.

circuit.reorder-section requires one assignment for every circuit currently in the section. Each assignment records the expected and target sortOrder. The command and its inverse change no circuit field other than sortOrder; equipment identifiers and complete device-row blocks remain unchanged.

circuit.renumber-section is an explicit operation requiring one assignment for every circuit in the section. Assignments contain expected and target equipment identifiers. The store rejects stale values, duplicate targets and targets occupied by other sections, then applies swaps through collision-safe temporary identifiers. Undo restores the exact prior identifiers; sort positions and device rows remain unchanged.

project-device.sync-rows represents synchronize, disconnect and reconnect operations. Every selected row carries complete expected and target snapshots of all ProjectDevice-sync fields, the link and overriddenFields. All rows, the inverse command, revision and history-stack transition commit or roll back together. Disconnect/reconnect may only change the link; stale row snapshots and cross-project devices or rows are rejected.

project-device.update changes one or multiple canonical ProjectDevice fields and derives its inverse from the persisted device. It validates project ownership and never writes linked CircuitDeviceRow values; synchronization remains a separate explicit command.

project-device.insert stores the complete canonical device with a stable id. User inserts cannot attach existing rows. project-device.delete captures the complete device and every currently linked row before deletion. Its inverse recreates the same device and reconnects only rows whose complete disconnected snapshot still matches; device, links, revision and history transition are atomic.

Project Device CRUD

  • GET /project-devices/projects/:projectId
    • lists the project's reusable devices
  • POST /project-devices/projects/:projectId
  • PUT /project-devices/projects/:projectId/:projectDeviceId
    • request: all canonical device fields plus expectedRevision
    • response: { "projectDevice": { ... }, "revision": { ... }, "history": { ... } }
  • DELETE /project-devices/projects/:projectId/:projectDeviceId
    • request: { "expectedRevision": 12 }
    • response: the project command result with the updated history state
  • POST /project-devices/projects/:projectId/import-global/:globalDeviceId
    • request: { "expectedRevision": 12 }
    • creates an independent project device with a stable UUID
    • response shape matches ProjectDevice create/update

These write endpoints execute the typed project-device.update, project-device.insert and project-device.delete commands. A stale revision returns 409 PROJECT_REVISION_CONFLICT. Updating a project device still never synchronizes linked circuit rows implicitly.

Example:

{
  "expectedRevision": 0,
  "description": "Rename circuit",
  "command": {
    "schemaVersion": 1,
    "type": "circuit.update",
    "payload": {
      "circuitId": "cir_1",
      "changes": [
        { "field": "displayName", "value": "Sockets East" }
      ]
    }
  }
}

A stale expectedRevision returns HTTP 409 with PROJECT_REVISION_CONFLICT. Undo or redo without an eligible stack entry returns HTTP 409 with PROJECT_HISTORY_OPERATION_UNAVAILABLE.

Circuit-First Endpoints

Distribution Board Setup

  • POST /projects/:projectId/distribution-boards
    • atomically creates the distribution board, its circuit list and all default circuit sections

Tree Endpoint

  • GET /projects/:projectId/circuit-lists/:circuitListId/tree
  • Purpose: returns section/circuit/device-row tree with calculated row and circuit totals.

Response sketch:

{
  "circuitListId": "cl_1",
  "currentRevision": 12,
  "sections": [
    {
      "id": "sec_1",
      "key": "lighting",
      "prefix": "-1F",
      "circuits": [
        {
          "id": "cir_1",
          "equipmentIdentifier": "-1F1",
          "circuitTotalPower": 4.2,
          "deviceRows": [
            {
              "id": "row_1",
              "displayName": "Office lights",
              "rowTotalPower": 1.8
            }
          ]
        }
      ]
    }
  ]
}

Circuit Structure

  • GET /circuit-sections/:sectionId/next-identifier
    • preview next identifier for section (prefix + maxSuffix + 1)

Circuit and device-row field updates, standalone insertions/deletions and single or bulk device-row moves are available only as the corresponding versioned commands through POST /projects/:projectId/commands. There are no direct field-update PATCH, structure POST, move, Circuit DELETE or CircuitDeviceRow DELETE routes.

Reorder Circuits

  • PATCH /circuit-sections/:sectionId/circuits/reorder
  • Purpose: persist explicit circuit order within one section.

Request sketch:

{
  "orderedCircuitIds": ["cir_2", "cir_1", "cir_3"]
}

Renumber Section

  • POST /circuit-sections/:sectionId/renumber
  • Purpose: explicit renumbering by section prefix; never implicit on move/sort.

Safe Equipment Identifier Update

  • PATCH /circuit-sections/:sectionId/equipment-identifiers
  • Purpose: apply explicit per-circuit identifiers safely even with unique constraints.

Request sketch:

{
  "identifiers": [
    { "circuitId": "cir_1", "equipmentIdentifier": "-2F1" },
    { "circuitId": "cir_2", "equipmentIdentifier": "-2F2" }
  ]
}

Removed Legacy Endpoints

The former /consumers read/write endpoints were removed after every retained consumer had a verified Circuit-First migration mapping. Database upgrade tooling reads retained legacy rows directly; application features must use the Circuit-First endpoints above.

Linked Project Device Review

  • GET /project-devices/projects/:projectId/:projectDeviceId/links
    • returns all linked circuit device rows with distribution-board/circuit context and field differences
  • POST /project-devices/projects/:projectId/:projectDeviceId/synchronize
    • applies only explicitly selected fields to explicitly selected linked rows
    • request: selected rowIds, selected fields and expectedRevision
    • response: updated preview, revision and project history state
  • POST /project-devices/projects/:projectId/:projectDeviceId/disconnect
    • disconnects explicitly selected rows without changing their local values
    • request: selected rowIds and expectedRevision
    • response: updated preview, revision and project history state

Both writes execute project-device.sync-rows. Undo uses the project-wide POST /projects/:projectId/history/undo endpoint; reconnect is the validated inverse command. The former direct restore/reconnect endpoints are removed.

displayName is included in the comparison but is not selected by default in the UI. Updating a project device never triggers synchronization implicitly.