Add company settings and number sequence API routes

- Add CompanySetting routes (list, get, set) backed by company-scoped store
- Add Number routes (list, update) for managing number sequences
- Register new store namespaces for numbers and companysetting
- Add CompanySetting and NumberSequence schemas to OpenAPI
- Regenerate API docs and index for the new routes
This commit is contained in:
seb
2026-08-08 09:01:28 +02:00
parent e6c24654dd
commit 57420ac0db
14 changed files with 1279 additions and 0 deletions

View File

@@ -63,6 +63,9 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/category/add-image.md`](routes/category/add-image.md) | `POST /category/{id}/image` |
| [`routes/company/list.md`](routes/company/list.md) | `GET /company` |
| [`routes/company/create.md`](routes/company/create.md) | `POST /company` |
| [`routes/companysetting/list.md`](routes/companysetting/list.md) | `GET /company/setting` |
| [`routes/companysetting/get.md`](routes/companysetting/get.md) | `GET /company/setting/{key}` |
| [`routes/companysetting/set.md`](routes/companysetting/set.md) | `PUT /company/setting/{key}` |
| [`routes/company/delete.md`](routes/company/delete.md) | `DELETE /company/{id}` |
| [`routes/company/update.md`](routes/company/update.md) | `PUT /company/{id}` |
| [`routes/customergroup/list.md`](routes/customergroup/list.md) | `GET /customergroup` |
@@ -92,6 +95,8 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/events.md`](routes/events.md) | `GET /events` |
| [`routes/health.md`](routes/health.md) | `GET /health` |
| [`routes/image/get.md`](routes/image/get.md) | `GET /image/{fullHash}` |
| [`routes/number/list.md`](routes/number/list.md) | `GET /number` |
| [`routes/number/update.md`](routes/number/update.md) | `PATCH /number/{id}` |
| [`routes/order/list.md`](routes/order/list.md) | `GET /order` |
| [`routes/order/create.md`](routes/order/create.md) | `POST /order` |
| [`routes/order/delete.md`](routes/order/delete.md) | `DELETE /order/{id}` |

View File

@@ -36,6 +36,10 @@
"name": "Company",
"description": "Company CRUD"
},
{
"name": "CompanySetting",
"description": "Company-scoped settings"
},
{
"name": "Unit",
"description": "Company-scoped units of measure"
@@ -2387,6 +2391,191 @@
}
}
},
"/number": {
"get": {
"tags": [
"Number"
],
"summary": "List number sequences",
"description": "Lists the number sequences of the authenticated user's selected company.",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "number/list.md",
"responses": {
"200": {
"description": "Number sequence list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"numbers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NumberSequence"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/number/{id}": {
"patch": {
"tags": [
"Number"
],
"summary": "Update number sequence",
"description": "Updates sequence settings for the selected company. Only provided fields\nare changed (`prefix`, `padding`, `next`). `prefix` is trimmed and must\nbe at most 50 characters. `padding` must be an integer `>= 0`. `next`\nmust be an integer `>= 1`. If no field is provided, the row is returned\nunchanged.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "number/update.md",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"prefix": {
"type": "string"
},
"padding": {
"type": "integer",
"minimum": 0
},
"next": {
"type": "integer",
"minimum": 1
}
},
"example": {
"prefix": "A-",
"padding": 4,
"next": 100
}
}
}
}
},
"responses": {
"200": {
"description": "Updated number sequence",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"number": {
"$ref": "#/components/schemas/NumberSequence"
}
}
}
}
}
},
"400": {
"description": "Invalid number, invalid id, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Number sequence or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/health": {
"get": {
"tags": [
@@ -4757,6 +4946,268 @@
}
}
},
"/company/setting": {
"get": {
"tags": [
"CompanySetting"
],
"summary": "List company settings",
"description": "Returns the visible settings of the authenticated user's selected\ncompany: the full set of allowed keys (with `value: null` when unset)\nplus any stored settings that are frontend-visible. The `frontend`\nflag is stripped from responses.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "companysetting/list.md",
"responses": {
"200": {
"description": "Company settings",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"settings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CompanySetting"
}
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company/setting/{key}": {
"get": {
"tags": [
"CompanySetting"
],
"summary": "Get company setting by key",
"description": "Returns a single company setting for the authenticated user's selected\ncompany, or `404` if it does not exist.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "companysetting/get.md",
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Company setting",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"setting": {
"$ref": "#/components/schemas/CompanySetting"
}
}
}
}
}
},
"400": {
"description": "No company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company setting or company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"put": {
"tags": [
"CompanySetting"
],
"summary": "Upsert company setting",
"description": "Sets (upserts) a company setting for the selected company.\nOnly a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`,\n`keys.openrouter`, `labelprinter.addr`, `labelprinter.type`.\n",
"security": [
{
"cookieAuth": []
}
],
"x-markdown": "companysetting/set.md",
"parameters": [
{
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "string"
},
"frontend": {
"type": "boolean"
}
},
"example": {
"value": "dark",
"frontend": true
}
}
}
}
},
"responses": {
"200": {
"description": "Upserted company setting",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"setting": {
"$ref": "#/components/schemas/CompanySetting"
}
}
}
}
}
},
"400": {
"description": "Invalid setting, invalid key, or no company selected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Not authorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Company not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/company": {
"get": {
"tags": [
@@ -8013,6 +8464,22 @@
}
}
},
"CompanySetting": {
"type": "object",
"required": [
"key",
"value"
],
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string",
"nullable": true
}
}
},
"Category": {
"type": "object",
"properties": {
@@ -8119,6 +8586,36 @@
}
}
},
"NumberSequence": {
"type": "object",
"required": [
"id",
"type",
"prefix",
"padding",
"next"
],
"properties": {
"id": {
"type": "integer"
},
"type": {
"type": "string",
"description": "Sequence type (e.g. entity, article, delivery)"
},
"prefix": {
"type": "string"
},
"padding": {
"type": "integer",
"description": "Zero-padding length for the numeric part"
},
"next": {
"type": "integer",
"description": "Next value to allocate"
}
}
},
"Supplier": {
"type": "object",
"required": [

View File

@@ -0,0 +1,41 @@
# `GET /company/setting/{key}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get company setting by key
Returns a single company setting for the authenticated user's selected
company, or `404` if it does not exist.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `key` | yes | string | |
## Response `200`
Company setting
```json
{
"setting": {
"key": "…",
"value": "…"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Company setting or company not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,39 @@
# `GET /company/setting`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List company settings
Returns the visible settings of the authenticated user's selected
company: the full set of allowed keys (with `value: null` when unset)
plus any stored settings that are frontend-visible. The `frontend`
flag is stripped from responses.
## Response `200`
Company settings
```json
{
"settings": [
{
"key": "…",
"value": "…"
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Company not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,51 @@
# `PUT /company/setting/{key}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Upsert company setting
Sets (upserts) a company setting for the selected company.
Only a fixed allow-list of keys can be set: `ui.theme`, `keys.tavily`,
`keys.openrouter`, `labelprinter.addr`, `labelprinter.type`.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `key` | yes | string | |
## Body (JSON)
```json
{
"value": "dark",
"frontend": true
}
```
## Response `200`
Upserted company setting
```json
{
"setting": {
"key": "…",
"value": "…"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid setting, invalid key, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Company not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,38 @@
# `GET /number`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List number sequences
Lists the number sequences of the authenticated user's selected company.
## Response `200`
Number sequence list
```json
{
"numbers": [
{
"id": 1,
"type": "…",
"prefix": "…",
"padding": 1,
"next": 1
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Company not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,57 @@
# `PATCH /number/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Update number sequence
Updates sequence settings for the selected company. Only provided fields
are changed (`prefix`, `padding`, `next`). `prefix` is trimmed and must
be at most 50 characters. `padding` must be an integer `>= 0`. `next`
must be an integer `>= 1`. If no field is provided, the row is returned
unchanged.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Body (JSON)
```json
{
"prefix": "A-",
"padding": 4,
"next": 100
}
```
## Response `200`
Updated number sequence
```json
{
"number": {
"id": 1,
"type": "…",
"prefix": "…",
"padding": 1,
"next": 1
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid number, invalid id, or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Number sequence or company not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._