Files
leistungsbilanz-ts/docs/circuit-list-editor-api.md
T

220 lines
9.3 KiB
Markdown

# 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
- `GET /projects/:projectId/history/revisions`
- returns revision metadata in descending `revisionNumber` order
- optional query: `limit` (default `25`, maximum `100`) and exclusive
`beforeRevision`
- returns `projectId`, `currentRevision`, `revisions` and
`nextBeforeRevision`; use the latter as the next page's
`beforeRevision`
- each revision contains ids, number, timestamp, actor, source, description,
command type and payload schema version
- stored forward/inverse command payloads are intentionally not exposed
- `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.reorder-sections`,
`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, device-row
moves, reorders and explicit renumbering in the editor consume these endpoints.
`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.reorder-sections` contains one complete assignment block per affected
section. All blocks are validated before any write and commit or roll back
together as one revision and one undo step. The editor uses it when applying a
sorted view that changes multiple sections.
`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:
```json
{
"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:
```json
{
"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, single
or bulk device-row moves, circuit reorders and explicit renumbering are
available only as the corresponding versioned commands through
`POST /projects/:projectId/commands`. There are no direct field-update PATCH,
structure POST, move, reorder, renumber, identifier-restore, Circuit DELETE or
CircuitDeviceRow DELETE routes.
## 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.