This commit is contained in:
seb
2026-10-07 21:23:10 +02:00
parent a29ad50b74
commit 7d537aaa6b
33 changed files with 598 additions and 327 deletions

View File

@@ -4,24 +4,24 @@ Overview of all 189 API endpoints across all modules.
### Notation ### Notation
Types: `str` string, `int` integer, `num` number, `bool` boolean, `obj` object; `T[]` array; `?` optional. Types: `str` string, `int` integer, `num` number, `bool` boolean, `{}` object; `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. 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 ### Reusable types
- `T1` = `{ article: T2 }` - `T1` = `{ article: T2 }`
- `T2` = `{ id: int, sku: str, name: str, slug: str, track_stock: bool, unitamount: num, weight: num, unit: obj, brand: obj, categoryIds: int[], photos: T17[], eans: str[], prices: T28[], suppliers: T9[] }` - `T2` = `{ id: int, sku: str, name: str, slug: str, track_stock: bool, unitamount: num, weight: num, unit: T28, brand: T24, categoryIds: int[], photos: T17[], eans: str[], prices: T29[], suppliers: T9[] }`
- `T3` = `{ delivery: T4 }` - `T3` = `{ delivery: T4 }`
- `T4` = `{ id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T33, lines: T18[], files: T8[] }` - `T4` = `{ id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T34, lines: T18[], files: T8[] }`
- `T5` = `{ order: T7 }` - `T5` = `{ order: T7 }`
- `T6` = `{ quote: T7 }` - `T6` = `{ quote: T7 }`
- `T7` = `{ id: int, number: str, type: str, entityId: int, date: str, createdAt: str, lines: T18[] }` - `T7` = `{ id: int, number: str, type: str, entityId: int, date: str, createdAt: str, lines: T18[] }`
- `T8` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created: str, modified: str, thumbavif: str, thumbx: int, thumby: int, meta: obj }` - `T8` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created: str, modified: str, thumbavif: str, thumbx: int, thumby: int, meta: { [k]: str } }`
- `T9` = `{ id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T36 }` - `T9` = `{ id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T37 }`
- `T10` = `{ ledger: T11[] }` - `T10` = `{ ledger: T11[] }`
- `T11` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash: str, sku: str, created_at: str }` - `T11` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash: str, sku: str, created_at: str }`
- `T12` = `{ id: int, entityId: int, firstName: str, name: str, street: str, streetNumber: str, zip: str, city: str, county: str, addressType: str }` - `T12` = `{ id: int, entityId: int, firstName: str, name: str, street: str, streetNumber: str, zip: str, city: str, county: str, addressType: str }`
- `T13` = `{ id: int, name: str, sort: int, exclusive: bool, values: T31[] }` - `T13` = `{ id: int, name: str, sort: int, exclusive: bool, values: T32[] }`
- `T14` = `{ entity: T20 }` - `T14` = `{ entity: T20 }`
- `T15` = `{ printer: T21 }` - `T15` = `{ printer: T21 }`
- `T16` = `{ labeltemplate: T23 }` - `T16` = `{ labeltemplate: T23 }`
@@ -31,22 +31,23 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- `T20` = `{ id: int, customergroupId: int, firstName: str, name: str, number: str, createdAt: str, updatedAt: str }` - `T20` = `{ id: int, customergroupId: int, firstName: str, name: str, number: str, createdAt: str, updatedAt: str }`
- `T21` = `{ id: int, company_id: int, address: str, type: str, model: str, serial: str, sort: int, desc: str }` - `T21` = `{ id: int, company_id: int, address: str, type: str, model: str, serial: str, sort: int, desc: str }`
- `T22` = `{ amount: num, net: num, taxrate: num, sku?: str, name?: str, pu?: num, rebate?: num, comment?: str }` - `T22` = `{ amount: num, net: num, taxrate: num, sku?: str, name?: str, pu?: num, rebate?: num, comment?: str }`
- `T23` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: obj, thumb: str }` - `T23` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: {}, thumb: str }`
- `T24` = `{ id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` - `T24` = `{ id: int, name: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
- `T25` = `{ article: T27 }` - `T25` = `{ article: T27 }`
- `T26` = `{ id: int, iso639: str, name: str, taxrate: num, reducedtaxrate: num }` - `T26` = `{ id: int, iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
- `T27` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` - `T27` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }`
- `T28` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` - `T28` = `{ si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
- `T29` = `{ id: int, email: str, selectedCompanyId: int, picture: str }` - `T29` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }`
- `T30` = `{ id: int, entityId: int, contactType: str, contact: str }` - `T30` = `{ id: int, email: str, selectedCompanyId: int, picture: str }`
- `T31` = `{ id: int, attributeId: int, value: str, sort: int }` - `T31` = `{ id: int, entityId: int, contactType: str, contact: str }`
- `T32` = `{ id: int, name: str, createdAt: str, updatedAt: str }` - `T32` = `{ id: int, attributeId: int, value: str, sort: int }`
- `T33` = `{ totalPositions: int, completelyOnLedgerPositions: int }` - `T33` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
- `T34` = `{ id: int, name: str, sort: int, exclusive: bool }` - `T34` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
- `T35` = `{ warehouse: T37 }` - `T35` = `{ id: int, name: str, sort: int, exclusive: bool }`
- `T36` = `{ net: num, taxId: int, taxRate: num }` - `T36` = `{ warehouse: T38 }`
- `T37` = `{ id: int, name: str, sort: int }` - `T37` = `{ net: num, taxId: int, taxRate: num }`
- `T38` = `{ id: int, categoryIds: int[] }` - `T38` = `{ id: int, name: str, sort: int }`
- `T39` = `{ id: int, categoryIds: int[] }`
### Routes ### Routes
@@ -70,7 +71,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }` - Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
#### Set Stückliste (bundle items) for an article — `PUT /article/{sku}/articlelist` #### 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 }[] }` - Params: `sku: str, body: { items: { articleId?: int, sku?: str, count?: int, sort?: int }[] }`
- Returns: `200 { items: obj[] }` - Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
#### List article attribute values — `GET /article/{sku}/attributevalue` #### List article attribute values — `GET /article/{sku}/attributevalue`
- Params: `sku: str` - Params: `sku: str`
- Returns: `200 { attributevalues: { id: int, articleId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }` - Returns: `200 { attributevalues: { id: int, articleId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }`
@@ -91,7 +92,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `201 T25` - Returns: `201 T25`
#### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children`
- Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }`
- Returns: `200 { article: T27, children: obj[] }` - Returns: `200 { article: T27, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }`
#### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}`
- Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Params: `sku: str, childSku: str, query: deleteArticle?: bool`
- Returns: `200 { success: bool, deleted: bool }` - Returns: `200 { success: bool, deleted: bool }`
@@ -115,12 +116,12 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }` - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
#### Set options list for an article — `PUT /article/{sku}/optionslist` #### 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 }[] }` - Params: `sku: str, body: { options: { articleId?: int, sku?: str, count?: int, sort?: int, mandated?: bool }[] }`
- Returns: `200 { options: obj[] }` - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
#### Toggle parent mode for an article — `POST /article/{sku}/parent` #### Toggle parent mode for an article — `POST /article/{sku}/parent`
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
- Returns: `200 T25` - Returns: `200 T25`
#### Replace POS prices by SKU — `PUT /article/{sku}/prices` #### Replace POS prices by SKU — `PUT /article/{sku}/prices`
- Params: `sku: str, body: { prices: { price: num, tax: num, minmultiplier?: num }[] }` - Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }`
- Returns: `200 T1` - Returns: `200 T1`
#### Print article label — `POST /article/{sku}/print` #### Print article label — `POST /article/{sku}/print`
- Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }` - Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }`
@@ -166,10 +167,10 @@ _none_
- Returns: `200` - Returns: `200`
#### List values for an attribute — `GET /attributename/{attributeId}/value` #### List values for an attribute — `GET /attributename/{attributeId}/value`
- Params: `attributeId: int` - Params: `attributeId: int`
- Returns: `200 { values: T31[] }` - Returns: `200 { values: T32[] }`
#### Create value for an attribute — `POST /attributename/{attributeId}/value` #### Create value for an attribute — `POST /attributename/{attributeId}/value`
- Params: `attributeId: int, body: { value: str, sort?: int }` - Params: `attributeId: int, body: { value: str, sort?: int }`
- Returns: `201 { attributevalue: T31 }` - Returns: `201 { attributevalue: T32 }`
#### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order` #### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order`
- Params: `attributeId: int, body: { attributevalueIds: int[] }` - Params: `attributeId: int, body: { attributevalueIds: int[] }`
- Returns: `200` - Returns: `200`
@@ -184,34 +185,34 @@ _none_
- Returns: `200` - Returns: `200`
#### Update attribute value — `PATCH /attributevalue/{id}` #### Update attribute value — `PATCH /attributevalue/{id}`
- Params: `id: int, body: { value?: str, sort?: int }` - Params: `id: int, body: { value?: str, sort?: int }`
- Returns: `200 { attributevalue: T31 }` - Returns: `200 { attributevalue: T32 }`
#### Login with Google ID token — `POST /auth/login` #### Login with Google ID token — `POST /auth/login`
- Params: `body: { idToken: str }` - Params: `body: { idToken: str }`
- Returns: `200 { message: str, user: T29 }` - Returns: `200 { message: str, user: T30 }`
#### Logout — `POST /auth/logout` #### Logout — `POST /auth/logout`
_none_ _none_
- Returns: `200 { message: str }` - Returns: `200 { message: str }`
#### Current user — `GET /auth/me` #### Current user — `GET /auth/me`
_none_ _none_
- Returns: `200 { user: T29, googleClientId: str }` - Returns: `200 { user: T30, googleClientId: str }`
#### Set selected company — `PUT /auth/selected-company` #### Set selected company — `PUT /auth/selected-company`
- Params: `body: { companyId: int }` - Params: `body: { companyId: int }`
- Returns: `200 { user: T29 }` - Returns: `200 { user: T30 }`
#### List brands — `GET /brand` #### List brands — `GET /brand`
_none_ _none_
- Returns: `200 { brands: { id: int, name: str, gsprpinfo: str, pics: T24[] }[] }` - Returns: `200 { brands: { id: int, name: str, gsprpinfo: str, pics: any[] }[] }`
#### Upsert brand — `PUT /brand` #### Upsert brand — `PUT /brand`
- Params: `body: { name: str, gsprpinfo?: str }` - Params: `body: { name: str, gsprpinfo?: str }`
- Returns: `200 { brand: { id: int, name: str, gsprpinfo: str, pics: T24[] } }` - Returns: `200 { brand: { id: int, name: str, gsprpinfo: str, pics: any[] } }`
#### Delete brand — `DELETE /brand/{id}` #### Delete brand — `DELETE /brand/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Delete brand image — `DELETE /brand/{id}/image` #### Delete brand image — `DELETE /brand/{id}/image`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { brand: { id: int, thumbW: int, thumbH: int, thumbHash: str, pics: T24[] } }` - Returns: `200 { brand: { id: int, thumbW: int, thumbH: int, thumbHash: str, pics: { id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] } }`
#### Add or replace brand image — `POST /brand/{id}/image` #### Add or replace brand image — `POST /brand/{id}/image`
- Params: `id: int, image/avif` - Params: `id: int, image/avif`
- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T24[] } }` - Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: { id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] } }`
#### Create category — `POST /category` #### Create category — `POST /category`
- Params: `body: { parentId: int, name: str }` - Params: `body: { parentId: int, name: str }`
- Returns: `201 { category: { id: int, parentId: int, sort: int, name: str } }` - Returns: `201 { category: { id: int, parentId: int, sort: int, name: str } }`
@@ -223,7 +224,7 @@ _none_
- Returns: `200 { categoryIds: int[] }` - Returns: `200 { categoryIds: int[] }`
#### Category tree — `GET /category/tree` #### Category tree — `GET /category/tree`
_none_ _none_
- Returns: `200 { languageId: int, tree: obj[], articles: obj[] }` - Returns: `200 { languageId: int, tree: { id: int, parentId: int, sort: int, name: str, slug: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, posterminalIds: int[] }[], articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, parentArticleId: int, parentSku: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, brand: T24 }[] }`
#### Delete category — `DELETE /category/{id}` #### Delete category — `DELETE /category/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
@@ -232,7 +233,7 @@ _none_
- Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }` - Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }`
#### Articles in category — `GET /category/{id}/articles` #### Articles in category — `GET /category/{id}/articles`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, brand: obj }[] }` - Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, parentArticleId: int, parentSku: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, brand: T24 }[] }`
#### List category attribute values — `GET /category/{id}/attributevalue` #### List category attribute values — `GET /category/{id}/attributevalue`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { attributevalues: { id: int, categoryId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }` - Returns: `200 { attributevalues: { id: int, categoryId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }`
@@ -259,7 +260,7 @@ _none_
- Returns: `200 { companies: T26[] }` - Returns: `200 { companies: T26[] }`
#### Create company — `POST /company` #### Create company — `POST /company`
- Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` - Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
- Returns: `201 { company: T26, user: T29 }` - Returns: `201 { company: T26, user: T30 }`
#### Get company history — `GET /company/history` #### Get company history — `GET /company/history`
- Params: `query: limit?: int` - Params: `query: limit?: int`
- Returns: `200 { history: { id: int, companyuserId: int, date: str, text: str }[], truncated: bool }` - Returns: `200 { history: { id: int, companyuserId: int, date: str, text: str }[], truncated: bool }`
@@ -286,7 +287,7 @@ _none_
- Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }` - Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }`
#### Create customer group — `POST /customergroup` #### Create customer group — `POST /customergroup`
- Params: `body: { name: str }` - Params: `body: { name: str }`
- Returns: `201 { customergroup: T37 }` - Returns: `201 { customergroup: T38 }`
#### Reorder customer groups — `PUT /customergroup/order` #### Reorder customer groups — `PUT /customergroup/order`
- Params: `body: { customergroupIds: int[] }` - Params: `body: { customergroupIds: int[] }`
- Returns: `200 { customergroupIds: int[] }` - Returns: `200 { customergroupIds: int[] }`
@@ -295,12 +296,12 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Update customer group — `PATCH /customergroup/{id}` #### Update customer group — `PATCH /customergroup/{id}`
- Params: `id: int, body: { name: str }` - Params: `id: int, body: { name: str }`
- Returns: `200 { customergroup: T37 }` - Returns: `200 { customergroup: T38 }`
#### List deliveries — `GET /delivery` #### List deliveries — `GET /delivery`
_none_ _none_
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T33 }[] }` - Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T34 }[] }`
#### Create delivery — `POST /delivery` #### Create delivery — `POST /delivery`
- Params: `body: { externalNumber: str, date: str, lines: T22[], supplierId?: int, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: obj }[] }` - Params: `body: { externalNumber: str, date: str, lines: T22[], supplierId?: int, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `201 T3` - Returns: `201 T3`
#### Parse document (SSE) — `POST /delivery/parse` #### Parse document (SSE) — `POST /delivery/parse`
- Params: `form: { file: str }` - Params: `form: { file: str }`
@@ -312,7 +313,7 @@ _none_
- Params: `id: int` - Params: `id: int`
- Returns: `200 T3` - Returns: `200 T3`
#### Update delivery — `PATCH /delivery/{id}` #### Update delivery — `PATCH /delivery/{id}`
- Params: `id: int, body: { externalNumber?: str, date?: str, supplierId?: int, lines?: T22[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: obj }[] }` - Params: `id: int, body: { externalNumber?: str, date?: str, supplierId?: int, lines?: T22[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `200 T3` - Returns: `200 T3`
#### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}` #### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}`
- Params: `id: int, fileId: int, query: download?: bool` - Params: `id: int, fileId: int, query: download?: bool`
@@ -340,16 +341,16 @@ _none_
- Returns: `200 { address: T12 }` - Returns: `200 { address: T12 }`
#### List entity contacts — `GET /entity/{entityId}/contact` #### List entity contacts — `GET /entity/{entityId}/contact`
- Params: `entityId: int` - Params: `entityId: int`
- Returns: `200 { contacts: T30[] }` - Returns: `200 { contacts: T31[] }`
#### Create entity contact — `POST /entity/{entityId}/contact` #### Create entity contact — `POST /entity/{entityId}/contact`
- Params: `entityId: int, body: { contactType: str, contact: str }` - Params: `entityId: int, body: { contactType: str, contact: str }`
- Returns: `201 { contact: T30 }` - Returns: `201 { contact: T31 }`
#### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}` #### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}`
- Params: `entityId: int, id: int` - Params: `entityId: int, id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update entity contact — `PATCH /entity/{entityId}/contact/{id}` #### Update entity contact — `PATCH /entity/{entityId}/contact/{id}`
- Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }` - Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }`
- Returns: `200 { contact: T30 }` - Returns: `200 { contact: T31 }`
#### Delete entity — `DELETE /entity/{id}` #### Delete entity — `DELETE /entity/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
@@ -372,7 +373,7 @@ _none_
_none_ _none_
- Returns: `200 { labeltemplates: T23[] }` - Returns: `200 { labeltemplates: T23[] }`
#### Create label template — `POST /labeltemplate` #### Create label template — `POST /labeltemplate`
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: obj }` - Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: {} }`
- Returns: `201 T16` - Returns: `201 T16`
#### Delete label template — `DELETE /labeltemplate/{id}` #### Delete label template — `DELETE /labeltemplate/{id}`
- Params: `id: int` - Params: `id: int`
@@ -381,10 +382,10 @@ _none_
- Params: `id: int` - Params: `id: int`
- Returns: `200 T16` - Returns: `200 T16`
#### Update label template (partial) — `PATCH /labeltemplate/{id}` #### Update label template (partial) — `PATCH /labeltemplate/{id}`
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: obj }` - Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: {} }`
- Returns: `200 T16` - Returns: `200 T16`
#### Update label template — `PUT /labeltemplate/{id}` #### Update label template — `PUT /labeltemplate/{id}`
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: obj }` - Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: {} }`
- Returns: `200 T16` - Returns: `200 T16`
#### Query ledger by manifest article ID or SKU — `GET /ledger` #### Query ledger by manifest article ID or SKU — `GET /ledger`
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str` - Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
@@ -451,16 +452,16 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Disable all categories on terminal — `DELETE /posterminal/{id}/all` #### Disable all categories on terminal — `DELETE /posterminal/{id}/all`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T38` - Returns: `200 T39`
#### Enable all categories on terminal — `PUT /posterminal/{id}/all` #### Enable all categories on terminal — `PUT /posterminal/{id}/all`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T38` - Returns: `200 T39`
#### Disable category on terminal — `DELETE /posterminal/{id}/category/{categoryId}` #### Disable category on terminal — `DELETE /posterminal/{id}/category/{categoryId}`
- Params: `id: int, categoryId: int` - Params: `id: int, categoryId: int`
- Returns: `200 T38` - Returns: `200 T39`
#### Enable category on terminal — `PUT /posterminal/{id}/category/{categoryId}` #### Enable category on terminal — `PUT /posterminal/{id}/category/{categoryId}`
- Params: `id: int, categoryId: int` - Params: `id: int, categoryId: int`
- Returns: `200 T38` - Returns: `200 T39`
#### List printers — `GET /printer` #### List printers — `GET /printer`
_none_ _none_
- Returns: `200 { printers: T21[] }` - Returns: `200 { printers: T21[] }`
@@ -484,10 +485,10 @@ _none_
- Returns: `200 T15` - Returns: `200 T15`
#### List property names — `GET /propertyname` #### List property names — `GET /propertyname`
_none_ _none_
- Returns: `200 { propertynames: T34[] }` - Returns: `200 { propertynames: T35[] }`
#### Create property name — `POST /propertyname` #### Create property name — `POST /propertyname`
- Params: `body: { name: str, sort?: int, exclusive?: bool }` - Params: `body: { name: str, sort?: int, exclusive?: bool }`
- Returns: `201 { propertyname: T34 }` - Returns: `201 { propertyname: T35 }`
#### Reorder property names — `PUT /propertyname/order` #### Reorder property names — `PUT /propertyname/order`
- Params: `body: { propertynameIds: int[] }` - Params: `body: { propertynameIds: int[] }`
- Returns: `200` - Returns: `200`
@@ -496,13 +497,13 @@ _none_
- Returns: `200` - Returns: `200`
#### Update property name — `PATCH /propertyname/{id}` #### Update property name — `PATCH /propertyname/{id}`
- Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }`
- Returns: `200 { propertyname: T34 }` - Returns: `200 { propertyname: T35 }`
#### List public articles — `GET /public/{companyId}/articles` #### List public articles — `GET /public/{companyId}/articles`
- Params: `companyId: int, query: categoryId?: int, search?: str, limit?: int, offset?: int` - Params: `companyId: int, query: categoryId?: int, search?: str, limit?: int, offset?: int`
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, gross: num, trackStock: bool, brand: { id: int, name: str }, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] }` - Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, gross: num, trackStock: bool, brand: { id: int, name: str }, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] }`
#### Get public article detail — `GET /public/{companyId}/articles/{sku}` #### Get public article detail — `GET /public/{companyId}/articles/{sku}`
- Params: `companyId: int, sku: str` - Params: `companyId: int, sku: str`
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, description: str, unitamount: num, weight: num, trackStock: bool, unit: obj, brand: obj, price: obj, prices: obj[], categories: obj[], photos: obj[], eans: str[], isParent: bool, parentSku: str, variants: obj[] } }` - Returns: `200 { article: { id: int, sku: str, name: str, slug: str, description: str, unitamount: num, weight: num, trackStock: bool, unit: {}, brand: {}, price: {}, prices: {}[], categories: {}[], photos: {}[], eans: str[], isParent: bool, parentSku: str, variants: {}[] } }`
#### List public categories — `GET /public/{companyId}/categories` #### List public categories — `GET /public/{companyId}/categories`
- Params: `companyId: int` - Params: `companyId: int`
- Returns: `200 { categories: { id: int, parentId: int, sort: int, name: str, slug: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] }` - Returns: `200 { categories: { id: int, parentId: int, sort: int, name: str, slug: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] }`
@@ -529,25 +530,25 @@ _none_
- Returns: `200 SSE` - Returns: `200 SSE`
#### Adjust stock manually — `POST /stock/adjust` #### Adjust stock manually — `POST /stock/adjust`
- Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }`
- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: obj[], ledger: T11[] }` - 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: T11[] }`
#### List suppliers — `GET /supplier` #### List suppliers — `GET /supplier`
_none_ _none_
- Returns: `200 { suppliers: T32[] }` - Returns: `200 { suppliers: T33[] }`
#### Create supplier — `POST /supplier` #### Create supplier — `POST /supplier`
- Params: `body: { name: str }` - Params: `body: { name: str }`
- Returns: `201 { supplier: T32 }` - Returns: `201 { supplier: T33 }`
#### Delete supplier — `DELETE /supplier/{id}` #### Delete supplier — `DELETE /supplier/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update supplier — `PATCH /supplier/{id}` #### Update supplier — `PATCH /supplier/{id}`
- Params: `id: int, body: { name: str }` - Params: `id: int, body: { name: str }`
- Returns: `200 { supplier: T32 }` - Returns: `200 { supplier: T33 }`
#### List supplier articles by SKUs — `GET /supplier/{id}/articles` #### List supplier articles by SKUs — `GET /supplier/{id}/articles`
- Params: `id: int, query: skus?: string` - Params: `id: int, query: skus?: string`
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T36, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }` - Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T37, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }`
#### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles` #### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles`
- Params: `id: int, body?: { skus?: str[] }` - Params: `id: int, body?: { skus?: str[] }`
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T36, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }` - Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T37, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }`
#### Thumb image by hash — `GET /thumb/{fullHash}` #### Thumb image by hash — `GET /thumb/{fullHash}`
- Params: `fullHash: str` - Params: `fullHash: str`
- Returns: `200 avif` - Returns: `200 avif`
@@ -556,13 +557,13 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### List units — `GET /unit` #### List units — `GET /unit`
_none_ _none_
- Returns: `200 { units: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }[] }` - Returns: `200 { units: T28[] }`
#### Upsert unit — `PUT /unit` #### Upsert unit — `PUT /unit`
- Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
- Returns: `200 { unit: { si: str, basesi: str, basesifactor: num, symbol: str, name: str } }` - Returns: `200 { unit: T28 }`
#### JTL-POS category synchronization — `GET /v1/category` #### JTL-POS category synchronization — `GET /v1/category`
- Params: `query: lastChangedCategory?: str, limit?: int` - Params: `query: lastChangedCategory?: str, limit?: int`
- Returns: `200 obj[]` - Returns: `200 {}[]`
#### POS discovery and pairing — `GET /v1/client` #### POS discovery and pairing — `GET /v1/client`
- Params: `query: authCode: str, name?: str` - Params: `query: authCode: str, name?: str`
- Returns: `200 { authCode: str, authToken: str, certificateFingerprint: str, certificateSerialNumber: str, mandantId: str, mandantName: str, mandantDatabase: str, serverFingerprint: str, name: str, serverTimestamp: str }` - Returns: `200 { authCode: str, authToken: str, certificateFingerprint: str, certificateSerialNumber: str, mandantId: str, mandantName: str, mandantDatabase: str, serverFingerprint: str, name: str, serverTimestamp: str }`
@@ -574,7 +575,7 @@ _none_
- Returns: `200 jpeg` - Returns: `200 jpeg`
#### JTL-POS product catalog synchronization — `GET /v1/product` #### JTL-POS product catalog synchronization — `GET /v1/product`
- Params: `query: lastChangedProduct?: str, limit?: int` - Params: `query: lastChangedProduct?: str, limit?: int`
- Returns: `200 obj[]` - Returns: `200 {}[]`
#### List virtual filesystem files — `GET /vfs` #### List virtual filesystem files — `GET /vfs`
_none_ _none_
- Returns: `200 { files: { name: str, size: int, mime: str, updated_at: str, created_at: str, isText: bool, isImage: bool, isPdf: bool }[] }` - Returns: `200 { files: { name: str, size: int, mime: str, updated_at: str, created_at: str, isText: bool, isImage: bool, isPdf: bool }[] }`
@@ -598,10 +599,10 @@ _none_
- Returns: `200 { success: bool, file: { name: str, size: int, mime: str, updated_at: str } }` - Returns: `200 { success: bool, file: { name: str, size: int, mime: str, updated_at: str } }`
#### List warehouses — `GET /warehouse` #### List warehouses — `GET /warehouse`
_none_ _none_
- Returns: `200 { warehouses: T37[] }` - Returns: `200 { warehouses: T38[] }`
#### Create warehouse — `POST /warehouse` #### Create warehouse — `POST /warehouse`
- Params: `body: { name: str, sort?: int }` - Params: `body: { name: str, sort?: int }`
- Returns: `201 T35` - Returns: `201 T36`
#### Reorder warehouses — `PUT /warehouse/order` #### Reorder warehouses — `PUT /warehouse/order`
- Params: `body: { warehouseIds: int[] }` - Params: `body: { warehouseIds: int[] }`
- Returns: `200 { warehouseIds: int[] }` - Returns: `200 { warehouseIds: int[] }`
@@ -610,10 +611,10 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Get warehouse by ID — `GET /warehouse/{id}` #### Get warehouse by ID — `GET /warehouse/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T35` - Returns: `200 T36`
#### Update warehouse (partial) — `PATCH /warehouse/{id}` #### Update warehouse (partial) — `PATCH /warehouse/{id}`
- Params: `id: int, body: { name?: str, sort?: int }` - Params: `id: int, body: { name?: str, sort?: int }`
- Returns: `200 T35` - Returns: `200 T36`
#### Update warehouse — `PUT /warehouse/{id}` #### Update warehouse — `PUT /warehouse/{id}`
- Params: `id: int, body: { name?: str, sort?: int }` - Params: `id: int, body: { name?: str, sort?: int }`
- Returns: `200 T35` - Returns: `200 T36`

View File

@@ -4,24 +4,24 @@ Overview of all 145 API endpoints across all modules.
### Notation ### Notation
Types: `str` string, `int` integer, `num` number, `bool` boolean, `obj` object; `T[]` array; `?` optional. Types: `str` string, `int` integer, `num` number, `bool` boolean, `{}` object; `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. 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 ### Reusable types
- `T1` = `{ article: T2 }` - `T1` = `{ article: T2 }`
- `T2` = `{ id: int, sku: str, name: str, slug: str, track_stock: bool, unitamount: num, weight: num, unit: obj, brand: obj, categoryIds: int[], photos: T17[], eans: str[], prices: T26[], suppliers: T9[] }` - `T2` = `{ id: int, sku: str, name: str, slug: str, track_stock: bool, unitamount: num, weight: num, unit: T26, brand: T23, categoryIds: int[], photos: T17[], eans: str[], prices: T27[], suppliers: T9[] }`
- `T3` = `{ delivery: T4 }` - `T3` = `{ delivery: T4 }`
- `T4` = `{ id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T30, lines: T18[], files: T8[] }` - `T4` = `{ id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T31, lines: T18[], files: T8[] }`
- `T5` = `{ order: T7 }` - `T5` = `{ order: T7 }`
- `T6` = `{ quote: T7 }` - `T6` = `{ quote: T7 }`
- `T7` = `{ id: int, number: str, type: str, entityId: int, date: str, createdAt: str, lines: T18[] }` - `T7` = `{ id: int, number: str, type: str, entityId: int, date: str, createdAt: str, lines: T18[] }`
- `T8` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created: str, modified: str, thumbavif: str, thumbx: int, thumby: int, meta: obj }` - `T8` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created: str, modified: str, thumbavif: str, thumbx: int, thumby: int, meta: { [k]: str } }`
- `T9` = `{ id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T33 }` - `T9` = `{ id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T34 }`
- `T10` = `{ ledger: T11[] }` - `T10` = `{ ledger: T11[] }`
- `T11` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash: str, sku: str, created_at: str }` - `T11` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash: str, sku: str, created_at: str }`
- `T12` = `{ id: int, entityId: int, firstName: str, name: str, street: str, streetNumber: str, zip: str, city: str, county: str, addressType: str }` - `T12` = `{ id: int, entityId: int, firstName: str, name: str, street: str, streetNumber: str, zip: str, city: str, county: str, addressType: str }`
- `T13` = `{ id: int, name: str, sort: int, exclusive: bool, values: T28[] }` - `T13` = `{ id: int, name: str, sort: int, exclusive: bool, values: T29[] }`
- `T14` = `{ entity: T19 }` - `T14` = `{ entity: T19 }`
- `T15` = `{ printer: T20 }` - `T15` = `{ printer: T20 }`
- `T16` = `{ labeltemplate: T22 }` - `T16` = `{ labeltemplate: T22 }`
@@ -30,19 +30,20 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- `T19` = `{ id: int, customergroupId: int, firstName: str, name: str, number: str, createdAt: str, updatedAt: str }` - `T19` = `{ id: int, customergroupId: int, firstName: str, name: str, number: str, createdAt: str, updatedAt: str }`
- `T20` = `{ id: int, company_id: int, address: str, type: str, model: str, serial: str, sort: int, desc: str }` - `T20` = `{ id: int, company_id: int, address: str, type: str, model: str, serial: str, sort: int, desc: str }`
- `T21` = `{ amount: num, net: num, taxrate: num, sku?: str, name?: str, pu?: num, rebate?: num, comment?: str }` - `T21` = `{ amount: num, net: num, taxrate: num, sku?: str, name?: str, pu?: num, rebate?: num, comment?: str }`
- `T22` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: obj, thumb: str }` - `T22` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: {}, thumb: str }`
- `T23` = `{ id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` - `T23` = `{ id: int, name: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
- `T24` = `{ article: T25 }` - `T24` = `{ article: T25 }`
- `T25` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` - `T25` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }`
- `T26` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` - `T26` = `{ si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
- `T27` = `{ id: int, entityId: int, contactType: str, contact: str }` - `T27` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }`
- `T28` = `{ id: int, attributeId: int, value: str, sort: int }` - `T28` = `{ id: int, entityId: int, contactType: str, contact: str }`
- `T29` = `{ id: int, name: str, createdAt: str, updatedAt: str }` - `T29` = `{ id: int, attributeId: int, value: str, sort: int }`
- `T30` = `{ totalPositions: int, completelyOnLedgerPositions: int }` - `T30` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
- `T31` = `{ id: int, name: str, sort: int, exclusive: bool }` - `T31` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
- `T32` = `{ warehouse: T34 }` - `T32` = `{ id: int, name: str, sort: int, exclusive: bool }`
- `T33` = `{ net: num, taxId: int, taxRate: num }` - `T33` = `{ warehouse: T35 }`
- `T34` = `{ id: int, name: str, sort: int }` - `T34` = `{ net: num, taxId: int, taxRate: num }`
- `T35` = `{ id: int, name: str, sort: int }`
### Routes ### Routes
@@ -63,7 +64,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }` - Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
#### Set Stückliste (bundle items) for an article — `PUT /article/{sku}/articlelist` #### 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 }[] }` - Params: `sku: str, body: { items: { articleId?: int, sku?: str, count?: int, sort?: int }[] }`
- Returns: `200 { items: obj[] }` - Returns: `200 { items: { id: int, articleId: int, sku: str, name: str, count: int, sort: int }[] }`
#### List article attribute values — `GET /article/{sku}/attributevalue` #### List article attribute values — `GET /article/{sku}/attributevalue`
- Params: `sku: str` - Params: `sku: str`
- Returns: `200 { attributevalues: { id: int, articleId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }` - Returns: `200 { attributevalues: { id: int, articleId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }`
@@ -84,7 +85,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `201 T24` - Returns: `201 T24`
#### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children`
- Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }`
- Returns: `200 { article: T25, children: obj[] }` - Returns: `200 { article: T25, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }`
#### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}`
- Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Params: `sku: str, childSku: str, query: deleteArticle?: bool`
- Returns: `200 { success: bool, deleted: bool }` - Returns: `200 { success: bool, deleted: bool }`
@@ -105,12 +106,12 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
- Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }` - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
#### Set options list for an article — `PUT /article/{sku}/optionslist` #### 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 }[] }` - Params: `sku: str, body: { options: { articleId?: int, sku?: str, count?: int, sort?: int, mandated?: bool }[] }`
- Returns: `200 { options: obj[] }` - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }`
#### Toggle parent mode for an article — `POST /article/{sku}/parent` #### Toggle parent mode for an article — `POST /article/{sku}/parent`
- Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }`
- Returns: `200 T24` - Returns: `200 T24`
#### Replace POS prices by SKU — `PUT /article/{sku}/prices` #### Replace POS prices by SKU — `PUT /article/{sku}/prices`
- Params: `sku: str, body: { prices: { price: num, tax: num, minmultiplier?: num }[] }` - Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }`
- Returns: `200 T1` - Returns: `200 T1`
#### Print article label — `POST /article/{sku}/print` #### Print article label — `POST /article/{sku}/print`
- Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }` - Params: `sku: str, body: { templateId: int, printerId: int, quantity: int }`
@@ -156,10 +157,10 @@ _none_
- Returns: `200` - Returns: `200`
#### List values for an attribute — `GET /attributename/{attributeId}/value` #### List values for an attribute — `GET /attributename/{attributeId}/value`
- Params: `attributeId: int` - Params: `attributeId: int`
- Returns: `200 { values: T28[] }` - Returns: `200 { values: T29[] }`
#### Create value for an attribute — `POST /attributename/{attributeId}/value` #### Create value for an attribute — `POST /attributename/{attributeId}/value`
- Params: `attributeId: int, body: { value: str, sort?: int }` - Params: `attributeId: int, body: { value: str, sort?: int }`
- Returns: `201 { attributevalue: T28 }` - Returns: `201 { attributevalue: T29 }`
#### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order` #### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order`
- Params: `attributeId: int, body: { attributevalueIds: int[] }` - Params: `attributeId: int, body: { attributevalueIds: int[] }`
- Returns: `200` - Returns: `200`
@@ -174,7 +175,7 @@ _none_
- Returns: `200` - Returns: `200`
#### Update attribute value — `PATCH /attributevalue/{id}` #### Update attribute value — `PATCH /attributevalue/{id}`
- Params: `id: int, body: { value?: str, sort?: int }` - Params: `id: int, body: { value?: str, sort?: int }`
- Returns: `200 { attributevalue: T28 }` - Returns: `200 { attributevalue: T29 }`
#### Login with Google ID token — `POST /auth/login` #### Login with Google ID token — `POST /auth/login`
- Params: `body: { idToken: str }` - Params: `body: { idToken: str }`
- Returns: `200 { message: str, user: { id: int, email: str, selectedCompanyId: int, picture: str } }` - Returns: `200 { message: str, user: { id: int, email: str, selectedCompanyId: int, picture: str } }`
@@ -183,19 +184,19 @@ _none_
- Returns: `200 { message: str }` - Returns: `200 { message: str }`
#### List brands — `GET /brand` #### List brands — `GET /brand`
_none_ _none_
- Returns: `200 { brands: { id: int, name: str, gsprpinfo: str, pics: T23[] }[] }` - Returns: `200 { brands: { id: int, name: str, gsprpinfo: str, pics: any[] }[] }`
#### Upsert brand — `PUT /brand` #### Upsert brand — `PUT /brand`
- Params: `body: { name: str, gsprpinfo?: str }` - Params: `body: { name: str, gsprpinfo?: str }`
- Returns: `200 { brand: { id: int, name: str, gsprpinfo: str, pics: T23[] } }` - Returns: `200 { brand: { id: int, name: str, gsprpinfo: str, pics: any[] } }`
#### Delete brand — `DELETE /brand/{id}` #### Delete brand — `DELETE /brand/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Delete brand image — `DELETE /brand/{id}/image` #### Delete brand image — `DELETE /brand/{id}/image`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { brand: { id: int, thumbW: int, thumbH: int, thumbHash: str, pics: T23[] } }` - Returns: `200 { brand: { id: int, thumbW: int, thumbH: int, thumbHash: str, pics: { id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] } }`
#### Add or replace brand image — `POST /brand/{id}/image` #### Add or replace brand image — `POST /brand/{id}/image`
- Params: `id: int, image/avif` - Params: `id: int, image/avif`
- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T23[] } }` - Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: { id: int, sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[] } }`
#### Create category — `POST /category` #### Create category — `POST /category`
- Params: `body: { parentId: int, name: str }` - Params: `body: { parentId: int, name: str }`
- Returns: `201 { category: { id: int, parentId: int, sort: int, name: str } }` - Returns: `201 { category: { id: int, parentId: int, sort: int, name: str } }`
@@ -204,7 +205,7 @@ _none_
- Returns: `200 { categoryIds: int[] }` - Returns: `200 { categoryIds: int[] }`
#### Category tree — `GET /category/tree` #### Category tree — `GET /category/tree`
_none_ _none_
- Returns: `200 { languageId: int, tree: obj[], articles: obj[] }` - Returns: `200 { languageId: int, tree: { id: int, parentId: int, sort: int, name: str, slug: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, posterminalIds: int[] }[], articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, parentArticleId: int, parentSku: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, brand: T23 }[] }`
#### Delete category — `DELETE /category/{id}` #### Delete category — `DELETE /category/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
@@ -213,7 +214,7 @@ _none_
- Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }` - Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }`
#### Articles in category — `GET /category/{id}/articles` #### Articles in category — `GET /category/{id}/articles`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, brand: obj }[] }` - Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num, taxRate: num, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, parentArticleId: int, parentSku: str, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, brand: T23 }[] }`
#### List category attribute values — `GET /category/{id}/attributevalue` #### List category attribute values — `GET /category/{id}/attributevalue`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { attributevalues: { id: int, categoryId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }` - Returns: `200 { attributevalues: { id: int, categoryId: int, attributeId: int, attributeName: str, exclusive: bool, attributeValueId: int, value: str, sort: int }[] }`
@@ -258,7 +259,7 @@ _none_
- Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }` - Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }`
#### Create customer group — `POST /customergroup` #### Create customer group — `POST /customergroup`
- Params: `body: { name: str }` - Params: `body: { name: str }`
- Returns: `201 { customergroup: T34 }` - Returns: `201 { customergroup: T35 }`
#### Reorder customer groups — `PUT /customergroup/order` #### Reorder customer groups — `PUT /customergroup/order`
- Params: `body: { customergroupIds: int[] }` - Params: `body: { customergroupIds: int[] }`
- Returns: `200 { customergroupIds: int[] }` - Returns: `200 { customergroupIds: int[] }`
@@ -267,12 +268,12 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Update customer group — `PATCH /customergroup/{id}` #### Update customer group — `PATCH /customergroup/{id}`
- Params: `id: int, body: { name: str }` - Params: `id: int, body: { name: str }`
- Returns: `200 { customergroup: T34 }` - Returns: `200 { customergroup: T35 }`
#### List deliveries — `GET /delivery` #### List deliveries — `GET /delivery`
_none_ _none_
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T30 }[] }` - Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, externalNumber: str, date: str, createdAt: str, ledger: T31 }[] }`
#### Create delivery — `POST /delivery` #### Create delivery — `POST /delivery`
- Params: `body: { externalNumber: str, date: str, lines: T21[], supplierId?: int, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: obj }[] }` - Params: `body: { externalNumber: str, date: str, lines: T21[], supplierId?: int, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `201 T3` - Returns: `201 T3`
#### Delete delivery — `DELETE /delivery/{id}` #### Delete delivery — `DELETE /delivery/{id}`
- Params: `id: int` - Params: `id: int`
@@ -281,7 +282,7 @@ _none_
- Params: `id: int` - Params: `id: int`
- Returns: `200 T3` - Returns: `200 T3`
#### Update delivery — `PATCH /delivery/{id}` #### Update delivery — `PATCH /delivery/{id}`
- Params: `id: int, body: { externalNumber?: str, date?: str, supplierId?: int, lines?: T21[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: obj }[] }` - Params: `id: int, body: { externalNumber?: str, date?: str, supplierId?: int, lines?: T21[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `200 T3` - Returns: `200 T3`
#### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}` #### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}`
- Params: `id: int, fileId: int, query: download?: bool` - Params: `id: int, fileId: int, query: download?: bool`
@@ -309,16 +310,16 @@ _none_
- Returns: `200 { address: T12 }` - Returns: `200 { address: T12 }`
#### List entity contacts — `GET /entity/{entityId}/contact` #### List entity contacts — `GET /entity/{entityId}/contact`
- Params: `entityId: int` - Params: `entityId: int`
- Returns: `200 { contacts: T27[] }` - Returns: `200 { contacts: T28[] }`
#### Create entity contact — `POST /entity/{entityId}/contact` #### Create entity contact — `POST /entity/{entityId}/contact`
- Params: `entityId: int, body: { contactType: str, contact: str }` - Params: `entityId: int, body: { contactType: str, contact: str }`
- Returns: `201 { contact: T27 }` - Returns: `201 { contact: T28 }`
#### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}` #### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}`
- Params: `entityId: int, id: int` - Params: `entityId: int, id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update entity contact — `PATCH /entity/{entityId}/contact/{id}` #### Update entity contact — `PATCH /entity/{entityId}/contact/{id}`
- Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }` - Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }`
- Returns: `200 { contact: T27 }` - Returns: `200 { contact: T28 }`
#### Delete entity — `DELETE /entity/{id}` #### Delete entity — `DELETE /entity/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
@@ -332,7 +333,7 @@ _none_
_none_ _none_
- Returns: `200 { labeltemplates: T22[] }` - Returns: `200 { labeltemplates: T22[] }`
#### Create label template — `POST /labeltemplate` #### Create label template — `POST /labeltemplate`
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: obj }` - Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: {} }`
- Returns: `201 T16` - Returns: `201 T16`
#### Delete label template — `DELETE /labeltemplate/{id}` #### Delete label template — `DELETE /labeltemplate/{id}`
- Params: `id: int` - Params: `id: int`
@@ -341,10 +342,10 @@ _none_
- Params: `id: int` - Params: `id: int`
- Returns: `200 T16` - Returns: `200 T16`
#### Update label template (partial) — `PATCH /labeltemplate/{id}` #### Update label template (partial) — `PATCH /labeltemplate/{id}`
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: obj }` - Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: {} }`
- Returns: `200 T16` - Returns: `200 T16`
#### Update label template — `PUT /labeltemplate/{id}` #### Update label template — `PUT /labeltemplate/{id}`
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: obj }` - Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: {} }`
- Returns: `200 T16` - Returns: `200 T16`
#### Query ledger by manifest article ID or SKU — `GET /ledger` #### Query ledger by manifest article ID or SKU — `GET /ledger`
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str` - Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
@@ -402,10 +403,10 @@ _none_
- Returns: `200 T15` - Returns: `200 T15`
#### List property names — `GET /propertyname` #### List property names — `GET /propertyname`
_none_ _none_
- Returns: `200 { propertynames: T31[] }` - Returns: `200 { propertynames: T32[] }`
#### Create property name — `POST /propertyname` #### Create property name — `POST /propertyname`
- Params: `body: { name: str, sort?: int, exclusive?: bool }` - Params: `body: { name: str, sort?: int, exclusive?: bool }`
- Returns: `201 { propertyname: T31 }` - Returns: `201 { propertyname: T32 }`
#### Reorder property names — `PUT /propertyname/order` #### Reorder property names — `PUT /propertyname/order`
- Params: `body: { propertynameIds: int[] }` - Params: `body: { propertynameIds: int[] }`
- Returns: `200` - Returns: `200`
@@ -414,7 +415,7 @@ _none_
- Returns: `200` - Returns: `200`
#### Update property name — `PATCH /propertyname/{id}` #### Update property name — `PATCH /propertyname/{id}`
- Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }`
- Returns: `200 { propertyname: T31 }` - Returns: `200 { propertyname: T32 }`
#### List quotes — `GET /quote` #### List quotes — `GET /quote`
_none_ _none_
- Returns: `200 { quotes: { id: int, number: str, type: str, entityId: int, date: str, createdAt: str }[] }` - Returns: `200 { quotes: { id: int, number: str, type: str, entityId: int, date: str, createdAt: str }[] }`
@@ -432,40 +433,40 @@ _none_
- Returns: `200 T6` - Returns: `200 T6`
#### Adjust stock manually — `POST /stock/adjust` #### Adjust stock manually — `POST /stock/adjust`
- Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }`
- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: obj[], ledger: T11[] }` - 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: T11[] }`
#### List suppliers — `GET /supplier` #### List suppliers — `GET /supplier`
_none_ _none_
- Returns: `200 { suppliers: T29[] }` - Returns: `200 { suppliers: T30[] }`
#### Create supplier — `POST /supplier` #### Create supplier — `POST /supplier`
- Params: `body: { name: str }` - Params: `body: { name: str }`
- Returns: `201 { supplier: T29 }` - Returns: `201 { supplier: T30 }`
#### Delete supplier — `DELETE /supplier/{id}` #### Delete supplier — `DELETE /supplier/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `204 void` - Returns: `204 void`
#### Update supplier — `PATCH /supplier/{id}` #### Update supplier — `PATCH /supplier/{id}`
- Params: `id: int, body: { name: str }` - Params: `id: int, body: { name: str }`
- Returns: `200 { supplier: T29 }` - Returns: `200 { supplier: T30 }`
#### List supplier articles by SKUs — `GET /supplier/{id}/articles` #### List supplier articles by SKUs — `GET /supplier/{id}/articles`
- Params: `id: int, query: skus?: string` - Params: `id: int, query: skus?: string`
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T33, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }` - Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T34, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }`
#### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles` #### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles`
- Params: `id: int, body?: { skus?: str[] }` - Params: `id: int, body?: { skus?: str[] }`
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T33, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }` - Returns: `200 { articles: { id: int, articleId: int, entityId: int, supplierName: str, sku: str, pu: num, stock: num, sort: int, priceId: int, price: T34, article: { id: int, sku: str, name: str, slug: str, photos: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[] } }[] }`
#### Delete unit — `DELETE /unit` #### Delete unit — `DELETE /unit`
- Params: `query: si: str` - Params: `query: si: str`
- Returns: `204 void` - Returns: `204 void`
#### List units — `GET /unit` #### List units — `GET /unit`
_none_ _none_
- Returns: `200 { units: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }[] }` - Returns: `200 { units: T26[] }`
#### Upsert unit — `PUT /unit` #### Upsert unit — `PUT /unit`
- Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
- Returns: `200 { unit: { si: str, basesi: str, basesifactor: num, symbol: str, name: str } }` - Returns: `200 { unit: T26 }`
#### List warehouses — `GET /warehouse` #### List warehouses — `GET /warehouse`
_none_ _none_
- Returns: `200 { warehouses: T34[] }` - Returns: `200 { warehouses: T35[] }`
#### Create warehouse — `POST /warehouse` #### Create warehouse — `POST /warehouse`
- Params: `body: { name: str, sort?: int }` - Params: `body: { name: str, sort?: int }`
- Returns: `201 T32` - Returns: `201 T33`
#### Reorder warehouses — `PUT /warehouse/order` #### Reorder warehouses — `PUT /warehouse/order`
- Params: `body: { warehouseIds: int[] }` - Params: `body: { warehouseIds: int[] }`
- Returns: `200 { warehouseIds: int[] }` - Returns: `200 { warehouseIds: int[] }`
@@ -474,13 +475,13 @@ _none_
- Returns: `204 void` - Returns: `204 void`
#### Get warehouse by ID — `GET /warehouse/{id}` #### Get warehouse by ID — `GET /warehouse/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 T32` - Returns: `200 T33`
#### Update warehouse (partial) — `PATCH /warehouse/{id}` #### Update warehouse (partial) — `PATCH /warehouse/{id}`
- Params: `id: int, body: { name?: str, sort?: int }` - Params: `id: int, body: { name?: str, sort?: int }`
- Returns: `200 T32` - Returns: `200 T33`
#### Update warehouse — `PUT /warehouse/{id}` #### Update warehouse — `PUT /warehouse/{id}`
- Params: `id: int, body: { name?: str, sort?: int }` - Params: `id: int, body: { name?: str, sort?: int }`
- Returns: `200 T32` - Returns: `200 T33`
### Binary & streaming routes (reachable only via `fetch()` in javascript_execute) ### Binary & streaming routes (reachable only via `fetch()` in javascript_execute)
These routes are NOT in the table above because their responses are binary or long-lived streams, which the `dashback_request` tool cannot return usefully. They ARE reachable from inside `javascript_execute` with `fetch("/route")`, where you can decode the bytes, write them to the VFS and embed them in your reply: These routes are NOT in the table above because their responses are binary or long-lived streams, which the `dashback_request` tool cannot return usefully. They ARE reachable from inside `javascript_execute` with `fetch("/route")`, where you can decode the bytes, write them to the VFS and embed them in your reply:

View File

@@ -42,7 +42,14 @@ Updated Stückliste items
```json ```json
{ {
"items": [ "items": [
{} {
"id": 1,
"articleId": 1,
"sku": "…",
"name": "…",
"count": 1,
"sort": 1
}
] ]
} }
``` ```

View File

@@ -44,7 +44,14 @@ Attached child article and list of children
"track_stock": true "track_stock": true
}, },
"children": [ "children": [
{} {
"id": 1,
"sku": "…",
"name": "…",
"parentsort": 1,
"attributeValueId": 1,
"attributeValue": "…"
}
] ]
} }
``` ```

View File

@@ -44,7 +44,15 @@ Updated options list
```json ```json
{ {
"options": [ "options": [
{} {
"id": 1,
"articleId": 1,
"sku": "…",
"name": "…",
"count": 1,
"sort": 1,
"mandated": true
}
] ]
} }
``` ```

View File

@@ -43,8 +43,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -26,8 +26,21 @@ Article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -23,8 +23,8 @@ Sets `articlepic.sort` to `0..n-1` in the given order.
```json ```json
{ {
"fullHashes": [ "fullHashes": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aB1cD2eF",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "gH3iJ4kL"
] ]
} }
``` ```

View File

@@ -44,8 +44,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -48,8 +48,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -46,8 +46,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -5,7 +5,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Replace POS prices by SKU Replace POS prices by SKU
Replace-all prices for the company POS customer group. Replace-all prices for the company POS customer group.
Each entry: `price` (net), `tax` (rate, ensured via tax.ensure), optional `minmultiplier` (default 1). Each entry accepts `net` OR `gross` price (or legacy `price`), `taxRate` OR `tax` (percentage, e.g. 19 for 19%, ensured via tax.ensure), optional `minmultiplier` (default 1).
When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
Empty `prices` clears all POS prices. Returns the same shape as GET by SKU (article `prices` array contains `{ minmultiplier, net, taxRate, customergroupId }`). Empty `prices` clears all POS prices. Returns the same shape as GET by SKU (article `prices` array contains `{ minmultiplier, net, taxRate, customergroupId }`).
@@ -20,20 +21,23 @@ Empty `prices` clears all POS prices. Returns the same shape as GET by SKU (arti
| Field | Required | Type | Description | | Field | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `prices` | yes | object[] | | | `prices` | yes | object[] | |
| `prices[].price` | yes | number | Net price | | `prices[].net` | no | number | Net price (excl. VAT). Either `net`, `gross`, or legacy `price` is required. |
| `prices[].tax` | yes | number | Tax rate (e.g. 19) | | `prices[].gross` | no | number | Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`. |
| `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request | | `prices[].price` | no | number | Legacy alias for `net`. |
| `prices[].taxRate` | no | number | Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%). |
| `prices[].tax` | no | number | Alias for `taxRate` percentage. |
| `prices[].minmultiplier` | no | number | Defaults to 1; must be unique per request. |
```json ```json
{ {
"prices": [ "prices": [
{ {
"price": 10.5, "net": 10.5,
"tax": 19 "taxRate": 19
}, },
{ {
"price": 9.9, "gross": 11.9,
"tax": 19, "taxRate": 19,
"minmultiplier": 10 "minmultiplier": 10
} }
] ]
@@ -58,8 +62,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -44,8 +44,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -48,8 +48,21 @@ Updated article detail
"track_stock": true, "track_stock": true,
"unitamount": 1, "unitamount": 1,
"weight": 1, "weight": 1,
"unit": {}, "unit": {
"brand": {}, "si": "…",
"basesi": "…",
"basesifactor": 1,
"symbol": "…",
"name": "…"
},
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
},
"categoryIds": [ "categoryIds": [
1 1
], ],

View File

@@ -17,16 +17,7 @@ Brand list
"id": 1, "id": 1,
"name": "…", "name": "…",
"gsprpinfo": "…", "gsprpinfo": "…",
"pics": [ "pics": []
{
"id": 1,
"sort": 1,
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
]
} }
] ]
} }

View File

@@ -31,16 +31,7 @@ Upserted brand
"id": 1, "id": 1,
"name": "…", "name": "…",
"gsprpinfo": "…", "gsprpinfo": "…",
"pics": [ "pics": []
{
"id": 1,
"sort": 1,
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
]
} }
} }
``` ```

View File

@@ -30,11 +30,20 @@ Articles list
"optionsCount": 1, "optionsCount": 1,
"articleListCount": 1, "articleListCount": 1,
"childCount": 1, "childCount": 1,
"parentArticleId": 1,
"parentSku": "…",
"fullHash": "…", "fullHash": "…",
"thumbW": 1, "thumbW": 1,
"thumbH": 1, "thumbH": 1,
"thumbHash": "…", "thumbHash": "…",
"brand": {} "brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
} }
] ]
} }

View File

@@ -14,10 +14,48 @@ Tree and articles for the selected company
{ {
"languageId": 1, "languageId": 1,
"tree": [ "tree": [
{} {
"id": 1,
"parentId": 1,
"sort": 1,
"name": "…",
"slug": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…",
"posterminalIds": [
1
]
}
], ],
"articles": [ "articles": [
{} {
"id": 1,
"sku": "…",
"name": "…",
"slug": "…",
"net": 1,
"taxRate": 1,
"track_stock": true,
"optionsCount": 1,
"articleListCount": 1,
"childCount": 1,
"parentArticleId": 1,
"parentSku": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…",
"brand": {
"id": 1,
"name": "…",
"fullHash": "…",
"thumbW": 1,
"thumbH": 1,
"thumbHash": "…"
}
}
] ]
} }
``` ```

View File

@@ -4,7 +4,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Full image by hash Full image by hash
Returns the full AVIF by `pic.hash` (SHA-256 hex). Returns the full AVIF by `pic.hash` (8-char alphanumeric Base62).
Auth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`). Auth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`).
@@ -12,7 +12,7 @@ Auth via article category access. Browser-cacheable (`private`, `immutable`, `ET
| Param | Required | Type | Description | | Param | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `fullHash` | yes | string | SHA-256 hex of the full image bytes | | `fullHash` | yes | string | 8-char alphanumeric hash of the full image bytes |
## Response `200` ## Response `200`

View File

@@ -55,7 +55,14 @@ Stock adjusted successfully
"createdAt": "2026-08-10T00:00:00.000Z" "createdAt": "2026-08-10T00:00:00.000Z"
}, },
"lines": [ "lines": [
{} {
"manifestarticle_id": 1,
"sku": "…",
"name": "…",
"warehouse_id": 1,
"amount": 1,
"text": "…"
}
], ],
"ledger": [ "ledger": [
{ {

View File

@@ -12,7 +12,7 @@ Auth via article category access. Browser-cacheable (`private`, `immutable`, `ET
| Param | Required | Type | Description | | Param | Required | Type | Description |
|-------|----------|------|-------------| |-------|----------|------|-------------|
| `fullHash` | yes | string | SHA-256 hex of the full image bytes | | `fullHash` | yes | string | 8-char alphanumeric hash of the full image bytes |
## Response `200` ## Response `200`

View File

@@ -5,7 +5,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete a virtual filesystem file Delete a virtual filesystem file
Deletes a file from both the on-disk virtual filesystem and database for the authenticated user's selected company. Deletes a file from both the on-disk virtual filesystem and database for the authenticated user's selected company.
Deleting read-only database files (`image/*` or filenames containing 64-character hex hashes) is blocked with HTTP 403. Deleting read-only database files (`image/*` or filenames containing 8-character image hashes) is blocked with HTTP 403.
## Path parameters ## Path parameters

View File

@@ -8,7 +8,7 @@ Uploads one or more files to the virtual filesystem for the authenticated user's
Files are written to the company's VFS root directory and saved to the database. Files are written to the company's VFS root directory and saved to the database.
An optional `targetDir` form field specifies a subfolder within the VFS. An optional `targetDir` form field specifies a subfolder within the VFS.
Writing to read-only database paths (`image/*` or filenames containing 64-character hex hashes) is Writing to read-only database paths (`image/*` or filenames containing 8-character image hashes) is
forbidden and automatically skipped during upload. forbidden and automatically skipped during upload.

View File

@@ -10,7 +10,7 @@ Supports multiple request payload formats:
- Binary body: raw binary buffer with optional `Content-Type` header - Binary body: raw binary buffer with optional `Content-Type` header
- Plain text: raw UTF-8 string - Plain text: raw UTF-8 string
Modifying read-only database paths (`image/*` or filenames containing 64-character hex hashes) is blocked with HTTP 403. Modifying read-only database paths (`image/*` or filenames containing 8-character image hashes) is blocked with HTTP 403.
## Path parameters ## Path parameters

View File

@@ -531,7 +531,7 @@ function summarizeSchema(
skipRootAlias = false, skipRootAlias = false,
) { ) {
if (!schema) return ''; if (!schema) return '';
if (depth > 6) return aliases?.compactTypes ? 'obj' : 'object'; if (depth > 6) return aliases?.compactTypes ? '{}' : 'object';
const resolved = resolveSchema(schema, components); const resolved = resolveSchema(schema, components);
if (!resolved) return schemaRefName(schema) || ''; if (!resolved) return schemaRefName(schema) || '';
@@ -549,7 +549,13 @@ function summarizeSchema(
const required = new Set(resolved.required || []); const required = new Set(resolved.required || []);
let entries = Object.entries(resolved.properties || {}); let entries = Object.entries(resolved.properties || {});
if (entries.length === 0) return aliases?.compactTypes ? 'obj' : 'object'; if (entries.length === 0) {
if (resolved.additionalProperties && typeof resolved.additionalProperties === 'object') {
const valType = summarizeSchema(resolved.additionalProperties, components, depth + 1, markOptional, aliases) || 'any';
return `{ [k]: ${valType} }`;
}
return aliases?.compactTypes ? '{}' : 'object';
}
// Required props first, then optional ones (stable within each group). // Required props first, then optional ones (stable within each group).
if (markOptional) { if (markOptional) {
@@ -574,7 +580,7 @@ function summarizeSchema(
integer: 'int', integer: 'int',
number: 'num', number: 'num',
boolean: 'bool', boolean: 'bool',
object: 'obj', object: '{}',
}[resolved.type] || resolved.type; }[resolved.type] || resolved.type;
} }
return resolved.type; return resolved.type;
@@ -741,7 +747,7 @@ function renderRoutesOverview(operations, title = 'Dash API Routes Overview') {
lines.push(''); lines.push('');
lines.push('### Notation'); lines.push('### Notation');
lines.push(''); lines.push('');
lines.push('Types: `str` string, `int` integer, `num` number, `bool` boolean, `obj` object; `T[]` array; `?` optional.'); lines.push('Types: `str` string, `int` integer, `num` number, `bool` boolean, `{}` object; `T[]` array; `?` optional.');
lines.push('Params: path values first, then `query:` and `body:` (`body?` = optional body). Responses: status code + JSON shape; `Tn` refers to a reusable type below.'); lines.push('Params: path values first, then `query:` and `body:` (`body?` = optional body). Responses: status code + JSON shape; `Tn` refers to a reusable type below.');
if (definitions.length) { if (definitions.length) {
lines.push(''); lines.push('');

View File

@@ -134,9 +134,9 @@
* type: number * type: number
* nullable: true * nullable: true
* unit: * unit:
* type: object * $ref: '#/components/schemas/Unit'
* brand: * brand:
* type: object * $ref: '#/components/schemas/ArticleBrand'
* nullable: true * nullable: true
* categoryIds: * categoryIds:
* type: array * type: array
@@ -353,7 +353,157 @@
* type: array * type: array
* description: Brand pictures sorted by sort * description: Brand pictures sorted by sort
* items: * items:
* $ref: '#/components/schemas/BrandPic' * ArticleBrand:
* type: object
* required: [name]
* properties:
* id:
* type: integer
* nullable: true
* name:
* type: string
* fullHash:
* type: string
* nullable: true
* thumbW:
* type: integer
* nullable: true
* thumbH:
* type: integer
* nullable: true
* thumbHash:
* type: string
* nullable: true
* StuecklisteItem:
* type: object
* required: [id, articleId, sku, name, count, sort]
* properties:
* id:
* type: integer
* articleId:
* type: integer
* sku:
* type: string
* name:
* type: string
* count:
* type: integer
* sort:
* type: integer
* ArticleChild:
* type: object
* required: [id, sku, name, parentsort, attributeValueId, attributeValue]
* properties:
* id:
* type: integer
* sku:
* type: string
* name:
* type: string
* parentsort:
* type: integer
* attributeValueId:
* type: integer
* attributeValue:
* type: string
* ArticleOptionItem:
* type: object
* required: [id, articleId, sku, name, count, sort, mandated]
* properties:
* id:
* type: integer
* articleId:
* type: integer
* sku:
* type: string
* name:
* type: string
* count:
* type: integer
* sort:
* type: integer
* mandated:
* type: boolean
* CategoryTreeNode:
* type: object
* required: [id, sort, posterminalIds]
* properties:
* id:
* type: integer
* parentId:
* type: integer
* nullable: true
* sort:
* type: integer
* name:
* type: string
* nullable: true
* slug:
* type: string
* nullable: true
* fullHash:
* type: string
* nullable: true
* thumbW:
* type: integer
* nullable: true
* thumbH:
* type: integer
* nullable: true
* thumbHash:
* type: string
* nullable: true
* posterminalIds:
* type: array
* items:
* type: integer
* CategoryArticle:
* type: object
* required: [id, sku, name, slug, track_stock, optionsCount, articleListCount, childCount]
* properties:
* id:
* type: integer
* sku:
* type: string
* name:
* type: string
* slug:
* type: string
* net:
* type: number
* nullable: true
* taxRate:
* type: number
* nullable: true
* track_stock:
* type: boolean
* optionsCount:
* type: integer
* articleListCount:
* type: integer
* childCount:
* type: integer
* parentArticleId:
* type: integer
* nullable: true
* parentSku:
* type: string
* nullable: true
* fullHash:
* type: string
* nullable: true
* thumbW:
* type: integer
* nullable: true
* thumbH:
* type: integer
* nullable: true
* thumbHash:
* type: string
* nullable: true
* brand:
* $ref: '#/components/schemas/ArticleBrand'
* nullable: true
* NumberSequence: * NumberSequence:
* type: object * type: object
* required: [id, type, prefix, padding, next] * required: [id, type, prefix, padding, next]
@@ -1151,6 +1301,20 @@
* type: array * type: array
* items: * items:
* type: object * type: object
* required: [manifestarticle_id, sku, name, warehouse_id, amount, text]
* properties:
* manifestarticle_id:
* type: integer
* sku:
* type: string
* name:
* type: string
* warehouse_id:
* type: integer
* amount:
* type: number
* text:
* type: string
* ledger: * ledger:
* type: array * type: array
* items: * items:

View File

@@ -11,7 +11,7 @@ import { emit } from '../sse.js';
import { buildArtikelCsv, toWindows1252 } from '../../util/artikelCsv.js'; import { buildArtikelCsv, toWindows1252 } from '../../util/artikelCsv.js';
const AVIF_BODY_LIMIT = '20mb'; const AVIF_BODY_LIMIT = '20mb';
const FULL_HASH_RE = /^[a-fA-F0-9]{64}$/; const FULL_HASH_RE = /^[a-zA-Z0-9]{8}$/;
/** Notify SSE subscribers after create / rename-ish list-level article changes. */ /** Notify SSE subscribers after create / rename-ish list-level article changes. */
function emitArticlesSignal(req) { function emitArticlesSignal(req) {
@@ -44,9 +44,9 @@ function parseSku(value) {
function parseFullHash(value) { function parseFullHash(value) {
const fullHash = typeof value === 'string' ? value.trim() : ''; const fullHash = typeof value === 'string' ? value.trim() : '';
if (!FULL_HASH_RE.test(fullHash)) { if (!FULL_HASH_RE.test(fullHash)) {
return { error: 'fullHash must be a 64-char hex SHA-256' }; return { error: 'fullHash must be an 8-char alphanumeric string' };
} }
return { fullHash: fullHash.toLowerCase() }; return { fullHash };
} }
function articleErrorStatus(error) { function articleErrorStatus(error) {
@@ -720,7 +720,8 @@ export function register(app, basePath = '') {
* summary: Replace POS prices by SKU * summary: Replace POS prices by SKU
* description: | * description: |
* Replace-all prices for the company POS customer group. * Replace-all prices for the company POS customer group.
* Each entry: `price` (net), `tax` (rate, ensured via tax.ensure), optional `minmultiplier` (default 1). * Each entry accepts `net` OR `gross` price (or legacy `price`), `taxRate` OR `tax` (percentage, e.g. 19 for 19%, ensured via tax.ensure), optional `minmultiplier` (default 1).
* When `gross` is provided, net price is computed as `gross / (1 + taxRate / 100)`.
* Empty `prices` clears all POS prices. Returns the same shape as GET by SKU (article `prices` array contains `{ minmultiplier, net, taxRate, customergroupId }`). * Empty `prices` clears all POS prices. Returns the same shape as GET by SKU (article `prices` array contains `{ minmultiplier, net, taxRate, customergroupId }`).
* security: * security:
* - cookieAuth: [] * - cookieAuth: []
@@ -744,23 +745,31 @@ export function register(app, basePath = '') {
* type: array * type: array
* items: * items:
* type: object * type: object
* required: [price, tax]
* properties: * properties:
* net:
* type: number
* description: Net price (excl. VAT). Either `net`, `gross`, or legacy `price` is required.
* gross:
* type: number
* description: Gross price (incl. VAT). Net is computed as `gross / (1 + taxRate / 100)`.
* price: * price:
* type: number * type: number
* description: Net price * description: Legacy alias for `net`.
* taxRate:
* type: number
* description: Tax rate percentage (e.g. 19 for 19%, 7 for 7%, 0 for 0%).
* tax: * tax:
* type: number * type: number
* description: Tax rate (e.g. 19) * description: Alias for `taxRate` percentage.
* minmultiplier: * minmultiplier:
* type: number * type: number
* description: Defaults to 1; must be unique per request * description: Defaults to 1; must be unique per request.
* example: * example:
* prices: * prices:
* - price: 10.5 * - net: 10.5
* tax: 19 * taxRate: 19
* - price: 9.9 * - gross: 11.9
* tax: 19 * taxRate: 19
* minmultiplier: 10 * minmultiplier: 10
* responses: * responses:
* 200: * 200:
@@ -936,7 +945,7 @@ export function register(app, basePath = '') {
* required: true * required: true
* schema: * schema:
* type: string * type: string
* pattern: '^[a-fA-F0-9]{64}$' * pattern: '^[a-zA-Z0-9]{8}$'
* responses: * responses:
* 204: * 204:
* description: Deleted * description: Deleted
@@ -1017,11 +1026,11 @@ export function register(app, basePath = '') {
* type: array * type: array
* items: * items:
* type: string * type: string
* pattern: '^[a-fA-F0-9]{64}$' * pattern: '^[a-zA-Z0-9]{8}$'
* example: * example:
* fullHashes: * fullHashes:
* - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" * - "aB1cD2eF"
* - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" * - "gH3iJ4kL"
* responses: * responses:
* 200: * 200:
* description: New order * description: New order
@@ -1806,20 +1815,7 @@ export function register(app, basePath = '') {
* children: * children:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/ArticleChild'
* properties:
* id:
* type: integer
* sku:
* type: string
* name:
* type: string
* parentsort:
* type: integer
* attributeValueId:
* type: integer
* attributeValue:
* type: string
* 404: * 404:
* description: Article not found * description: Article not found
*/ */
@@ -1930,7 +1926,7 @@ export function register(app, basePath = '') {
* children: * children:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/ArticleChild'
* 400: * 400:
* description: Missing sku or attribute value, or invalid input * description: Missing sku or attribute value, or invalid input
* 404: * 404:
@@ -2080,20 +2076,7 @@ export function register(app, basePath = '') {
* items: * items:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/StuecklisteItem'
* properties:
* id:
* type: integer
* articleId:
* type: integer
* sku:
* type: string
* name:
* type: string
* count:
* type: integer
* sort:
* type: integer
* 404: * 404:
* description: Article not found * description: Article not found
*/ */
@@ -2161,7 +2144,7 @@ export function register(app, basePath = '') {
* items: * items:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/StuecklisteItem'
* 400: * 400:
* description: Article is a parent, has options, or contains itself * description: Article is a parent, has options, or contains itself
* 404: * 404:
@@ -2216,22 +2199,7 @@ export function register(app, basePath = '') {
* options: * options:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/ArticleOptionItem'
* properties:
* id:
* type: integer
* articleId:
* type: integer
* sku:
* type: string
* name:
* type: string
* count:
* type: integer
* sort:
* type: integer
* mandated:
* type: boolean
* 404: * 404:
* description: Article not found * description: Article not found
*/ */
@@ -2302,7 +2270,7 @@ export function register(app, basePath = '') {
* options: * options:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/ArticleOptionItem'
* 400: * 400:
* description: Article is a parent, a Stückliste, or contains itself * description: Article is a parent, a Stückliste, or contains itself
* 404: * 404:

View File

@@ -74,13 +74,11 @@ export function register(app, basePath = '') {
* tree: * tree:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/CategoryTreeNode'
* additionalProperties: true
* articles: * articles:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/CategoryArticle'
* additionalProperties: true
* 400: * 400:
* description: No company selected * description: No company selected
* content: * content:
@@ -326,48 +324,7 @@ export function register(app, basePath = '') {
* articles: * articles:
* type: array * type: array
* items: * items:
* type: object * $ref: '#/components/schemas/CategoryArticle'
* properties:
* id:
* type: integer
* sku:
* type: string
* name:
* type: string
* slug:
* type: string
* net:
* type: number
* nullable: true
* taxRate:
* type: number
* nullable: true
* track_stock:
* type: boolean
* optionsCount:
* type: integer
* articleListCount:
* type: integer
* childCount:
* type: integer
* description: Number of child articles belonging to this article
* fullHash:
* type: string
* nullable: true
* thumbW:
* type: integer
* nullable: true
* thumbH:
* type: integer
* nullable: true
* thumbHash:
* type: string
* nullable: true
* brand:
* type: object
* nullable: true
* additionalProperties: true
* additionalProperties: true
* 400: * 400:
* description: Invalid id * description: Invalid id
* content: * content:

View File

@@ -1,15 +1,15 @@
import * as db from '../../db/pic.js'; import * as db from '../../db/pic.js';
const FULL_HASH_RE = /^[a-fA-F0-9]{64}$/; const FULL_HASH_RE = /^[a-zA-Z0-9]{8}$/;
/** Content-addressed by SHA-256 — safe for long-lived browser cache (auth still required on first fetch). */ /** Content-addressed by SHA-256 (Base62, 8 chars) — safe for long-lived browser cache (auth still required on first fetch). */
const CACHE_CONTROL = 'private, max-age=31536000, immutable'; const CACHE_CONTROL = 'private, max-age=31536000, immutable';
function parseFullHash(value) { function parseFullHash(value) {
const fullHash = typeof value === 'string' ? value.trim() : ''; const fullHash = typeof value === 'string' ? value.trim() : '';
if (!FULL_HASH_RE.test(fullHash)) { if (!FULL_HASH_RE.test(fullHash)) {
return { error: 'fullHash must be a 64-char hex SHA-256' }; return { error: 'fullHash must be an 8-char alphanumeric string' };
} }
return { fullHash: fullHash.toLowerCase() }; return { fullHash };
} }
function picErrorStatus(error) { function picErrorStatus(error) {
@@ -47,7 +47,7 @@ export function register(app, basePath = '') {
* tags: [Image] * tags: [Image]
* summary: Full image by hash * summary: Full image by hash
* description: | * description: |
* Returns the full AVIF by `pic.hash` (SHA-256 hex). * Returns the full AVIF by `pic.hash` (8-char alphanumeric Base62).
* Auth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`). * Auth via article category access. Browser-cacheable (`private`, `immutable`, `ETag`).
* security: * security:
* - cookieAuth: [] * - cookieAuth: []
@@ -58,8 +58,8 @@ export function register(app, basePath = '') {
* required: true * required: true
* schema: * schema:
* type: string * type: string
* pattern: '^[a-fA-F0-9]{64}$' * pattern: '^[a-zA-Z0-9]{8}$'
* description: SHA-256 hex of the full image bytes * description: 8-char alphanumeric hash of the full image bytes
* responses: * responses:
* 200: * 200:
* description: AVIF image bytes * description: AVIF image bytes
@@ -143,8 +143,8 @@ export function register(app, basePath = '') {
* required: true * required: true
* schema: * schema:
* type: string * type: string
* pattern: '^[a-fA-F0-9]{64}$' * pattern: '^[a-zA-Z0-9]{8}$'
* description: SHA-256 hex of the full image bytes * description: 8-char alphanumeric hash of the full image bytes
* responses: * responses:
* 200: * 200:
* description: AVIF thumb bytes * description: AVIF thumb bytes

View File

@@ -479,7 +479,7 @@ export function register(app, basePath = '') {
* Files are written to the company's VFS root directory and saved to the database. * Files are written to the company's VFS root directory and saved to the database.
* An optional `targetDir` form field specifies a subfolder within the VFS. * An optional `targetDir` form field specifies a subfolder within the VFS.
* *
* Writing to read-only database paths (`image/*` or filenames containing 64-character hex hashes) is * Writing to read-only database paths (`image/*` or filenames containing 8-character image hashes) is
* forbidden and automatically skipped during upload. * forbidden and automatically skipped during upload.
* security: * security:
* - cookieAuth: [] * - cookieAuth: []
@@ -813,7 +813,7 @@ export function register(app, basePath = '') {
* - Binary body: raw binary buffer with optional `Content-Type` header * - Binary body: raw binary buffer with optional `Content-Type` header
* - Plain text: raw UTF-8 string * - Plain text: raw UTF-8 string
* *
* Modifying read-only database paths (`image/*` or filenames containing 64-character hex hashes) is blocked with HTTP 403. * Modifying read-only database paths (`image/*` or filenames containing 8-character image hashes) is blocked with HTTP 403.
* security: * security:
* - cookieAuth: [] * - cookieAuth: []
* x-signals: [vfs] * x-signals: [vfs]
@@ -959,7 +959,7 @@ export function register(app, basePath = '') {
* summary: Delete a virtual filesystem file * summary: Delete a virtual filesystem file
* description: | * description: |
* Deletes a file from both the on-disk virtual filesystem and database for the authenticated user's selected company. * Deletes a file from both the on-disk virtual filesystem and database for the authenticated user's selected company.
* Deleting read-only database files (`image/*` or filenames containing 64-character hex hashes) is blocked with HTTP 403. * Deleting read-only database files (`image/*` or filenames containing 8-character image hashes) is blocked with HTTP 403.
* security: * security:
* - cookieAuth: [] * - cookieAuth: []
* x-signals: [vfs] * x-signals: [vfs]

View File

@@ -236,7 +236,7 @@ function extractImageInputs(args = {}) {
/** /**
* Fetches a database image by its content hash using store or authenticated local endpoint. * Fetches a database image by its content hash using store or authenticated local endpoint.
* @param {string} hash 64-char SHA-256 hex * @param {string} hash 8-char SHA-256 Base62
* @param {number} googleAuthId * @param {number} googleAuthId
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
@@ -323,7 +323,7 @@ export function createAnalyzeImageTool({ googleAuthId, model }) {
// ── Load the bytes ──────────────────────────────────────────── // ── Load the bytes ────────────────────────────────────────────
const items = []; const items = [];
for (const item of inputs) { for (const item of inputs) {
// Only a path that actually carries a 64-char content hash is a database image. // Only a path that actually carries an 8-char content hash is a database image.
// A bare "images/…" prefix is just the VFS folder, so it must NOT be treated as // A bare "images/…" prefix is just the VFS folder, so it must NOT be treated as
// a DB lookup or `images/candidates/x.avif` would be misread as a hash path. // a DB lookup or `images/candidates/x.avif` would be misread as a hash path.
const hash = extractFullHash(item.path); const hash = extractFullHash(item.path);
@@ -355,7 +355,7 @@ export function createAnalyzeImageTool({ googleAuthId, model }) {
throw new ToolError( throw new ToolError(
`Image not found or empty: "${item.path}". ` + `Image not found or empty: "${item.path}". ` +
(hash (hash
? 'Database images must be addressed as "image/<fullHash>" with a 64-char hash.' ? 'Database images must be addressed as "image/<fullHash>" with an 8-char hash.'
: 'Store it first (search_images writes to "images/…") or check the path with find_files.') : 'Store it first (search_images writes to "images/…") or check the path with find_files.')
); );
} }

View File

@@ -134,7 +134,7 @@ function resolveImagesApiUrl() {
} }
/** /**
* Fetches a database image by its 64-char content hash using store or authenticated local endpoint. * Fetches a database image by its 8-char content hash using store or authenticated local endpoint.
* @param {string} hash * @param {string} hash
* @param {number} googleAuthId * @param {number} googleAuthId
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}

View File

@@ -4,7 +4,7 @@ import { getStore } from '../db/store.js';
import env from '../config/env.js'; import env from '../config/env.js';
import { getDefaultSkills } from './skill.js'; import { getDefaultSkills } from './skill.js';
const FULL_HASH_REGEX = /^[a-fA-F0-9]{64}$/; const FULL_HASH_REGEX = /^[a-zA-Z0-9]{8}$/;
const VIRTUAL_IMAGE_FOLDERS = new Set(['image', 'images']); const VIRTUAL_IMAGE_FOLDERS = new Set(['image', 'images']);
export function getMimeType(filePath) { export function getMimeType(filePath) {
@@ -55,18 +55,26 @@ export function getMimeType(filePath) {
export function extractFullHash(p) { export function extractFullHash(p) {
if (typeof p !== 'string' || !p.trim()) return null; if (typeof p !== 'string' || !p.trim()) return null;
const clean = p.trim().replace(/^vfs:\/+/i, '').replace(/^([a-zA-Z]:|\/)+/, ''); const clean = p.trim().replace(/^vfs:\/+/i, '').replace(/^([a-zA-Z]:|\/)+/, '');
const parts = clean.split('/'); const parts = clean.split('/').filter(Boolean);
if (parts.length === 0) return null;
const first = parts[0].toLowerCase();
const last = parts[parts.length - 1]; const last = parts[parts.length - 1];
const ext = path.extname(last).toLowerCase();
const withoutExt = last.replace(/\.[a-zA-Z0-9]+$/, ''); const withoutExt = last.replace(/\.[a-zA-Z0-9]+$/, '');
if (FULL_HASH_REGEX.test(withoutExt)) {
return withoutExt.toLowerCase(); // Under virtual image folder (e.g. image/aB1cD2eF, images/aB1cD2eF.avif)
} if (VIRTUAL_IMAGE_FOLDERS.has(first)) {
for (const part of parts) { if (parts.length === 2 && FULL_HASH_REGEX.test(withoutExt)) {
const seg = part.replace(/\.[a-zA-Z0-9]+$/, ''); return withoutExt;
if (FULL_HASH_REGEX.test(seg)) {
return seg.toLowerCase();
} }
} }
// Direct hash (e.g. "aB1cD2eF" or "aB1cD2eF.avif")
if (parts.length === 1 && FULL_HASH_REGEX.test(withoutExt) && (!ext || ext === '.avif')) {
return withoutExt;
}
return null; return null;
} }