Compare commits
358 Commits
8c19388901
...
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 | ||
|
|
7fcaca318a | ||
|
|
76235b0e1f | ||
|
|
a6cb52e415 | ||
|
|
36a135011f | ||
|
|
a27f507138 | ||
|
|
e83be2ca21 | ||
|
|
1a0023b775 | ||
|
|
1aa1582682 | ||
|
|
be834412e7 | ||
|
|
5be5644329 | ||
|
|
ada4d93f65 | ||
|
|
ee0eb2820e | ||
|
|
e4e4d2dcf0 | ||
|
|
63f4c3a0e4 | ||
|
|
b7058df5d2 | ||
|
|
5a5981dc07 | ||
|
|
fde381937c | ||
|
|
de3e74c928 | ||
|
|
a04094a7be | ||
|
|
3f540de768 | ||
|
|
3450552f7c | ||
|
|
6fbdeaa070 | ||
|
|
1bf99deeb9 | ||
|
|
e14a8ee8f0 | ||
|
|
c958ecf604 | ||
|
|
f571328e0c | ||
|
|
a717a05e8d | ||
|
|
c06a5e3413 | ||
|
|
5e4cb5a3f8 | ||
|
|
0ec993a0b6 | ||
|
|
69a9781cb6 | ||
|
|
7445a573ab | ||
|
|
9c50d9efc3 | ||
|
|
36d62138d4 | ||
|
|
0463003681 | ||
|
|
d0a2ede749 | ||
|
|
badce6819c | ||
|
|
832cfaca41 | ||
|
|
a01b42b24b | ||
|
|
04943fb7a6 | ||
|
|
e085e064ee | ||
|
|
df846f4f06 | ||
|
|
4bd977ed12 | ||
|
|
1778815a1e | ||
|
|
856692b66c | ||
|
|
5e9b168e0d | ||
|
|
bee21d2c20 | ||
|
|
7197a6ddb7 | ||
|
|
988b4848cf | ||
|
|
c61df92d63 | ||
|
|
fd21cb7319 | ||
|
|
da1254b0b1 | ||
|
|
8e841d2c23 | ||
|
|
0fe672a054 | ||
|
|
d7f7d669f9 | ||
|
|
188cd71f63 | ||
|
|
de6c4987b9 | ||
|
|
d05ba9276f | ||
|
|
75925ab35c | ||
|
|
52f49fb651 | ||
|
|
2f0c4aced1 | ||
|
|
99ac40e6fa | ||
|
|
7f28a05ed3 | ||
|
|
3b875c9f9a | ||
|
|
f506dd35be | ||
|
|
5ed634990e | ||
|
|
bed19f8b10 | ||
|
|
e083d66a75 | ||
|
|
a560a78f4d | ||
|
|
144bf0ca43 | ||
|
|
50f41bca8c | ||
|
|
2165afcc02 | ||
|
|
084b1b285e | ||
|
|
9a4e2e30df | ||
|
|
0039b80d31 | ||
|
|
78ad8f12e4 | ||
|
|
29a07d132c | ||
|
|
8af63d6bf8 | ||
|
|
508e1b6d7a | ||
|
|
e462a3d250 | ||
|
|
529b142341 | ||
|
|
f331625d63 | ||
|
|
8c55f899b5 | ||
|
|
bdd7ebf5b8 | ||
|
|
507dffa567 | ||
|
|
0ea7da4f3a | ||
|
|
d880273629 | ||
|
|
9c54a8a1d3 | ||
|
|
04e10cffdb | ||
|
|
90b782e500 | ||
|
|
37c58648db | ||
|
|
5fb8df6f6a | ||
|
|
80450e8748 | ||
|
|
e230c4cf12 | ||
|
|
6c821d381e | ||
|
|
1991c0b529 | ||
|
|
8ccdad18f4 | ||
|
|
f9ed4fe75d | ||
|
|
74b24686bc | ||
|
|
11f6a8fc0f | ||
|
|
b7b5d554d8 | ||
|
|
c3d4fa5cd9 | ||
|
|
2de2bc0d90 | ||
|
|
8acca998bc | ||
|
|
97540fb745 | ||
|
|
2f92199d7f | ||
|
|
3d009ba216 | ||
|
|
f743ace6af | ||
|
|
cc77bf4377 | ||
|
|
77367b7716 | ||
|
|
3748020b03 | ||
|
|
c35126bb11 | ||
|
|
713c31a21e | ||
|
|
358a023b10 | ||
|
|
06dc4629da | ||
|
|
88ecf5bf5c | ||
|
|
280ed1b2b8 | ||
|
|
1f09e620e3 | ||
|
|
4429294d8b | ||
|
|
a2e446af47 | ||
|
|
15a17574c3 | ||
|
|
7fd8d5d704 | ||
|
|
fe7be79b90 | ||
|
|
1eb5cb4e59 | ||
|
|
9443f20820 | ||
|
|
e72467da50 | ||
|
|
1a51944e59 | ||
|
|
663589a450 | ||
|
|
0621416aa8 | ||
|
|
1149ffddcb | ||
|
|
8262e35e53 | ||
|
|
b5e7643e12 | ||
|
|
47c61c9f2a | ||
|
|
29057e8d7d | ||
|
|
6a8716a6ef | ||
|
|
f110173e57 | ||
|
|
188b8efe09 | ||
|
|
7fb7435779 | ||
|
|
67678038c0 | ||
|
|
eed10ea38d | ||
|
|
c012b82af3 | ||
|
|
7f8661be3e | ||
|
|
d4e2b05cb0 | ||
|
|
ac10840fb6 | ||
|
|
cd6879f3b7 | ||
|
|
daf2d0525f | ||
|
|
848ca0aff5 | ||
|
|
de7afbcbe9 | ||
|
|
728b3ec172 | ||
|
|
011d84c3c1 | ||
|
|
969c064f3a | ||
|
|
0493b218d7 | ||
|
|
c0a743654f | ||
|
|
f4ae04af15 | ||
|
|
bc323f2891 | ||
|
|
8111c3f377 | ||
|
|
c2d3cdb088 | ||
|
|
e2e7f8d566 | ||
|
|
79a1b72292 | ||
|
|
dd8cbf2851 | ||
|
|
717f4754d4 | ||
|
|
c543c76a21 | ||
|
|
73461c6e6f | ||
|
|
cd396363bd | ||
|
|
9ba86c4216 | ||
|
|
446f465a48 | ||
|
|
1a236b568a | ||
|
|
85635cc0a5 | ||
|
|
7d537aaa6b | ||
|
|
a29ad50b74 | ||
|
|
857963b1c3 | ||
|
|
1743cef11c | ||
|
|
cbd7920eb6 | ||
|
|
2e8c36fa01 | ||
|
|
07f0d5ba80 | ||
|
|
cab06d96e8 | ||
|
|
0bd12de8d8 | ||
|
|
665b87c350 | ||
|
|
965bd34ade | ||
|
|
5eb0f28300 | ||
|
|
b99e767c57 | ||
|
|
1d1017e44b | ||
|
|
1be561376e | ||
|
|
fe5990af9f | ||
|
|
75ab6ce171 | ||
|
|
d5e58e76e1 | ||
|
|
0369b4e819 | ||
|
|
159f02992b | ||
|
|
87f9a8d97e | ||
|
|
609ca55af2 | ||
|
|
18699eb40b | ||
|
|
af0ef29e22 | ||
|
|
f55990c25e | ||
|
|
5f9231f8fd | ||
|
|
296fceedc0 | ||
|
|
4a08e7f20b | ||
|
|
25266cfcec | ||
|
|
9831bea110 | ||
|
|
ab40a38a75 | ||
|
|
9feca54a0b | ||
|
|
ec060f5c62 | ||
|
|
3ac713732c | ||
|
|
45369d11da | ||
|
|
6777f423d8 | ||
|
|
d56b3e5c23 | ||
|
|
ab9ce9b600 | ||
|
|
e00533f9fa | ||
|
|
a3926d3547 | ||
|
|
a8e51724f8 | ||
|
|
c5cb76a66c | ||
|
|
43fa8ea105 | ||
|
|
e5ff4ef307 | ||
|
|
9a7f345d8b | ||
|
|
1786879c4e | ||
|
|
a162feefbb | ||
|
|
3f84ff2be8 | ||
|
|
20886e7c76 | ||
|
|
ea51f30a8d | ||
|
|
6b58947064 | ||
|
|
c28b30d92a | ||
|
|
7383e13894 | ||
|
|
8a7500cb5a | ||
|
|
956fadcc8d | ||
|
|
120e59631e | ||
|
|
71c92e0718 | ||
|
|
a6d3328e11 | ||
|
|
23414dfc96 | ||
|
|
6249fcf812 | ||
|
|
20fbea985e | ||
|
|
d8b2524822 | ||
|
|
de306bb652 | ||
|
|
541a34382f | ||
|
|
e358eccca9 | ||
|
|
d6fe6ff1d2 | ||
|
|
caacbe4968 | ||
|
|
82cc138ba6 | ||
|
|
1de802fbaa | ||
|
|
79b48c5d47 | ||
|
|
63af77eab3 | ||
|
|
9beab57ab4 | ||
|
|
7f3558b4cb | ||
|
|
b92e2deb41 | ||
|
|
6966c5f092 | ||
|
|
0e64233418 | ||
|
|
cf4af1480b | ||
|
|
3089cc7252 | ||
|
|
62d46a1df3 | ||
|
|
e8e4265dc4 | ||
|
|
d2aa9ff8b1 | ||
|
|
68b793c9ba | ||
|
|
bab15b0978 | ||
|
|
76a49833d4 | ||
|
|
e8b23db276 | ||
|
|
65b4ce5098 | ||
|
|
9f97e3f3c6 | ||
|
|
3e64fff747 | ||
|
|
69f27a2942 | ||
|
|
cbb2fdeb6d | ||
|
|
f0176e0fe0 | ||
|
|
67758d5ec2 | ||
|
|
37828fdef9 | ||
|
|
68aadd483b | ||
|
|
a46e9742f4 | ||
|
|
15664223ee | ||
|
|
dff254e923 | ||
|
|
dbabce2a2d | ||
|
|
043facff48 | ||
|
|
fb2ab77ab8 | ||
|
|
2130a1e408 | ||
|
|
bc0e277b81 | ||
|
|
37a676bb50 | ||
|
|
96d35bcf6d | ||
|
|
c2147de284 |
42
.agents/skills/database-schema-updates/SKILL.md
Normal file
42
.agents/skills/database-schema-updates/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: database-schema-updates
|
||||
description: "Use when changing or diagnosing the Dash database schema, applying a local schema patch, or rerunning the off-repository development migration script."
|
||||
---
|
||||
|
||||
# Database Schema Updates
|
||||
|
||||
Use this workflow for schema changes and local database updates in the Dashback + `dashdbschema` setup.
|
||||
|
||||
## Project map
|
||||
|
||||
- The schema/store repository is the sibling [`dashdbschema`](../../../../dashdbschema) (`libdashstore`).
|
||||
- MSSQL schema DDL: [`schema.js`](../../../../dashdbschema/src/providers/mssql/schema.js).
|
||||
- SQLite schema DDL: [`schema.js`](../../../../dashdbschema/src/providers/sqlite3/schema.js).
|
||||
- Provider behavior: `../../../../dashdbschema/src/providers/{mssql,sqlite3}/`.
|
||||
- Store implementation: `../../../../dashdbschema/src/store/`; SQL queries: `../../../../dashdbschema/src/providers/mssql/queries/`.
|
||||
- Schema/store overview and conventions: [`README.md`](../../../../dashdbschema/README.md) and [`AGENTS.md`](../../../../dashdbschema/AGENTS.md).
|
||||
- Store API documentation index: [`API.md`](../../../../dashdbschema/API.md), with per-domain details in `../../../../dashdbschema/API.<module>.md`.
|
||||
- Dashback database configuration: `src/config/env.js` and the local, untracked `.env`. SQLite defaults to `data/dash.db` unless `SQLITE_PATH` or `SQLITE_FILENAME` overrides it; `DB_PROVIDER` determines the active provider.
|
||||
|
||||
## Rules
|
||||
|
||||
- Do not add migration/upgrade code, migration scripts, or migration-only DDL to a Git repository. The checked-in schema must describe the desired state for a fresh database.
|
||||
- Existing local-development databases may need separate schema-patch scripts maintained in the Git-ignored `data/` folder. Name them `NNN-description.mjs`, using a three-digit sequence that increments from the highest existing number (for example, `001-schema-patch.mjs`, then `002-add-index.mjs`). Never reuse a number or renumber existing scripts; retain older scripts so patches can be tracked and rerun in order. Do not put these scripts in Git-tracked folders.
|
||||
- In this dashback workspace, sibling repositories are read-only. Read their schema and conventions, but do not edit, install dependencies, or run their scripts unless the user explicitly authorizes work in that sibling repository.
|
||||
- Never use `purge-db` or `reset-db` on a database that contains data: those commands purge tables. Do not assume that `initialize()` upgrades existing tables.
|
||||
- Do not print credentials or expose `.env` values in tool output.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Confirm which schema/provider and environment the change concerns. Read the applicable schema, provider, and domain query/store code, plus the relevant repository instructions. For SQL changes, check both MSSQL and SQLite behavior where applicable.
|
||||
2. Make the checked-in schema represent the desired fresh-database shape. Respect the sibling-repository read-only boundary; ask the user to coordinate or authorize a `dashdbschema` change rather than editing across that boundary.
|
||||
3. For an existing local dev database, identify the numbered patch script in `data/` and verify its exact path and contents before running it. Create new local-only patch scripts with the next consecutive three-digit number; never overwrite or renumber existing patches. Confirm the target is the local development database by checking the active provider and configured database path/name without displaying secrets. If the script, target, or safety of rerunning it is unclear, ask the user rather than guessing.
|
||||
4. Preserve local data. For a file-backed SQLite database, make a backup before applying a script that changes it. Do not run against production, a shared database, or an unconfirmed target. Do not stop or start the API as part of this workflow.
|
||||
5. Run or rerun the requested numbered script against the confirmed local dev database from its established location and documented invocation; do not embed credentials in command arguments or logs. Report the result and any errors explicitly.
|
||||
6. Verify the intended schema change against the local dev database using a targeted, read-only schema check. Do not claim success if the script failed or verification was not possible.
|
||||
|
||||
## Important notes
|
||||
|
||||
- The schema source is DDL for new databases, not an upgrade mechanism. Keep fresh database initialization aligned with any manual/local schema changes.
|
||||
- [`no-schema-migrations.mdc`](../../../../dashdbschema/.cursor/rules/no-schema-migrations.mdc) documents the no-migration-code convention. Its reset-db workflow is destructive; it is not a substitute for the separate local dev migration script.
|
||||
- Local patches belong in the Git-ignored `data/` directory and use continuous three-digit numbering. If the correct database target or script operation cannot be verified, ask the user rather than guessing.
|
||||
89
.agents/skills/naming-conventions-and-style/SKILL.md
Normal file
89
.agents/skills/naming-conventions-and-style/SKILL.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: naming-conventions-and-style
|
||||
description: "Use when: improving variable, function, module, or API names; refactoring code for clarity; choosing consistent naming in the dashback backend."
|
||||
---
|
||||
|
||||
# Style
|
||||
|
||||
use vs code region markers in code files
|
||||
use:
|
||||
// MARK: calls startApi from ./api/index.js
|
||||
atop important functions
|
||||
|
||||
# Naming Conventions — dashback
|
||||
|
||||
Apply these conventions when writing or refactoring code in this project.
|
||||
|
||||
## Core rules
|
||||
|
||||
- Prefer clear domain names over vague generic names.
|
||||
- Name functions as actions, not nouns.
|
||||
- Name variables and parameters by what they represent, not by their implementation detail.
|
||||
- Prefer explicit names over abbreviations unless the abbreviation is standard in the project.
|
||||
- Keep names consistent across the same subsystem.
|
||||
|
||||
## Function naming
|
||||
|
||||
- Use verb-based names for routines that do work: `registerRoutes`, `buildApiApp`, `listenOnPort`, `resolveUser`, `validateInput`.
|
||||
- Use `register`, `build`, `create`, `resolve`, `configure`, `respond`, `validate`, and `render` when the function performs an action.
|
||||
- Avoid vague names like `load`, `walk`, `handle`, `do`, `process`, `thing`, and `data` when a more specific name is available.
|
||||
- When a function builds a response object, prefer names like `respondWith...`, `build...Response`, or `create...Payload`.
|
||||
|
||||
## Variable naming
|
||||
|
||||
- Prefer descriptive nouns or noun phrases: `serverStartedAt`, `startupPromise`, `localPrivateHostPattern`.
|
||||
- Use boolean names that read like a question when possible: `isAllowedCorsOrigin`, `hasSelectedCompany`, `isDocsRequest`.
|
||||
- Use `statusCode` instead of `status` when a numeric HTTP status is involved.
|
||||
- Use `origin` and `request`/`req` precisely; avoid ambiguous names like `body` when `payload` or `userData` is clearer.
|
||||
|
||||
## Module naming
|
||||
|
||||
- Prefer names that describe the module’s responsibility, not incidental implementation details.
|
||||
- For API helper modules, names should communicate the response shape or purpose: `meJson` → `respondWithAuthenticatedUser` or `buildCurrentUserResponse`.
|
||||
- Use consistent file-level naming patterns across the same feature area.
|
||||
|
||||
## File naming
|
||||
|
||||
- Keep each route responsibility in its own file under `src/api/routes`.
|
||||
- Use one route module per resource or workflow, not multiple unrelated endpoints in a single file.
|
||||
- Prefer a single clear file name per route group, for example `auth/me.js`, `company/list.js`, `article/create.js`, or `journal/entries.js`.
|
||||
- Match the file name to the route’s primary resource or action so the file’s purpose is obvious from the directory tree.
|
||||
- Avoid generic names like `index.js`, `utils.js`, or `helpers.js` for route modules unless the file is truly the entry point for a module group.
|
||||
- Keep nested route directories aligned with the URL hierarchy when it improves clarity, e.g. `auth/me.js` for `/auth/me`.
|
||||
|
||||
## API and HTTP naming
|
||||
|
||||
- Prefer `registerRoutes` over `loadRoutes` when the function actively registers route modules on the app.
|
||||
- Prefer `buildApiApp` over `createApi` when the function constructs the Express app object.
|
||||
- Prefer `isAllowedCorsOrigin` over `isAllowedOrigin` for clarity around the specific policy being enforced.
|
||||
- When a helper adds Google client info to a response, include the domain concept in the name, e.g. `respondWithAuthenticatedUser`.
|
||||
|
||||
## Examples from this codebase
|
||||
|
||||
Good:
|
||||
- `registerRoutes`
|
||||
- `buildApiApp`
|
||||
- `listenOnPort`
|
||||
- `startupPromise`
|
||||
- `serverStartedAt`
|
||||
- `respondWithAuthenticatedUser`
|
||||
- `isAllowedCorsOrigin`
|
||||
- `LOCAL_PRIVATE_HOST_PATTERN`
|
||||
|
||||
Avoid:
|
||||
- `loadRoutes` when the function actually registers route modules
|
||||
- `walk` in a route auto-loader unless it is explicitly about recursive traversal and the context is clear
|
||||
- `starting` and `startedAt` when `startupPromise` and `serverStartedAt` are more specific
|
||||
- `meJson` when the function is really a response builder for authenticated-user payloads
|
||||
- generic names like `data`, `thing`, `value`, `handle`, `doWork`
|
||||
|
||||
## Review checklist
|
||||
|
||||
Before finalizing a rename, ask:
|
||||
- Does the name describe the actual responsibility?
|
||||
- Does it read clearly in the local code context?
|
||||
- Is it consistent with neighboring names?
|
||||
- Is there any ambiguity about the action, domain, or response shape?
|
||||
- Is it specific enough that a teammate can infer the purpose without reading the implementation?
|
||||
|
||||
Use these rules as the default naming standard for backend refactors and new code in the project.
|
||||
70
.agents/skills/project-overview/SKILL.md
Normal file
70
.agents/skills/project-overview/SKILL.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: project-overview
|
||||
description: Architecture and conventions for the dashback + dashdbschema (libdashstore) monorepo. Use for any backend/store task: providers, store modules, routes, auth, ledger/stock, error mapping, testing.
|
||||
---
|
||||
|
||||
# Project Overview — dashback + dashdbschema
|
||||
|
||||
Multi-root workspace:
|
||||
|
||||
- `/home/seb/src/dashback` — Express backend (`dashback`, ESM, `type: module`). Depends on `libdashstore` via `file:../dashdbschema`.
|
||||
- `/home/seb/src/dashdbschema` — `libdashstore` library: MSSQL schema + domain store API. One class, methods mirror `src/store/<domain>/<action>.js`.
|
||||
- `/home/seb/src/dashback/quixpos2jtl/` — separate JTL/QuixPOS bridge (own `package.json`, Electron/CLI). Ignore unless task mentions JTL.
|
||||
|
||||
## libdashstore (`dashdbschema`)
|
||||
|
||||
Entry: `src/index.js` → `export default class LibDashStore`. Constructor requires a provider with `query/transaction/initialize/purge`. Namespaces are wired there, e.g. `this.stock = { adjust: stockAdjust(provider) }`, `this.ledger = { add, listByManifestarticleId, listBySku }`, plus `article, category, company, companyuser, delivery, order, quote, warehouse, ledger, stock, numbers, printer, labeltemplate, posterminal, supplier, articlesupplier, entity*, tax, unit, brand, lang, pic, manifestattachment, propertyname/articleproperty/categoryproperty, attributename/attributevalue/... , articlelist, optionslist`.
|
||||
|
||||
### Provider abstraction
|
||||
|
||||
- `src/providers/provider.js` — contract (`query(sql, params)`, `transaction(fn)`, `initialize()`, `purge()`).
|
||||
- `src/providers/mssql/` — production. Raw SQL in `queries/<domain>.js`, DDL in `schema.js` (`IF NOT EXISTS`, `SCHEMA_SCRIPTS`). Uses `OUTPUT INSERTED.*`, `TOP 1`, `WITH (UPDLOCK, HOLDLOCK)`.
|
||||
- `src/providers/sqlite3/` — dev/test (`BetterSqlite3Provider`, `better-sqlite3`). Mostly `import * as source from '../../mssql/queries/<x>.js'` + `translateQueries(source, '<domain>')`. DDL in `sqlite3/schema.js` (`RETURNING`, `LIMIT 1`). Auto-migrates `article.track_stock`, `ledger.created_at/warehouse_id` in `sqlite3/index.js`.
|
||||
- `bindParams` (sqlite): strips leading `@/:$`, maps `Date→ISO`, `boolean→1/0`, `null/undefined→null`. Both `@sku` and `sku` keys work.
|
||||
- `src/store/runner.js` (`makeRunner(provider, tx)`) — normalizes pool / tx / callable runner. Store code calls `runner.query(sql, params)` or `runner(sql, params)`. `runner.dialect === 'sqlite3'` selects query set. MSSQL legacy pool wrapped via `wrapMssql`.
|
||||
|
||||
### Store conventions
|
||||
|
||||
- Every store module: `export default function <action>(provider) { ...; return async (googleAuthId, input, tx) => {...} }`.
|
||||
- `googleAuthId` is always `google_auth.id`, never `companyuser.id`.
|
||||
- Company scoping: no `companyId` arg. `resolveSelectedCompanyAccess(provider)` reads `google_auth.selected_company` → `{ companyId, companyuserId, rootCategoryId }`. Missing → throw `No company selected`. Exceptions taking explicit `companyId`: `setSelectedCompany`, `companyuser.add`, `company.delete`, internals.
|
||||
- SKU resolution: `store/article/resolveArticleBySku.js` (`RESOLVE_ARTICLE_ACCESS_BY_SKU`) checks company + membership. Throws `Article not found` / `Parent not found` / `Not authorized`.
|
||||
- Errors are plain `Error` with exact message strings; HTTP mapping happens in `dashback` route `*ErrorStatus()` functions. Keep messages stable/greppable. Conflict → `409` (e.g. `EAN already in use`, `Warehouse in use in ledger`, `Stock tracking is disabled...`).
|
||||
- Manifest docs (`delivery/order/quote/adjustment`): header in `manifest`, lines in `manifestarticle`, helpers in `src/store/manifestLines.js` (`normalizeLines`, `insertLines`, `parseDate`, `shapeLine`) and `manifestDoc/helpers.js`. `insertLines` rejects parent articles (`A parent article cannot be booked on documents (SKU)`), resolves name/pu, ensures tax.
|
||||
- Numbers: `store/numbers/nextNumber.js`, types `sku, entity, delivery, adjustment`.
|
||||
|
||||
### Ledger + stock (important)
|
||||
|
||||
- `ledger` is append-only, tamper-chained: `hash_version=1`, `prev_hash = SHA-512(canonical JSON of prev row)` or `TODO_TSE_BASED_INITHASH` for genesis. See `store/ledger/add.js` (`shape`, `computePrevHash`, `INIT_HASH`).
|
||||
- Stock is **derived**: `SUM(ledger.amount) GROUP BY warehouse`, joined `ledger → manifestarticle → manifest`, matched on `ma.sku = article.sku OR ma.sku IN (SELECT sku FROM articlesupplier WHERE article_id = ...)`, filtered by `m.company_id`. See `providers/*/queries/article.js` (`warehousesJson`/`stock_calc`) and `posterminal.js` (`fBestand`). No `stock` column on article.
|
||||
- `store/stock/adjust.js` creates implicit manifest `type='adjustment'` (number from `adjustment` scheme) + one `manifestarticle` line per item (`amount: abs(amount)`) + one `ledger` row per item (`amount: signed`) + `articlehistory` (`Bestandskorrektur ±n (text)`). Validates: parent → `A parent article cannot have stock (SKU)`; Stückliste (`articlelist_id != null`) → `A Stückliste cannot have manual stock adjustments (SKU)`; **`track_stock==0` → `Stock tracking is disabled for article (SKU)`** (added as bugfix; checked before number allocation/manifest write, so batch is all-or-nothing).
|
||||
- `store/ledger/add.js` enforces the same guard for **any warehouse-scoped entry** (`warehouse_id != null` + line has SKU): looks up `article` by `sku` or `articlesupplier.sku` in same company, rejects if any match has `track_stock==0`. Non-warehouse entries (e.g. `PAYMENT`, memo `NOTE`) are still allowed. Guard runs before `GET_LATEST_ROW`/`INSERT_ROW`, inside the same runner/tx.
|
||||
- `POST /ledger` with `warehouse_id` is therefore also blocked for untracked articles; delivery receipts booked via ledger go through the same check.
|
||||
- `article.track_stock`: `BIT/INTEGER DEFAULT 1`. Managed via `article.createUnderParent({track_stock})`, `updateBySku`, `setTrackStockBySku`, `POST /article` (`track_stock`/`trackStock`, default true). `toggleParent` sets `track_stock=0` when becoming parent. POS surfaces it as `use_stock '1'/'0'` (`store/posterminal/getProductList.js`).
|
||||
|
||||
## dashback (Express API)
|
||||
|
||||
- `src/index.js` → `src/api/index.js` (`startApi/stopApi`). Env in `src/config/env.js` (`.env`: `PORT` default 4208, `HTTPS_PORT`, `GOOGLE_CLIENT_ID`, `JWT_SECRET`, `CORS_ORIGINS`, `DB_PROVIDER=mssql|sqlite`, `SQLITE_PATH`, `MSSQL_*`).
|
||||
- Routes auto-loaded: `loadRoutes()` walks `src/api/routes/**/*.js`, calls `register(app, basePath)`. Each route file owns its `@openapi` JSDoc + `*ErrorStatus(error)` mapper + `requireSelectedCompany(req)`.
|
||||
- `src/db/store.js` — lazy singleton provider+store (`ensureConnection`), wraps every namespace method with timing logs (`libstore <ns>.<method> (Nms)` / `FAILED`). `src/db/<domain>.js` thin wrappers (`store.stock.adjust`, `store.ledger.add`, ...).
|
||||
- Auth: `src/api/middleware/auth.js` (Google SSO + JWT cookie/session, `req.user.{id, selectedCompanyId}`), `src/api/routes/auth/*`, `scripts/manageUsers.js` (allowlist). `src/api/middleware/logger.js` colors `>=400`.
|
||||
- Docs: `npm run docs:api` (`scripts/generateApiDocs.js` + `src/api/openapi.js` swagger) → `docs/api/routes.md` + `docs/api/routes/`. Per-route `x-markdown` hints.
|
||||
- SSE: `src/api/sse.js` (`emit`), `routes/events.js`. Stock adjust emits `stock`, `ledger`, `articles`, `article-<sku>`.
|
||||
- `src/v1/` — legacy POS compat (`/v1/...`, `posAuth`, static `use_stock: '0'` defaults in some stubs). `src/util/` — `cert.js` (self-signed), `errorLog.js`. `certs/`, `data/` (sqlite + `storage/content|fullbinary|thumbavif|thumbbinary`), `logs/errors/`.
|
||||
- HTTP status conventions per route file: `No company selected→400`, `Not authorized→403`, `*not found→404`, validation→400 (often via `includes()`), uniqueness/in-use→409, else 500 + `console.error`. Stock/ledger map `Stock tracking is disabled...→409` (fix for reported bug: `POST /stock/adjust` on `track_stock:false` returned `201` and wrote manifest+ledger; now `409` before any write).
|
||||
|
||||
## Tests / scripts
|
||||
|
||||
- Automated (sqlite temp DB, exit cleanly): `dashdbschema/scripts/test-ledger.js`, `test-stueckliste-stock.js`, `test-sqlite-storage.js`, `test-variant-flow.js`, `test-tombstone.js`, etc. Run with `cd /home/seb/src/dashdbschema && node scripts/test-ledger.js`.
|
||||
- **Do NOT run `scripts/test-api.js` for verification** — it is an interactive `@clack/prompts` menu (MSSQL) that hangs waiting for input.
|
||||
- `dashdbschema` scripts: `init-schema`, `purge-db`, `reset-db`, `benchmark`, `generate-api-doc.js`, `generate-schema-graph.js`. `dashback` scripts: `manageUsers.js`, `generateApiDocs.js`, `testImageCrop.js`. `dashback` has no `npm test`.
|
||||
- Quick regression pattern (sqlite in-memory/temp): create provider → `new LibDashStore(provider)` → `google_auth.add` → `company.create` → `setSelectedCompany` → `unit.upsert` → `warehouse.create` → `article.createUnderParent({track_stock:false})` → assert `stock.adjust` throws, `ledger.listBySku` empty, tracked article still adjusts.
|
||||
|
||||
## Agent tips
|
||||
|
||||
- Add stock invariants in `dashdbschema/src/store/` (not routes) so both `stock.adjust` and direct `ledger.add` are covered; mirror the error string in `dashback/src/api/routes/{stock,ledger,delivery}.js` mappers if a new status is needed.
|
||||
- When touching SQL, update **both** `mssql/queries/*` and check `sqlite3/queries/*` translation; watch `OUTPUT INSERTED` vs `RETURNING`, `TOP 1` vs `LIMIT 1`.
|
||||
- Prefer `runner.query('SELECT ..., track_stock FROM article WHERE id = @articleId', { articleId })` for per-article flags; for ledger-by-SKU use the `article OR articlesupplier.sku` lookup scoped by `company_id`.
|
||||
- Keep `API.*.md` in `dashdbschema/` (generated via `npm run api-doc`) in mind but edit JSDoc source, not the markdown.
|
||||
|
||||
|
||||
@@ -30,8 +30,9 @@ Wenn ein Vertrag unklar oder fehlt: **nicht weiterwursteln**. Human fragen, Vors
|
||||
| Repo | Pfad | Inhalt |
|
||||
|------|------|--------|
|
||||
| **dieses** | `docs/api/README.md` | HTTP-Routen, Auth, CORS |
|
||||
| **dieses** | `docs/api/routes.md`, `docs/api/routes-all.md` | Endpoint-Übersicht |
|
||||
| **dieses** | `docs/api/routes/` | eine Markdown-Datei pro Endpoint |
|
||||
| **dieses** | `docs/api/openapi.json` | maschinenlesbare Spec |
|
||||
| **dieses** | `/docs/` (lokal) | interaktive Swagger-UI; Spec wird zur Laufzeit aus den `@openapi`-JSDoc gebaut (`src/api/openapiSpec.js`) — es gibt bewusst **keine** `openapi.json` auf Disk |
|
||||
| dashdbschema | `../dashdbschema/README.md` | Store-Übersicht, Konventionen, HTTP-Mapping |
|
||||
| dashdbschema | `../dashdbschema/API.md` | Store-API-Index (TOC → `API.<modul>.md`) |
|
||||
| dashdbschema | `../dashdbschema/API.<modul>.md` | pro Namespace (`entity`, `article`, …): Methoden, Signaturen, JSDoc-Beispiele |
|
||||
|
||||
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
|
||||
|
||||
46
.env.example
46
.env.example
@@ -1,5 +1,15 @@
|
||||
# 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
|
||||
# HTTPS server port (defaults to 8443, set to 0 or false to disable)
|
||||
HTTPS_PORT=8443
|
||||
# Backend Monitor Interface port (served when backend is started with npm run dev)
|
||||
MONITOR_PORT=4210
|
||||
# Optional custom TLS cert/key paths (defaults to auto-generated certs/key.pem and certs/cert.pem)
|
||||
# HTTPS_KEY_PATH=./certs/key.pem
|
||||
# HTTPS_CERT_PATH=./certs/cert.pem
|
||||
BODY_LIMIT=100mb
|
||||
|
||||
# CORS — comma-separated allowed origins
|
||||
CORS_ORIGINS=http://localhost:5173
|
||||
@@ -7,9 +17,15 @@ CORS_ORIGINS=http://localhost:5173
|
||||
# Auth — Google OAuth client ID and JWT signing secret
|
||||
GOOGLE_CLIENT_ID=
|
||||
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
|
||||
|
||||
# Database — MSSQL (auth allowlist and JTL-Wawi article data)
|
||||
# Database Provider — 'sqlite' or 'mssql' (defaults to 'mssql', or 'sqlite' if SQLITE_PATH is set)
|
||||
DB_PROVIDER=sqlite
|
||||
# SQLite database path (e.g. './data/dash.db' or ':memory:' for in-memory)
|
||||
SQLITE_PATH=./data/dash.db
|
||||
|
||||
# Database — MSSQL (used when DB_PROVIDER=mssql)
|
||||
MSSQL_SERVER=
|
||||
MSSQL_DATABASE=
|
||||
MSSQL_USER=
|
||||
@@ -24,3 +40,31 @@ KAT_LISTS=0
|
||||
|
||||
# Tax zone name for VAT rates (tSteuerzone.cName)
|
||||
STEUER_ZONE=Zone-EU
|
||||
|
||||
# OpenRouter (leave OPENROUTER_API_URL unset for default OpenRouter endpoint)
|
||||
# OPENROUTER_API_URL=http://127.0.0.1:8787/api/v1/chat/completions
|
||||
OPENROUTER_FREE_KEY=sk-or-v1-3d3c4e
|
||||
OPENROUTER_FREE_MODEL=openrouter/free
|
||||
OPENROUTER_PAID_MODEL=openai/gpt-6-luna:floor
|
||||
|
||||
# Backup Service
|
||||
# BACKUP_ENABLED=true
|
||||
# BACKUP_DIR=./data/backups
|
||||
# BACKUP_INTERVAL_MS=3600000
|
||||
# BACKUP_HOURLY_RETENTION=50
|
||||
# BACKUP_DAILY_RETENTION=50
|
||||
|
||||
# Redis (Healthcheck Latency Metric)
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
REDIS_KEY=healthcheck:latency_ms
|
||||
# REDIS_URL=redis://127.0.0.1:6379
|
||||
|
||||
# Health Monitor Service
|
||||
# HEALTH_MONITOR_ENABLED=true
|
||||
# HEALTH_MONITOR_INTERVAL_MS=60000
|
||||
|
||||
|
||||
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
node_modules/
|
||||
.env
|
||||
data/
|
||||
logs/
|
||||
print_spool/
|
||||
certs/
|
||||
output/
|
||||
dist/
|
||||
|
||||
36
AGENTS.md
Normal file
36
AGENTS.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# AGENTS.md — dashback (Express API)
|
||||
|
||||
ESM (`type: module`), Express 5, Node. Depends on `libdashstore` via `file:../dashdbschema`.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Entry: `src/index.js` → `src/api/index.js` (`startApi`/`stopApi`, `loadRoutes()` walks `src/api/routes/**/*.js` → `register(app, basePath)`).
|
||||
- Each route file owns its `@openapi` JSDoc + `*ErrorStatus(err)` mapper + `requireSelectedCompany(req)`. Source of truth is code.
|
||||
- DB: `src/db/store.js` lazy singleton provider+store (timing logs `libstore <ns>.<method>`); `src/db/<domain>.js` are thin wrappers around `store.<ns>.<method>`.
|
||||
- Config: `src/config/env.js` (`.env`; `PORT` 4208, `HTTPS_PORT`, `GOOGLE_CLIENT_ID`, `JWT_SECRET`, `CORS_ORIGINS`, `DB_PROVIDER=mssql|sqlite`, `SQLITE_PATH`, `MSSQL_*`).
|
||||
- Auth: `src/api/middleware/auth.js` (Google SSO + JWT cookie, `req.user.{id,selectedCompanyId}`); allowlist via `scripts/manageUsers.js`.
|
||||
- SSE: `src/api/sse.js` (`emit`), `routes/events.js`. Stock adjust emits `stock`, `ledger`, `articles`, `article-<sku>`.
|
||||
- `src/v1/` legacy POS compat — don't touch unless task is POS. `quixpos2jtl/` is a separate Electron/CLI bridge — ignore unless task mentions JTL.
|
||||
- Docs: generated, never hand-edit — see below. Interactive API documentation & tester served under `/docs/` (`/docs/openapi.json`).
|
||||
|
||||
## Build / Test / Docs
|
||||
|
||||
```bash
|
||||
cp .env.example .env && npm install
|
||||
npm start # PORT 4208 (HTTPS 8443)
|
||||
npm run docs:api # regenerates docs/api/routes.md + docs/api/routes-all.md + docs/api/routes/ (no openapi.json on disk — /docs/ builds it live)
|
||||
npm run api-graph # regenerates api-graph.svg + api-graph.dot
|
||||
npm run manage-users # email allowlist
|
||||
```
|
||||
|
||||
- No `npm test`. Verify with syntax check or a self-exiting run on a **different port**; never leave a listener running — user keeps their own dev server.
|
||||
- Store contract lives in sibling repo: `../dashdbschema/API.md` + `API.<module>.md`, `../dashdbschema/README.md`. Read-only.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Sibling repos (`../dashdbschema`, `../dashfront`) are **read-only**: may read docs/code + `git -C ../<repo> log --oneline -20`, never write/install/start. Missing contract → ask human, don't guess. No fallbacks/defaults. See `.cursor/rules/sibling-repos.mdc`, `.cursor/rules/dont-start-the-API-server.mdc`.
|
||||
- After any route change run `npm run docs:api`. Docs index: `docs/api/README.md`.
|
||||
- HTTP mapping per route file: `No company selected→400`, `Not authorized→403`, `*not found→404`, validation (`includes()`)→400, uniqueness/in-use→409, else 500 + `console.error`. `Stock tracking is disabled…→409`.
|
||||
- Keep error strings stable/greppable — mapping depends on exact messages from `libdashstore`.
|
||||
- Stock/ledger invariants belong in `dashdbschema/src/store/`, not routes, so both `stock.adjust` and direct `ledger.add` are covered.
|
||||
- Full backend/store conventions: `.agents/skills/project-overview/SKILL.md`.
|
||||
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"]
|
||||
21
README.md
21
README.md
@@ -27,9 +27,20 @@ Docs are generated from `@openapi` JSDoc on route handlers:
|
||||
|
||||
```bash
|
||||
npm run docs:api
|
||||
npm run api-graph # regenerate api-graph.svg + api-graph.dot
|
||||
```
|
||||
|
||||
That writes `docs/api/openapi.json` and per-route Markdown under `docs/api/routes/`.
|
||||
That writes `docs/api/routes.md`, `docs/api/routes-all.md`, and per-route Markdown under `docs/api/routes/`. `npm run api-graph` generates `api-graph.svg` and `api-graph.dot` visualizing the API resources, endpoints, and dataflow relationships. There is no generated `openapi.json` on disk — the interactive Swagger UI at `/docs/` builds the spec from the same `@openapi` JSDoc at runtime (`src/api/openapiSpec.js`).
|
||||
|
||||
### Function review files
|
||||
|
||||
Create one JavaScript file per function in the project:
|
||||
|
||||
```bash
|
||||
npm run review:functions
|
||||
```
|
||||
|
||||
The generated files are written to `output/` and include each function's source filename and line numbers. The same directory also contains `functions.txt`, an index of source filenames, line numbers, and function signatures. Existing generated function files and the index are replaced each time the command runs. Dependencies, runtime data, and the output directory itself are excluded from the scan.
|
||||
|
||||
### Brands (company-scoped, `selectedCompanyId` aus der Session)
|
||||
|
||||
@@ -55,6 +66,14 @@ That writes `docs/api/openapi.json` and per-route Markdown under `docs/api/route
|
||||
| `PATCH` | `/api/category/:id` | `category.rename` und/oder `category.move` (Name+Slug und/oder Verschieben unter neuen Eltern) |
|
||||
| `PUT` | `/api/category/order` | `category.order` (Geschwister-Sortierung unter einem Elternteil) |
|
||||
|
||||
### Public Product Catalog (ohne Auth, company-scoped)
|
||||
|
||||
| Methode | Route | Beschreibung |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/public/:companyId/categories` | Liefert den Kategoriebaum der Firma |
|
||||
| `GET` | `/api/public/:companyId/articles` | Liefert paginierte Artikelliste (optional gefiltert nach `?categoryId=`, `?search=`) |
|
||||
| `GET` | `/api/public/:companyId/articles/:sku` | Liefert öffentliche Artikeldetails (Preise, Bilder, Kategorien, EANs, Varianten) |
|
||||
|
||||
## Users
|
||||
|
||||
Manage the email allowlist:
|
||||
|
||||
264
SANDBOX.md
Normal file
264
SANDBOX.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Proposal: Dockerized Bash Sandbox for Dashback Agent Tools
|
||||
|
||||
## 1. Overview & Motivation
|
||||
|
||||
Dashback currently equips LLM agents with code execution capabilities via `javascript.js` running inside a Node.js `worker_threads` VM with a virtualized file system (`vfs.js`). While effective for lightweight JavaScript transformations, this approach has limitations:
|
||||
|
||||
1. **Language & Tooling Constraints:** Agents are restricted to JavaScript. They cannot run shell scripts, Python, `jq`, `ripgrep`, `git`, or standard command-line tools.
|
||||
2. **Isolation Boundaries:** Worker threads share host process memory and kernel resources. A native or non-VM shell tool running directly on the host would pose severe security and privilege escalation risks.
|
||||
3. **Reproducibility:** A containerized environment guarantees identical toolchain versions and execution environments across development and production deployments.
|
||||
|
||||
This proposal specifies an isolated, container-based **Bash Tool** (`bash.js`) powered by a lightweight Docker image with sandboxed access to the company's virtual file system (VFS).
|
||||
|
||||
---
|
||||
|
||||
## 2. Sandboxing Architecture
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ dashback Host │
|
||||
│ │
|
||||
│ 1. prepareVfs(googleAuthId) │
|
||||
│ └─ hydrates DB -> data/vfs/<cid>/ │
|
||||
└──────────────────┬─────────────────────┘
|
||||
│
|
||||
Mounts host path│ (scoped to company)
|
||||
data/vfs/<cid> │ as /home/agent/workspace
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ Docker Sandbox Container (`dashback-sandbox`) │
|
||||
│ │
|
||||
│ User: agent (UID 1000, non-root) │
|
||||
│ Rootfs: Read-Only (no modifying /usr, /bin, /lib) │
|
||||
│ Capabilities: None (--cap-drop=ALL) │
|
||||
│ Privilege Escalation: Blocked (no-new-privileges) │
|
||||
│ Tmpfs: /tmp (64M, nosuid), /home/agent/.cache (64M, nosuid) │
|
||||
│ Limits: 512MB RAM, 1.0 CPU, 100 PIDs (fork-bomb proof) │
|
||||
│ Network: None (--network none) or Internal Bridge │
|
||||
│ │
|
||||
│ /home/agent/workspace/ <─ (Shared VFS Mount) │
|
||||
│ ├── package.json │
|
||||
│ ├── script.js │
|
||||
│ └── output.csv │
|
||||
└────────────────────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────┴─────────────────────┐
|
||||
│ 3. syncVfsToDb(googleAuthId) │
|
||||
│ └─ persists created/updated files │
|
||||
│ back to modelvfs table │
|
||||
│ 4. Return stdout/stderr to LLM │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Docker Image Specification
|
||||
|
||||
### Location
|
||||
`docker/sandbox/Dockerfile`
|
||||
|
||||
### `Dockerfile`
|
||||
```dockerfile
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
# Prevent interactive prompts during package install
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install essential CLI tools and dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
ripgrep \
|
||||
procps \
|
||||
tree \
|
||||
tar \
|
||||
unzip \
|
||||
gzip \
|
||||
python3 \
|
||||
bc \
|
||||
sed \
|
||||
gawk \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set up unprivileged user
|
||||
ENV USER=agent
|
||||
ENV HOME=/home/agent
|
||||
ENV WORKSPACE=/home/agent/workspace
|
||||
|
||||
RUN groupadd -g 1000 ${USER} && \
|
||||
useradd -u 1000 -g ${USER} -d ${HOME} -s /bin/bash -m ${USER} && \
|
||||
mkdir -p ${WORKSPACE} && \
|
||||
chown -R ${USER}:${USER} ${HOME}
|
||||
|
||||
# Switch context to agent
|
||||
WORKDIR ${WORKSPACE}
|
||||
USER ${USER}
|
||||
|
||||
# Provide local node bin in PATH
|
||||
ENV PATH="${WORKSPACE}/node_modules/.bin:${HOME}/.local/bin:${PATH}"
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
CMD ["-l"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Security & Runtime Constraints
|
||||
|
||||
Containers must never be invoked with default privileged parameters. The runner applies strict Linux namespace and cgroup boundaries:
|
||||
|
||||
| Flag | Rationale |
|
||||
| :--- | :--- |
|
||||
| `--user 1000:1000` | Executes strictly as unprivileged user `agent`. Matches UID with host file permissions. |
|
||||
| `--cap-drop=ALL` | Drops all Linux capabilities (prevents raw network creation, kernel module loading, chroot, ptrace). |
|
||||
| `--security-opt=no-new-privileges:true` | Disallows gaining privileges via `setuid`/`setgid` binaries. |
|
||||
| `--read-only` | The root filesystem is mounted read-only. The container cannot modify OS binaries, libraries, or configs. |
|
||||
| `--tmpfs /tmp:rw,nosuid,nodev,size=64m` | Provides a volatile, memory-backed scratch space with `nosuid` and `nodev`. |
|
||||
| `--tmpfs /home/agent/.cache:rw,nosuid,size=64m` | Allows tools like npm or python to write temporary caches without disk persistence. |
|
||||
| `--pids-limit 100` | Protects host from fork bombs (`:(){ :|:& };:`). |
|
||||
| `--memory 512m` | Caps RAM usage; terminates process if memory leaks or runaway allocations occur. |
|
||||
| `--cpus 1.0` | Restricts CPU usage to a single core equivalent to avoid CPU starvation. |
|
||||
| `--network none` | Prevents unauthorized external data exfiltration by default (can be bridged if specifically required). |
|
||||
| `-v <vfs_root>:/home/agent/workspace:rw` | Restricts file access solely to the company's workspace directory. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration with Dashback
|
||||
|
||||
### 5.1 VFS Lifecycle
|
||||
|
||||
Each execution turn synchronizes through the existing Dashback VFS pipeline:
|
||||
|
||||
1. **Preparation:**
|
||||
```javascript
|
||||
const vfsRoot = await resolveVfsRoot(googleAuthId);
|
||||
await prepareVfs(googleAuthId); // Hydrates files from `modelvfs` table
|
||||
```
|
||||
2. **Mounting:**
|
||||
The directory `data/vfs/<companyId>` on the host is mounted to `/home/agent/workspace` inside the container.
|
||||
3. **Execution:**
|
||||
Command is executed with a hard timeout (e.g. default 30s, max 120s).
|
||||
4. **Synchronization:**
|
||||
```javascript
|
||||
await syncVfsToDb(googleAuthId); // Syncs any new or edited files back to `modelvfs`
|
||||
```
|
||||
|
||||
### 5.2 Tool Implementation (`src/tools/bash.js`)
|
||||
|
||||
```javascript
|
||||
import { execFile } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import path from 'node:path';
|
||||
import { defineTool, ToolError } from 'libllmorbiter';
|
||||
import { resolveVfsRoot, prepareVfs, syncVfsToDb } from './vfs.js';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const IMAGE_NAME = 'dashback-sandbox:latest';
|
||||
const DEFAULT_TIMEOUT_MS = 30_000;
|
||||
const MAX_BUFFER = 1024 * 1024; // 1 MB output cap
|
||||
|
||||
export function createBashTool(googleAuthId) {
|
||||
return defineTool({
|
||||
name: 'bash_execute',
|
||||
description: 'Execute a bash command in a secure sandboxed Linux environment with Node.js, Python, git, jq, and ripgrep. Working directory is the company workspace.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
command: {
|
||||
type: 'string',
|
||||
description: 'The bash command line to execute (e.g., "node script.js", "jq . data.json", "rg pattern").',
|
||||
},
|
||||
timeout_ms: {
|
||||
type: 'integer',
|
||||
description: 'Execution timeout in milliseconds (default 30000, max 120000).',
|
||||
},
|
||||
},
|
||||
required: ['command'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
invoke: async ({ command, timeout_ms = DEFAULT_TIMEOUT_MS }) => {
|
||||
const vfsRoot = await resolveVfsRoot(googleAuthId);
|
||||
await prepareVfs(googleAuthId);
|
||||
|
||||
const effectiveTimeout = Math.min(Math.max(timeout_ms, 1000), 120000);
|
||||
|
||||
const dockerArgs = [
|
||||
'run', '--rm', '-i',
|
||||
'--user', '1000:1000',
|
||||
'--cap-drop=ALL',
|
||||
'--security-opt=no-new-privileges:true',
|
||||
'--read-only',
|
||||
'--tmpfs', '/tmp:rw,nosuid,nodev,size=64m',
|
||||
'--tmpfs', '/home/agent/.cache:rw,nosuid,size=64m',
|
||||
'--pids-limit', '100',
|
||||
'--memory', '512m',
|
||||
'--cpus', '1.0',
|
||||
'--network', 'none',
|
||||
'-v', `${path.resolve(vfsRoot)}:/home/agent/workspace:rw`,
|
||||
IMAGE_NAME,
|
||||
'-c', command,
|
||||
];
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync('docker', dockerArgs, {
|
||||
timeout: effectiveTimeout,
|
||||
maxBuffer: MAX_BUFFER,
|
||||
});
|
||||
|
||||
await syncVfsToDb(googleAuthId);
|
||||
|
||||
return {
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
exitCode: 0,
|
||||
};
|
||||
} catch (err) {
|
||||
// Ensure partial file modifications are synced even on exit error
|
||||
try { await syncVfsToDb(googleAuthId); } catch (_) {}
|
||||
|
||||
if (err.killed || err.signal === 'SIGTERM') {
|
||||
throw new ToolError(`Execution timed out after ${effectiveTimeout}ms.`);
|
||||
}
|
||||
|
||||
return {
|
||||
stdout: err.stdout ? err.stdout.trim() : '',
|
||||
stderr: err.stderr ? err.stderr.trim() : err.message,
|
||||
exitCode: err.code ?? 1,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Execution Models: Ephemeral vs. Persistent Daemon
|
||||
|
||||
| Consideration | Ephemeral (`docker run --rm`) | Persistent Container (`docker exec`) |
|
||||
| :--- | :--- | :--- |
|
||||
| **Startup Overhead** | ~200-400ms per command | ~20-50ms per command |
|
||||
| **State Cleanliness** | Completely fresh OS environment every call | Processes/background jobs remain running |
|
||||
| **Lifecycle Complexity** | None. Docker handles lifecycle and cleanup | Requires container pool management and reaper |
|
||||
| **Failure Recovery** | Automatic | If container crashes, must be recreated |
|
||||
| **Recommendation** | **Adopt for Phase 1** | Evaluate if high execution frequency warrants it |
|
||||
|
||||
---
|
||||
|
||||
## 7. Implementation Roadmap
|
||||
|
||||
1. **Phase 1: Docker Setup & Scripting**
|
||||
- Add `docker/sandbox/Dockerfile`.
|
||||
- Add build script: `npm run docker:sandbox:build` to `package.json`.
|
||||
- Verify image build and base tools (`node -v`, `python3 --version`, `rg --version`, `jq --version`).
|
||||
2. **Phase 2: Bash Tool Implementation**
|
||||
- Create `src/tools/bash.js` conforming to `defineTool`.
|
||||
- Integrate with `prepareVfs` and `syncVfsToDb`.
|
||||
- Add unit tests validating command execution, timeouts, and file synchronization.
|
||||
3. **Phase 3: Integration into Tool Registries**
|
||||
- Register `createBashTool` in the agent execution registry alongside existing tools (`javascript.js`, `read-file.js`, `patch.js`).
|
||||
4. **Phase 4: Optional Network & API Bridge**
|
||||
- If commands inside the container need to invoke Dashback endpoints (`http://host.docker.internal:4208/api`), introduce an opt-in network mode using Docker custom bridge networks with short-lived JWT tokens.
|
||||
176
api-graph.dot
Normal file
176
api-graph.dot
Normal file
File diff suppressed because one or more lines are too long
2727
api-graph.svg
Normal file
2727
api-graph.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 330 KiB |
1066
artikeltabelle.csv
1066
artikeltabelle.csv
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
||||
|
||||
```
|
||||
http://localhost:4208/api
|
||||
https://localhost:8443/api
|
||||
https://localhost:8443/v1 (POS API)
|
||||
```
|
||||
|
||||
## CORS & cookies
|
||||
@@ -30,33 +32,90 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
|
||||
| File | Endpoint |
|
||||
|------|----------|
|
||||
| [`routes/.well-known/oauth-authorization-server.md`](routes/.well-known/oauth-authorization-server.md) | `GET /.well-known/oauth-authorization-server` |
|
||||
| [`routes/.well-known/oauth-protected-resource.md`](routes/.well-known/oauth-protected-resource.md) | `GET /.well-known/oauth-protected-resource` |
|
||||
| [`routes/account.get.md`](routes/account.get.md) | `GET /account` |
|
||||
| [`routes/account.post.md`](routes/account.post.md) | `POST /account` |
|
||||
| [`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/export.zip.md`](routes/article/export.zip.md) | `GET /article/export.zip` |
|
||||
| [`routes/article/history-list.md`](routes/article/history-list.md) | `GET /article/history` |
|
||||
| [`routes/article/delete.md`](routes/article/delete.md) | `DELETE /article/{sku}` |
|
||||
| [`routes/article/get.md`](routes/article/get.md) | `GET /article/{sku}` |
|
||||
| [`routes/article/update.md`](routes/article/update.md) | `PATCH /article/{sku}` |
|
||||
| [`routes/article/_sku/articlelist.get.md`](routes/article/_sku/articlelist.get.md) | `GET /article/{sku}/articlelist` |
|
||||
| [`routes/article/_sku/articlelist.put.md`](routes/article/_sku/articlelist.put.md) | `PUT /article/{sku}/articlelist` |
|
||||
| [`routes/article/_sku/attributevalue.get.md`](routes/article/_sku/attributevalue.get.md) | `GET /article/{sku}/attributevalue` |
|
||||
| [`routes/article/_sku/attributevalue.put.md`](routes/article/_sku/attributevalue.put.md) | `PUT /article/{sku}/attributevalue` |
|
||||
| [`routes/article/set-brand.md`](routes/article/set-brand.md) | `PUT /article/{sku}/brand` |
|
||||
| [`routes/article/set-categories.md`](routes/article/set-categories.md) | `PUT /article/{sku}/categories` |
|
||||
| [`routes/article/_sku/children.get.md`](routes/article/_sku/children.get.md) | `GET /article/{sku}/children` |
|
||||
| [`routes/article/_sku/children.post.md`](routes/article/_sku/children.post.md) | `POST /article/{sku}/children` |
|
||||
| [`routes/article/_sku/children.put.md`](routes/article/_sku/children.put.md) | `PUT /article/{sku}/children` |
|
||||
| [`routes/article/_sku/children/_childSku.md`](routes/article/_sku/children/_childSku.md) | `DELETE /article/{sku}/children/{childSku}` |
|
||||
| [`routes/article/set-eans.md`](routes/article/set-eans.md) | `PUT /article/{sku}/eans` |
|
||||
| [`routes/article/history.md`](routes/article/history.md) | `GET /article/{sku}/history` |
|
||||
| [`routes/article/add-image.md`](routes/article/add-image.md) | `POST /article/{sku}/image` |
|
||||
| [`routes/article/delete-image.md`](routes/article/delete-image.md) | `DELETE /article/{sku}/image/{fullHash}` |
|
||||
| [`routes/article/order-images.md`](routes/article/order-images.md) | `PUT /article/{sku}/images/order` |
|
||||
| [`routes/article/_sku/optionslist.get.md`](routes/article/_sku/optionslist.get.md) | `GET /article/{sku}/optionslist` |
|
||||
| [`routes/article/_sku/optionslist.put.md`](routes/article/_sku/optionslist.put.md) | `PUT /article/{sku}/optionslist` |
|
||||
| [`routes/article/_sku/parent.md`](routes/article/_sku/parent.md) | `POST /article/{sku}/parent` |
|
||||
| [`routes/article/set-prices.md`](routes/article/set-prices.md) | `PUT /article/{sku}/prices` |
|
||||
| [`routes/article/_sku/print.md`](routes/article/_sku/print.md) | `POST /article/{sku}/print` |
|
||||
| [`routes/article/_sku/property.get.md`](routes/article/_sku/property.get.md) | `GET /article/{sku}/property` |
|
||||
| [`routes/article/_sku/property.put.md`](routes/article/_sku/property.put.md) | `PUT /article/{sku}/property` |
|
||||
| [`routes/article/_sku/property/_propertyId.md`](routes/article/_sku/property/_propertyId.md) | `DELETE /article/{sku}/property/{propertyId}` |
|
||||
| [`routes/article/supplier/list.md`](routes/article/supplier/list.md) | `GET /article/{sku}/supplier` |
|
||||
| [`routes/article/supplier/create.md`](routes/article/supplier/create.md) | `POST /article/{sku}/supplier` |
|
||||
| [`routes/article/supplier/order.md`](routes/article/supplier/order.md) | `PUT /article/{sku}/supplier/order` |
|
||||
| [`routes/article/supplier/delete.md`](routes/article/supplier/delete.md) | `DELETE /article/{sku}/supplier/{id}` |
|
||||
| [`routes/article/supplier/update.md`](routes/article/supplier/update.md) | `PATCH /article/{sku}/supplier/{id}` |
|
||||
| [`routes/article/_sku/track_stock.md`](routes/article/_sku/track_stock.md) | `PUT /article/{sku}/track_stock` |
|
||||
| [`routes/article/set-weight.md`](routes/article/set-weight.md) | `PUT /article/{sku}/weight` |
|
||||
| [`routes/articleIndex.md`](routes/articleIndex.md) | `GET /articleIndex` |
|
||||
| [`routes/attributename.get.md`](routes/attributename.get.md) | `GET /attributename` |
|
||||
| [`routes/attributename.post.md`](routes/attributename.post.md) | `POST /attributename` |
|
||||
| [`routes/attributename/order.md`](routes/attributename/order.md) | `PUT /attributename/order` |
|
||||
| [`routes/attributename/_attributeId/value.get.md`](routes/attributename/_attributeId/value.get.md) | `GET /attributename/{attributeId}/value` |
|
||||
| [`routes/attributename/_attributeId/value.post.md`](routes/attributename/_attributeId/value.post.md) | `POST /attributename/{attributeId}/value` |
|
||||
| [`routes/attributename/_attributeId/value/order.md`](routes/attributename/_attributeId/value/order.md) | `PUT /attributename/{attributeId}/value/order` |
|
||||
| [`routes/attributename/_id.delete.md`](routes/attributename/_id.delete.md) | `DELETE /attributename/{id}` |
|
||||
| [`routes/attributename/_id.patch.md`](routes/attributename/_id.patch.md) | `PATCH /attributename/{id}` |
|
||||
| [`routes/attributevalue/_id.delete.md`](routes/attributevalue/_id.delete.md) | `DELETE /attributevalue/{id}` |
|
||||
| [`routes/attributevalue/_id.patch.md`](routes/attributevalue/_id.patch.md) | `PATCH /attributevalue/{id}` |
|
||||
| [`routes/attributevalue/_id/image.delete.md`](routes/attributevalue/_id/image.delete.md) | `DELETE /attributevalue/{id}/image` |
|
||||
| [`routes/attributevalue/_id/image.post.md`](routes/attributevalue/_id/image.post.md) | `POST /attributevalue/{id}/image` |
|
||||
| [`routes/auth/login.md`](routes/auth/login.md) | `POST /auth/login` |
|
||||
| [`routes/auth/logout.md`](routes/auth/logout.md) | `POST /auth/logout` |
|
||||
| [`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/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/_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/brand/list.md`](routes/brand/list.md) | `GET /brand` |
|
||||
| [`routes/brand/upsert.md`](routes/brand/upsert.md) | `PUT /brand` |
|
||||
| [`routes/brand/create.md`](routes/brand/create.md) | `POST /brand` |
|
||||
| [`routes/brand/delete.md`](routes/brand/delete.md) | `DELETE /brand/{id}` |
|
||||
| [`routes/brand/update.md`](routes/brand/update.md) | `PATCH /brand/{id}` |
|
||||
| [`routes/brand/_id/image.delete.md`](routes/brand/_id/image.delete.md) | `DELETE /brand/{id}/image` |
|
||||
| [`routes/brand/_id/image.post.md`](routes/brand/_id/image.post.md) | `POST /brand/{id}/image` |
|
||||
| [`routes/carrier.get.md`](routes/carrier.get.md) | `GET /carrier` |
|
||||
| [`routes/carrier.post.md`](routes/carrier.post.md) | `POST /carrier` |
|
||||
| [`routes/carrier/order.md`](routes/carrier/order.md) | `PUT /carrier/order` |
|
||||
| [`routes/carrier/_id.delete.md`](routes/carrier/_id.delete.md) | `DELETE /carrier/{id}` |
|
||||
| [`routes/carrier/_id.get.md`](routes/carrier/_id.get.md) | `GET /carrier/{id}` |
|
||||
| [`routes/carrier/_id.patch.md`](routes/carrier/_id.patch.md) | `PATCH /carrier/{id}` |
|
||||
| [`routes/category/create.md`](routes/category/create.md) | `POST /category` |
|
||||
| [`routes/category/export.zip.md`](routes/category/export.zip.md) | `GET /category/export.zip` |
|
||||
| [`routes/category/order.md`](routes/category/order.md) | `PUT /category/order` |
|
||||
@@ -64,15 +123,30 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/category/delete.md`](routes/category/delete.md) | `DELETE /category/{id}` |
|
||||
| [`routes/category/update.md`](routes/category/update.md) | `PATCH /category/{id}` |
|
||||
| [`routes/category/articles.md`](routes/category/articles.md) | `GET /category/{id}/articles` |
|
||||
| [`routes/category/_id/attributevalue.get.md`](routes/category/_id/attributevalue.get.md) | `GET /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/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.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/company/list.md`](routes/company/list.md) | `GET /company` |
|
||||
| [`routes/company/create.md`](routes/company/create.md) | `POST /company` |
|
||||
| [`routes/company/export.md`](routes/company/export.md) | `GET /company/export` |
|
||||
| [`routes/company/export.post.md`](routes/company/export.post.md) | `POST /company/export` |
|
||||
| [`routes/company/history.md`](routes/company/history.md) | `GET /company/history` |
|
||||
| [`routes/company/addHistory.md`](routes/company/addHistory.md) | `POST /company/history` |
|
||||
| [`routes/company/import.md`](routes/company/import.md) | `POST /company/import` |
|
||||
| [`routes/companysetting/list.md`](routes/companysetting/list.md) | `GET /company/setting` |
|
||||
| [`routes/companysetting/get.md`](routes/companysetting/get.md) | `GET /company/setting/{key}` |
|
||||
| [`routes/companysetting/set.md`](routes/companysetting/set.md) | `PUT /company/setting/{key}` |
|
||||
| [`routes/company/delete.md`](routes/company/delete.md) | `DELETE /company/{id}` |
|
||||
| [`routes/company/update.md`](routes/company/update.md) | `PUT /company/{id}` |
|
||||
| [`routes/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/create.md`](routes/customergroup/create.md) | `POST /customergroup` |
|
||||
| [`routes/customergroup/order.md`](routes/customergroup/order.md) | `PUT /customergroup/order` |
|
||||
@@ -80,10 +154,12 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/customergroup/update.md`](routes/customergroup/update.md) | `PATCH /customergroup/{id}` |
|
||||
| [`routes/delivery/list.md`](routes/delivery/list.md) | `GET /delivery` |
|
||||
| [`routes/delivery/create.md`](routes/delivery/create.md) | `POST /delivery` |
|
||||
| [`routes/delivery/parse.md`](routes/delivery/parse.md) | `POST /delivery/parse` |
|
||||
| [`routes/delivery/delete.md`](routes/delivery/delete.md) | `DELETE /delivery/{id}` |
|
||||
| [`routes/delivery/get.md`](routes/delivery/get.md) | `GET /delivery/{id}` |
|
||||
| [`routes/delivery/update.md`](routes/delivery/update.md) | `PATCH /delivery/{id}` |
|
||||
| [`routes/delivery/_id/file/_fileId.delete.md`](routes/delivery/_id/file/_fileId.delete.md) | `DELETE /delivery/{id}/file/{fileId}` |
|
||||
| [`routes/delivery/file.md`](routes/delivery/file.md) | `GET /delivery/{id}/file/{fileId}` |
|
||||
| [`routes/delivery/_id/pdf.md`](routes/delivery/_id/pdf.md) | `POST /delivery/{id}/pdf` |
|
||||
| [`routes/entity/list.md`](routes/entity/list.md) | `GET /entity` |
|
||||
| [`routes/entity/create.md`](routes/entity/create.md) | `POST /entity` |
|
||||
| [`routes/entity/next-number.md`](routes/entity/next-number.md) | `GET /entity/next-number` |
|
||||
@@ -100,26 +176,138 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/entity/update.md`](routes/entity/update.md) | `PATCH /entity/{id}` |
|
||||
| [`routes/events.md`](routes/events.md) | `GET /events` |
|
||||
| [`routes/health.md`](routes/health.md) | `GET /health` |
|
||||
| [`routes/health/latency.md`](routes/health/latency.md) | `GET /health/latency` |
|
||||
| [`routes/image/get.md`](routes/image/get.md) | `GET /image/{fullHash}` |
|
||||
| [`routes/labeltemplate/list.md`](routes/labeltemplate/list.md) | `GET /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/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) | `PUT /labeltemplate/{id}` |
|
||||
| [`routes/ledger.md`](routes/ledger.md) | `GET /ledger` |
|
||||
| [`routes/ledger/history.md`](routes/ledger/history.md) | `GET /ledger/history` |
|
||||
| [`routes/ledger/manifestarticle/_manifestarticleId.md`](routes/ledger/manifestarticle/_manifestarticleId.md) | `GET /ledger/manifestarticle/{manifestarticleId}` |
|
||||
| [`routes/ledger/sku/_sku.md`](routes/ledger/sku/_sku.md) | `GET /ledger/sku/{sku}` |
|
||||
| [`routes/mcp.md`](routes/mcp.md) | `POST /mcp` |
|
||||
| [`routes/mcp/authorizations.md`](routes/mcp/authorizations.md) | `GET /mcp/authorizations` |
|
||||
| [`routes/mcp/authorizations/_clientId.md`](routes/mcp/authorizations/_clientId.md) | `DELETE /mcp/authorizations/{clientId}` |
|
||||
| [`routes/mcp/authorize.get.md`](routes/mcp/authorize.get.md) | `GET /mcp/authorize` |
|
||||
| [`routes/mcp/authorize.post.md`](routes/mcp/authorize.post.md) | `POST /mcp/authorize` |
|
||||
| [`routes/mcp/clients.md`](routes/mcp/clients.md) | `GET /mcp/clients` |
|
||||
| [`routes/mcp/clients/_clientId.md`](routes/mcp/clients/_clientId.md) | `DELETE /mcp/clients/{clientId}` |
|
||||
| [`routes/modelconversation.get.md`](routes/modelconversation.get.md) | `GET /modelconversation` |
|
||||
| [`routes/modelconversation.post.md`](routes/modelconversation.post.md) | `POST /modelconversation` |
|
||||
| [`routes/modelconversation/_id.delete.md`](routes/modelconversation/_id.delete.md) | `DELETE /modelconversation/{id}` |
|
||||
| [`routes/modelconversation/_id.get.md`](routes/modelconversation/_id.get.md) | `GET /modelconversation/{id}` |
|
||||
| [`routes/modelconversation/_id/message.get.md`](routes/modelconversation/_id/message.get.md) | `GET /modelconversation/{id}/message` |
|
||||
| [`routes/modelconversation/_id/message.post.md`](routes/modelconversation/_id/message.post.md) | `POST /modelconversation/{id}/message` |
|
||||
| [`routes/modelconversation/_id/stream.md`](routes/modelconversation/_id/stream.md) | `GET /modelconversation/{id}/stream` |
|
||||
| [`routes/number/list.md`](routes/number/list.md) | `GET /number` |
|
||||
| [`routes/number/update.md`](routes/number/update.md) | `PATCH /number/{id}` |
|
||||
| [`routes/oauth/authorize.md`](routes/oauth/authorize.md) | `GET /oauth/authorize` |
|
||||
| [`routes/oauth/introspect.md`](routes/oauth/introspect.md) | `POST /oauth/introspect` |
|
||||
| [`routes/oauth/register.md`](routes/oauth/register.md) | `POST /oauth/register` |
|
||||
| [`routes/oauth/token.md`](routes/oauth/token.md) | `POST /oauth/token` |
|
||||
| [`routes/order/list.md`](routes/order/list.md) | `GET /order` |
|
||||
| [`routes/order/create.md`](routes/order/create.md) | `POST /order` |
|
||||
| [`routes/order/delete.md`](routes/order/delete.md) | `DELETE /order/{id}` |
|
||||
| [`routes/order/get.md`](routes/order/get.md) | `GET /order/{id}` |
|
||||
| [`routes/order/update.md`](routes/order/update.md) | `PATCH /order/{id}` |
|
||||
| [`routes/order/_id/file/_fileId.delete.md`](routes/order/_id/file/_fileId.delete.md) | `DELETE /order/{id}/file/{fileId}` |
|
||||
| [`routes/order/_id/file/_fileId.get.md`](routes/order/_id/file/_fileId.get.md) | `GET /order/{id}/file/{fileId}` |
|
||||
| [`routes/order/_id/payment.get.md`](routes/order/_id/payment.get.md) | `GET /order/{id}/payment` |
|
||||
| [`routes/order/_id/payment.post.md`](routes/order/_id/payment.post.md) | `POST /order/{id}/payment` |
|
||||
| [`routes/order/_id/pdf.md`](routes/order/_id/pdf.md) | `POST /order/{id}/pdf` |
|
||||
| [`routes/order/_id/shipment.get.md`](routes/order/_id/shipment.get.md) | `GET /order/{id}/shipment` |
|
||||
| [`routes/order/_id/shipment.post.md`](routes/order/_id/shipment.post.md) | `POST /order/{id}/shipment` |
|
||||
| [`routes/order/_id/shipping.get.md`](routes/order/_id/shipping.get.md) | `GET /order/{id}/shipping` |
|
||||
| [`routes/order/_id/shipping.post.md`](routes/order/_id/shipping.post.md) | `POST /order/{id}/shipping` |
|
||||
| [`routes/package/_id.md`](routes/package/_id.md) | `DELETE /package/{id}` |
|
||||
| [`routes/payment/_id.md`](routes/payment/_id.md) | `DELETE /payment/{id}` |
|
||||
| [`routes/posterminal/list.md`](routes/posterminal/list.md) | `GET /posterminal` |
|
||||
| [`routes/posterminal/upsert.md`](routes/posterminal/upsert.md) | `PUT /posterminal` |
|
||||
| [`routes/posterminal/delete.md`](routes/posterminal/delete.md) | `DELETE /posterminal/{id}` |
|
||||
| [`routes/posterminal/update.md`](routes/posterminal/update.md) | `PATCH /posterminal/{id}` |
|
||||
| [`routes/posterminal/setOffAll.md`](routes/posterminal/setOffAll.md) | `DELETE /posterminal/{id}/all` |
|
||||
| [`routes/posterminal/setOnAll.md`](routes/posterminal/setOnAll.md) | `PUT /posterminal/{id}/all` |
|
||||
| [`routes/posterminal/setOff.md`](routes/posterminal/setOff.md) | `DELETE /posterminal/{id}/category/{categoryId}` |
|
||||
| [`routes/posterminal/setOn.md`](routes/posterminal/setOn.md) | `PUT /posterminal/{id}/category/{categoryId}` |
|
||||
| [`routes/printer/list.md`](routes/printer/list.md) | `GET /printer` |
|
||||
| [`routes/printer/create.md`](routes/printer/create.md) | `POST /printer` |
|
||||
| [`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/get.md`](routes/printer/get.md) | `GET /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/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.post.md`](routes/propertyname.post.md) | `POST /propertyname` |
|
||||
| [`routes/propertyname/order.md`](routes/propertyname/order.md) | `PUT /propertyname/order` |
|
||||
| [`routes/propertyname/_id.delete.md`](routes/propertyname/_id.delete.md) | `DELETE /propertyname/{id}` |
|
||||
| [`routes/propertyname/_id.patch.md`](routes/propertyname/_id.patch.md) | `PATCH /propertyname/{id}` |
|
||||
| [`routes/public/telemetry/crash.md`](routes/public/telemetry/crash.md) | `POST /public/telemetry/crash` |
|
||||
| [`routes/public/_companyId/articles.md`](routes/public/_companyId/articles.md) | `GET /public/{companyId}/articles` |
|
||||
| [`routes/public/_companyId/articles/_sku.md`](routes/public/_companyId/articles/_sku.md) | `GET /public/{companyId}/articles/{sku}` |
|
||||
| [`routes/public/_companyId/categories.md`](routes/public/_companyId/categories.md) | `GET /public/{companyId}/categories` |
|
||||
| [`routes/quote/list.md`](routes/quote/list.md) | `GET /quote` |
|
||||
| [`routes/quote/create.md`](routes/quote/create.md) | `POST /quote` |
|
||||
| [`routes/quote/delete.md`](routes/quote/delete.md) | `DELETE /quote/{id}` |
|
||||
| [`routes/quote/get.md`](routes/quote/get.md) | `GET /quote/{id}` |
|
||||
| [`routes/quote/update.md`](routes/quote/update.md) | `PATCH /quote/{id}` |
|
||||
| [`routes/quote/_id/file/_fileId.delete.md`](routes/quote/_id/file/_fileId.delete.md) | `DELETE /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/_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/images.md`](routes/serper/images.md) | `POST /serper/images` |
|
||||
| [`routes/shipment.md`](routes/shipment.md) | `GET /shipment` |
|
||||
| [`routes/shipment/_id.delete.md`](routes/shipment/_id.delete.md) | `DELETE /shipment/{id}` |
|
||||
| [`routes/shipment/_id.get.md`](routes/shipment/_id.get.md) | `GET /shipment/{id}` |
|
||||
| [`routes/shipment/_id.patch.md`](routes/shipment/_id.patch.md) | `PATCH /shipment/{id}` |
|
||||
| [`routes/shipment/_id/file/_fileId.delete.md`](routes/shipment/_id/file/_fileId.delete.md) | `DELETE /shipment/{id}/file/{fileId}` |
|
||||
| [`routes/shipment/_id/file/_fileId.get.md`](routes/shipment/_id/file/_fileId.get.md) | `GET /shipment/{id}/file/{fileId}` |
|
||||
| [`routes/shipment/_id/pdf.md`](routes/shipment/_id/pdf.md) | `POST /shipment/{id}/pdf` |
|
||||
| [`routes/shipment/_id/shipping.get.md`](routes/shipment/_id/shipping.get.md) | `GET /shipment/{id}/shipping` |
|
||||
| [`routes/shipment/_id/shipping.post.md`](routes/shipment/_id/shipping.post.md) | `POST /shipment/{id}/shipping` |
|
||||
| [`routes/shipping/_id.delete.md`](routes/shipping/_id.delete.md) | `DELETE /shipping/{id}` |
|
||||
| [`routes/shipping/_id.patch.md`](routes/shipping/_id.patch.md) | `PATCH /shipping/{id}` |
|
||||
| [`routes/shipping/_id/package.get.md`](routes/shipping/_id/package.get.md) | `GET /shipping/{id}/package` |
|
||||
| [`routes/shipping/_id/package.post.md`](routes/shipping/_id/package.post.md) | `POST /shipping/{id}/package` |
|
||||
| [`routes/smallthumb/get.md`](routes/smallthumb/get.md) | `GET /smallthumb/{fullHash}` |
|
||||
| [`routes/stock/accept.md`](routes/stock/accept.md) | `POST /stock/accept` |
|
||||
| [`routes/stock/adjust.md`](routes/stock/adjust.md) | `POST /stock/adjust` |
|
||||
| [`routes/stock/ship.md`](routes/stock/ship.md) | `POST /stock/ship` |
|
||||
| [`routes/subagentschedule.get.md`](routes/subagentschedule.get.md) | `GET /subagentschedule` |
|
||||
| [`routes/subagentschedule.post.md`](routes/subagentschedule.post.md) | `POST /subagentschedule` |
|
||||
| [`routes/subagentschedule/_id.delete.md`](routes/subagentschedule/_id.delete.md) | `DELETE /subagentschedule/{id}` |
|
||||
| [`routes/subagentschedule/_id.get.md`](routes/subagentschedule/_id.get.md) | `GET /subagentschedule/{id}` |
|
||||
| [`routes/subagentschedule/_id.put.md`](routes/subagentschedule/_id.put.md) | `PUT /subagentschedule/{id}` |
|
||||
| [`routes/supplier/list.md`](routes/supplier/list.md) | `GET /supplier` |
|
||||
| [`routes/supplier/create.md`](routes/supplier/create.md) | `POST /supplier` |
|
||||
| [`routes/supplier/delete.md`](routes/supplier/delete.md) | `DELETE /supplier/{id}` |
|
||||
@@ -130,8 +318,36 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/unit/delete.md`](routes/unit/delete.md) | `DELETE /unit` |
|
||||
| [`routes/unit/list.md`](routes/unit/list.md) | `GET /unit` |
|
||||
| [`routes/unit/upsert.md`](routes/unit/upsert.md) | `PUT /unit` |
|
||||
| [`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/v1/customer.md`](routes/v1/customer.md) | `GET /v1/customer` |
|
||||
| [`routes/v1/customergroup.md`](routes/v1/customergroup.md) | `GET /v1/customergroup` |
|
||||
| [`routes/v1/deletedentity.md`](routes/v1/deletedentity.md) | `GET /v1/deletedentity` |
|
||||
| [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` |
|
||||
| [`routes/v1/order.md`](routes/v1/order.md) | `POST /v1/order` |
|
||||
| [`routes/pos/pimage.md`](routes/pos/pimage.md) | `GET /v1/pimage` |
|
||||
| [`routes/v1/product.md`](routes/v1/product.md) | `GET /v1/product` |
|
||||
| [`routes/vfs/list.md`](routes/vfs/list.md) | `GET /vfs` |
|
||||
| [`routes/vfs/applets-restore.md`](routes/vfs/applets-restore.md) | `POST /vfs/applets/restore` |
|
||||
| [`routes/vfs/mkdir.md`](routes/vfs/mkdir.md) | `POST /vfs/mkdir` |
|
||||
| [`routes/vfs/rename.md`](routes/vfs/rename.md) | `POST /vfs/rename` |
|
||||
| [`routes/vfs/skills-restore.md`](routes/vfs/skills-restore.md) | `POST /vfs/skills/restore` |
|
||||
| [`routes/vfs/upload.md`](routes/vfs/upload.md) | `POST /vfs/upload` |
|
||||
| [`routes/vfs/delete.md`](routes/vfs/delete.md) | `DELETE /vfs/{*path}` |
|
||||
| [`routes/vfs/_*path.md`](routes/vfs/_*path.md) | `GET /vfs/{*path}` |
|
||||
| [`routes/vfs/exec.md`](routes/vfs/exec.md) | `POST /vfs/{*path}` |
|
||||
| [`routes/vfs/write.md`](routes/vfs/write.md) | `PUT /vfs/{*path}` |
|
||||
| [`routes/warehouse/list.md`](routes/warehouse/list.md) | `GET /warehouse` |
|
||||
| [`routes/warehouse/create.md`](routes/warehouse/create.md) | `POST /warehouse` |
|
||||
| [`routes/warehouse/order.md`](routes/warehouse/order.md) | `PUT /warehouse/order` |
|
||||
| [`routes/warehouse/delete.md`](routes/warehouse/delete.md) | `DELETE /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/_id.put.md`](routes/warehouse/_id.put.md) | `PUT /warehouse/{id}` |
|
||||
| [`routes/zpljob.md`](routes/zpljob.md) | `POST /zpljob` |
|
||||
|
||||
Machine-readable spec: [`openapi.json`](openapi.json).
|
||||
Route overview: [`routes.md`](routes.md) | All routes: [`routes-all.md`](routes-all.md).
|
||||
|
||||
## Signals (SSE)
|
||||
|
||||
@@ -139,9 +355,33 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
|
||||
|
||||
| Signal | Emitted by |
|
||||
|--------|------------|
|
||||
| `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}/weight` |
|
||||
| `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}` |
|
||||
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
| `autoinvite` | `POST /autoinvite`, `DELETE /autoinvite/{id}` |
|
||||
| `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}` |
|
||||
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image`, `POST /category/{id}/image/generate` |
|
||||
| `company` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}`, `PUT /company/{id}` |
|
||||
| `companyuser` | `POST /companyuser`, `DELETE /companyuser/{id}`, `PATCH /companyuser/{id}` |
|
||||
| `delivery` | `DELETE /delivery/{id}/file/{fileId}`, `POST /delivery/{id}/pdf`, `POST /stock/accept` |
|
||||
| `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` |
|
||||
| `ledger` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
||||
| `modelconversation` | `POST /modelconversation`, `DELETE /modelconversation/{id}` |
|
||||
| `modelconversation-<id>` | `POST /modelconversation/{id}/message` |
|
||||
| `order` | `DELETE /order/{id}/file/{fileId}`, `POST /order/{id}/pdf`, `DELETE /shipment/{id}`, `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}` |
|
||||
| `printer` | `POST /printer`, `PUT /printer/order`, `DELETE /printer/{id}`, `PATCH /printer/{id}`, `PUT /printer/{id}` |
|
||||
| `propertyname` | `POST /propertyname`, `PUT /propertyname/order`, `DELETE /propertyname/{id}`, `PATCH /propertyname/{id}` |
|
||||
| `quote` | `DELETE /quote/{id}/file/{fileId}`, `POST /quote/{id}/pdf` |
|
||||
| `shipment` | `DELETE /shipment/{id}`, `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}`, `POST /shipment/{id}/pdf` |
|
||||
| `shipping` | `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}` |
|
||||
| `supplier` | `POST /supplier`, `DELETE /supplier/{id}`, `PATCH /supplier/{id}` |
|
||||
| `user` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}` |
|
||||
| `vfs` | `POST /vfs/applets/restore`, `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/skills/restore`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` |
|
||||
| `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` |
|
||||
|
||||
## DESIGN
|
||||
|
||||
@@ -149,5 +389,5 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
|
||||
- that should loosly bound to services in /src/services
|
||||
- for every service we have a CLI tester in /scripts/
|
||||
- all DB SQL code shall be in /src/db
|
||||
- with mssql staements in /src/db/mssql/
|
||||
- with SQL staements in /src/db/
|
||||
- API docs: `@openapi` JSDoc on handlers → `npm run docs:api` → `docs/api/`
|
||||
|
||||
10440
docs/api/openapi.json
10440
docs/api/openapi.json
File diff suppressed because it is too large
Load Diff
1184
docs/api/routes-all.md
Normal file
1184
docs/api/routes-all.md
Normal file
File diff suppressed because it is too large
Load Diff
754
docs/api/routes.md
Normal file
754
docs/api/routes.md
Normal file
@@ -0,0 +1,754 @@
|
||||
# Dash API Routes Overview
|
||||
|
||||
Overview of all 196 API endpoints across all modules.
|
||||
|
||||
### Notation
|
||||
|
||||
Types: `str` string, `int` integer, `num` number, `bool` boolean; `T[]` array; `?` optional.
|
||||
Params: path values first, then `query:` and `body:` (`body?` = optional body). Responses: status code + JSON shape; `Tn` refers to a reusable type below.
|
||||
|
||||
### Reusable types
|
||||
|
||||
- `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: 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: 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: 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?: T27|null, incoming?: { date: str, amount: num }[] }`
|
||||
- `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 }`
|
||||
- `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, 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, 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` = `{ 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: 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` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
||||
- `T18` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
||||
- `T19` = `{ id: int, googleAuthId: int, email: str, role: str, picture?: str|null }`
|
||||
- `T20` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
||||
- `T21` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
||||
- `T22` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
||||
- `T23` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
||||
- `T24` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
||||
- `T25` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
||||
- `T26` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
||||
- `T27` = `{ net: num, taxId: int, taxRate?: num|null }`
|
||||
|
||||
### Routes
|
||||
|
||||
|
||||
### Section: /article
|
||||
|
||||
#### Create article in category — `POST /article`
|
||||
- Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }`
|
||||
- Returns: `201 { sku: str }`
|
||||
#### Query company-wide article history — `GET /article/history`
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||
- Returns: `200 { history: { id: int, date: str, sku: str, historytext: str, name?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Delete article by SKU — `DELETE /article/{sku}`
|
||||
- Params: `sku: str, query: cascade?: bool`
|
||||
- Returns: `204 void`
|
||||
#### Get article by SKU — `GET /article/{sku}`
|
||||
- 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: 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}`
|
||||
- Params: `sku: str, body: { name?: str, desc?: str, seo?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
|
||||
- Returns: `204 void`
|
||||
#### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
|
||||
#### Set Stückliste (bundle items) for an article — `PUT /article/{sku}/articlelist`
|
||||
- Params: `sku: str, body: { items: { articleId?: int, sku?: str, count?: int, sort?: int }[] }`
|
||||
- Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
|
||||
#### List article attribute values — `GET /article/{sku}/attributevalue`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, articleId?: int, exclusive?: bool }[] }`
|
||||
#### Bulk set article attribute values — `PUT /article/{sku}/attributevalue`
|
||||
- Params: `sku: str, body: int[]`
|
||||
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, articleId?: int, exclusive?: bool }[] }`
|
||||
#### Set or clear article brand by SKU (null to clear) — `PUT /article/{sku}/brand`
|
||||
- Params: `sku: str, body: { brandId: int|null }`
|
||||
- Returns: `204 void`
|
||||
#### Replace article categories by SKU — `PUT /article/{sku}/categories`
|
||||
- Params: `sku: str, body: { categoryIds: int[] }`
|
||||
- Returns: `204 void`
|
||||
#### List child articles of a parent article — `GET /article/{sku}/children`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }`
|
||||
#### Create and attach a new child article to a parent article — `POST /article/{sku}/children`
|
||||
- Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }`
|
||||
- Returns: `201 { sku: str }`
|
||||
#### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children`
|
||||
- Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }`
|
||||
- Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }`
|
||||
#### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}`
|
||||
- Params: `sku: str, childSku: str, query: deleteArticle?: bool`
|
||||
- Returns: `200 { success: bool, deleted: bool }`
|
||||
#### Replace article EANs by SKU — `PUT /article/{sku}/eans`
|
||||
- Params: `sku: str, body: { eans: str[] }`
|
||||
- Returns: `204 void`
|
||||
#### Get article history by SKU — `GET /article/{sku}/history`
|
||||
- Params: `sku: str, query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||
- 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}`
|
||||
- Params: `sku: str, fullHash: str`
|
||||
- Returns: `204 void`
|
||||
#### Reorder article images — `PUT /article/{sku}/images/order`
|
||||
- Params: `sku: str, body: { fullHashes: str[] }`
|
||||
- Returns: `200 { fullHashes: str[] }`
|
||||
#### Get options list for an article — `GET /article/{sku}/optionslist`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
|
||||
#### Set options list for an article — `PUT /article/{sku}/optionslist`
|
||||
- Params: `sku: str, body: { options: { articleId?: int, sku?: str, count?: int, sort?: int, mandated?: bool }[] }`
|
||||
- Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
|
||||
#### Toggle parent mode for an article — `POST /article/{sku}/parent`
|
||||
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
|
||||
- Returns: `204 void`
|
||||
#### Replace POS prices by SKU (net > gross) — `PUT /article/{sku}/prices`
|
||||
- Params: `sku: str, body: { prices: { net?: num, gross?: num, taxRate?: num, minmultiplier?: num }[] }`
|
||||
- Returns: `204 void`
|
||||
#### Print article label — `POST /article/{sku}/print`
|
||||
- Params: `sku: str, body: { templateId: int, agentprinterId: int, quantity: int }`
|
||||
- Returns: `200 { success: bool }`
|
||||
#### List article properties — `GET /article/{sku}/property`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, articleId?: int }[] }`
|
||||
#### Bulk set article properties — `PUT /article/{sku}/property`
|
||||
- Params: `sku: str, body: { properties: { propertyId: int, value: str, sort?: int }[] }`
|
||||
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, articleId?: int }[] }`
|
||||
#### Delete article property — `DELETE /article/{sku}/property/{propertyId}`
|
||||
- Params: `sku: str, propertyId: int`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### List article suppliers — `GET /article/{sku}/supplier`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { suppliers: T5[] }`
|
||||
#### 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 }`
|
||||
- Returns: `201 { supplier: T5 }`
|
||||
#### Reorder article suppliers — `PUT /article/{sku}/supplier/order`
|
||||
- Params: `sku: str, body: { ids: int[] }`
|
||||
- Returns: `200 { ids: int[] }`
|
||||
#### Remove supplier assignment from article — `DELETE /article/{sku}/supplier/{id}`
|
||||
- Params: `sku: str, id: int`
|
||||
- Returns: `204 void`
|
||||
#### 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 }`
|
||||
- Returns: `200 { supplier: T5 }`
|
||||
#### Set article track_stock flag by SKU — `PUT /article/{sku}/track_stock`
|
||||
- Params: `sku: str, body: { track_stock: bool }`
|
||||
- Returns: `204 void`
|
||||
#### Set article weight by SKU — `PUT /article/{sku}/weight`
|
||||
- Params: `sku: str, body: { weight: num }`
|
||||
- Returns: `204 void`
|
||||
|
||||
### Section: /attributename
|
||||
|
||||
#### List attribute names — `GET /attributename`
|
||||
_none_
|
||||
- Returns: `200 { attributenames: T10[] }`
|
||||
#### Create attribute name — `POST /attributename`
|
||||
- Params: `body: { name: str, sort?: int, exclusive?: bool }`
|
||||
- Returns: `201 { attributename: T10 }`
|
||||
#### Reorder attribute names — `PUT /attributename/order`
|
||||
- Params: `body: { attributenameIds: int[] }`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### List values for an attribute — `GET /attributename/{attributeId}/value`
|
||||
- Params: `attributeId: int`
|
||||
- Returns: `200 { values: T18[] }`
|
||||
#### Create value for an attribute — `POST /attributename/{attributeId}/value`
|
||||
- Params: `attributeId: int, body: { value: str, sort?: int }`
|
||||
- Returns: `201 { attributevalue: T18 }`
|
||||
#### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order`
|
||||
- Params: `attributeId: int, body: { attributevalueIds: int[] }`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### Delete attribute name — `DELETE /attributename/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### Update attribute name — `PATCH /attributename/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }`
|
||||
- Returns: `200 { attributename: T10 }`
|
||||
|
||||
### Section: /attributevalue
|
||||
|
||||
#### Delete attribute value — `DELETE /attributevalue/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### Update attribute value — `PATCH /attributevalue/{id}`
|
||||
- Params: `id: int, body: { value?: str, sort?: int }`
|
||||
- Returns: `200 { attributevalue: T18 }`
|
||||
#### Delete attribute value image — `DELETE /attributevalue/{id}/image`
|
||||
- Params: `id: int`
|
||||
- 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
|
||||
|
||||
#### List brands — `GET /brand`
|
||||
- Params: `query: offset?: int, limit?: int, includeSmallThumbs?: bool`
|
||||
- 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`
|
||||
- Params: `body: { name: str, gsprpinfo?: str }`
|
||||
- Returns: `201 { brand: { id: int, name: str, pics: T23[], gsprpinfo?: str|null } }`
|
||||
#### Delete brand — `DELETE /brand/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Rename brand — `PATCH /brand/{id}`
|
||||
- Params: `id: int, body: { name?: str, gsprpinfo?: str }`
|
||||
- Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }`
|
||||
#### Delete brand image — `DELETE /brand/{id}/image`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T23[] } }`
|
||||
|
||||
### Section: /carrier
|
||||
|
||||
#### List carriers — `GET /carrier`
|
||||
_none_
|
||||
- Returns: `200 { carriers: T22[] }`
|
||||
#### Create carrier — `POST /carrier`
|
||||
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||
- Returns: `201 { carrier: T22 }`
|
||||
#### Reorder carriers — `PUT /carrier/order`
|
||||
- Params: `body: { carrierIds: int[] }`
|
||||
- Returns: `200 { carrierIds: int[] }`
|
||||
#### Delete carrier — `DELETE /carrier/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get carrier by ID — `GET /carrier/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { carrier: T22 }`
|
||||
#### Update carrier — `PATCH /carrier/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||
- Returns: `200 { carrier: T22 }`
|
||||
|
||||
### Section: /category
|
||||
|
||||
#### Create category — `POST /category`
|
||||
- Params: `body: { parentId: int, name: str }`
|
||||
- Returns: `201 { category: { id: int, parentId: int|null, sort: int, name: str } }`
|
||||
#### Reorder sibling categories — `PUT /category/order`
|
||||
- Params: `body: { parentId: int, categoryIds: int[] }`
|
||||
- Returns: `200 { categoryIds: int[] }`
|
||||
#### Category tree — `GET /category/tree`
|
||||
_none_
|
||||
- Returns: `200 { languageId: int, tree: { id: int, sort: int, posterminalIds: int[], parentId?: int|null, name?: str|null, slug?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, articleCount?: int }[], articleCount: int }`
|
||||
#### Delete category — `DELETE /category/{id}`
|
||||
- Params: `id: int, query: recursive?: bool`
|
||||
- Returns: `204 void`
|
||||
#### Update category (rename or move) — `PATCH /category/{id}`
|
||||
- Params: `id: int, body: { name?: str, parentId?: int }`
|
||||
- Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }`
|
||||
#### Articles in category — `GET /category/{id}/articles`
|
||||
- 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 }[] }[] }`
|
||||
#### List category attribute values — `GET /category/{id}/attributevalue`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }`
|
||||
#### Bulk set category attribute values — `PUT /category/{id}/attributevalue`
|
||||
- Params: `id: int, body: int[]`
|
||||
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }`
|
||||
#### Delete category image — `DELETE /category/{id}/image`
|
||||
- Params: `id: int`
|
||||
- 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`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, categoryId?: int }[] }`
|
||||
#### Bulk set category properties — `PUT /category/{id}/property`
|
||||
- Params: `id: int, body: { properties: { propertyId: int, value: str, sort?: int }[] }`
|
||||
- Returns: `200 { properties: { id: int, propertyId: int, name: str, exclusive: bool, value: str, sort: int, categoryId?: int }[] }`
|
||||
#### Delete category property — `DELETE /category/{id}/property/{propertyId}`
|
||||
- Params: `id: int, propertyId: int`
|
||||
- Returns: `200 { ok: bool }`
|
||||
|
||||
### Section: /company
|
||||
|
||||
#### List companies — `GET /company`
|
||||
_none_
|
||||
- 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`
|
||||
- Params: `query: limit?: int`
|
||||
- Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }`
|
||||
#### Add company history entry — `POST /company/history`
|
||||
- Params: `body: { text: str }`
|
||||
- Returns: `201 { history: { id: int, date: str, text: str, companyuserId?: int|null } }`
|
||||
#### Import company catalog from zip archive (SSE) — `POST /company/import`
|
||||
- Params: `form: { file?: str }`
|
||||
- Returns: `200 SSE`
|
||||
#### List company settings — `GET /company/setting`
|
||||
_none_
|
||||
- Returns: `200 { settings: { key: str, value: str|null }[] }`
|
||||
#### Get company setting by key — `GET /company/setting/{key}`
|
||||
- Params: `key: str`
|
||||
- Returns: `200 { setting: { key: str, value: str|null } }`
|
||||
#### Upsert company setting — `PUT /company/setting/{key}`
|
||||
- Params: `key: str, body: { value: str, frontend?: bool }`
|
||||
- Returns: `200 { setting: { key: str, value: str|null } }`
|
||||
#### Update company — `PUT /company/{id}`
|
||||
- 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, 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
|
||||
|
||||
#### List customer groups — `GET /customergroup`
|
||||
_none_
|
||||
- Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }`
|
||||
#### Create customer group — `POST /customergroup`
|
||||
- Params: `body: { name: str }`
|
||||
- Returns: `201 { customergroup: { id: int, name: str, sort: int } }`
|
||||
#### Reorder customer groups — `PUT /customergroup/order`
|
||||
- Params: `body: { customergroupIds: int[] }`
|
||||
- Returns: `200 { customergroupIds: int[] }`
|
||||
#### Delete customer group — `DELETE /customergroup/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Update customer group — `PATCH /customergroup/{id}`
|
||||
- Params: `id: int, body: { name: str }`
|
||||
- Returns: `200 { customergroup: { id: int, name: str, sort: int } }`
|
||||
|
||||
### Section: /delivery
|
||||
|
||||
#### List deliveries — `GET /delivery`
|
||||
- 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?: T26 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### 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 } }[] }`
|
||||
- Returns: `201 { delivery: T4 }`
|
||||
#### Delete delivery — `DELETE /delivery/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get delivery — `GET /delivery/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { delivery: T4 }`
|
||||
#### Update delivery — `PATCH /delivery/{id}`
|
||||
- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: { amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
|
||||
- Returns: `200 { delivery: T4 }`
|
||||
#### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}`
|
||||
- Params: `id: int, fileId: int`
|
||||
- Returns: `204 void`
|
||||
#### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T7, fileUrl: str }`
|
||||
|
||||
### Section: /entity
|
||||
|
||||
#### List entities — `GET /entity`
|
||||
- Params: `query: customergroupId?: int, role?: str`
|
||||
- Returns: `200 { entities: T16[] }`
|
||||
#### Create entity — `POST /entity`
|
||||
- Params: `body: { customergroupId: int, name: str, firstName?: str|null, number?: str }`
|
||||
- Returns: `201 { entity: T16 }`
|
||||
#### Peek next customer number — `GET /entity/next-number`
|
||||
_none_
|
||||
- Returns: `200 { number: str }`
|
||||
#### List entity addresses — `GET /entity/{entityId}/address`
|
||||
- Params: `entityId: int`
|
||||
- Returns: `200 { addresses: T8[] }`
|
||||
#### 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 }`
|
||||
- Returns: `201 { address: T8 }`
|
||||
#### Delete entity address — `DELETE /entity/{entityId}/address/{id}`
|
||||
- Params: `entityId: int, id: int`
|
||||
- Returns: `204 void`
|
||||
#### 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 }`
|
||||
- Returns: `200 { address: T8 }`
|
||||
#### List entity contacts — `GET /entity/{entityId}/contact`
|
||||
- Params: `entityId: int`
|
||||
- Returns: `200 { contacts: T24[] }`
|
||||
#### Create entity contact — `POST /entity/{entityId}/contact`
|
||||
- Params: `entityId: int, body: { contactType: str, contact: str }`
|
||||
- Returns: `201 { contact: T24 }`
|
||||
#### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}`
|
||||
- Params: `entityId: int, id: int`
|
||||
- Returns: `204 void`
|
||||
#### Update entity contact — `PATCH /entity/{entityId}/contact/{id}`
|
||||
- Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }`
|
||||
- Returns: `200 { contact: T24 }`
|
||||
#### Delete entity — `DELETE /entity/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get entity — `GET /entity/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { entity: T16 }`
|
||||
#### Update entity — `PATCH /entity/{id}`
|
||||
- Params: `id: int, body: { name?: str, firstName?: str|null, number?: str|null }`
|
||||
- Returns: `200 { entity: T16 }`
|
||||
|
||||
### Section: /labeltemplate
|
||||
|
||||
#### List label templates — `GET /labeltemplate`
|
||||
_none_
|
||||
- Returns: `200 { labeltemplates: { id: int, name: str, width: num, height: num, dpi: int, type: str, thumb?: str|null }[] }`
|
||||
#### Create label template — `POST /labeltemplate`
|
||||
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T15[] } }`
|
||||
- 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}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get label template by ID — `GET /labeltemplate/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { labeltemplate: T6 }`
|
||||
#### Update label template (partial) — `PATCH /labeltemplate/{id}`
|
||||
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T15[] } }`
|
||||
- Returns: `200 { labeltemplate: T6 }`
|
||||
#### Update label template — `PUT /labeltemplate/{id}`
|
||||
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T15[] } }`
|
||||
- Returns: `200 { labeltemplate: T6 }`
|
||||
|
||||
### Section: /ledger
|
||||
|
||||
#### Query ledger by manifest article ID or SKU — `GET /ledger`
|
||||
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
|
||||
- Returns: `200 { ledger: T9[] }`
|
||||
#### Query company-wide ledger history — `GET /ledger/history`
|
||||
- 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 }`
|
||||
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
|
||||
- Params: `manifestarticleId: int, query: transaction_type?: str`
|
||||
- Returns: `200 { ledger: T9[] }`
|
||||
#### Query ledger by article SKU — `GET /ledger/sku/{sku}`
|
||||
- Params: `sku: str, query: transaction_type?: str`
|
||||
- Returns: `200 { ledger: T9[] }`
|
||||
|
||||
### Section: /number
|
||||
|
||||
#### List number sequences — `GET /number`
|
||||
_none_
|
||||
- Returns: `200 { numbers: { id: int, type: str, prefix: str, padding: int, next: int }[] }`
|
||||
#### Update number sequence — `PATCH /number/{id}`
|
||||
- Params: `id: int, body: { prefix?: str, padding?: int, next?: int }`
|
||||
- Returns: `200 { number: { id: int, type: str, prefix: str, padding: int, next: int } }`
|
||||
|
||||
### Section: /order
|
||||
|
||||
#### List orders — `GET /order`
|
||||
- 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 }`
|
||||
#### Create order — `POST /order`
|
||||
- Params: `body: { number: str, date: str, entityId: int, lines: T12[], externalNumber?: str|null }`
|
||||
- Returns: `201 { order: T1 }`
|
||||
#### Delete order — `DELETE /order/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get order — `GET /order/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { order: T1 }`
|
||||
#### Update order — `PATCH /order/{id}`
|
||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
|
||||
- Returns: `200 { order: T1 }`
|
||||
#### Delete order file attachment — `DELETE /order/{id}/file/{fileId}`
|
||||
- Params: `id: int, fileId: int`
|
||||
- Returns: `204 void`
|
||||
#### List payments of an order — `GET /order/{id}/payment`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { payments: T21[] }`
|
||||
#### Add payment to an order — `POST /order/{id}/payment`
|
||||
- Params: `id: int, body: { accountId: int, type: str, amount: num }`
|
||||
- Returns: `201 { payment: T21 }`
|
||||
#### Generate and attach order PDF — `POST /order/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T7, fileUrl: str }`
|
||||
#### List shipments of an order — `GET /order/{id}/shipment`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shipments: T2[] }`
|
||||
#### Create shipment for an order — `POST /order/{id}/shipment`
|
||||
- Params: `id: int, body: { lines: { sku?: str, orderLineId?: int, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[], date?: str, warehouseId?: int, externalNumber?: str }`
|
||||
- Returns: `201 { shipment: T2 }`
|
||||
#### List shippings of an order — `GET /order/{id}/shipping`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shippings: T13[] }`
|
||||
#### Create shipping for an order — `POST /order/{id}/shipping`
|
||||
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||
- Returns: `201 { shipping: T13 }`
|
||||
|
||||
### Section: /package
|
||||
|
||||
#### Delete package — `DELETE /package/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
|
||||
### Section: /payment
|
||||
|
||||
#### Delete payment — `DELETE /payment/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
|
||||
### Section: /propertyname
|
||||
|
||||
#### List property names — `GET /propertyname`
|
||||
_none_
|
||||
- Returns: `200 { propertynames: T20[] }`
|
||||
#### Create property name — `POST /propertyname`
|
||||
- Params: `body: { name: str, sort?: int, exclusive?: bool }`
|
||||
- Returns: `201 { propertyname: T20 }`
|
||||
#### Reorder property names — `PUT /propertyname/order`
|
||||
- Params: `body: { propertynameIds: int[] }`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### Delete property name — `DELETE /propertyname/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### Update property name — `PATCH /propertyname/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }`
|
||||
- Returns: `200 { propertyname: T20 }`
|
||||
|
||||
### Section: /quote
|
||||
|
||||
#### List quotes — `GET /quote`
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||
- Returns: `200 { quotes: T3[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Create quote — `POST /quote`
|
||||
- Params: `body: { number: str, date: str, lines: T12[], entityId?: int|null, externalNumber?: str|null }`
|
||||
- Returns: `201 { quote: T3 }`
|
||||
#### Delete quote — `DELETE /quote/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get quote — `GET /quote/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { quote: T3 }`
|
||||
#### Update quote — `PATCH /quote/{id}`
|
||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T12[] }`
|
||||
- Returns: `200 { quote: T3 }`
|
||||
#### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}`
|
||||
- Params: `id: int, fileId: int`
|
||||
- Returns: `204 void`
|
||||
#### 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?: T12[] }`
|
||||
- Returns: `201 { order: T1 }`
|
||||
#### Generate and attach quote PDF — `POST /quote/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- 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
|
||||
|
||||
#### List shipments — `GET /shipment`
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, state?: str, seekMonths?: int`
|
||||
- Returns: `200 { shipments: { id: int, number: str, type: str, date: str, positions: int, totalQuantity: num, packageCount: int, state: str, entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, orderId?: int|null, orderNumber?: str|null, customerName?: str|null, carrier?: str|null, trackingCode?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Delete shipment — `DELETE /shipment/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get shipment by ID — `GET /shipment/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shipment: T2 }`
|
||||
#### Update shipment state or metadata — `PATCH /shipment/{id}`
|
||||
- Params: `id: int, body: { state?: str }`
|
||||
- Returns: `200 { shipment: T2 }`
|
||||
#### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}`
|
||||
- Params: `id: int, fileId: int`
|
||||
- Returns: `204 void`
|
||||
#### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T7, fileUrl: str }`
|
||||
#### List packages of a shipment — `GET /shipment/{id}/shipping`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shippings: T13[] }`
|
||||
#### Create package for a shipment — `POST /shipment/{id}/shipping`
|
||||
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||
- Returns: `201 { shipping: T13 }`
|
||||
|
||||
### Section: /shipping
|
||||
|
||||
#### Delete shipping — `DELETE /shipping/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Update shipping — `PATCH /shipping/{id}`
|
||||
- Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrierId?: int, warehouseId?: int }`
|
||||
- Returns: `200 { shipping: T13 }`
|
||||
#### List packages of a shipping — `GET /shipping/{id}/package`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { packages: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null }[] }`
|
||||
#### Create package for a shipping — `POST /shipping/{id}/package`
|
||||
- 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 } }`
|
||||
|
||||
### Section: /stock
|
||||
|
||||
#### Accept delivery manifest item(s) — `POST /stock/accept`
|
||||
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
||||
- Returns: `200`
|
||||
#### Adjust stock manually — `POST /stock/adjust`
|
||||
- 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: T9[] }`
|
||||
#### Ship shipment manifest item(s) — `POST /stock/ship`
|
||||
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
||||
- Returns: `200`
|
||||
|
||||
### Section: /supplier
|
||||
|
||||
#### List suppliers — `GET /supplier`
|
||||
_none_
|
||||
- Returns: `200 { suppliers: T25[] }`
|
||||
#### Create supplier — `POST /supplier`
|
||||
- Params: `body: { name: str }`
|
||||
- Returns: `201 { supplier: T25 }`
|
||||
#### Delete supplier — `DELETE /supplier/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Update supplier — `PATCH /supplier/{id}`
|
||||
- Params: `id: int, body: { name: str }`
|
||||
- Returns: `200 { supplier: T25 }`
|
||||
#### List supplier articles by SKUs — `GET /supplier/{id}/articles`
|
||||
- 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?: T27|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||
#### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles`
|
||||
- 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?: T27|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||
|
||||
### Section: /unit
|
||||
|
||||
#### Delete unit — `DELETE /unit`
|
||||
- Params: `query: si: str`
|
||||
- Returns: `204 void`
|
||||
#### List units — `GET /unit`
|
||||
_none_
|
||||
- Returns: `200 { units: T17[] }`
|
||||
#### Upsert unit — `PUT /unit`
|
||||
- Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
|
||||
- Returns: `200 { unit: T17 }`
|
||||
|
||||
### Section: /warehouse
|
||||
|
||||
#### List warehouses — `GET /warehouse`
|
||||
_none_
|
||||
- Returns: `200 { warehouses: { id: int, name: str, sort: int }[] }`
|
||||
#### Create warehouse — `POST /warehouse`
|
||||
- Params: `body: { name: str, sort?: int }`
|
||||
- Returns: `201 { warehouse: { id: int, name: str, sort: int } }`
|
||||
#### Reorder warehouses — `PUT /warehouse/order`
|
||||
- Params: `body: { warehouseIds: int[] }`
|
||||
- Returns: `200 { warehouseIds: int[] }`
|
||||
#### Delete warehouse — `DELETE /warehouse/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get warehouse by ID — `GET /warehouse/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
|
||||
#### Update warehouse (partial) — `PATCH /warehouse/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int }`
|
||||
- Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
|
||||
#### Update warehouse — `PUT /warehouse/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int }`
|
||||
- Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
|
||||
### Binary & streaming routes (reachable only via `fetch()` in javascript_execute)
|
||||
|
||||
These routes are NOT in the table above because their payloads (binary request body or binary response) or long-lived streams cannot be handled usefully by the `dashback_request` tool. They ARE reachable from inside `javascript_execute` with `fetch("/route")`:
|
||||
|
||||
| Method | Route | Returns | Notes |
|
||||
|:---|:---|:---|:---|
|
||||
| `GET` | `/image/{fullHash}` | `image/avif` | Full-size article/brand/category image by SHA-256 hash. Use for inspecting or re-encoding an existing image.
|
||||
| `GET` | `/thumb/{fullHash}` | `image/avif` | Thumb AVIF by SHA-256 hash — cheaper than the full image. Takes the same `fullHash` as `/image/{fullHash}`.
|
||||
| `POST` | `/serper/image` | `image/avif` | Crawl an image from a URL, auto-crop transparent/white borders. Body: `{ url }`.
|
||||
| `POST` | `/article/{sku}/image` | `201 { image: { picId, sort, full, thumb, hash, thumbHash } }` | Upload full-size AVIF or WebP image for article SKU. WebP is auto-converted to AVIF. Body: raw binary (`image/avif` or `image/webp`).
|
||||
| `POST` | `/attributevalue/{id}/image` | `201 { attributevalue: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | Upload or replace AVIF/WebP image for attribute value ID. Body: raw binary (`image/avif` or `image/webp`).
|
||||
| `POST` | `/brand/{id}/image` | `201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics } }` | Upload or replace AVIF/WebP image for brand ID. Body: raw binary (`image/avif` or `image/webp`).
|
||||
| `POST` | `/category/{id}/image` | `201 { category: { id, thumbW, thumbH, thumbHash } }` | Upload or replace AVIF/WebP image for category ID. Body: raw binary (`image/avif` or `image/webp`).
|
||||
|
||||
Save downloaded bytes to the VFS, then embed them in your reply as ``:
|
||||
|
||||
```js
|
||||
module.exports = (async () => {
|
||||
const hash = "…64-char sha-256…";
|
||||
const res = await fetch(`/thumb/${hash}`); // authenticated automatically
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
fs.writeFileSync("artifacts/thumb.avif", buf);
|
||||
return { bytes: buf.length };
|
||||
})();
|
||||
```
|
||||
|
||||
Upload an image from the VFS:
|
||||
|
||||
```js
|
||||
module.exports = (async () => {
|
||||
const buf = fs.readFileSync("images/product.avif");
|
||||
const res = await fetch("/article/SKU123/image", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "image/avif" },
|
||||
body: buf,
|
||||
});
|
||||
return await res.json();
|
||||
})();
|
||||
```
|
||||
|
||||
`fullHash` appears on articles, brands and categories. `thumbHash` is a base64 blur-up placeholder, not a content hash, and is not accepted here.
|
||||
15
docs/api/routes/.well-known/oauth-authorization-server.md
Normal file
15
docs/api/routes/.well-known/oauth-authorization-server.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# `GET /.well-known/oauth-authorization-server`
|
||||
|
||||
Public. No cookie required.
|
||||
|
||||
OAuth 2.0 Authorization Server Metadata (RFC 8414)
|
||||
|
||||
Returns OAuth 2.0 authorization server endpoints and capabilities.
|
||||
|
||||
## Response `200`
|
||||
|
||||
Authorization Server Metadata
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
15
docs/api/routes/.well-known/oauth-protected-resource.md
Normal file
15
docs/api/routes/.well-known/oauth-protected-resource.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# `GET /.well-known/oauth-protected-resource`
|
||||
|
||||
Public. No cookie required.
|
||||
|
||||
OAuth 2.0 Protected Resource Metadata (RFC 9728)
|
||||
|
||||
Returns protected resource metadata discovering the authorization server for the MCP endpoint.
|
||||
|
||||
## Response `200`
|
||||
|
||||
Protected Resource Metadata
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
35
docs/api/routes/account.get.md
Normal file
35
docs/api/routes/account.get.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# `GET /account`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List accounts
|
||||
|
||||
Lists payment accounts for the selected company.
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of accounts
|
||||
|
||||
```json
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"id": 1,
|
||||
"companyId": 1,
|
||||
"type": "…",
|
||||
"name": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 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`._
|
||||
48
docs/api/routes/account.post.md
Normal file
48
docs/api/routes/account.post.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# `POST /account`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create account
|
||||
|
||||
Creates a payment account for the selected company.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `type` | yes | string | |
|
||||
| `name` | yes | string | |
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "…",
|
||||
"name": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created account
|
||||
|
||||
```json
|
||||
{
|
||||
"account": {
|
||||
"id": 1,
|
||||
"companyId": 1,
|
||||
"type": "…",
|
||||
"name": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Validation error or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
32
docs/api/routes/account/_id.md
Normal file
32
docs/api/routes/account/_id.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# `DELETE /account/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete account
|
||||
|
||||
Deletes an account of the selected company. Its payments are removed as well.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Account ID |
|
||||
|
||||
## Response `204`
|
||||
|
||||
Account deleted
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Account not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
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`._
|
||||
42
docs/api/routes/article/_sku/articlelist.get.md
Normal file
42
docs/api/routes/article/_sku/articlelist.get.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# `GET /article/{sku}/articlelist`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Get Stückliste (bundle items) for an article
|
||||
|
||||
Returns the list of component items for a Stückliste article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of bundle items
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"count": 1,
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 404 | Article not found |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
66
docs/api/routes/article/_sku/articlelist.put.md
Normal file
66
docs/api/routes/article/_sku/articlelist.put.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# `PUT /article/{sku}/articlelist`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Set Stückliste (bundle items) for an article
|
||||
|
||||
Configures the article as a Stückliste covering a list of count * article. A parent article cannot be a Stückliste. Can be combined with options. Pass an empty array to remove the Stückliste.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `items` | yes | object[] | |
|
||||
| `items[].articleId` | no | integer | |
|
||||
| `items[].sku` | no | string | |
|
||||
| `items[].count` | no | integer | |
|
||||
| `items[].sort` | no | integer | |
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"count": 1,
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated Stückliste items
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"count": 1,
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Article is a parent, has options, or contains itself |
|
||||
| 404 | Article or component article not found |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
38
docs/api/routes/article/_sku/attributevalue.get.md
Normal file
38
docs/api/routes/article/_sku/attributevalue.get.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# `GET /article/{sku}/attributevalue`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List article attribute values
|
||||
|
||||
Fetches all attribute values assigned to an article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of attribute values assigned to this article
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalues": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"attributeId": 1,
|
||||
"attributeName": "…",
|
||||
"exclusive": true,
|
||||
"attributeValueId": 1,
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
46
docs/api/routes/article/_sku/attributevalue.put.md
Normal file
46
docs/api/routes/article/_sku/attributevalue.put.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# `PUT /article/{sku}/attributevalue`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Bulk set article attribute values
|
||||
|
||||
Replaces all attribute values for an article. Enforces exclusive constraint (max one value per exclusive attribute).
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
```json
|
||||
[
|
||||
166
|
||||
]
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated article attribute values
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalues": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"attributeId": 1,
|
||||
"attributeName": "…",
|
||||
"exclusive": true,
|
||||
"attributeValueId": 1,
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
42
docs/api/routes/article/_sku/children.get.md
Normal file
42
docs/api/routes/article/_sku/children.get.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# `GET /article/{sku}/children`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List child articles of a parent article
|
||||
|
||||
Returns variants/children linked to this parent article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Parent article SKU |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of children
|
||||
|
||||
```json
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"id": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"parentsort": 1,
|
||||
"attributeValueId": 1,
|
||||
"attributeValue": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 404 | Article not found |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
49
docs/api/routes/article/_sku/children.post.md
Normal file
49
docs/api/routes/article/_sku/children.post.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# `POST /article/{sku}/children`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create and attach a new child article to a parent article
|
||||
|
||||
Automatically generates SKU as parentSku-nextFreeIndex and name from parent name + value. Rejects a caller-supplied sku.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Parent article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `attributeValueId` | no | integer | ID of existing attribute value for the parent's attribute |
|
||||
| `value` | no | string | String value to find or create under the parent's attribute |
|
||||
| `name` | no | string | Optional custom variant name |
|
||||
|
||||
```json
|
||||
{
|
||||
"attributeValueId": 1,
|
||||
"value": "…",
|
||||
"name": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created child article
|
||||
|
||||
```json
|
||||
{
|
||||
"sku": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Parent not configured as parent, missing value, duplicate variant, or sku was provided in body |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
62
docs/api/routes/article/_sku/children.put.md
Normal file
62
docs/api/routes/article/_sku/children.put.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# `PUT /article/{sku}/children`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Attach an existing article as a child to a parent article
|
||||
|
||||
Attaches an existing article to this parent article. Idempotent if already a child. Returns 409 if the article already belongs to another parent.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Parent article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | SKU of the existing article to attach as child |
|
||||
| `attributeValueId` | no | integer | ID of existing attribute value for the parent's attribute |
|
||||
| `value` | no | string | String value to find or create under the parent's attribute |
|
||||
| `reparent` | no | boolean | Whether to allow re-parenting if child already belongs to another parent |
|
||||
|
||||
```json
|
||||
{
|
||||
"sku": "…",
|
||||
"attributeValueId": 1,
|
||||
"value": "…",
|
||||
"reparent": true
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of children after attaching
|
||||
|
||||
```json
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"id": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"parentsort": 1,
|
||||
"attributeValueId": 1,
|
||||
"attributeValue": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Missing sku or attribute value, or invalid input |
|
||||
| 404 | Parent or child article not found |
|
||||
| 409 | Article already belongs to another parent, or variant value already exists |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
35
docs/api/routes/article/_sku/children/_childSku.md
Normal file
35
docs/api/routes/article/_sku/children/_childSku.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# `DELETE /article/{sku}/children/{childSku}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Remove a child article from a parent article
|
||||
|
||||
Unlinks the child from the parent (or deletes if deleteArticle query/body is true).
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Parent article SKU |
|
||||
| `childSku` | yes | string | Child article SKU |
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `deleteArticle` | no | boolean | If true, permanently deletes the child article (and cascades its entire descendant subtree if it is a parent) instead of just unlinking |
|
||||
|
||||
## Response `200`
|
||||
|
||||
Child removed
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"deleted": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
43
docs/api/routes/article/_sku/optionslist.get.md
Normal file
43
docs/api/routes/article/_sku/optionslist.get.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# `GET /article/{sku}/optionslist`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Get options list for an article
|
||||
|
||||
Returns the available options for an article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of options
|
||||
|
||||
```json
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"count": 1,
|
||||
"sort": 1,
|
||||
"mandated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 404 | Article not found |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
69
docs/api/routes/article/_sku/optionslist.put.md
Normal file
69
docs/api/routes/article/_sku/optionslist.put.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# `PUT /article/{sku}/optionslist`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Set options list for an article
|
||||
|
||||
Configures options for an article. Some options can be mandated (come with the article in any case). A parent article cannot have options. Can be combined with a Stückliste. Pass an empty array to remove options.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `options` | yes | object[] | |
|
||||
| `options[].articleId` | no | integer | |
|
||||
| `options[].sku` | no | string | |
|
||||
| `options[].count` | no | integer | |
|
||||
| `options[].sort` | no | integer | |
|
||||
| `options[].mandated` | no | boolean | |
|
||||
|
||||
```json
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"count": 1,
|
||||
"sort": 1,
|
||||
"mandated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated options list
|
||||
|
||||
```json
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"count": 1,
|
||||
"sort": 1,
|
||||
"mandated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Article is a parent, a Stückliste, or contains itself |
|
||||
| 404 | Article or option article not found |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
57
docs/api/routes/article/_sku/parent.md
Normal file
57
docs/api/routes/article/_sku/parent.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# `POST /article/{sku}/parent`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Toggle parent mode for an article
|
||||
|
||||
Enables or disables parent article status. Enabling requires specifying an
|
||||
attributeNameId; disabling requires the article to have zero children.
|
||||
|
||||
`attributeNameId` is **conditionally required**: it is optional in the schema
|
||||
because it is only required when `isParent` is `true`. Omitting it (or passing
|
||||
`0`/a negative value) together with `isParent: true` returns `400`
|
||||
`attributeNameId is required to become a parent article`. It must not be sent
|
||||
when `isParent` is `false`.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `isParent` | yes | boolean | True to make article a parent, false to revert to regular article |
|
||||
| `attributeNameId` | no | integer | **Conditionally required when `isParent` is `true`** (required iff isParent=true). The attributename id used for the article's variants. Omitting it while enabling parent mode returns 400. Must be omitted when `isParent` is `false`. |
|
||||
|
||||
```json
|
||||
{
|
||||
"isParent": true,
|
||||
"attributeNameId": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Response `204`
|
||||
|
||||
Updated
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid input — `attributeNameId` missing while `isParent` is true, article still
|
||||
has children (disabling), or the attribute is already used by an ancestor parent.
|
||||
|
|
||||
| 404 | Article or attribute name not found |
|
||||
| 409 | Article state conflicts with becoming a variant parent — it still has stock,
|
||||
assigned suppliers, a Stückliste, or options. Clear these first.
|
||||
|
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
51
docs/api/routes/article/_sku/print.md
Normal file
51
docs/api/routes/article/_sku/print.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# `POST /article/{sku}/print`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Print article label
|
||||
|
||||
Prints a label for the article using a label template.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `templateId` | yes | integer | |
|
||||
| `agentprinterId` | yes | integer | |
|
||||
| `quantity` | yes | integer | |
|
||||
|
||||
```json
|
||||
{
|
||||
"templateId": 1,
|
||||
"agentprinterId": 1,
|
||||
"quantity": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Print job dispatched
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid parameters |
|
||||
| 404 | Printer, Template or Article not found |
|
||||
| 500 | Server error |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
37
docs/api/routes/article/_sku/property.get.md
Normal file
37
docs/api/routes/article/_sku/property.get.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# `GET /article/{sku}/property`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List article properties
|
||||
|
||||
Fetches all free-form properties set on an article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of properties for this article
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"propertyId": 1,
|
||||
"name": "…",
|
||||
"exclusive": true,
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
58
docs/api/routes/article/_sku/property.put.md
Normal file
58
docs/api/routes/article/_sku/property.put.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# `PUT /article/{sku}/property`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Bulk set article properties
|
||||
|
||||
Replaces all properties for an article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `properties` | yes | object[] | |
|
||||
| `properties[].propertyId` | yes | integer | |
|
||||
| `properties[].value` | yes | string | |
|
||||
| `properties[].sort` | no | integer | |
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"propertyId": 1,
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated article properties
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"propertyId": 1,
|
||||
"name": "…",
|
||||
"exclusive": true,
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
28
docs/api/routes/article/_sku/property/_propertyId.md
Normal file
28
docs/api/routes/article/_sku/property/_propertyId.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# `DELETE /article/{sku}/property/{propertyId}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete article property
|
||||
|
||||
Removes a single property from an article.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | |
|
||||
| `propertyId` | yes | integer | |
|
||||
|
||||
## Response `200`
|
||||
|
||||
Deleted successfully
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
49
docs/api/routes/article/_sku/track_stock.md
Normal file
49
docs/api/routes/article/_sku/track_stock.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# `PUT /article/{sku}/track_stock`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Set article track_stock flag by SKU
|
||||
|
||||
Enables or disables inventory stock tracking for this article.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `track_stock` | yes | boolean | Whether to track inventory stock for this article |
|
||||
|
||||
```json
|
||||
{
|
||||
"track_stock": true
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `204`
|
||||
|
||||
Updated
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid track_stock — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Article not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -4,7 +4,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Add article image
|
||||
|
||||
Accepts a full-size AVIF body (`Content-Type: image/avif`).
|
||||
Accepts a full-size AVIF body (`Content-Type: image/avif`) or WebP body (`Content-Type: image/webp`).
|
||||
WebP images are automatically converted to AVIF on the server.
|
||||
Full: stored as-is when already AVIF and ≤1000×1000; otherwise scaled to max 1000×1000.
|
||||
Thumb always scaled to max 200×200. Stores hash/thumbhash, links `articlepic`.
|
||||
Auth like GET by SKU.
|
||||
@@ -20,6 +21,8 @@ Auth like GET by SKU.
|
||||
|
||||
Raw binary (`image/avif`).
|
||||
|
||||
Raw binary (`image/webp`).
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
@@ -2,15 +2,23 @@
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create article
|
||||
Create article in category
|
||||
|
||||
Creates an article under an authorized category (`parentId`).
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `categoryId` | yes | integer | Category ID under which to create the article |
|
||||
| `name` | yes | string | |
|
||||
| `desc` | no | string | Article description text/HTML |
|
||||
| `seo` | no | string | SEO text |
|
||||
| `track_stock` | no | boolean | Whether to track inventory stock (defaults to false) |
|
||||
|
||||
```json
|
||||
{
|
||||
"parentId": 10,
|
||||
"categoryId": 10,
|
||||
"name": "New article"
|
||||
}
|
||||
```
|
||||
@@ -25,13 +33,8 @@ Created
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {
|
||||
"id": 1,
|
||||
"parentId": 1,
|
||||
"name": "…",
|
||||
"sku": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
@@ -42,6 +45,7 @@ Created
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized for parent category — `{"error":"…"}` |
|
||||
| 404 | Parent not found — `{"error":"…"}` |
|
||||
| 409 | SKU already exists — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ Auth like GET by SKU.
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `cascade` | no | boolean | If true, recursively deletes all descendant child articles in the subtree |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `articles` (see [`GET /events`](../events.md)).
|
||||
|
||||
@@ -18,7 +18,91 @@ Article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
"article": {
|
||||
"id": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"slug": "…",
|
||||
"desc": "…",
|
||||
"seo": "…",
|
||||
"track_stock": true,
|
||||
"unitamount": 1,
|
||||
"weight": 1,
|
||||
"unit": {
|
||||
"si": "…",
|
||||
"basesi": "…",
|
||||
"basesifactor": 1,
|
||||
"symbol": "…",
|
||||
"name": "…"
|
||||
},
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
},
|
||||
"categoryIds": [
|
||||
1
|
||||
],
|
||||
"photos": [
|
||||
{
|
||||
"sort": 1,
|
||||
"fullHash": "…",
|
||||
"fullW": 1,
|
||||
"fullH": 1,
|
||||
"fullSize": 1,
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
],
|
||||
"eans": [
|
||||
"…"
|
||||
],
|
||||
"prices": [
|
||||
{
|
||||
"minmultiplier": 1,
|
||||
"net": 1,
|
||||
"taxRate": 1,
|
||||
"customergroupId": 1
|
||||
}
|
||||
],
|
||||
"suppliers": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"entityId": 1,
|
||||
"supplierName": "…",
|
||||
"sku": "…",
|
||||
"pu": 1,
|
||||
"stock": 1,
|
||||
"sort": 1,
|
||||
"priceId": 1,
|
||||
"price": {
|
||||
"net": 1,
|
||||
"taxId": 1,
|
||||
"taxRate": 1
|
||||
},
|
||||
"incoming": [
|
||||
{
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"amount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"undeliveredOrders": [
|
||||
{
|
||||
"orderId": 1,
|
||||
"orderNumber": "…",
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"customerName": "…",
|
||||
"amount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
52
docs/api/routes/article/history-list.md
Normal file
52
docs/api/routes/article/history-list.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# `GET /article/history`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Query company-wide article history
|
||||
|
||||
Queries article change history across all articles for the authenticated user's selected company.
|
||||
Supports pagination, search filter, and date cutoff via `seekMonths`.
|
||||
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `offset` | no | integer | Paging offset |
|
||||
| `limit` | no | integer | Page size |
|
||||
| `search` | no | string | Optional search filter across SKU, article name, and history text |
|
||||
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -13) |
|
||||
|
||||
## Response `200`
|
||||
|
||||
Paged article history list
|
||||
|
||||
```json
|
||||
{
|
||||
"history": [
|
||||
{
|
||||
"id": 1,
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"historytext": "…"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"unfilteredTotal": 1,
|
||||
"offset": 1,
|
||||
"limit": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Validation error or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -5,6 +5,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
Get article history by SKU
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -14,9 +15,18 @@ Auth like GET by SKU.
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | yes | string | Article SKU |
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `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`
|
||||
|
||||
Article history (newest first)
|
||||
Paged article history list
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -31,7 +41,11 @@ Article history (newest first)
|
||||
"date": "2026-08-01T09:00:00.000Z",
|
||||
"text": "Name geändert von \"\" auf \"Tee\""
|
||||
}
|
||||
]
|
||||
],
|
||||
"total": 2,
|
||||
"unfilteredTotal": 2,
|
||||
"offset": 0,
|
||||
"limit": 50
|
||||
}
|
||||
```
|
||||
|
||||
@@ -39,7 +53,7 @@ Article history (newest first)
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Missing sku — `{"error":"…"}` |
|
||||
| 400 | Missing sku or invalid query parameters — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Article not found — `{"error":"…"}` |
|
||||
|
||||
@@ -16,11 +16,15 @@ Sets `articlepic.sort` to `0..n-1` in the given order.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `fullHashes` | yes | string[] | |
|
||||
|
||||
```json
|
||||
{
|
||||
"fullHashes": [
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
"aB1cD2eF",
|
||||
"gH3iJ4kL"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Set or clear article brand by SKU
|
||||
Set or clear article brand by SKU (null to clear)
|
||||
|
||||
Sets the article's `brandId` (must belong to the article's company).
|
||||
Pass `null` to clear the brand. Returns the same shape as GET by SKU.
|
||||
@@ -16,6 +16,10 @@ Pass `null` to clear the brand. Returns the same shape as GET by SKU.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `brandId` | yes | integer | Brand ID to assign, or null to remove the brand |
|
||||
|
||||
```json
|
||||
{
|
||||
"brandId": 3
|
||||
@@ -26,15 +30,11 @@ Pass `null` to clear the brand. Returns the same shape as GET by SKU.
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
## Response `204`
|
||||
|
||||
Updated article detail
|
||||
Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
Replace article categories by SKU
|
||||
|
||||
Replace-all the article's category list. Each category must belong to
|
||||
the article's company; ids must be unique. Empty array removes the
|
||||
article from all categories. Returns the same shape as GET by SKU.
|
||||
the article's company; ids must be unique. The article must belong to
|
||||
at least one category (empty array is rejected). Returns the same shape as GET by SKU.
|
||||
|
||||
|
||||
## Path parameters
|
||||
@@ -17,6 +17,10 @@ article from all categories. Returns the same shape as GET by SKU.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `categoryIds` | yes | integer[] | |
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryIds": [
|
||||
@@ -30,21 +34,17 @@ article from all categories. Returns the same shape as GET by SKU.
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
## Response `204`
|
||||
|
||||
Updated article detail
|
||||
Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid categoryIds — `{"error":"…"}` |
|
||||
| 400 | Invalid categoryIds or empty array — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Article not found — `{"error":"…"}` |
|
||||
|
||||
@@ -16,6 +16,10 @@ empty array clears all EANs. Returns the same shape as GET by SKU.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `eans` | yes | string[] | |
|
||||
|
||||
```json
|
||||
{
|
||||
"eans": [
|
||||
@@ -28,15 +32,11 @@ empty array clears all EANs. Returns the same shape as GET by SKU.
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
## Response `204`
|
||||
|
||||
Updated article detail
|
||||
Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
@@ -46,6 +46,7 @@ Updated article detail
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Article not found — `{"error":"…"}` |
|
||||
| 409 | EAN already in use — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Replace POS prices by SKU
|
||||
Replace POS prices by SKU (net > gross)
|
||||
|
||||
Replace-all prices for the company POS customer group.
|
||||
Each entry: `price` (net), `tax` (rate, ensured via tax.ensure), optional `minmultiplier` (default 1).
|
||||
`net` takes precedence over `gross`.
|
||||
`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)`.
|
||||
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.
|
||||
|
||||
|
||||
@@ -17,16 +20,24 @@ Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `prices` | yes | object[] | |
|
||||
| `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[].taxRate` | no | number | Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%). |
|
||||
| `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request. |
|
||||
|
||||
```json
|
||||
{
|
||||
"prices": [
|
||||
{
|
||||
"price": 10.5,
|
||||
"tax": 19
|
||||
"net": 10.5,
|
||||
"taxRate": 19
|
||||
},
|
||||
{
|
||||
"price": 9.9,
|
||||
"tax": 19,
|
||||
"gross": 11.9,
|
||||
"taxRate": 19,
|
||||
"minmultiplier": 10
|
||||
}
|
||||
]
|
||||
@@ -37,21 +48,17 @@ Empty `prices` clears all POS prices. Returns the same shape as GET by SKU.
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
## Response `204`
|
||||
|
||||
Updated article detail
|
||||
Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid prices or POS customer group required — `{"error":"…"}` |
|
||||
| 400 | Invalid prices, POS customer group required, or parent article cannot have prices — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Article not found — `{"error":"…"}` |
|
||||
|
||||
@@ -16,6 +16,10 @@ Returns the same shape as GET by SKU.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `weight` | yes | number | |
|
||||
|
||||
```json
|
||||
{
|
||||
"weight": 1.5
|
||||
@@ -26,15 +30,11 @@ Returns the same shape as GET by SKU.
|
||||
|
||||
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
## Response `204`
|
||||
|
||||
Updated article detail
|
||||
Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
|
||||
@@ -15,6 +15,17 @@ Assigns a supplier to an article with optional purchase price, stock, and suppli
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `entityId` | yes | integer | Supplier entity ID (in Lieferanten group) |
|
||||
| `sku` | no | string | Supplier's item number |
|
||||
| `pu` | no | number | Packaging unit (VPE) |
|
||||
| `stock` | no | number | Supplier stock |
|
||||
| `sort` | no | integer | Display sort order (defaults to next sort) |
|
||||
| `netPrice` | no | number | Net purchase price |
|
||||
| `taxId` | no | integer | Tax ID for purchase price |
|
||||
| `taxRate` | no | number | Tax rate in percent |
|
||||
|
||||
```json
|
||||
{
|
||||
"entityId": 88,
|
||||
@@ -51,7 +62,13 @@ Created supplier assignment
|
||||
"net": 1,
|
||||
"taxId": 1,
|
||||
"taxRate": 1
|
||||
},
|
||||
"incoming": [
|
||||
{
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"amount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -33,7 +33,13 @@ List of suppliers linked to the article
|
||||
"net": 1,
|
||||
"taxId": 1,
|
||||
"taxRate": 1
|
||||
},
|
||||
"incoming": [
|
||||
{
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"amount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ Expects an array of all assigned `articlesupplier` IDs in the desired order.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `ids` | yes | integer[] | Full list of articlesupplier ids in their new order |
|
||||
|
||||
```json
|
||||
{
|
||||
"ids": [
|
||||
|
||||
@@ -16,6 +16,16 @@ Updates an existing article-supplier association (supplier SKU, packaging unit,
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `sku` | no | string | |
|
||||
| `pu` | no | number | |
|
||||
| `stock` | no | number | |
|
||||
| `sort` | no | integer | Display sort order |
|
||||
| `netPrice` | no | number | |
|
||||
| `taxId` | no | integer | |
|
||||
| `taxRate` | no | number | |
|
||||
|
||||
```json
|
||||
{
|
||||
"sku": "MET-44910-B",
|
||||
@@ -49,7 +59,13 @@ Updated supplier assignment
|
||||
"net": 1,
|
||||
"taxId": 1,
|
||||
"taxRate": 1
|
||||
},
|
||||
"incoming": [
|
||||
{
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"amount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -16,6 +16,15 @@ Patches article fields. At least one of `name`, `unitamount`, `unitSi` required.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | no | string | |
|
||||
| `desc` | no | string | Article description text/HTML |
|
||||
| `seo` | no | string | SEO text |
|
||||
| `unitamount` | no | number | Must be finite and `> 0` |
|
||||
| `unitSi` | no | string | Existing company unit `si` |
|
||||
| `track_stock` | no | boolean | Whether to track inventory stock |
|
||||
|
||||
```json
|
||||
{
|
||||
"unitamount": 0.24,
|
||||
@@ -27,15 +36,11 @@ Patches article fields. At least one of `name`, `unitamount`, `unitSi` required.
|
||||
|
||||
On success, emits SSE signal: `articles` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
## Response `204`
|
||||
|
||||
Updated article detail
|
||||
Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
|
||||
38
docs/api/routes/attributename.get.md
Normal file
38
docs/api/routes/attributename.get.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# `GET /attributename`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List attribute names
|
||||
|
||||
Lists all attribute names defined for the selected company, including nested values. Each attribute name includes `articleCount` (the number of parent articles configured with this attribute) and each value includes `articleCount` (the number of articles having this attribute value).
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of attribute names
|
||||
|
||||
```json
|
||||
{
|
||||
"attributenames": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"exclusive": true,
|
||||
"articleCount": 1,
|
||||
"values": [
|
||||
{
|
||||
"id": 1,
|
||||
"attributeId": 1,
|
||||
"value": "…",
|
||||
"sort": 1,
|
||||
"articleCount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
56
docs/api/routes/attributename.post.md
Normal file
56
docs/api/routes/attributename.post.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# `POST /attributename`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create attribute name
|
||||
|
||||
Creates an attribute name for the selected company.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | yes | string | |
|
||||
| `sort` | no | integer | |
|
||||
| `exclusive` | no | boolean | |
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"exclusive": true
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `attributename` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created attribute name
|
||||
|
||||
```json
|
||||
{
|
||||
"attributename": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"exclusive": true,
|
||||
"articleCount": 1,
|
||||
"values": [
|
||||
{
|
||||
"id": 1,
|
||||
"attributeId": 1,
|
||||
"value": "…",
|
||||
"sort": 1,
|
||||
"articleCount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
35
docs/api/routes/attributename/_attributeId/value.get.md
Normal file
35
docs/api/routes/attributename/_attributeId/value.get.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# `GET /attributename/{attributeId}/value`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List values for an attribute
|
||||
|
||||
Lists all values for a specific attribute. Each value includes `articleCount` (the number of articles having this attribute value).
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `attributeId` | yes | integer | |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of attribute values
|
||||
|
||||
```json
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"id": 1,
|
||||
"attributeId": 1,
|
||||
"value": "…",
|
||||
"sort": 1,
|
||||
"articleCount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
47
docs/api/routes/attributename/_attributeId/value.post.md
Normal file
47
docs/api/routes/attributename/_attributeId/value.post.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# `POST /attributename/{attributeId}/value`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create value for an attribute
|
||||
|
||||
Adds a new predefined value to an attribute.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `attributeId` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `value` | yes | string | |
|
||||
| `sort` | no | integer | |
|
||||
|
||||
```json
|
||||
{
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created attribute value
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalue": {
|
||||
"id": 1,
|
||||
"attributeId": 1,
|
||||
"value": "…",
|
||||
"sort": 1,
|
||||
"articleCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
41
docs/api/routes/attributename/_attributeId/value/order.md
Normal file
41
docs/api/routes/attributename/_attributeId/value/order.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# `PUT /attributename/{attributeId}/value/order`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Reorder values of an attribute
|
||||
|
||||
Updates the display sort order of values belonging to an attribute.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `attributeId` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `attributevalueIds` | yes | integer[] | |
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalueIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Reordered successfully
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
42
docs/api/routes/attributename/_id.delete.md
Normal file
42
docs/api/routes/attributename/_id.delete.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# `DELETE /attributename/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete attribute name
|
||||
|
||||
Deletes an attribute name and cascades to attribute values and article assignments.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `attributename` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Deleted successfully
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Bad request or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Forbidden — `{"error":"…"}` |
|
||||
| 404 | Attribute name not found — `{"error":"…"}` |
|
||||
| 409 | Attribute name in use — `{"error":"…"}` |
|
||||
| 500 | Server error — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
62
docs/api/routes/attributename/_id.patch.md
Normal file
62
docs/api/routes/attributename/_id.patch.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# `PATCH /attributename/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Update attribute name
|
||||
|
||||
Updates name, sort, and/or exclusive flag for an attribute name.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | no | string | |
|
||||
| `sort` | no | integer | |
|
||||
| `exclusive` | no | boolean | |
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"exclusive": true
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `attributename` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated attribute name
|
||||
|
||||
```json
|
||||
{
|
||||
"attributename": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"exclusive": true,
|
||||
"articleCount": 1,
|
||||
"values": [
|
||||
{
|
||||
"id": 1,
|
||||
"attributeId": 1,
|
||||
"value": "…",
|
||||
"sort": 1,
|
||||
"articleCount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
39
docs/api/routes/attributename/order.md
Normal file
39
docs/api/routes/attributename/order.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# `PUT /attributename/order`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Reorder attribute names
|
||||
|
||||
Updates the display sort order of attribute names for the selected company.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `attributenameIds` | yes | integer[] | |
|
||||
|
||||
```json
|
||||
{
|
||||
"attributenameIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `attributename` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Reordered successfully
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
38
docs/api/routes/attributevalue/_id.delete.md
Normal file
38
docs/api/routes/attributevalue/_id.delete.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# `DELETE /attributevalue/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete attribute value
|
||||
|
||||
Deletes an attribute value and cascades to article assignments.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Response `200`
|
||||
|
||||
Deleted successfully
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Bad request or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Forbidden — `{"error":"…"}` |
|
||||
| 404 | Attribute value not found — `{"error":"…"}` |
|
||||
| 409 | Attribute value in use — `{"error":"…"}` |
|
||||
| 500 | Server error — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
47
docs/api/routes/attributevalue/_id.patch.md
Normal file
47
docs/api/routes/attributevalue/_id.patch.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# `PATCH /attributevalue/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Update attribute value
|
||||
|
||||
Updates value text and/or sort for an attribute value.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `value` | no | string | |
|
||||
| `sort` | no | integer | |
|
||||
|
||||
```json
|
||||
{
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated attribute value
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalue": {
|
||||
"id": 1,
|
||||
"attributeId": 1,
|
||||
"value": "…",
|
||||
"sort": 1,
|
||||
"articleCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
53
docs/api/routes/attributevalue/_id/image.delete.md
Normal file
53
docs/api/routes/attributevalue/_id/image.delete.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# `DELETE /attributevalue/{id}/image`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete attribute value image
|
||||
|
||||
Removes the attribute value's image. Sets thumbW, thumbH, thumbHash back to null. Emits SSE signal `attributename`.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Attribute value id |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `attributename` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Image removed
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalue": {
|
||||
"id": 1,
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…",
|
||||
"pics": [
|
||||
{
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected or invalid id — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Attribute value or image not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
64
docs/api/routes/attributevalue/_id/image.post.md
Normal file
64
docs/api/routes/attributevalue/_id/image.post.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# `POST /attributevalue/{id}/image`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Add or replace attribute value image
|
||||
|
||||
Uploads an image for the attribute value (AVIF, WebP, or octet-stream). Converted to AVIF full and thumbnail.
|
||||
Replaces any existing image for this attribute value. Emits SSE signal `attributename`.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Attribute value id |
|
||||
|
||||
## Body
|
||||
|
||||
Raw binary (`image/avif`).
|
||||
|
||||
Raw binary (`image/webp`).
|
||||
|
||||
Raw binary (`application/octet-stream`).
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `attributename` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Image stored and linked
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalue": {
|
||||
"id": 1,
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…",
|
||||
"pics": [
|
||||
{
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Missing body or invalid image — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Attribute value not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -4,13 +4,18 @@ Public. No cookie required.
|
||||
|
||||
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.
|
||||
`user.picture` is the profile image as base64 (same as GET /auth/me).
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `idToken` | yes | string | Google ID token from Google Identity Services |
|
||||
|
||||
```json
|
||||
{
|
||||
"idToken": "<google-id-token>"
|
||||
@@ -39,7 +44,7 @@ Login successful; `token` cookie set
|
||||
|--------|-------------|
|
||||
| 400 | Missing token or Google client not configured — `{"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":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
@@ -6,7 +6,7 @@ Current user
|
||||
|
||||
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.
|
||||
Unauthenticated calls still receive `googleClientId` with 401.
|
||||
Unauthenticated calls still receive `googleClientId` and `schemaVersion` with 401.
|
||||
|
||||
|
||||
## Response `200`
|
||||
@@ -21,7 +21,10 @@ Authenticated
|
||||
"selectedCompanyId": 1,
|
||||
"picture": "…"
|
||||
},
|
||||
"googleClientId": "…"
|
||||
"googleClientId": "…",
|
||||
"schemaVersion": 1,
|
||||
"vhost": true,
|
||||
"vhostSettings": "…"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -29,7 +32,7 @@ Authenticated
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 401 | Unauthorized — still includes `googleClientId` for the frontend |
|
||||
| 401 | Unauthorized — still includes `googleClientId` and `schemaVersion` for the frontend |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -8,12 +8,20 @@ Sets the authenticated user's selected company.
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `companyId` | yes | integer | |
|
||||
|
||||
```json
|
||||
{
|
||||
"companyId": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated user
|
||||
@@ -35,6 +43,7 @@ Updated user
|
||||
|--------|-------------|
|
||||
| 400 | Invalid companyId — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (the user is not a member of this company) — `{"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`._
|
||||
254
docs/api/routes/backup.md
Normal file
254
docs/api/routes/backup.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# `GET /backup`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List backups and service status
|
||||
|
||||
Retrieves the current status of the internal backup service and lists available hourly, daily, and monthly backups.
|
||||
|
||||
## Response `200`
|
||||
|
||||
Backup service status and listing
|
||||
|
||||
```json
|
||||
{
|
||||
"status": {
|
||||
"enabled": true,
|
||||
"isRunning": true,
|
||||
"dbProvider": "…",
|
||||
"mssqlWarning": true,
|
||||
"backupDir": "…",
|
||||
"retention": {
|
||||
"hourlyMax": 1,
|
||||
"dailyMax": 1,
|
||||
"monthly": "…"
|
||||
},
|
||||
"counts": {
|
||||
"hourly": 1,
|
||||
"daily": 1,
|
||||
"monthly": 1
|
||||
},
|
||||
"latestHourly": {
|
||||
"name": "…",
|
||||
"path": "…",
|
||||
"totalSizeBytes": 1,
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"manifest": {
|
||||
"id": "…",
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"dateKey": "…",
|
||||
"monthKey": "…",
|
||||
"dbProvider": "…",
|
||||
"files": {
|
||||
"vfs": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"storage": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"database": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"backedUp": true,
|
||||
"reason": "…"
|
||||
}
|
||||
},
|
||||
"totalSizeBytes": 1,
|
||||
"durationMs": 1
|
||||
}
|
||||
},
|
||||
"latestDaily": {
|
||||
"name": "…",
|
||||
"path": "…",
|
||||
"totalSizeBytes": 1,
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"manifest": {
|
||||
"id": "…",
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"dateKey": "…",
|
||||
"monthKey": "…",
|
||||
"dbProvider": "…",
|
||||
"files": {
|
||||
"vfs": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"storage": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"database": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"backedUp": true,
|
||||
"reason": "…"
|
||||
}
|
||||
},
|
||||
"totalSizeBytes": 1,
|
||||
"durationMs": 1
|
||||
}
|
||||
},
|
||||
"latestMonthly": {
|
||||
"name": "…",
|
||||
"path": "…",
|
||||
"totalSizeBytes": 1,
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"manifest": {
|
||||
"id": "…",
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"dateKey": "…",
|
||||
"monthKey": "…",
|
||||
"dbProvider": "…",
|
||||
"files": {
|
||||
"vfs": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"storage": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"database": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"backedUp": true,
|
||||
"reason": "…"
|
||||
}
|
||||
},
|
||||
"totalSizeBytes": 1,
|
||||
"durationMs": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
"counts": {
|
||||
"hourly": 1,
|
||||
"daily": 1,
|
||||
"monthly": 1
|
||||
},
|
||||
"hourly": [
|
||||
{
|
||||
"name": "…",
|
||||
"path": "…",
|
||||
"totalSizeBytes": 1,
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"manifest": {
|
||||
"id": "…",
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"dateKey": "…",
|
||||
"monthKey": "…",
|
||||
"dbProvider": "…",
|
||||
"files": {
|
||||
"vfs": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"storage": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"database": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"backedUp": true,
|
||||
"reason": "…"
|
||||
}
|
||||
},
|
||||
"totalSizeBytes": 1,
|
||||
"durationMs": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"daily": [
|
||||
{
|
||||
"name": "…",
|
||||
"path": "…",
|
||||
"totalSizeBytes": 1,
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"manifest": {
|
||||
"id": "…",
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"dateKey": "…",
|
||||
"monthKey": "…",
|
||||
"dbProvider": "…",
|
||||
"files": {
|
||||
"vfs": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"storage": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"database": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"backedUp": true,
|
||||
"reason": "…"
|
||||
}
|
||||
},
|
||||
"totalSizeBytes": 1,
|
||||
"durationMs": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"monthly": [
|
||||
{
|
||||
"name": "…",
|
||||
"path": "…",
|
||||
"totalSizeBytes": 1,
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"manifest": {
|
||||
"id": "…",
|
||||
"timestamp": "2026-08-10T00:00:00.000Z",
|
||||
"dateKey": "…",
|
||||
"monthKey": "…",
|
||||
"dbProvider": "…",
|
||||
"files": {
|
||||
"vfs": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"storage": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"sourceExists": true
|
||||
},
|
||||
"database": {
|
||||
"name": "…",
|
||||
"sizeBytes": 1,
|
||||
"backedUp": true,
|
||||
"reason": "…"
|
||||
}
|
||||
},
|
||||
"totalSizeBytes": 1,
|
||||
"durationMs": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 500 | Internal error — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
51
docs/api/routes/backup/_id/vfs.md
Normal file
51
docs/api/routes/backup/_id/vfs.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# `GET /backup/{id}/vfs`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List VFS files in a backup
|
||||
|
||||
Lists all virtual filesystem (VFS) files stored for the authorized company inside a specific backup archive.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | string | Backup identifier (e.g. backup-2026-10-14_02-00-00, backup-2026-10-14, backup-2026-10) |
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `companyId` | no | integer | Optional company ID to inspect (defaults to user's selected company) |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of VFS files in the backup for the authorized company
|
||||
|
||||
```json
|
||||
{
|
||||
"backupId": "…",
|
||||
"companyId": 1,
|
||||
"files": [
|
||||
{
|
||||
"name": "…",
|
||||
"size": 1,
|
||||
"mime": "…",
|
||||
"mtime": "2026-08-10T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected or invalid companyId — `{"error":"…"}` |
|
||||
| 403 | Not authorized for the requested company — `{"error":"…"}` |
|
||||
| 404 | Backup or VFS archive not found — `{"error":"…"}` |
|
||||
| 500 | Internal server error — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
55
docs/api/routes/backup/_id/vfs/recover.md
Normal file
55
docs/api/routes/backup/_id/vfs/recover.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# `POST /backup/{id}/vfs/recover`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Recover VFS file from a backup
|
||||
|
||||
Recovers a specific virtual filesystem (VFS) file from a backup archive into the active VFS directory for the authorized company.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | string | Backup identifier (e.g. backup-2026-10-14_02-00-00, backup-2026-10-14, backup-2026-10) |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `path` | yes | string | Relative path of the VFS file to recover |
|
||||
| `companyId` | no | integer | Optional company ID (defaults to user's selected company) |
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "…",
|
||||
"companyId": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
VFS file recovered successfully
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"backupId": "…",
|
||||
"companyId": 1,
|
||||
"path": "…",
|
||||
"sizeBytes": 1,
|
||||
"mime": "…"
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Validation error or path escapes boundary — `{"error":"…"}` |
|
||||
| 403 | Not authorized for the company — `{"error":"…"}` |
|
||||
| 404 | Backup or file not found — `{"error":"…"}` |
|
||||
| 500 | Internal server error — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
53
docs/api/routes/brand/_id/image.delete.md
Normal file
53
docs/api/routes/brand/_id/image.delete.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# `DELETE /brand/{id}/image`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete brand image
|
||||
|
||||
Removes the brand's image. Sets thumbW, thumbH, thumbHash back to null. Emits SSE signal `brand` with payload `{ sender, brandId }`.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Brand id |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Image removed
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…",
|
||||
"pics": [
|
||||
{
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected or invalid id — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Brand or image not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
65
docs/api/routes/brand/_id/image.post.md
Normal file
65
docs/api/routes/brand/_id/image.post.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# `POST /brand/{id}/image`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Add or replace brand image
|
||||
|
||||
Uploads an image for the brand (AVIF, WebP, or octet-stream). Converted to AVIF full and thumbnail.
|
||||
Replaces any existing image for this brand. Emits SSE signal `brand`
|
||||
with payload `{ sender, brandId }`.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Brand id |
|
||||
|
||||
## Body
|
||||
|
||||
Raw binary (`image/avif`).
|
||||
|
||||
Raw binary (`image/webp`).
|
||||
|
||||
Raw binary (`application/octet-stream`).
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Image stored and linked
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…",
|
||||
"pics": [
|
||||
{
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Missing body or invalid image — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Brand not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
64
docs/api/routes/brand/create.md
Normal file
64
docs/api/routes/brand/create.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# `POST /brand`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create brand
|
||||
|
||||
Creates a brand for the selected company. Fails with `Brand already exists`
|
||||
if the name is taken. Emits SSE signal `brand` with payload
|
||||
`{ sender, brandId }` (`sender` echoes the caller's `x-client-id`).
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | yes | string | |
|
||||
| `gsprpinfo` | no | string | |
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Acme",
|
||||
"gsprpinfo": "Acme GmbH"
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created brand
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"gsprpinfo": "…",
|
||||
"pics": [
|
||||
{
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid brand or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
| 409 | Brand already exists — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -5,7 +5,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
Delete brand
|
||||
|
||||
Deletes the brand for the selected company. Fails with `Brand in use`
|
||||
if any article still references it.
|
||||
if any article still references it. Emits SSE signal `brand`
|
||||
with payload `{ sender, brandId }`.
|
||||
|
||||
|
||||
## Path parameters
|
||||
@@ -14,6 +15,10 @@ if any article still references it.
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `204`
|
||||
|
||||
Deleted
|
||||
|
||||
@@ -4,7 +4,15 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List brands
|
||||
|
||||
Lists brands for the authenticated user's selected company, sorted by name.
|
||||
Lists brands for the authenticated user's selected company, sorted by name. Paged; `total` is the unpaged count. Each brand includes `articleCount` (child articles inherit the brand from their father article; father articles are not counted). Small-thumbnail fields are omitted by default. Set `includeSmallThumbs=true` to include each picture's `smallThumbW`, `smallThumbH`, and `smallThumbBinary` (base64-encoded AVIF bytes, resized with a 32px maximum edge).
|
||||
|
||||
## Query parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `offset` | no | integer | Number of brands to skip |
|
||||
| `limit` | no | integer | Number of brands to return |
|
||||
| `includeSmallThumbs` | no | boolean | Include each picture's small thumbnail dimensions and base64 AVIF bytes. |
|
||||
|
||||
## Response `200`
|
||||
|
||||
@@ -16,17 +24,32 @@ Brand list
|
||||
{
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"gsprpinfo": "…"
|
||||
"articleCount": 1,
|
||||
"gsprpinfo": "…",
|
||||
"pics": [
|
||||
{
|
||||
"fullHash": "…",
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…",
|
||||
"smallThumbW": 1,
|
||||
"smallThumbH": 1,
|
||||
"smallThumbBinary": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"offset": 1,
|
||||
"limit": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected — `{"error":"…"}` |
|
||||
| 400 | Invalid offset, invalid limit, invalid includeSmallThumbs, or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
|
||||
65
docs/api/routes/brand/update.md
Normal file
65
docs/api/routes/brand/update.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# `PATCH /brand/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Rename brand
|
||||
|
||||
Renames/updates a brand by id for the selected company. Fails with
|
||||
`Brand already exists` if the name is taken by another brand.
|
||||
When the name changes, linked articles (`brand_id`) get their
|
||||
`updated_at` bumped; the response reports `touchedArticleCount`.
|
||||
Emits SSE signal `brand` with payload `{ sender, brandId }`
|
||||
(`sender` echoes the caller's `x-client-id`).
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | no | string | |
|
||||
| `gsprpinfo` | no | string | |
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Acme GmbH"
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated brand
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"gsprpinfo": "…",
|
||||
"touchedArticleCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid brand, invalid id, or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Brand or company not found — `{"error":"…"}` |
|
||||
| 409 | Brand already exists — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -1,44 +0,0 @@
|
||||
# `PUT /brand`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Upsert brand
|
||||
|
||||
Insert or update brand by `name` for the selected company. `gsprpinfo` optional.
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Acme",
|
||||
"gsprpinfo": "Acme GmbH"
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Upserted brand
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"gsprpinfo": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid brand or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -1,31 +1,24 @@
|
||||
# `GET /articleIndex`
|
||||
# `GET /carrier`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List article search index
|
||||
|
||||
Returns every article reachable via the company's category tree.
|
||||
Designed for client-side search / autocomplete preload.
|
||||
Auth via company access.
|
||||
List carriers
|
||||
|
||||
Lists carriers for the selected company, ordered by sort and name.
|
||||
|
||||
## Response `200`
|
||||
|
||||
Search index
|
||||
List of carriers
|
||||
|
||||
```json
|
||||
{
|
||||
"articles": [
|
||||
"carriers": [
|
||||
{
|
||||
"id": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"eans": [
|
||||
"…"
|
||||
],
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
"sort": 1,
|
||||
"hasPackages": true,
|
||||
"isReverse": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -38,7 +31,6 @@ Search index
|
||||
| 400 | No company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
60
docs/api/routes/carrier.post.md
Normal file
60
docs/api/routes/carrier.post.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# `POST /carrier`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create carrier
|
||||
|
||||
Creates a new carrier for the selected company.
|
||||
Sort order will be automatically appended if omitted.
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | yes | string | |
|
||||
| `sort` | no | integer | Optional sort order |
|
||||
| `hasPackages` | no | boolean | |
|
||||
| `isReverse` | no | boolean | |
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"hasPackages": true,
|
||||
"isReverse": true
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created carrier
|
||||
|
||||
```json
|
||||
{
|
||||
"carrier": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"hasPackages": true,
|
||||
"isReverse": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid input or missing fields — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 409 | Carrier already exists — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
37
docs/api/routes/carrier/_id.delete.md
Normal file
37
docs/api/routes/carrier/_id.delete.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# `DELETE /carrier/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete carrier
|
||||
|
||||
Deletes a carrier for the selected company, unless referenced in shipping.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Carrier ID |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `204`
|
||||
|
||||
Carrier deleted
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Carrier not found — `{"error":"…"}` |
|
||||
| 409 | Carrier in use in shipping — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
42
docs/api/routes/carrier/_id.get.md
Normal file
42
docs/api/routes/carrier/_id.get.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# `GET /carrier/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Get carrier by ID
|
||||
|
||||
Retrieves a single carrier for the authenticated user's selected company.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Carrier ID |
|
||||
|
||||
## Response `200`
|
||||
|
||||
Carrier details
|
||||
|
||||
```json
|
||||
{
|
||||
"carrier": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"hasPackages": true,
|
||||
"isReverse": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid ID or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Carrier not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
65
docs/api/routes/carrier/_id.patch.md
Normal file
65
docs/api/routes/carrier/_id.patch.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# `PATCH /carrier/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Update carrier
|
||||
|
||||
Updates carrier name, sort order, hasPackages, and/or isReverse for the selected company.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Carrier ID |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `name` | no | string | |
|
||||
| `sort` | no | integer | |
|
||||
| `hasPackages` | no | boolean | |
|
||||
| `isReverse` | no | boolean | |
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"hasPackages": true,
|
||||
"isReverse": true
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated carrier
|
||||
|
||||
```json
|
||||
{
|
||||
"carrier": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"sort": 1,
|
||||
"hasPackages": true,
|
||||
"isReverse": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid input or missing fields — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Carrier not found — `{"error":"…"}` |
|
||||
| 409 | Carrier already exists — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
51
docs/api/routes/carrier/order.md
Normal file
51
docs/api/routes/carrier/order.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# `PUT /carrier/order`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Reorder carriers
|
||||
|
||||
Reorders carriers for the selected company.
|
||||
`carrierIds` must include all existing carrier IDs exactly once.
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `carrierIds` | yes | integer[] | |
|
||||
|
||||
```json
|
||||
{
|
||||
"carrierIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `carrier` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Applied carrier order
|
||||
|
||||
```json
|
||||
{
|
||||
"carrierIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid carrier order or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
38
docs/api/routes/category/_id/attributevalue.get.md
Normal file
38
docs/api/routes/category/_id/attributevalue.get.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# `GET /category/{id}/attributevalue`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List category attribute values
|
||||
|
||||
Fetches all attribute values assigned to a category.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | Category ID |
|
||||
|
||||
## Response `200`
|
||||
|
||||
List of attribute values assigned to this category
|
||||
|
||||
```json
|
||||
{
|
||||
"attributevalues": [
|
||||
{
|
||||
"id": 1,
|
||||
"categoryId": 1,
|
||||
"attributeId": 1,
|
||||
"attributeName": "…",
|
||||
"exclusive": true,
|
||||
"attributeValueId": 1,
|
||||
"value": "…",
|
||||
"sort": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user