Compare commits

...

154 Commits

Author SHA1 Message Date
seb
2a45b43f50 u 2026-11-04 06:42:57 +01:00
seb
9f61c7dee2 u 2026-11-04 06:30:45 +01:00
seb
980578e1e4 u 2026-11-04 03:30:25 +01:00
seb
ce4dc15aac u 2026-11-04 03:22:20 +01:00
seb
da785da2dd u 2026-11-03 06:20:06 +01:00
seb
24563964a6 u 2026-11-03 04:13:13 +01:00
seb
44e91a6973 u 2026-11-03 03:54:49 +01:00
seb
de2957acf1 u 2026-11-02 00:51:15 +01:00
seb
17df88ced5 u 2026-11-02 00:19:19 +01:00
seb
d3209d73c1 u 2026-11-01 21:24:28 +01:00
seb
518106ede2 u 2026-11-01 21:16:07 +01:00
seb
833cee5040 u 2026-11-01 21:11:38 +01:00
seb
aeb79c1acb u 2026-11-01 06:20:44 +01:00
seb
ff185a59dd u 2026-11-01 06:16:29 +01:00
seb
689f12ba69 u 2026-11-01 05:57:35 +01:00
seb
bf081f875d u 2026-11-01 05:38:45 +01:00
seb
8f9c941699 u 2026-11-01 05:37:15 +01:00
seb
e6c15dd053 u 2026-11-01 05:01:21 +01:00
seb
8a5a307c50 u 2026-11-01 04:58:46 +01:00
seb
43d7049a7b u 2026-11-01 04:50:07 +01:00
seb
c60b899c4d u 2026-11-01 04:33:45 +01:00
seb
e8a5322775 u 2026-11-01 04:33:01 +01:00
seb
cb30352b57 u 2026-11-01 03:33:14 +01:00
seb
5d6b31de15 u 2026-11-01 00:38:04 +01:00
seb
8827d6fc14 u 2026-11-01 00:29:29 +01:00
seb
16f70603bc u 2026-11-01 00:24:43 +01:00
seb
ce7f5aad2a u 2026-11-01 00:20:26 +01:00
seb
26adfc5aa9 u 2026-10-31 22:14:05 +01:00
seb
0480a5dcbe u 2026-10-31 21:46:36 +01:00
seb
dd5c9646b3 u 2026-10-31 21:02:53 +01:00
seb
5a0b603306 u 2026-10-31 20:37:27 +01:00
seb
72d05a34d0 u 2026-10-31 20:28:37 +01:00
seb
8fece9d916 u 2026-10-30 18:09:31 +01:00
seb
5fac537268 u 2026-10-30 17:28:25 +01:00
seb
61529023b0 u 2026-10-30 16:56:27 +01:00
seb
2df7c59feb u 2026-10-30 16:48:39 +01:00
seb
02301ff172 u 2026-10-30 16:10:07 +01:00
seb
ab6fcc107f u 2026-10-30 12:00:06 +00:00
seb
bc1340419c u 2026-10-30 11:54:16 +00:00
seb
fbe3105dc9 u 2026-10-30 12:08:08 +01:00
seb
e82ea15d02 u 2026-10-30 11:54:04 +01:00
seb
527e55fec2 u 2026-10-30 11:49:18 +01:00
seb
e1d27b44a7 u 2026-10-30 07:00:33 +01:00
seb
a7258abf35 u 2026-10-30 06:43:45 +01:00
seb
1b432cb6c6 u 2026-10-30 05:50:46 +01:00
seb
0303f8121c u 2026-10-30 02:04:54 +01:00
seb
d151bb5fe3 u 2026-10-30 01:42:19 +01:00
seb
676a40d4fe u 2026-10-29 16:27:02 +01:00
seb
005b48ff66 u 2026-10-29 03:39:12 +01:00
seb
72e6e79d20 u 2026-10-29 03:29:06 +01:00
seb
6a80473594 u 2026-10-29 03:28:41 +01:00
seb
bcdfba1742 u 2026-10-29 03:28:02 +01:00
seb
907ee998c0 u 2026-10-29 02:55:49 +01:00
seb
158748c5e6 u 2026-10-28 23:09:50 +01:00
seb
dc9fcdad96 u 2026-10-28 22:31:29 +01:00
seb
116b51f0a9 u 2026-10-28 22:19:59 +01:00
seb
c937bad151 u 2026-10-28 21:11:38 +00:00
seb
1881830b0a u 2026-10-28 19:31:41 +01:00
seb
01d6aa0e3f u 2026-10-28 18:33:30 +01:00
seb
ff73750092 u 2026-10-28 01:51:54 +01:00
seb
e7884c03ef sharp by wasm 2026-10-28 01:46:51 +01:00
seb
1c5385061c u 2026-10-28 00:08:02 +01:00
seb
1ac6435c3f u 2026-10-27 17:18:08 +01:00
seb
80ecad6ddd u 2026-10-27 16:20:02 +01:00
seb
97cae698db u 2026-10-27 12:38:20 +01:00
seb
88ed2be452 u 2026-10-27 08:39:11 +01:00
seb
b3368a53f8 x 2026-10-27 06:48:30 +00:00
seb
e490ad78b5 u 2026-10-27 03:21:13 +00:00
seb
41c532e2ca u 2026-10-27 00:24:55 +01:00
seb
b6f68f04a4 u 2026-10-26 22:21:53 +00:00
seb
2cc166bbec u 2026-10-26 23:09:23 +01:00
seb
2dea4e86f1 u 2026-10-26 23:03:23 +01:00
seb
74d3ba5aeb u 2026-10-26 22:49:40 +01:00
seb
69388c789f u 2026-10-26 22:28:55 +01:00
seb
a5a9e51898 u 2026-10-26 21:21:08 +00:00
seb
5d6d07e8bf u 2026-10-26 01:01:05 +01:00
seb
fb2dafd920 u 2026-10-25 22:38:01 +01:00
seb
c07e4dc083 u 2026-10-25 12:13:29 +01:00
seb
369fbb2a70 u 2026-10-24 06:19:34 +02:00
seb
464021318a u 2026-10-24 05:13:13 +02:00
seb
9929e322a3 u 2026-10-24 02:57:32 +02:00
seb
ebbeb6437a u 2026-10-24 02:31:35 +02:00
seb
3f2bdce5ad u 2026-10-23 23:21:08 +02:00
seb
1a5e3f23e2 u 2026-10-23 22:22:30 +02:00
seb
e83116c83d u 2026-10-23 20:31:38 +02:00
seb
7fcaca318a u 2026-10-23 12:08:08 +02:00
seb
76235b0e1f u 2026-10-23 04:59:02 +02:00
seb
a6cb52e415 u 2026-10-23 01:56:11 +02:00
seb
36a135011f u 2026-10-22 14:39:46 +02:00
seb
a27f507138 u 2026-10-22 12:30:04 +02:00
seb
e83be2ca21 u 2026-10-21 12:05:50 +02:00
seb
1a0023b775 u 2026-10-21 12:03:30 +02:00
seb
1aa1582682 u 2026-10-21 11:59:39 +02:00
seb
be834412e7 u 2026-10-21 11:55:19 +02:00
seb
5be5644329 u 2026-10-21 06:52:49 +02:00
seb
ada4d93f65 u 2026-10-21 06:13:31 +02:00
seb
ee0eb2820e u 2026-10-21 04:28:58 +02:00
seb
e4e4d2dcf0 u 2026-10-21 03:31:55 +02:00
seb
63f4c3a0e4 u 2026-10-21 01:54:10 +02:00
seb
b7058df5d2 u 2026-10-20 23:21:48 +02:00
seb
5a5981dc07 u 2026-10-20 23:16:45 +02:00
seb
fde381937c u 2026-10-20 05:33:14 +02:00
seb
de3e74c928 u 2026-10-20 02:23:00 +02:00
seb
a04094a7be u 2026-10-19 23:57:00 +02:00
seb
3f540de768 u 2026-10-19 13:25:02 +02:00
seb
3450552f7c u 2026-10-19 04:05:07 +02:00
seb
6fbdeaa070 u 2026-10-19 02:28:17 +02:00
seb
1bf99deeb9 u 2026-10-19 02:03:44 +02:00
seb
e14a8ee8f0 u 2026-10-19 01:53:25 +02:00
seb
c958ecf604 u 2026-10-19 01:50:32 +02:00
seb
f571328e0c u 2026-10-18 21:24:12 +02:00
seb
a717a05e8d u 2026-10-18 20:48:50 +02:00
seb
c06a5e3413 u 2026-10-18 20:29:48 +02:00
seb
5e4cb5a3f8 u 2026-10-18 18:20:07 +02:00
seb
0ec993a0b6 u 2026-10-18 17:29:56 +02:00
seb
69a9781cb6 u 2026-10-18 16:24:37 +02:00
seb
7445a573ab u 2026-10-18 15:45:03 +02:00
seb
9c50d9efc3 u 2026-10-18 15:41:21 +02:00
seb
36d62138d4 u 2026-10-18 15:17:13 +02:00
seb
0463003681 u 2026-10-18 14:44:23 +02:00
seb
d0a2ede749 u 2026-10-18 14:29:01 +02:00
seb
badce6819c u 2026-10-18 14:20:37 +02:00
seb
832cfaca41 u 2026-10-18 13:24:28 +02:00
seb
a01b42b24b u 2026-10-18 03:25:03 +02:00
seb
04943fb7a6 u 2026-10-18 03:15:23 +02:00
seb
e085e064ee u 2026-10-17 23:59:28 +02:00
seb
df846f4f06 u 2026-10-17 23:31:40 +02:00
seb
4bd977ed12 u 2026-10-17 22:26:02 +02:00
seb
1778815a1e u 2026-10-17 22:03:24 +02:00
seb
856692b66c u 2026-10-17 21:37:36 +02:00
seb
5e9b168e0d u 2026-10-17 20:53:56 +02:00
seb
bee21d2c20 u 2026-10-17 19:51:34 +02:00
seb
7197a6ddb7 u 2026-10-17 05:10:25 +02:00
seb
988b4848cf u 2026-10-17 03:08:18 +02:00
seb
c61df92d63 u 2026-10-17 02:59:35 +02:00
seb
fd21cb7319 u 2026-10-17 02:36:24 +02:00
seb
da1254b0b1 u 2026-10-17 02:20:53 +02:00
seb
8e841d2c23 u 2026-10-16 22:41:36 +02:00
seb
0fe672a054 u 2026-10-16 22:35:24 +02:00
seb
d7f7d669f9 u 2026-10-16 22:09:15 +02:00
seb
188cd71f63 u 2026-10-16 18:11:28 +02:00
seb
de6c4987b9 u 2026-10-16 18:04:42 +02:00
seb
d05ba9276f u 2026-10-16 17:34:38 +02:00
seb
75925ab35c u 2026-10-16 17:32:31 +02:00
seb
52f49fb651 u 2026-10-16 17:23:59 +02:00
seb
2f0c4aced1 u 2026-10-16 17:11:09 +02:00
seb
99ac40e6fa u 2026-10-16 17:05:43 +02:00
seb
7f28a05ed3 u 2026-10-16 16:36:32 +02:00
seb
3b875c9f9a u 2026-10-16 16:24:58 +02:00
seb
f506dd35be u 2026-10-16 15:38:47 +02:00
seb
5ed634990e u 2026-10-16 15:22:13 +02:00
seb
bed19f8b10 u 2026-10-16 13:59:02 +02:00
seb
e083d66a75 u 2026-10-16 13:09:25 +02:00
seb
a560a78f4d u 2026-10-16 12:35:04 +02:00
742 changed files with 30560 additions and 8502 deletions

17
.dockerignore Normal file
View File

@@ -0,0 +1,17 @@
node_modules/*
!node_modules/better-sqlite3
!node_modules/canvas
!node_modules/@img
!node_modules/sharp
dist/*
!dist/dashback
data
certs
.git
.env
.env.*
!.env.example
*.log
docs/api
scratch

View File

@@ -1,4 +1,6 @@
# Server # Server
# Host interface to listen on (defaults to 127.0.0.1 for local only, use 0.0.0.0 for all network interfaces)
HOST=127.0.0.1
PORT=4208 PORT=4208
# HTTPS server port (defaults to 8443, set to 0 or false to disable) # HTTPS server port (defaults to 8443, set to 0 or false to disable)
HTTPS_PORT=8443 HTTPS_PORT=8443
@@ -15,6 +17,7 @@ CORS_ORIGINS=http://localhost:5173
# Auth — Google OAuth client ID and JWT signing secret # Auth — Google OAuth client ID and JWT signing secret
GOOGLE_CLIENT_ID= GOOGLE_CLIENT_ID=
JWT_SECRET=fallback_secret_please_change_in_production JWT_SECRET=fallback_secret_please_change_in_production
# Auto-create user on first login (when false, only allowlisted or invited emails can sign in / create an account)
IMPLICIT_CREATE=false IMPLICIT_CREATE=false
# Database Provider — 'sqlite' or 'mssql' (defaults to 'mssql', or 'sqlite' if SQLITE_PATH is set) # Database Provider — 'sqlite' or 'mssql' (defaults to 'mssql', or 'sqlite' if SQLITE_PATH is set)
@@ -51,4 +54,17 @@ OPENROUTER_PAID_MODEL=openai/gpt-6-luna:floor
# BACKUP_HOURLY_RETENTION=50 # BACKUP_HOURLY_RETENTION=50
# BACKUP_DAILY_RETENTION=50 # BACKUP_DAILY_RETENTION=50
# Redis (Healthcheck Latency Metric)
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_KEY=healthcheck:latency_ms
# REDIS_URL=redis://127.0.0.1:6379
# Health Monitor Service
# HEALTH_MONITOR_ENABLED=true
# HEALTH_MONITOR_INTERVAL_MS=60000

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ logs/
print_spool/ print_spool/
certs/ certs/
output/ output/
dist/

25
Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1
FROM debian:12-slim AS syslibs
RUN apt-get update && apt-get install -y --no-install-recommends \
libatomic1 libuuid1 \
&& rm -rf /var/lib/apt/lists/*
FROM gcr.io/distroless/cc-debian12
WORKDIR /app
# Shared dynamic libraries needed by Node.js SEA and native addons (better-sqlite3)
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libatomic.so* /usr/lib/x86_64-linux-gnu/
COPY --from=syslibs /usr/lib/x86_64-linux-gnu/libuuid.so* /usr/lib/x86_64-linux-gnu/
# Set production defaults: port 4208
ENV RUNNING_IN_DOCKER=true \
NODE_ENV=production \
HOST=0.0.0.0 \
PORT=4208 \
HTTPS_PORT=0 \
SQLITE_PATH=/app/data/dash.db \
STORAGE_DIR=/app/data/storage \
BACKUP_DIR=/app/data/backups
CMD ["./dist/dashback"]

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 330 KiB

View File

@@ -37,8 +37,18 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/account.get.md`](routes/account.get.md) | `GET /account` | | [`routes/account.get.md`](routes/account.get.md) | `GET /account` |
| [`routes/account.post.md`](routes/account.post.md) | `POST /account` | | [`routes/account.post.md`](routes/account.post.md) | `POST /account` |
| [`routes/account/_id.md`](routes/account/_id.md) | `DELETE /account/{id}` | | [`routes/account/_id.md`](routes/account/_id.md) | `DELETE /account/{id}` |
| [`routes/agent/events.md`](routes/agent/events.md) | `GET /agent/events` |
| [`routes/agent/pair.get.md`](routes/agent/pair.get.md) | `GET /agent/pair` |
| [`routes/agent/pair.post.md`](routes/agent/pair.post.md) | `POST /agent/pair` |
| [`routes/agent/state.md`](routes/agent/state.md) | `POST /agent/state` |
| [`routes/agentprinter.md`](routes/agentprinter.md) | `GET /agentprinter` |
| [`routes/agentprinter/order.md`](routes/agentprinter/order.md) | `PUT /agentprinter/order` |
| [`routes/agentprinter/_id.delete.md`](routes/agentprinter/_id.delete.md) | `DELETE /agentprinter/{id}` |
| [`routes/agentprinter/_id.get.md`](routes/agentprinter/_id.get.md) | `GET /agentprinter/{id}` |
| [`routes/agentprinter/_id.patch.md`](routes/agentprinter/_id.patch.md) | `PATCH /agentprinter/{id}` |
| [`routes/article/create.md`](routes/article/create.md) | `POST /article` | | [`routes/article/create.md`](routes/article/create.md) | `POST /article` |
| [`routes/article/export.zip.md`](routes/article/export.zip.md) | `GET /article/export.zip` | | [`routes/article/export.zip.md`](routes/article/export.zip.md) | `GET /article/export.zip` |
| [`routes/article/history-list.md`](routes/article/history-list.md) | `GET /article/history` |
| [`routes/article/delete.md`](routes/article/delete.md) | `DELETE /article/{sku}` | | [`routes/article/delete.md`](routes/article/delete.md) | `DELETE /article/{sku}` |
| [`routes/article/get.md`](routes/article/get.md) | `GET /article/{sku}` | | [`routes/article/get.md`](routes/article/get.md) | `GET /article/{sku}` |
| [`routes/article/update.md`](routes/article/update.md) | `PATCH /article/{sku}` | | [`routes/article/update.md`](routes/article/update.md) | `PATCH /article/{sku}` |
@@ -88,6 +98,9 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/auth/logout.md`](routes/auth/logout.md) | `POST /auth/logout` | | [`routes/auth/logout.md`](routes/auth/logout.md) | `POST /auth/logout` |
| [`routes/auth/me.md`](routes/auth/me.md) | `GET /auth/me` | | [`routes/auth/me.md`](routes/auth/me.md) | `GET /auth/me` |
| [`routes/auth/selected-company.md`](routes/auth/selected-company.md) | `PUT /auth/selected-company` | | [`routes/auth/selected-company.md`](routes/auth/selected-company.md) | `PUT /auth/selected-company` |
| [`routes/autoinvite/list.md`](routes/autoinvite/list.md) | `GET /autoinvite` |
| [`routes/autoinvite/create.md`](routes/autoinvite/create.md) | `POST /autoinvite` |
| [`routes/autoinvite/delete.md`](routes/autoinvite/delete.md) | `DELETE /autoinvite/{id}` |
| [`routes/backup.md`](routes/backup.md) | `GET /backup` | | [`routes/backup.md`](routes/backup.md) | `GET /backup` |
| [`routes/backup/_id/vfs.md`](routes/backup/_id/vfs.md) | `GET /backup/{id}/vfs` | | [`routes/backup/_id/vfs.md`](routes/backup/_id/vfs.md) | `GET /backup/{id}/vfs` |
| [`routes/backup/_id/vfs/recover.md`](routes/backup/_id/vfs/recover.md) | `POST /backup/{id}/vfs/recover` | | [`routes/backup/_id/vfs/recover.md`](routes/backup/_id/vfs/recover.md) | `POST /backup/{id}/vfs/recover` |
@@ -97,6 +110,12 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/brand/update.md`](routes/brand/update.md) | `PATCH /brand/{id}` | | [`routes/brand/update.md`](routes/brand/update.md) | `PATCH /brand/{id}` |
| [`routes/brand/_id/image.delete.md`](routes/brand/_id/image.delete.md) | `DELETE /brand/{id}/image` | | [`routes/brand/_id/image.delete.md`](routes/brand/_id/image.delete.md) | `DELETE /brand/{id}/image` |
| [`routes/brand/_id/image.post.md`](routes/brand/_id/image.post.md) | `POST /brand/{id}/image` | | [`routes/brand/_id/image.post.md`](routes/brand/_id/image.post.md) | `POST /brand/{id}/image` |
| [`routes/carrier.get.md`](routes/carrier.get.md) | `GET /carrier` |
| [`routes/carrier.post.md`](routes/carrier.post.md) | `POST /carrier` |
| [`routes/carrier/order.md`](routes/carrier/order.md) | `PUT /carrier/order` |
| [`routes/carrier/_id.delete.md`](routes/carrier/_id.delete.md) | `DELETE /carrier/{id}` |
| [`routes/carrier/_id.get.md`](routes/carrier/_id.get.md) | `GET /carrier/{id}` |
| [`routes/carrier/_id.patch.md`](routes/carrier/_id.patch.md) | `PATCH /carrier/{id}` |
| [`routes/category/create.md`](routes/category/create.md) | `POST /category` | | [`routes/category/create.md`](routes/category/create.md) | `POST /category` |
| [`routes/category/export.zip.md`](routes/category/export.zip.md) | `GET /category/export.zip` | | [`routes/category/export.zip.md`](routes/category/export.zip.md) | `GET /category/export.zip` |
| [`routes/category/order.md`](routes/category/order.md) | `PUT /category/order` | | [`routes/category/order.md`](routes/category/order.md) | `PUT /category/order` |
@@ -108,6 +127,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/category/_id/attributevalue.put.md`](routes/category/_id/attributevalue.put.md) | `PUT /category/{id}/attributevalue` | | [`routes/category/_id/attributevalue.put.md`](routes/category/_id/attributevalue.put.md) | `PUT /category/{id}/attributevalue` |
| [`routes/category/delete-image.md`](routes/category/delete-image.md) | `DELETE /category/{id}/image` | | [`routes/category/delete-image.md`](routes/category/delete-image.md) | `DELETE /category/{id}/image` |
| [`routes/category/add-image.md`](routes/category/add-image.md) | `POST /category/{id}/image` | | [`routes/category/add-image.md`](routes/category/add-image.md) | `POST /category/{id}/image` |
| [`routes/category/_id/image/generate.md`](routes/category/_id/image/generate.md) | `POST /category/{id}/image/generate` |
| [`routes/category/_id/property.get.md`](routes/category/_id/property.get.md) | `GET /category/{id}/property` | | [`routes/category/_id/property.get.md`](routes/category/_id/property.get.md) | `GET /category/{id}/property` |
| [`routes/category/_id/property.put.md`](routes/category/_id/property.put.md) | `PUT /category/{id}/property` | | [`routes/category/_id/property.put.md`](routes/category/_id/property.put.md) | `PUT /category/{id}/property` |
| [`routes/category/_id/property/_propertyId.md`](routes/category/_id/property/_propertyId.md) | `DELETE /category/{id}/property/{propertyId}` | | [`routes/category/_id/property/_propertyId.md`](routes/category/_id/property/_propertyId.md) | `DELETE /category/{id}/property/{propertyId}` |
@@ -123,6 +143,10 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/companysetting/set.md`](routes/companysetting/set.md) | `PUT /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/delete.md`](routes/company/delete.md) | `DELETE /company/{id}` |
| [`routes/company/update.md`](routes/company/update.md) | `PUT /company/{id}` | | [`routes/company/update.md`](routes/company/update.md) | `PUT /company/{id}` |
| [`routes/companyuser/list.md`](routes/companyuser/list.md) | `GET /companyuser` |
| [`routes/companyuser/create.md`](routes/companyuser/create.md) | `POST /companyuser` |
| [`routes/companyuser/delete.md`](routes/companyuser/delete.md) | `DELETE /companyuser/{id}` |
| [`routes/companyuser/update.md`](routes/companyuser/update.md) | `PATCH /companyuser/{id}` |
| [`routes/customergroup/list.md`](routes/customergroup/list.md) | `GET /customergroup` | | [`routes/customergroup/list.md`](routes/customergroup/list.md) | `GET /customergroup` |
| [`routes/customergroup/create.md`](routes/customergroup/create.md) | `POST /customergroup` | | [`routes/customergroup/create.md`](routes/customergroup/create.md) | `POST /customergroup` |
| [`routes/customergroup/order.md`](routes/customergroup/order.md) | `PUT /customergroup/order` | | [`routes/customergroup/order.md`](routes/customergroup/order.md) | `PUT /customergroup/order` |
@@ -130,7 +154,6 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/customergroup/update.md`](routes/customergroup/update.md) | `PATCH /customergroup/{id}` | | [`routes/customergroup/update.md`](routes/customergroup/update.md) | `PATCH /customergroup/{id}` |
| [`routes/delivery/list.md`](routes/delivery/list.md) | `GET /delivery` | | [`routes/delivery/list.md`](routes/delivery/list.md) | `GET /delivery` |
| [`routes/delivery/create.md`](routes/delivery/create.md) | `POST /delivery` | | [`routes/delivery/create.md`](routes/delivery/create.md) | `POST /delivery` |
| [`routes/delivery/parse.md`](routes/delivery/parse.md) | `POST /delivery/parse` |
| [`routes/delivery/delete.md`](routes/delivery/delete.md) | `DELETE /delivery/{id}` | | [`routes/delivery/delete.md`](routes/delivery/delete.md) | `DELETE /delivery/{id}` |
| [`routes/delivery/get.md`](routes/delivery/get.md) | `GET /delivery/{id}` | | [`routes/delivery/get.md`](routes/delivery/get.md) | `GET /delivery/{id}` |
| [`routes/delivery/update.md`](routes/delivery/update.md) | `PATCH /delivery/{id}` | | [`routes/delivery/update.md`](routes/delivery/update.md) | `PATCH /delivery/{id}` |
@@ -153,15 +176,17 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/entity/update.md`](routes/entity/update.md) | `PATCH /entity/{id}` | | [`routes/entity/update.md`](routes/entity/update.md) | `PATCH /entity/{id}` |
| [`routes/events.md`](routes/events.md) | `GET /events` | | [`routes/events.md`](routes/events.md) | `GET /events` |
| [`routes/health.md`](routes/health.md) | `GET /health` | | [`routes/health.md`](routes/health.md) | `GET /health` |
| [`routes/health/latency.md`](routes/health/latency.md) | `GET /health/latency` |
| [`routes/image/get.md`](routes/image/get.md) | `GET /image/{fullHash}` | | [`routes/image/get.md`](routes/image/get.md) | `GET /image/{fullHash}` |
| [`routes/labeltemplate/list.md`](routes/labeltemplate/list.md) | `GET /labeltemplate` | | [`routes/labeltemplate/list.md`](routes/labeltemplate/list.md) | `GET /labeltemplate` |
| [`routes/labeltemplate/create.md`](routes/labeltemplate/create.md) | `POST /labeltemplate` | | [`routes/labeltemplate/create.md`](routes/labeltemplate/create.md) | `POST /labeltemplate` |
| [`routes/labeltemplate/testprint.md`](routes/labeltemplate/testprint.md) | `POST /labeltemplate/testprint` |
| [`routes/labeltemplate/delete.md`](routes/labeltemplate/delete.md) | `DELETE /labeltemplate/{id}` | | [`routes/labeltemplate/delete.md`](routes/labeltemplate/delete.md) | `DELETE /labeltemplate/{id}` |
| [`routes/labeltemplate/get.md`](routes/labeltemplate/get.md) | `GET /labeltemplate/{id}` | | [`routes/labeltemplate/get.md`](routes/labeltemplate/get.md) | `GET /labeltemplate/{id}` |
| [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PATCH /labeltemplate/{id}` | | [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PATCH /labeltemplate/{id}` |
| [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PUT /labeltemplate/{id}` | | [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PUT /labeltemplate/{id}` |
| [`routes/ledger.get.md`](routes/ledger.get.md) | `GET /ledger` | | [`routes/ledger.md`](routes/ledger.md) | `GET /ledger` |
| [`routes/ledger.post.md`](routes/ledger.post.md) | `POST /ledger` | | [`routes/ledger/history.md`](routes/ledger/history.md) | `GET /ledger/history` |
| [`routes/ledger/manifestarticle/_manifestarticleId.md`](routes/ledger/manifestarticle/_manifestarticleId.md) | `GET /ledger/manifestarticle/{manifestarticleId}` | | [`routes/ledger/manifestarticle/_manifestarticleId.md`](routes/ledger/manifestarticle/_manifestarticleId.md) | `GET /ledger/manifestarticle/{manifestarticleId}` |
| [`routes/ledger/sku/_sku.md`](routes/ledger/sku/_sku.md) | `GET /ledger/sku/{sku}` | | [`routes/ledger/sku/_sku.md`](routes/ledger/sku/_sku.md) | `GET /ledger/sku/{sku}` |
| [`routes/mcp.md`](routes/mcp.md) | `POST /mcp` | | [`routes/mcp.md`](routes/mcp.md) | `POST /mcp` |
@@ -211,10 +236,22 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/printer/list.md`](routes/printer/list.md) | `GET /printer` | | [`routes/printer/list.md`](routes/printer/list.md) | `GET /printer` |
| [`routes/printer/create.md`](routes/printer/create.md) | `POST /printer` | | [`routes/printer/create.md`](routes/printer/create.md) | `POST /printer` |
| [`routes/printer/order.md`](routes/printer/order.md) | `PUT /printer/order` | | [`routes/printer/order.md`](routes/printer/order.md) | `PUT /printer/order` |
| [`routes/printer/state.md`](routes/printer/state.md) | `GET /printer/state` |
| [`routes/printer/delete.md`](routes/printer/delete.md) | `DELETE /printer/{id}` | | [`routes/printer/delete.md`](routes/printer/delete.md) | `DELETE /printer/{id}` |
| [`routes/printer/get.md`](routes/printer/get.md) | `GET /printer/{id}` | | [`routes/printer/get.md`](routes/printer/get.md) | `GET /printer/{id}` |
| [`routes/printer/update.md`](routes/printer/update.md) | `PATCH /printer/{id}` | | [`routes/printer/update.md`](routes/printer/update.md) | `PATCH /printer/{id}` |
| [`routes/printer/_id.put.md`](routes/printer/_id.put.md) | `PUT /printer/{id}` | | [`routes/printer/_id.put.md`](routes/printer/_id.put.md) | `PUT /printer/{id}` |
| [`routes/printer/_id/state.md`](routes/printer/_id/state.md) | `GET /printer/{id}/state` |
| [`routes/printeragent.get.md`](routes/printeragent.get.md) | `GET /printeragent` |
| [`routes/printeragent.put.md`](routes/printeragent.put.md) | `PUT /printeragent` |
| [`routes/printeragent/_id.delete.md`](routes/printeragent/_id.delete.md) | `DELETE /printeragent/{id}` |
| [`routes/printeragent/_id.patch.md`](routes/printeragent/_id.patch.md) | `PATCH /printeragent/{id}` |
| [`routes/printerjob.get.md`](routes/printerjob.get.md) | `GET /printerjob` |
| [`routes/printerjob.post.md`](routes/printerjob.post.md) | `POST /printerjob` |
| [`routes/printerjob/cancel-all.md`](routes/printerjob/cancel-all.md) | `POST /printerjob/cancel-all` |
| [`routes/printerjob/_id.delete.md`](routes/printerjob/_id.delete.md) | `DELETE /printerjob/{id}` |
| [`routes/printerjob/_id.get.md`](routes/printerjob/_id.get.md) | `GET /printerjob/{id}` |
| [`routes/printerjob/_id/cancel.md`](routes/printerjob/_id/cancel.md) | `POST /printerjob/{id}/cancel` |
| [`routes/propertyname.get.md`](routes/propertyname.get.md) | `GET /propertyname` | | [`routes/propertyname.get.md`](routes/propertyname.get.md) | `GET /propertyname` |
| [`routes/propertyname.post.md`](routes/propertyname.post.md) | `POST /propertyname` | | [`routes/propertyname.post.md`](routes/propertyname.post.md) | `POST /propertyname` |
| [`routes/propertyname/order.md`](routes/propertyname/order.md) | `PUT /propertyname/order` | | [`routes/propertyname/order.md`](routes/propertyname/order.md) | `PUT /propertyname/order` |
@@ -233,6 +270,20 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/quote/_id/file/_fileId.get.md`](routes/quote/_id/file/_fileId.get.md) | `GET /quote/{id}/file/{fileId}` | | [`routes/quote/_id/file/_fileId.get.md`](routes/quote/_id/file/_fileId.get.md) | `GET /quote/{id}/file/{fileId}` |
| [`routes/quote/order.md`](routes/quote/order.md) | `POST /quote/{id}/order` | | [`routes/quote/order.md`](routes/quote/order.md) | `POST /quote/{id}/order` |
| [`routes/quote/_id/pdf.md`](routes/quote/_id/pdf.md) | `POST /quote/{id}/pdf` | | [`routes/quote/_id/pdf.md`](routes/quote/_id/pdf.md) | `POST /quote/{id}/pdf` |
| [`routes/retoure.get.md`](routes/retoure.get.md) | `GET /retoure` |
| [`routes/retoure.post.md`](routes/retoure.post.md) | `POST /retoure` |
| [`routes/retoure/rebuild-summaries.md`](routes/retoure/rebuild-summaries.md) | `POST /retoure/rebuild-summaries` |
| [`routes/retoure/_id.delete.md`](routes/retoure/_id.delete.md) | `DELETE /retoure/{id}` |
| [`routes/retoure/_id.get.md`](routes/retoure/_id.get.md) | `GET /retoure/{id}` |
| [`routes/retoure/_id.put.md`](routes/retoure/_id.put.md) | `PUT /retoure/{id}` |
| [`routes/retoure/_id/shipment.md`](routes/retoure/_id/shipment.md) | `POST /retoure/{id}/shipment` |
| [`routes/return.get.md`](routes/return.get.md) | `GET /return` |
| [`routes/return.post.md`](routes/return.post.md) | `POST /return` |
| [`routes/return/rebuild-summaries.md`](routes/return/rebuild-summaries.md) | `POST /return/rebuild-summaries` |
| [`routes/return/_id.delete.md`](routes/return/_id.delete.md) | `DELETE /return/{id}` |
| [`routes/return/_id.get.md`](routes/return/_id.get.md) | `GET /return/{id}` |
| [`routes/return/_id.put.md`](routes/return/_id.put.md) | `PUT /return/{id}` |
| [`routes/return/_id/book.md`](routes/return/_id/book.md) | `POST /return/{id}/book` |
| [`routes/serper/image.md`](routes/serper/image.md) | `POST /serper/image` | | [`routes/serper/image.md`](routes/serper/image.md) | `POST /serper/image` |
| [`routes/serper/images.md`](routes/serper/images.md) | `POST /serper/images` | | [`routes/serper/images.md`](routes/serper/images.md) | `POST /serper/images` |
| [`routes/shipment.md`](routes/shipment.md) | `GET /shipment` | | [`routes/shipment.md`](routes/shipment.md) | `GET /shipment` |
@@ -249,7 +300,14 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/shipping/_id/package.get.md`](routes/shipping/_id/package.get.md) | `GET /shipping/{id}/package` | | [`routes/shipping/_id/package.get.md`](routes/shipping/_id/package.get.md) | `GET /shipping/{id}/package` |
| [`routes/shipping/_id/package.post.md`](routes/shipping/_id/package.post.md) | `POST /shipping/{id}/package` | | [`routes/shipping/_id/package.post.md`](routes/shipping/_id/package.post.md) | `POST /shipping/{id}/package` |
| [`routes/smallthumb/get.md`](routes/smallthumb/get.md) | `GET /smallthumb/{fullHash}` | | [`routes/smallthumb/get.md`](routes/smallthumb/get.md) | `GET /smallthumb/{fullHash}` |
| [`routes/stock/accept.md`](routes/stock/accept.md) | `POST /stock/accept` |
| [`routes/stock/adjust.md`](routes/stock/adjust.md) | `POST /stock/adjust` | | [`routes/stock/adjust.md`](routes/stock/adjust.md) | `POST /stock/adjust` |
| [`routes/stock/ship.md`](routes/stock/ship.md) | `POST /stock/ship` |
| [`routes/subagentschedule.get.md`](routes/subagentschedule.get.md) | `GET /subagentschedule` |
| [`routes/subagentschedule.post.md`](routes/subagentschedule.post.md) | `POST /subagentschedule` |
| [`routes/subagentschedule/_id.delete.md`](routes/subagentschedule/_id.delete.md) | `DELETE /subagentschedule/{id}` |
| [`routes/subagentschedule/_id.get.md`](routes/subagentschedule/_id.get.md) | `GET /subagentschedule/{id}` |
| [`routes/subagentschedule/_id.put.md`](routes/subagentschedule/_id.put.md) | `PUT /subagentschedule/{id}` |
| [`routes/supplier/list.md`](routes/supplier/list.md) | `GET /supplier` | | [`routes/supplier/list.md`](routes/supplier/list.md) | `GET /supplier` |
| [`routes/supplier/create.md`](routes/supplier/create.md) | `POST /supplier` | | [`routes/supplier/create.md`](routes/supplier/create.md) | `POST /supplier` |
| [`routes/supplier/delete.md`](routes/supplier/delete.md) | `DELETE /supplier/{id}` | | [`routes/supplier/delete.md`](routes/supplier/delete.md) | `DELETE /supplier/{id}` |
@@ -261,14 +319,20 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/unit/list.md`](routes/unit/list.md) | `GET /unit` | | [`routes/unit/list.md`](routes/unit/list.md) | `GET /unit` |
| [`routes/unit/upsert.md`](routes/unit/upsert.md) | `PUT /unit` | | [`routes/unit/upsert.md`](routes/unit/upsert.md) | `PUT /unit` |
| [`routes/v1/category.md`](routes/v1/category.md) | `GET /v1/category` | | [`routes/v1/category.md`](routes/v1/category.md) | `GET /v1/category` |
| [`routes/pos/cimage.md`](routes/pos/cimage.md) | `GET /v1/cimage` |
| [`routes/pos/client.md`](routes/pos/client.md) | `GET /v1/client` | | [`routes/pos/client.md`](routes/pos/client.md) | `GET /v1/client` |
| [`routes/v1/customer.md`](routes/v1/customer.md) | `GET /v1/customer` |
| [`routes/v1/customergroup.md`](routes/v1/customergroup.md) | `GET /v1/customergroup` |
| [`routes/v1/deletedentity.md`](routes/v1/deletedentity.md) | `GET /v1/deletedentity` |
| [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` | | [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` |
| [`routes/v1/order.md`](routes/v1/order.md) | `POST /v1/order` |
| [`routes/pos/pimage.md`](routes/pos/pimage.md) | `GET /v1/pimage` | | [`routes/pos/pimage.md`](routes/pos/pimage.md) | `GET /v1/pimage` |
| [`routes/v1/product.md`](routes/v1/product.md) | `GET /v1/product` | | [`routes/v1/product.md`](routes/v1/product.md) | `GET /v1/product` |
| [`routes/vfs/list.md`](routes/vfs/list.md) | `GET /vfs` | | [`routes/vfs/list.md`](routes/vfs/list.md) | `GET /vfs` |
| [`routes/vfs/drafts/response.md`](routes/vfs/drafts/response.md) | `POST /vfs/drafts/response` | | [`routes/vfs/applets-restore.md`](routes/vfs/applets-restore.md) | `POST /vfs/applets/restore` |
| [`routes/vfs/mkdir.md`](routes/vfs/mkdir.md) | `POST /vfs/mkdir` | | [`routes/vfs/mkdir.md`](routes/vfs/mkdir.md) | `POST /vfs/mkdir` |
| [`routes/vfs/rename.md`](routes/vfs/rename.md) | `POST /vfs/rename` | | [`routes/vfs/rename.md`](routes/vfs/rename.md) | `POST /vfs/rename` |
| [`routes/vfs/skills-restore.md`](routes/vfs/skills-restore.md) | `POST /vfs/skills/restore` |
| [`routes/vfs/upload.md`](routes/vfs/upload.md) | `POST /vfs/upload` | | [`routes/vfs/upload.md`](routes/vfs/upload.md) | `POST /vfs/upload` |
| [`routes/vfs/delete.md`](routes/vfs/delete.md) | `DELETE /vfs/{*path}` | | [`routes/vfs/delete.md`](routes/vfs/delete.md) | `DELETE /vfs/{*path}` |
| [`routes/vfs/_*path.md`](routes/vfs/_*path.md) | `GET /vfs/{*path}` | | [`routes/vfs/_*path.md`](routes/vfs/_*path.md) | `GET /vfs/{*path}` |
@@ -281,6 +345,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/warehouse/get.md`](routes/warehouse/get.md) | `GET /warehouse/{id}` | | [`routes/warehouse/get.md`](routes/warehouse/get.md) | `GET /warehouse/{id}` |
| [`routes/warehouse/update.md`](routes/warehouse/update.md) | `PATCH /warehouse/{id}` | | [`routes/warehouse/update.md`](routes/warehouse/update.md) | `PATCH /warehouse/{id}` |
| [`routes/warehouse/_id.put.md`](routes/warehouse/_id.put.md) | `PUT /warehouse/{id}` | | [`routes/warehouse/_id.put.md`](routes/warehouse/_id.put.md) | `PUT /warehouse/{id}` |
| [`routes/zpljob.md`](routes/zpljob.md) | `POST /zpljob` |
Route overview: [`routes.md`](routes.md) | All routes: [`routes-all.md`](routes-all.md). Route overview: [`routes.md`](routes.md) | All routes: [`routes-all.md`](routes-all.md).
@@ -290,23 +355,32 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
| Signal | Emitted by | | Signal | Emitted by |
|--------|------------| |--------|------------|
| `agentprinter` | `PUT /agentprinter/order`, `DELETE /agentprinter/{id}`, `PATCH /agentprinter/{id}` |
| `article-<sku>` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `POST /article/{sku}/supplier`, `PUT /article/{sku}/supplier/order`, `DELETE /article/{sku}/supplier/{id}`, `PATCH /article/{sku}/supplier/{id}`, `PUT /article/{sku}/track_stock`, `PUT /article/{sku}/weight`, `POST /stock/adjust` | | `article-<sku>` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `POST /article/{sku}/supplier`, `PUT /article/{sku}/supplier/order`, `DELETE /article/{sku}/supplier/{id}`, `PATCH /article/{sku}/supplier/{id}`, `PUT /article/{sku}/track_stock`, `PUT /article/{sku}/weight`, `POST /stock/adjust` |
| `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}`, `DELETE /category/{id}`, `POST /stock/adjust` | | `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}`, `DELETE /category/{id}`, `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
| `attributename` | `POST /attributename`, `PUT /attributename/order`, `DELETE /attributename/{id}`, `PATCH /attributename/{id}`, `DELETE /attributevalue/{id}/image`, `POST /attributevalue/{id}/image` | | `attributename` | `POST /attributename`, `PUT /attributename/order`, `DELETE /attributename/{id}`, `PATCH /attributename/{id}`, `DELETE /attributevalue/{id}/image`, `POST /attributevalue/{id}/image` |
| `autoinvite` | `POST /autoinvite`, `DELETE /autoinvite/{id}` |
| `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` | | `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` |
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` | | `carrier` | `POST /carrier`, `PUT /carrier/order`, `DELETE /carrier/{id}`, `PATCH /carrier/{id}` |
| `delivery` | `DELETE /delivery/{id}/file/{fileId}`, `POST /delivery/{id}/pdf` | | `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image`, `POST /category/{id}/image/generate` |
| `company` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}`, `PUT /company/{id}` |
| `companyuser` | `POST /companyuser`, `DELETE /companyuser/{id}`, `PATCH /companyuser/{id}` |
| `delivery` | `DELETE /delivery/{id}/file/{fileId}`, `POST /delivery/{id}/pdf`, `POST /stock/accept` |
| `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` | | `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` |
| `ledger` | `POST /stock/adjust` | | `ledger` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
| `modelconversation` | `POST /modelconversation`, `DELETE /modelconversation/{id}` | | `modelconversation` | `POST /modelconversation`, `DELETE /modelconversation/{id}` |
| `modelconversation-<id>` | `POST /modelconversation/{id}/message` | | `modelconversation-<id>` | `POST /modelconversation/{id}/message` |
| `order` | `DELETE /order/{id}/file/{fileId}`, `POST /order/{id}/pdf`, `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}` | | `order` | `DELETE /order/{id}/file/{fileId}`, `POST /order/{id}/pdf`, `DELETE /shipment/{id}`, `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}` |
| `printer` | `POST /printer`, `PUT /printer/order`, `DELETE /printer/{id}`, `PATCH /printer/{id}`, `PUT /printer/{id}` | | `printer` | `POST /printer`, `PUT /printer/order`, `DELETE /printer/{id}`, `PATCH /printer/{id}`, `PUT /printer/{id}` |
| `propertyname` | `POST /propertyname`, `PUT /propertyname/order`, `DELETE /propertyname/{id}`, `PATCH /propertyname/{id}` | | `propertyname` | `POST /propertyname`, `PUT /propertyname/order`, `DELETE /propertyname/{id}`, `PATCH /propertyname/{id}` |
| `quote` | `DELETE /quote/{id}/file/{fileId}`, `POST /quote/{id}/pdf` | | `quote` | `DELETE /quote/{id}/file/{fileId}`, `POST /quote/{id}/pdf` |
| `shipment` | `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}`, `POST /shipment/{id}/pdf` | | `shipment` | `DELETE /shipment/{id}`, `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}`, `POST /shipment/{id}/pdf` |
| `stock` | `POST /stock/adjust` | | `shipping` | `POST /stock/ship` |
| `vfs` | `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` | | `stock` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
| `subagentschedule` | `POST /subagentschedule`, `DELETE /subagentschedule/{id}`, `PUT /subagentschedule/{id}` |
| `supplier` | `POST /supplier`, `DELETE /supplier/{id}`, `PATCH /supplier/{id}` |
| `user` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}` |
| `vfs` | `POST /vfs/applets/restore`, `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/skills/restore`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` |
| `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` | | `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` |
## DESIGN ## DESIGN

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview # Dash API Routes Overview
Overview of all 174 API endpoints across all modules. Overview of all 196 API endpoints across all modules.
### Notation ### Notation
@@ -9,53 +9,33 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
### Reusable types ### Reusable types
- `T1` = `{ order: T2 }` - `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T11[], externalNumber?: str|null, createdAt?: str, shipping?: T13|null, shippings?: T13[], payment?: T21|null, payments?: T21[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T7[] }`
- `T2` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T24[], externalNumber?: str|null, createdAt?: str, shipping?: T32|null, shippings?: T32[], payment?: T38|null, payments?: T38[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T6[], files?: T14[] }` - `T2` = `{ id: int, number: str, type: str, date: str, lines: T11[], shippings: T13[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T7[], state?: str }`
- `T3` = `{ article: T4 }` - `T3` = `{ id: int, number: str, type: str, date: str, lines: T11[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T7[] }`
- `T4` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T35, brand: T33|null, categoryIds: int[], photos: T30[], eans: str[], prices: T40[], suppliers: T15[] }` - `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T14[], files: T7[], externalNumber?: str|null, createdAt?: str, ledger?: T26 }`
- `T5` = `{ shipment: T6 }` - `T5` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T27|null, incoming?: { date: str, amount: num }[] }`
- `T6` = `{ id: int, number: str, type: str, date: str, lines: T24[], shippings: T32[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T14[], state?: str }` - `T6` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T15[] }, thumb?: str|null }`
- `T7` = `{ quote: T8 }` - `T7` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }`
- `T8` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T24[], externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T14[] }` - `T8` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }`
- `T9` = `{ delivery: T10 }` - `T9` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }`
- `T10` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T24[], files: T14[], externalNumber?: str|null, createdAt?: str, ledger?: T44 }` - `T10` = `{ id: int, name: str, sort: int, exclusive: bool, values: T18[], articleCount?: int }`
- `T11` = `{ labeltemplate: T13 }` - `T11` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
- `T12` = `{ attachment: T14, fileUrl: str }` - `T12` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
- `T13` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: T20, thumb?: str|null }` - `T13` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
- `T14` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }` - `T14` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }`
- `T15` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T46|null }` - `T15` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }`
- `T16` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` - `T16` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
- `T17` = `{ ledger: T18[] }` - `T17` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
- `T18` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }` - `T18` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
- `T19` = `{ id: int, name: str, sort: int, exclusive: bool, values: T36[], articleCount?: int }` - `T19` = `{ id: int, googleAuthId: int, email: str, role: str, picture?: str|null }`
- `T20` = `{ objects: T25[] }` - `T20` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
- `T21` = `{ objects: T26[] }` - `T21` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
- `T22` = `{ printer: T28 }` - `T22` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
- `T23` = `{ entity: T31 }` - `T23` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
- `T24` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }` - `T24` = `{ id: int, entityId: int, contactType: str, contact: str }`
- `T25` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` - `T25` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
- `T26` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` - `T26` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
- `T27` = `{ shipping: T32 }` - `T27` = `{ net: num, taxId: int, taxRate?: num|null }`
- `T28` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }`
- `T29` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }`
- `T30` = `{ sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }`
- `T31` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
- `T32` = `{ id: int, manifestId: int, number: str, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
- `T33` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }`
- `T34` = `{ article: T39 }`
- `T35` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
- `T36` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
- `T37` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
- `T38` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
- `T39` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }`
- `T40` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }`
- `T41` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
- `T42` = `{ id: int, entityId: int, contactType: str, contact: str }`
- `T43` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
- `T44` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
- `T45` = `{ warehouse: T47 }`
- `T46` = `{ net: num, taxId: int, taxRate?: num|null }`
- `T47` = `{ id: int, name: str, sort: int }`
### Routes ### Routes
@@ -64,16 +44,19 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
#### Create article in category — `POST /article` #### Create article in category — `POST /article`
- Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }` - Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }`
- Returns: `201 T34` - Returns: `201 { sku: str }`
#### Query company-wide article history — `GET /article/history`
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
- Returns: `200 { history: { id: int, date: str, sku: str, historytext: str, name?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
#### Delete article by SKU — `DELETE /article/{sku}` #### Delete article by SKU — `DELETE /article/{sku}`
- Params: `sku: str, query: cascade?: bool` - Params: `sku: str, query: cascade?: bool`
- Returns: `204 void` - Returns: `204 void`
#### Get article by SKU — `GET /article/{sku}` #### Get article by SKU — `GET /article/{sku}`
- Params: `sku: str` - Params: `sku: str`
- Returns: `200 T3` - Returns: `200 { article: { id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T17, brand: { name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }, categoryIds: int[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], prices: { minmultiplier: num, net: num, taxRate: num, customergroupId: int }[], suppliers: T5[], undeliveredOrders: { orderId: int, orderNumber: str, date: str, amount: num, customerName?: str|null }[] } }`
#### Update article by SKU — `PATCH /article/{sku}` #### Update article by SKU — `PATCH /article/{sku}`
- Params: `sku: str, body: { name?: str, desc?: str, seo?: str, unitamount?: num, unitSi?: str, track_stock?: bool }` - Params: `sku: str, body: { name?: str, desc?: str, seo?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
- Returns: `200 T3` - Returns: `204 void`
#### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist` #### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist`
- Params: `sku: str` - Params: `sku: str`
- Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }` - Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
@@ -88,28 +71,28 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, articleId?: int, exclusive?: bool }[] }` - Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, articleId?: int, exclusive?: bool }[] }`
#### Set or clear article brand by SKU (null to clear) — `PUT /article/{sku}/brand` #### Set or clear article brand by SKU (null to clear) — `PUT /article/{sku}/brand`
- Params: `sku: str, body: { brandId: int|null }` - Params: `sku: str, body: { brandId: int|null }`
- Returns: `200 T3` - Returns: `204 void`
#### Replace article categories by SKU — `PUT /article/{sku}/categories` #### Replace article categories by SKU — `PUT /article/{sku}/categories`
- Params: `sku: str, body: { categoryIds: int[] }` - Params: `sku: str, body: { categoryIds: int[] }`
- Returns: `200 T3` - Returns: `204 void`
#### List child articles of a parent article — `GET /article/{sku}/children` #### List child articles of a parent article — `GET /article/{sku}/children`
- Params: `sku: str` - Params: `sku: str`
- Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` - Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }`
#### Create and attach a new child article to a parent article — `POST /article/{sku}/children` #### Create and attach a new child article to a parent article — `POST /article/{sku}/children`
- Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }` - Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }`
- Returns: `201 T34` - Returns: `201 { sku: str }`
#### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children`
- Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }`
- Returns: `200 { article: T39, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` - Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }`
#### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}`
- Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Params: `sku: str, childSku: str, query: deleteArticle?: bool`
- Returns: `200 { success: bool, deleted: bool }` - Returns: `200 { success: bool, deleted: bool }`
#### Replace article EANs by SKU — `PUT /article/{sku}/eans` #### Replace article EANs by SKU — `PUT /article/{sku}/eans`
- Params: `sku: str, body: { eans: str[] }` - Params: `sku: str, body: { eans: str[] }`
- Returns: `200 T3` - Returns: `204 void`
#### Get article history by SKU — `GET /article/{sku}/history` #### Get article history by SKU — `GET /article/{sku}/history`
- Params: `sku: str, query: limit?: int` - Params: `sku: str, query: offset?: int, limit?: int, search?: str, seekMonths?: int`
- Returns: `200 { history: { id: int, date: str, text: str }[], truncated: bool }` - Returns: `200 { history: { id: int, date: str, text: str }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
#### Delete article image — `DELETE /article/{sku}/image/{fullHash}` #### Delete article image — `DELETE /article/{sku}/image/{fullHash}`
- Params: `sku: str, fullHash: str` - Params: `sku: str, fullHash: str`
- Returns: `204 void` - Returns: `204 void`
@@ -124,12 +107,12 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }` - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
#### Toggle parent mode for an article — `POST /article/{sku}/parent` #### Toggle parent mode for an article — `POST /article/{sku}/parent`
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
- Returns: `200 T34` - Returns: `204 void`
#### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices` #### Replace POS prices by SKU (net > gross) — `PUT /article/{sku}/prices`
- Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }` - Params: `sku: str, body: { prices: { net?: num, gross?: num, taxRate?: num, minmultiplier?: num }[] }`
- Returns: `200 T3` - Returns: `204 void`
#### Print article label — `POST /article/{sku}/print` #### Print article label — `POST /article/{sku}/print`
- Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }` - Params: `sku: str, body: { templateId: int, agentprinterId: int, quantity: int }`
- Returns: `200 { success: bool }` - Returns: `200 { success: bool }`
#### List article properties — `GET /article/{sku}/property` #### List article properties — `GET /article/{sku}/property`
- Params: `sku: str` - Params: `sku: str`
@@ -142,10 +125,10 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
#### List article suppliers — `GET /article/{sku}/supplier` #### List article suppliers — `GET /article/{sku}/supplier`
- Params: `sku: str` - Params: `sku: str`
- Returns: `200 { suppliers: T15[] }` - Returns: `200 { suppliers: T5[] }`
#### Assign supplier to article — `POST /article/{sku}/supplier` #### Assign supplier to article — `POST /article/{sku}/supplier`
- Params: `sku: str, body: { entityId: int, sku?: str|null, pu?: num, stock?: num|null, sort?: int|null, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }` - Params: `sku: str, body: { entityId: int, sku?: str|null, pu?: num, stock?: num|null, sort?: int|null, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }`
- Returns: `201 { supplier: T15 }` - Returns: `201 { supplier: T5 }`
#### Reorder article suppliers — `PUT /article/{sku}/supplier/order` #### Reorder article suppliers — `PUT /article/{sku}/supplier/order`
- Params: `sku: str, body: { ids: int[] }` - Params: `sku: str, body: { ids: int[] }`
- Returns: `200 { ids: int[] }` - Returns: `200 { ids: int[] }`
@@ -154,31 +137,31 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `204 void` - Returns: `204 void`
#### Update article supplier assignment — `PATCH /article/{sku}/supplier/{id}` #### Update article supplier assignment — `PATCH /article/{sku}/supplier/{id}`
- Params: `sku: str, id: int, body: { sku?: str|null, pu?: num, stock?: num|null, sort?: int, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }` - Params: `sku: str, id: int, body: { sku?: str|null, pu?: num, stock?: num|null, sort?: int, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }`
- Returns: `200 { supplier: T15 }` - Returns: `200 { supplier: T5 }`
#### Set article track_stock flag by SKU — `PUT /article/{sku}/track_stock` #### Set article track_stock flag by SKU — `PUT /article/{sku}/track_stock`
- Params: `sku: str, body: { track_stock: bool }` - Params: `sku: str, body: { track_stock: bool }`
- Returns: `200 T3` - Returns: `204 void`
#### Set article weight by SKU — `PUT /article/{sku}/weight` #### Set article weight by SKU — `PUT /article/{sku}/weight`
- Params: `sku: str, body: { weight: num }` - Params: `sku: str, body: { weight: num }`
- Returns: `200 T3` - Returns: `204 void`
### Section: /attributename ### Section: /attributename
#### List attribute names — `GET /attributename` #### List attribute names — `GET /attributename`
_none_ _none_
- Returns: `200 { attributenames: T19[] }` - Returns: `200 { attributenames: T10[] }`
#### Create attribute name — `POST /attributename` #### Create attribute name — `POST /attributename`
- Params: `body: { name: str, sort?: int, exclusive?: bool }` - Params: `body: { name: str, sort?: int, exclusive?: bool }`
- Returns: `201 { attributename: T19 }` - Returns: `201 { attributename: T10 }`
#### Reorder attribute names — `PUT /attributename/order` #### Reorder attribute names — `PUT /attributename/order`
- Params: `body: { attributenameIds: int[] }` - Params: `body: { attributenameIds: int[] }`
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
#### List values for an attribute — `GET /attributename/{attributeId}/value` #### List values for an attribute — `GET /attributename/{attributeId}/value`
- Params: `attributeId: int` - Params: `attributeId: int`
- Returns: `200 { values: T36[] }` - Returns: `200 { values: T18[] }`
#### Create value for an attribute — `POST /attributename/{attributeId}/value` #### Create value for an attribute — `POST /attributename/{attributeId}/value`
- Params: `attributeId: int, body: { value: str, sort?: int }` - Params: `attributeId: int, body: { value: str, sort?: int }`
- Returns: `201 { attributevalue: T36 }` - Returns: `201 { attributevalue: T18 }`
#### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order` #### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order`
- Params: `attributeId: int, body: { attributevalueIds: int[] }` - Params: `attributeId: int, body: { attributevalueIds: int[] }`
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
@@ -187,7 +170,7 @@ _none_
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
#### Update attribute name — `PATCH /attributename/{id}` #### Update attribute name — `PATCH /attributename/{id}`
- Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }`
- Returns: `200 { attributename: T19 }` - Returns: `200 { attributename: T10 }`
### Section: /attributevalue ### Section: /attributevalue
@@ -196,13 +179,22 @@ _none_
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
#### Update attribute value — `PATCH /attributevalue/{id}` #### Update attribute value — `PATCH /attributevalue/{id}`
- Params: `id: int, body: { value?: str, sort?: int }` - Params: `id: int, body: { value?: str, sort?: int }`
- Returns: `200 { attributevalue: T36 }` - Returns: `200 { attributevalue: T18 }`
#### Delete attribute value image — `DELETE /attributevalue/{id}/image` #### Delete attribute value image — `DELETE /attributevalue/{id}/image`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T41[] } }` - Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T23[] } }`
#### Add or replace attribute value image — `POST /attributevalue/{id}/image`
- Params: `id: int, image/avif` ### Section: /autoinvite
- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T41[] } }`
#### List auto-invitations — `GET /autoinvite`
_none_
- Returns: `200 { autoinvites: { id: int, email: str, role: str, createdAt: str }[] }`
#### Create auto-invitation — `POST /autoinvite`
- Params: `body: { email: str, role?: str }`
- Returns: `201 { autoinvite: { id: int, email: str, role: str } }`
#### Delete auto-invitation — `DELETE /autoinvite/{id}`
- Params: `id: int`
- Returns: `204 void`
### Section: /brand ### Section: /brand
@@ -211,7 +203,7 @@ _none_
- Returns: `200 { brands: { id: int, name: str, articleCount: int, pics: { fullHash: str, thumbW: int, thumbH: int, thumbHash: str, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null }[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }` - Returns: `200 { brands: { id: int, name: str, articleCount: int, pics: { fullHash: str, thumbW: int, thumbH: int, thumbHash: str, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null }[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }`
#### Create brand — `POST /brand` #### Create brand — `POST /brand`
- Params: `body: { name: str, gsprpinfo?: str }` - Params: `body: { name: str, gsprpinfo?: str }`
- Returns: `201 { brand: { id: int, name: str, pics: T41[], gsprpinfo?: str|null } }` - Returns: `201 { brand: { id: int, name: str, pics: T23[], gsprpinfo?: str|null } }`
#### Delete brand — `DELETE /brand/{id}` #### Delete brand — `DELETE /brand/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
@@ -220,10 +212,28 @@ _none_
- Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` - Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }`
#### Delete brand image — `DELETE /brand/{id}/image` #### Delete brand image — `DELETE /brand/{id}/image`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T41[] } }` - Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T23[] } }`
#### Add or replace brand image — `POST /brand/{id}/image`
- Params: `id: int, image/avif` ### Section: /carrier
- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T41[] } }`
#### List carriers — `GET /carrier`
_none_
- Returns: `200 { carriers: T22[] }`
#### Create carrier — `POST /carrier`
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
- Returns: `201 { carrier: T22 }`
#### Reorder carriers — `PUT /carrier/order`
- Params: `body: { carrierIds: int[] }`
- Returns: `200 { carrierIds: int[] }`
#### Delete carrier — `DELETE /carrier/{id}`
- Params: `id: int`
- Returns: `204 void`
#### Get carrier by ID — `GET /carrier/{id}`
- Params: `id: int`
- Returns: `200 { carrier: T22 }`
#### Update carrier — `PATCH /carrier/{id}`
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
- Returns: `200 { carrier: T22 }`
### Section: /category ### Section: /category
@@ -243,8 +253,8 @@ _none_
- Params: `id: int, body: { name?: str, parentId?: int }` - Params: `id: int, body: { name?: str, parentId?: int }`
- Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }` - Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }`
#### Articles in category — `GET /category/{id}/articles` #### Articles in category — `GET /category/{id}/articles`
- Params: `id: int` - Params: `id: int, query: includeChildren?: bool`
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, brand?: T33|null }[] }` - Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null, brand?: { name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }, articlewarehousesummary?: { id: int, articleId: int, warehouseId: int, stock: num, article_id?: int, warehouse_id?: int, warehouseName?: str|null }[], warehouses?: { id: int, name: str, stock: num }[] }[] }`
#### List category attribute values — `GET /category/{id}/attributevalue` #### List category attribute values — `GET /category/{id}/attributevalue`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }` - Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }`
@@ -254,6 +264,9 @@ _none_
#### Delete category image — `DELETE /category/{id}/image` #### Delete category image — `DELETE /category/{id}/image`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { category: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null } }` - Returns: `200 { category: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null } }`
#### Generate category mosaic image from contents — `POST /category/{id}/image/generate`
- Params: `id: int, body?: { size?: int }`
- Returns: `200 { category: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, size: int, grid: str, articleCount: int } }`
#### List category properties — `GET /category/{id}/property` #### List category properties — `GET /category/{id}/property`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, categoryId?: int }[] }` - Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, categoryId?: int }[] }`
@@ -268,7 +281,7 @@ _none_
#### List companies — `GET /company` #### List companies — `GET /company`
_none_ _none_
- Returns: `200 { companies: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }[] }` - Returns: `200 { companies: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num, role: str }[] }`
#### Get company history — `GET /company/history` #### Get company history — `GET /company/history`
- Params: `query: limit?: int` - Params: `query: limit?: int`
- Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }` - Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }`
@@ -289,7 +302,22 @@ _none_
- Returns: `200 { setting: { key: str, value: str|null } }` - Returns: `200 { setting: { key: str, value: str|null } }`
#### Update company — `PUT /company/{id}` #### Update company — `PUT /company/{id}`
- Params: `id: int, body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` - Params: `id: int, body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
- Returns: `200 { company: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num } }` - Returns: `200 { company: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num, role: str } }`
### Section: /companyuser
#### List company users — `GET /companyuser`
_none_
- Returns: `200 { companyusers: T19[] }`
#### Add an existing user to the company — `POST /companyuser`
- Params: `body: { email: str, role?: str }`
- Returns: `201 { companyuser: T19 }`
#### Remove a user from the company — `DELETE /companyuser/{id}`
- Params: `id: int`
- Returns: `204 void`
#### Change a company user's role — `PATCH /companyuser/{id}`
- Params: `id: int, body: { role: str }`
- Returns: `200 { companyuser: T19 }`
### Section: /customergroup ### Section: /customergroup
@@ -298,7 +326,7 @@ _none_
- Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }` - Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }`
#### Create customer group — `POST /customergroup` #### Create customer group — `POST /customergroup`
- Params: `body: { name: str }` - Params: `body: { name: str }`
- Returns: `201 { customergroup: T47 }` - Returns: `201 { customergroup: { id: int, name: str, sort: int } }`
#### Reorder customer groups — `PUT /customergroup/order` #### Reorder customer groups — `PUT /customergroup/order`
- Params: `body: { customergroupIds: int[] }` - Params: `body: { customergroupIds: int[] }`
- Returns: `200 { customergroupIds: int[] }` - Returns: `200 { customergroupIds: int[] }`
@@ -307,76 +335,76 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Update customer group — `PATCH /customergroup/{id}` #### Update customer group — `PATCH /customergroup/{id}`
- Params: `id: int, body: { name: str }` - Params: `id: int, body: { name: str }`
- Returns: `200 { customergroup: T47 }` - Returns: `200 { customergroup: { id: int, name: str, sort: int } }`
### Section: /delivery ### Section: /delivery
#### List deliveries — `GET /delivery` #### List deliveries — `GET /delivery`
_none_ - Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int`
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int|null, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T44 }[] }` - Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T26 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
#### Create delivery — `POST /delivery` #### Create delivery — `POST /delivery`
- Params: `body: { externalNumber: str, date: str, lines: T29[], supplierId?: int|null, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` - Params: `body: { externalNumber: str, date: str, supplierId: int, lines: { amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `201 T9` - Returns: `201 { delivery: T4 }`
#### Delete delivery — `DELETE /delivery/{id}` #### Delete delivery — `DELETE /delivery/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Get delivery — `GET /delivery/{id}` #### Get delivery — `GET /delivery/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T9` - Returns: `200 { delivery: T4 }`
#### Update delivery — `PATCH /delivery/{id}` #### Update delivery — `PATCH /delivery/{id}`
- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T29[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` - Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: { amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `200 T9` - Returns: `200 { delivery: T4 }`
#### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}` #### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}`
- Params: `id: int, fileId: int` - Params: `id: int, fileId: int`
- Returns: `204 void` - Returns: `204 void`
#### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf` #### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T12` - Returns: `201 { attachment: T7, fileUrl: str }`
### Section: /entity ### Section: /entity
#### List entities — `GET /entity` #### List entities — `GET /entity`
- Params: `query: customergroupId?: int, role?: str` - Params: `query: customergroupId?: int, role?: str`
- Returns: `200 { entities: T31[] }` - Returns: `200 { entities: T16[] }`
#### Create entity — `POST /entity` #### Create entity — `POST /entity`
- Params: `body: { customergroupId: int, name: str, firstName?: str|null, number?: str }` - Params: `body: { customergroupId: int, name: str, firstName?: str|null, number?: str }`
- Returns: `201 T23` - Returns: `201 { entity: T16 }`
#### Peek next customer number — `GET /entity/next-number` #### Peek next customer number — `GET /entity/next-number`
_none_ _none_
- Returns: `200 { number: str }` - Returns: `200 { number: str }`
#### List entity addresses — `GET /entity/{entityId}/address` #### List entity addresses — `GET /entity/{entityId}/address`
- Params: `entityId: int` - Params: `entityId: int`
- Returns: `200 { addresses: T16[] }` - Returns: `200 { addresses: T8[] }`
#### Create entity address — `POST /entity/{entityId}/address` #### Create entity address — `POST /entity/{entityId}/address`
- Params: `entityId: int, body: { addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` - Params: `entityId: int, body: { addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }`
- Returns: `201 { address: T16 }` - Returns: `201 { address: T8 }`
#### Delete entity address — `DELETE /entity/{entityId}/address/{id}` #### Delete entity address — `DELETE /entity/{entityId}/address/{id}`
- Params: `entityId: int, id: int` - Params: `entityId: int, id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update entity address — `PATCH /entity/{entityId}/address/{id}` #### Update entity address — `PATCH /entity/{entityId}/address/{id}`
- Params: `entityId: int, id: int, body: { addressType?: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` - Params: `entityId: int, id: int, body: { addressType?: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }`
- Returns: `200 { address: T16 }` - Returns: `200 { address: T8 }`
#### List entity contacts — `GET /entity/{entityId}/contact` #### List entity contacts — `GET /entity/{entityId}/contact`
- Params: `entityId: int` - Params: `entityId: int`
- Returns: `200 { contacts: T42[] }` - Returns: `200 { contacts: T24[] }`
#### Create entity contact — `POST /entity/{entityId}/contact` #### Create entity contact — `POST /entity/{entityId}/contact`
- Params: `entityId: int, body: { contactType: str, contact: str }` - Params: `entityId: int, body: { contactType: str, contact: str }`
- Returns: `201 { contact: T42 }` - Returns: `201 { contact: T24 }`
#### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}` #### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}`
- Params: `entityId: int, id: int` - Params: `entityId: int, id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update entity contact — `PATCH /entity/{entityId}/contact/{id}` #### Update entity contact — `PATCH /entity/{entityId}/contact/{id}`
- Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }` - Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }`
- Returns: `200 { contact: T42 }` - Returns: `200 { contact: T24 }`
#### Delete entity — `DELETE /entity/{id}` #### Delete entity — `DELETE /entity/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Get entity — `GET /entity/{id}` #### Get entity — `GET /entity/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T23` - Returns: `200 { entity: T16 }`
#### Update entity — `PATCH /entity/{id}` #### Update entity — `PATCH /entity/{id}`
- Params: `id: int, body: { name?: str, firstName?: str|null, number?: str|null }` - Params: `id: int, body: { name?: str, firstName?: str|null, number?: str|null }`
- Returns: `200 T23` - Returns: `200 { entity: T16 }`
### Section: /labeltemplate ### Section: /labeltemplate
@@ -384,35 +412,38 @@ _none_
_none_ _none_
- Returns: `200 { labeltemplates: { id: int, name: str, width: num, height: num, dpi: int, type: str, thumb?: str|null }[] }` - Returns: `200 { labeltemplates: { id: int, name: str, width: num, height: num, dpi: int, type: str, thumb?: str|null }[] }`
#### Create label template — `POST /labeltemplate` #### Create label template — `POST /labeltemplate`
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: T21 }` - Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T15[] } }`
- Returns: `201 T11` - Returns: `201 { labeltemplate: T6 }`
#### Test print label template — `POST /labeltemplate/testprint`
- Params: `body: { agentprinterId: int, templateId?: int, objects?: any[], width?: int, height?: int, dpi?: int, sku?: str, quantity?: int }`
- Returns: `200 { success: bool }`
#### Delete label template — `DELETE /labeltemplate/{id}` #### Delete label template — `DELETE /labeltemplate/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Get label template by ID — `GET /labeltemplate/{id}` #### Get label template by ID — `GET /labeltemplate/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T11` - Returns: `200 { labeltemplate: T6 }`
#### Update label template (partial) — `PATCH /labeltemplate/{id}` #### Update label template (partial) — `PATCH /labeltemplate/{id}`
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T21 }` - Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T15[] } }`
- Returns: `200 T11` - Returns: `200 { labeltemplate: T6 }`
#### Update label template — `PUT /labeltemplate/{id}` #### Update label template — `PUT /labeltemplate/{id}`
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T21 }` - Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T15[] } }`
- Returns: `200 T11` - Returns: `200 { labeltemplate: T6 }`
### Section: /ledger ### Section: /ledger
#### Query ledger by manifest article ID or SKU — `GET /ledger` #### Query ledger by manifest article ID or SKU — `GET /ledger`
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str` - Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
- Returns: `200 T17` - Returns: `200 { ledger: T9[] }`
#### Add ledger entry — `POST /ledger` #### Query company-wide ledger history — `GET /ledger/history`
- Params: `body: { manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str }` - Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
- Returns: `201 { ledger: T18 }` - Returns: `200 { ledger: { id: int, date: str, transaction_type: str, transaction_text: str, sku?: str|null, name?: str|null, amount?: num }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}` #### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
- Params: `manifestarticleId: int, query: transaction_type?: str` - Params: `manifestarticleId: int, query: transaction_type?: str`
- Returns: `200 T17` - Returns: `200 { ledger: T9[] }`
#### Query ledger by article SKU — `GET /ledger/sku/{sku}` #### Query ledger by article SKU — `GET /ledger/sku/{sku}`
- Params: `sku: str, query: transaction_type?: str` - Params: `sku: str, query: transaction_type?: str`
- Returns: `200 T17` - Returns: `200 { ledger: T9[] }`
### Section: /number ### Section: /number
@@ -427,43 +458,43 @@ _none_
#### List orders — `GET /order` #### List orders — `GET /order`
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int` - Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int`
- Returns: `200 { orders: T2[], total: int, unfilteredTotal: int, offset: int, limit: int }` - Returns: `200 { orders: T1[], total: int, unfilteredTotal: int, offset: int, limit: int }`
#### Create order — `POST /order` #### Create order — `POST /order`
- Params: `body: { number: str, date: str, lines: T29[], entityId?: int|null, externalNumber?: str|null }` - Params: `body: { number: str, date: str, entityId: int, lines: T12[], externalNumber?: str|null }`
- Returns: `201 T1` - Returns: `201 { order: T1 }`
#### Delete order — `DELETE /order/{id}` #### Delete order — `DELETE /order/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Get order — `GET /order/{id}` #### Get order — `GET /order/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T1` - Returns: `200 { order: T1 }`
#### Update order — `PATCH /order/{id}` #### Update order — `PATCH /order/{id}`
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T29[] }` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
- Returns: `200 T1` - Returns: `200 { order: T1 }`
#### Delete order file attachment — `DELETE /order/{id}/file/{fileId}` #### Delete order file attachment — `DELETE /order/{id}/file/{fileId}`
- Params: `id: int, fileId: int` - Params: `id: int, fileId: int`
- Returns: `204 void` - Returns: `204 void`
#### List payments of an order — `GET /order/{id}/payment` #### List payments of an order — `GET /order/{id}/payment`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { payments: T38[] }` - Returns: `200 { payments: T21[] }`
#### Add payment to an order — `POST /order/{id}/payment` #### Add payment to an order — `POST /order/{id}/payment`
- Params: `id: int, body: { accountId: int, type: str, amount: num }` - Params: `id: int, body: { accountId: int, type: str, amount: num }`
- Returns: `201 { payment: T38 }` - Returns: `201 { payment: T21 }`
#### Generate and attach order PDF — `POST /order/{id}/pdf` #### Generate and attach order PDF — `POST /order/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T12` - Returns: `201 { attachment: T7, fileUrl: str }`
#### List shipments of an order — `GET /order/{id}/shipment` #### List shipments of an order — `GET /order/{id}/shipment`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { shipments: T6[] }` - Returns: `200 { shipments: T2[] }`
#### Create shipment for an order — `POST /order/{id}/shipment` #### Create shipment for an order — `POST /order/{id}/shipment`
- Params: `id: int, body?: { date?: str, externalNumber?: str, lines?: { sku: str, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[] }` - Params: `id: int, body: { lines: { sku?: str, orderLineId?: int, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[], date?: str, warehouseId?: int, externalNumber?: str }`
- Returns: `201 T5` - Returns: `201 { shipment: T2 }`
#### List shippings of an order — `GET /order/{id}/shipping` #### List shippings of an order — `GET /order/{id}/shipping`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { shippings: T32[] }` - Returns: `200 { shippings: T13[] }`
#### Create shipping for an order — `POST /order/{id}/shipping` #### Create shipping for an order — `POST /order/{id}/shipping`
- Params: `id: int, body: { type: str, state: str, warehouseId?: int }` - Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
- Returns: `201 T27` - Returns: `201 { shipping: T13 }`
### Section: /package ### Section: /package
@@ -477,38 +508,14 @@ _none_
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
### Section: /printer
#### List printers — `GET /printer`
_none_
- Returns: `200 { printers: T28[] }`
#### Create printer — `POST /printer`
- Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
- Returns: `201 T22`
#### Reorder printers — `PUT /printer/order`
- Params: `body: { printerIds: int[] }`
- Returns: `200 { printerIds: int[] }`
#### Delete printer — `DELETE /printer/{id}`
- Params: `id: int`
- Returns: `204 void`
#### Get printer by ID — `GET /printer/{id}`
- Params: `id: int`
- Returns: `200 T22`
#### Update printer (partial) — `PATCH /printer/{id}`
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
- Returns: `200 T22`
#### Update printer — `PUT /printer/{id}`
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
- Returns: `200 T22`
### Section: /propertyname ### Section: /propertyname
#### List property names — `GET /propertyname` #### List property names — `GET /propertyname`
_none_ _none_
- Returns: `200 { propertynames: T37[] }` - Returns: `200 { propertynames: T20[] }`
#### Create property name — `POST /propertyname` #### Create property name — `POST /propertyname`
- Params: `body: { name: str, sort?: int, exclusive?: bool }` - Params: `body: { name: str, sort?: int, exclusive?: bool }`
- Returns: `201 { propertyname: T37 }` - Returns: `201 { propertyname: T20 }`
#### Reorder property names — `PUT /propertyname/order` #### Reorder property names — `PUT /propertyname/order`
- Params: `body: { propertynameIds: int[] }` - Params: `body: { propertynameIds: int[] }`
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
@@ -517,34 +524,82 @@ _none_
- Returns: `200 { ok: bool }` - Returns: `200 { ok: bool }`
#### Update property name — `PATCH /propertyname/{id}` #### Update property name — `PATCH /propertyname/{id}`
- Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }`
- Returns: `200 { propertyname: T37 }` - Returns: `200 { propertyname: T20 }`
### Section: /quote ### Section: /quote
#### List quotes — `GET /quote` #### List quotes — `GET /quote`
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int` - Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
- Returns: `200 { quotes: T8[], total: int, unfilteredTotal: int, offset: int, limit: int }` - Returns: `200 { quotes: T3[], total: int, unfilteredTotal: int, offset: int, limit: int }`
#### Create quote — `POST /quote` #### Create quote — `POST /quote`
- Params: `body: { number: str, date: str, lines: T29[], entityId?: int|null, externalNumber?: str|null }` - Params: `body: { number: str, date: str, lines: T12[], entityId?: int|null, externalNumber?: str|null }`
- Returns: `201 T7` - Returns: `201 { quote: T3 }`
#### Delete quote — `DELETE /quote/{id}` #### Delete quote — `DELETE /quote/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Get quote — `GET /quote/{id}` #### Get quote — `GET /quote/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T7` - Returns: `200 { quote: T3 }`
#### Update quote — `PATCH /quote/{id}` #### Update quote — `PATCH /quote/{id}`
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T29[] }` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
- Returns: `200 T7` - Returns: `200 { quote: T3 }`
#### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}` #### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}`
- Params: `id: int, fileId: int` - Params: `id: int, fileId: int`
- Returns: `204 void` - Returns: `204 void`
#### Create order from quote — `POST /quote/{id}/order` #### Create order from quote — `POST /quote/{id}/order`
- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T29[] }` - Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
- Returns: `201 T1` - Returns: `201 { order: T1 }`
#### Generate and attach quote PDF — `POST /quote/{id}/pdf` #### Generate and attach quote PDF — `POST /quote/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T12` - Returns: `201 { attachment: T7, fileUrl: str }`
### Section: /retoure
#### List retoures — `GET /retoure`
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, entityId?: int, seekMonths?: int`
- Returns: `200`
#### Create retoure — `POST /retoure`
- Params: `body: { date: str, number?: str, entityId?: int, externalNumber?: str, lines?: any[] }`
- Returns: `201`
#### Rebuild retoure summaries — `POST /retoure/rebuild-summaries`
_none_
- Returns: `200`
#### Delete retoure — `DELETE /retoure/{id}`
- Params: `id: int`
- Returns: `204 void`
#### Get retoure by ID — `GET /retoure/{id}`
- Params: `id: int`
- Returns: `200`
#### Update retoure — `PUT /retoure/{id}`
- Params: `id: int, body: {}`
- Returns: `200`
#### Create shipment for retoure — `POST /retoure/{id}/shipment`
- Params: `id: int`
- Returns: `201`
### Section: /return
#### List returns — `GET /return`
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int`
- Returns: `200`
#### Create return — `POST /return`
- Params: `body: {}`
- Returns: `201`
#### Rebuild return summaries — `POST /return/rebuild-summaries`
_none_
- Returns: `200`
#### Delete return — `DELETE /return/{id}`
- Params: `id: int`
- Returns: `204 void`
#### Get return by ID — `GET /return/{id}`
- Params: `id: int`
- Returns: `200`
#### Update return — `PUT /return/{id}`
- Params: `id: int`
- Returns: `200`
#### Book return to ledger — `POST /return/{id}/book`
- Params: `id: int`
- Returns: `200`
### Section: /shipment ### Section: /shipment
@@ -556,22 +611,22 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Get shipment by ID — `GET /shipment/{id}` #### Get shipment by ID — `GET /shipment/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T5` - Returns: `200 { shipment: T2 }`
#### Update shipment state or metadata — `PATCH /shipment/{id}` #### Update shipment state or metadata — `PATCH /shipment/{id}`
- Params: `id: int, body: { state?: str }` - Params: `id: int, body: { state?: str }`
- Returns: `200 T5` - Returns: `200 { shipment: T2 }`
#### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}` #### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}`
- Params: `id: int, fileId: int` - Params: `id: int, fileId: int`
- Returns: `204 void` - Returns: `204 void`
#### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf` #### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T12` - Returns: `201 { attachment: T7, fileUrl: str }`
#### List packages of a shipment — `GET /shipment/{id}/shipping` #### List packages of a shipment — `GET /shipment/{id}/shipping`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { shippings: T32[] }` - Returns: `200 { shippings: T13[] }`
#### Create package for a shipment — `POST /shipment/{id}/shipping` #### Create package for a shipment — `POST /shipment/{id}/shipping`
- Params: `id: int, body: { type: str, state: str, trackingCode?: str, carrier?: str, warehouseId?: int }` - Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
- Returns: `201 T27` - Returns: `201 { shipping: T13 }`
### Section: /shipping ### Section: /shipping
@@ -579,8 +634,8 @@ _none_
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update shipping — `PATCH /shipping/{id}` #### Update shipping — `PATCH /shipping/{id}`
- Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrier?: str, warehouseId?: int }` - Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrierId?: int, warehouseId?: int }`
- Returns: `200 T27` - Returns: `200 { shipping: T13 }`
#### List packages of a shipping — `GET /shipping/{id}/package` #### List packages of a shipping — `GET /shipping/{id}/package`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { packages: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null }[] }` - Returns: `200 { packages: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null }[] }`
@@ -588,38 +643,38 @@ _none_
- Params: `id: int, body: { entityaddressId: int, type: str, state: str, details?: str }` - Params: `id: int, body: { entityaddressId: int, type: str, state: str, details?: str }`
- Returns: `201 { package: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null } }` - Returns: `201 { package: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null } }`
### Section: /smallthumb
#### Small thumb image by hash — `GET /smallthumb/{fullHash}`
- Params: `fullHash: str`
- Returns: `200 avif`
### Section: /stock ### Section: /stock
#### Accept delivery manifest item(s) — `POST /stock/accept`
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
- Returns: `200`
#### Adjust stock manually — `POST /stock/adjust` #### Adjust stock manually — `POST /stock/adjust`
- Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }`
- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T18[] }` - Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T9[] }`
#### Ship shipment manifest item(s) — `POST /stock/ship`
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
- Returns: `200`
### Section: /supplier ### Section: /supplier
#### List suppliers — `GET /supplier` #### List suppliers — `GET /supplier`
_none_ _none_
- Returns: `200 { suppliers: T43[] }` - Returns: `200 { suppliers: T25[] }`
#### Create supplier — `POST /supplier` #### Create supplier — `POST /supplier`
- Params: `body: { name: str }` - Params: `body: { name: str }`
- Returns: `201 { supplier: T43 }` - Returns: `201 { supplier: T25 }`
#### Delete supplier — `DELETE /supplier/{id}` #### Delete supplier — `DELETE /supplier/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update supplier — `PATCH /supplier/{id}` #### Update supplier — `PATCH /supplier/{id}`
- Params: `id: int, body: { name: str }` - Params: `id: int, body: { name: str }`
- Returns: `200 { supplier: T43 }` - Returns: `200 { supplier: T25 }`
#### List supplier articles by SKUs — `GET /supplier/{id}/articles` #### List supplier articles by SKUs — `GET /supplier/{id}/articles`
- Params: `id: int, query: skus?: string` - Params: `id: int, query: skus?: str`
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T46|null }[] }` - Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T27|null, incoming?: { date: str, amount: num }[] }[] }`
#### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles` #### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles`
- Params: `id: int, body?: { skus?: str[] }` - Params: `id: int, body?: { skus?: str[] }`
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T46|null }[] }` - Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T27|null, incoming?: { date: str, amount: num }[] }[] }`
### Section: /unit ### Section: /unit
@@ -628,19 +683,19 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### List units — `GET /unit` #### List units — `GET /unit`
_none_ _none_
- Returns: `200 { units: T35[] }` - Returns: `200 { units: T17[] }`
#### Upsert unit — `PUT /unit` #### Upsert unit — `PUT /unit`
- Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
- Returns: `200 { unit: T35 }` - Returns: `200 { unit: T17 }`
### Section: /warehouse ### Section: /warehouse
#### List warehouses — `GET /warehouse` #### List warehouses — `GET /warehouse`
_none_ _none_
- Returns: `200 { warehouses: T47[] }` - Returns: `200 { warehouses: { id: int, name: str, sort: int }[] }`
#### Create warehouse — `POST /warehouse` #### Create warehouse — `POST /warehouse`
- Params: `body: { name: str, sort?: int }` - Params: `body: { name: str, sort?: int }`
- Returns: `201 T45` - Returns: `201 { warehouse: { id: int, name: str, sort: int } }`
#### Reorder warehouses — `PUT /warehouse/order` #### Reorder warehouses — `PUT /warehouse/order`
- Params: `body: { warehouseIds: int[] }` - Params: `body: { warehouseIds: int[] }`
- Returns: `200 { warehouseIds: int[] }` - Returns: `200 { warehouseIds: int[] }`
@@ -649,24 +704,28 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Get warehouse by ID — `GET /warehouse/{id}` #### Get warehouse by ID — `GET /warehouse/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T45` - Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
#### Update warehouse (partial) — `PATCH /warehouse/{id}` #### Update warehouse (partial) — `PATCH /warehouse/{id}`
- Params: `id: int, body: { name?: str, sort?: int }` - Params: `id: int, body: { name?: str, sort?: int }`
- Returns: `200 T45` - Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
#### Update warehouse — `PUT /warehouse/{id}` #### Update warehouse — `PUT /warehouse/{id}`
- Params: `id: int, body: { name?: str, sort?: int }` - Params: `id: int, body: { name?: str, sort?: int }`
- Returns: `200 T45` - Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
### Binary & streaming routes (reachable only via `fetch()` in javascript_execute) ### Binary & streaming routes (reachable only via `fetch()` in javascript_execute)
These routes are NOT in the table above because their responses are binary or long-lived streams, which the `dashback_request` tool cannot return usefully. They ARE reachable from inside `javascript_execute` with `fetch("/route")`, where you can decode the bytes, write them to the VFS and embed them in your reply: These routes are NOT in the table above because their payloads (binary request body or binary response) or long-lived streams cannot be handled usefully by the `dashback_request` tool. They ARE reachable from inside `javascript_execute` with `fetch("/route")`:
| Method | Route | Returns | Notes | | Method | Route | Returns | Notes |
|:---|:---|:---|:---| |:---|:---|:---|:---|
| `GET` | `/image/{fullHash}` | `image/avif` | Full-size article/brand/category image by SHA-256 hash. Use for inspecting or re-encoding an existing image. | `GET` | `/image/{fullHash}` | `image/avif` | Full-size article/brand/category image by SHA-256 hash. Use for inspecting or re-encoding an existing image.
| `GET` | `/thumb/{fullHash}` | `image/avif` | Thumb AVIF by SHA-256 hash — cheaper than the full image. Takes the same `fullHash` as `/image/{fullHash}`. | `GET` | `/thumb/{fullHash}` | `image/avif` | Thumb AVIF by SHA-256 hash — cheaper than the full image. Takes the same `fullHash` as `/image/{fullHash}`.
| `POST` | `/serper/image` | `image/avif` | Crawl an image from a URL, auto-crop transparent/white borders. Body: `{ url }`. | `POST` | `/serper/image` | `image/avif` | Crawl an image from a URL, auto-crop transparent/white borders. Body: `{ url }`.
| `POST` | `/article/{sku}/image` | `201 { image: { picId, sort, full, thumb, hash, thumbHash } }` | Upload full-size AVIF or WebP image for article SKU. WebP is auto-converted to AVIF. Body: raw binary (`image/avif` or `image/webp`).
| `POST` | `/attributevalue/{id}/image` | `201 { attributevalue: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | Upload or replace AVIF/WebP image for attribute value ID. Body: raw binary (`image/avif` or `image/webp`).
| `POST` | `/brand/{id}/image` | `201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | Upload or replace AVIF/WebP image for brand ID. Body: raw binary (`image/avif` or `image/webp`).
| `POST` | `/category/{id}/image` | `201 { category: { id, thumbW, thumbH, thumbHash } }` | Upload or replace AVIF/WebP image for category ID. Body: raw binary (`image/avif` or `image/webp`).
Save the bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`: Save downloaded bytes to the VFS, then embed them in your reply as `![title](artifacts/thumb.avif)`:
```js ```js
module.exports = (async () => { module.exports = (async () => {
@@ -678,4 +737,18 @@ module.exports = (async () => {
})(); })();
``` ```
Upload an image from the VFS:
```js
module.exports = (async () => {
const buf = fs.readFileSync("images/product.avif");
const res = await fetch("/article/SKU123/image", {
method: "POST",
headers: { "Content-Type": "image/avif" },
body: buf,
});
return await res.json();
})();
```
`fullHash` appears on articles, brands and categories. `thumbHash` is a base64 blur-up placeholder, not a content hash, and is not accepted here. `fullHash` appears on articles, brands and categories. `thumbHash` is a base64 blur-up placeholder, not a content hash, and is not accepted here.

View File

@@ -0,0 +1,30 @@
# `GET /agent/events`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Printer agent SSE stream
Authorized Server-Sent Events stream for connected printer agents.
Maintains a persistent connection with regular heartbeats (`: heartbeat\n\n`) and updates `lastseen_at` and `agentversion`.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `token` | no | string | Agent auth token if not provided via Authorization header |
| `agentversion` | no | string | Current agent version |
## Response `200`
Event stream established
## Errors
| Status | Description |
|--------|-------------|
| 401 | Unauthorized |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,31 @@
# `GET /agent/pair`
Public. No cookie required.
Pair printer agent via query parameters
Alternative GET endpoint for devices that pair via query parameters.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `authCode` | no | string | Pairing PIN (or pairingpin) |
| `pairingpin` | no | string | |
| `serial` | no | string | |
| `model` | no | string | |
| `version` | no | string | |
## Response `200`
Pairing successful
## Errors
| Status | Description |
|--------|-------------|
| 400 | Missing or invalid pairing PIN |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,55 @@
# `POST /agent/pair`
Public. No cookie required.
Pair printer agent
Public, unauthorized endpoint used by the printer agent to complete pairing using a 6-digit PIN.
Updates `clientserial`, `clientmodel`, and `agentversion`, clears the pairing PIN, and returns a device authentication token.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `pairingpin` | yes | string | |
| `clientserial` | no | string | |
| `clientmodel` | no | string | |
| `agentversion` | no | string | |
```json
{
"pairingpin": "123456",
"clientserial": "SN-987654321",
"clientmodel": "Epson TM-T88VI",
"agentversion": "1.0.0"
}
```
## Response `200`
Pairing successful
```json
{
"id": 1,
"name": "…",
"companyId": 1,
"token": "…",
"pairedAt": "…",
"clientserial": "…",
"clientmodel": "…",
"agentversion": "…",
"sort": 1
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Missing or invalid pairing PIN — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,53 @@
# `POST /agent/state`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Post agent state
Authorized endpoint for the printer agent to report its operational status/telemetry.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `state` | no | | Status text or status object |
```json
{
"state": "…"
}
```
## Response `200`
State recorded
```json
{
"success": true,
"agent": {
"id": 1,
"companyId": 1,
"name": "…",
"token": "…",
"pairingpin": "…",
"pairedAt": "…",
"agentversion": "…",
"lastseenAt": "…",
"updatedAt": "…",
"sort": 1,
"enabled": true
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 401 | Unauthorized |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,51 @@
# `GET /agentprinter`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List agent printers
Lists agent printers for the authenticated user's selected company,
optionally filtered by printer agent ID.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `printeragentId` | no | integer | Optional printer agent ID filter |
## Response `200`
List of agent printers
```json
{
"agentprinters": [
{
"id": 1,
"printeragentId": 1,
"type": "…",
"model": "…",
"serial": "…",
"enabled": true,
"sort": 1,
"open": true,
"pause": true,
"error": true,
"online": true,
"labelLength": 1
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,35 @@
# `DELETE /agentprinter/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete agent printer
Deletes an agent printer from the selected company.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Signals
On success, emits SSE signal: `agentprinter` (see [`GET /events`](../events.md)).
## Response `204`
Agent printer deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 404 | Agent printer not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,48 @@
# `GET /agentprinter/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get agent printer
Retrieves a single agent printer by ID.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Response `200`
Agent printer details
```json
{
"agentprinter": {
"id": 1,
"printeragentId": 1,
"type": "…",
"model": "…",
"serial": "…",
"enabled": true,
"sort": 1,
"open": true,
"pause": true,
"error": true,
"online": true,
"labelLength": 1
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 404 | Agent printer not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,67 @@
# `PATCH /agentprinter/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Update agent printer
Updates enabled and/or display order (sort) of an agent printer.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `enabled` | no | boolean | |
| `sort` | no | integer | |
```json
{
"enabled": true,
"sort": 1
}
```
## Signals
On success, emits SSE signal: `agentprinter` (see [`GET /events`](../events.md)).
## Response `200`
Updated agent printer
```json
{
"agentprinter": {
"id": 1,
"printeragentId": 1,
"type": "…",
"model": "…",
"serial": "…",
"enabled": true,
"sort": 1,
"open": true,
"pause": true,
"error": true,
"online": true,
"labelLength": 1
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid input or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 404 | Agent printer not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,51 @@
# `PUT /agentprinter/order`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Reorder agent printers
Reorders agent printers for a specific printer agent within the selected company.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `printeragentId` | no | integer | Optional agent id. If omitted, reorders all printers in the company. |
| `agentprinterIds` | yes | integer[] | |
```json
{
"printeragentId": 1,
"agentprinterIds": [
1
]
}
```
## Signals
On success, emits SSE signal: `agentprinter` (see [`GET /events`](../events.md)).
## Response `200`
Applied order
```json
{
"agentprinterIds": [
1
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid order or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -34,13 +34,7 @@ Created child article
```json ```json
{ {
"article": { "sku": "…"
"id": 1,
"categoryId": 1,
"name": "…",
"sku": "…",
"track_stock": true
}
} }
``` ```

View File

@@ -32,17 +32,10 @@ Attaches an existing article to this parent article. Idempotent if already a chi
## Response `200` ## Response `200`
Attached child article and list of children List of children after attaching
```json ```json
{ {
"article": {
"id": 1,
"categoryId": 1,
"name": "…",
"sku": "…",
"track_stock": true
},
"children": [ "children": [
{ {
"id": 1, "id": 1,

View File

@@ -34,21 +34,11 @@ when `isParent` is `false`.
} }
``` ```
## Response `200` ## Response `204`
Updated article Updated
```json _No content_
{
"article": {
"id": 1,
"categoryId": 1,
"name": "…",
"sku": "…",
"track_stock": true
}
}
```
## Errors ## Errors

View File

@@ -17,13 +17,13 @@ Prints a label for the article using a label template.
| Field | Required | Type | Description | | Field | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `templateId` | yes | integer | | | `templateId` | yes | integer | |
| `printerId` | yes | integer | | | `agentprinterId` | yes | integer | |
| `quantity` | yes | integer | | | `quantity` | yes | integer | |
```json ```json
{ {
"templateId": 1, "templateId": 1,
"printerId": 1, "agentprinterId": 1,
"quantity": 1 "quantity": 1
} }
``` ```

View File

@@ -29,84 +29,11 @@ Enables or disables inventory stock tracking for this article.
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)). On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -14,7 +14,7 @@ Creates an article under an authorized category (`parentId`).
| `name` | yes | string | | | `name` | yes | string | |
| `desc` | no | string | Article description text/HTML | | `desc` | no | string | Article description text/HTML |
| `seo` | no | string | SEO text | | `seo` | no | string | SEO text |
| `track_stock` | no | boolean | Whether to track inventory stock (defaults to true) | | `track_stock` | no | boolean | Whether to track inventory stock (defaults to false) |
```json ```json
{ {
@@ -33,13 +33,7 @@ Created
```json ```json
{ {
"article": { "sku": "…"
"id": 1,
"categoryId": 1,
"name": "…",
"sku": "…",
"track_stock": true
}
} }
``` ```

View File

@@ -84,7 +84,22 @@ Article detail
"net": 1, "net": 1,
"taxId": 1, "taxId": 1,
"taxRate": 1 "taxRate": 1
} },
"incoming": [
{
"date": "2026-08-10T00:00:00.000Z",
"amount": 1
}
]
}
],
"undeliveredOrders": [
{
"orderId": 1,
"orderNumber": "…",
"date": "2026-08-10T00:00:00.000Z",
"customerName": "…",
"amount": 1
} }
] ]
} }

View File

@@ -0,0 +1,52 @@
# `GET /article/history`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Query company-wide article history
Queries article change history across all articles for the authenticated user's selected company.
Supports pagination, search filter, and date cutoff via `seekMonths`.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `offset` | no | integer | Paging offset |
| `limit` | no | integer | Page size |
| `search` | no | string | Optional search filter across SKU, article name, and history text |
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -13) |
## Response `200`
Paged article history list
```json
{
"history": [
{
"id": 1,
"date": "2026-08-10T00:00:00.000Z",
"sku": "…",
"name": "…",
"historytext": "…"
}
],
"total": 1,
"unfilteredTotal": 1,
"offset": 1,
"limit": 1
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Validation error or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -5,6 +5,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Get article history by SKU Get article history by SKU
Returns `articlehistory` rows for the article, newest first (`date` DESC, `id` DESC). Returns `articlehistory` rows for the article, newest first (`date` DESC, `id` DESC).
Supports pagination, search filter, and date cutoff via `seekMonths`.
Auth like GET by SKU. Auth like GET by SKU.
@@ -18,11 +19,14 @@ Auth like GET by SKU.
| Param | Required | Type | Description | | Param | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `limit` | no | integer | Maximum number of entries to return (newest first). Omit for all entries. | | `offset` | no | integer | Paging offset |
| `limit` | no | integer | Page size |
| `search` | no | string | Optional search filter for history text |
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -13) |
## Response `200` ## Response `200`
Article history (newest first) Paged article history list
```json ```json
{ {
@@ -37,7 +41,11 @@ Article history (newest first)
"date": "2026-08-01T09:00:00.000Z", "date": "2026-08-01T09:00:00.000Z",
"text": "Name geändert von \"\" auf \"Tee\"" "text": "Name geändert von \"\" auf \"Tee\""
} }
] ],
"total": 2,
"unfilteredTotal": 2,
"offset": 0,
"limit": 50
} }
``` ```
@@ -45,7 +53,7 @@ Article history (newest first)
| Status | Description | | Status | Description |
|--------|-------------| |--------|-------------|
| 400 | Missing sku — `{"error":"…"}` | | 400 | Missing sku or invalid query parameters — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` | | 403 | Not authorized — `{"error":"…"}` |
| 404 | Article not found — `{"error":"…"}` | | 404 | Article not found — `{"error":"…"}` |

View File

@@ -30,84 +30,11 @@ Pass `null` to clear the brand. Returns the same shape as GET by SKU.
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)). On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -34,84 +34,11 @@ at least one category (empty array is rejected). Returns the same shape as GET b
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)). On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -32,84 +32,11 @@ empty array clears all EANs. Returns the same shape as GET by SKU.
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)). On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -2,11 +2,11 @@
Protected. Requires a valid session cookie (`credentials: 'include'`). Protected. Requires a valid session cookie (`credentials: 'include'`).
Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) Replace POS prices by SKU (net > gross)
Replace-all prices for the company POS customer group. Replace-all prices for the company POS customer group.
`net` takes precedence over `price` (legacy alias for net) and `gross`. `net` takes precedence over `gross`.
`tax` and `taxRate` both specify the tax rate percentage (e.g. 19 for 19%), not monetary tax. `taxRate` specifies the tax rate percentage (e.g. 19 for 19%), not monetary tax.
When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`. When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
Prices are strictly scoped to the company's designated POS customer group (customergroupId returned on GET). Prices are strictly scoped to the company's designated POS customer group (customergroupId returned on GET).
Empty `prices` clears all POS prices. Returns the same shape as GET by SKU. Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
@@ -23,11 +23,9 @@ Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
| Field | Required | Type | Description | | Field | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `prices` | yes | object[] | | | `prices` | yes | object[] | |
| `prices[].net` | no | number | Net price (excl. VAT). Either `net`, `gross`, or legacy `price` is required. | | `prices[].net` | no | number | Net price (excl. VAT). Either `net` or `gross` is required. |
| `prices[].gross` | no | number | Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`. | | `prices[].gross` | no | number | Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`. |
| `prices[].price` | no | number | Legacy alias for `net`. |
| `prices[].taxRate` | no | number | Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%). | | `prices[].taxRate` | no | number | Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%). |
| `prices[].tax` | no | number | Alias for `taxRate` percentage. |
| `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request. | | `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request. |
```json ```json
@@ -50,84 +48,11 @@ Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)). On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -30,84 +30,11 @@ Returns the same shape as GET by SKU.
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)). On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -62,7 +62,13 @@ Created supplier assignment
"net": 1, "net": 1,
"taxId": 1, "taxId": 1,
"taxRate": 1 "taxRate": 1
} },
"incoming": [
{
"date": "2026-08-10T00:00:00.000Z",
"amount": 1
}
]
} }
} }
``` ```

View File

@@ -33,7 +33,13 @@ List of suppliers linked to the article
"net": 1, "net": 1,
"taxId": 1, "taxId": 1,
"taxRate": 1 "taxRate": 1
} },
"incoming": [
{
"date": "2026-08-10T00:00:00.000Z",
"amount": 1
}
]
} }
] ]
} }

View File

@@ -59,7 +59,13 @@ Updated supplier assignment
"net": 1, "net": 1,
"taxId": 1, "taxId": 1,
"taxRate": 1 "taxRate": 1
} },
"incoming": [
{
"date": "2026-08-10T00:00:00.000Z",
"amount": 1
}
]
} }
} }
``` ```

View File

@@ -36,84 +36,11 @@ Patches article fields. At least one of `name`, `unitamount`, `unitSi` required.
On success, emits SSE signal: `articles` (see [`GET /events`](../events.md)). On success, emits SSE signal: `articles` (see [`GET /events`](../events.md)).
## Response `200` ## Response `204`
Updated article detail Updated
```json _No content_
{
"article": {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"desc": "…",
"seo": "…",
"track_stock": true,
"unitamount": 1,
"weight": 1,
"unit": {
"si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [
1
],
"photos": [
{
"sort": 1,
"fullHash": "…",
"fullW": 1,
"fullH": 1,
"fullSize": 1,
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
],
"eans": [
"…"
],
"prices": [
{
"minmultiplier": 1,
"net": 1,
"taxRate": 1,
"customergroupId": 1
}
],
"suppliers": [
{
"id": 1,
"articleId": 1,
"entityId": 1,
"supplierName": "…",
"sku": "…",
"pu": 1,
"stock": 1,
"sort": 1,
"priceId": 1,
"price": {
"net": 1,
"taxId": 1,
"taxRate": 1
}
}
]
}
}
```
## Errors ## Errors

View File

@@ -4,7 +4,8 @@ Public. No cookie required.
Login with Google ID token Login with Google ID token
Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`), Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`
or when the email has a pending auto-invitation),
stores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie. stores Google `sub` and profile image bytes, and sets an HTTP-only `token` cookie.
`user.picture` is the profile image as base64 (same as GET /auth/me). `user.picture` is the profile image as base64 (same as GET /auth/me).
@@ -43,7 +44,7 @@ Login successful; `token` cookie set
|--------|-------------| |--------|-------------|
| 400 | Missing token or Google client not configured — `{"error":"…"}` | | 400 | Missing token or Google client not configured — `{"error":"…"}` |
| 401 | Invalid Google token or account — `{"error":"…"}` | | 401 | Invalid Google token or account — `{"error":"…"}` |
| 403 | Email not on allowlist — `{"error":"…"}` | | 403 | Email not on allowlist or invited — `{"error":"…"}` |
| 500 | Server error — `{"error":"…"}` | | 500 | Server error — `{"error":"…"}` |
--- ---

View File

@@ -6,7 +6,7 @@ Current user
Returns the authenticated user from the session cookie and the Google client ID. Returns the authenticated user from the session cookie and the Google client ID.
`user.picture` is the stored profile image as a base64 string when present. `user.picture` is the stored profile image as a base64 string when present.
Unauthenticated calls still receive `googleClientId` with 401. Unauthenticated calls still receive `googleClientId` and `schemaVersion` with 401.
## Response `200` ## Response `200`
@@ -21,7 +21,10 @@ Authenticated
"selectedCompanyId": 1, "selectedCompanyId": 1,
"picture": "…" "picture": "…"
}, },
"googleClientId": "…" "googleClientId": "…",
"schemaVersion": 1,
"vhost": true,
"vhostSettings": "…"
} }
``` ```
@@ -29,7 +32,7 @@ Authenticated
| Status | Description | | Status | Description |
|--------|-------------| |--------|-------------|
| 401 | Unauthorized — still includes `googleClientId` for the frontend | | 401 | Unauthorized — still includes `googleClientId` and `schemaVersion` for the frontend |
--- ---

View File

@@ -18,6 +18,10 @@ Sets the authenticated user's selected company.
} }
``` ```
## Signals
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
## Response `200` ## Response `200`
Updated user Updated user
@@ -39,6 +43,7 @@ Updated user
|--------|-------------| |--------|-------------|
| 400 | Invalid companyId — `{"error":"…"}` | | 400 | Invalid companyId — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (the user is not a member of this company) — `{"error":"…"}` |
| 500 | Server error — `{"error":"…"}` | | 500 | Server error — `{"error":"…"}` |
--- ---

View File

@@ -0,0 +1,55 @@
# `POST /autoinvite`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Create auto-invitation
Registers an email for automatic membership. When a user with this email registers,
the invitation is removed from the list and the user is added to the company with the given role.
Emails that are already registered are rejected (409); use `POST /companyuser` for those. Admin only.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `email` | yes | string | |
| `role` | no | string | |
```json
{
"email": "newhire@example.com",
"role": "user"
}
```
## Signals
On success, emits SSE signal: `autoinvite` (see [`GET /events`](../events.md)).
## Response `201`
Created invitation
```json
{
"autoinvite": {
"id": 1,
"email": "…",
"role": "admin"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected, invalid email or invalid role — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
| 409 | User already registered or invitation already exists — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,36 @@
# `DELETE /autoinvite/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete auto-invitation
Deletes a pending auto-invitation of the selected company. Admin only.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Signals
On success, emits SSE signal: `autoinvite` (see [`GET /events`](../events.md)).
## Response `204`
Deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid id or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
| 404 | Invitation not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,36 @@
# `GET /autoinvite`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List auto-invitations
Lists pending auto-invitations of the selected company. Admin only.
## Response `200`
Pending invitations
```json
{
"autoinvites": [
{
"id": 1,
"email": "…",
"role": "admin",
"createdAt": "…"
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,37 @@
# `GET /carrier`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List carriers
Lists carriers for the selected company, ordered by sort and name.
## Response `200`
List of carriers
```json
{
"carriers": [
{
"id": 1,
"name": "…",
"sort": 1,
"hasPackages": true,
"isReverse": true
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,60 @@
# `POST /carrier`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Create carrier
Creates a new carrier for the selected company.
Sort order will be automatically appended if omitted.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `name` | yes | string | |
| `sort` | no | integer | Optional sort order |
| `hasPackages` | no | boolean | |
| `isReverse` | no | boolean | |
```json
{
"name": "…",
"sort": 1,
"hasPackages": true,
"isReverse": true
}
```
## Signals
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
## Response `201`
Created carrier
```json
{
"carrier": {
"id": 1,
"name": "…",
"sort": 1,
"hasPackages": true,
"isReverse": true
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid input or missing fields — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 409 | Carrier already exists — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,37 @@
# `DELETE /carrier/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete carrier
Deletes a carrier for the selected company, unless referenced in shipping.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Carrier ID |
## Signals
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
## Response `204`
Carrier deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Carrier not found — `{"error":"…"}` |
| 409 | Carrier in use in shipping — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,42 @@
# `GET /carrier/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get carrier by ID
Retrieves a single carrier for the authenticated user's selected company.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Carrier ID |
## Response `200`
Carrier details
```json
{
"carrier": {
"id": 1,
"name": "…",
"sort": 1,
"hasPackages": true,
"isReverse": true
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Carrier not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,65 @@
# `PATCH /carrier/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Update carrier
Updates carrier name, sort order, hasPackages, and/or isReverse for the selected company.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Carrier ID |
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `name` | no | string | |
| `sort` | no | integer | |
| `hasPackages` | no | boolean | |
| `isReverse` | no | boolean | |
```json
{
"name": "…",
"sort": 1,
"hasPackages": true,
"isReverse": true
}
```
## Signals
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
## Response `200`
Updated carrier
```json
{
"carrier": {
"id": 1,
"name": "…",
"sort": 1,
"hasPackages": true,
"isReverse": true
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid input or missing fields — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Carrier not found — `{"error":"…"}` |
| 409 | Carrier already exists — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,51 @@
# `PUT /carrier/order`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Reorder carriers
Reorders carriers for the selected company.
`carrierIds` must include all existing carrier IDs exactly once.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `carrierIds` | yes | integer[] | |
```json
{
"carrierIds": [
1
]
}
```
## Signals
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
## Response `200`
Applied carrier order
```json
{
"carrierIds": [
1
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid carrier order or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,64 @@
# `POST /category/{id}/image/generate`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Generate category mosaic image from contents
Generates a 2x2, 3x3, or 4x4 mosaic image from the category's and its subcategories'
articles (grouped by subcategory) and sets it as the new main picture for the category.
If size is not specified, defaults to 3 (3x3), falling back to 2 (2x2) if fewer than 7 images are available.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Category id |
## Body (JSON) — _optional_
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `size` | no | integer | Mosaic grid size (2 for 2x2, 3 for 3x3, 4 for 4x4). Defaults to 3x3 (or 2x2 if < 7 images). |
```json
{
"size": 1
}
```
## Signals
On success, emits SSE signal: `category` (see [`GET /events`](../events.md)).
## Response `200`
Mosaic image generated and linked as category main picture
```json
{
"category": {
"id": 1,
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…",
"size": 1,
"grid": "…",
"articleCount": 1
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid category ID or size — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Category not found or no article images found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -4,7 +4,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Articles in category Articles in category
Returns articles in a category the user is authorized for (omits child articles of a parent article), sorted alphabetically by name. Each article includes `childCount` (the number of its child articles). Returns articles in a category the user is authorized for, sorted alphabetically by name. By default includes child articles (variants); pass `includeChildren=false` to omit them. Each article includes `childCount` (the number of its child articles) and small thumbnail fields (`smallThumbW`, `smallThumbH`, `smallThumbBinary`).
## Path parameters ## Path parameters
@@ -12,6 +12,12 @@ Returns articles in a category the user is authorized for (omits child articles
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `id` | yes | integer | Category id | | `id` | yes | integer | Category id |
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `includeChildren` | no | boolean | When true (default), includes child articles (variants) alongside parent articles. Set to false to omit child articles. |
## Response `200` ## Response `200`
Articles list Articles list
@@ -36,6 +42,9 @@ Articles list
"thumbW": 1, "thumbW": 1,
"thumbH": 1, "thumbH": 1,
"thumbHash": "…", "thumbHash": "…",
"smallThumbW": 1,
"smallThumbH": 1,
"smallThumbBinary": "…",
"brand": { "brand": {
"id": 1, "id": 1,
"name": "…", "name": "…",
@@ -43,7 +52,25 @@ Articles list
"thumbW": 1, "thumbW": 1,
"thumbH": 1, "thumbH": 1,
"thumbHash": "…" "thumbHash": "…"
} },
"articlewarehousesummary": [
{
"id": 1,
"articleId": 1,
"article_id": 1,
"warehouseId": 1,
"warehouse_id": 1,
"warehouseName": "…",
"stock": 1
}
],
"warehouses": [
{
"id": 1,
"name": "…",
"stock": 1
}
]
} }
] ]
} }

View File

@@ -24,6 +24,10 @@ Creates a company and selects it for the current user.
} }
``` ```
## Signals
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
## Response `201` ## Response `201`
Created; selected company updated Created; selected company updated
@@ -38,7 +42,8 @@ Created; selected company updated
"iso639": "…", "iso639": "…",
"taxrate": 1, "taxrate": 1,
"reducedtaxrate": 1, "reducedtaxrate": 1,
"taxfreerate": 1 "taxfreerate": 1,
"role": "admin"
}, },
"user": { "user": {
"id": 1, "id": 1,
@@ -55,7 +60,7 @@ Created; selected company updated
|--------|-------------| |--------|-------------|
| 400 | Validation error — `{"error":"…"}` | | 400 | Validation error — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` |
| 409 | Company name not available — `{"error":"…"}` | | 403 | Not authorized (invited users cannot create companies when implicit create is disabled) — `{"error":"…"}` |
--- ---

View File

@@ -12,6 +12,10 @@ Deletes a company.
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `id` | yes | integer | | | `id` | yes | integer | |
## Signals
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
## Response `204` ## Response `204`
Deleted Deleted

View File

@@ -21,7 +21,8 @@ Company list
"iso639": "…", "iso639": "…",
"taxrate": 1, "taxrate": 1,
"reducedtaxrate": 1, "reducedtaxrate": 1,
"taxfreerate": 1 "taxfreerate": 1,
"role": "admin"
} }
] ]
} }

View File

@@ -30,6 +30,10 @@ Updates a company.
} }
``` ```
## Signals
On success, emits SSE signal: `company` (see [`GET /events`](../events.md)).
## Response `200` ## Response `200`
Updated Updated
@@ -44,7 +48,8 @@ Updated
"iso639": "…", "iso639": "…",
"taxrate": 1, "taxrate": 1,
"reducedtaxrate": 1, "reducedtaxrate": 1,
"taxfreerate": 1 "taxfreerate": 1,
"role": "admin"
} }
} }
``` ```
@@ -56,7 +61,6 @@ Updated
| 400 | Validation error — `{"error":"…"}` | | 400 | Validation error — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` |
| 404 | Company not found — `{"error":"…"}` | | 404 | Company not found — `{"error":"…"}` |
| 409 | Company name not available — `{"error":"…"}` |
--- ---

View File

@@ -0,0 +1,58 @@
# `POST /companyuser`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Add an existing user to the company
Adds an existing (registered) user, identified by email, to the selected company. Admin only.
The added user can then switch to this company via `PUT /auth/selected-company`.
For emails that are not registered yet use `POST /autoinvite`.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `email` | yes | string | |
| `role` | no | string | |
```json
{
"email": "colleague@example.com",
"role": "user"
}
```
## Signals
On success, emits SSE signal: `companyuser` (see [`GET /events`](../events.md)).
## Response `201`
Created company user
```json
{
"companyuser": {
"id": 1,
"googleAuthId": 1,
"email": "…",
"picture": "…",
"role": "admin"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected, invalid email or invalid role — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
| 404 | No registered user with this email — `{"error":"…"}` |
| 409 | User already in company — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,40 @@
# `DELETE /companyuser/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Remove a user from the company
Removes a member from the selected company. History entries stay but lose their author link;
if the removed user had this company selected, their selection is cleared.
The last admin cannot be removed. Admin only.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Signals
On success, emits SSE signal: `companyuser` (see [`GET /events`](../events.md)).
## Response `204`
Removed
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid id or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
| 404 | Company user not found — `{"error":"…"}` |
| 409 | Cannot remove the last admin — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,37 @@
# `GET /companyuser`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List company users
Lists the members of the selected company with their role. Admin only.
## Response `200`
Company members
```json
{
"companyusers": [
{
"id": 1,
"googleAuthId": 1,
"email": "…",
"picture": "…",
"role": "admin"
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,59 @@
# `PATCH /companyuser/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Change a company user's role
Changes the role of a member of the selected company. The last admin cannot be demoted. Admin only.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `role` | yes | string | |
```json
{
"role": "admin"
}
```
## Signals
On success, emits SSE signal: `companyuser` (see [`GET /events`](../events.md)).
## Response `200`
Updated company user
```json
{
"companyuser": {
"id": 1,
"googleAuthId": 1,
"email": "…",
"picture": "…",
"role": "admin"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid id, role or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
| 404 | Company user not found — `{"error":"…"}` |
| 409 | Cannot demote the last admin — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -17,7 +17,7 @@ The manifest `number` is allocated automatically via
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `externalNumber` | yes | string | User-supplied reference number | | `externalNumber` | yes | string | User-supplied reference number |
| `date` | yes | string | | | `date` | yes | string | |
| `supplierId` | no | integer | | | `supplierId` | yes | integer | |
| `lines` | yes | DeliveryLineInput[] | | | `lines` | yes | DeliveryLineInput[] | |
| `files` | no | DeliveryFileInput[] | Attached documents (pdf or txt) stored with the delivery and returned in `files` on every delivery response. Thumbnails (AVIF) are generated server-side. | | `files` | no | DeliveryFileInput[] | Attached documents (pdf or txt) stored with the delivery and returned in `files` on every delivery response. Thumbnails (AVIF) are generated server-side. |

View File

@@ -5,12 +5,23 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
List deliveries List deliveries
Lists goods receipts (`manifest` rows with `type = 'delivery'`) Lists goods receipts (`manifest` rows with `type = 'delivery'`)
for the authenticated user's selected company. for the authenticated user's selected company. Supports pagination and filtering.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `offset` | no | integer | Number of records to skip |
| `limit` | no | integer | Number of records to return (1-1000) |
| `search` | no | string | Substring match for delivery number, external number, or supplier name |
| `onlyUnbooked` | no | boolean | When true, returns only deliveries with unbooked stock items |
| `supplierId` | no | integer | Filter by supplier entity id |
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -1) |
## Response `200` ## Response `200`
Delivery list Delivery list (windowed when pagination/filters are used)
```json ```json
{ {
@@ -28,7 +39,11 @@ Delivery list
"completelyOnLedgerPositions": 1 "completelyOnLedgerPositions": 1
} }
} }
] ],
"total": 1,
"unfilteredTotal": 1,
"offset": 1,
"limit": 1
} }
``` ```

View File

@@ -1,91 +0,0 @@
# `POST /delivery/parse`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Parse document (SSE)
Accepts an invoice or delivery note as PDF or text file
(`multipart/form-data`, field `file`) and streams extraction results as
Server-Sent Events until parsing is complete.
PDFs are read via `pdftotext -layout`; text files are decoded as UTF-8.
Pages are separated by the form feed character (`\f`). If no form feed
is present, the input is treated as a single page.
The extracted pages are fed to an LLM (OpenRouter) which fills a structured
manifest of header data and line items. The OpenRouter API key is read from
the authenticated user's company setting `keys.openrouter` (or the server's
`OPENROUTER_FREE_KEY` if not configured); the endpoint
returns `400` if the key is not configured.
The stream ends after the final `result` event (or an `error` event on failure).
## Body (`multipart/form-data`)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `file` | yes | file | Document to parse (max 50 MB, application/pdf or text file) |
## SSE Events
The response is a `text/event-stream`. Events are emitted in order until the stream closes.
| Event | Description | Payload |
|-------|-------------|---------|
| `status` | Document parse confirmation and LLM status messages | `{ "message": "…" }` |
| `llm_call` | Fired before each LLM request | `{ "model": "…", "promptSize": 0, "messages": 0 }` |
| `tool_call` | Fired when the output_manifest tool is invoked | `{ "name": "output_manifest" }` |
| `usage` | Token usage after each LLM response | `{ "input": 0, "cached": 0, "output": 0, "cost": 0 }` |
| `progress` | Intermediate accumulated extraction state (basedata / notes / items) | `ManifestResult` |
| `result` | Final accumulated extraction — emitted just before the stream closes | `ManifestResult` |
| `error` | Emitted on document parse or LLM failure; stream closes immediately after | `{ "message": "…" }` |
## Response `200`
`text/event-stream` — emits `status`, `llm_call`, `tool_call`, `usage`,
`progress`, `result` (and `error` on failure). See SSE Events table below.
**ManifestResult shape** (used by `progress` and `result`):
```json
{
"basedata": {
"date": "2026-01-15",
"lieferant_id": 42,
"lieferantName": "Muster GmbH",
"manifestNr": "LS-2026-001",
"manifestType": "lieferschein"
},
"notes": ["Pos. 3: VPE-Angabe fehlte, Standardwert 1 angenommen."],
"items": {
"pagesFrom": 1,
"pagesTo": 2,
"items": [
{
"position": 1,
"count": 10,
"vpe": 1,
"unit": "Stk.",
"articleName": "Einhängesieb Stahl",
"liefArtNr": "ES-100",
"mwst": "19",
"eknetto": 450,
"page": 1
}
]
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No file uploaded, unsupported file type, or OpenRouter key not configured — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -4,48 +4,58 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Subscribe to update signals (SSE) Subscribe to update signals (SSE)
Opens a Server-Sent Events stream for the authenticated user's Opens a Server-Sent Events stream for the authenticated user.
**selected company**. Clients receive named signals when another Can be opened before a company is selected (`selectedCompanyId == null`);
request successfully mutates shared data for that company when a company is selected or changed, the stream receives `user`
(including the same browser's other tabs). (`{ selectedCompanyId }`) and `company` (`{ companyId }`) events and
updates its company scope in-place.
Clients also receive named signals when another request successfully
mutates shared data for the active company (including the same
browser's other tabs).
Use `EventSource` with credentials so the session cookie is sent: Use `EventSource` with credentials so the session cookie is sent:
```js ```js
const es = new EventSource('https://…/api/events', { withCredentials: true }); const es = new EventSource('https://…/api/events', { withCredentials: true });
es.addEventListener('user', () => { refetchAuthMe(); });
es.addEventListener('company', () => { refetchCompany(); });
es.addEventListener('category', () => { refetchCategoryTree(); }); es.addEventListener('category', () => { refetchCategoryTree(); });
es.addEventListener('articles', () => { refetchArticleIndex(); }); es.addEventListener('articles', () => { refetchArticleIndex(); });
es.addEventListener('article-42000045', () => { refetchArticle('42000045'); }); es.addEventListener('article-42000045', () => { refetchArticle('42000045'); });
``` ```
Keep-alive comments (`: heartbeat`) are sent roughly every 20s. Keep-alive comments (`: heartbeat`) are sent roughly every 20s.
Reconnect after changing the selected company (the stream is bound
to the company at connect time).
## Signals ## Signals
Every SSE event uses the event name as the signal id; payloads are Every SSE event uses the event name as the signal id; payloads are
JSON and most events send an empty object `{}`. Only `brand` emits a JSON and most events send an empty object `{}`. `user` emits
richer payload. `{ selectedCompanyId }`, `company` emits `{ companyId }`, and `brand`
emits `{ sender, brandId }`.
| Signal | Payload | Emitted after successful | | Signal | Payload | Emitted after successful |
|--------|---------|--------------------------| |--------|---------|--------------------------|
| `user` | `{ selectedCompanyId }` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}` |
| `company` | `{ companyId }` | `PUT /auth/selected-company`, `POST /company`, `PUT /company/{id}`, `DELETE /company/{id}` |
| `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` | | `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` |
| `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations | | `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations |
| `article-<sku>` | `{}` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, supplier/article-property mutations affecting the article | | `article-<sku>` | `{}` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, supplier/article-property mutations affecting the article |
| `stock` | `{}` | `POST /stock/adjust` | | `stock` | `{}` | `POST /stock/adjust`, `POST /company/import` (also `category`, `articles`, `brand`, `attributename`, `warehouse`, `carrier`, `supplier`, `order`, `shipment`, `ledger`) |
| `ledger` | `{}` | `POST /stock/adjust` | | `ledger` | `{}` | `POST /stock/adjust` |
| `attributename` | `{}` | `POST /attributename`, `PUT /attributename/order`, `PATCH /attributename/{id}`, `DELETE /attributename/{id}` | | `attributename` | `{}` | `POST /attributename`, `PUT /attributename/order`, `PATCH /attributename/{id}`, `DELETE /attributename/{id}` |
| `propertyname` | `{}` | `POST /propertyname`, `PUT /propertyname/order`, `PATCH /propertyname/{id}`, `DELETE /propertyname/{id}` | | `propertyname` | `{}` | `POST /propertyname`, `PUT /propertyname/order`, `PATCH /propertyname/{id}`, `DELETE /propertyname/{id}` |
| `labeltemplate` | `{}` | `POST /labeltemplate`, `PUT /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `DELETE /labeltemplate/{id}` | | `labeltemplate` | `{}` | `POST /labeltemplate`, `PUT /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `DELETE /labeltemplate/{id}` |
| `printer` | `{}` | `POST /printer`, `PUT /printer/{id}`, `PATCH /printer/{id}`, `DELETE /printer/{id}` | | `printer` | `{}` | `POST /printer`, `PUT /printer/{id}`, `PATCH /printer/{id}`, `DELETE /printer/{id}` |
| `printeragent` | `{}` | `PUT /printeragent`, `PATCH /printeragent/{id}`, `DELETE /printeragent/{id}`, printer agent connect/disconnect/state |
| `agentprinter` | `{ printers }` or `{}` | `PATCH /agentprinter/{id}`, `PUT /agentprinter/order`, `DELETE /agentprinter/{id}`, agent state updates / printer discovery |
| `companyuser` | `{}` | `POST /companyuser`, `PATCH /companyuser/{id}`, `DELETE /companyuser/{id}` |
| `autoinvite` | `{}` | `POST /autoinvite`, `DELETE /autoinvite/{id}` |
| `warehouse` | `{}` | `POST /warehouse`, `PUT /warehouse/{id}`, `PATCH /warehouse/{id}`, `DELETE /warehouse/{id}` | | `warehouse` | `{}` | `POST /warehouse`, `PUT /warehouse/{id}`, `PATCH /warehouse/{id}`, `DELETE /warehouse/{id}` |
| `supplier` | `{}` | `POST /supplier`, `PATCH /supplier/{id}`, `DELETE /supplier/{id}` |
| `vfs` | `{}` | VFS uploads and filesystem mutations such as `POST /vfs/upload`, `POST /vfs/mkdir`, `POST /vfs/rename`, `PUT /vfs/{*path}`, `DELETE /vfs/{*path}` | | `vfs` | `{}` | VFS uploads and filesystem mutations such as `POST /vfs/upload`, `POST /vfs/mkdir`, `POST /vfs/rename`, `PUT /vfs/{*path}`, `DELETE /vfs/{*path}` |
| `modelconversation` | `{}` | `POST /modelconversation`, `DELETE /modelconversation/{id}` | | `modelconversation` | `{}` | `POST /modelconversation`, `DELETE /modelconversation/{id}` |
| `modelconversation-<id>` | `{}` | Conversation-specific updates on the same model conversation thread; the concrete ID is used in the event name | | `modelconversation-<id>` | `{}` | Conversation-specific updates on the same model conversation thread; the concrete ID is used in the event name |
| `brand` | `{ sender, brandId }` | `POST /brand`, `PATCH /brand/{id}`, `DELETE /brand/{id}`, `POST /brand/{id}/image`, `DELETE /brand/{id}/image` | | `brand` | `{ sender, brandId }` | `POST /brand`, `PATCH /brand/{id}`, `DELETE /brand/{id}`, `POST /brand/{id}/image`, `DELETE /brand/{id}/image` |
| `draft` | `{ sender, action, name, content }` | Draft update on `drafts/quote`, `drafts/order`, `drafts/delivery` (`sender` echoes the caller's `x-client-id`) |
| `draft:request` | `{ requestId, action, name, target, sender }` | Server request to frontend to read a draft or list drafts |
The `brand` payload is `{ sender, brandId }`: `sender` echoes the The `brand` payload is `{ sender, brandId }`: `sender` echoes the
caller's `x-client-id` header (empty string if absent) so the sender caller's `x-client-id` header (empty string if absent) so the sender
@@ -62,14 +72,15 @@ runtime (for example `article-42000045` and `modelconversation-123`).
SSE stream (`text/event-stream`). Each signal is an SSE event SSE stream (`text/event-stream`). Each signal is an SSE event
whose name is the signal id. Most signals send `{}` as data; whose name is the signal id. Most signals send `{}` as data;
`brand` sends `{ "sender": "<x-client-id>", "brandId": 12 }`. `user` sends `{ "selectedCompanyId": 1 }`, `company` sends
`{ "companyId": 1 }`, and `brand` sends
`{ "sender": "<x-client-id>", "brandId": 12 }`.
## Errors ## Errors
| Status | Description | | Status | Description |
|--------|-------------| |--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` |
--- ---

View File

@@ -4,11 +4,11 @@ Public. No cookie required.
Database health check Database health check
Verifies the database client can establish a connection. Verifies database connectivity and transaction execution by updating a healthcheck sequence.
## Response `200` ## Response `200`
Database reachable Database reachable and healthy
```json ```json
{ {

View File

@@ -0,0 +1,51 @@
# `GET /health/latency`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Health latency history
Returns the database healthcheck latency (ms), each series oldest first with unix-ms timestamps. By default (`range=3h`) only `raw` is returned, the per-minute samples of the last 3 hours. With `range=all` the 30-day `avg` and `max` series (30-minute aggregates over the last 4 hours of samples) are included as well. Requires a valid JWT; no company selection needed.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `range` | no | string | `3h` (default) returns only the per-minute samples of the last 3 hours; `all` also includes the 30-day avg and max series. |
## Response `200`
Latency series
```json
{
"raw": [
{
"timestamp": 1,
"value": 1
}
],
"avg": [
{
"timestamp": 1,
"value": 1
}
],
"max": [
{
"timestamp": 1,
"value": 1
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 401 | Unauthorized |
| 500 | Redis read failed |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,57 @@
# `POST /labeltemplate/testprint`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Test print label template
Dispatches a test print job for a label template or unsaved canvas objects to a specified ZPL printer.
Placeholders in text and barcodes are interpolated using a sample article or the specified article SKU.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `agentprinterId` | yes | integer | Agent printer ID |
| `templateId` | no | integer | |
| `objects` | no | any[] | |
| `width` | no | integer | |
| `height` | no | integer | |
| `dpi` | no | integer | |
| `sku` | no | string | |
| `quantity` | no | integer | |
```json
{
"agentprinterId": 1,
"templateId": 1,
"objects": [],
"width": 1,
"height": 1,
"dpi": 1,
"sku": "…",
"quantity": 1
}
```
## Response `200`
Print job created
```json
{
"success": true
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid parameters |
| 404 | Printer or template not found |
| 500 | Server error |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -1,63 +0,0 @@
# `POST /ledger`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Add ledger entry
Appends a new entry to the append-only ledger table.
`hash_version` is fixed to 1. `prev_hash` is cryptographically chained:
for the genesis row it defaults to `TODO_TSE_BASED_INITHASH`, and for subsequent rows
it is the SHA-512 hex hash of the canonical JSON representation of the previous highest-id entry.
Requires authentication and membership in the company owning the referenced manifest article.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `manifestarticle_id` | yes | integer | References manifestarticle.id |
| `transaction_type` | yes | string | Transaction identifier / type (max 100 chars) |
| `amount` | yes | number | Movement amount |
| `transaction_text` | yes | string | Transaction memo / details |
```json
{
"manifestarticle_id": 1,
"transaction_type": "…",
"amount": 1,
"transaction_text": "…"
}
```
## Response `201`
Ledger entry added successfully
```json
{
"ledger": {
"id": 1,
"manifestarticle_id": 1,
"transaction_type": "…",
"amount": 1,
"transaction_text": "…",
"hash_version": 1,
"prev_hash": "…",
"sku": "…",
"created_at": "2026-08-10T00:00:00.000Z"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid ledger entry or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Manifest article or company not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,54 @@
# `GET /ledger/history`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Query company-wide ledger history
Queries ledger entries across all articles for the authenticated user's selected company.
Supports pagination, search filter, and date cutoff via `seekMonths`.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `offset` | no | integer | Paging offset |
| `limit` | no | integer | Page size |
| `search` | no | string | Optional search filter across SKU, article name, manifest number, external number, transaction type, and transaction text |
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -1) |
## Response `200`
Paged ledger history list
```json
{
"ledger": [
{
"id": 1,
"date": "2026-08-10T00:00:00.000Z",
"sku": "…",
"name": "…",
"transaction_type": "…",
"transaction_text": "…",
"amount": 1
}
],
"total": 1,
"unfilteredTotal": 1,
"offset": 1,
"limit": 1
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Validation error or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -21,7 +21,7 @@ List of conversations
"created_at": "…", "created_at": "…",
"updated_at": "…", "updated_at": "…",
"state": "idle", "state": "idle",
"started_by": "…", "started_by": 1,
"first_message": "…" "first_message": "…"
} }
] ]

View File

@@ -38,7 +38,7 @@ Created conversation
"created_at": "…", "created_at": "…",
"updated_at": "…", "updated_at": "…",
"state": "idle", "state": "idle",
"started_by": "…", "started_by": 1,
"first_message": "…" "first_message": "…"
} }
} }

View File

@@ -26,7 +26,7 @@ Conversation details
"created_at": "…", "created_at": "…",
"updated_at": "…", "updated_at": "…",
"state": "idle", "state": "idle",
"started_by": "…", "started_by": 1,
"first_message": "…" "first_message": "…"
} }
} }

View File

@@ -39,7 +39,8 @@ List of shipments
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shippings": [ "shippings": [
@@ -48,6 +49,7 @@ List of shipments
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -8,8 +8,9 @@ Creates a new shipment manifest (Lieferschein) for an order containing one, some
If `lines` is omitted or empty, all remaining unshipped items from the order are included automatically. If `lines` is omitted or empty, all remaining unshipped items from the order are included automatically.
On individual line items, `amount` is optional and defaults to the remaining unshipped quantity for that article. On individual line items, `amount` is optional and defaults to the remaining unshipped quantity for that article.
Auto-assigns the shipment number formatted as `<order_number><shipment_prefix><index>`, e.g. `AUF-000001-L-1`. Auto-assigns the shipment number formatted as `<order_number><shipment_prefix><index>`, e.g. `AUF-000001-L-1`.
Automatically books shipment lines out on the ledger and deducts warehouse stock for stock-tracked articles.
Updates the materialized `ordersummary` table (`shipped_quantity`, `is_shipped`) and emits SSE events Updates the materialized `ordersummary` table (`shipped_quantity`, `is_shipped`) and emits SSE events
(`order`, `shipment`) so that the order list and summary tables are rerendered in real time. (`order`, `shipment`, `stock`, `ledger`, `articles`) so that inventory and summaries are rerendered in real time.
## Path parameters ## Path parameters
@@ -18,14 +19,16 @@ Updates the materialized `ordersummary` table (`shipped_quantity`, `is_shipped`)
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `id` | yes | integer | Order ID | | `id` | yes | integer | Order ID |
## Body (JSON) — _optional_ ## Body (JSON)
| Field | Required | Type | Description | | Field | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `date` | no | string | Optional shipment date (defaults to current date) | | `date` | no | string | Optional shipment date (defaults to current date) |
| `warehouseId` | no | integer | Optional target warehouse ID to deduct stock from (defaults to company default warehouse) |
| `externalNumber` | no | string | Optional external delivery note reference (defaults to order.externalNumber or order.number) | | `externalNumber` | no | string | Optional external delivery note reference (defaults to order.externalNumber or order.number) |
| `lines` | no | object[] | Optional positions to ship. If omitted, all remaining unshipped items are shipped. | | `lines` | yes | object[] | Positions to ship |
| `lines[].sku` | yes | string | Article SKU | | `lines[].sku` | no | string | Article SKU (omit for SKU-less order lines) |
| `lines[].orderLineId` | no | integer | Order line id (`lines[].id` of the order); required for SKU-less lines, ignored when `sku` is given |
| `lines[].amount` | no | number | Quantity to ship. Defaults to remaining unshipped quantity for this SKU. | | `lines[].amount` | no | number | Quantity to ship. Defaults to remaining unshipped quantity for this SKU. |
| `lines[].name` | no | string | | | `lines[].name` | no | string | |
| `lines[].pu` | no | number | | | `lines[].pu` | no | number | |
@@ -37,10 +40,12 @@ Updates the materialized `ordersummary` table (`shipped_quantity`, `is_shipped`)
```json ```json
{ {
"date": "2026-08-10", "date": "2026-08-10",
"warehouseId": 1,
"externalNumber": "…", "externalNumber": "…",
"lines": [ "lines": [
{ {
"sku": "…", "sku": "…",
"orderLineId": 1,
"amount": 1, "amount": 1,
"name": "…", "name": "…",
"pu": 1, "pu": 1,
@@ -79,7 +84,8 @@ Created shipment
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shippings": [ "shippings": [
@@ -88,6 +94,7 @@ Created shipment
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -120,10 +127,10 @@ Created shipment
| Status | Description | | Status | Description |
|--------|-------------| |--------|-------------|
| 400 | Validation error or article not in order — `{"error":"…"}` | | 400 | Validation error, article not in order, or article not shippable — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` |
| 404 | Order not found — `{"error":"…"}` | | 404 | Order not found — `{"error":"…"}` |
| 409 | Cumulative shipment amount exceeds ordered amount, or all items are already shipped — `{"error":"…"}` | | 409 | Cumulative shipment amount exceeds ordered amount, or insufficient stock — `{"error":"…"}` |
--- ---

View File

@@ -24,6 +24,7 @@ List of shippings
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -19,14 +19,18 @@ If `state` is `shipped`, stock is adjusted via the ledger and article history is
| Field | Required | Type | Description | | Field | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `carrierId` | yes | integer | Carrier ID |
| `type` | yes | string | | | `type` | yes | string | |
| `state` | yes | string | | | `state` | yes | string | |
| `trackingCode` | no | string | Carrier parcel tracking code |
| `warehouseId` | no | integer | Optional warehouse ID for stock deduction when state is shipped | | `warehouseId` | no | integer | Optional warehouse ID for stock deduction when state is shipped |
```json ```json
{ {
"carrierId": 1,
"type": "…", "type": "…",
"state": "…", "state": "…",
"trackingCode": "…",
"warehouseId": 1 "warehouseId": 1
} }
``` ```
@@ -42,6 +46,7 @@ Created shipping
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -6,8 +6,8 @@ Create order
Creates a sales order with header and lines in one transaction. Creates a sales order with header and lines in one transaction.
`date` is the document date (expected delivery). `date` is the document date (expected delivery).
`entityId` must be a company customer (not Lieferanten), or null. `entityId` must be a company customer (not Lieferanten).
Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`. Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`; `shippable` defaults to `true` (`false` = not shippable, excluded from order completeness).
## Body (JSON) ## Body (JSON)
@@ -16,7 +16,7 @@ Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu`
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `number` | yes | string | | | `number` | yes | string | |
| `date` | yes | string | Document date / expected delivery | | `date` | yes | string | Document date / expected delivery |
| `entityId` | no | integer | Customer entity (manifest.entity_id) | | `entityId` | yes | integer | Customer entity (manifest.entity_id) |
| `externalNumber` | no | string | External customer reference number (manifest.external_number) | | `externalNumber` | no | string | External customer reference number (manifest.external_number) |
| `lines` | yes | ManifestLineInput[] | | | `lines` | yes | ManifestLineInput[] | |
@@ -62,7 +62,8 @@ Created order
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shipping": { "shipping": {
@@ -70,6 +71,7 @@ Created order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -80,6 +82,7 @@ Created order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -129,7 +132,8 @@ Created order
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shippings": [ "shippings": [
@@ -138,6 +142,7 @@ Created order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -36,7 +36,8 @@ Order
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shipping": { "shipping": {
@@ -44,6 +45,7 @@ Order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -54,6 +56,7 @@ Order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -103,7 +106,8 @@ Order
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shippings": [ "shippings": [
@@ -112,6 +116,7 @@ Order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -45,7 +45,8 @@ Order list
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shipping": { "shipping": {
@@ -53,6 +54,7 @@ Order list
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -63,6 +65,7 @@ Order list
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -112,7 +115,8 @@ Order list
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shippings": [ "shippings": [
@@ -121,6 +125,7 @@ Order list
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -56,7 +56,8 @@ Updated order
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shipping": { "shipping": {
@@ -64,6 +65,7 @@ Updated order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -74,6 +76,7 @@ Updated order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"
@@ -123,7 +126,8 @@ Updated order
"net": 1, "net": 1,
"rebate": 1, "rebate": 1,
"taxrate": 1, "taxrate": 1,
"comment": "…" "comment": "…",
"shippable": true
} }
], ],
"shippings": [ "shippings": [
@@ -132,6 +136,7 @@ Updated order
"manifestId": 1, "manifestId": 1,
"number": "…", "number": "…",
"trackingCode": "…", "trackingCode": "…",
"carrierId": 1,
"carrier": "…", "carrier": "…",
"type": "…", "type": "…",
"state": "…" "state": "…"

View File

@@ -0,0 +1,34 @@
# `GET /v1/cimage`
Protected. Requires a valid session cookie (`credentials: 'include'`).
POS category image delivery
Returns category image by hash (`path` query param) scaled to preview size (padded to 1:1 with white background).
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `path` | yes | string | SHA-256 hash of the category image |
| `size` | no | integer | Max dimension (width & height, capped to 200) |
| `w` | no | integer | Max width (capped to 200) |
| `h` | no | integer | Max height (capped to 200) |
| `format` | no | string | Optional target format (defaults to jpeg/png or client Accept preference) |
## Response `200`
Resized image binary
## Errors
| Status | Description |
|--------|-------------|
| 304 | Image not modified |
| 400 | Missing required query parameter 'path' |
| 404 | Image not found |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -15,6 +15,7 @@ Special API for POS bind and sync.
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `authCode` | yes | string | Discovery code (1-4 digits) or pairing PIN (6 digits) | | `authCode` | yes | string | Discovery code (1-4 digits) or pairing PIN (6 digits) |
| `name` | no | string | Client device name or serial identifier (default 'JTL-POS') | | `name` | no | string | Client device name or serial identifier (default 'JTL-POS') |
| `clientversion` | no | string | Optional client app version (for future POS clients during pairing) |
## Response `200` ## Response `200`

View File

@@ -4,20 +4,21 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
POS sync initialization counts POS sync initialization counts
Returns entity change counts since the provided `lastChanged*` unix timestamp cursors Returns entity change counts since the provided `lastChanged*` sync queue ID cursors
for the authenticated POS terminal (`Authorization: Bearer <authToken>`). (`posterminalsyncqueue.id` / `posterminalobituary.id`) for the authenticated POS terminal
(`Authorization: Bearer <authToken>`).
## Query parameters ## Query parameters
| Param | Required | Type | Description | | Param | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `lastChangedProduct` | no | integer | Unix timestamp of last synced product update | | `lastChangedProduct` | no | integer | Sequential ID cursor of last synced product entry (`posterminalsyncqueue.id`) |
| `lastChangedCategory` | no | integer | Unix timestamp of last synced category update | | `lastChangedCategory` | no | integer | Sequential ID cursor of last synced category entry (`posterminalsyncqueue.id`) |
| `lastChangedCustomer` | no | integer | Unix timestamp of last synced customer update | | `lastChangedCustomer` | no | integer | Sequential ID cursor of last synced customer entry (`posterminalsyncqueue.id`) |
| `lastChangedCustomerGroup` | no | integer | Unix timestamp of last synced customer group update | | `lastChangedCustomerGroup` | no | integer | Sequential ID cursor of last synced customer group entry (`posterminalsyncqueue.id`) |
| `lastChangedCompositeProduct` | no | integer | Unix timestamp of last synced composite product update | | `lastChangedCompositeProduct` | no | integer | Sequential ID cursor of last synced composite product entry (`posterminalsyncqueue.id`) |
| `lastChangedDeletedEntity` | no | integer | Unix timestamp of last synced deleted entity | | `lastChangedDeletedEntity` | no | integer | Sequential ID cursor of last synced deleted entity (`posterminalobituary.id`) |
## Response `200` ## Response `200`

View File

@@ -12,6 +12,7 @@ Returns product image by hash (`path` query param) scaled to preview size.
| Param | Required | Type | Description | | Param | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `path` | yes | string | SHA-256 hash of the product image | | `path` | yes | string | SHA-256 hash of the product image |
| `size` | no | integer | Max dimension (width & height) |
| `w` | no | integer | Max width | | `w` | no | integer | Max width |
| `h` | no | integer | Max height | | `h` | no | integer | Max height |
| `format` | no | string | Optional target format (defaults to jpeg/png or client Accept preference) | | `format` | no | string | Optional target format (defaults to jpeg/png or client Accept preference) |

View File

@@ -22,6 +22,9 @@ Posterminal list
"clientserial": "…", "clientserial": "…",
"rootCategoryId": 1, "rootCategoryId": 1,
"syncRootArticles": true, "syncRootArticles": true,
"sort": 1,
"enabled": true,
"clientversion": "…",
"lastseenAt": "…", "lastseenAt": "…",
"updatedAt": "…", "updatedAt": "…",
"categoryIds": [ "categoryIds": [

View File

@@ -2,9 +2,9 @@
Protected. Requires a valid session cookie (`credentials: 'include'`). Protected. Requires a valid session cookie (`credentials: 'include'`).
Disable category on terminal Disable category and subcategories on terminal
Removes a single category's visibility on a terminal (idempotent). Removes a category and all its descendant subcategories from a terminal (idempotent).
## Path parameters ## Path parameters

View File

@@ -2,9 +2,9 @@
Protected. Requires a valid session cookie (`credentials: 'include'`). Protected. Requires a valid session cookie (`credentials: 'include'`).
Enable category on terminal Enable category and subcategories on terminal
Toggles a single category's visibility on a terminal (idempotent). Enables a category and all its descendant subcategories on a terminal (idempotent).
## Path parameters ## Path parameters

View File

@@ -19,6 +19,8 @@ Updates a POS terminal configuration (name, root category, sync settings, or pai
| `name` | no | string | | | `name` | no | string | |
| `rootCategoryId` | no | integer | | | `rootCategoryId` | no | integer | |
| `syncRootArticles` | no | boolean | | | `syncRootArticles` | no | boolean | |
| `sort` | no | integer | |
| `enabled` | no | boolean | |
| `pairingpin` | no | string | | | `pairingpin` | no | string | |
```json ```json
@@ -44,6 +46,9 @@ Updated posterminal
"clientserial": "…", "clientserial": "…",
"rootCategoryId": 1, "rootCategoryId": 1,
"syncRootArticles": true, "syncRootArticles": true,
"sort": 1,
"enabled": true,
"clientversion": "…",
"lastseenAt": "…", "lastseenAt": "…",
"updatedAt": "…", "updatedAt": "…",
"categoryIds": [ "categoryIds": [

View File

@@ -15,6 +15,8 @@ The pairingpin is randomly generated server-side and returned in the response.
| `name` | yes | string | | | `name` | yes | string | |
| `rootCategoryId` | no | integer | | | `rootCategoryId` | no | integer | |
| `syncRootArticles` | no | boolean | | | `syncRootArticles` | no | boolean | |
| `sort` | no | integer | |
| `enabled` | no | boolean | |
```json ```json
{ {
@@ -37,6 +39,9 @@ Created posterminal with generated pairingpin
"clientserial": "…", "clientserial": "…",
"rootCategoryId": 1, "rootCategoryId": 1,
"syncRootArticles": true, "syncRootArticles": true,
"sort": 1,
"enabled": true,
"clientversion": "…",
"lastseenAt": "…", "lastseenAt": "…",
"updatedAt": "…", "updatedAt": "…",
"categoryIds": [ "categoryIds": [

View File

@@ -0,0 +1,37 @@
# `GET /printer/{id}/state`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get live state for a printer
Retrieves live operational status for a single printer by configured printer ID,
serial number, or device identifier for the authenticated user's selected company.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | string | Configured printer ID, serial number, or device identifier |
## Response `200`
Live printer state
```json
{
"printer": {}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected or invalid ID — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 404 | Printer not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,39 @@
# `GET /printer/state`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get live printer states
Retrieves real-time operational status and telemetry for all discovered printers
reported by active printer agents for the authenticated user's selected company.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `serial` | no | string | Optional serial number filter |
| `agentId` | no | integer | Optional agent ID filter |
## Response `200`
Live printer states
```json
{
"printers": [
{}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,44 @@
# `GET /printeragent`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List printer agents
Lists printer agents for the authenticated user's selected company.
## Response `200`
Printer agent list
```json
{
"printeragents": [
{
"id": 1,
"companyId": 1,
"name": "…",
"token": "…",
"pairingpin": "…",
"pairedAt": "…",
"agentversion": "…",
"lastseenAt": "…",
"updatedAt": "…",
"sort": 1,
"enabled": true
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected or invalid input — `{"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,58 @@
# `PUT /printeragent`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Create printer agent
Creates an unpaired printer agent for the selected company.
The pairingpin is randomly generated server-side and returned in the response.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `name` | yes | string | |
| `sort` | no | integer | |
| `enabled` | no | boolean | |
```json
{
"name": "Bar printer"
}
```
## Response `200`
Created printer agent with generated pairingpin
```json
{
"printeragent": {
"id": 1,
"companyId": 1,
"name": "…",
"token": "…",
"pairingpin": "…",
"pairedAt": "…",
"agentversion": "…",
"lastseenAt": "…",
"updatedAt": "…",
"sort": 1,
"enabled": true
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected or invalid input — `{"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,30 @@
# `DELETE /printeragent/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete printer agent
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Response `204`
Deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected or invalid id — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Printeragent not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,64 @@
# `PATCH /printeragent/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Update printer agent
Updates name and/or display order (sort) of a printer agent.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `name` | no | string | |
| `sort` | no | integer | |
| `enabled` | no | boolean | |
```json
{
"name": "…",
"sort": 1,
"enabled": true
}
```
## Response `200`
Updated printer agent
```json
{
"printeragent": {
"id": 1,
"companyId": 1,
"name": "…",
"token": "…",
"pairingpin": "…",
"pairedAt": "…",
"agentversion": "…",
"lastseenAt": "…",
"updatedAt": "…",
"sort": 1,
"enabled": true
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected or invalid input — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Printeragent not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,46 @@
# `GET /printerjob`
Protected. Requires a valid session cookie (`credentials: 'include'`).
List printer jobs
Lists printer jobs for the selected company, optionally filtered by agent printer or state.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `agentprinterId` | no | integer | Optional agent printer ID filter |
| `state` | no | string | Optional job state filter |
## Response `200`
List of printer jobs
```json
{
"printerjobs": [
{
"id": 1,
"agentprinterId": 1,
"printeragentId": 1,
"type": "zpl",
"state": "pending",
"data": "…",
"createdAt": "2026-08-10T00:00:00.000Z",
"completedAt": "2026-08-10T00:00:00.000Z"
}
]
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | No company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,46 @@
# `POST /printerjob`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Create print job
Creates a new print job for an agent printer. Notifies the agent in real time.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `agentprinterId` | yes | integer | |
| `type` | no | string | |
| `data` | yes | string | |
```json
{
"agentprinterId": 1,
"type": "zpl",
"data": "…"
}
```
## Response `201`
Printer job created
```json
{
"printerjob": {
"id": 1,
"agentprinterId": 1,
"printeragentId": 1,
"type": "zpl",
"state": "pending",
"data": "…",
"createdAt": "2026-08-10T00:00:00.000Z",
"completedAt": "2026-08-10T00:00:00.000Z"
}
}
```
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,39 @@
# `DELETE /printerjob/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Cancel and delete printer job
Cancels and deletes a print job. If the job was pending or processing, sends a cancellation
event to the connected printer agent before deletion.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Response `200`
Job cancelled and deleted
```json
{
"success": true,
"deletedJob": {
"id": 1,
"agentprinterId": 1,
"printeragentId": 1,
"type": "zpl",
"state": "pending",
"data": "…",
"createdAt": "2026-08-10T00:00:00.000Z",
"completedAt": "2026-08-10T00:00:00.000Z"
}
}
```
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,44 @@
# `GET /printerjob/{id}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Get printer job
Retrieves details of a specific printer job by ID.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Response `200`
Printer job details
```json
{
"printerjob": {
"id": 1,
"agentprinterId": 1,
"printeragentId": 1,
"type": "zpl",
"state": "pending",
"data": "…",
"createdAt": "2026-08-10T00:00:00.000Z",
"completedAt": "2026-08-10T00:00:00.000Z"
}
}
```
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 404 | Printer job not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,37 @@
# `POST /printerjob/{id}/cancel`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Cancel printer job
Marks a print job as cancelled and notifies the connected agent in real time.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | |
## Response `200`
Printer job cancelled
```json
{
"success": true,
"printerjob": {
"id": 1,
"agentprinterId": 1,
"printeragentId": 1,
"type": "zpl",
"state": "pending",
"data": "…",
"createdAt": "2026-08-10T00:00:00.000Z",
"completedAt": "2026-08-10T00:00:00.000Z"
}
}
```
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,48 @@
# `POST /printerjob/cancel-all`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Cancel all pending and processing printer jobs
Cancels all queued or in-flight print jobs for the selected company, optionally filtered by agentprinterId.
Notifies connected agents in real time.
## Body (JSON)
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `agentprinterId` | no | integer | |
```json
{
"agentprinterId": 1
}
```
## Response `200`
All matching jobs cancelled
```json
{
"success": true,
"cancelledCount": 1,
"jobs": [
{
"id": 1,
"agentprinterId": 1,
"printeragentId": 1,
"type": "zpl",
"state": "pending",
"data": "…",
"createdAt": "2026-08-10T00:00:00.000Z",
"completedAt": "2026-08-10T00:00:00.000Z"
}
]
}
```
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

Some files were not shown because too many files have changed in this diff Show More