From 508e1b6d7a0a77315225a1788ccaf0a36ac46ec2 Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 15 Oct 2026 20:14:54 +0200 Subject: [PATCH] u --- docs/api/README.md | 6 +- docs/api/routes-all.md | 333 +++++++++++---------- docs/api/routes.md | 279 ++++++++--------- docs/api/routes/order/_id/shipment.get.md | 3 +- docs/api/routes/order/_id/shipment.post.md | 26 +- docs/api/routes/order/create.md | 3 +- docs/api/routes/order/get.md | 3 +- docs/api/routes/order/list.md | 3 +- docs/api/routes/order/update.md | 3 +- docs/api/routes/quote/order.md | 11 +- docs/api/routes/shipment.md | 64 ++++ docs/api/routes/shipment/_id.get.md | 3 +- docs/api/routes/shipment/_id.patch.md | 106 +++++++ src/api/openapi.js | 50 ++++ src/api/routes/quote.js | 10 +- src/api/routes/shipment.js | 274 ++++++++++++++++- src/db/order.js | 5 + src/db/shipment.js | 10 + 18 files changed, 859 insertions(+), 333 deletions(-) create mode 100644 docs/api/routes/shipment.md create mode 100644 docs/api/routes/shipment/_id.patch.md diff --git a/docs/api/README.md b/docs/api/README.md index a87bfee..12ef3fc 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -235,8 +235,10 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route | [`routes/quote/_id/pdf.md`](routes/quote/_id/pdf.md) | `POST /quote/{id}/pdf` | | [`routes/serper/image.md`](routes/serper/image.md) | `POST /serper/image` | | [`routes/serper/images.md`](routes/serper/images.md) | `POST /serper/images` | +| [`routes/shipment.md`](routes/shipment.md) | `GET /shipment` | | [`routes/shipment/_id.delete.md`](routes/shipment/_id.delete.md) | `DELETE /shipment/{id}` | | [`routes/shipment/_id.get.md`](routes/shipment/_id.get.md) | `GET /shipment/{id}` | +| [`routes/shipment/_id.patch.md`](routes/shipment/_id.patch.md) | `PATCH /shipment/{id}` | | [`routes/shipment/_id/file/_fileId.delete.md`](routes/shipment/_id/file/_fileId.delete.md) | `DELETE /shipment/{id}/file/{fileId}` | | [`routes/shipment/_id/file/_fileId.get.md`](routes/shipment/_id/file/_fileId.get.md) | `GET /shipment/{id}/file/{fileId}` | | [`routes/shipment/_id/pdf.md`](routes/shipment/_id/pdf.md) | `POST /shipment/{id}/pdf` | @@ -298,11 +300,11 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A | `ledger` | `POST /stock/adjust` | | `modelconversation` | `POST /modelconversation`, `DELETE /modelconversation/{id}` | | `modelconversation-` | `POST /modelconversation/{id}/message` | -| `order` | `DELETE /order/{id}/file/{fileId}`, `POST /order/{id}/pdf`, `DELETE /shipment/{id}/file/{fileId}` | +| `order` | `DELETE /order/{id}/file/{fileId}`, `POST /order/{id}/pdf`, `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}` | | `printer` | `POST /printer`, `PUT /printer/order`, `DELETE /printer/{id}`, `PATCH /printer/{id}`, `PUT /printer/{id}` | | `propertyname` | `POST /propertyname`, `PUT /propertyname/order`, `DELETE /propertyname/{id}`, `PATCH /propertyname/{id}` | | `quote` | `DELETE /quote/{id}/file/{fileId}`, `POST /quote/{id}/pdf` | -| `shipment` | `DELETE /shipment/{id}/file/{fileId}`, `POST /shipment/{id}/pdf` | +| `shipment` | `PATCH /shipment/{id}`, `DELETE /shipment/{id}/file/{fileId}`, `POST /shipment/{id}/pdf` | | `stock` | `POST /stock/adjust` | | `vfs` | `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` | | `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` | diff --git a/docs/api/routes-all.md b/docs/api/routes-all.md index 2a460b3..47a84d9 100644 --- a/docs/api/routes-all.md +++ b/docs/api/routes-all.md @@ -1,6 +1,6 @@ # Dash API Routes Overview (All) -Overview of all 247 API endpoints across all modules. +Overview of all 249 API endpoints across all modules. ### Notation @@ -10,61 +10,62 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). ### Reusable types - `T1` = `{ order: T2 }` -- `T2` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T29[], externalNumber?: str|null, createdAt?: str, shipping?: T37|null, shippings?: T37[], payment?: T43|null, payments?: T43[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T5[], files?: T15[] }` +- `T2` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T30[], externalNumber?: str|null, createdAt?: str, shipping?: T38|null, shippings?: T38[], payment?: T44|null, payments?: T44[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T6[], files?: T16[] }` - `T3` = `{ article: T4 }` -- `T4` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T40, brand: T38|null, categoryIds: int[], photos: T35[], eans: str[], prices: T47[], suppliers: T17[] }` -- `T5` = `{ id: int, number: str, type: str, date: str, lines: T29[], shippings: T37[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T15[] }` -- `T6` = `{ quote: T7 }` -- `T7` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T29[], externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T15[] }` -- `T8` = `{ delivery: T9 }` -- `T9` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T29[], files: T15[], externalNumber?: str|null, createdAt?: str, ledger?: T51 }` -- `T10` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T11|null }` -- `T11` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T18, totalSizeBytes: int, durationMs: int }` -- `T12` = `{ labeltemplate: T14 }` -- `T13` = `{ attachment: T15, fileUrl: str }` -- `T14` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: T25, thumb?: str|null }` -- `T15` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }` -- `T16` = `{ id: int, name: str, pairingpin?: str|null, token?: str|null, pairedAt?: str|null, clientserial?: str|null, rootCategoryId?: int|null, syncRootArticles?: bool, lastseenAt?: str|null, updatedAt?: str|null, categoryIds?: int[] }` -- `T17` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T54|null }` -- `T18` = `{ vfs: T52, storage: T52, database: T45 }` -- `T19` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` -- `T20` = `{ ledger: T21[] }` -- `T21` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }` -- `T22` = `{ id: int, name: str, sort: int, exclusive: bool, values: T41[], articleCount?: int }` -- `T23` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: integer,null, first_message?: string,null }` -- `T24` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }` -- `T25` = `{ objects: T30[] }` +- `T4` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T41, brand: T39|null, categoryIds: int[], photos: T36[], eans: str[], prices: T48[], suppliers: T18[] }` +- `T5` = `{ shipment: T6 }` +- `T6` = `{ id: int, number: str, type: str, date: str, lines: T30[], shippings: T38[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T16[], state?: str }` +- `T7` = `{ quote: T8 }` +- `T8` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T30[], externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T16[] }` +- `T9` = `{ delivery: T10 }` +- `T10` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T30[], files: T16[], externalNumber?: str|null, createdAt?: str, ledger?: T52 }` +- `T11` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T12|null }` +- `T12` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T19, totalSizeBytes: int, durationMs: int }` +- `T13` = `{ labeltemplate: T15 }` +- `T14` = `{ attachment: T16, fileUrl: str }` +- `T15` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: T26, thumb?: str|null }` +- `T16` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }` +- `T17` = `{ id: int, name: str, pairingpin?: str|null, token?: str|null, pairedAt?: str|null, clientserial?: str|null, rootCategoryId?: int|null, syncRootArticles?: bool, lastseenAt?: str|null, updatedAt?: str|null, categoryIds?: int[] }` +- `T18` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T55|null }` +- `T19` = `{ vfs: T53, storage: T53, database: T46 }` +- `T20` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` +- `T21` = `{ ledger: T22[] }` +- `T22` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }` +- `T23` = `{ id: int, name: str, sort: int, exclusive: bool, values: T42[], articleCount?: int }` +- `T24` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: integer,null, first_message?: string,null }` +- `T25` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }` - `T26` = `{ objects: T31[] }` -- `T27` = `{ printer: T33 }` -- `T28` = `{ entity: T36 }` -- `T29` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }` -- `T30` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` +- `T27` = `{ objects: T32[] }` +- `T28` = `{ printer: T34 }` +- `T29` = `{ entity: T37 }` +- `T30` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }` - `T31` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` -- `T32` = `{ shipping: T37 }` -- `T33` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }` -- `T34` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }` -- `T35` = `{ sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }` -- `T36` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }` -- `T37` = `{ id: int, manifestId: int, number: str, type: str, state: str, trackingCode?: str|null, carrier?: str|null }` -- `T38` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` -- `T39` = `{ article: T44 }` -- `T40` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` -- `T41` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }` -- `T42` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }` -- `T43` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }` -- `T44` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` -- `T45` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }` -- `T46` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }` -- `T47` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` -- `T48` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` -- `T49` = `{ id: int, entityId: int, contactType: str, contact: str }` -- `T50` = `{ id: int, name: str, createdAt: str, updatedAt: str }` -- `T51` = `{ totalPositions: int, completelyOnLedgerPositions: int }` -- `T52` = `{ name: str, sizeBytes: int, sourceExists: bool }` -- `T53` = `{ warehouse: T55 }` -- `T54` = `{ net: num, taxId: int, taxRate?: num|null }` -- `T55` = `{ id: int, name: str, sort: int }` -- `T56` = `{ id: int, categoryIds: int[] }` +- `T32` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` +- `T33` = `{ shipping: T38 }` +- `T34` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }` +- `T35` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }` +- `T36` = `{ sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }` +- `T37` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }` +- `T38` = `{ id: int, manifestId: int, number: str, type: str, state: str, trackingCode?: str|null, carrier?: str|null }` +- `T39` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` +- `T40` = `{ article: T45 }` +- `T41` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` +- `T42` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }` +- `T43` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }` +- `T44` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }` +- `T45` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` +- `T46` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }` +- `T47` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }` +- `T48` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` +- `T49` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` +- `T50` = `{ id: int, entityId: int, contactType: str, contact: str }` +- `T51` = `{ id: int, name: str, createdAt: str, updatedAt: str }` +- `T52` = `{ totalPositions: int, completelyOnLedgerPositions: int }` +- `T53` = `{ name: str, sizeBytes: int, sourceExists: bool }` +- `T54` = `{ warehouse: T56 }` +- `T55` = `{ net: num, taxId: int, taxRate?: num|null }` +- `T56` = `{ id: int, name: str, sort: int }` +- `T57` = `{ id: int, categoryIds: int[] }` ### Routes @@ -94,7 +95,7 @@ _none_ #### Create article in category — `POST /article` - Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }` -- Returns: `201 T39` +- Returns: `201 T40` #### Export articles as ZIP — `GET /article/export.zip` - Params: `query: categoryId?: int` - Returns: `200 zip` @@ -130,10 +131,10 @@ _none_ - Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Create and attach a new child article to a parent article — `POST /article/{sku}/children` - Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }` -- Returns: `201 T39` +- Returns: `201 T40` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` -- Returns: `200 { article: T44, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` +- Returns: `200 { article: T45, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` - Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Returns: `200 { success: bool, deleted: bool }` @@ -160,7 +161,7 @@ _none_ - Returns: `200 { options: { id: int, articleId: int, sku: str, name: str, count: int, sort: int, mandated: bool }[] }` #### Toggle parent mode for an article — `POST /article/{sku}/parent` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` -- Returns: `200 T39` +- Returns: `200 T40` #### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices` - Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }` - Returns: `200 T3` @@ -178,10 +179,10 @@ _none_ - Returns: `200 { ok: bool }` #### List article suppliers — `GET /article/{sku}/supplier` - Params: `sku: str` -- Returns: `200 { suppliers: T17[] }` +- Returns: `200 { suppliers: T18[] }` #### Assign supplier to article — `POST /article/{sku}/supplier` - Params: `sku: str, body: { entityId: int, sku?: str|null, pu?: num, stock?: num|null, sort?: int|null, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }` -- Returns: `201 { supplier: T17 }` +- Returns: `201 { supplier: T18 }` #### Reorder article suppliers — `PUT /article/{sku}/supplier/order` - Params: `sku: str, body: { ids: int[] }` - Returns: `200 { ids: int[] }` @@ -190,7 +191,7 @@ _none_ - Returns: `204 void` #### Update article supplier assignment — `PATCH /article/{sku}/supplier/{id}` - Params: `sku: str, id: int, body: { sku?: str|null, pu?: num, stock?: num|null, sort?: int, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }` -- Returns: `200 { supplier: T17 }` +- Returns: `200 { supplier: T18 }` #### Set article track_stock flag by SKU — `PUT /article/{sku}/track_stock` - Params: `sku: str, body: { track_stock: bool }` - Returns: `200 T3` @@ -202,19 +203,19 @@ _none_ #### List attribute names — `GET /attributename` _none_ -- Returns: `200 { attributenames: T22[] }` +- Returns: `200 { attributenames: T23[] }` #### Create attribute name — `POST /attributename` - Params: `body: { name: str, sort?: int, exclusive?: bool }` -- Returns: `201 { attributename: T22 }` +- Returns: `201 { attributename: T23 }` #### Reorder attribute names — `PUT /attributename/order` - Params: `body: { attributenameIds: int[] }` - Returns: `200 { ok: bool }` #### List values for an attribute — `GET /attributename/{attributeId}/value` - Params: `attributeId: int` -- Returns: `200 { values: T41[] }` +- Returns: `200 { values: T42[] }` #### Create value for an attribute — `POST /attributename/{attributeId}/value` - Params: `attributeId: int, body: { value: str, sort?: int }` -- Returns: `201 { attributevalue: T41 }` +- Returns: `201 { attributevalue: T42 }` #### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order` - Params: `attributeId: int, body: { attributevalueIds: int[] }` - Returns: `200 { ok: bool }` @@ -223,7 +224,7 @@ _none_ - Returns: `200 { ok: bool }` #### Update attribute name — `PATCH /attributename/{id}` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` -- Returns: `200 { attributename: T22 }` +- Returns: `200 { attributename: T23 }` ### Section: /attributevalue @@ -232,34 +233,34 @@ _none_ - Returns: `200 { ok: bool }` #### Update attribute value — `PATCH /attributevalue/{id}` - Params: `id: int, body: { value?: str, sort?: int }` -- Returns: `200 { attributevalue: T41 }` +- Returns: `200 { attributevalue: T42 }` #### Delete attribute value image — `DELETE /attributevalue/{id}/image` - Params: `id: int` -- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T48[] } }` +- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T49[] } }` #### Add or replace attribute value image — `POST /attributevalue/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T48[] } }` +- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T49[] } }` ### Section: /auth #### Login with Google ID token — `POST /auth/login` - Params: `body: { idToken: str }` -- Returns: `200 { message: str, user: T46 }` +- Returns: `200 { message: str, user: T47 }` #### Logout — `POST /auth/logout` _none_ - Returns: `200 { message: str }` #### Current user — `GET /auth/me` _none_ -- Returns: `200 { user: T46, googleClientId: str }` +- Returns: `200 { user: T47, googleClientId: str }` #### Set selected company — `PUT /auth/selected-company` - Params: `body: { companyId: int }` -- Returns: `200 { user: T46 }` +- Returns: `200 { user: T47 }` ### Section: /backup #### List backups and service status — `GET /backup` _none_ -- Returns: `200 { status: { enabled: bool, isRunning: bool, dbProvider: str, mssqlWarning: bool, backupDir: str, retention: { hourlyMax: int, dailyMax: int, monthly: str }, counts: { hourly: int, daily: int, monthly: int }, latestHourly?: T10|null, latestDaily?: T10|null, latestMonthly?: T10|null }, backups: { counts: { hourly: int, daily: int, monthly: int }, hourly: T10[], daily: T10[], monthly: T10[] } }` +- Returns: `200 { status: { enabled: bool, isRunning: bool, dbProvider: str, mssqlWarning: bool, backupDir: str, retention: { hourlyMax: int, dailyMax: int, monthly: str }, counts: { hourly: int, daily: int, monthly: int }, latestHourly?: T11|null, latestDaily?: T11|null, latestMonthly?: T11|null }, backups: { counts: { hourly: int, daily: int, monthly: int }, hourly: T11[], daily: T11[], monthly: T11[] } }` #### List VFS files in a backup — `GET /backup/{id}/vfs` - Params: `id: str, query: companyId?: int` - Returns: `200 { backupId: str, companyId: int, files: { name: str, size: int, mime: str, mtime: str }[] }` @@ -274,7 +275,7 @@ _none_ - Returns: `200 { brands: { id: int, name: str, articleCount: int, pics: { fullHash: str, thumbW: int, thumbH: int, thumbHash: str, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null }[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }` #### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `201 { brand: { id: int, name: str, pics: T48[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T49[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` @@ -283,10 +284,10 @@ _none_ - Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` -- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T48[] } }` +- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T49[] } }` #### Add or replace brand image — `POST /brand/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T48[] } }` +- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T49[] } }` ### Section: /category @@ -310,7 +311,7 @@ _none_ - Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }` #### Articles in category — `GET /category/{id}/articles` - Params: `id: int` -- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, brand?: T38|null }[] }` +- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, brand?: T39|null }[] }` #### List category attribute values — `GET /category/{id}/attributevalue` - Params: `id: int` - Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }` @@ -337,10 +338,10 @@ _none_ #### List companies — `GET /company` _none_ -- Returns: `200 { companies: T24[] }` +- Returns: `200 { companies: T25[] }` #### Create company — `POST /company` - Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` -- Returns: `201 { company: T24, user: T46 }` +- Returns: `201 { company: T25, user: T47 }` #### Export company catalog as zip archive (SSE) — `GET /company/export` - Params: `query: stream?: str` - Returns: `200 SSE` @@ -370,7 +371,7 @@ _none_ - Returns: `204 void` #### Update company — `PUT /company/{id}` - Params: `id: int, body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }` -- Returns: `200 { company: T24 }` +- Returns: `200 { company: T25 }` ### Section: /customergroup @@ -379,7 +380,7 @@ _none_ - Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }` #### Create customer group — `POST /customergroup` - Params: `body: { name: str }` -- Returns: `201 { customergroup: T55 }` +- Returns: `201 { customergroup: T56 }` #### Reorder customer groups — `PUT /customergroup/order` - Params: `body: { customergroupIds: int[] }` - Returns: `200 { customergroupIds: int[] }` @@ -388,16 +389,16 @@ _none_ - Returns: `204 void` #### Update customer group — `PATCH /customergroup/{id}` - Params: `id: int, body: { name: str }` -- Returns: `200 { customergroup: T55 }` +- Returns: `200 { customergroup: T56 }` ### Section: /delivery #### List deliveries — `GET /delivery` _none_ -- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int|null, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T51 }[] }` +- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int|null, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T52 }[] }` #### Create delivery — `POST /delivery` -- Params: `body: { externalNumber: str, date: str, lines: T34[], supplierId?: int|null, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` -- Returns: `201 T8` +- Params: `body: { externalNumber: str, date: str, lines: T35[], supplierId?: int|null, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` +- Returns: `201 T9` #### Parse document (SSE) — `POST /delivery/parse` - Params: `form: { file: str }` - Returns: `200 SSE` @@ -415,10 +416,10 @@ _none_ - Returns: `204 void` #### Get delivery — `GET /delivery/{id}` - Params: `id: int` -- Returns: `200 T8` +- Returns: `200 T9` #### Update delivery — `PATCH /delivery/{id}` -- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T34[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` -- Returns: `200 T8` +- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T35[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` +- Returns: `200 T9` #### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` @@ -427,52 +428,52 @@ _none_ - Returns: `200 str` #### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf` - Params: `id: int` -- Returns: `201 T13` +- Returns: `201 T14` ### Section: /entity #### List entities — `GET /entity` - Params: `query: customergroupId?: int, role?: str` -- Returns: `200 { entities: T36[] }` +- Returns: `200 { entities: T37[] }` #### Create entity — `POST /entity` - Params: `body: { customergroupId: int, name: str, firstName?: str|null, number?: str }` -- Returns: `201 T28` +- Returns: `201 T29` #### Peek next customer number — `GET /entity/next-number` _none_ - Returns: `200 { number: str }` #### List entity addresses — `GET /entity/{entityId}/address` - Params: `entityId: int` -- Returns: `200 { addresses: T19[] }` +- Returns: `200 { addresses: T20[] }` #### Create entity address — `POST /entity/{entityId}/address` - Params: `entityId: int, body: { addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` -- Returns: `201 { address: T19 }` +- Returns: `201 { address: T20 }` #### Delete entity address — `DELETE /entity/{entityId}/address/{id}` - Params: `entityId: int, id: int` - Returns: `204 void` #### Update entity address — `PATCH /entity/{entityId}/address/{id}` - Params: `entityId: int, id: int, body: { addressType?: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` -- Returns: `200 { address: T19 }` +- Returns: `200 { address: T20 }` #### List entity contacts — `GET /entity/{entityId}/contact` - Params: `entityId: int` -- Returns: `200 { contacts: T49[] }` +- Returns: `200 { contacts: T50[] }` #### Create entity contact — `POST /entity/{entityId}/contact` - Params: `entityId: int, body: { contactType: str, contact: str }` -- Returns: `201 { contact: T49 }` +- Returns: `201 { contact: T50 }` #### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}` - Params: `entityId: int, id: int` - Returns: `204 void` #### Update entity contact — `PATCH /entity/{entityId}/contact/{id}` - Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }` -- Returns: `200 { contact: T49 }` +- Returns: `200 { contact: T50 }` #### Delete entity — `DELETE /entity/{id}` - Params: `id: int` - Returns: `204 void` #### Get entity — `GET /entity/{id}` - Params: `id: int` -- Returns: `200 T28` +- Returns: `200 T29` #### Update entity — `PATCH /entity/{id}` - Params: `id: int, body: { name?: str, firstName?: str|null, number?: str|null }` -- Returns: `200 T28` +- Returns: `200 T29` ### Section: /events @@ -516,35 +517,35 @@ _none_ _none_ - Returns: `200 { labeltemplates: { id: int, name: str, width: num, height: num, dpi: int, type: str, thumb?: str|null }[] }` #### Create label template — `POST /labeltemplate` -- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: T26 }` -- Returns: `201 T12` +- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: T27 }` +- Returns: `201 T13` #### Delete label template — `DELETE /labeltemplate/{id}` - Params: `id: int` - Returns: `204 void` #### Get label template by ID — `GET /labeltemplate/{id}` - Params: `id: int` -- Returns: `200 T12` +- Returns: `200 T13` #### Update label template (partial) — `PATCH /labeltemplate/{id}` -- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T26 }` -- Returns: `200 T12` +- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T27 }` +- Returns: `200 T13` #### Update label template — `PUT /labeltemplate/{id}` -- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T26 }` -- Returns: `200 T12` +- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T27 }` +- Returns: `200 T13` ### Section: /ledger #### Query ledger by manifest article ID or SKU — `GET /ledger` - Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str` -- Returns: `200 T20` +- Returns: `200 T21` #### Add ledger entry — `POST /ledger` - Params: `body: { manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str }` -- Returns: `201 { ledger: T21 }` +- Returns: `201 { ledger: T22 }` #### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}` - Params: `manifestarticleId: int, query: transaction_type?: str` -- Returns: `200 T20` +- Returns: `200 T21` #### Query ledger by article SKU — `GET /ledger/sku/{sku}` - Params: `sku: str, query: transaction_type?: str` -- Returns: `200 T20` +- Returns: `200 T21` ### Section: /mcp @@ -574,16 +575,16 @@ _none_ #### List conversations — `GET /modelconversation` _none_ -- Returns: `200 { conversations: T23[] }` +- Returns: `200 { conversations: T24[] }` #### Create conversation — `POST /modelconversation` - Params: `body: { provider: str, model: str }` -- Returns: `201 { conversation: T23 }` +- Returns: `201 { conversation: T24 }` #### Delete conversation — `DELETE /modelconversation/{id}` - Params: `id: int` - Returns: `204 void` #### Get conversation — `GET /modelconversation/{id}` - Params: `id: int` -- Returns: `200 { conversation: T23 }` +- Returns: `200 { conversation: T24 }` #### List conversation messages — `GET /modelconversation/{id}/message` - Params: `id: int` - Returns: `200 { messages: { id: int, conversation_id: int, created_at: str, role: str, message: str }[] }` @@ -624,7 +625,7 @@ _none_ - Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int` - Returns: `200 { orders: T2[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Create order — `POST /order` -- Params: `body: { number: str, date: str, lines: T34[], entityId?: int|null, externalNumber?: str|null }` +- Params: `body: { number: str, date: str, lines: T35[], entityId?: int|null, externalNumber?: str|null }` - Returns: `201 T1` #### Delete order — `DELETE /order/{id}` - Params: `id: int` @@ -633,7 +634,7 @@ _none_ - Params: `id: int` - Returns: `200 T1` #### Update order — `PATCH /order/{id}` -- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T34[] }` +- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T35[] }` - Returns: `200 T1` #### Delete order file attachment — `DELETE /order/{id}/file/{fileId}` - Params: `id: int, fileId: int` @@ -643,25 +644,25 @@ _none_ - Returns: `200 str` #### List payments of an order — `GET /order/{id}/payment` - Params: `id: int` -- Returns: `200 { payments: T43[] }` +- Returns: `200 { payments: T44[] }` #### Add payment to an order — `POST /order/{id}/payment` - Params: `id: int, body: { accountId: int, type: str, amount: num }` -- Returns: `201 { payment: T43 }` +- Returns: `201 { payment: T44 }` #### Generate and attach order PDF — `POST /order/{id}/pdf` - Params: `id: int` -- Returns: `201 T13` +- Returns: `201 T14` #### List shipments of an order — `GET /order/{id}/shipment` - Params: `id: int` -- Returns: `200 { shipments: T5[] }` +- Returns: `200 { shipments: T6[] }` #### Create shipment for an order — `POST /order/{id}/shipment` -- Params: `id: int, body: { lines: { sku: str, amount: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[], date?: str, externalNumber?: str }` -- Returns: `201 { shipment: T5 }` +- Params: `id: int, body?: { date?: str, externalNumber?: str, lines?: { sku: str, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[] }` +- Returns: `201 T5` #### List shippings of an order — `GET /order/{id}/shipping` - Params: `id: int` -- Returns: `200 { shippings: T37[] }` +- Returns: `200 { shippings: T38[] }` #### Create shipping for an order — `POST /order/{id}/shipping` - Params: `id: int, body: { type: str, state: str, warehouseId?: int }` -- Returns: `201 T32` +- Returns: `201 T33` ### Section: /package @@ -679,37 +680,37 @@ _none_ #### List POS terminals — `GET /posterminal` _none_ -- Returns: `200 { posterminals: T16[] }` +- Returns: `200 { posterminals: T17[] }` #### Create POS terminal — `PUT /posterminal` - Params: `body: { name: str, rootCategoryId?: int|null, syncRootArticles?: bool }` -- Returns: `200 { posterminal: T16 }` +- Returns: `200 { posterminal: T17 }` #### Delete POS terminal — `DELETE /posterminal/{id}` - Params: `id: int` - Returns: `204 void` #### Update POS terminal — `PATCH /posterminal/{id}` - Params: `id: int, body: { name?: str, rootCategoryId?: int|null, syncRootArticles?: bool, pairingpin?: str|null }` -- Returns: `200 { posterminal: T16 }` +- Returns: `200 { posterminal: T17 }` #### Disable all categories on terminal — `DELETE /posterminal/{id}/all` - Params: `id: int` -- Returns: `200 T56` +- Returns: `200 T57` #### Enable all categories on terminal — `PUT /posterminal/{id}/all` - Params: `id: int` -- Returns: `200 T56` +- Returns: `200 T57` #### Disable category on terminal — `DELETE /posterminal/{id}/category/{categoryId}` - Params: `id: int, categoryId: int` -- Returns: `200 T56` +- Returns: `200 T57` #### Enable category on terminal — `PUT /posterminal/{id}/category/{categoryId}` - Params: `id: int, categoryId: int` -- Returns: `200 T56` +- Returns: `200 T57` ### Section: /printer #### List printers — `GET /printer` _none_ -- Returns: `200 { printers: T33[] }` +- Returns: `200 { printers: T34[] }` #### Create printer — `POST /printer` - Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }` -- Returns: `201 T27` +- Returns: `201 T28` #### Reorder printers — `PUT /printer/order` - Params: `body: { printerIds: int[] }` - Returns: `200 { printerIds: int[] }` @@ -718,22 +719,22 @@ _none_ - Returns: `204 void` #### Get printer by ID — `GET /printer/{id}` - Params: `id: int` -- Returns: `200 T27` +- Returns: `200 T28` #### Update printer (partial) — `PATCH /printer/{id}` - Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }` -- Returns: `200 T27` +- Returns: `200 T28` #### Update printer — `PUT /printer/{id}` - Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }` -- Returns: `200 T27` +- Returns: `200 T28` ### Section: /propertyname #### List property names — `GET /propertyname` _none_ -- Returns: `200 { propertynames: T42[] }` +- Returns: `200 { propertynames: T43[] }` #### Create property name — `POST /propertyname` - Params: `body: { name: str, sort?: int, exclusive?: bool }` -- Returns: `201 { propertyname: T42 }` +- Returns: `201 { propertyname: T43 }` #### Reorder property names — `PUT /propertyname/order` - Params: `body: { propertynameIds: int[] }` - Returns: `200 { ok: bool }` @@ -742,7 +743,7 @@ _none_ - Returns: `200 { ok: bool }` #### Update property name — `PATCH /propertyname/{id}` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` -- Returns: `200 { propertyname: T42 }` +- Returns: `200 { propertyname: T43 }` ### Section: /public @@ -763,19 +764,19 @@ _none_ #### List quotes — `GET /quote` - Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int` -- Returns: `200 { quotes: T7[], total: int, unfilteredTotal: int, offset: int, limit: int }` +- Returns: `200 { quotes: T8[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Create quote — `POST /quote` -- Params: `body: { number: str, date: str, lines: T34[], entityId?: int|null, externalNumber?: str|null }` -- Returns: `201 T6` +- Params: `body: { number: str, date: str, lines: T35[], entityId?: int|null, externalNumber?: str|null }` +- Returns: `201 T7` #### Delete quote — `DELETE /quote/{id}` - Params: `id: int` - Returns: `204 void` #### Get quote — `GET /quote/{id}` - Params: `id: int` -- Returns: `200 T6` +- Returns: `200 T7` #### Update quote — `PATCH /quote/{id}` -- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T34[] }` -- Returns: `200 T6` +- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T35[] }` +- Returns: `200 T7` #### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` @@ -783,11 +784,11 @@ _none_ - Params: `id: int, fileId: int, query: download?: bool` - Returns: `200 str` #### Create order from quote — `POST /quote/{id}/order` -- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T34[] }` +- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T35[] }` - Returns: `201 T1` #### Generate and attach quote PDF — `POST /quote/{id}/pdf` - Params: `id: int` -- Returns: `201 T13` +- Returns: `201 T14` ### Section: /serper @@ -806,12 +807,18 @@ _none_ ### Section: /shipment +#### List shipments — `GET /shipment` +- Params: `query: offset?: int, limit?: int, search?: str, state?: str, seekMonths?: int` +- Returns: `200 { shipments: { id: int, number: str, type: str, date: str, positions: int, totalQuantity: num, packageCount: int, state: str, entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, orderId?: int|null, orderNumber?: str|null, customerName?: str|null, carrier?: str|null, trackingCode?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Delete shipment — `DELETE /shipment/{id}` - Params: `id: int` - Returns: `204 void` #### Get shipment by ID — `GET /shipment/{id}` - Params: `id: int` -- Returns: `200 { shipment: T5 }` +- Returns: `200 T5` +#### Update shipment state or metadata — `PATCH /shipment/{id}` +- Params: `id: int, body: { state?: str }` +- Returns: `200 T5` #### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` @@ -820,13 +827,13 @@ _none_ - Returns: `200 str` #### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf` - Params: `id: int` -- Returns: `201 T13` +- Returns: `201 T14` #### List packages of a shipment — `GET /shipment/{id}/shipping` - Params: `id: int` -- Returns: `200 { shippings: T37[] }` +- Returns: `200 { shippings: T38[] }` #### Create package for a shipment — `POST /shipment/{id}/shipping` - Params: `id: int, body: { type: str, state: str, trackingCode?: str, carrier?: str, warehouseId?: int }` -- Returns: `201 T32` +- Returns: `201 T33` ### Section: /shipping @@ -835,7 +842,7 @@ _none_ - Returns: `204 void` #### Update shipping — `PATCH /shipping/{id}` - Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrier?: str, warehouseId?: int }` -- Returns: `200 T32` +- Returns: `200 T33` #### List packages of a shipping — `GET /shipping/{id}/package` - Params: `id: int` - Returns: `200 { packages: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null }[] }` @@ -853,28 +860,28 @@ _none_ #### Adjust stock manually — `POST /stock/adjust` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` -- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T21[] }` +- 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: T22[] }` ### Section: /supplier #### List suppliers — `GET /supplier` _none_ -- Returns: `200 { suppliers: T50[] }` +- Returns: `200 { suppliers: T51[] }` #### Create supplier — `POST /supplier` - Params: `body: { name: str }` -- Returns: `201 { supplier: T50 }` +- Returns: `201 { supplier: T51 }` #### Delete supplier — `DELETE /supplier/{id}` - Params: `id: int` - Returns: `204 void` #### Update supplier — `PATCH /supplier/{id}` - Params: `id: int, body: { name: str }` -- Returns: `200 { supplier: T50 }` +- Returns: `200 { supplier: T51 }` #### List supplier articles by SKUs — `GET /supplier/{id}/articles` - Params: `id: int, query: skus?: string` -- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T54|null }[] }` +- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T55|null }[] }` #### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles` - Params: `id: int, body?: { skus?: str[] }` -- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T54|null }[] }` +- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T55|null }[] }` ### Section: /thumb @@ -889,10 +896,10 @@ _none_ - Returns: `204 void` #### List units — `GET /unit` _none_ -- Returns: `200 { units: T40[] }` +- Returns: `200 { units: T41[] }` #### Upsert unit — `PUT /unit` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` -- Returns: `200 { unit: T40 }` +- Returns: `200 { unit: T41 }` ### Section: /v1 @@ -946,10 +953,10 @@ _none_ #### List warehouses — `GET /warehouse` _none_ -- Returns: `200 { warehouses: T55[] }` +- Returns: `200 { warehouses: T56[] }` #### Create warehouse — `POST /warehouse` - Params: `body: { name: str, sort?: int }` -- Returns: `201 T53` +- Returns: `201 T54` #### Reorder warehouses — `PUT /warehouse/order` - Params: `body: { warehouseIds: int[] }` - Returns: `200 { warehouseIds: int[] }` @@ -958,10 +965,10 @@ _none_ - Returns: `204 void` #### Get warehouse by ID — `GET /warehouse/{id}` - Params: `id: int` -- Returns: `200 T53` +- Returns: `200 T54` #### Update warehouse (partial) — `PATCH /warehouse/{id}` - Params: `id: int, body: { name?: str, sort?: int }` -- Returns: `200 T53` +- Returns: `200 T54` #### Update warehouse — `PUT /warehouse/{id}` - Params: `id: int, body: { name?: str, sort?: int }` -- Returns: `200 T53` +- Returns: `200 T54` diff --git a/docs/api/routes.md b/docs/api/routes.md index f962076..2b9d6f2 100644 --- a/docs/api/routes.md +++ b/docs/api/routes.md @@ -1,6 +1,6 @@ # Dash API Routes Overview -Overview of all 172 API endpoints across all modules. +Overview of all 174 API endpoints across all modules. ### Notation @@ -10,51 +10,52 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). ### Reusable types - `T1` = `{ order: T2 }` -- `T2` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T23[], externalNumber?: str|null, createdAt?: str, shipping?: T31|null, shippings?: T31[], payment?: T37|null, payments?: T37[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T5[], files?: T13[] }` +- `T2` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T24[], externalNumber?: str|null, createdAt?: str, shipping?: T32|null, shippings?: T32[], payment?: T38|null, payments?: T38[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T6[], files?: T14[] }` - `T3` = `{ article: T4 }` -- `T4` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T34, brand: T32|null, categoryIds: int[], photos: T29[], eans: str[], prices: T39[], suppliers: T14[] }` -- `T5` = `{ id: int, number: str, type: str, date: str, lines: T23[], shippings: T31[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T13[] }` -- `T6` = `{ quote: T7 }` -- `T7` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T23[], externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T13[] }` -- `T8` = `{ delivery: T9 }` -- `T9` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T23[], files: T13[], externalNumber?: str|null, createdAt?: str, ledger?: T43 }` -- `T10` = `{ labeltemplate: T12 }` -- `T11` = `{ attachment: T13, fileUrl: str }` -- `T12` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: T19, thumb?: str|null }` -- `T13` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }` -- `T14` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T45|null }` -- `T15` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` -- `T16` = `{ ledger: T17[] }` -- `T17` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }` -- `T18` = `{ id: int, name: str, sort: int, exclusive: bool, values: T35[], articleCount?: int }` -- `T19` = `{ objects: T24[] }` +- `T4` = `{ id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T35, brand: T33|null, categoryIds: int[], photos: T30[], eans: str[], prices: T40[], suppliers: T15[] }` +- `T5` = `{ shipment: T6 }` +- `T6` = `{ id: int, number: str, type: str, date: str, lines: T24[], shippings: T32[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T14[], state?: str }` +- `T7` = `{ quote: T8 }` +- `T8` = `{ id: int, number: str, type: str, entityId: int|null, date: str, lines: T24[], externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T14[] }` +- `T9` = `{ delivery: T10 }` +- `T10` = `{ id: int, number: str, type: str, supplierId: int|null, date: str, lines: T24[], files: T14[], externalNumber?: str|null, createdAt?: str, ledger?: T44 }` +- `T11` = `{ labeltemplate: T13 }` +- `T12` = `{ attachment: T14, fileUrl: str }` +- `T13` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: T20, thumb?: str|null }` +- `T14` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }` +- `T15` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T46|null }` +- `T16` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` +- `T17` = `{ ledger: T18[] }` +- `T18` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }` +- `T19` = `{ id: int, name: str, sort: int, exclusive: bool, values: T36[], articleCount?: int }` - `T20` = `{ objects: T25[] }` -- `T21` = `{ printer: T27 }` -- `T22` = `{ entity: T30 }` -- `T23` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }` -- `T24` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` +- `T21` = `{ objects: T26[] }` +- `T22` = `{ printer: T28 }` +- `T23` = `{ entity: T31 }` +- `T24` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }` - `T25` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` -- `T26` = `{ shipping: T31 }` -- `T27` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }` -- `T28` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }` -- `T29` = `{ sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }` -- `T30` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }` -- `T31` = `{ id: int, manifestId: int, number: str, type: str, state: str, trackingCode?: str|null, carrier?: str|null }` -- `T32` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` -- `T33` = `{ article: T38 }` -- `T34` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` -- `T35` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }` -- `T36` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }` -- `T37` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }` -- `T38` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` -- `T39` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` -- `T40` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` -- `T41` = `{ id: int, entityId: int, contactType: str, contact: str }` -- `T42` = `{ id: int, name: str, createdAt: str, updatedAt: str }` -- `T43` = `{ totalPositions: int, completelyOnLedgerPositions: int }` -- `T44` = `{ warehouse: T46 }` -- `T45` = `{ net: num, taxId: int, taxRate?: num|null }` -- `T46` = `{ id: int, name: str, sort: int }` +- `T26` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }` +- `T27` = `{ shipping: T32 }` +- `T28` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }` +- `T29` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }` +- `T30` = `{ sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }` +- `T31` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }` +- `T32` = `{ id: int, manifestId: int, number: str, type: str, state: str, trackingCode?: str|null, carrier?: str|null }` +- `T33` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }` +- `T34` = `{ article: T39 }` +- `T35` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }` +- `T36` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }` +- `T37` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }` +- `T38` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }` +- `T39` = `{ id: int, categoryId: int, name: str, sku: str, track_stock: bool }` +- `T40` = `{ minmultiplier: num, net: num, taxRate: num, customergroupId: int }` +- `T41` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }` +- `T42` = `{ id: int, entityId: int, contactType: str, contact: str }` +- `T43` = `{ id: int, name: str, createdAt: str, updatedAt: str }` +- `T44` = `{ totalPositions: int, completelyOnLedgerPositions: int }` +- `T45` = `{ warehouse: T47 }` +- `T46` = `{ net: num, taxId: int, taxRate?: num|null }` +- `T47` = `{ id: int, name: str, sort: int }` ### Routes @@ -63,7 +64,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). #### Create article in category — `POST /article` - Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }` -- Returns: `201 T33` +- Returns: `201 T34` #### Delete article by SKU — `DELETE /article/{sku}` - Params: `sku: str, query: cascade?: bool` - Returns: `204 void` @@ -96,10 +97,10 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). - Returns: `200 { children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Create and attach a new child article to a parent article — `POST /article/{sku}/children` - Params: `sku: str, body: { attributeValueId?: int, value?: str, name?: str }` -- Returns: `201 T33` +- Returns: `201 T34` #### Attach an existing article as a child to a parent article — `PUT /article/{sku}/children` - Params: `sku: str, body: { sku: str, attributeValueId?: int, value?: str, reparent?: bool }` -- Returns: `200 { article: T38, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` +- Returns: `200 { article: T39, children: { id: int, sku: str, name: str, parentsort: int, attributeValueId: int, attributeValue: str }[] }` #### Remove a child article from a parent article — `DELETE /article/{sku}/children/{childSku}` - Params: `sku: str, childSku: str, query: deleteArticle?: bool` - Returns: `200 { success: bool, deleted: bool }` @@ -123,7 +124,7 @@ 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 }[] }` #### Toggle parent mode for an article — `POST /article/{sku}/parent` - Params: `sku: str, body: { isParent: bool, attributeNameId?: int }` -- Returns: `200 T33` +- Returns: `200 T34` #### Replace POS prices by SKU (net > gross; price=net, tax=taxRate %) — `PUT /article/{sku}/prices` - Params: `sku: str, body: { prices: { net?: num, gross?: num, price?: num, taxRate?: num, tax?: num, minmultiplier?: num }[] }` - Returns: `200 T3` @@ -141,10 +142,10 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). - Returns: `200 { ok: bool }` #### List article suppliers — `GET /article/{sku}/supplier` - Params: `sku: str` -- Returns: `200 { suppliers: T14[] }` +- Returns: `200 { suppliers: T15[] }` #### Assign supplier to article — `POST /article/{sku}/supplier` - Params: `sku: str, body: { entityId: int, sku?: str|null, pu?: num, stock?: num|null, sort?: int|null, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }` -- Returns: `201 { supplier: T14 }` +- Returns: `201 { supplier: T15 }` #### Reorder article suppliers — `PUT /article/{sku}/supplier/order` - Params: `sku: str, body: { ids: int[] }` - Returns: `200 { ids: int[] }` @@ -153,7 +154,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). - Returns: `204 void` #### Update article supplier assignment — `PATCH /article/{sku}/supplier/{id}` - Params: `sku: str, id: int, body: { sku?: str|null, pu?: num, stock?: num|null, sort?: int, netPrice?: num|null, taxId?: int|null, taxRate?: num|null }` -- Returns: `200 { supplier: T14 }` +- Returns: `200 { supplier: T15 }` #### Set article track_stock flag by SKU — `PUT /article/{sku}/track_stock` - Params: `sku: str, body: { track_stock: bool }` - Returns: `200 T3` @@ -165,19 +166,19 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body). #### List attribute names — `GET /attributename` _none_ -- Returns: `200 { attributenames: T18[] }` +- Returns: `200 { attributenames: T19[] }` #### Create attribute name — `POST /attributename` - Params: `body: { name: str, sort?: int, exclusive?: bool }` -- Returns: `201 { attributename: T18 }` +- Returns: `201 { attributename: T19 }` #### Reorder attribute names — `PUT /attributename/order` - Params: `body: { attributenameIds: int[] }` - Returns: `200 { ok: bool }` #### List values for an attribute — `GET /attributename/{attributeId}/value` - Params: `attributeId: int` -- Returns: `200 { values: T35[] }` +- Returns: `200 { values: T36[] }` #### Create value for an attribute — `POST /attributename/{attributeId}/value` - Params: `attributeId: int, body: { value: str, sort?: int }` -- Returns: `201 { attributevalue: T35 }` +- Returns: `201 { attributevalue: T36 }` #### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order` - Params: `attributeId: int, body: { attributevalueIds: int[] }` - Returns: `200 { ok: bool }` @@ -186,7 +187,7 @@ _none_ - Returns: `200 { ok: bool }` #### Update attribute name — `PATCH /attributename/{id}` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` -- Returns: `200 { attributename: T18 }` +- Returns: `200 { attributename: T19 }` ### Section: /attributevalue @@ -195,13 +196,13 @@ _none_ - Returns: `200 { ok: bool }` #### Update attribute value — `PATCH /attributevalue/{id}` - Params: `id: int, body: { value?: str, sort?: int }` -- Returns: `200 { attributevalue: T35 }` +- Returns: `200 { attributevalue: T36 }` #### Delete attribute value image — `DELETE /attributevalue/{id}/image` - Params: `id: int` -- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T40[] } }` +- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T41[] } }` #### Add or replace attribute value image — `POST /attributevalue/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T40[] } }` +- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T41[] } }` ### Section: /brand @@ -210,7 +211,7 @@ _none_ - Returns: `200 { brands: { id: int, name: str, articleCount: int, pics: { fullHash: str, thumbW: int, thumbH: int, thumbHash: str, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null }[], gsprpinfo?: str|null }[], total: int, offset: int, limit: int }` #### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `201 { brand: { id: int, name: str, pics: T40[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T41[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` @@ -219,10 +220,10 @@ _none_ - Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` -- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T40[] } }` +- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T41[] } }` #### Add or replace brand image — `POST /brand/{id}/image` - Params: `id: int, image/avif` -- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T40[] } }` +- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T41[] } }` ### Section: /category @@ -243,7 +244,7 @@ _none_ - Returns: `200 { category: { id: int, name: str, slug: str, parentId: int, sort: int } }` #### Articles in category — `GET /category/{id}/articles` - Params: `id: int` -- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, brand?: T32|null }[] }` +- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, brand?: T33|null }[] }` #### List category attribute values — `GET /category/{id}/attributevalue` - Params: `id: int` - Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }` @@ -297,7 +298,7 @@ _none_ - Returns: `200 { customergroups: { id: int, name: str, sort: int, editable: bool }[] }` #### Create customer group — `POST /customergroup` - Params: `body: { name: str }` -- Returns: `201 { customergroup: T46 }` +- Returns: `201 { customergroup: T47 }` #### Reorder customer groups — `PUT /customergroup/order` - Params: `body: { customergroupIds: int[] }` - Returns: `200 { customergroupIds: int[] }` @@ -306,76 +307,76 @@ _none_ - Returns: `204 void` #### Update customer group — `PATCH /customergroup/{id}` - Params: `id: int, body: { name: str }` -- Returns: `200 { customergroup: T46 }` +- Returns: `200 { customergroup: T47 }` ### Section: /delivery #### List deliveries — `GET /delivery` _none_ -- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int|null, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T43 }[] }` +- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int|null, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T44 }[] }` #### Create delivery — `POST /delivery` -- Params: `body: { externalNumber: str, date: str, lines: T28[], supplierId?: int|null, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` -- Returns: `201 T8` +- Params: `body: { externalNumber: str, date: str, lines: T29[], supplierId?: int|null, files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` +- Returns: `201 T9` #### Delete delivery — `DELETE /delivery/{id}` - Params: `id: int` - Returns: `204 void` #### Get delivery — `GET /delivery/{id}` - Params: `id: int` -- Returns: `200 T8` +- Returns: `200 T9` #### Update delivery — `PATCH /delivery/{id}` -- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T28[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` -- Returns: `200 T8` +- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T29[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` +- Returns: `200 T9` #### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` #### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf` - Params: `id: int` -- Returns: `201 T11` +- Returns: `201 T12` ### Section: /entity #### List entities — `GET /entity` - Params: `query: customergroupId?: int, role?: str` -- Returns: `200 { entities: T30[] }` +- Returns: `200 { entities: T31[] }` #### Create entity — `POST /entity` - Params: `body: { customergroupId: int, name: str, firstName?: str|null, number?: str }` -- Returns: `201 T22` +- Returns: `201 T23` #### Peek next customer number — `GET /entity/next-number` _none_ - Returns: `200 { number: str }` #### List entity addresses — `GET /entity/{entityId}/address` - Params: `entityId: int` -- Returns: `200 { addresses: T15[] }` +- Returns: `200 { addresses: T16[] }` #### Create entity address — `POST /entity/{entityId}/address` - Params: `entityId: int, body: { addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` -- Returns: `201 { address: T15 }` +- Returns: `201 { address: T16 }` #### Delete entity address — `DELETE /entity/{entityId}/address/{id}` - Params: `entityId: int, id: int` - Returns: `204 void` #### Update entity address — `PATCH /entity/{entityId}/address/{id}` - Params: `entityId: int, id: int, body: { addressType?: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }` -- Returns: `200 { address: T15 }` +- Returns: `200 { address: T16 }` #### List entity contacts — `GET /entity/{entityId}/contact` - Params: `entityId: int` -- Returns: `200 { contacts: T41[] }` +- Returns: `200 { contacts: T42[] }` #### Create entity contact — `POST /entity/{entityId}/contact` - Params: `entityId: int, body: { contactType: str, contact: str }` -- Returns: `201 { contact: T41 }` +- Returns: `201 { contact: T42 }` #### Delete entity contact — `DELETE /entity/{entityId}/contact/{id}` - Params: `entityId: int, id: int` - Returns: `204 void` #### Update entity contact — `PATCH /entity/{entityId}/contact/{id}` - Params: `entityId: int, id: int, body: { contactType?: str, contact?: str }` -- Returns: `200 { contact: T41 }` +- Returns: `200 { contact: T42 }` #### Delete entity — `DELETE /entity/{id}` - Params: `id: int` - Returns: `204 void` #### Get entity — `GET /entity/{id}` - Params: `id: int` -- Returns: `200 T22` +- Returns: `200 T23` #### Update entity — `PATCH /entity/{id}` - Params: `id: int, body: { name?: str, firstName?: str|null, number?: str|null }` -- Returns: `200 T22` +- Returns: `200 T23` ### Section: /labeltemplate @@ -383,35 +384,35 @@ _none_ _none_ - Returns: `200 { labeltemplates: { id: int, name: str, width: num, height: num, dpi: int, type: str, thumb?: str|null }[] }` #### Create label template — `POST /labeltemplate` -- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: T20 }` -- Returns: `201 T10` +- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: T21 }` +- Returns: `201 T11` #### Delete label template — `DELETE /labeltemplate/{id}` - Params: `id: int` - Returns: `204 void` #### Get label template by ID — `GET /labeltemplate/{id}` - Params: `id: int` -- Returns: `200 T10` +- Returns: `200 T11` #### Update label template (partial) — `PATCH /labeltemplate/{id}` -- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T20 }` -- Returns: `200 T10` +- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T21 }` +- Returns: `200 T11` #### Update label template — `PUT /labeltemplate/{id}` -- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T20 }` -- Returns: `200 T10` +- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: T21 }` +- Returns: `200 T11` ### Section: /ledger #### Query ledger by manifest article ID or SKU — `GET /ledger` - Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str` -- Returns: `200 T16` +- Returns: `200 T17` #### Add ledger entry — `POST /ledger` - Params: `body: { manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str }` -- Returns: `201 { ledger: T17 }` +- Returns: `201 { ledger: T18 }` #### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}` - Params: `manifestarticleId: int, query: transaction_type?: str` -- Returns: `200 T16` +- Returns: `200 T17` #### Query ledger by article SKU — `GET /ledger/sku/{sku}` - Params: `sku: str, query: transaction_type?: str` -- Returns: `200 T16` +- Returns: `200 T17` ### Section: /number @@ -428,7 +429,7 @@ _none_ - Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int` - Returns: `200 { orders: T2[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Create order — `POST /order` -- Params: `body: { number: str, date: str, lines: T28[], entityId?: int|null, externalNumber?: str|null }` +- Params: `body: { number: str, date: str, lines: T29[], entityId?: int|null, externalNumber?: str|null }` - Returns: `201 T1` #### Delete order — `DELETE /order/{id}` - Params: `id: int` @@ -437,32 +438,32 @@ _none_ - Params: `id: int` - Returns: `200 T1` #### Update order — `PATCH /order/{id}` -- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T28[] }` +- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T29[] }` - Returns: `200 T1` #### Delete order file attachment — `DELETE /order/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` #### List payments of an order — `GET /order/{id}/payment` - Params: `id: int` -- Returns: `200 { payments: T37[] }` +- Returns: `200 { payments: T38[] }` #### Add payment to an order — `POST /order/{id}/payment` - Params: `id: int, body: { accountId: int, type: str, amount: num }` -- Returns: `201 { payment: T37 }` +- Returns: `201 { payment: T38 }` #### Generate and attach order PDF — `POST /order/{id}/pdf` - Params: `id: int` -- Returns: `201 T11` +- Returns: `201 T12` #### List shipments of an order — `GET /order/{id}/shipment` - Params: `id: int` -- Returns: `200 { shipments: T5[] }` +- Returns: `200 { shipments: T6[] }` #### Create shipment for an order — `POST /order/{id}/shipment` -- Params: `id: int, body: { lines: { sku: str, amount: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[], date?: str, externalNumber?: str }` -- Returns: `201 { shipment: T5 }` +- Params: `id: int, body?: { date?: str, externalNumber?: str, lines?: { sku: str, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[] }` +- Returns: `201 T5` #### List shippings of an order — `GET /order/{id}/shipping` - Params: `id: int` -- Returns: `200 { shippings: T31[] }` +- Returns: `200 { shippings: T32[] }` #### Create shipping for an order — `POST /order/{id}/shipping` - Params: `id: int, body: { type: str, state: str, warehouseId?: int }` -- Returns: `201 T26` +- Returns: `201 T27` ### Section: /package @@ -480,10 +481,10 @@ _none_ #### List printers — `GET /printer` _none_ -- Returns: `200 { printers: T27[] }` +- Returns: `200 { printers: T28[] }` #### Create printer — `POST /printer` - Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }` -- Returns: `201 T21` +- Returns: `201 T22` #### Reorder printers — `PUT /printer/order` - Params: `body: { printerIds: int[] }` - Returns: `200 { printerIds: int[] }` @@ -492,22 +493,22 @@ _none_ - Returns: `204 void` #### Get printer by ID — `GET /printer/{id}` - Params: `id: int` -- Returns: `200 T21` +- Returns: `200 T22` #### Update printer (partial) — `PATCH /printer/{id}` - Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }` -- Returns: `200 T21` +- Returns: `200 T22` #### Update printer — `PUT /printer/{id}` - Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }` -- Returns: `200 T21` +- Returns: `200 T22` ### Section: /propertyname #### List property names — `GET /propertyname` _none_ -- Returns: `200 { propertynames: T36[] }` +- Returns: `200 { propertynames: T37[] }` #### Create property name — `POST /propertyname` - Params: `body: { name: str, sort?: int, exclusive?: bool }` -- Returns: `201 { propertyname: T36 }` +- Returns: `201 { propertyname: T37 }` #### Reorder property names — `PUT /propertyname/order` - Params: `body: { propertynameIds: int[] }` - Returns: `200 { ok: bool }` @@ -516,55 +517,61 @@ _none_ - Returns: `200 { ok: bool }` #### Update property name — `PATCH /propertyname/{id}` - Params: `id: int, body: { name?: str, sort?: int, exclusive?: bool }` -- Returns: `200 { propertyname: T36 }` +- Returns: `200 { propertyname: T37 }` ### Section: /quote #### List quotes — `GET /quote` - Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int` -- Returns: `200 { quotes: T7[], total: int, unfilteredTotal: int, offset: int, limit: int }` +- Returns: `200 { quotes: T8[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Create quote — `POST /quote` -- Params: `body: { number: str, date: str, lines: T28[], entityId?: int|null, externalNumber?: str|null }` -- Returns: `201 T6` +- Params: `body: { number: str, date: str, lines: T29[], entityId?: int|null, externalNumber?: str|null }` +- Returns: `201 T7` #### Delete quote — `DELETE /quote/{id}` - Params: `id: int` - Returns: `204 void` #### Get quote — `GET /quote/{id}` - Params: `id: int` -- Returns: `200 T6` +- Returns: `200 T7` #### Update quote — `PATCH /quote/{id}` -- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T28[] }` -- Returns: `200 T6` +- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T29[] }` +- Returns: `200 T7` #### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` #### Create order from quote — `POST /quote/{id}/order` -- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T28[] }` +- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T29[] }` - Returns: `201 T1` #### Generate and attach quote PDF — `POST /quote/{id}/pdf` - Params: `id: int` -- Returns: `201 T11` +- Returns: `201 T12` ### Section: /shipment +#### List shipments — `GET /shipment` +- Params: `query: offset?: int, limit?: int, search?: str, state?: str, seekMonths?: int` +- Returns: `200 { shipments: { id: int, number: str, type: str, date: str, positions: int, totalQuantity: num, packageCount: int, state: str, entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, orderId?: int|null, orderNumber?: str|null, customerName?: str|null, carrier?: str|null, trackingCode?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }` #### Delete shipment — `DELETE /shipment/{id}` - Params: `id: int` - Returns: `204 void` #### Get shipment by ID — `GET /shipment/{id}` - Params: `id: int` -- Returns: `200 { shipment: T5 }` +- Returns: `200 T5` +#### Update shipment state or metadata — `PATCH /shipment/{id}` +- Params: `id: int, body: { state?: str }` +- Returns: `200 T5` #### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}` - Params: `id: int, fileId: int` - Returns: `204 void` #### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf` - Params: `id: int` -- Returns: `201 T11` +- Returns: `201 T12` #### List packages of a shipment — `GET /shipment/{id}/shipping` - Params: `id: int` -- Returns: `200 { shippings: T31[] }` +- Returns: `200 { shippings: T32[] }` #### Create package for a shipment — `POST /shipment/{id}/shipping` - Params: `id: int, body: { type: str, state: str, trackingCode?: str, carrier?: str, warehouseId?: int }` -- Returns: `201 T26` +- Returns: `201 T27` ### Section: /shipping @@ -573,7 +580,7 @@ _none_ - Returns: `204 void` #### Update shipping — `PATCH /shipping/{id}` - Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrier?: str, warehouseId?: int }` -- Returns: `200 T26` +- Returns: `200 T27` #### List packages of a shipping — `GET /shipping/{id}/package` - Params: `id: int` - Returns: `200 { packages: { id: int, shippingId: int, entityaddressId: int, number: str, type: str, state: str, details?: str|null }[] }` @@ -591,28 +598,28 @@ _none_ #### Adjust stock manually — `POST /stock/adjust` - Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }` -- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T17[] }` +- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T18[] }` ### Section: /supplier #### List suppliers — `GET /supplier` _none_ -- Returns: `200 { suppliers: T42[] }` +- Returns: `200 { suppliers: T43[] }` #### Create supplier — `POST /supplier` - Params: `body: { name: str }` -- Returns: `201 { supplier: T42 }` +- Returns: `201 { supplier: T43 }` #### Delete supplier — `DELETE /supplier/{id}` - Params: `id: int` - Returns: `204 void` #### Update supplier — `PATCH /supplier/{id}` - Params: `id: int, body: { name: str }` -- Returns: `200 { supplier: T42 }` +- Returns: `200 { supplier: T43 }` #### List supplier articles by SKUs — `GET /supplier/{id}/articles` - Params: `id: int, query: skus?: string` -- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T45|null }[] }` +- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T46|null }[] }` #### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles` - Params: `id: int, body?: { skus?: str[] }` -- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T45|null }[] }` +- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T46|null }[] }` ### Section: /unit @@ -621,19 +628,19 @@ _none_ - Returns: `204 void` #### List units — `GET /unit` _none_ -- Returns: `200 { units: T34[] }` +- Returns: `200 { units: T35[] }` #### Upsert unit — `PUT /unit` - Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }` -- Returns: `200 { unit: T34 }` +- Returns: `200 { unit: T35 }` ### Section: /warehouse #### List warehouses — `GET /warehouse` _none_ -- Returns: `200 { warehouses: T46[] }` +- Returns: `200 { warehouses: T47[] }` #### Create warehouse — `POST /warehouse` - Params: `body: { name: str, sort?: int }` -- Returns: `201 T44` +- Returns: `201 T45` #### Reorder warehouses — `PUT /warehouse/order` - Params: `body: { warehouseIds: int[] }` - Returns: `200 { warehouseIds: int[] }` @@ -642,13 +649,13 @@ _none_ - Returns: `204 void` #### Get warehouse by ID — `GET /warehouse/{id}` - Params: `id: int` -- Returns: `200 T44` +- Returns: `200 T45` #### Update warehouse (partial) — `PATCH /warehouse/{id}` - Params: `id: int, body: { name?: str, sort?: int }` -- Returns: `200 T44` +- Returns: `200 T45` #### Update warehouse — `PUT /warehouse/{id}` - Params: `id: int, body: { name?: str, sort?: int }` -- Returns: `200 T44` +- Returns: `200 T45` ### 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: diff --git a/docs/api/routes/order/_id/shipment.get.md b/docs/api/routes/order/_id/shipment.get.md index 3adbb61..d8d9d03 100644 --- a/docs/api/routes/order/_id/shipment.get.md +++ b/docs/api/routes/order/_id/shipment.get.md @@ -70,7 +70,8 @@ List of shipments "thumby": 1, "meta": {} } - ] + ], + "state": "new" } ] } diff --git a/docs/api/routes/order/_id/shipment.post.md b/docs/api/routes/order/_id/shipment.post.md index 0cc00d7..6df073e 100644 --- a/docs/api/routes/order/_id/shipment.post.md +++ b/docs/api/routes/order/_id/shipment.post.md @@ -4,9 +4,12 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). Create shipment for an order -Creates a new partial or full shipment manifest for the order. -Validates that article positions belong to the order and cumulative shipped quantities -do not exceed ordered quantities. +Creates a new shipment manifest (Lieferschein) for an order containing one, some, or all items from the order. +If `lines` is omitted or empty, all remaining unshipped items from the order are included automatically. +On individual line items, `amount` is optional and defaults to the remaining unshipped quantity for that article. +Auto-assigns the shipment number formatted as ``, e.g. `AUF-000001-L-1`. +Updates the materialized `ordersummary` table (`shipped_quantity`, `is_shipped`) and emits SSE events +(`order`, `shipment`) so that the order list and summary tables are rerendered in real time. ## Path parameters @@ -15,15 +18,15 @@ do not exceed ordered quantities. |-------|----------|------|-------------| | `id` | yes | integer | Order ID | -## Body (JSON) +## Body (JSON) — _optional_ | Field | Required | Type | Description | |-------|----------|------|-------------| -| `date` | no | string | | -| `externalNumber` | no | string | | -| `lines` | yes | object[] | | -| `lines[].sku` | yes | string | | -| `lines[].amount` | yes | number | | +| `date` | no | string | Optional shipment date (defaults to current date) | +| `externalNumber` | no | string | Optional external delivery note reference (defaults to order.externalNumber or order.number) | +| `lines` | no | object[] | Optional positions to ship. If omitted, all remaining unshipped items are shipped. | +| `lines[].sku` | yes | string | Article SKU | +| `lines[].amount` | no | number | Quantity to ship. Defaults to remaining unshipped quantity for this SKU. | | `lines[].name` | no | string | | | `lines[].pu` | no | number | | | `lines[].net` | no | number | | @@ -107,7 +110,8 @@ Created shipment "thumby": 1, "meta": {} } - ] + ], + "state": "new" } } ``` @@ -119,7 +123,7 @@ Created shipment | 400 | Validation error or article not in order — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` | | 404 | Order not found — `{"error":"…"}` | -| 409 | Cumulative shipment amount exceeds ordered amount — `{"error":"…"}` | +| 409 | Cumulative shipment amount exceeds ordered amount, or all items are already shipped — `{"error":"…"}` | --- diff --git a/docs/api/routes/order/create.md b/docs/api/routes/order/create.md index 2130e22..955d30e 100644 --- a/docs/api/routes/order/create.md +++ b/docs/api/routes/order/create.md @@ -160,7 +160,8 @@ Created order "thumby": 1, "meta": {} } - ] + ], + "state": "new" } ], "files": [ diff --git a/docs/api/routes/order/get.md b/docs/api/routes/order/get.md index 22ed581..4496929 100644 --- a/docs/api/routes/order/get.md +++ b/docs/api/routes/order/get.md @@ -134,7 +134,8 @@ Order "thumby": 1, "meta": {} } - ] + ], + "state": "new" } ], "files": [ diff --git a/docs/api/routes/order/list.md b/docs/api/routes/order/list.md index 06d3077..d5fdcfc 100644 --- a/docs/api/routes/order/list.md +++ b/docs/api/routes/order/list.md @@ -143,7 +143,8 @@ Order list "thumby": 1, "meta": {} } - ] + ], + "state": "new" } ], "files": [ diff --git a/docs/api/routes/order/update.md b/docs/api/routes/order/update.md index 79cbab1..bc0dc8e 100644 --- a/docs/api/routes/order/update.md +++ b/docs/api/routes/order/update.md @@ -154,7 +154,8 @@ Updated order "thumby": 1, "meta": {} } - ] + ], + "state": "new" } ], "files": [ diff --git a/docs/api/routes/quote/order.md b/docs/api/routes/quote/order.md index 4ba6b72..2a85119 100644 --- a/docs/api/routes/quote/order.md +++ b/docs/api/routes/quote/order.md @@ -5,8 +5,10 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). Create order from quote Creates a sales order from an existing quote for the authenticated user's selected company. -Copies lines, customer entity, and external number from the quote unless explicitly overridden. +Copies lines and customer entity from the quote. +Sets external reference (externalNumber) to the quote number unless explicitly overridden. If the order number is omitted, it is auto-allocated from the sequence. +Fails with 400 (No entity selected) if neither the quote nor the request body specifies a customer entity. ## Path parameters @@ -23,7 +25,7 @@ If the order number is omitted, it is auto-allocated from the sequence. | `date` | no | string | Optional order date (ISO format). Defaults to current date. | | `createdAt` | no | string | Optional creation timestamp. | | `entityId` | no | integer | Customer entity ID override. Defaults to quote's entityId. | -| `externalNumber` | no | string | External customer reference override. Defaults to quote's externalNumber. | +| `externalNumber` | no | string | External customer reference override. Defaults to quote's number. | | `lines` | no | ManifestLineInput[] | Optional line items override. Defaults to quote's lines. | ```json @@ -170,7 +172,8 @@ Created order "thumby": 1, "meta": {} } - ] + ], + "state": "new" } ], "files": [ @@ -199,7 +202,7 @@ Created order | Status | Description | |--------|-------------| -| 400 | No company selected, invalid quote id, or invalid order data — `{"error":"…"}` | +| 400 | No company selected, no entity selected, invalid quote id, or invalid order data — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` | | 403 | Not authorized for company — `{"error":"…"}` | | 404 | Quote not found or entity not found — `{"error":"…"}` | diff --git a/docs/api/routes/shipment.md b/docs/api/routes/shipment.md new file mode 100644 index 0000000..bb07fb5 --- /dev/null +++ b/docs/api/routes/shipment.md @@ -0,0 +1,64 @@ +# `GET /shipment` + +Protected. Requires a valid session cookie (`credentials: 'include'`). + +List shipments + +Company-wide paginated list of shipment manifests (`manifest` rows with `type = 'shipment'`) +with delivery state, package tracking info, and order references. + + +## Query parameters + +| Param | Required | Type | Description | +|-------|----------|------|-------------| +| `offset` | no | integer | Number of shipments to skip | +| `limit` | no | integer | Number of shipments to return | +| `search` | no | string | Free-text search matching shipment number, order number, external number, tracking code, or customer name | +| `state` | no | string | Optional filter by delivery state | +| `seekMonths` | no | integer | Negative integer for cutoff months ago (e.g. -1 for shipments older than 1 month) | + +## Response `200` + +Paginated shipment list + +```json +{ + "shipments": [ + { + "id": 1, + "number": "…", + "type": "shipment", + "entityId": 1, + "externalNumber": "…", + "date": "…", + "createdAt": "…", + "orderId": 1, + "orderNumber": "…", + "customerName": "…", + "positions": 1, + "totalQuantity": 1, + "packageCount": 1, + "carrier": "…", + "trackingCode": "…", + "state": "new" + } + ], + "total": 1, + "unfilteredTotal": 1, + "offset": 1, + "limit": 1 +} +``` + +## Errors + +| Status | Description | +|--------|-------------| +| 400 | Invalid query parameters or no company selected — `{"error":"…"}` | +| 401 | Unauthorized — `{"error":"…"}` | +| 403 | Not authorized — `{"error":"…"}` | + +--- + +_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._ diff --git a/docs/api/routes/shipment/_id.get.md b/docs/api/routes/shipment/_id.get.md index d4e247a..ca7b86a 100644 --- a/docs/api/routes/shipment/_id.get.md +++ b/docs/api/routes/shipment/_id.get.md @@ -69,7 +69,8 @@ Shipment details "thumby": 1, "meta": {} } - ] + ], + "state": "new" } } ``` diff --git a/docs/api/routes/shipment/_id.patch.md b/docs/api/routes/shipment/_id.patch.md new file mode 100644 index 0000000..e05b79f --- /dev/null +++ b/docs/api/routes/shipment/_id.patch.md @@ -0,0 +1,106 @@ +# `PATCH /shipment/{id}` + +Protected. Requires a valid session cookie (`credentials: 'include'`). + +Update shipment state or metadata + +Updates delivery state (`new`, `delivered`, `returned`) for the shipment. +Emits SSE events (`shipment`, `order`) so connected clients rerender in real time. + + +## Path parameters + +| Param | Required | Type | Description | +|-------|----------|------|-------------| +| `id` | yes | integer | Shipment ID | + +## Body (JSON) + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `state` | no | string | Delivery state | + +```json +{ + "state": "delivered" +} +``` + +## Signals + +On success, emits SSE signals: `shipment`, `order` (see [`GET /events`](../events.md)). + +## Response `200` + +Updated shipment + +```json +{ + "shipment": { + "id": 1, + "number": "…", + "type": "shipment", + "entityId": 1, + "externalNumber": "…", + "date": "…", + "createdAt": "…", + "lineCount": 1, + "packageCount": 1, + "lines": [ + { + "id": 1, + "sku": "…", + "name": "…", + "pu": 1, + "amount": 1, + "net": 1, + "rebate": 1, + "taxrate": 1, + "comment": "…" + } + ], + "shippings": [ + { + "id": 1, + "manifestId": 1, + "number": "…", + "trackingCode": "…", + "carrier": "…", + "type": "…", + "state": "…" + } + ], + "files": [ + { + "id": 1, + "manifestId": 1, + "fileId": 1, + "sort": 1, + "hash": "…", + "mimetype": "…", + "name": "…", + "size": 1, + "created": "2026-08-10T00:00:00.000Z", + "modified": "2026-08-10T00:00:00.000Z", + "skeledash": "…", + "thumbx": 1, + "thumby": 1, + "meta": {} + } + ], + "state": "new" + } +} +``` + +## Errors + +| Status | Description | +|--------|-------------| +| 400 | Invalid state, invalid ID, or no company selected — `{"error":"…"}` | +| 401 | Unauthorized — `{"error":"…"}` | +| 404 | Shipment not found — `{"error":"…"}` | + +--- + +_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._ diff --git a/src/api/openapi.js b/src/api/openapi.js index 9c87314..809c38d 100644 --- a/src/api/openapi.js +++ b/src/api/openapi.js @@ -1310,6 +1310,56 @@ * description: Attached documents (e.g. shipment label PDF) for the shipment * items: * $ref: '#/components/schemas/DeliveryFile' + * state: + * type: string + * enum: [new, delivered, returned] + * description: Delivery state + * ShipmentSummary: + * type: object + * required: [id, number, type, date, state, positions, totalQuantity, packageCount] + * properties: + * id: + * type: integer + * number: + * type: string + * type: + * type: string + * enum: [shipment] + * entityId: + * type: integer + * nullable: true + * externalNumber: + * type: string + * nullable: true + * date: + * type: string + * createdAt: + * type: string + * nullable: true + * orderId: + * type: integer + * nullable: true + * orderNumber: + * type: string + * nullable: true + * customerName: + * type: string + * nullable: true + * positions: + * type: integer + * totalQuantity: + * type: number + * packageCount: + * type: integer + * carrier: + * type: string + * nullable: true + * trackingCode: + * type: string + * nullable: true + * state: + * type: string + * enum: [new, delivered, returned] * Package: * type: object * required: [id, shippingId, entityaddressId, number, type, state] diff --git a/src/api/routes/quote.js b/src/api/routes/quote.js index b342f68..ada26e2 100644 --- a/src/api/routes/quote.js +++ b/src/api/routes/quote.js @@ -15,6 +15,8 @@ function requireSelectedCompany(req) { function quoteErrorStatus(error) { if (error.message === 'No company selected') return 400; + if (error.message === 'No entity selected') return 400; + if (error.message === 'Entity required') return 400; if (error.message === 'Not authorized') return 403; if (error.message === 'Company not found') return 404; if (error.message === 'Quote not found') return 404; @@ -581,8 +583,10 @@ export function register(app, basePath = '') { * summary: Create order from quote * description: | * Creates a sales order from an existing quote for the authenticated user's selected company. - * Copies lines, customer entity, and external number from the quote unless explicitly overridden. + * Copies lines and customer entity from the quote. + * Sets external reference (externalNumber) to the quote number unless explicitly overridden. * If the order number is omitted, it is auto-allocated from the sequence. + * Fails with 400 (No entity selected) if neither the quote nor the request body specifies a customer entity. * security: * - cookieAuth: [] * parameters: @@ -616,7 +620,7 @@ export function register(app, basePath = '') { * externalNumber: * type: string * nullable: true - * description: External customer reference override. Defaults to quote's externalNumber. + * description: External customer reference override. Defaults to quote's number. * lines: * type: array * description: Optional line items override. Defaults to quote's lines. @@ -634,7 +638,7 @@ export function register(app, basePath = '') { * order: * $ref: '#/components/schemas/Order' * 400: - * description: No company selected, invalid quote id, or invalid order data + * description: No company selected, no entity selected, invalid quote id, or invalid order data * content: * application/json: * schema: diff --git a/src/api/routes/shipment.js b/src/api/routes/shipment.js index dd4add3..fc41653 100644 --- a/src/api/routes/shipment.js +++ b/src/api/routes/shipment.js @@ -27,7 +27,11 @@ function shipmentErrorStatus(error) { if ( error.message?.endsWith(' required') || error.message?.startsWith('Article (') || - error.message?.includes('Invalid lines') + error.message?.includes('Invalid lines') || + error.message === 'Invalid state' || + error.message === 'Invalid offset' || + error.message === 'Invalid limit' || + error.message === 'Invalid seekMonths' ) { return 400; } @@ -134,9 +138,12 @@ export function register(app, basePath = '') { * tags: [Shipment] * summary: Create shipment for an order * description: | - * Creates a new partial or full shipment manifest for the order. - * Validates that article positions belong to the order and cumulative shipped quantities - * do not exceed ordered quantities. + * Creates a new shipment manifest (Lieferschein) for an order containing one, some, or all items from the order. + * If `lines` is omitted or empty, all remaining unshipped items from the order are included automatically. + * On individual line items, `amount` is optional and defaults to the remaining unshipped quantity for that article. + * Auto-assigns the shipment number formatted as ``, e.g. `AUF-000001-L-1`. + * Updates the materialized `ordersummary` table (`shipped_quantity`, `is_shipped`) and emits SSE events + * (`order`, `shipment`) so that the order list and summary tables are rerendered in real time. * security: * - cookieAuth: [] * parameters: @@ -147,28 +154,32 @@ export function register(app, basePath = '') { * type: integer * description: Order ID * requestBody: - * required: true + * required: false * content: * application/json: * schema: * type: object - * required: [lines] * properties: * date: * type: string * format: date + * description: Optional shipment date (defaults to current date) * externalNumber: * type: string + * description: Optional external delivery note reference (defaults to order.externalNumber or order.number) * lines: * type: array + * description: Optional positions to ship. If omitted, all remaining unshipped items are shipped. * items: * type: object - * required: [sku, amount] + * required: [sku] * properties: * sku: * type: string + * description: Article SKU * amount: * type: number + * description: Quantity to ship. Defaults to remaining unshipped quantity for this SKU. * name: * type: string * pu: @@ -210,7 +221,7 @@ export function register(app, basePath = '') { * schema: * $ref: '#/components/schemas/Error' * 409: - * description: Cumulative shipment amount exceeds ordered amount + * description: Cumulative shipment amount exceeds ordered amount, or all items are already shipped * content: * application/json: * schema: @@ -245,6 +256,157 @@ export function register(app, basePath = '') { // #endregion + /** + * @openapi + * /shipment: + * get: + * tags: [Shipment] + * summary: List shipments + * description: | + * Company-wide paginated list of shipment manifests (`manifest` rows with `type = 'shipment'`) + * with delivery state, package tracking info, and order references. + * security: + * - cookieAuth: [] + * parameters: + * - in: query + * name: offset + * schema: + * type: integer + * default: 0 + * minimum: 0 + * description: Number of shipments to skip + * - in: query + * name: limit + * schema: + * type: integer + * default: 50 + * minimum: 1 + * maximum: 100 + * description: Number of shipments to return + * - in: query + * name: search + * schema: + * type: string + * description: Free-text search matching shipment number, order number, external number, tracking code, or customer name + * - in: query + * name: state + * schema: + * type: string + * enum: [new, delivered, returned] + * description: Optional filter by delivery state + * - in: query + * name: seekMonths + * schema: + * type: integer + * description: Negative integer for cutoff months ago (e.g. -1 for shipments older than 1 month) + * responses: + * 200: + * description: Paginated shipment list + * content: + * application/json: + * schema: + * type: object + * required: [shipments, total, unfilteredTotal, offset, limit] + * properties: + * shipments: + * type: array + * items: + * $ref: '#/components/schemas/ShipmentSummary' + * total: + * type: integer + * unfilteredTotal: + * type: integer + * offset: + * type: integer + * limit: + * type: integer + * 400: + * description: Invalid query parameters or no company selected + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 401: + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 403: + * description: Not authorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ + // #region GET /shipment + + app.get(`${basePath}/shipment`, async (req, res) => { + const selected = requireSelectedCompany(req); + if (selected.error) { + return res.status(400).json({ error: selected.error }); + } + + const rawOffset = req.query.offset; + const rawLimit = req.query.limit; + + let offset = 0; + let limit = 50; + + if (rawOffset !== undefined) { + offset = parseInt(rawOffset, 10); + if (!Number.isInteger(offset) || offset < 0) { + return res.status(400).json({ error: 'offset must be an integer >= 0' }); + } + } + + if (rawLimit !== undefined) { + limit = parseInt(rawLimit, 10); + if (!Number.isInteger(limit) || limit < 1 || limit > 100) { + return res.status(400).json({ error: 'limit must be an integer between 1 and 100' }); + } + } + + const search = typeof req.query.search === 'string' && req.query.search.trim() !== '' + ? req.query.search.trim() + : undefined; + + let state = undefined; + if (typeof req.query.state === 'string' && req.query.state.trim() !== '') { + const trimmedState = req.query.state.trim().toLowerCase(); + if (!['new', 'delivered', 'returned'].includes(trimmedState)) { + return res.status(400).json({ error: 'Invalid state' }); + } + state = trimmedState; + } + + let seekMonths = undefined; + if (req.query.seekMonths !== undefined && req.query.seekMonths !== '') { + const parsedSeek = parseInt(req.query.seekMonths, 10); + if (!Number.isInteger(parsedSeek)) { + return res.status(400).json({ error: 'seekMonths must be an integer' }); + } + seekMonths = parsedSeek; + } + + try { + const result = await db.list(req.user.id, { + offset, + limit, + search, + state, + seekMonths, + }); + res.status(200).json(result); + } catch (error) { + const status = shipmentErrorStatus(error); + if (status === 500) console.error('List shipments failed:', error); + res.status(status).json({ error: error.message }); + } + }); + + // #endregion + /** * @openapi * /shipment/{id}: @@ -315,6 +477,102 @@ export function register(app, basePath = '') { // #endregion + /** + * @openapi + * /shipment/{id}: + * patch: + * tags: [Shipment] + * summary: Update shipment state or metadata + * description: | + * Updates delivery state (`new`, `delivered`, `returned`) for the shipment. + * Emits SSE events (`shipment`, `order`) so connected clients rerender in real time. + * security: + * - cookieAuth: [] + * x-signals: [shipment, order] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: Shipment ID + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * state: + * type: string + * enum: [new, delivered, returned] + * description: Delivery state + * example: + * state: delivered + * responses: + * 200: + * description: Updated shipment + * content: + * application/json: + * schema: + * type: object + * properties: + * shipment: + * $ref: '#/components/schemas/Shipment' + * 400: + * description: Invalid state, invalid ID, or no company selected + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 401: + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 404: + * description: Shipment not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ + // #region PATCH /shipment/:id + + app.patch(`${basePath}/shipment/:id`, async (req, res) => { + const selected = requireSelectedCompany(req); + if (selected.error) { + return res.status(400).json({ error: selected.error }); + } + + const parsedId = parsePositiveInt(req.params.id, 'id'); + if (parsedId.error) { + return res.status(400).json({ error: parsedId.error }); + } + + const { state } = req.body ?? {}; + if (state !== undefined) { + if (typeof state !== 'string' || !['new', 'delivered', 'returned'].includes(state.trim().toLowerCase())) { + return res.status(400).json({ error: 'Invalid state' }); + } + } + + try { + const shipment = await db.update(req.user.id, parsedId.id, { + state: state ? state.trim().toLowerCase() : undefined, + }); + emitShipmentEvents(selected.companyId); + res.status(200).json({ shipment }); + } catch (error) { + const status = shipmentErrorStatus(error); + if (status === 500) console.error('Update shipment failed:', error); + res.status(status).json({ error: error.message }); + } + }); + + // #endregion + /** * @openapi * /shipment/{id}: diff --git a/src/db/order.js b/src/db/order.js index 98b5095..9d2b603 100644 --- a/src/db/order.js +++ b/src/db/order.js @@ -155,6 +155,11 @@ export async function createFromQuote(googleAuthId, quoteId, data) { return enrichSingleOrder(store, googleAuthId, order); } +export async function createShipment(googleAuthId, orderId, data) { + const store = await getStore(); + return store.order.createShipment(googleAuthId, orderId, data); +} + export async function update(googleAuthId, id, data) { const store = await getStore(); const order = await store.order.update(googleAuthId, id, data); diff --git a/src/db/shipment.js b/src/db/shipment.js index 4aae4db..f3a7cf6 100644 --- a/src/db/shipment.js +++ b/src/db/shipment.js @@ -1,6 +1,16 @@ import { getStore } from './store.js'; import { formatAttachments } from '../util/manifestAttachment.js'; +export async function list(googleAuthId, opts) { + const store = await getStore(); + return store.shipment.list(googleAuthId, opts); +} + +export async function update(googleAuthId, id, patch) { + const store = await getStore(); + return store.shipment.update(googleAuthId, id, patch); +} + export async function create(googleAuthId, data) { const store = await getStore(); return store.shipment.create(googleAuthId, data);