Compare commits
85 Commits
7fcaca318a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a45b43f50 | ||
|
|
9f61c7dee2 | ||
|
|
980578e1e4 | ||
|
|
ce4dc15aac | ||
|
|
da785da2dd | ||
|
|
24563964a6 | ||
|
|
44e91a6973 | ||
|
|
de2957acf1 | ||
|
|
17df88ced5 | ||
|
|
d3209d73c1 | ||
|
|
518106ede2 | ||
|
|
833cee5040 | ||
|
|
aeb79c1acb | ||
|
|
ff185a59dd | ||
|
|
689f12ba69 | ||
|
|
bf081f875d | ||
|
|
8f9c941699 | ||
|
|
e6c15dd053 | ||
|
|
8a5a307c50 | ||
|
|
43d7049a7b | ||
|
|
c60b899c4d | ||
|
|
e8a5322775 | ||
|
|
cb30352b57 | ||
|
|
5d6b31de15 | ||
|
|
8827d6fc14 | ||
|
|
16f70603bc | ||
|
|
ce7f5aad2a | ||
|
|
26adfc5aa9 | ||
|
|
0480a5dcbe | ||
|
|
dd5c9646b3 | ||
|
|
5a0b603306 | ||
|
|
72d05a34d0 | ||
|
|
8fece9d916 | ||
|
|
5fac537268 | ||
|
|
61529023b0 | ||
|
|
2df7c59feb | ||
|
|
02301ff172 | ||
|
|
ab6fcc107f | ||
|
|
bc1340419c | ||
|
|
fbe3105dc9 | ||
|
|
e82ea15d02 | ||
|
|
527e55fec2 | ||
|
|
e1d27b44a7 | ||
|
|
a7258abf35 | ||
|
|
1b432cb6c6 | ||
|
|
0303f8121c | ||
|
|
d151bb5fe3 | ||
|
|
676a40d4fe | ||
|
|
005b48ff66 | ||
|
|
72e6e79d20 | ||
|
|
6a80473594 | ||
|
|
bcdfba1742 | ||
|
|
907ee998c0 | ||
|
|
158748c5e6 | ||
|
|
dc9fcdad96 | ||
|
|
116b51f0a9 | ||
|
|
c937bad151 | ||
|
|
1881830b0a | ||
|
|
01d6aa0e3f | ||
|
|
ff73750092 | ||
|
|
e7884c03ef | ||
|
|
1c5385061c | ||
|
|
1ac6435c3f | ||
|
|
80ecad6ddd | ||
|
|
97cae698db | ||
|
|
88ed2be452 | ||
|
|
b3368a53f8 | ||
|
|
e490ad78b5 | ||
|
|
41c532e2ca | ||
|
|
b6f68f04a4 | ||
|
|
2cc166bbec | ||
|
|
2dea4e86f1 | ||
|
|
74d3ba5aeb | ||
|
|
69388c789f | ||
|
|
a5a9e51898 | ||
|
|
5d6d07e8bf | ||
|
|
fb2dafd920 | ||
|
|
c07e4dc083 | ||
|
|
369fbb2a70 | ||
|
|
464021318a | ||
|
|
9929e322a3 | ||
|
|
ebbeb6437a | ||
|
|
3f2bdce5ad | ||
|
|
1a5e3f23e2 | ||
|
|
e83116c83d |
17
.dockerignore
Normal file
17
.dockerignore
Normal 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
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ logs/
|
|||||||
print_spool/
|
print_spool/
|
||||||
certs/
|
certs/
|
||||||
output/
|
output/
|
||||||
|
dist/
|
||||||
|
|||||||
25
Dockerfile
Normal file
25
Dockerfile
Normal 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"]
|
||||||
@@ -37,6 +37,15 @@ 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/history-list.md`](routes/article/history-list.md) | `GET /article/history` |
|
||||||
@@ -89,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` |
|
||||||
@@ -115,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}` |
|
||||||
@@ -130,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` |
|
||||||
@@ -163,6 +180,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
|||||||
| [`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}` |
|
||||||
@@ -218,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` |
|
||||||
@@ -240,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` |
|
||||||
@@ -275,6 +319,7 @@ 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/customer.md`](routes/v1/customer.md) | `GET /v1/customer` |
|
||||||
| [`routes/v1/customergroup.md`](routes/v1/customergroup.md) | `GET /v1/customergroup` |
|
| [`routes/v1/customergroup.md`](routes/v1/customergroup.md) | `GET /v1/customergroup` |
|
||||||
@@ -300,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).
|
||||||
|
|
||||||
@@ -309,12 +355,16 @@ 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}`, `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
| `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` |
|
||||||
| `carrier` | `POST /carrier`, `PUT /carrier/order`, `DELETE /carrier/{id}`, `PATCH /carrier/{id}` |
|
| `carrier` | `POST /carrier`, `PUT /carrier/order`, `DELETE /carrier/{id}`, `PATCH /carrier/{id}` |
|
||||||
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
|
| `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` |
|
| `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` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
| `ledger` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
||||||
@@ -328,6 +378,8 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
|
|||||||
| `shipping` | `POST /stock/ship` |
|
| `shipping` | `POST /stock/ship` |
|
||||||
| `stock` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
| `stock` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
||||||
| `subagentschedule` | `POST /subagentschedule`, `DELETE /subagentschedule/{id}`, `PUT /subagentschedule/{id}` |
|
| `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}` |
|
| `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}` |
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Dash API Routes Overview (All)
|
# Dash API Routes Overview (All)
|
||||||
|
|
||||||
Overview of all 268 API endpoints across all modules.
|
Overview of all 314 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -9,44 +9,48 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
|
|
||||||
### Reusable types
|
### Reusable types
|
||||||
|
|
||||||
- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T18[], externalNumber?: str|null, createdAt?: str, shipping?: T20|null, shippings?: T20[], payment?: T29|null, payments?: T29[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T9[] }`
|
- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, shipping?: T23|null, shippings?: T23[], payment?: T33|null, payments?: T33[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T10[] }`
|
||||||
- `T2` = `{ id: int, number: str, type: str, date: str, lines: T18[], shippings: T20[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T9[], state?: str }`
|
- `T2` = `{ id: int, number: str, type: str, date: str, lines: T20[], shippings: T23[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T10[], state?: str }`
|
||||||
- `T3` = `{ id: int, number: str, type: str, date: str, lines: T18[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T9[] }`
|
- `T3` = `{ id: int, number: str, type: str, date: str, lines: T20[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T10[] }`
|
||||||
- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T21[], files: T9[], externalNumber?: str|null, createdAt?: str, ledger?: T36 }`
|
- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T24[], files: T10[], externalNumber?: str|null, createdAt?: str, ledger?: T40 }`
|
||||||
- `T5` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T6|null }`
|
- `T5` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T6|null }`
|
||||||
- `T6` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T11, totalSizeBytes: int, durationMs: int }`
|
- `T6` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T11, totalSizeBytes: int, durationMs: int }`
|
||||||
- `T7` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T38|null, incoming?: { date: str, amount: num }[] }`
|
- `T7` = `{ id: int, name: str, pairingpin?: str|null, token?: str|null, pairedAt?: str|null, clientserial?: str|null, rootCategoryId?: int|null, syncRootArticles?: bool, sort?: int, enabled?: bool, clientversion?: str|null, lastseenAt?: str|null, updatedAt?: str|null, categoryIds?: int[] }`
|
||||||
- `T8` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T22[] }, thumb?: str|null }`
|
- `T8` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T42|null, incoming?: { date: str, amount: num }[] }`
|
||||||
- `T9` = `{ 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 }`
|
- `T9` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] }, thumb?: str|null }`
|
||||||
- `T10` = `{ id: int, name: str, pairingpin?: str|null, token?: str|null, pairedAt?: str|null, clientserial?: str|null, rootCategoryId?: int|null, syncRootArticles?: bool, lastseenAt?: str|null, updatedAt?: str|null, categoryIds?: int[] }`
|
- `T10` = `{ 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 }`
|
||||||
- `T11` = `{ vfs: T37, storage: T37, database: T31 }`
|
- `T11` = `{ vfs: T41, storage: T41, database: T35 }`
|
||||||
- `T12` = `{ 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 }`
|
- `T12` = `{ id: int, name: str, companyId?: int, token?: str|null, pairingpin?: str|null, pairedAt?: str|null, agentversion?: str|null, lastseenAt?: str|null, updatedAt?: str, sort?: int, enabled?: bool }`
|
||||||
- `T13` = `{ id: int, company_id: int, prompt: str, max_itr: int, cron: str, created_at: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, lastrun_at?: str|null }`
|
- `T13` = `{ id: int, printeragentId: int, type: str, enabled: bool, sort: int, model?: str|null, serial?: str|null, open?: bool, pause?: bool, error?: bool, online?: bool, labelLength?: int|null }`
|
||||||
- `T14` = `{ 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 }`
|
- `T14` = `{ 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 }`
|
||||||
- `T15` = `{ id: int, name: str, sort: int, exclusive: bool, values: T27[], articleCount?: int }`
|
- `T15` = `{ id: int, company_id: int, prompt: str, max_itr: int, cron: str, created_at: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, lastrun_at?: str|null }`
|
||||||
- `T16` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: int|null, first_message?: str|null }`
|
- `T16` = `{ 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 }`
|
||||||
- `T17` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }`
|
- `T17` = `{ id: int, name: str, sort: int, exclusive: bool, values: T30[], articleCount?: int }`
|
||||||
- `T18` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
|
- `T18` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: int|null, first_message?: str|null }`
|
||||||
- `T19` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
- `T19` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num, role: str }`
|
||||||
- `T20` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
|
- `T20` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
|
||||||
- `T21` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }`
|
- `T21` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
||||||
- `T22` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }`
|
- `T22` = `{ id: int, agentprinterId: int, type: str, state: str, data: str, createdAt: str, printeragentId?: int|null, completedAt?: str|null }`
|
||||||
- `T23` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }`
|
- `T23` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
|
||||||
- `T24` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
|
- `T24` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }`
|
||||||
- `T25` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }`
|
- `T25` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }`
|
||||||
- `T26` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
- `T26` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }`
|
||||||
- `T27` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
- `T27` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
|
||||||
- `T28` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
- `T28` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }`
|
||||||
- `T29` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
- `T29` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
||||||
- `T30` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
- `T30` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
||||||
- `T31` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }`
|
- `T31` = `{ id: int, googleAuthId: int, email: str, role: str, picture?: str|null }`
|
||||||
- `T32` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }`
|
- `T32` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
||||||
- `T33` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
- `T33` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
||||||
- `T34` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
- `T34` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
||||||
- `T35` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
- `T35` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }`
|
||||||
- `T36` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
- `T36` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }`
|
||||||
- `T37` = `{ name: str, sizeBytes: int, sourceExists: bool }`
|
- `T37` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
||||||
- `T38` = `{ net: num, taxId: int, taxRate?: num|null }`
|
- `T38` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
||||||
|
- `T39` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
||||||
|
- `T40` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
||||||
|
- `T41` = `{ name: str, sizeBytes: int, sourceExists: bool }`
|
||||||
|
- `T42` = `{ net: num, taxId: int, taxRate?: num|null }`
|
||||||
|
|
||||||
### Routes
|
### Routes
|
||||||
|
|
||||||
@@ -72,6 +76,39 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
|
|
||||||
|
### Section: /agent
|
||||||
|
|
||||||
|
#### Printer agent SSE stream — `GET /agent/events`
|
||||||
|
- Params: `query: token?: str, agentversion?: str`
|
||||||
|
- Returns: `200 SSE`
|
||||||
|
#### Pair printer agent via query parameters — `GET /agent/pair`
|
||||||
|
- Params: `query: authCode?: str, pairingpin?: str, serial?: str, model?: str, version?: str`
|
||||||
|
- Returns: `200`
|
||||||
|
#### Pair printer agent — `POST /agent/pair`
|
||||||
|
- Params: `body: { pairingpin: str, clientserial?: str, clientmodel?: str, agentversion?: str }`
|
||||||
|
- Returns: `200 { id: int, name: str, companyId: int, token: str, pairedAt: str, clientserial: str|null, clientmodel: str|null, agentversion: str|null, sort: int }`
|
||||||
|
#### Post agent state — `POST /agent/state`
|
||||||
|
- Params: `body: { state?: any }`
|
||||||
|
- Returns: `200 { success: bool, agent: T12 }`
|
||||||
|
|
||||||
|
### Section: /agentprinter
|
||||||
|
|
||||||
|
#### List agent printers — `GET /agentprinter`
|
||||||
|
- Params: `query: printeragentId?: int`
|
||||||
|
- Returns: `200 { agentprinters: T13[] }`
|
||||||
|
#### Reorder agent printers — `PUT /agentprinter/order`
|
||||||
|
- Params: `body: { agentprinterIds: int[], printeragentId?: int }`
|
||||||
|
- Returns: `200 { agentprinterIds: int[] }`
|
||||||
|
#### Delete agent printer — `DELETE /agentprinter/{id}`
|
||||||
|
- Params: `id: int`
|
||||||
|
- Returns: `204 void`
|
||||||
|
#### Get agent printer — `GET /agentprinter/{id}`
|
||||||
|
- Params: `id: int`
|
||||||
|
- Returns: `200 { agentprinter: T13 }`
|
||||||
|
#### Update agent printer — `PATCH /agentprinter/{id}`
|
||||||
|
- Params: `id: int, body: { enabled?: bool, sort?: int }`
|
||||||
|
- Returns: `200 { agentprinter: T13 }`
|
||||||
|
|
||||||
### Section: /article
|
### Section: /article
|
||||||
|
|
||||||
#### Create article in category — `POST /article`
|
#### Create article in category — `POST /article`
|
||||||
@@ -88,7 +125,7 @@ _none_
|
|||||||
- 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 { 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: T26, brand: T25|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: T7[], undeliveredOrders: { orderId: int, orderNumber: str, date: str, amount: num, customerName?: str|null }[] } }`
|
- 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: T29, brand: T28|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: T8[], 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: `204 void`
|
- Returns: `204 void`
|
||||||
@@ -126,8 +163,8 @@ _none_
|
|||||||
- Params: `sku: str, body: { eans: str[] }`
|
- Params: `sku: str, body: { eans: str[] }`
|
||||||
- Returns: `204 void`
|
- 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 }`
|
||||||
#### Add article image — `POST /article/{sku}/image`
|
#### Add article image — `POST /article/{sku}/image`
|
||||||
- Params: `sku: str, image/avif`
|
- Params: `sku: str, image/avif`
|
||||||
- Returns: `201 { image: { picId: int, sort: int, full: { w: int, h: int, size: int }, thumb: { w: int, h: int }, hash: str, thumbHash: str } }`
|
- Returns: `201 { image: { picId: int, sort: int, full: { w: int, h: int, size: int }, thumb: { w: int, h: int }, hash: str, thumbHash: str } }`
|
||||||
@@ -146,11 +183,11 @@ _none_
|
|||||||
#### 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: `204 void`
|
- 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: `204 void`
|
- 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`
|
||||||
@@ -163,10 +200,10 @@ _none_
|
|||||||
- 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: T7[] }`
|
- Returns: `200 { suppliers: T8[] }`
|
||||||
#### 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: T7 }`
|
- Returns: `201 { supplier: T8 }`
|
||||||
#### 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[] }`
|
||||||
@@ -175,7 +212,7 @@ _none_
|
|||||||
- 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: T7 }`
|
- Returns: `200 { supplier: T8 }`
|
||||||
#### 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: `204 void`
|
- Returns: `204 void`
|
||||||
@@ -187,19 +224,19 @@ _none_
|
|||||||
|
|
||||||
#### List attribute names — `GET /attributename`
|
#### List attribute names — `GET /attributename`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { attributenames: T15[] }`
|
- Returns: `200 { attributenames: T17[] }`
|
||||||
#### 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: T15 }`
|
- Returns: `201 { attributename: T17 }`
|
||||||
#### 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: T27[] }`
|
- Returns: `200 { values: T30[] }`
|
||||||
#### 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: T27 }`
|
- Returns: `201 { attributevalue: T30 }`
|
||||||
#### 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 }`
|
||||||
@@ -208,7 +245,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: T15 }`
|
- Returns: `200 { attributename: T17 }`
|
||||||
|
|
||||||
### Section: /attributevalue
|
### Section: /attributevalue
|
||||||
|
|
||||||
@@ -217,28 +254,40 @@ _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: T27 }`
|
- Returns: `200 { attributevalue: T30 }`
|
||||||
#### 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: T33[] } }`
|
- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T37[] } }`
|
||||||
#### Add or replace attribute value image — `POST /attributevalue/{id}/image`
|
#### Add or replace attribute value image — `POST /attributevalue/{id}/image`
|
||||||
- Params: `id: int, image/avif`
|
- Params: `id: int, image/avif`
|
||||||
- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T33[] } }`
|
- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T37[] } }`
|
||||||
|
|
||||||
### Section: /auth
|
### Section: /auth
|
||||||
|
|
||||||
#### Login with Google ID token — `POST /auth/login`
|
#### Login with Google ID token — `POST /auth/login`
|
||||||
- Params: `body: { idToken: str }`
|
- Params: `body: { idToken: str }`
|
||||||
- Returns: `200 { message: str, user: T32 }`
|
- Returns: `200 { message: str, user: T36 }`
|
||||||
#### Logout — `POST /auth/logout`
|
#### Logout — `POST /auth/logout`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { message: str }`
|
- Returns: `200 { message: str }`
|
||||||
#### Current user — `GET /auth/me`
|
#### Current user — `GET /auth/me`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { user: T32, googleClientId: str }`
|
- Returns: `200 { user: T36, googleClientId: str, schemaVersion: int, vhost: bool, vhostSettings: str }`
|
||||||
#### Set selected company — `PUT /auth/selected-company`
|
#### Set selected company — `PUT /auth/selected-company`
|
||||||
- Params: `body: { companyId: int }`
|
- Params: `body: { companyId: int }`
|
||||||
- Returns: `200 { user: T32 }`
|
- Returns: `200 { user: T36 }`
|
||||||
|
|
||||||
|
### Section: /autoinvite
|
||||||
|
|
||||||
|
#### 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: /backup
|
### Section: /backup
|
||||||
|
|
||||||
@@ -259,7 +308,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: T33[], gsprpinfo?: str|null } }`
|
- Returns: `201 { brand: { id: int, name: str, pics: T37[], 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`
|
||||||
@@ -268,19 +317,19 @@ _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: T33[] } }`
|
- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T37[] } }`
|
||||||
#### Add or replace brand image — `POST /brand/{id}/image`
|
#### Add or replace brand image — `POST /brand/{id}/image`
|
||||||
- Params: `id: int, image/avif`
|
- Params: `id: int, image/avif`
|
||||||
- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T33[] } }`
|
- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T37[] } }`
|
||||||
|
|
||||||
### Section: /carrier
|
### Section: /carrier
|
||||||
|
|
||||||
#### List carriers — `GET /carrier`
|
#### List carriers — `GET /carrier`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { carriers: T30[] }`
|
- Returns: `200 { carriers: T34[] }`
|
||||||
#### Create carrier — `POST /carrier`
|
#### Create carrier — `POST /carrier`
|
||||||
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||||
- Returns: `201 { carrier: T30 }`
|
- Returns: `201 { carrier: T34 }`
|
||||||
#### Reorder carriers — `PUT /carrier/order`
|
#### Reorder carriers — `PUT /carrier/order`
|
||||||
- Params: `body: { carrierIds: int[] }`
|
- Params: `body: { carrierIds: int[] }`
|
||||||
- Returns: `200 { carrierIds: int[] }`
|
- Returns: `200 { carrierIds: int[] }`
|
||||||
@@ -289,10 +338,10 @@ _none_
|
|||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Get carrier by ID — `GET /carrier/{id}`
|
#### Get carrier by ID — `GET /carrier/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { carrier: T30 }`
|
- Returns: `200 { carrier: T34 }`
|
||||||
#### Update carrier — `PATCH /carrier/{id}`
|
#### Update carrier — `PATCH /carrier/{id}`
|
||||||
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||||
- Returns: `200 { carrier: T30 }`
|
- Returns: `200 { carrier: T34 }`
|
||||||
|
|
||||||
### Section: /category
|
### Section: /category
|
||||||
|
|
||||||
@@ -315,8 +364,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, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null, brand?: T25|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 }[] }[] }`
|
- 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?: T28|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 }[] }`
|
||||||
@@ -329,6 +378,9 @@ _none_
|
|||||||
#### Add category image — `POST /category/{id}/image`
|
#### Add category image — `POST /category/{id}/image`
|
||||||
- Params: `id: int, image/avif`
|
- Params: `id: int, image/avif`
|
||||||
- Returns: `201 { category: { id: int, thumbW: int, thumbH: int, thumbHash: str } }`
|
- Returns: `201 { category: { id: int, thumbW: int, thumbH: int, thumbHash: str } }`
|
||||||
|
#### 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 }[] }`
|
||||||
@@ -343,10 +395,10 @@ _none_
|
|||||||
|
|
||||||
#### List companies — `GET /company`
|
#### List companies — `GET /company`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { companies: T17[] }`
|
- Returns: `200 { companies: T19[] }`
|
||||||
#### Create company — `POST /company`
|
#### Create company — `POST /company`
|
||||||
- Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
- Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
||||||
- Returns: `201 { company: T17, user: T32 }`
|
- Returns: `201 { company: T19, user: T36 }`
|
||||||
#### Export company catalog as zip archive (SSE) — `GET /company/export`
|
#### Export company catalog as zip archive (SSE) — `GET /company/export`
|
||||||
- Params: `query: stream?: str`
|
- Params: `query: stream?: str`
|
||||||
- Returns: `200 SSE`
|
- Returns: `200 SSE`
|
||||||
@@ -376,7 +428,22 @@ _none_
|
|||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### 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: T17 }`
|
- Returns: `200 { company: T19 }`
|
||||||
|
|
||||||
|
### Section: /companyuser
|
||||||
|
|
||||||
|
#### List company users — `GET /companyuser`
|
||||||
|
_none_
|
||||||
|
- Returns: `200 { companyusers: T31[] }`
|
||||||
|
#### Add an existing user to the company — `POST /companyuser`
|
||||||
|
- Params: `body: { email: str, role?: str }`
|
||||||
|
- Returns: `201 { companyuser: T31 }`
|
||||||
|
#### 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: T31 }`
|
||||||
|
|
||||||
### Section: /customergroup
|
### Section: /customergroup
|
||||||
|
|
||||||
@@ -400,7 +467,7 @@ _none_
|
|||||||
|
|
||||||
#### List deliveries — `GET /delivery`
|
#### List deliveries — `GET /delivery`
|
||||||
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int`
|
- 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, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T36 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T40 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Create delivery — `POST /delivery`
|
#### Create delivery — `POST /delivery`
|
||||||
- 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 } }[] }`
|
- 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 { delivery: T4 }`
|
- Returns: `201 { delivery: T4 }`
|
||||||
@@ -421,52 +488,52 @@ _none_
|
|||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
#### 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 { attachment: T9, fileUrl: str }`
|
- Returns: `201 { attachment: T10, 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: T24[] }`
|
- Returns: `200 { entities: T27[] }`
|
||||||
#### 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 { entity: T24 }`
|
- Returns: `201 { entity: T27 }`
|
||||||
#### 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: T12[] }`
|
- Returns: `200 { addresses: T14[] }`
|
||||||
#### 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: T12 }`
|
- Returns: `201 { address: T14 }`
|
||||||
#### 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: T12 }`
|
- Returns: `200 { address: T14 }`
|
||||||
#### List entity contacts — `GET /entity/{entityId}/contact`
|
#### List entity contacts — `GET /entity/{entityId}/contact`
|
||||||
- Params: `entityId: int`
|
- Params: `entityId: int`
|
||||||
- Returns: `200 { contacts: T34[] }`
|
- Returns: `200 { contacts: T38[] }`
|
||||||
#### 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: T34 }`
|
- Returns: `201 { contact: T38 }`
|
||||||
#### 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: T34 }`
|
- Returns: `200 { contact: T38 }`
|
||||||
#### 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 { entity: T24 }`
|
- Returns: `200 { entity: T27 }`
|
||||||
#### 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 { entity: T24 }`
|
- Returns: `200 { entity: T27 }`
|
||||||
|
|
||||||
### Section: /events
|
### Section: /events
|
||||||
|
|
||||||
@@ -475,6 +542,8 @@ _none_
|
|||||||
- Returns: `200 SSE`
|
- Returns: `200 SSE`
|
||||||
|
|
||||||
- Events:
|
- Events:
|
||||||
|
- `user`: `{ selectedCompanyId }`
|
||||||
|
- `company`: `{ companyId }`
|
||||||
- `category`: `{}`
|
- `category`: `{}`
|
||||||
- `articles`: `{}`
|
- `articles`: `{}`
|
||||||
- `article-<sku>`: `{}`
|
- `article-<sku>`: `{}`
|
||||||
@@ -482,9 +551,15 @@ _none_
|
|||||||
- `ledger`: `{}`
|
- `ledger`: `{}`
|
||||||
- `attributename`: `{}`
|
- `attributename`: `{}`
|
||||||
- `propertyname`: `{}`
|
- `propertyname`: `{}`
|
||||||
|
- `companyuser`: `{}`
|
||||||
|
- `autoinvite`: `{}`
|
||||||
- `labeltemplate`: `{}`
|
- `labeltemplate`: `{}`
|
||||||
- `printer`: `{}`
|
- `printer`: `{}`
|
||||||
|
- `printeragent`: `{}`
|
||||||
|
- `agentprinter`: `{ printers } or {}`
|
||||||
|
- `printerjob`: `{ job } or { jobId, state }`
|
||||||
- `warehouse`: `{}`
|
- `warehouse`: `{}`
|
||||||
|
- `supplier`: `{}`
|
||||||
- `vfs`: `{}`
|
- `vfs`: `{}`
|
||||||
- `modelconversation`: `{}`
|
- `modelconversation`: `{}`
|
||||||
- `modelconversation-<id>`: `{}`
|
- `modelconversation-<id>`: `{}`
|
||||||
@@ -511,35 +586,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: { objects: T22[] } }`
|
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] } }`
|
||||||
- Returns: `201 { labeltemplate: T8 }`
|
- Returns: `201 { labeltemplate: T9 }`
|
||||||
|
#### 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 { labeltemplate: T8 }`
|
- Returns: `200 { labeltemplate: T9 }`
|
||||||
#### 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?: { objects: T22[] } }`
|
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T25[] } }`
|
||||||
- Returns: `200 { labeltemplate: T8 }`
|
- Returns: `200 { labeltemplate: T9 }`
|
||||||
#### 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?: { objects: T22[] } }`
|
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T25[] } }`
|
||||||
- Returns: `200 { labeltemplate: T8 }`
|
- Returns: `200 { labeltemplate: T9 }`
|
||||||
|
|
||||||
### 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 { ledger: T14[] }`
|
- Returns: `200 { ledger: T16[] }`
|
||||||
#### Query company-wide ledger history — `GET /ledger/history`
|
#### Query company-wide ledger history — `GET /ledger/history`
|
||||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
- 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 }`
|
- 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 { ledger: T14[] }`
|
- Returns: `200 { ledger: T16[] }`
|
||||||
#### 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 { ledger: T14[] }`
|
- Returns: `200 { ledger: T16[] }`
|
||||||
|
|
||||||
### Section: /mcp
|
### Section: /mcp
|
||||||
|
|
||||||
@@ -569,16 +647,16 @@ _none_
|
|||||||
|
|
||||||
#### List conversations — `GET /modelconversation`
|
#### List conversations — `GET /modelconversation`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { conversations: T16[] }`
|
- Returns: `200 { conversations: T18[] }`
|
||||||
#### Create conversation — `POST /modelconversation`
|
#### Create conversation — `POST /modelconversation`
|
||||||
- Params: `body: { provider: str, model: str }`
|
- Params: `body: { provider: str, model: str }`
|
||||||
- Returns: `201 { conversation: T16 }`
|
- Returns: `201 { conversation: T18 }`
|
||||||
#### Delete conversation — `DELETE /modelconversation/{id}`
|
#### Delete conversation — `DELETE /modelconversation/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Get conversation — `GET /modelconversation/{id}`
|
#### Get conversation — `GET /modelconversation/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { conversation: T16 }`
|
- Returns: `200 { conversation: T18 }`
|
||||||
#### List conversation messages — `GET /modelconversation/{id}/message`
|
#### List conversation messages — `GET /modelconversation/{id}/message`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { messages: { id: int, conversation_id: int, created_at: str, role: str, message: str }[] }`
|
- Returns: `200 { messages: { id: int, conversation_id: int, created_at: str, role: str, message: str }[] }`
|
||||||
@@ -619,7 +697,7 @@ _none_
|
|||||||
- 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: T1[], 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, entityId: int, lines: T19[], externalNumber?: str|null }`
|
- Params: `body: { number: str, date: str, entityId: int, lines: T21[], externalNumber?: str|null }`
|
||||||
- Returns: `201 { order: T1 }`
|
- Returns: `201 { order: T1 }`
|
||||||
#### Delete order — `DELETE /order/{id}`
|
#### Delete order — `DELETE /order/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
@@ -628,7 +706,7 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { order: 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?: T19[] }`
|
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T21[] }`
|
||||||
- Returns: `200 { order: 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`
|
||||||
@@ -638,13 +716,13 @@ _none_
|
|||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
#### 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: T29[] }`
|
- Returns: `200 { payments: T33[] }`
|
||||||
#### 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: T29 }`
|
- Returns: `201 { payment: T33 }`
|
||||||
#### 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 { attachment: T9, fileUrl: str }`
|
- Returns: `201 { attachment: T10, 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: T2[] }`
|
- Returns: `200 { shipments: T2[] }`
|
||||||
@@ -653,10 +731,10 @@ _none_
|
|||||||
- Returns: `201 { shipment: T2 }`
|
- 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: T20[] }`
|
- Returns: `200 { shippings: T23[] }`
|
||||||
#### Create shipping for an order — `POST /order/{id}/shipping`
|
#### Create shipping for an order — `POST /order/{id}/shipping`
|
||||||
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||||
- Returns: `201 { shipping: T20 }`
|
- Returns: `201 { shipping: T23 }`
|
||||||
|
|
||||||
### Section: /package
|
### Section: /package
|
||||||
|
|
||||||
@@ -674,16 +752,16 @@ _none_
|
|||||||
|
|
||||||
#### List POS terminals — `GET /posterminal`
|
#### List POS terminals — `GET /posterminal`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { posterminals: T10[] }`
|
- Returns: `200 { posterminals: T7[] }`
|
||||||
#### Create POS terminal — `PUT /posterminal`
|
#### Create POS terminal — `PUT /posterminal`
|
||||||
- Params: `body: { name: str, rootCategoryId?: int|null, syncRootArticles?: bool }`
|
- Params: `body: { name: str, rootCategoryId?: int|null, syncRootArticles?: bool, sort?: int, enabled?: bool }`
|
||||||
- Returns: `200 { posterminal: T10 }`
|
- Returns: `200 { posterminal: T7 }`
|
||||||
#### Delete POS terminal — `DELETE /posterminal/{id}`
|
#### Delete POS terminal — `DELETE /posterminal/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Update POS terminal — `PATCH /posterminal/{id}`
|
#### Update POS terminal — `PATCH /posterminal/{id}`
|
||||||
- Params: `id: int, body: { name?: str, rootCategoryId?: int|null, syncRootArticles?: bool, pairingpin?: str|null }`
|
- Params: `id: int, body: { name?: str, rootCategoryId?: int|null, syncRootArticles?: bool, sort?: int, enabled?: bool, pairingpin?: str|null }`
|
||||||
- Returns: `200 { posterminal: T10 }`
|
- Returns: `200 { posterminal: T7 }`
|
||||||
#### Disable all categories on terminal — `DELETE /posterminal/{id}/all`
|
#### Disable all categories on terminal — `DELETE /posterminal/{id}/all`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { id: int, categoryIds: int[] }`
|
- Returns: `200 { id: int, categoryIds: int[] }`
|
||||||
@@ -701,34 +779,76 @@ _none_
|
|||||||
|
|
||||||
#### List printers — `GET /printer`
|
#### List printers — `GET /printer`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { printers: T23[] }`
|
- Returns: `200 { printers: T26[] }`
|
||||||
#### Create printer — `POST /printer`
|
#### Create printer — `POST /printer`
|
||||||
- Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
- Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
||||||
- Returns: `201 { printer: T23 }`
|
- Returns: `201 { printer: T26 }`
|
||||||
#### Reorder printers — `PUT /printer/order`
|
#### Reorder printers — `PUT /printer/order`
|
||||||
- Params: `body: { printerIds: int[] }`
|
- Params: `body: { printerIds: int[] }`
|
||||||
- Returns: `200 { printerIds: int[] }`
|
- Returns: `200 { printerIds: int[] }`
|
||||||
|
#### Get live printer states — `GET /printer/state`
|
||||||
|
- Params: `query: serial?: str, agentId?: int`
|
||||||
|
- Returns: `200 { printers: {}[] }`
|
||||||
#### Delete printer — `DELETE /printer/{id}`
|
#### Delete printer — `DELETE /printer/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Get printer by ID — `GET /printer/{id}`
|
#### Get printer by ID — `GET /printer/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { printer: T23 }`
|
- Returns: `200 { printer: T26 }`
|
||||||
#### Update printer (partial) — `PATCH /printer/{id}`
|
#### 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 }`
|
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
||||||
- Returns: `200 { printer: T23 }`
|
- Returns: `200 { printer: T26 }`
|
||||||
#### Update printer — `PUT /printer/{id}`
|
#### Update printer — `PUT /printer/{id}`
|
||||||
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
||||||
- Returns: `200 { printer: T23 }`
|
- Returns: `200 { printer: T26 }`
|
||||||
|
#### Get live state for a printer — `GET /printer/{id}/state`
|
||||||
|
- Params: `id: str`
|
||||||
|
- Returns: `200 { printer: {} }`
|
||||||
|
|
||||||
|
### Section: /printeragent
|
||||||
|
|
||||||
|
#### List printer agents — `GET /printeragent`
|
||||||
|
_none_
|
||||||
|
- Returns: `200 { printeragents: T12[] }`
|
||||||
|
#### Create printer agent — `PUT /printeragent`
|
||||||
|
- Params: `body: { name: str, sort?: int, enabled?: bool }`
|
||||||
|
- Returns: `200 { printeragent: T12 }`
|
||||||
|
#### Delete printer agent — `DELETE /printeragent/{id}`
|
||||||
|
- Params: `id: int`
|
||||||
|
- Returns: `204 void`
|
||||||
|
#### Update printer agent — `PATCH /printeragent/{id}`
|
||||||
|
- Params: `id: int, body: { name?: str, sort?: int, enabled?: bool }`
|
||||||
|
- Returns: `200 { printeragent: T12 }`
|
||||||
|
|
||||||
|
### Section: /printerjob
|
||||||
|
|
||||||
|
#### List printer jobs — `GET /printerjob`
|
||||||
|
- Params: `query: agentprinterId?: int, state?: str`
|
||||||
|
- Returns: `200 { printerjobs: T22[] }`
|
||||||
|
#### Create print job — `POST /printerjob`
|
||||||
|
- Params: `body: { agentprinterId: int, data: str, type?: str }`
|
||||||
|
- Returns: `201 { printerjob: T22 }`
|
||||||
|
#### Cancel all pending and processing printer jobs — `POST /printerjob/cancel-all`
|
||||||
|
- Params: `body?: { agentprinterId?: int }`
|
||||||
|
- Returns: `200 { success: bool, cancelledCount: int, jobs: T22[] }`
|
||||||
|
#### Cancel and delete printer job — `DELETE /printerjob/{id}`
|
||||||
|
- Params: `id: int`
|
||||||
|
- Returns: `200 { success: bool, deletedJob: T22 }`
|
||||||
|
#### Get printer job — `GET /printerjob/{id}`
|
||||||
|
- Params: `id: int`
|
||||||
|
- Returns: `200 { printerjob: T22 }`
|
||||||
|
#### Cancel printer job — `POST /printerjob/{id}/cancel`
|
||||||
|
- Params: `id: int`
|
||||||
|
- Returns: `200 { success: bool, printerjob: T22 }`
|
||||||
|
|
||||||
### Section: /propertyname
|
### Section: /propertyname
|
||||||
|
|
||||||
#### List property names — `GET /propertyname`
|
#### List property names — `GET /propertyname`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { propertynames: T28[] }`
|
- Returns: `200 { propertynames: T32[] }`
|
||||||
#### 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: T28 }`
|
- Returns: `201 { propertyname: T32 }`
|
||||||
#### 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 }`
|
||||||
@@ -737,7 +857,7 @@ _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: T28 }`
|
- Returns: `200 { propertyname: T32 }`
|
||||||
|
|
||||||
### Section: /public
|
### Section: /public
|
||||||
|
|
||||||
@@ -749,7 +869,7 @@ _none_
|
|||||||
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num|null, taxRate: num|null, gross: num|null, trackStock: bool, brand: { id: int|null, name: str }, fullHash: str|null, thumbW: int|null, thumbH: int|null, thumbHash: str|null }[] }`
|
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num|null, taxRate: num|null, gross: num|null, trackStock: bool, brand: { id: int|null, name: str }, fullHash: str|null, thumbW: int|null, thumbH: int|null, thumbHash: str|null }[] }`
|
||||||
#### Get public article detail — `GET /public/{companyId}/articles/{sku}`
|
#### Get public article detail — `GET /public/{companyId}/articles/{sku}`
|
||||||
- Params: `companyId: int, sku: str`
|
- Params: `companyId: int, sku: str`
|
||||||
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, description: str, unitamount: num|null, weight: num|null, trackStock: bool, unit: T26|null, brand: T25|null, price: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }, prices: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }[], categories: { id: int, name: str, slug: str }[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], isParent: bool, parentSku: str|null, variants: { id: int, sku: str, sort: int, name: str, slug: str, attributeValueId: int|null, attributeValue: str|null, unitamount: num|null, weight: num|null, trackStock: bool }[] } }`
|
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, description: str, unitamount: num|null, weight: num|null, trackStock: bool, unit: T29|null, brand: T28|null, price: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }, prices: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }[], categories: { id: int, name: str, slug: str }[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], isParent: bool, parentSku: str|null, variants: { id: int, sku: str, sort: int, name: str, slug: str, attributeValueId: int|null, attributeValue: str|null, unitamount: num|null, weight: num|null, trackStock: bool }[] } }`
|
||||||
#### List public categories — `GET /public/{companyId}/categories`
|
#### List public categories — `GET /public/{companyId}/categories`
|
||||||
- Params: `companyId: int`
|
- Params: `companyId: int`
|
||||||
- Returns: `200 { categories: { id: int, parentId: int|null, sort: int, name: str, slug: str, fullHash: str|null, thumbW: int|null, thumbH: int|null, thumbHash: str|null }[] }`
|
- Returns: `200 { categories: { id: int, parentId: int|null, sort: int, name: str, slug: str, fullHash: str|null, thumbW: int|null, thumbH: int|null, thumbHash: str|null }[] }`
|
||||||
@@ -760,7 +880,7 @@ _none_
|
|||||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
- Returns: `200 { quotes: T3[], 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: T19[], entityId?: int|null, externalNumber?: str|null }`
|
- Params: `body: { number: str, date: str, lines: T21[], entityId?: int|null, externalNumber?: str|null }`
|
||||||
- Returns: `201 { quote: T3 }`
|
- Returns: `201 { quote: T3 }`
|
||||||
#### Delete quote — `DELETE /quote/{id}`
|
#### Delete quote — `DELETE /quote/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
@@ -769,7 +889,7 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { quote: T3 }`
|
- 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?: T19[] }`
|
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T21[] }`
|
||||||
- Returns: `200 { quote: T3 }`
|
- 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`
|
||||||
@@ -778,11 +898,59 @@ _none_
|
|||||||
- Params: `id: int, fileId: int, query: download?: bool`
|
- Params: `id: int, fileId: int, query: download?: bool`
|
||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
#### 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?: T19[] }`
|
- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T21[] }`
|
||||||
- Returns: `201 { order: 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 { attachment: T9, fileUrl: str }`
|
- Returns: `201 { attachment: T10, 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: /serper
|
### Section: /serper
|
||||||
|
|
||||||
@@ -821,13 +989,13 @@ _none_
|
|||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
#### 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 { attachment: T9, fileUrl: str }`
|
- Returns: `201 { attachment: T10, 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: T20[] }`
|
- Returns: `200 { shippings: T23[] }`
|
||||||
#### Create package for a shipment — `POST /shipment/{id}/shipping`
|
#### Create package for a shipment — `POST /shipment/{id}/shipping`
|
||||||
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||||
- Returns: `201 { shipping: T20 }`
|
- Returns: `201 { shipping: T23 }`
|
||||||
|
|
||||||
### Section: /shipping
|
### Section: /shipping
|
||||||
|
|
||||||
@@ -836,7 +1004,7 @@ _none_
|
|||||||
- 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, carrierId?: int, warehouseId?: int }`
|
- Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrierId?: int, warehouseId?: int }`
|
||||||
- Returns: `200 { shipping: T20 }`
|
- Returns: `200 { shipping: T23 }`
|
||||||
#### 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 }[] }`
|
||||||
@@ -857,7 +1025,7 @@ _none_
|
|||||||
- Returns: `200`
|
- 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: T14[] }`
|
- 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: T16[] }`
|
||||||
#### Ship shipment manifest item(s) — `POST /stock/ship`
|
#### Ship shipment manifest item(s) — `POST /stock/ship`
|
||||||
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
||||||
- Returns: `200`
|
- Returns: `200`
|
||||||
@@ -866,40 +1034,40 @@ _none_
|
|||||||
|
|
||||||
#### List subagent schedules — `GET /subagentschedule`
|
#### List subagent schedules — `GET /subagentschedule`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { schedules: T13[] }`
|
- Returns: `200 { schedules: T15[] }`
|
||||||
#### Create subagent schedule — `POST /subagentschedule`
|
#### Create subagent schedule — `POST /subagentschedule`
|
||||||
- Params: `body: { prompt: str, cron: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int }`
|
- Params: `body: { prompt: str, cron: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int }`
|
||||||
- Returns: `201 { schedule: T13 }`
|
- Returns: `201 { schedule: T15 }`
|
||||||
#### Delete subagent schedule — `DELETE /subagentschedule/{id}`
|
#### Delete subagent schedule — `DELETE /subagentschedule/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Get subagent schedule by ID — `GET /subagentschedule/{id}`
|
#### Get subagent schedule by ID — `GET /subagentschedule/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { schedule: T13 }`
|
- Returns: `200 { schedule: T15 }`
|
||||||
#### Update subagent schedule — `PUT /subagentschedule/{id}`
|
#### Update subagent schedule — `PUT /subagentschedule/{id}`
|
||||||
- Params: `id: int, body: { prompt?: str, cron?: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int, lastrun_at?: str|null }`
|
- Params: `id: int, body: { prompt?: str, cron?: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int, lastrun_at?: str|null }`
|
||||||
- Returns: `200 { schedule: T13 }`
|
- Returns: `200 { schedule: T15 }`
|
||||||
|
|
||||||
### Section: /supplier
|
### Section: /supplier
|
||||||
|
|
||||||
#### List suppliers — `GET /supplier`
|
#### List suppliers — `GET /supplier`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { suppliers: T35[] }`
|
- Returns: `200 { suppliers: T39[] }`
|
||||||
#### Create supplier — `POST /supplier`
|
#### Create supplier — `POST /supplier`
|
||||||
- Params: `body: { name: str }`
|
- Params: `body: { name: str }`
|
||||||
- Returns: `201 { supplier: T35 }`
|
- Returns: `201 { supplier: T39 }`
|
||||||
#### 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: T35 }`
|
- Returns: `200 { supplier: T39 }`
|
||||||
#### List supplier articles by SKUs — `GET /supplier/{id}/articles`
|
#### List supplier articles by SKUs — `GET /supplier/{id}/articles`
|
||||||
- Params: `id: int, query: skus?: str`
|
- 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?: T38|null, incoming?: { date: str, amount: num }[] }[] }`
|
- 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?: T42|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?: T38|null, incoming?: { date: str, amount: num }[] }[] }`
|
- 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?: T42|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||||
|
|
||||||
### Section: /thumb
|
### Section: /thumb
|
||||||
|
|
||||||
@@ -914,18 +1082,21 @@ _none_
|
|||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### List units — `GET /unit`
|
#### List units — `GET /unit`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { units: T26[] }`
|
- Returns: `200 { units: T29[] }`
|
||||||
#### 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: T26 }`
|
- Returns: `200 { unit: T29 }`
|
||||||
|
|
||||||
### Section: /v1
|
### Section: /v1
|
||||||
|
|
||||||
#### JTL-POS category synchronization — `GET /v1/category`
|
#### JTL-POS category synchronization — `GET /v1/category`
|
||||||
- Params: `query: lastChangedCategory?: str, limit?: int`
|
- Params: `query: lastChangedCategory?: str, limit?: int`
|
||||||
- Returns: `200 {}[]`
|
- Returns: `200 {}[]`
|
||||||
|
#### POS category image delivery — `GET /v1/cimage`
|
||||||
|
- Params: `query: path: str, size?: int, w?: int, h?: int, format?: str`
|
||||||
|
- Returns: `200 jpeg`
|
||||||
#### POS discovery and pairing — `GET /v1/client`
|
#### POS discovery and pairing — `GET /v1/client`
|
||||||
- Params: `query: authCode: str, name?: str`
|
- Params: `query: authCode: str, name?: str, clientversion?: str`
|
||||||
- Returns: `200 { authCode: str|null, authToken: str|null, certificateFingerprint: str, certificateSerialNumber: str, mandantId: str, mandantName: str|null, mandantDatabase: str|null, serverFingerprint: str|null, name: str|null, serverTimestamp: str }`
|
- Returns: `200 { authCode: str|null, authToken: str|null, certificateFingerprint: str, certificateSerialNumber: str, mandantId: str, mandantName: str|null, mandantDatabase: str|null, serverFingerprint: str|null, name: str|null, serverTimestamp: str }`
|
||||||
#### JTL-POS customer catalog synchronization — `GET /v1/customer`
|
#### JTL-POS customer catalog synchronization — `GET /v1/customer`
|
||||||
- Params: `query: lastChangedCustomer?: str, limit?: int`
|
- Params: `query: lastChangedCustomer?: str, limit?: int`
|
||||||
@@ -943,7 +1114,7 @@ _none_
|
|||||||
- Params: `query: mandantId?: str, body: { orders?: {}[] }`
|
- Params: `query: mandantId?: str, body: { orders?: {}[] }`
|
||||||
- Returns: `200 { status: str, externalOrderId: str, message: str }[]`
|
- Returns: `200 { status: str, externalOrderId: str, message: str }[]`
|
||||||
#### POS product image delivery — `GET /v1/pimage`
|
#### POS product image delivery — `GET /v1/pimage`
|
||||||
- Params: `query: path: str, w?: int, h?: int, format?: str`
|
- Params: `query: path: str, size?: int, w?: int, h?: int, format?: str`
|
||||||
- Returns: `200 jpeg`
|
- Returns: `200 jpeg`
|
||||||
#### JTL-POS product catalog synchronization — `GET /v1/product`
|
#### JTL-POS product catalog synchronization — `GET /v1/product`
|
||||||
- Params: `query: lastChangedProduct?: str, limit?: int`
|
- Params: `query: lastChangedProduct?: str, limit?: int`
|
||||||
@@ -1005,3 +1176,9 @@ _none_
|
|||||||
#### 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 { warehouse: { id: int, name: str, sort: int } }`
|
- Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
|
||||||
|
|
||||||
|
### Section: /zpljob
|
||||||
|
|
||||||
|
#### Create ZPL print job — `POST /zpljob`
|
||||||
|
- Params: `body: { agentprinterId: int, data: str, copiescount?: int }`
|
||||||
|
- Returns: `201 { printerjob: T22 }`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Dash API Routes Overview
|
# Dash API Routes Overview
|
||||||
|
|
||||||
Overview of all 189 API endpoints across all modules.
|
Overview of all 196 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -9,34 +9,33 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
|
|
||||||
### Reusable types
|
### Reusable types
|
||||||
|
|
||||||
- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T12[], externalNumber?: str|null, createdAt?: str, shipping?: T14|null, shippings?: T14[], payment?: T22|null, payments?: T22[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T7[] }`
|
- `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, date: str, lines: T12[], shippings: T14[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T7[], state?: str }`
|
- `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` = `{ id: int, number: str, type: str, date: str, lines: T12[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T7[] }`
|
- `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, number: str, type: str, supplierId: int, date: str, lines: T15[], files: T7[], externalNumber?: str|null, createdAt?: str, ledger?: T27 }`
|
- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T14[], files: T7[], externalNumber?: str|null, createdAt?: str, ledger?: T26 }`
|
||||||
- `T5` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T28|null, incoming?: { date: str, amount: num }[] }`
|
- `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, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T16[] }, thumb?: str|null }`
|
- `T6` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T15[] }, thumb?: str|null }`
|
||||||
- `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 }`
|
- `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, 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 }`
|
- `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` = `{ id: int, company_id: int, prompt: str, max_itr: int, cron: str, created_at: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, lastrun_at?: str|null }`
|
- `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, 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, name: str, sort: int, exclusive: bool, values: T18[], articleCount?: int }`
|
||||||
- `T11` = `{ id: int, name: str, sort: int, exclusive: bool, values: T20[], articleCount?: int }`
|
- `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` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
|
- `T12` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
||||||
- `T13` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
- `T13` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
|
||||||
- `T14` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: 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, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }`
|
- `T15` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }`
|
||||||
- `T16` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }`
|
- `T16` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
|
||||||
- `T17` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }`
|
- `T17` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
||||||
- `T18` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
|
- `T18` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
||||||
- `T19` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
- `T19` = `{ id: int, googleAuthId: int, email: str, role: str, picture?: str|null }`
|
||||||
- `T20` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
- `T20` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
||||||
- `T21` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
- `T21` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
||||||
- `T22` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
- `T22` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
||||||
- `T23` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
- `T23` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
||||||
- `T24` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
- `T24` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
||||||
- `T25` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
- `T25` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
||||||
- `T26` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
- `T26` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
||||||
- `T27` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
- `T27` = `{ net: num, taxId: int, taxRate?: num|null }`
|
||||||
- `T28` = `{ net: num, taxId: int, taxRate?: num|null }`
|
|
||||||
|
|
||||||
### Routes
|
### Routes
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
- 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 { 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: T19, 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 }[] } }`
|
- 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: `204 void`
|
- Returns: `204 void`
|
||||||
@@ -92,8 +91,8 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
- Params: `sku: str, body: { eans: str[] }`
|
- Params: `sku: str, body: { eans: str[] }`
|
||||||
- Returns: `204 void`
|
- 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`
|
||||||
@@ -109,11 +108,11 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
#### 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: `204 void`
|
- 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: `204 void`
|
- 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`
|
||||||
@@ -150,19 +149,19 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
|
|
||||||
#### List attribute names — `GET /attributename`
|
#### List attribute names — `GET /attributename`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { attributenames: T11[] }`
|
- 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: T11 }`
|
- 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: T20[] }`
|
- 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: T20 }`
|
- 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 }`
|
||||||
@@ -171,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: T11 }`
|
- Returns: `200 { attributename: T10 }`
|
||||||
|
|
||||||
### Section: /attributevalue
|
### Section: /attributevalue
|
||||||
|
|
||||||
@@ -180,10 +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: T20 }`
|
- 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: T24[] } }`
|
- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T23[] } }`
|
||||||
|
|
||||||
|
### Section: /autoinvite
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
@@ -192,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: T24[], 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`
|
||||||
@@ -201,16 +212,16 @@ _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: T24[] } }`
|
- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T23[] } }`
|
||||||
|
|
||||||
### Section: /carrier
|
### Section: /carrier
|
||||||
|
|
||||||
#### List carriers — `GET /carrier`
|
#### List carriers — `GET /carrier`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { carriers: T23[] }`
|
- Returns: `200 { carriers: T22[] }`
|
||||||
#### Create carrier — `POST /carrier`
|
#### Create carrier — `POST /carrier`
|
||||||
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||||
- Returns: `201 { carrier: T23 }`
|
- Returns: `201 { carrier: T22 }`
|
||||||
#### Reorder carriers — `PUT /carrier/order`
|
#### Reorder carriers — `PUT /carrier/order`
|
||||||
- Params: `body: { carrierIds: int[] }`
|
- Params: `body: { carrierIds: int[] }`
|
||||||
- Returns: `200 { carrierIds: int[] }`
|
- Returns: `200 { carrierIds: int[] }`
|
||||||
@@ -219,10 +230,10 @@ _none_
|
|||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### Get carrier by ID — `GET /carrier/{id}`
|
#### Get carrier by ID — `GET /carrier/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { carrier: T23 }`
|
- Returns: `200 { carrier: T22 }`
|
||||||
#### Update carrier — `PATCH /carrier/{id}`
|
#### Update carrier — `PATCH /carrier/{id}`
|
||||||
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||||
- Returns: `200 { carrier: T23 }`
|
- Returns: `200 { carrier: T22 }`
|
||||||
|
|
||||||
### Section: /category
|
### Section: /category
|
||||||
|
|
||||||
@@ -242,7 +253,7 @@ _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, 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 }[] }[] }`
|
- 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`
|
||||||
@@ -253,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 }[] }`
|
||||||
@@ -267,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 }`
|
||||||
@@ -288,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
|
||||||
|
|
||||||
@@ -312,7 +341,7 @@ _none_
|
|||||||
|
|
||||||
#### List deliveries — `GET /delivery`
|
#### List deliveries — `GET /delivery`
|
||||||
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int`
|
- 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, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T27 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
- 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, 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 } }[] }`
|
- 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 { delivery: T4 }`
|
- Returns: `201 { delivery: T4 }`
|
||||||
@@ -336,10 +365,10 @@ _none_
|
|||||||
|
|
||||||
#### List entities — `GET /entity`
|
#### List entities — `GET /entity`
|
||||||
- Params: `query: customergroupId?: int, role?: str`
|
- Params: `query: customergroupId?: int, role?: str`
|
||||||
- Returns: `200 { entities: T18[] }`
|
- 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 { entity: T18 }`
|
- 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 }`
|
||||||
@@ -357,31 +386,25 @@ _none_
|
|||||||
- Returns: `200 { address: T8 }`
|
- 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: T25[] }`
|
- 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: T25 }`
|
- 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: T25 }`
|
- 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 { entity: T18 }`
|
- 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 { entity: T18 }`
|
- Returns: `200 { entity: T16 }`
|
||||||
|
|
||||||
### Section: /health
|
|
||||||
|
|
||||||
#### Health latency history — `GET /health/latency`
|
|
||||||
- Params: `query: range?: str`
|
|
||||||
- Returns: `200 { raw: { timestamp: int, value: num }[], avg: { timestamp: int, value: num }[], max: { timestamp: int, value: num }[] }`
|
|
||||||
|
|
||||||
### Section: /labeltemplate
|
### Section: /labeltemplate
|
||||||
|
|
||||||
@@ -389,8 +412,11 @@ _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: { objects: T16[] } }`
|
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T15[] } }`
|
||||||
- Returns: `201 { labeltemplate: T6 }`
|
- 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`
|
||||||
@@ -398,26 +424,26 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { labeltemplate: T6 }`
|
- 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?: { objects: T16[] } }`
|
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T15[] } }`
|
||||||
- Returns: `200 { labeltemplate: T6 }`
|
- 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?: { objects: T16[] } }`
|
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T15[] } }`
|
||||||
- Returns: `200 { labeltemplate: T6 }`
|
- 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 { ledger: T10[] }`
|
- Returns: `200 { ledger: T9[] }`
|
||||||
#### Query company-wide ledger history — `GET /ledger/history`
|
#### Query company-wide ledger history — `GET /ledger/history`
|
||||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
- 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 }`
|
- 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 { ledger: T10[] }`
|
- 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 { ledger: T10[] }`
|
- Returns: `200 { ledger: T9[] }`
|
||||||
|
|
||||||
### Section: /number
|
### Section: /number
|
||||||
|
|
||||||
@@ -434,7 +460,7 @@ _none_
|
|||||||
- 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: T1[], 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, entityId: int, lines: T13[], externalNumber?: str|null }`
|
- Params: `body: { number: str, date: str, entityId: int, lines: T12[], externalNumber?: str|null }`
|
||||||
- Returns: `201 { order: T1 }`
|
- Returns: `201 { order: T1 }`
|
||||||
#### Delete order — `DELETE /order/{id}`
|
#### Delete order — `DELETE /order/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
@@ -443,17 +469,17 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { order: 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?: T13[] }`
|
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
|
||||||
- Returns: `200 { order: 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: T22[] }`
|
- 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: T22 }`
|
- 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 { attachment: T7, fileUrl: str }`
|
- Returns: `201 { attachment: T7, fileUrl: str }`
|
||||||
@@ -465,10 +491,10 @@ _none_
|
|||||||
- Returns: `201 { shipment: T2 }`
|
- 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: T14[] }`
|
- 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: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||||
- Returns: `201 { shipping: T14 }`
|
- Returns: `201 { shipping: T13 }`
|
||||||
|
|
||||||
### Section: /package
|
### Section: /package
|
||||||
|
|
||||||
@@ -482,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: T17[] }`
|
|
||||||
#### Create printer — `POST /printer`
|
|
||||||
- Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
|
||||||
- Returns: `201 { printer: T17 }`
|
|
||||||
#### 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 { printer: T17 }`
|
|
||||||
#### 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 { printer: T17 }`
|
|
||||||
#### 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 { printer: T17 }`
|
|
||||||
|
|
||||||
### Section: /propertyname
|
### Section: /propertyname
|
||||||
|
|
||||||
#### List property names — `GET /propertyname`
|
#### List property names — `GET /propertyname`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { propertynames: T21[] }`
|
- 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: T21 }`
|
- 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 }`
|
||||||
@@ -522,7 +524,7 @@ _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: T21 }`
|
- Returns: `200 { propertyname: T20 }`
|
||||||
|
|
||||||
### Section: /quote
|
### Section: /quote
|
||||||
|
|
||||||
@@ -530,7 +532,7 @@ _none_
|
|||||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
- Returns: `200 { quotes: T3[], 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: T13[], entityId?: int|null, externalNumber?: str|null }`
|
- Params: `body: { number: str, date: str, lines: T12[], entityId?: int|null, externalNumber?: str|null }`
|
||||||
- Returns: `201 { quote: T3 }`
|
- Returns: `201 { quote: T3 }`
|
||||||
#### Delete quote — `DELETE /quote/{id}`
|
#### Delete quote — `DELETE /quote/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
@@ -539,18 +541,66 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { quote: T3 }`
|
- 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?: T13[] }`
|
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
|
||||||
- Returns: `200 { quote: T3 }`
|
- 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?: T13[] }`
|
- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
|
||||||
- Returns: `201 { order: 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 { attachment: T7, fileUrl: str }`
|
- 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
|
||||||
|
|
||||||
#### List shipments — `GET /shipment`
|
#### List shipments — `GET /shipment`
|
||||||
@@ -573,10 +623,10 @@ _none_
|
|||||||
- Returns: `201 { attachment: T7, fileUrl: str }`
|
- 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: T14[] }`
|
- 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: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||||
- Returns: `201 { shipping: T14 }`
|
- Returns: `201 { shipping: T13 }`
|
||||||
|
|
||||||
### Section: /shipping
|
### Section: /shipping
|
||||||
|
|
||||||
@@ -585,7 +635,7 @@ _none_
|
|||||||
- 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, carrierId?: int, warehouseId?: int }`
|
- Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrierId?: int, warehouseId?: int }`
|
||||||
- Returns: `200 { shipping: T14 }`
|
- 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 }[] }`
|
||||||
@@ -593,12 +643,6 @@ _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`
|
#### Accept delivery manifest item(s) — `POST /stock/accept`
|
||||||
@@ -606,49 +650,31 @@ _none_
|
|||||||
- Returns: `200`
|
- 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: T10[] }`
|
- 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`
|
#### Ship shipment manifest item(s) — `POST /stock/ship`
|
||||||
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
||||||
- Returns: `200`
|
- Returns: `200`
|
||||||
|
|
||||||
### Section: /subagentschedule
|
|
||||||
|
|
||||||
#### List subagent schedules — `GET /subagentschedule`
|
|
||||||
_none_
|
|
||||||
- Returns: `200 { schedules: T9[] }`
|
|
||||||
#### Create subagent schedule — `POST /subagentschedule`
|
|
||||||
- Params: `body: { prompt: str, cron: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int }`
|
|
||||||
- Returns: `201 { schedule: T9 }`
|
|
||||||
#### Delete subagent schedule — `DELETE /subagentschedule/{id}`
|
|
||||||
- Params: `id: int`
|
|
||||||
- Returns: `204 void`
|
|
||||||
#### Get subagent schedule by ID — `GET /subagentschedule/{id}`
|
|
||||||
- Params: `id: int`
|
|
||||||
- Returns: `200 { schedule: T9 }`
|
|
||||||
#### Update subagent schedule — `PUT /subagentschedule/{id}`
|
|
||||||
- Params: `id: int, body: { prompt?: str, cron?: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int, lastrun_at?: str|null }`
|
|
||||||
- Returns: `200 { schedule: T9 }`
|
|
||||||
|
|
||||||
### Section: /supplier
|
### Section: /supplier
|
||||||
|
|
||||||
#### List suppliers — `GET /supplier`
|
#### List suppliers — `GET /supplier`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { suppliers: T26[] }`
|
- Returns: `200 { suppliers: T25[] }`
|
||||||
#### Create supplier — `POST /supplier`
|
#### Create supplier — `POST /supplier`
|
||||||
- Params: `body: { name: str }`
|
- Params: `body: { name: str }`
|
||||||
- Returns: `201 { supplier: T26 }`
|
- 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: T26 }`
|
- 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?: str`
|
- 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?: T28|null, incoming?: { date: str, amount: num }[] }[] }`
|
- 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?: T28|null, incoming?: { date: str, amount: num }[] }[] }`
|
- 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
|
||||||
|
|
||||||
@@ -657,19 +683,10 @@ _none_
|
|||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
#### List units — `GET /unit`
|
#### List units — `GET /unit`
|
||||||
_none_
|
_none_
|
||||||
- Returns: `200 { units: T19[] }`
|
- 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: T19 }`
|
- Returns: `200 { unit: T17 }`
|
||||||
|
|
||||||
### Section: /v1
|
|
||||||
|
|
||||||
#### JTL-POS deleted entity synchronization — `GET /v1/deletedentity`
|
|
||||||
- Params: `query: lastChangedDeletedEntity?: str, limit?: int`
|
|
||||||
- Returns: `200 { entityId: str, entityType: int, lastChanged: int }[]`
|
|
||||||
#### JTL-POS order upload — `POST /v1/order`
|
|
||||||
- Params: `query: mandantId?: str, body: { orders?: {}[] }`
|
|
||||||
- Returns: `200 { status: str, externalOrderId: str, message: str }[]`
|
|
||||||
|
|
||||||
### Section: /warehouse
|
### Section: /warehouse
|
||||||
|
|
||||||
|
|||||||
30
docs/api/routes/agent/events.md
Normal file
30
docs/api/routes/agent/events.md
Normal 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`._
|
||||||
31
docs/api/routes/agent/pair.get.md
Normal file
31
docs/api/routes/agent/pair.get.md
Normal 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`._
|
||||||
55
docs/api/routes/agent/pair.post.md
Normal file
55
docs/api/routes/agent/pair.post.md
Normal 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`._
|
||||||
53
docs/api/routes/agent/state.md
Normal file
53
docs/api/routes/agent/state.md
Normal 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`._
|
||||||
51
docs/api/routes/agentprinter.md
Normal file
51
docs/api/routes/agentprinter.md
Normal 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`._
|
||||||
35
docs/api/routes/agentprinter/_id.delete.md
Normal file
35
docs/api/routes/agentprinter/_id.delete.md
Normal 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`._
|
||||||
48
docs/api/routes/agentprinter/_id.get.md
Normal file
48
docs/api/routes/agentprinter/_id.get.md
Normal 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`._
|
||||||
67
docs/api/routes/agentprinter/_id.patch.md
Normal file
67
docs/api/routes/agentprinter/_id.patch.md
Normal 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`._
|
||||||
51
docs/api/routes/agentprinter/order.md
Normal file
51
docs/api/routes/agentprinter/order.md
Normal 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`._
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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":"…"}` |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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":"…"}` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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":"…"}` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
55
docs/api/routes/autoinvite/create.md
Normal file
55
docs/api/routes/autoinvite/create.md
Normal 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`._
|
||||||
36
docs/api/routes/autoinvite/delete.md
Normal file
36
docs/api/routes/autoinvite/delete.md
Normal 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`._
|
||||||
36
docs/api/routes/autoinvite/list.md
Normal file
36
docs/api/routes/autoinvite/list.md
Normal 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`._
|
||||||
64
docs/api/routes/category/_id/image/generate.md
Normal file
64
docs/api/routes/category/_id/image/generate.md
Normal 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`._
|
||||||
@@ -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) and small thumbnail fields (`smallThumbW`, `smallThumbH`, `smallThumbBinary`).
|
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
|
||||||
|
|||||||
@@ -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,6 +60,7 @@ Created; selected company updated
|
|||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| 400 | Validation error — `{"error":"…"}` |
|
| 400 | Validation error — `{"error":"…"}` |
|
||||||
| 401 | Unauthorized — `{"error":"…"}` |
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized (invited users cannot create companies when implicit create is disabled) — `{"error":"…"}` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ Company list
|
|||||||
"iso639": "…",
|
"iso639": "…",
|
||||||
"taxrate": 1,
|
"taxrate": 1,
|
||||||
"reducedtaxrate": 1,
|
"reducedtaxrate": 1,
|
||||||
"taxfreerate": 1
|
"taxfreerate": 1,
|
||||||
|
"role": "admin"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
58
docs/api/routes/companyuser/create.md
Normal file
58
docs/api/routes/companyuser/create.md
Normal 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`._
|
||||||
40
docs/api/routes/companyuser/delete.md
Normal file
40
docs/api/routes/companyuser/delete.md
Normal 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`._
|
||||||
37
docs/api/routes/companyuser/list.md
Normal file
37
docs/api/routes/companyuser/list.md
Normal 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`._
|
||||||
59
docs/api/routes/companyuser/update.md
Normal file
59
docs/api/routes/companyuser/update.md
Normal 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`._
|
||||||
@@ -4,42 +4,54 @@ 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 |
|
||||||
@@ -60,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":"…"}` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
57
docs/api/routes/labeltemplate/testprint.md
Normal file
57
docs/api/routes/labeltemplate/testprint.md
Normal 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`._
|
||||||
34
docs/api/routes/pos/cimage.md
Normal file
34
docs/api/routes/pos/cimage.md
Normal 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`._
|
||||||
@@ -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`
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
|
|||||||
@@ -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) |
|
||||||
|
|||||||
@@ -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": [
|
||||||
|
|||||||
@@ -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": [
|
||||||
|
|||||||
@@ -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": [
|
||||||
|
|||||||
37
docs/api/routes/printer/_id/state.md
Normal file
37
docs/api/routes/printer/_id/state.md
Normal 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`._
|
||||||
39
docs/api/routes/printer/state.md
Normal file
39
docs/api/routes/printer/state.md
Normal 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`._
|
||||||
44
docs/api/routes/printeragent.get.md
Normal file
44
docs/api/routes/printeragent.get.md
Normal 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`._
|
||||||
58
docs/api/routes/printeragent.put.md
Normal file
58
docs/api/routes/printeragent.put.md
Normal 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`._
|
||||||
30
docs/api/routes/printeragent/_id.delete.md
Normal file
30
docs/api/routes/printeragent/_id.delete.md
Normal 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`._
|
||||||
64
docs/api/routes/printeragent/_id.patch.md
Normal file
64
docs/api/routes/printeragent/_id.patch.md
Normal 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`._
|
||||||
46
docs/api/routes/printerjob.get.md
Normal file
46
docs/api/routes/printerjob.get.md
Normal 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`._
|
||||||
46
docs/api/routes/printerjob.post.md
Normal file
46
docs/api/routes/printerjob.post.md
Normal 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`._
|
||||||
39
docs/api/routes/printerjob/_id.delete.md
Normal file
39
docs/api/routes/printerjob/_id.delete.md
Normal 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`._
|
||||||
44
docs/api/routes/printerjob/_id.get.md
Normal file
44
docs/api/routes/printerjob/_id.get.md
Normal 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`._
|
||||||
37
docs/api/routes/printerjob/_id/cancel.md
Normal file
37
docs/api/routes/printerjob/_id/cancel.md
Normal 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`._
|
||||||
48
docs/api/routes/printerjob/cancel-all.md
Normal file
48
docs/api/routes/printerjob/cancel-all.md
Normal 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`._
|
||||||
36
docs/api/routes/retoure.get.md
Normal file
36
docs/api/routes/retoure.get.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# `GET /retoure`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
List retoures
|
||||||
|
|
||||||
|
Lists customer return manifests (`manifest` rows with `type = 'retoure'`)
|
||||||
|
for the authenticated user's selected company.
|
||||||
|
|
||||||
|
|
||||||
|
## Query parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `offset` | no | integer | |
|
||||||
|
| `limit` | no | integer | |
|
||||||
|
| `search` | no | string | |
|
||||||
|
| `onlyUnpaid` | no | boolean | |
|
||||||
|
| `onlyUndelivered` | no | boolean | |
|
||||||
|
| `entityId` | no | integer | |
|
||||||
|
| `seekMonths` | no | integer | |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
List of retoures
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | Validation error |
|
||||||
|
| 403 | Not authorized |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
33
docs/api/routes/retoure.post.md
Normal file
33
docs/api/routes/retoure.post.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# `POST /retoure`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Create retoure
|
||||||
|
|
||||||
|
## Body (JSON)
|
||||||
|
|
||||||
|
| Field | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `number` | no | string | |
|
||||||
|
| `date` | yes | string | |
|
||||||
|
| `entityId` | no | integer | |
|
||||||
|
| `externalNumber` | no | string | |
|
||||||
|
| `lines` | no | any[] | |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"number": "…",
|
||||||
|
"date": "…",
|
||||||
|
"entityId": 1,
|
||||||
|
"externalNumber": "…",
|
||||||
|
"lines": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response `201`
|
||||||
|
|
||||||
|
Retoure created
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
21
docs/api/routes/retoure/_id.delete.md
Normal file
21
docs/api/routes/retoure/_id.delete.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# `DELETE /retoure/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Delete retoure
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `204`
|
||||||
|
|
||||||
|
Retoure deleted
|
||||||
|
|
||||||
|
_No content_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
25
docs/api/routes/retoure/_id.get.md
Normal file
25
docs/api/routes/retoure/_id.get.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# `GET /retoure/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Get retoure by ID
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Retoure details
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 404 | Retoure not found |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
25
docs/api/routes/retoure/_id.put.md
Normal file
25
docs/api/routes/retoure/_id.put.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# `PUT /retoure/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Update retoure
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Body (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Retoure updated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
19
docs/api/routes/retoure/_id/shipment.md
Normal file
19
docs/api/routes/retoure/_id/shipment.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# `POST /retoure/{id}/shipment`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Create shipment for retoure
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `201`
|
||||||
|
|
||||||
|
Shipment created
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
13
docs/api/routes/retoure/rebuild-summaries.md
Normal file
13
docs/api/routes/retoure/rebuild-summaries.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# `POST /retoure/rebuild-summaries`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Rebuild retoure summaries
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Summaries rebuilt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
23
docs/api/routes/return.get.md
Normal file
23
docs/api/routes/return.get.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# `GET /return`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
List returns
|
||||||
|
|
||||||
|
## Query parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `offset` | no | integer | |
|
||||||
|
| `limit` | no | integer | |
|
||||||
|
| `search` | no | string | |
|
||||||
|
| `onlyUnbooked` | no | boolean | |
|
||||||
|
| `supplierId` | no | integer | |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
List of returns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
19
docs/api/routes/return.post.md
Normal file
19
docs/api/routes/return.post.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# `POST /return`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Create return
|
||||||
|
|
||||||
|
## Body (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response `201`
|
||||||
|
|
||||||
|
Return created
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
21
docs/api/routes/return/_id.delete.md
Normal file
21
docs/api/routes/return/_id.delete.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# `DELETE /return/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Delete return
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `204`
|
||||||
|
|
||||||
|
Return deleted
|
||||||
|
|
||||||
|
_No content_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
19
docs/api/routes/return/_id.get.md
Normal file
19
docs/api/routes/return/_id.get.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# `GET /return/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Get return by ID
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Return details
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
19
docs/api/routes/return/_id.put.md
Normal file
19
docs/api/routes/return/_id.put.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# `PUT /return/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Update return
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Return updated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
19
docs/api/routes/return/_id/book.md
Normal file
19
docs/api/routes/return/_id/book.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# `POST /return/{id}/book`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Book return to ledger
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Return booked
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
13
docs/api/routes/return/rebuild-summaries.md
Normal file
13
docs/api/routes/return/rebuild-summaries.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# `POST /return/rebuild-summaries`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Rebuild return summaries
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Summaries rebuilt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
@@ -4,7 +4,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
|||||||
|
|
||||||
Update shipment state or metadata
|
Update shipment state or metadata
|
||||||
|
|
||||||
Updates delivery state (`new`, `delivered`, `returned`) for the shipment.
|
Updates delivery state (`new`, `delivered`) for the shipment.
|
||||||
Emits SSE events (`shipment`, `order`) so connected clients rerender in real time.
|
Emits SSE events (`shipment`, `order`) so connected clients rerender in real time.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ Ship shipment manifest item(s)
|
|||||||
Books shipment for a shipment manifest item on the ledger (warehouse_id = null).
|
Books shipment for a shipment manifest item on the ledger (warehouse_id = null).
|
||||||
If the connected article has stock tracking enabled, an adjustment entry is also
|
If the connected article has stock tracking enabled, an adjustment entry is also
|
||||||
booked to deduct physical stock from the target warehouse.
|
booked to deduct physical stock from the target warehouse.
|
||||||
If line amount is negative, books as a customer retoure (incoming).
|
|
||||||
|
|
||||||
|
|
||||||
## Body (JSON)
|
## Body (JSON)
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ Creates a supplier in the company `Lieferanten` group (created on first use).
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
On success, emits SSE signal: `supplier` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
## Response `201`
|
## Response `201`
|
||||||
|
|
||||||
Created supplier
|
Created supplier
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ Related `entityhistory` cascades; `manifest.entity_id` is set null.
|
|||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `id` | yes | integer | |
|
| `id` | yes | integer | |
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
On success, emits SSE signal: `supplier` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
## Response `204`
|
## Response `204`
|
||||||
|
|
||||||
Deleted
|
Deleted
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ Renames a supplier for the selected company.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
On success, emits SSE signal: `supplier` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|
||||||
Updated supplier
|
Updated supplier
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Returns category hierarchy modified after lastChangedCategory cursor for the aut
|
|||||||
|
|
||||||
| Param | Required | Type | Description |
|
| Param | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `lastChangedCategory` | no | string | Unix timestamp cursor of last category sync |
|
| `lastChangedCategory` | no | string | Sequential ID cursor of last synced category entry (`posterminalsyncqueue.id`) |
|
||||||
| `limit` | no | integer | Maximum number of categories to return |
|
| `limit` | no | integer | Maximum number of categories to return |
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Returns customer list modified after lastChangedCustomer cursor for the authenti
|
|||||||
|
|
||||||
| Param | Required | Type | Description |
|
| Param | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `lastChangedCustomer` | no | string | Unix timestamp cursor of last customer sync |
|
| `lastChangedCustomer` | no | string | Sequential ID cursor of last synced customer entry (`posterminalsyncqueue.id`) |
|
||||||
| `limit` | no | integer | Maximum number of customers to return |
|
| `limit` | no | integer | Maximum number of customers to return |
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Returns customer groups modified after lastChangedCustomerGroup cursor for the a
|
|||||||
|
|
||||||
| Param | Required | Type | Description |
|
| Param | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `lastChangedCustomerGroup` | no | string | Unix timestamp cursor of last customer group sync |
|
| `lastChangedCustomerGroup` | no | string | Sequential ID cursor of last synced customer group entry (`posterminalsyncqueue.id`) |
|
||||||
| `limit` | no | integer | Maximum number of customer groups to return |
|
| `limit` | no | integer | Maximum number of customer groups to return |
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
@@ -24,7 +24,7 @@ Array of customer group objects for JTL-POS
|
|||||||
"name": "Endkunden",
|
"name": "Endkunden",
|
||||||
"standard": "1",
|
"standard": "1",
|
||||||
"discountPercent": "0.000000",
|
"discountPercent": "0.000000",
|
||||||
"lastChanged": "1721000000"
|
"lastChanged": "12"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
|||||||
|
|
||||||
JTL-POS order upload
|
JTL-POS order upload
|
||||||
|
|
||||||
Imports orders from JTL-POS, creating order manifests, payments, and shipments with stock deductions.
|
Imports orders from JTL-POS, creating order and retoure manifests, payments, and shipments with stock adjustments.
|
||||||
|
|
||||||
## Query parameters
|
## Query parameters
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Returns product catalog with prices, stock, variants, and image hashes modified
|
|||||||
|
|
||||||
| Param | Required | Type | Description |
|
| Param | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `lastChangedProduct` | no | string | Unix timestamp cursor of last product sync |
|
| `lastChangedProduct` | no | string | Sequential ID cursor of last synced product entry (`posterminalsyncqueue.id`) |
|
||||||
| `limit` | no | integer | Maximum number of products to return |
|
| `limit` | no | integer | Maximum number of products to return |
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Applets return typed CSV, not JSON: `result` is a `;`-separated CSV string with
|
|||||||
response also carries `columns` (name/type) and `rows` (data row count). Only declared columns are
|
response also carries `columns` (name/type) and `rows` (data row count). Only declared columns are
|
||||||
emitted, in header order; missing values become empty cells. Execution runs in the same isolated worker
|
emitted, in header order; missing values become empty cells. Execution runs in the same isolated worker
|
||||||
sandbox as the `javascript_execute` tool — including authenticated `fetch()` against this API,
|
sandbox as the `javascript_execute` tool — including authenticated `fetch()` against this API,
|
||||||
`fs`/`vfs`, `sharp`, ExcelJS, PDFKit and WebCrypto globals.
|
`fs`/`vfs`, `optimizeImage`, ExcelJS, PDFKit and WebCrypto globals.
|
||||||
|
|
||||||
The path is taken from the URL (like `DELETE /vfs/{*path}`); the run arguments are the JSON body.
|
The path is taken from the URL (like `DELETE /vfs/{*path}`); the run arguments are the JSON body.
|
||||||
Path traversal (`..`), null bytes, and backslashes are rejected with HTTP 400, as is a path that does
|
Path traversal (`..`), null bytes, and backslashes are rejected with HTTP 400, as is a path that does
|
||||||
|
|||||||
56
docs/api/routes/zpljob.md
Normal file
56
docs/api/routes/zpljob.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# `POST /zpljob`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Create ZPL print job
|
||||||
|
|
||||||
|
Issues a new ZPL print job to an agent printer. If copiescount > 1, the ZPL quantity command (^PQ)
|
||||||
|
is automatically applied. The connected printer agent receives an immediate real-time event.
|
||||||
|
|
||||||
|
|
||||||
|
## Body (JSON)
|
||||||
|
|
||||||
|
| Field | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `agentprinterId` | yes | integer | Target agent printer ID |
|
||||||
|
| `copiescount` | no | integer | Optional copy count (defaults to 1) |
|
||||||
|
| `data` | yes | string | Raw ZPL string |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agentprinterId": 1,
|
||||||
|
"copiescount": 2,
|
||||||
|
"data": "^XA^FO50,50^ADN,36,20^FDHello Zebra^FS^XZ"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response `201`
|
||||||
|
|
||||||
|
Print 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | Invalid input or missing fields — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 404 | Agent printer not found — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
41
docs/dash.sh
Executable file
41
docs/dash.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Ensure private Docker network exists and sql_server is connected
|
||||||
|
docker network create dash_net 2>/dev/null || true
|
||||||
|
docker network connect dash_net sql_server 2>/dev/null || true
|
||||||
|
|
||||||
|
docker rm -f dashback 2>/dev/null
|
||||||
|
|
||||||
|
docker run -d \
|
||||||
|
--name dashback \
|
||||||
|
--log-opt max-size=10m \
|
||||||
|
--log-opt max-file=3 \
|
||||||
|
--restart always \
|
||||||
|
--network dash_net \
|
||||||
|
--read-only \
|
||||||
|
--tmpfs /tmp:rw,noexec,nosuid,size=512m \
|
||||||
|
--tmpfs /run:rw,noexec,nosuid,size=16m \
|
||||||
|
-u $(id -u):$(id -g) \
|
||||||
|
-p 127.0.0.1:4211:4211 \
|
||||||
|
-e HOST=0.0.0.0 \
|
||||||
|
-e PORT=4211 \
|
||||||
|
-e HTTPS_PORT=0 \
|
||||||
|
-e MSSQL_SERVER=sql_server \
|
||||||
|
-e HEALTH_MONITOR_ENABLED=false \
|
||||||
|
-e SQLITE_PATH=/app/data/dash.db \
|
||||||
|
-e STORAGE_DIR=/app/data/storage \
|
||||||
|
-e BACKUP_DIR=/app/data/backups \
|
||||||
|
-e GOOGLE_CLIENT_ID=928121624463-m9du45ek2d5f6p9cke8qpnf89ii9no20.apps.googleusercontent.com \
|
||||||
|
-e JWT_SECRET=dfhjkswezui32476sdjkhvbn347865 \
|
||||||
|
-e IMPLICIT_CREATE=true \
|
||||||
|
-e VHOST_SETTINGS="" \
|
||||||
|
-e DB_PROVIDER=mssql \
|
||||||
|
-e MSSQL_DATABASE=dashback \
|
||||||
|
-e MSSQL_USER=sa \
|
||||||
|
-e MSSQL_PASSWORD=420Green \
|
||||||
|
-e MSSQL_PORT=1433 \
|
||||||
|
-e MSSQL_ENCRYPT=false \
|
||||||
|
-e MSSQL_TRUST_SERVER_CERTIFICATE=false \
|
||||||
|
-v "/home/ubuntu/src/docker/dashback_data:/app/data:rw" \
|
||||||
|
-v "/home/ubuntu/src/docker/dashback/dist:/app/dist:ro" \
|
||||||
|
-v "/home/ubuntu/src/docker/dashback/dist/dashback_current:/app/dist/dashback:ro" \
|
||||||
|
dashback:latest
|
||||||
27
docs/dashdemo.sh
Executable file
27
docs/dashdemo.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
docker rm -f dashdemo 2>/dev/null
|
||||||
|
|
||||||
|
docker run -d \
|
||||||
|
--name dashdemo \
|
||||||
|
--log-opt max-size=10m \
|
||||||
|
--log-opt max-file=3 \
|
||||||
|
--restart always \
|
||||||
|
--read-only \
|
||||||
|
--tmpfs /tmp:rw,noexec,nosuid,size=512m \
|
||||||
|
--tmpfs /run:rw,noexec,nosuid,size=16m \
|
||||||
|
-u $(id -u):$(id -g) \
|
||||||
|
-p 127.0.0.1:4209:4209 \
|
||||||
|
-e HOST=0.0.0.0 \
|
||||||
|
-e PORT=4209 \
|
||||||
|
-e HTTPS_PORT=0 \
|
||||||
|
-e HEALTH_MONITOR_ENABLED=false \
|
||||||
|
-e SQLITE_PATH=/app/data/dash.db \
|
||||||
|
-e STORAGE_DIR=/app/data/storage \
|
||||||
|
-e BACKUP_DIR=/app/data/backups \
|
||||||
|
-e GOOGLE_CLIENT_ID=928121624463-m9du45ek2d5f6p9cke8qpnf89ii9no20.apps.googleusercontent.com \
|
||||||
|
-e JWT_SECRET=dfhjkswezui32476sdjkhvbn347865 \
|
||||||
|
-e IMPLICIT_CREATE=true \
|
||||||
|
-v "/home/ubuntu/dashdemo:/app/data:rw" \
|
||||||
|
-v "/home/ubuntu/src/docker/dashback/dist:/app/dist:ro" \
|
||||||
|
-v "/home/ubuntu/src/docker/dashback/dist/dashback_current:/app/dist/dashback:ro" \
|
||||||
|
dashback:latest
|
||||||
14
docs/mssql.sh
Executable file
14
docs/mssql.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
docker run -d \
|
||||||
|
--name sql_server \
|
||||||
|
--restart always \
|
||||||
|
-e "ACCEPT_EULA=Y" \
|
||||||
|
-e "MSSQL_PID=Standard" \
|
||||||
|
-e "MSSQL_SA_PASSWORD=420Green" \
|
||||||
|
-e "MSSQL_COLLATION=Latin1_General_CI_AS" \
|
||||||
|
-p 127.0.0.1:1433:1433 \
|
||||||
|
-p 192.168.122.1:1433:1433 \
|
||||||
|
-v /home/ubuntu/mssql_data:/var/opt/mssql \
|
||||||
|
-v /home/ubuntu/shared_backups:/shared \
|
||||||
|
mcr.microsoft.com/mssql/server:2025-latest
|
||||||
|
|
||||||
|
|
||||||
74
docs/update.sh
Executable file
74
docs/update.sh
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
echo "=== 1. Pulling latest changes in all repositories ==="
|
||||||
|
for dir in "$ROOT_DIR"/*/; do
|
||||||
|
if [ -d "${dir}.git" ]; then
|
||||||
|
repo_name="$(basename "$dir")"
|
||||||
|
echo "-> git pull in ${repo_name}"
|
||||||
|
git -C "$dir" pull
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== 2. Running npm ci in dashfront, dashdbschema, and dashback ==="
|
||||||
|
for pkg in dashfront dashdbschema dashback; do
|
||||||
|
echo "-> npm ci in ${pkg}"
|
||||||
|
npm --prefix "${ROOT_DIR}/${pkg}" ci
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== 3. Building dashfront ==="
|
||||||
|
npm --prefix "${ROOT_DIR}/dashfront" run build
|
||||||
|
|
||||||
|
echo "=== 4. Building SEA in dashback ==="
|
||||||
|
npm --prefix "${ROOT_DIR}/dashback" run sea
|
||||||
|
|
||||||
|
DOCKER_DIST_DIR="/home/ubuntu/src/docker/dashback/dist"
|
||||||
|
mkdir -p "$DOCKER_DIST_DIR"
|
||||||
|
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
|
||||||
|
NEW_SEA="${DOCKER_DIST_DIR}/dashback_${TIMESTAMP}"
|
||||||
|
CURRENT_SEA="${DOCKER_DIST_DIR}/dashback_current"
|
||||||
|
PREV_SEA="$(readlink -f "$CURRENT_SEA" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
echo "-> Copying SEA to ${NEW_SEA} and linking ${CURRENT_SEA}..."
|
||||||
|
touch "${DOCKER_DIST_DIR}/dashback"
|
||||||
|
cp "${ROOT_DIR}/dashback/dist/dashback" "$NEW_SEA"
|
||||||
|
chmod +x "$NEW_SEA"
|
||||||
|
ln -sfn "$NEW_SEA" "$CURRENT_SEA"
|
||||||
|
|
||||||
|
echo "=== 5. Running self-test on dashback_current in test container ==="
|
||||||
|
exec 3>&1
|
||||||
|
set +e
|
||||||
|
SELFTEST_OUTPUT=$(docker run --rm \
|
||||||
|
--read-only \
|
||||||
|
--tmpfs /tmp:rw,noexec,nosuid,size=512m \
|
||||||
|
--tmpfs /run:rw,noexec,nosuid,size=16m \
|
||||||
|
--tmpfs /app/data:rw,noexec,nosuid,size=64m \
|
||||||
|
-u "$(id -u):$(id -g)" \
|
||||||
|
-v "${DOCKER_DIST_DIR}:/app/dist:ro" \
|
||||||
|
-v "${CURRENT_SEA}:/app/dist/dashback:ro" \
|
||||||
|
dashback:latest \
|
||||||
|
./dist/dashback --selftest 2>&1 | tee /dev/fd/3; exit "${PIPESTATUS[0]}")
|
||||||
|
SELFTEST_EXIT=$?
|
||||||
|
set -e
|
||||||
|
exec 3>&-
|
||||||
|
|
||||||
|
if [ "$SELFTEST_EXIT" -ne 0 ] || ! grep -q "Self-test PASSED" <<< "$SELFTEST_OUTPUT"; then
|
||||||
|
echo "ERROR: Self-test did not confirm 'Self-test PASSED'. Aborting container restart." >&2
|
||||||
|
if [ -n "$PREV_SEA" ] && [ -f "$PREV_SEA" ]; then
|
||||||
|
echo "-> Restoring ${CURRENT_SEA} to ${PREV_SEA}..." >&2
|
||||||
|
ln -sfn "$PREV_SEA" "$CURRENT_SEA"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== 6. Restarting dashback containers ==="
|
||||||
|
CONTAINERS=$(docker ps -q --filter "ancestor=dashback:latest")
|
||||||
|
if [ -n "$CONTAINERS" ]; then
|
||||||
|
echo "-> Restarting dashback containers ($(docker ps --filter "ancestor=dashback:latest" --format '{{.Names}}' | paste -sd ', ' -))..."
|
||||||
|
docker restart $CONTAINERS
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Update complete! ==="
|
||||||
|
|
||||||
1133
package-lock.json
generated
1133
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -9,11 +9,17 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"dev": "cross-env DEV_BACKEND_MONITOR=true node --watch-path=./src --watch-path=./.env --watch-path=../dashdbschema/src --watch-path=../llmorbiter/src src/index.js",
|
"dev": "cross-env DEV_BACKEND_MONITOR=true node --watch-path=./src --watch-path=./.env --watch-path=../dashdbschema/src --watch-path=../llmorbiter/src src/index.js",
|
||||||
|
"self-test": "node src/index.js --self-test",
|
||||||
"manage-users": "node scripts/manageUsers.js",
|
"manage-users": "node scripts/manageUsers.js",
|
||||||
"wipe-company": "node scripts/wipeCompany.js",
|
"wipe-company": "node scripts/wipeCompany.js",
|
||||||
|
"downgrade": "node scripts/downgradeSchema.js",
|
||||||
"docs:api": "node scripts/generateApiDocs.js",
|
"docs:api": "node scripts/generateApiDocs.js",
|
||||||
"api-graph": "node generate-api-graph.js",
|
"api-graph": "node generate-api-graph.js",
|
||||||
"review:functions": "node scripts/extractFunctions.js",
|
"review:functions": "node scripts/extractFunctions.js",
|
||||||
|
"build:sea": "node scripts/buildSea.js",
|
||||||
|
"sea": "npm run build:sea",
|
||||||
|
"docker:build": "docker build -t dashback:latest -t dashback:1.0.0 .",
|
||||||
|
"docker": "npm run docker:build",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -37,12 +43,12 @@
|
|||||||
"pdfconvert-wasm": "^0.3.0",
|
"pdfconvert-wasm": "^0.3.0",
|
||||||
"pdfkit": "^0.20.2",
|
"pdfkit": "^0.20.2",
|
||||||
"react": "^19.3.1",
|
"react": "^19.3.1",
|
||||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor#v20.0.0",
|
||||||
"selfsigned": "^5.5.0",
|
"selfsigned": "^5.5.0",
|
||||||
"sharp": "^0.35.5",
|
"skelehash-wasm": "^0.1.1",
|
||||||
"skelehash-wasm": "^0.1.0",
|
|
||||||
"swagger-ui-dist": "^5.33.3",
|
"swagger-ui-dist": "^5.33.3",
|
||||||
"vm2": "^3.12.2",
|
"vm2": "^3.12.2",
|
||||||
|
"wasm-image-optimization": "^2.0.13",
|
||||||
"zod": "^4.6.7"
|
"zod": "^4.6.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -51,8 +57,7 @@
|
|||||||
"swagger-jsdoc": "^6.3.0"
|
"swagger-jsdoc": "^6.3.0"
|
||||||
},
|
},
|
||||||
"allowScripts": {
|
"allowScripts": {
|
||||||
"github:sebseb7/react-canvas-label-editor#7f0e31b66ff22da011e872b201017398b14e9dd9": true,
|
"react-canvas-label-editor@20.0.0": true,
|
||||||
"canvas@3.2.3": true,
|
|
||||||
"better-sqlite3@13.0.3": true
|
"better-sqlite3@13.0.3": true
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|||||||
BIN
scratch/curl-pimage.jpg
Normal file
BIN
scratch/curl-pimage.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
172
scripts/buildSea.js
Normal file
172
scripts/buildSea.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import zlib from 'node:zlib';
|
||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const rootDir = path.resolve(__dirname, '..');
|
||||||
|
const distDir = path.join(rootDir, 'dist');
|
||||||
|
const srcDir = path.join(rootDir, 'src');
|
||||||
|
const configPath = path.join(rootDir, 'sea-config.json');
|
||||||
|
const payloadPath = path.join(distDir, 'app-payload.bin');
|
||||||
|
|
||||||
|
const dbschemaDir = path.resolve(rootDir, '../dashdbschema');
|
||||||
|
const llmorbiterDir = path.resolve(rootDir, '../llmorbiter');
|
||||||
|
|
||||||
|
if (!fs.existsSync(distDir)) {
|
||||||
|
fs.mkdirSync(distDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Packaging application, siblings, and JavaScript dependencies into SEA payload (excluding native binaries)...');
|
||||||
|
|
||||||
|
const nativeNodes = {};
|
||||||
|
|
||||||
|
function findNearestPackageJson(startDir) {
|
||||||
|
let dir = startDir;
|
||||||
|
while (dir && dir !== path.dirname(dir)) {
|
||||||
|
const pkgFile = path.join(dir, 'package.json');
|
||||||
|
if (fs.existsSync(pkgFile)) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
dir = path.dirname(dir);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionedNodeFilename(fullPath) {
|
||||||
|
const base = path.basename(fullPath, '.node');
|
||||||
|
const pkg = findNearestPackageJson(path.dirname(fullPath));
|
||||||
|
if (!pkg || !pkg.name || !pkg.version) {
|
||||||
|
return `${base}.node`;
|
||||||
|
}
|
||||||
|
const shortName = pkg.name.replace(/^@[^/]+\//, '');
|
||||||
|
if (base.includes(pkg.version)) {
|
||||||
|
return `${base}.node`;
|
||||||
|
}
|
||||||
|
if (base === shortName) {
|
||||||
|
return `${shortName}-${process.platform}-${process.arch}-${pkg.version}.node`;
|
||||||
|
}
|
||||||
|
return `${shortName}-${base}-${pkg.version}.node`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNativeBinary(name) {
|
||||||
|
return (
|
||||||
|
name.endsWith('.node') ||
|
||||||
|
name.endsWith('.so') ||
|
||||||
|
name.includes('.so.') ||
|
||||||
|
name.endsWith('.dylib') ||
|
||||||
|
name.endsWith('.dll') ||
|
||||||
|
name.endsWith('.a') ||
|
||||||
|
name.endsWith('.o') ||
|
||||||
|
name.endsWith('.cc') ||
|
||||||
|
name.endsWith('.cpp') ||
|
||||||
|
name.endsWith('.c') ||
|
||||||
|
name.endsWith('.h')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function packDirectory(dir, prefix, result) {
|
||||||
|
if (!fs.existsSync(dir)) return;
|
||||||
|
try {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
const rel = path.join(prefix, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (entry.name === 'test' || entry.name === 'tests' || entry.name === 'docs' || entry.name === '.github' || entry.name === '.git' || entry.name === 'obj.target') continue;
|
||||||
|
packDirectory(full, rel, result);
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
if (entry.name.endsWith('.node')) {
|
||||||
|
nativeNodes[rel] = getVersionedNodeFilename(full);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isNativeBinary(entry.name)) continue;
|
||||||
|
result[rel] = fs.readFileSync(full).toString('base64');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
// 1. Pack dashback src and package.json
|
||||||
|
packDirectory(srcDir, 'src', payload);
|
||||||
|
const pkgPath = path.join(rootDir, 'package.json');
|
||||||
|
if (fs.existsSync(pkgPath)) {
|
||||||
|
payload['package.json'] = fs.readFileSync(pkgPath).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Pack sibling dependencies (libdashstore and libllmorbiter) directly into node_modules
|
||||||
|
if (fs.existsSync(path.join(dbschemaDir, 'src'))) {
|
||||||
|
packDirectory(path.join(dbschemaDir, 'src'), 'node_modules/libdashstore/src', payload);
|
||||||
|
const dbschemaPkg = path.join(dbschemaDir, 'package.json');
|
||||||
|
if (fs.existsSync(dbschemaPkg)) {
|
||||||
|
payload['node_modules/libdashstore/package.json'] = fs.readFileSync(dbschemaPkg).toString('base64');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(llmorbiterDir, 'src'))) {
|
||||||
|
packDirectory(path.join(llmorbiterDir, 'src'), 'node_modules/libllmorbiter/src', payload);
|
||||||
|
const llmorbiterPkg = path.join(llmorbiterDir, 'package.json');
|
||||||
|
if (fs.existsSync(llmorbiterPkg)) {
|
||||||
|
payload['node_modules/libllmorbiter/package.json'] = fs.readFileSync(llmorbiterPkg).toString('base64');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Pack all node_modules dependencies
|
||||||
|
function packNodeModules(nmPath) {
|
||||||
|
if (!fs.existsSync(nmPath)) return;
|
||||||
|
for (const name of fs.readdirSync(nmPath)) {
|
||||||
|
if (name.startsWith('.') || name === 'libdashstore' || name === 'libllmorbiter') continue;
|
||||||
|
const pkgDir = path.join(nmPath, name);
|
||||||
|
if (name.startsWith('@')) {
|
||||||
|
for (const sub of fs.readdirSync(pkgDir)) {
|
||||||
|
packDirectory(path.join(pkgDir, sub), `node_modules/${name}/${sub}`, payload);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
packDirectory(pkgDir, `node_modules/${name}`, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packNodeModules(path.join(rootDir, 'node_modules'));
|
||||||
|
packNodeModules(path.join(dbschemaDir, 'node_modules'));
|
||||||
|
|
||||||
|
payload['.native-nodes.json'] = Buffer.from(JSON.stringify(nativeNodes)).toString('base64');
|
||||||
|
|
||||||
|
const compressedPayload = zlib.deflateSync(Buffer.from(JSON.stringify(payload)));
|
||||||
|
fs.writeFileSync(payloadPath, compressedPayload);
|
||||||
|
|
||||||
|
console.log(`Packed ${Object.keys(payload).length} files (${(compressedPayload.length / 1024 / 1024).toFixed(2)} MB compressed)`);
|
||||||
|
|
||||||
|
// Prepare sea-config.json
|
||||||
|
const config = {
|
||||||
|
main: 'src/sea-entry.cjs',
|
||||||
|
output: 'dist/dashback',
|
||||||
|
assets: {
|
||||||
|
'app-payload.bin': 'dist/app-payload.bin',
|
||||||
|
},
|
||||||
|
disableExperimentalSEAWarning: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
||||||
|
|
||||||
|
console.log('Generating Single Executable Application (SEA)...');
|
||||||
|
try {
|
||||||
|
execFileSync(process.execPath, ['--build-sea', 'sea-config.json'], {
|
||||||
|
cwd: rootDir,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
const outputPath = path.join(distDir, 'dashback');
|
||||||
|
if (process.platform !== 'win32' && fs.existsSync(outputPath)) {
|
||||||
|
fs.chmodSync(outputPath, 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nSuccessfully built standalone SEA: ${path.relative(rootDir, outputPath)}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('\nFailed to build SEA:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
30
scripts/downgradeSchema.js
Normal file
30
scripts/downgradeSchema.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Downgrades the configured database (from `.env`) to a target schema version.
|
||||||
|
*
|
||||||
|
* npm run downgrade -- <targetVersion>
|
||||||
|
*
|
||||||
|
* Uses libdashstore's `src/providers/<provider>/downgrade/<from>_to_<to>.js`.
|
||||||
|
* Stop the API first; starting it again upgrades back to the latest version.
|
||||||
|
*/
|
||||||
|
import { createProvider } from '../src/db/provider.js';
|
||||||
|
import env from '../src/config/env.js';
|
||||||
|
|
||||||
|
const [rawTarget] = process.argv.slice(2);
|
||||||
|
const target = Number(rawTarget);
|
||||||
|
if (!rawTarget || !Number.isInteger(target) || target < 1) {
|
||||||
|
console.error('Usage: npm run downgrade -- <targetVersion>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = createProvider();
|
||||||
|
try {
|
||||||
|
const before = await provider.getSchemaVersion();
|
||||||
|
const steps = await provider.downgradeSchema(target);
|
||||||
|
console.log(`Schema ${env.dbProvider}: ${before} -> ${target} (${steps} step(s))`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Downgrade failed: ${err.message}`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
} finally {
|
||||||
|
await provider.close();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -39,12 +39,16 @@ const OMITTED_ROUTES = new Set([
|
|||||||
'POST /serper/image',
|
'POST /serper/image',
|
||||||
'POST /serper/images',
|
'POST /serper/images',
|
||||||
'GET /shipment/{id}/file/{fileId}',
|
'GET /shipment/{id}/file/{fileId}',
|
||||||
|
'GET /smallthumb/{fullHash}',
|
||||||
'GET /thumb/{fullHash}',
|
'GET /thumb/{fullHash}',
|
||||||
'GET /v1/category',
|
'GET /v1/category',
|
||||||
|
'GET /v1/cimage',
|
||||||
'GET /v1/client',
|
'GET /v1/client',
|
||||||
'GET /v1/customer',
|
'GET /v1/customer',
|
||||||
'GET /v1/customergroup',
|
'GET /v1/customergroup',
|
||||||
|
'GET /v1/deletedentity',
|
||||||
'GET /v1/init',
|
'GET /v1/init',
|
||||||
|
'POST /v1/order',
|
||||||
'GET /v1/pimage',
|
'GET /v1/pimage',
|
||||||
'GET /v1/product',
|
'GET /v1/product',
|
||||||
]);
|
]);
|
||||||
@@ -80,6 +84,36 @@ function isRouteOmitted(method, apiPath) {
|
|||||||
if (apiPath === '/auth' || apiPath.startsWith('/auth/')) {
|
if (apiPath === '/auth' || apiPath.startsWith('/auth/')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (apiPath === '/agent' || apiPath.startsWith('/agent/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/agentprinter' || apiPath.startsWith('/agentprinter/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/printer' || apiPath.startsWith('/printer/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/printeragent' || apiPath.startsWith('/printeragent/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/printerjob' || apiPath.startsWith('/printerjob/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/smallthumb' || apiPath.startsWith('/smallthumb/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/v1' || apiPath.startsWith('/v1/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/health' || apiPath.startsWith('/health/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/subagentschedule' || apiPath.startsWith('/subagentschedule/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apiPath === '/zpljob' || apiPath.startsWith('/zpljob/')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return OMITTED_ROUTES.has(`${method.toUpperCase()} ${apiPath}`);
|
return OMITTED_ROUTES.has(`${method.toUpperCase()} ${apiPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
-- Rename article "father" columns to "parent" (SQL Server / MSSQL).
|
|
||||||
--
|
|
||||||
-- One-off manual migration (no migration code is kept in the repo).
|
|
||||||
-- sqlcmd -S <server> -d <db> -E -i scripts/migrate-parent-rename.mssql.sql
|
|
||||||
--
|
|
||||||
-- Guarded and safe to re-run. Safe to retry after a partial run.
|
|
||||||
--
|
|
||||||
-- NOTE ON BATCHING: sp_rename is deferred and its effect is not visible to
|
|
||||||
-- subsequent statements in the SAME batch. Each rename therefore runs in its
|
|
||||||
-- own batch (GO), and constraint/index recreation happens after a GO so the
|
|
||||||
-- new column names resolve.
|
|
||||||
|
|
||||||
SET XACT_ABORT ON;
|
|
||||||
GO
|
|
||||||
|
|
||||||
BEGIN TRANSACTION;
|
|
||||||
GO
|
|
||||||
|
|
||||||
-- Drop the old filtered unique index (references fathersort).
|
|
||||||
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'UQ_article_fatherarticle_fathersort')
|
|
||||||
DROP INDEX UQ_article_fatherarticle_fathersort ON article;
|
|
||||||
GO
|
|
||||||
|
|
||||||
-- Drop the default constraints bound to the old column names.
|
|
||||||
IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_article_is_father')
|
|
||||||
ALTER TABLE article DROP CONSTRAINT DF_article_is_father;
|
|
||||||
IF EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_article_fathersort')
|
|
||||||
ALTER TABLE article DROP CONSTRAINT DF_article_fathersort;
|
|
||||||
GO
|
|
||||||
|
|
||||||
-- Drop foreign keys bound to the old columns.
|
|
||||||
IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_article_fatherarticle')
|
|
||||||
ALTER TABLE article DROP CONSTRAINT FK_article_fatherarticle;
|
|
||||||
IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_article_fatherattributename')
|
|
||||||
ALTER TABLE article DROP CONSTRAINT FK_article_fatherattributename;
|
|
||||||
GO
|
|
||||||
|
|
||||||
-- Rename the columns (each sp_rename must be its own batch).
|
|
||||||
IF COL_LENGTH('article', 'fatherarticle_id') IS NOT NULL
|
|
||||||
AND COL_LENGTH('article', 'parentarticle_id') IS NULL
|
|
||||||
EXEC sp_rename 'article.fatherarticle_id', 'parentarticle_id', 'COLUMN';
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF COL_LENGTH('article', 'fatherattributename_id') IS NOT NULL
|
|
||||||
AND COL_LENGTH('article', 'parentattributename_id') IS NULL
|
|
||||||
EXEC sp_rename 'article.fatherattributename_id', 'parentattributename_id', 'COLUMN';
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF COL_LENGTH('article', 'fathersort') IS NOT NULL
|
|
||||||
AND COL_LENGTH('article', 'parentsort') IS NULL
|
|
||||||
EXEC sp_rename 'article.fathersort', 'parentsort', 'COLUMN';
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF COL_LENGTH('article', 'is_father') IS NOT NULL
|
|
||||||
AND COL_LENGTH('article', 'is_parent') IS NULL
|
|
||||||
EXEC sp_rename 'article.is_father', 'is_parent', 'COLUMN';
|
|
||||||
GO
|
|
||||||
|
|
||||||
COMMIT TRANSACTION;
|
|
||||||
GO
|
|
||||||
|
|
||||||
-- Recreate the constraints under their new names (after the rename batches).
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_article_parentarticle')
|
|
||||||
ALTER TABLE article ADD CONSTRAINT FK_article_parentarticle
|
|
||||||
FOREIGN KEY (parentarticle_id) REFERENCES article(id) ON DELETE NO ACTION;
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_article_parentattributename')
|
|
||||||
ALTER TABLE article ADD CONSTRAINT FK_article_parentattributename
|
|
||||||
FOREIGN KEY (parentattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION;
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_article_is_parent')
|
|
||||||
ALTER TABLE article ADD CONSTRAINT DF_article_is_parent DEFAULT (0) FOR is_parent;
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.default_constraints WHERE name = 'DF_article_parentsort')
|
|
||||||
ALTER TABLE article ADD CONSTRAINT DF_article_parentsort DEFAULT (0) FOR parentsort;
|
|
||||||
GO
|
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'UQ_article_parentarticle_parentsort')
|
|
||||||
CREATE UNIQUE INDEX UQ_article_parentarticle_parentsort
|
|
||||||
ON article (parentarticle_id, parentsort)
|
|
||||||
WHERE parentarticle_id IS NOT NULL;
|
|
||||||
GO
|
|
||||||
|
|
||||||
-- Verify
|
|
||||||
SELECT 'COL:' + c.name AS result
|
|
||||||
FROM sys.columns c
|
|
||||||
WHERE c.object_id = OBJECT_ID('article')
|
|
||||||
AND (c.name LIKE '%father%' OR c.name LIKE '%parent%');
|
|
||||||
GO
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
-- Rename article "father" columns to "parent".
|
|
||||||
--
|
|
||||||
-- One-off manual migration (no migration code is kept in the repo).
|
|
||||||
-- sqlite3 data/dash.db < scripts/migrate-parent-rename.sqlite.sql
|
|
||||||
--
|
|
||||||
-- Safe to re-run: every step is guarded by a column-existence check.
|
|
||||||
|
|
||||||
ALTER TABLE article RENAME COLUMN fatherarticle_id TO parentarticle_id;
|
|
||||||
ALTER TABLE article RENAME COLUMN fatherattributename_id TO parentattributename_id;
|
|
||||||
ALTER TABLE article RENAME COLUMN fathersort TO parentsort;
|
|
||||||
ALTER TABLE article RENAME COLUMN is_father TO is_parent;
|
|
||||||
|
|
||||||
-- Verify
|
|
||||||
PRAGMA table_info(article);
|
|
||||||
@@ -1,73 +1,46 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import sharp from 'sharp';
|
import { optimizeImage } from 'wasm-image-optimization';
|
||||||
import { cropAndConvertToAvif, crawlAndProcessImage } from '../src/services/imageCrop.js';
|
import { cropAndConvertToAvif, crawlAndProcessImage } from '../src/services/imageCrop.js';
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
console.log('--- 1. Testing synthetic images ---');
|
console.log('--- 1. Testing synthetic images ---');
|
||||||
|
|
||||||
// Synthetic test 1: White border
|
// Synthetic test 1: White border
|
||||||
const whiteImg = await sharp({
|
const whiteSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">
|
||||||
create: { width: 300, height: 300, channels: 4, background: { r: 255, g: 255, b: 255, alpha: 1 } },
|
<rect width="300" height="300" fill="#ffffff"/>
|
||||||
})
|
<rect x="100" y="100" width="100" height="100" fill="purple"/>
|
||||||
.composite([
|
</svg>`;
|
||||||
{
|
const whiteImg = Buffer.from((await optimizeImage({ image: whiteSvg, format: 'png' })).data);
|
||||||
input: Buffer.from('<svg width="100" height="100"><rect width="100" height="100" fill="purple"/></svg>'),
|
|
||||||
top: 100,
|
|
||||||
left: 100,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
const res1 = await cropAndConvertToAvif(whiteImg);
|
const res1 = await cropAndConvertToAvif(whiteImg);
|
||||||
console.log(`White border: 300x300 -> ${res1.width}x${res1.height}, ${res1.size} bytes`);
|
console.log(`White border: 300x300 -> ${res1.width}x${res1.height}, ${res1.size} bytes`);
|
||||||
if (res1.width !== 100 || res1.height !== 100) {
|
if (res1.width !== 110 || res1.height !== 110) {
|
||||||
throw new Error(`Expected 100x100, got ${res1.width}x${res1.height}`);
|
throw new Error(`Expected 110x110, got ${res1.width}x${res1.height}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synthetic test 2: Transparent border
|
// Synthetic test 2: Transparent border
|
||||||
const transImg = await sharp({
|
const transSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">
|
||||||
create: { width: 300, height: 300, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
|
<rect x="90" y="110" width="120" height="80" fill="green"/>
|
||||||
})
|
</svg>`;
|
||||||
.composite([
|
const transImg = Buffer.from((await optimizeImage({ image: transSvg, format: 'png' })).data);
|
||||||
{
|
|
||||||
input: Buffer.from('<svg width="120" height="80"><rect width="120" height="80" fill="green"/></svg>'),
|
|
||||||
top: 110,
|
|
||||||
left: 90,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
const res2 = await cropAndConvertToAvif(transImg);
|
const res2 = await cropAndConvertToAvif(transImg);
|
||||||
console.log(`Transparent border: 300x300 -> ${res2.width}x${res2.height}, ${res2.size} bytes`);
|
console.log(`Transparent border: 300x300 -> ${res2.width}x${res2.height}, ${res2.size} bytes`);
|
||||||
if (res2.width !== 120 || res2.height !== 80) {
|
if (res2.width !== 130 || res2.height !== 90) {
|
||||||
throw new Error(`Expected 120x80, got ${res2.width}x${res2.height}`);
|
throw new Error(`Expected 130x90, got ${res2.width}x${res2.height}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synthetic test 3: Outer transparent, inner white border
|
// Synthetic test 3: Outer transparent, inner white border
|
||||||
const mixedImg = await sharp({
|
const mixedSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">
|
||||||
create: { width: 400, height: 400, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
|
<rect x="100" y="100" width="200" height="200" fill="white"/>
|
||||||
})
|
<rect x="150" y="170" width="100" height="60" fill="blue"/>
|
||||||
.composite([
|
</svg>`;
|
||||||
{
|
const mixedImg = Buffer.from((await optimizeImage({ image: mixedSvg, format: 'png' })).data);
|
||||||
input: Buffer.from('<svg width="200" height="200"><rect width="200" height="200" fill="white"/></svg>'),
|
|
||||||
top: 100,
|
|
||||||
left: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: Buffer.from('<svg width="100" height="60"><rect width="100" height="60" fill="blue"/></svg>'),
|
|
||||||
top: 170,
|
|
||||||
left: 150,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
const res3 = await cropAndConvertToAvif(mixedImg);
|
const res3 = await cropAndConvertToAvif(mixedImg);
|
||||||
console.log(`Mixed border: 400x400 -> ${res3.width}x${res3.height}, ${res3.size} bytes`);
|
console.log(`Mixed border: 400x400 -> ${res3.width}x${res3.height}, ${res3.size} bytes`);
|
||||||
if (res3.width !== 100 || res3.height !== 60) {
|
if (res3.width !== 110 || res3.height !== 70) {
|
||||||
throw new Error(`Expected 100x60, got ${res3.width}x${res3.height}`);
|
throw new Error(`Expected 110x70, got ${res3.width}x${res3.height}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\nAll synthetic tests PASSED!');
|
console.log('\nAll synthetic tests PASSED!');
|
||||||
|
|||||||
8
sea-config.json
Normal file
8
sea-config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"main": "src/sea-entry.cjs",
|
||||||
|
"output": "dist/dashback",
|
||||||
|
"assets": {
|
||||||
|
"app-payload.bin": "dist/app-payload.bin"
|
||||||
|
},
|
||||||
|
"disableExperimentalSEAWarning": true
|
||||||
|
}
|
||||||
520
src/api/agent/index.js
Normal file
520
src/api/agent/index.js
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
import * as db from '../../db/printeragent.js';
|
||||||
|
import * as agentprinterDb from '../../db/agentprinter.js';
|
||||||
|
import * as printerjobDb from '../../db/printerjob.js';
|
||||||
|
import { agentAuthMiddleware, extractAgentVersion } from '../middleware/agentAuth.js';
|
||||||
|
import { emit } from '../sse.js';
|
||||||
|
import { createLogger } from '../../util/logger.js';
|
||||||
|
import {
|
||||||
|
enrichAgentWithState,
|
||||||
|
getAgentState,
|
||||||
|
setAgentState,
|
||||||
|
} from './stateStore.js';
|
||||||
|
import {
|
||||||
|
updateAgentPrinters,
|
||||||
|
removeAgentPrinters,
|
||||||
|
getCompanyPrinters,
|
||||||
|
} from '../../services/companyPrinterService.js';
|
||||||
|
|
||||||
|
const log = createLogger('agent');
|
||||||
|
|
||||||
|
export const agentRouter = express.Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to extract pairing parameters from body or query.
|
||||||
|
*/
|
||||||
|
function extractPairParams(req) {
|
||||||
|
const rawPin = req.body?.pairingpin ||
|
||||||
|
req.body?.authCode ||
|
||||||
|
req.body?.pin ||
|
||||||
|
req.body?.code ||
|
||||||
|
req.query?.pairingpin ||
|
||||||
|
req.query?.authCode ||
|
||||||
|
req.query?.pin ||
|
||||||
|
req.query?.code ||
|
||||||
|
'';
|
||||||
|
const pairingpin = String(rawPin).trim();
|
||||||
|
|
||||||
|
const rawSerial = req.body?.clientserial ||
|
||||||
|
req.body?.serial ||
|
||||||
|
req.body?.name ||
|
||||||
|
req.query?.clientserial ||
|
||||||
|
req.query?.serial ||
|
||||||
|
req.query?.name ||
|
||||||
|
null;
|
||||||
|
const clientserial = rawSerial ? String(rawSerial).trim() : null;
|
||||||
|
|
||||||
|
const rawModel = req.body?.clientmodel ||
|
||||||
|
req.body?.model ||
|
||||||
|
req.query?.clientmodel ||
|
||||||
|
req.query?.model ||
|
||||||
|
null;
|
||||||
|
const clientmodel = rawModel ? String(rawModel).trim() : null;
|
||||||
|
|
||||||
|
const agentversion = extractAgentVersion(req);
|
||||||
|
|
||||||
|
return { pairingpin, clientserial, clientmodel, agentversion };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePair(req, res) {
|
||||||
|
const { pairingpin, clientserial, clientmodel, agentversion } = extractPairParams(req);
|
||||||
|
|
||||||
|
if (!pairingpin) {
|
||||||
|
return res.status(400).json({ error: 'pairingpin is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const agent = await db.findByPairingpin(pairingpin);
|
||||||
|
if (!agent) {
|
||||||
|
return res.status(400).json({ error: 'Invalid pairing PIN' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = crypto.randomBytes(32).toString('hex');
|
||||||
|
const pairedAt = new Date().toISOString();
|
||||||
|
|
||||||
|
const paired = await db.pair(agent.id, {
|
||||||
|
token,
|
||||||
|
clientserial,
|
||||||
|
clientmodel,
|
||||||
|
agentversion,
|
||||||
|
pairedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (paired.companyId) {
|
||||||
|
emit('printeragent', { companyId: paired.companyId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
id: paired.id,
|
||||||
|
name: paired.name,
|
||||||
|
companyId: paired.companyId,
|
||||||
|
token,
|
||||||
|
pairedAt: paired.pairedAt,
|
||||||
|
clientserial: paired.clientserial,
|
||||||
|
clientmodel: paired.clientmodel,
|
||||||
|
agentversion: paired.agentversion,
|
||||||
|
sort: paired.sort,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Agent pairing failed:', err);
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /agent/pair:
|
||||||
|
* post:
|
||||||
|
* tags: [Agent]
|
||||||
|
* summary: Pair printer agent
|
||||||
|
* description: |
|
||||||
|
* 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.
|
||||||
|
* security: []
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required: [pairingpin]
|
||||||
|
* properties:
|
||||||
|
* pairingpin:
|
||||||
|
* type: string
|
||||||
|
* example: "123456"
|
||||||
|
* clientserial:
|
||||||
|
* type: string
|
||||||
|
* example: "SN-987654321"
|
||||||
|
* clientmodel:
|
||||||
|
* type: string
|
||||||
|
* example: "Epson TM-T88VI"
|
||||||
|
* agentversion:
|
||||||
|
* type: string
|
||||||
|
* example: "1.0.0"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Pairing successful
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* name:
|
||||||
|
* type: string
|
||||||
|
* companyId:
|
||||||
|
* type: integer
|
||||||
|
* token:
|
||||||
|
* type: string
|
||||||
|
* pairedAt:
|
||||||
|
* type: string
|
||||||
|
* clientserial:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* clientmodel:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* agentversion:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* sort:
|
||||||
|
* type: integer
|
||||||
|
* 400:
|
||||||
|
* description: Missing or invalid pairing PIN
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* get:
|
||||||
|
* tags: [Agent]
|
||||||
|
* summary: Pair printer agent via query parameters
|
||||||
|
* description: Alternative GET endpoint for devices that pair via query parameters.
|
||||||
|
* security: []
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: authCode
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Pairing PIN (or pairingpin)
|
||||||
|
* - in: query
|
||||||
|
* name: pairingpin
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* - in: query
|
||||||
|
* name: serial
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* - in: query
|
||||||
|
* name: model
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* - in: query
|
||||||
|
* name: version
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Pairing successful
|
||||||
|
* 400:
|
||||||
|
* description: Missing or invalid pairing PIN
|
||||||
|
*/
|
||||||
|
agentRouter.post('/pair', handlePair);
|
||||||
|
agentRouter.get('/pair', handlePair);
|
||||||
|
|
||||||
|
const activeAgentEvents = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies a connected agent of a new or cancelled printer job.
|
||||||
|
* @param {number} agentId
|
||||||
|
* @param {'created'|'cancelled'} action
|
||||||
|
* @param {object} job
|
||||||
|
* @returns {boolean} True if delivered to at least one active agent connection.
|
||||||
|
*/
|
||||||
|
export function notifyAgentJob(agentId, action, job) {
|
||||||
|
const aId = Number(agentId);
|
||||||
|
const conns = activeAgentEvents.get(aId);
|
||||||
|
const count = conns ? conns.size : 0;
|
||||||
|
log.info(`notifyAgentJob: agentId=${aId} action=${action} jobId=${job?.id} activeConnections=${count}`);
|
||||||
|
if (!conns || conns.size === 0) {
|
||||||
|
log.warn(`notifyAgentJob: No active SSE connection for agent ${aId} (jobId=${job?.id}); agent must poll/reconnect`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
action,
|
||||||
|
jobId: job.id,
|
||||||
|
agentprinterId: job.agentprinterId,
|
||||||
|
type: job.type,
|
||||||
|
state: job.state,
|
||||||
|
createdAt: job.createdAt,
|
||||||
|
});
|
||||||
|
for (const clientRes of conns) {
|
||||||
|
if (!clientRes.writableEnded) {
|
||||||
|
clientRes.write(`event: job\ndata: ${payload}\n\n`);
|
||||||
|
clientRes.flush?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /agent/events:
|
||||||
|
* get:
|
||||||
|
* tags: [Agent]
|
||||||
|
* summary: Printer agent SSE stream
|
||||||
|
* description: |
|
||||||
|
* 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`.
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - apiKeyAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: token
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Agent auth token if not provided via Authorization header
|
||||||
|
* - in: query
|
||||||
|
* name: agentversion
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Current agent version
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Event stream established
|
||||||
|
* content:
|
||||||
|
* text/event-stream:
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
*/
|
||||||
|
agentRouter.get('/events', agentAuthMiddleware, async (req, res) => {
|
||||||
|
const version = extractAgentVersion(req);
|
||||||
|
if (version) {
|
||||||
|
db.touchLastseen(req.agent.id, version).catch(() => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/event-stream');
|
||||||
|
res.setHeader('Cache-Control', 'no-cache');
|
||||||
|
res.setHeader('Connection', 'keep-alive');
|
||||||
|
res.setHeader('X-Accel-Buffering', 'no');
|
||||||
|
res.flushHeaders?.();
|
||||||
|
|
||||||
|
const agentVersion = version || req.agent?.agentversion;
|
||||||
|
const versionSuffix = agentVersion ? ` v${agentVersion}` : '';
|
||||||
|
const agentName = req.agent?.name || 'unnamed';
|
||||||
|
log.info(`Agent connected: id=${req.agent.id} ("${agentName}")${versionSuffix}`);
|
||||||
|
|
||||||
|
if (!getAgentState(req.agent.id)) {
|
||||||
|
setAgentState(req.agent.id, 'online');
|
||||||
|
}
|
||||||
|
|
||||||
|
const aId = Number(req.agent.id);
|
||||||
|
if (!activeAgentEvents.has(aId)) {
|
||||||
|
activeAgentEvents.set(aId, new Set());
|
||||||
|
}
|
||||||
|
activeAgentEvents.get(aId).add(res);
|
||||||
|
|
||||||
|
res.write(`event: connected\ndata: ${JSON.stringify({ agentId: req.agent.id, name: req.agent.name })}\n\n`);
|
||||||
|
|
||||||
|
const keepAlive = setInterval(() => {
|
||||||
|
if (res.writableEnded) return;
|
||||||
|
res.write(': heartbeat\n\n');
|
||||||
|
db.touchLastseen(req.agent.id).catch(() => { });
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
let closed = false;
|
||||||
|
const cleanup = () => {
|
||||||
|
if (closed) return;
|
||||||
|
closed = true;
|
||||||
|
clearInterval(keepAlive);
|
||||||
|
|
||||||
|
const conns = activeAgentEvents.get(aId);
|
||||||
|
if (conns) {
|
||||||
|
conns.delete(res);
|
||||||
|
if (conns.size === 0) activeAgentEvents.delete(aId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Agent disconnected: id=${req.agent.id} ("${agentName}")`);
|
||||||
|
setAgentState(req.agent.id, 'offline');
|
||||||
|
if (req.agent?.companyId) {
|
||||||
|
removeAgentPrinters(req.agent.companyId, req.agent.id);
|
||||||
|
emit('printeragent', { companyId: req.agent.companyId });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
req.on('close', cleanup);
|
||||||
|
res.on('close', cleanup);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /agent/state:
|
||||||
|
* post:
|
||||||
|
* tags: [Agent]
|
||||||
|
* summary: Post agent state
|
||||||
|
* description: |
|
||||||
|
* Authorized endpoint for the printer agent to report its operational status/telemetry.
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* - apiKeyAuth: []
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* state:
|
||||||
|
* description: Status text or status object
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: State recorded
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* success:
|
||||||
|
* type: boolean
|
||||||
|
* agent:
|
||||||
|
* $ref: '#/components/schemas/Printeragent'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
*/
|
||||||
|
async function handleState(req, res) {
|
||||||
|
try {
|
||||||
|
const rawState = req.body?.state;
|
||||||
|
setAgentState(req.agent.id, rawState);
|
||||||
|
|
||||||
|
const version = extractAgentVersion(req);
|
||||||
|
if (version) {
|
||||||
|
await db.touchLastseen(req.agent.id, version);
|
||||||
|
} else {
|
||||||
|
await db.touchLastseen(req.agent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawPrinters = Array.isArray(rawState?.printers) ? rawState.printers : null;
|
||||||
|
|
||||||
|
let anyAgentPrinterCreated = false;
|
||||||
|
if (rawPrinters && rawPrinters.length > 0) {
|
||||||
|
for (const p of rawPrinters) {
|
||||||
|
try {
|
||||||
|
const result = await agentprinterDb.findOrCreate(req.agent.id, {
|
||||||
|
type: p.type || 'zpl',
|
||||||
|
model: p.model || null,
|
||||||
|
serial: p.serial || null,
|
||||||
|
});
|
||||||
|
if (result?.created) {
|
||||||
|
anyAgentPrinterCreated = true;
|
||||||
|
}
|
||||||
|
if (result?.agentprinter) {
|
||||||
|
p.agentprinterId = result.agentprinter.id;
|
||||||
|
p.enabled = result.agentprinter.enabled;
|
||||||
|
p.sort = result.agentprinter.sort;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log.warn(`Failed to record agentprinter for agent ${req.agent.id}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawState?.job?.id && rawState?.job?.state) {
|
||||||
|
try {
|
||||||
|
await printerjobDb.updateState(
|
||||||
|
{ agentId: req.agent.id },
|
||||||
|
rawState.job.id,
|
||||||
|
rawState.job.state
|
||||||
|
);
|
||||||
|
} catch (jobErr) {
|
||||||
|
log.debug(`Agent reported job state update for ${rawState.job.id}:`, jobErr?.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawPrinters && req.agent.companyId) {
|
||||||
|
updateAgentPrinters(req.agent.companyId, req.agent.id, req.agent.name, rawPrinters);
|
||||||
|
} else if (anyAgentPrinterCreated && req.agent.companyId) {
|
||||||
|
emit('agentprinter', { companyId: req.agent.companyId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.agent.companyId) {
|
||||||
|
emit('printeragent', { companyId: req.agent.companyId });
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = enrichAgentWithState({
|
||||||
|
...req.agent,
|
||||||
|
agentversion: version || req.agent.agentversion,
|
||||||
|
lastseenAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
agent,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
log.error('Agent post state failed:', err);
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
agentRouter.post('/state', agentAuthMiddleware, handleState);
|
||||||
|
agentRouter.put('/state', agentAuthMiddleware, handleState);
|
||||||
|
agentRouter.get('/state', agentAuthMiddleware, (req, res) => {
|
||||||
|
const memoryState = getAgentState(req.agent.id);
|
||||||
|
const companyPrinters = req.agent.companyId ? getCompanyPrinters(req.agent.companyId) : [];
|
||||||
|
const agentPrinters = companyPrinters.filter((p) => p.agentId === req.agent.id);
|
||||||
|
res.status(200).json({
|
||||||
|
state: memoryState ?? null,
|
||||||
|
printers: agentPrinters,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// #region Agent Jobs
|
||||||
|
|
||||||
|
agentRouter.get('/jobs', agentAuthMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const jobs = await printerjobDb.list({ agentId: req.agent.id }, req.query);
|
||||||
|
res.status(200).json({ jobs });
|
||||||
|
} catch (err) {
|
||||||
|
log.error('Agent get jobs failed:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agentRouter.get('/jobs/:id', agentAuthMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const job = await printerjobDb.getById({ agentId: req.agent.id }, req.params.id);
|
||||||
|
if (!job) {
|
||||||
|
return res.status(404).json({ error: 'Printerjob not found' });
|
||||||
|
}
|
||||||
|
res.status(200).json({ job });
|
||||||
|
} catch (err) {
|
||||||
|
log.error('Agent get job failed:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agentRouter.post('/jobs/:id/processing', agentAuthMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const job = await printerjobDb.updateState({ agentId: req.agent.id }, req.params.id, 'processing');
|
||||||
|
res.status(200).json({ success: true, job });
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === 'Printerjob not found') return res.status(404).json({ error: err.message });
|
||||||
|
log.error('Agent set job processing failed:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agentRouter.post('/jobs/:id/completed', agentAuthMiddleware, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const job = await printerjobDb.updateState({ agentId: req.agent.id }, req.params.id, 'completed');
|
||||||
|
res.status(200).json({ success: true, job });
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === 'Printerjob not found') return res.status(404).json({ error: err.message });
|
||||||
|
log.error('Agent set job completed failed:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agentRouter.patch('/jobs/:id', agentAuthMiddleware, async (req, res) => {
|
||||||
|
const state = req.body?.state;
|
||||||
|
if (!state) {
|
||||||
|
return res.status(400).json({ error: 'state is required' });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const job = await printerjobDb.updateState({ agentId: req.agent.id }, req.params.id, state);
|
||||||
|
res.status(200).json({ success: true, job });
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === 'Printerjob not found') return res.status(404).json({ error: err.message });
|
||||||
|
if (err.message?.startsWith('Invalid printerjob state')) return res.status(400).json({ error: err.message });
|
||||||
|
log.error('Agent patch job failed:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
|
||||||
75
src/api/agent/stateStore.js
Normal file
75
src/api/agent/stateStore.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* In-memory state store for connected printer agents.
|
||||||
|
* Keeps transient agent telemetry out of the database.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Map<number, any>} */
|
||||||
|
const agentStates = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current telemetry/state for an agent.
|
||||||
|
* @param {number} agentId
|
||||||
|
* @param {any} state
|
||||||
|
*/
|
||||||
|
export function setAgentState(agentId, state) {
|
||||||
|
if (agentId == null) return;
|
||||||
|
agentStates.set(Number(agentId), state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the raw in-memory state for an agent.
|
||||||
|
* @param {number} agentId
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
export function getAgentState(agentId) {
|
||||||
|
if (agentId == null) return undefined;
|
||||||
|
return agentStates.get(Number(agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a state value into a concise clientstate string suitable for display (e.g. 'online', 'offline').
|
||||||
|
* @param {any} state
|
||||||
|
* @returns {string|null}
|
||||||
|
*/
|
||||||
|
export function formatClientState(state) {
|
||||||
|
if (state == null) return null;
|
||||||
|
if (typeof state === 'string') return state;
|
||||||
|
if (typeof state === 'object') {
|
||||||
|
if (typeof state.status === 'string' && state.status.trim()) {
|
||||||
|
return state.status.trim();
|
||||||
|
}
|
||||||
|
if (typeof state.state === 'string' && state.state.trim()) {
|
||||||
|
return state.state.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSON.stringify(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes in-memory state for an agent (e.g. when deleted).
|
||||||
|
* @param {number} agentId
|
||||||
|
*/
|
||||||
|
export function removeAgentState(agentId) {
|
||||||
|
if (agentId == null) return;
|
||||||
|
agentStates.delete(Number(agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches a printeragent object with the in-memory state.
|
||||||
|
* @template {object} T
|
||||||
|
* @param {T} agent
|
||||||
|
* @returns {T}
|
||||||
|
*/
|
||||||
|
export function enrichAgentWithState(agent) {
|
||||||
|
if (!agent || typeof agent !== 'object') return agent;
|
||||||
|
const memoryState = getAgentState(agent.id);
|
||||||
|
if (memoryState !== undefined) {
|
||||||
|
return {
|
||||||
|
...agent,
|
||||||
|
state: memoryState,
|
||||||
|
clientstate: formatClientState(memoryState),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
import env from '../../config/env.js';
|
import env from '../../config/env.js';
|
||||||
|
import { getSchemaVersion } from '../../db/store.js';
|
||||||
|
|
||||||
export function respondWithAuthenticatedUser(res, status, body) {
|
export async function respondWithAuthenticatedUser(res, status, body) {
|
||||||
return res.status(status).json({ ...body, googleClientId: env.googleClientId });
|
let schemaVersion = null;
|
||||||
|
try {
|
||||||
|
schemaVersion = await getSchemaVersion();
|
||||||
|
} catch {
|
||||||
|
// ignore if database not accessible
|
||||||
|
}
|
||||||
|
return res.status(status).json({
|
||||||
|
...body,
|
||||||
|
googleClientId: env.googleClientId,
|
||||||
|
schemaVersion,
|
||||||
|
vhost: env.vhost,
|
||||||
|
vhostSettings: env.vhostSettings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import cookieParser from 'cookie-parser';
|
|||||||
import { loggerMiddleware } from './middleware/logger.js';
|
import { loggerMiddleware } from './middleware/logger.js';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { authMiddleware } from './middleware/auth.js';
|
import { authMiddleware } from './middleware/auth.js';
|
||||||
import { ensureConnection } from '../db/store.js';
|
import { ensureConnection, getSchemaVersion, closeStore } from '../db/store.js';
|
||||||
import { closeAll as closeAllSse } from './sse.js';
|
import { closeAll as closeAllSse } from './sse.js';
|
||||||
import { closeAllManagers } from '../services/conversationManager.js';
|
import { closeAllManagers } from '../services/conversationManager.js';
|
||||||
import { shutdownImageCropWorkers } from '../services/imageCrop.js';
|
import { shutdownImageCropWorkers } from '../services/imageCrop.js';
|
||||||
|
import { shutdownImageHashWorkers } from '../services/companyImport/imageHasher.js';
|
||||||
import { shutdownJavascriptSandboxWorkers } from '../services/javascriptSandbox.js';
|
import { shutdownJavascriptSandboxWorkers } from '../services/javascriptSandbox.js';
|
||||||
import { getOrCreateCert } from '../util/cert.js';
|
import { getOrCreateCert } from '../util/cert.js';
|
||||||
import { createLogger } from '../util/logger.js';
|
import { createLogger } from '../util/logger.js';
|
||||||
import v1Router from '../v1/index.js';
|
import v1Router from '../v1/index.js';
|
||||||
|
import { agentRouter } from './agent/index.js';
|
||||||
import { startBackendMonitor, stopBackendMonitor, isDevMode } from '../backendmonitor/server.js';
|
import { startBackendMonitor, stopBackendMonitor, isDevMode } from '../backendmonitor/server.js';
|
||||||
import { startBackupService, stopBackupService } from '../services/backupService.js';
|
import { startBackupService, stopBackupService } from '../services/backupService.js';
|
||||||
import { startHealthMonitorService, stopHealthMonitorService } from '../services/healthMonitorService.js';
|
import { startHealthMonitorService, stopHealthMonitorService } from '../services/healthMonitorService.js';
|
||||||
@@ -28,6 +30,7 @@ const __dirname = path.dirname(__filename);
|
|||||||
|
|
||||||
async function registerRoutes(app, basePath = '') {
|
async function registerRoutes(app, basePath = '') {
|
||||||
const routesDir = path.join(__dirname, 'routes');
|
const routesDir = path.join(__dirname, 'routes');
|
||||||
|
let loadedCount = 0;
|
||||||
|
|
||||||
async function registerRouteFilesRecursively(dir) {
|
async function registerRouteFilesRecursively(dir) {
|
||||||
for (const file of fs.readdirSync(dir)) {
|
for (const file of fs.readdirSync(dir)) {
|
||||||
@@ -40,12 +43,14 @@ async function registerRoutes(app, basePath = '') {
|
|||||||
const routeModule = await import(pathToFileURL(fullPath).href);
|
const routeModule = await import(pathToFileURL(fullPath).href);
|
||||||
if (routeModule.register) {
|
if (routeModule.register) {
|
||||||
routeModule.register(app, basePath);
|
routeModule.register(app, basePath);
|
||||||
|
loadedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await registerRouteFilesRecursively(routesDir);
|
await registerRouteFilesRecursively(routesDir);
|
||||||
|
return loadedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
@@ -96,6 +101,20 @@ function corsMiddleware(req, res, next) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.path.startsWith('/api/agent')) {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
||||||
|
const reqHeaders = req.headers['access-control-request-headers'];
|
||||||
|
res.setHeader(
|
||||||
|
'Access-Control-Allow-Headers',
|
||||||
|
reqHeaders || 'Content-Type, Authorization, x-agent-token, agentversion'
|
||||||
|
);
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
return res.sendStatus(204);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
if (req.path.startsWith('/v1')) {
|
if (req.path.startsWith('/v1')) {
|
||||||
if (origin) {
|
if (origin) {
|
||||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||||
@@ -140,7 +159,7 @@ export async function buildApiApp() {
|
|||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
// Do not attach no-store to SSE or public endpoints
|
// Do not attach no-store to SSE or public endpoints
|
||||||
if (req.path !== '/api/events' && !req.path.startsWith('/api/public') && !req.path.startsWith('/public')) {
|
if (req.path !== '/api/events' && !req.path.endsWith('/events') && !req.path.startsWith('/api/public') && !req.path.startsWith('/public')) {
|
||||||
res.setHeader('Cache-Control', 'no-store');
|
res.setHeader('Cache-Control', 'no-store');
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
@@ -159,8 +178,8 @@ export async function buildApiApp() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await registerRoutes(app, '/api');
|
app.routeCount = await registerRoutes(app, '/api');
|
||||||
app.use('/v1', v1Router);
|
app.use('/api/agent', agentRouter);
|
||||||
app.use('/api/v1', v1Router);
|
app.use('/api/v1', v1Router);
|
||||||
|
|
||||||
app.use((_req, res) => {
|
app.use((_req, res) => {
|
||||||
@@ -182,7 +201,7 @@ export async function buildApiApp() {
|
|||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenOnPort(instance, port, protocol) {
|
function listenOnPort(instance, port, protocol, host = env.host) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
instance.once('listening', () => {
|
instance.once('listening', () => {
|
||||||
resolve();
|
resolve();
|
||||||
@@ -197,14 +216,14 @@ function listenOnPort(instance, port, protocol) {
|
|||||||
} catch {
|
} catch {
|
||||||
// lsof unavailable or no match
|
// lsof unavailable or no match
|
||||||
}
|
}
|
||||||
apiLog.error(`Port ${port} (${protocol}) is already in use${detail}. Check with: lsof -i :${port}`);
|
apiLog.error(`Port ${port} (${protocol}) on ${host} is already in use${detail}. Check with: lsof -i :${port}`);
|
||||||
} else {
|
} else {
|
||||||
apiLog.error(`${protocol} server error:`, err.message);
|
apiLog.error(`${protocol} server error:`, err.message);
|
||||||
}
|
}
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.listen(port);
|
instance.listen(port, host);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +244,7 @@ export async function startApi() {
|
|||||||
const httpInstance = http.createServer(app);
|
const httpInstance = http.createServer(app);
|
||||||
await listenOnPort(httpInstance, env.port, 'HTTP');
|
await listenOnPort(httpInstance, env.port, 'HTTP');
|
||||||
server = httpInstance;
|
server = httpInstance;
|
||||||
apiLog.info(`API listening on port ${env.port} (HTTP)`);
|
apiLog.info(`API listening on ${env.host}:${env.port} (HTTP)`);
|
||||||
|
|
||||||
if (env.httpsPort) {
|
if (env.httpsPort) {
|
||||||
try {
|
try {
|
||||||
@@ -233,7 +252,7 @@ export async function startApi() {
|
|||||||
const httpsInstance = https.createServer(tls, app);
|
const httpsInstance = https.createServer(tls, app);
|
||||||
await listenOnPort(httpsInstance, env.httpsPort, 'HTTPS');
|
await listenOnPort(httpsInstance, env.httpsPort, 'HTTPS');
|
||||||
httpsServer = httpsInstance;
|
httpsServer = httpsInstance;
|
||||||
apiLog.info(`API listening on port ${env.httpsPort} (HTTPS self-signed)`);
|
apiLog.info(`API listening on ${env.host}:${env.httpsPort} (HTTPS self-signed)`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
apiLog.error('Failed to start HTTPS server:', err.message);
|
apiLog.error('Failed to start HTTPS server:', err.message);
|
||||||
}
|
}
|
||||||
@@ -279,6 +298,7 @@ export async function stopApi() {
|
|||||||
// Pooled image-crop workers are live threads and must be terminated too, or the
|
// Pooled image-crop workers are live threads and must be terminated too, or the
|
||||||
// process stays alive after the last request has been served.
|
// process stays alive after the last request has been served.
|
||||||
await shutdownImageCropWorkers();
|
await shutdownImageCropWorkers();
|
||||||
|
await shutdownImageHashWorkers();
|
||||||
// Same for the pooled sandbox threads that run `javascript_execute` / applets.
|
// Same for the pooled sandbox threads that run `javascript_execute` / applets.
|
||||||
await shutdownJavascriptSandboxWorkers();
|
await shutdownJavascriptSandboxWorkers();
|
||||||
await stopBackupService();
|
await stopBackupService();
|
||||||
@@ -302,3 +322,64 @@ export async function stopApi() {
|
|||||||
}
|
}
|
||||||
await Promise.all(closers);
|
await Promise.all(closers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function runSelfTest() {
|
||||||
|
const start = Date.now();
|
||||||
|
apiLog.info('Starting dashback self-test (loading modules without binding ports)...');
|
||||||
|
|
||||||
|
// 1. Check configuration
|
||||||
|
apiLog.info(`Config: host=${env.host} port=${env.port} dbProvider=${env.dbProvider}`);
|
||||||
|
|
||||||
|
// 2. Test database & store initialization
|
||||||
|
await ensureConnection();
|
||||||
|
const schemaVer = await getSchemaVersion();
|
||||||
|
apiLog.info(`Database & store: OK (provider: ${env.dbProvider}, schema v${schemaVer})`);
|
||||||
|
|
||||||
|
// 3. Build API app and register all route modules
|
||||||
|
const app = await buildApiApp();
|
||||||
|
apiLog.info(`Routes & middleware: OK (${app.routeCount || 'all'} route modules loaded)`);
|
||||||
|
|
||||||
|
// 4. Test key native/WASM dependencies
|
||||||
|
try {
|
||||||
|
const { optimizeImage } = await import('wasm-image-optimization');
|
||||||
|
const tinyPng = Buffer.from(
|
||||||
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
||||||
|
'base64'
|
||||||
|
);
|
||||||
|
await optimizeImage({ image: tinyPng, format: 'png' });
|
||||||
|
apiLog.info('WASM image pipeline: OK');
|
||||||
|
} catch (err) {
|
||||||
|
apiLog.warn(`WASM image pipeline warning: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Test WASM label rasterizer
|
||||||
|
try {
|
||||||
|
const { renderThumb } = await import('../services/labelThumb.js');
|
||||||
|
const thumb = await renderThumb({
|
||||||
|
width: 300,
|
||||||
|
height: 150,
|
||||||
|
objects: [
|
||||||
|
{ type: 'textbox', text: 'SELFTEST', x: 10, y: 10, font: 'outfit' },
|
||||||
|
{ type: 'barcode', format: 'EAN13', code: '4012345678901', x: 10, y: 40, scale: 2 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!thumb || thumb.length === 0) {
|
||||||
|
throw new Error('Empty thumbnail rendered');
|
||||||
|
}
|
||||||
|
apiLog.info('WASM label rasterizer: OK');
|
||||||
|
} catch (err) {
|
||||||
|
apiLog.error(`WASM label rasterizer FAILED: ${err.message}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Clean up store connection
|
||||||
|
try {
|
||||||
|
await closeStore();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
apiLog.info(`Self-test PASSED: All modules and routes loaded successfully in ${duration}ms (no ports bound).`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user