255 lines
8.0 KiB
Markdown
255 lines
8.0 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` and `GET /projects/:projectId` 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.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. The editor does not consume
|
|
these endpoints yet.
|
|
|
|
`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.
|
|
|
|
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",
|
|
"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 CRUD
|
|
|
|
- `POST /projects/:projectId/circuit-lists/:circuitListId/circuits`
|
|
- create circuit in list/section
|
|
- `POST /projects/:projectId/circuit-lists/:circuitListId/circuits-with-device-rows`
|
|
- atomically create one circuit and one or more initial device rows
|
|
- `PATCH /circuits/:circuitId`
|
|
- update circuit-level fields (BMK, protection, cable, reserve flag, etc.)
|
|
- `DELETE /circuits/:circuitId`
|
|
- delete circuit (and related rows via DB relations)
|
|
- `GET /circuit-sections/:sectionId/next-identifier`
|
|
- preview next identifier for section (`prefix + maxSuffix + 1`)
|
|
|
|
Request sketch (`POST .../circuits`):
|
|
|
|
```json
|
|
{
|
|
"sectionId": "sec_1",
|
|
"equipmentIdentifier": "-2F14",
|
|
"displayName": "Sockets East",
|
|
"sortOrder": 140
|
|
}
|
|
```
|
|
|
|
### Device Row CRUD
|
|
|
|
- `POST /circuits/:circuitId/device-rows`
|
|
- create row inside a circuit
|
|
- `PATCH /circuit-device-rows/:rowId`
|
|
- update row values
|
|
- `DELETE /circuit-device-rows/:rowId`
|
|
- delete row
|
|
|
|
### Move Device Row
|
|
|
|
- `PATCH /circuit-device-rows/:rowId/move`
|
|
- Purpose: move one row to existing circuit or to newly created circuit in target section.
|
|
|
|
Request sketch:
|
|
|
|
```json
|
|
{
|
|
"targetCircuitId": "cir_target"
|
|
}
|
|
```
|
|
|
|
or
|
|
|
|
```json
|
|
{
|
|
"targetSectionId": "sec_target",
|
|
"createNewCircuit": true
|
|
}
|
|
```
|
|
|
|
### Bulk Move Device Rows
|
|
|
|
- `PATCH /circuit-device-rows/move-bulk`
|
|
- Purpose: move multiple rows in one command flow.
|
|
|
|
Request sketch:
|
|
|
|
```json
|
|
{
|
|
"rowIds": ["row_1", "row_2"],
|
|
"targetCircuitId": "cir_target"
|
|
}
|
|
```
|
|
|
|
### Reorder Circuits
|
|
|
|
- `PATCH /circuit-sections/:sectionId/circuits/reorder`
|
|
- Purpose: persist explicit circuit order within one section.
|
|
|
|
Request sketch:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
- `POST /project-devices/projects/:projectId/:projectDeviceId/restore`
|
|
- restores the captured pre-sync values for session-local undo
|
|
- `POST /project-devices/projects/:projectId/:projectDeviceId/disconnect`
|
|
- disconnects explicitly selected rows without changing their local values
|
|
- `POST /project-devices/projects/:projectId/:projectDeviceId/reconnect`
|
|
- restores a disconnected link if the row has not been linked elsewhere meanwhile
|
|
|
|
`displayName` is included in the comparison but is not selected by default in the UI. Updating a
|
|
project device never triggers synchronization implicitly.
|