Compare commits
5 Commits
9cb82745e3
...
df015da8fc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df015da8fc | ||
|
|
2d1a7364f2 | ||
|
|
f6eb31b70d | ||
|
|
6719cf9d51 | ||
|
|
cdb031921b |
@@ -131,7 +131,7 @@ Strip supplierName?: str from T11 (resolve via entityId in cached supplier list)
|
||||
|
||||
# Dash API Routes Overview (All)
|
||||
|
||||
Overview of all 288 API endpoints across all modules.
|
||||
Overview of all 305 API endpoints across all modules.
|
||||
|
||||
### Notation
|
||||
|
||||
@@ -140,45 +140,47 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
||||
|
||||
### Reusable types
|
||||
|
||||
- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T19[], externalNumber?: str|null, createdAt?: str, shipping?: T21|null, shippings?: T21[], payment?: T30|null, payments?: T30[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T11[] }`
|
||||
- `T2` = `{ id: int, number: str, type: str, date: str, lines: T19[], shippings: T21[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T11[], state?: str }`
|
||||
- `T3` = `{ id: int, number: str, type: str, date: str, lines: T19[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T11[] }`
|
||||
- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T22[], files: T11[], externalNumber?: str|null, createdAt?: str, ledger?: T37 }`
|
||||
- `T1` = `{ id: int, number: str, type: str, entityId: int, date: str, lines: T20[], externalNumber?: str|null, createdAt?: str, shipping?: T23|null, shippings?: T23[], payment?: T32|null, payments?: T32[], paid?: bool, shipped?: bool, delivered?: bool, orderTotal?: num, paidTotal?: num, orderedQuantity?: num, shippedQuantity?: num, shipments?: T2[], files?: T10[] }`
|
||||
- `T2` = `{ id: int, number: str, type: str, date: str, lines: T20[], shippings: T23[], entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, lineCount?: int, packageCount?: int, files?: T10[], state?: str }`
|
||||
- `T3` = `{ id: int, number: str, type: str, date: str, lines: T20[], entityId?: int|null, externalNumber?: str|null, createdAt?: str, positions?: int, grossTotal?: num, totalQuantity?: num, customerName?: str|null, files?: T10[] }`
|
||||
- `T4` = `{ id: int, number: str, type: str, supplierId: int, date: str, lines: T24[], files: T10[], externalNumber?: str|null, createdAt?: str, ledger?: T39 }`
|
||||
- `T5` = `{ name: str, path: str, totalSizeBytes: int, timestamp: str|null, manifest?: T6|null }`
|
||||
- `T6` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T12, totalSizeBytes: int, durationMs: int }`
|
||||
- `T6` = `{ id: str, timestamp: str, dateKey: str, monthKey: str, dbProvider: str, files: T11, totalSizeBytes: int, durationMs: int }`
|
||||
- `T7` = `{ id: int, name: str, pairingpin?: str|null, token?: str|null, pairedAt?: str|null, clientserial?: str|null, rootCategoryId?: int|null, syncRootArticles?: bool, sort?: int, enabled?: bool, clientversion?: str|null, lastseenAt?: str|null, updatedAt?: str|null, categoryIds?: int[] }`
|
||||
- `T8` = `{ id: int, name: str, companyId?: int, token?: str|null, pairingpin?: str|null, pairedAt?: str|null, clientserial?: str|null, clientmodel?: str|null, clientstate?: str|null, agentversion?: str|null, lastseenAt?: str|null, updatedAt?: str, sort?: int }`
|
||||
- `T9` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T39|null, incoming?: { date: str, amount: num }[] }`
|
||||
- `T10` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T23[] }, thumb?: str|null }`
|
||||
- `T11` = `{ 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 }`
|
||||
- `T12` = `{ vfs: T38, storage: T38, database: T32 }`
|
||||
- `T13` = `{ 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 }`
|
||||
- `T14` = `{ id: int, company_id: int, prompt: str, max_itr: int, cron: str, created_at: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, lastrun_at?: str|null }`
|
||||
- `T15` = `{ 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 }`
|
||||
- `T16` = `{ id: int, name: str, sort: int, exclusive: bool, values: T28[], articleCount?: int }`
|
||||
- `T17` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: int|null, first_message?: str|null }`
|
||||
- `T18` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }`
|
||||
- `T19` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
|
||||
- `T20` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
||||
- `T21` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
|
||||
- `T22` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null }`
|
||||
- `T23` = `{ id: str, type: str, x: num, y: num, w?: num, h?: num, text?: str, font?: str, fontSize?: num, barcodeFormat?: str }`
|
||||
- `T24` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }`
|
||||
- `T25` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
|
||||
- `T26` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }`
|
||||
- `T27` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
||||
- `T28` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
||||
- `T29` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
||||
- `T30` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
||||
- `T31` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
||||
- `T32` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }`
|
||||
- `T33` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }`
|
||||
- `T34` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
||||
- `T35` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
||||
- `T36` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
||||
- `T37` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
||||
- `T38` = `{ name: str, sizeBytes: int, sourceExists: bool }`
|
||||
- `T39` = `{ net: num, taxId: int, taxRate?: num|null }`
|
||||
- `T8` = `{ id: int, articleId: int, entityId: int, pu: num, sort: int, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T41|null, incoming?: { date: str, amount: num }[] }`
|
||||
- `T9` = `{ id: int, name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] }, thumb?: str|null }`
|
||||
- `T10` = `{ id: int, manifestId: int, fileId: int, sort: int, hash: str, mimetype: str, name: str, size: int, created?: str, modified?: str, skeledash?: str|null, thumbx?: int|null, thumby?: int|null, meta?: { [k]: str }|null }`
|
||||
- `T11` = `{ vfs: T40, storage: T40, database: T34 }`
|
||||
- `T12` = `{ id: int, name: str, companyId?: int, token?: str|null, pairingpin?: str|null, pairedAt?: str|null, agentversion?: str|null, lastseenAt?: str|null, updatedAt?: str, sort?: int, enabled?: bool }`
|
||||
- `T13` = `{ id: int, printeragentId: int, type: str, enabled: bool, sort: int, model?: str|null, serial?: str|null, open?: bool, pause?: bool, error?: bool, online?: bool, labelLength?: int|null }`
|
||||
- `T14` = `{ id: int, entityId: int, addressType: str, firstName?: str|null, name?: str|null, street?: str|null, streetNumber?: str|null, zip?: str|null, city?: str|null, county?: str|null }`
|
||||
- `T15` = `{ id: int, company_id: int, prompt: str, max_itr: int, cron: str, created_at: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, lastrun_at?: str|null }`
|
||||
- `T16` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }`
|
||||
- `T17` = `{ id: int, name: str, sort: int, exclusive: bool, values: T30[], articleCount?: int }`
|
||||
- `T18` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: int|null, first_message?: str|null }`
|
||||
- `T19` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }`
|
||||
- `T20` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
|
||||
- `T21` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
||||
- `T22` = `{ id: int, agentprinterId: int, type: str, state: str, data: str, createdAt: str, printeragentId?: int|null, completedAt?: str|null }`
|
||||
- `T23` = `{ id: int, manifestId: int, number: str, carrierId: int, type: str, state: str, trackingCode?: str|null, carrier?: str|null }`
|
||||
- `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` = `{ id: int, company_id: int, address: str, type: str, sort: int, model?: str|null, serial?: str|null, desc?: str|null }`
|
||||
- `T27` = `{ id: int, customergroupId: int, firstName: str|null, name: str, number: str|null, createdAt: str, updatedAt: str }`
|
||||
- `T28` = `{ name: str, id?: int|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null }`
|
||||
- `T29` = `{ si: str, basesi: str, basesifactor: num, symbol?: str|null, name?: str|null }`
|
||||
- `T30` = `{ id: int, attributeId: int, value: str, sort: int, articleCount?: int }`
|
||||
- `T31` = `{ id: int, name: str, sort: int, exclusive: bool, articleCount?: int }`
|
||||
- `T32` = `{ id: int, accountId: int, manifestId: int, type: str, amount: num }`
|
||||
- `T33` = `{ id: int, name: str, sort: int, hasPackages: bool, isReverse: bool }`
|
||||
- `T34` = `{ name: str|null, sizeBytes: int, backedUp: bool, reason?: str|null }`
|
||||
- `T35` = `{ id: int, email: str, selectedCompanyId: int|null, picture?: str }`
|
||||
- `T36` = `{ fullHash: str, thumbW: int, thumbH: int, thumbHash: str }`
|
||||
- `T37` = `{ id: int, entityId: int, contactType: str, contact: str }`
|
||||
- `T38` = `{ id: int, name: str, createdAt: str, updatedAt: str }`
|
||||
- `T39` = `{ totalPositions: int, completelyOnLedgerPositions: int }`
|
||||
- `T40` = `{ name: str, sizeBytes: int, sourceExists: bool }`
|
||||
- `T41` = `{ net: num, taxId: int, taxRate?: num|null }`
|
||||
|
||||
### Routes
|
||||
|
||||
@@ -204,6 +206,39 @@ _none_
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
|
||||
### Section: /agent
|
||||
|
||||
#### Printer agent SSE stream — `GET /agent/events`
|
||||
- Params: `query: token?: str, agentversion?: str`
|
||||
- Returns: `200 SSE`
|
||||
#### Pair printer agent via query parameters — `GET /agent/pair`
|
||||
- Params: `query: authCode?: str, pairingpin?: str, serial?: str, model?: str, version?: str`
|
||||
- Returns: `200`
|
||||
#### Pair printer agent — `POST /agent/pair`
|
||||
- Params: `body: { pairingpin: str, clientserial?: str, clientmodel?: str, agentversion?: str }`
|
||||
- Returns: `200 { id: int, name: str, companyId: int, token: str, pairedAt: str, clientserial: str|null, clientmodel: str|null, agentversion: str|null, sort: int }`
|
||||
#### Post agent state — `POST /agent/state`
|
||||
- Params: `body: { state?: any }`
|
||||
- Returns: `200 { success: bool, agent: T12 }`
|
||||
|
||||
### Section: /agentprinter
|
||||
|
||||
#### List agent printers — `GET /agentprinter`
|
||||
- Params: `query: printeragentId?: int`
|
||||
- Returns: `200 { agentprinters: T13[] }`
|
||||
#### Reorder agent printers — `PUT /agentprinter/order`
|
||||
- Params: `body: { printeragentId: int, agentprinterIds: int[] }`
|
||||
- Returns: `200 { agentprinterIds: int[] }`
|
||||
#### Delete agent printer — `DELETE /agentprinter/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get agent printer — `GET /agentprinter/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { agentprinter: T13 }`
|
||||
#### Update agent printer — `PATCH /agentprinter/{id}`
|
||||
- Params: `id: int, body: { enabled?: bool, sort?: int }`
|
||||
- Returns: `200 { agentprinter: T13 }`
|
||||
|
||||
### Section: /article
|
||||
|
||||
#### Create article in category — `POST /article`
|
||||
@@ -220,7 +255,7 @@ _none_
|
||||
- Returns: `204 void`
|
||||
#### Get article by SKU — `GET /article/{sku}`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T27, brand: T26|null, categoryIds: int[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], prices: { minmultiplier: num, net: num, taxRate: num, customergroupId: int }[], suppliers: T9[], undeliveredOrders: { orderId: int, orderNumber: str, date: str, amount: num, customerName?: str|null }[] } }`
|
||||
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, desc: str|null, seo: str|null, track_stock: bool, unitamount: num|null, weight: num|null, unit: T29, brand: T28|null, categoryIds: int[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], prices: { minmultiplier: num, net: num, taxRate: num, customergroupId: int }[], suppliers: T8[], undeliveredOrders: { orderId: int, orderNumber: str, date: str, amount: num, customerName?: str|null }[] } }`
|
||||
#### Update article by SKU — `PATCH /article/{sku}`
|
||||
- Params: `sku: str, body: { name?: str, desc?: str, seo?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
|
||||
- Returns: `204 void`
|
||||
@@ -295,10 +330,10 @@ _none_
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### List article suppliers — `GET /article/{sku}/supplier`
|
||||
- Params: `sku: str`
|
||||
- Returns: `200 { suppliers: T9[] }`
|
||||
- Returns: `200 { suppliers: T8[] }`
|
||||
#### 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: T9 }`
|
||||
- Returns: `201 { supplier: T8 }`
|
||||
#### Reorder article suppliers — `PUT /article/{sku}/supplier/order`
|
||||
- Params: `sku: str, body: { ids: int[] }`
|
||||
- Returns: `200 { ids: int[] }`
|
||||
@@ -307,7 +342,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: T9 }`
|
||||
- Returns: `200 { supplier: T8 }`
|
||||
#### Set article track_stock flag by SKU — `PUT /article/{sku}/track_stock`
|
||||
- Params: `sku: str, body: { track_stock: bool }`
|
||||
- Returns: `204 void`
|
||||
@@ -319,19 +354,19 @@ _none_
|
||||
|
||||
#### List attribute names — `GET /attributename`
|
||||
_none_
|
||||
- Returns: `200 { attributenames: T16[] }`
|
||||
- Returns: `200 { attributenames: T17[] }`
|
||||
#### Create attribute name — `POST /attributename`
|
||||
- Params: `body: { name: str, sort?: int, exclusive?: bool }`
|
||||
- Returns: `201 { attributename: T16 }`
|
||||
- Returns: `201 { attributename: T17 }`
|
||||
#### 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: T28[] }`
|
||||
- Returns: `200 { values: T30[] }`
|
||||
#### Create value for an attribute — `POST /attributename/{attributeId}/value`
|
||||
- Params: `attributeId: int, body: { value: str, sort?: int }`
|
||||
- Returns: `201 { attributevalue: T28 }`
|
||||
- Returns: `201 { attributevalue: T30 }`
|
||||
#### Reorder values of an attribute — `PUT /attributename/{attributeId}/value/order`
|
||||
- Params: `attributeId: int, body: { attributevalueIds: int[] }`
|
||||
- Returns: `200 { ok: bool }`
|
||||
@@ -340,7 +375,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: T16 }`
|
||||
- Returns: `200 { attributename: T17 }`
|
||||
|
||||
### Section: /attributevalue
|
||||
|
||||
@@ -349,28 +384,28 @@ _none_
|
||||
- Returns: `200 { ok: bool }`
|
||||
#### Update attribute value — `PATCH /attributevalue/{id}`
|
||||
- Params: `id: int, body: { value?: str, sort?: int }`
|
||||
- Returns: `200 { attributevalue: T28 }`
|
||||
- Returns: `200 { attributevalue: T30 }`
|
||||
#### 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: T34[] } }`
|
||||
- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T36[] } }`
|
||||
#### 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: T34[] } }`
|
||||
- Returns: `201 { attributevalue: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T36[] } }`
|
||||
|
||||
### Section: /auth
|
||||
|
||||
#### Login with Google ID token — `POST /auth/login`
|
||||
- Params: `body: { idToken: str }`
|
||||
- Returns: `200 { message: str, user: T33 }`
|
||||
- Returns: `200 { message: str, user: T35 }`
|
||||
#### Logout — `POST /auth/logout`
|
||||
_none_
|
||||
- Returns: `200 { message: str }`
|
||||
#### Current user — `GET /auth/me`
|
||||
_none_
|
||||
- Returns: `200 { user: T33, googleClientId: str, schemaVersion: int, vhost: bool, vhostSettings: str }`
|
||||
- Returns: `200 { user: T35, googleClientId: str, schemaVersion: int, vhost: bool, vhostSettings: str }`
|
||||
#### Set selected company — `PUT /auth/selected-company`
|
||||
- Params: `body: { companyId: int }`
|
||||
- Returns: `200 { user: T33 }`
|
||||
- Returns: `200 { user: T35 }`
|
||||
|
||||
### Section: /backup
|
||||
|
||||
@@ -391,7 +426,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: T34[], gsprpinfo?: str|null } }`
|
||||
- Returns: `201 { brand: { id: int, name: str, pics: T36[], gsprpinfo?: str|null } }`
|
||||
#### Delete brand — `DELETE /brand/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
@@ -400,19 +435,19 @@ _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: T34[] } }`
|
||||
- Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T36[] } }`
|
||||
#### 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: T34[] } }`
|
||||
- Returns: `201 { brand: { id: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str, pics: T36[] } }`
|
||||
|
||||
### Section: /carrier
|
||||
|
||||
#### List carriers — `GET /carrier`
|
||||
_none_
|
||||
- Returns: `200 { carriers: T31[] }`
|
||||
- Returns: `200 { carriers: T33[] }`
|
||||
#### Create carrier — `POST /carrier`
|
||||
- Params: `body: { name: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||
- Returns: `201 { carrier: T31 }`
|
||||
- Returns: `201 { carrier: T33 }`
|
||||
#### Reorder carriers — `PUT /carrier/order`
|
||||
- Params: `body: { carrierIds: int[] }`
|
||||
- Returns: `200 { carrierIds: int[] }`
|
||||
@@ -421,10 +456,10 @@ _none_
|
||||
- Returns: `204 void`
|
||||
#### Get carrier by ID — `GET /carrier/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { carrier: T31 }`
|
||||
- Returns: `200 { carrier: T33 }`
|
||||
#### Update carrier — `PATCH /carrier/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int, hasPackages?: bool, isReverse?: bool }`
|
||||
- Returns: `200 { carrier: T31 }`
|
||||
- Returns: `200 { carrier: T33 }`
|
||||
|
||||
### Section: /category
|
||||
|
||||
@@ -448,7 +483,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, query: includeChildren?: bool`
|
||||
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null, brand?: T26|null, articlewarehousesummary?: { id: int, articleId: int, warehouseId: int, stock: num, article_id?: int, warehouse_id?: int, warehouseName?: str|null }[], warehouses?: { id: int, name: str, stock: num }[] }[] }`
|
||||
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, track_stock: bool, optionsCount: int, articleListCount: int, childCount: int, net?: num|null, taxRate?: num|null, parentArticleId?: int|null, parentSku?: str|null, fullHash?: str|null, thumbW?: int|null, thumbH?: int|null, thumbHash?: str|null, smallThumbW?: int|null, smallThumbH?: int|null, smallThumbBinary?: str|null, brand?: T28|null, articlewarehousesummary?: { id: int, articleId: int, warehouseId: int, stock: num, article_id?: int, warehouse_id?: int, warehouseName?: str|null }[], warehouses?: { id: int, name: str, stock: num }[] }[] }`
|
||||
#### List category attribute values — `GET /category/{id}/attributevalue`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { attributevalues: { id: int, attributeId: int, attributeName: str, attributeValueId: int, value: str, sort: int, categoryId?: int, exclusive?: bool }[] }`
|
||||
@@ -478,10 +513,10 @@ _none_
|
||||
|
||||
#### List companies — `GET /company`
|
||||
_none_
|
||||
- Returns: `200 { companies: T18[] }`
|
||||
- Returns: `200 { companies: T19[] }`
|
||||
#### Create company — `POST /company`
|
||||
- Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
||||
- Returns: `201 { company: T18, user: T33 }`
|
||||
- Returns: `201 { company: T19, user: T35 }`
|
||||
#### Export company catalog as zip archive (SSE) — `GET /company/export`
|
||||
- Params: `query: stream?: str`
|
||||
- Returns: `200 SSE`
|
||||
@@ -511,7 +546,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: T18 }`
|
||||
- Returns: `200 { company: T19 }`
|
||||
|
||||
### Section: /customergroup
|
||||
|
||||
@@ -535,7 +570,7 @@ _none_
|
||||
|
||||
#### List deliveries — `GET /delivery`
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnbooked?: bool, supplierId?: int, seekMonths?: int`
|
||||
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T37 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
- Returns: `200 { deliveries: { id: int, number: str, type: str, supplierId: int, date: str, externalNumber?: str|null, createdAt?: str, ledger?: T39 }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Create delivery — `POST /delivery`
|
||||
- Params: `body: { externalNumber: str, date: str, supplierId: int, lines: { amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null }[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
|
||||
- Returns: `201 { delivery: T4 }`
|
||||
@@ -556,52 +591,52 @@ _none_
|
||||
- Returns: `200 str`
|
||||
#### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T11, fileUrl: str }`
|
||||
- Returns: `201 { attachment: T10, fileUrl: str }`
|
||||
|
||||
### Section: /entity
|
||||
|
||||
#### List entities — `GET /entity`
|
||||
- Params: `query: customergroupId?: int, role?: str`
|
||||
- Returns: `200 { entities: T25[] }`
|
||||
- Returns: `200 { entities: T27[] }`
|
||||
#### Create entity — `POST /entity`
|
||||
- Params: `body: { customergroupId: int, name: str, firstName?: str|null, number?: str }`
|
||||
- Returns: `201 { entity: T25 }`
|
||||
- Returns: `201 { entity: T27 }`
|
||||
#### 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: T13[] }`
|
||||
- Returns: `200 { addresses: T14[] }`
|
||||
#### 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: T13 }`
|
||||
- Returns: `201 { address: T14 }`
|
||||
#### 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: T13 }`
|
||||
- Returns: `200 { address: T14 }`
|
||||
#### List entity contacts — `GET /entity/{entityId}/contact`
|
||||
- Params: `entityId: int`
|
||||
- Returns: `200 { contacts: T35[] }`
|
||||
- Returns: `200 { contacts: T37[] }`
|
||||
#### Create entity contact — `POST /entity/{entityId}/contact`
|
||||
- Params: `entityId: int, body: { contactType: str, contact: str }`
|
||||
- Returns: `201 { contact: T35 }`
|
||||
- Returns: `201 { contact: T37 }`
|
||||
#### 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: T35 }`
|
||||
- Returns: `200 { contact: T37 }`
|
||||
#### Delete entity — `DELETE /entity/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get entity — `GET /entity/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { entity: T25 }`
|
||||
- Returns: `200 { entity: T27 }`
|
||||
#### Update entity — `PATCH /entity/{id}`
|
||||
- Params: `id: int, body: { name?: str, firstName?: str|null, number?: str|null }`
|
||||
- Returns: `200 { entity: T25 }`
|
||||
- Returns: `200 { entity: T27 }`
|
||||
|
||||
### Section: /events
|
||||
|
||||
@@ -621,6 +656,9 @@ _none_
|
||||
- `propertyname`: `{}`
|
||||
- `labeltemplate`: `{}`
|
||||
- `printer`: `{}`
|
||||
- `printeragent`: `{}`
|
||||
- `agentprinter`: `{ printers } or {}`
|
||||
- `printerjob`: `{ job } or { jobId, state }`
|
||||
- `warehouse`: `{}`
|
||||
- `supplier`: `{}`
|
||||
- `vfs`: `{}`
|
||||
@@ -649,35 +687,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: { objects: T23[] } }`
|
||||
- Returns: `201 { labeltemplate: T10 }`
|
||||
- Params: `body: { name: str, width: num, height: num, dpi: int, type: str, content: { objects: T25[] } }`
|
||||
- Returns: `201 { labeltemplate: T9 }`
|
||||
#### Delete label template — `DELETE /labeltemplate/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get label template by ID — `GET /labeltemplate/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { labeltemplate: T10 }`
|
||||
- Returns: `200 { labeltemplate: T9 }`
|
||||
#### Update label template (partial) — `PATCH /labeltemplate/{id}`
|
||||
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T23[] } }`
|
||||
- Returns: `200 { labeltemplate: T10 }`
|
||||
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T25[] } }`
|
||||
- Returns: `200 { labeltemplate: T9 }`
|
||||
#### Update label template — `PUT /labeltemplate/{id}`
|
||||
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T23[] } }`
|
||||
- Returns: `200 { labeltemplate: T10 }`
|
||||
- Params: `id: int, body: { name?: str, width?: num, height?: num, dpi?: int, type?: str, content?: { objects: T25[] } }`
|
||||
- Returns: `200 { labeltemplate: T9 }`
|
||||
|
||||
### Section: /ledger
|
||||
|
||||
#### Query ledger by manifest article ID or SKU — `GET /ledger`
|
||||
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
|
||||
- Returns: `200 { ledger: T15[] }`
|
||||
- Returns: `200 { ledger: T16[] }`
|
||||
#### Query company-wide ledger history — `GET /ledger/history`
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||
- Returns: `200 { ledger: { id: int, date: str, transaction_type: str, transaction_text: str, sku?: str|null, name?: str|null, amount?: num }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
|
||||
- Params: `manifestarticleId: int, query: transaction_type?: str`
|
||||
- Returns: `200 { ledger: T15[] }`
|
||||
- Returns: `200 { ledger: T16[] }`
|
||||
#### Query ledger by article SKU — `GET /ledger/sku/{sku}`
|
||||
- Params: `sku: str, query: transaction_type?: str`
|
||||
- Returns: `200 { ledger: T15[] }`
|
||||
- Returns: `200 { ledger: T16[] }`
|
||||
|
||||
### Section: /mcp
|
||||
|
||||
@@ -707,16 +745,16 @@ _none_
|
||||
|
||||
#### List conversations — `GET /modelconversation`
|
||||
_none_
|
||||
- Returns: `200 { conversations: T17[] }`
|
||||
- Returns: `200 { conversations: T18[] }`
|
||||
#### Create conversation — `POST /modelconversation`
|
||||
- Params: `body: { provider: str, model: str }`
|
||||
- Returns: `201 { conversation: T17 }`
|
||||
- Returns: `201 { conversation: T18 }`
|
||||
#### Delete conversation — `DELETE /modelconversation/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get conversation — `GET /modelconversation/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { conversation: T17 }`
|
||||
- Returns: `200 { conversation: T18 }`
|
||||
#### 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 }[] }`
|
||||
@@ -757,7 +795,7 @@ _none_
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, onlyUnpaid?: bool, onlyUndelivered?: bool, seekMonths?: int`
|
||||
- Returns: `200 { orders: T1[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Create order — `POST /order`
|
||||
- Params: `body: { number: str, date: str, entityId: int, lines: T20[], externalNumber?: str|null }`
|
||||
- Params: `body: { number: str, date: str, entityId: int, lines: T21[], externalNumber?: str|null }`
|
||||
- Returns: `201 { order: T1 }`
|
||||
#### Delete order — `DELETE /order/{id}`
|
||||
- Params: `id: int`
|
||||
@@ -766,7 +804,7 @@ _none_
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { order: T1 }`
|
||||
#### Update order — `PATCH /order/{id}`
|
||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T20[] }`
|
||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T21[] }`
|
||||
- Returns: `200 { order: T1 }`
|
||||
#### Delete order file attachment — `DELETE /order/{id}/file/{fileId}`
|
||||
- Params: `id: int, fileId: int`
|
||||
@@ -776,13 +814,13 @@ _none_
|
||||
- Returns: `200 str`
|
||||
#### List payments of an order — `GET /order/{id}/payment`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { payments: T30[] }`
|
||||
- Returns: `200 { payments: T32[] }`
|
||||
#### Add payment to an order — `POST /order/{id}/payment`
|
||||
- Params: `id: int, body: { accountId: int, type: str, amount: num }`
|
||||
- Returns: `201 { payment: T30 }`
|
||||
- Returns: `201 { payment: T32 }`
|
||||
#### Generate and attach order PDF — `POST /order/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T11, fileUrl: str }`
|
||||
- Returns: `201 { attachment: T10, fileUrl: str }`
|
||||
#### List shipments of an order — `GET /order/{id}/shipment`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shipments: T2[] }`
|
||||
@@ -791,10 +829,10 @@ _none_
|
||||
- Returns: `201 { shipment: T2 }`
|
||||
#### List shippings of an order — `GET /order/{id}/shipping`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shippings: T21[] }`
|
||||
- Returns: `200 { shippings: T23[] }`
|
||||
#### Create shipping for an order — `POST /order/{id}/shipping`
|
||||
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||
- Returns: `201 { shipping: T21 }`
|
||||
- Returns: `201 { shipping: T23 }`
|
||||
|
||||
### Section: /package
|
||||
|
||||
@@ -839,49 +877,73 @@ _none_
|
||||
|
||||
#### List printers — `GET /printer`
|
||||
_none_
|
||||
- Returns: `200 { printers: T24[] }`
|
||||
- Returns: `200 { printers: T26[] }`
|
||||
#### Create printer — `POST /printer`
|
||||
- Params: `body: { address: str, type: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
||||
- Returns: `201 { printer: T24 }`
|
||||
- Returns: `201 { printer: T26 }`
|
||||
#### Reorder printers — `PUT /printer/order`
|
||||
- Params: `body: { printerIds: int[] }`
|
||||
- Returns: `200 { printerIds: int[] }`
|
||||
#### Get live printer states — `GET /printer/state`
|
||||
- Params: `query: serial?: str, agentId?: int`
|
||||
- Returns: `200 { printers: {}[] }`
|
||||
#### Delete printer — `DELETE /printer/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get printer by ID — `GET /printer/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { printer: T24 }`
|
||||
- Returns: `200 { printer: T26 }`
|
||||
#### Update printer (partial) — `PATCH /printer/{id}`
|
||||
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
||||
- Returns: `200 { printer: T24 }`
|
||||
- Returns: `200 { printer: T26 }`
|
||||
#### Update printer — `PUT /printer/{id}`
|
||||
- Params: `id: int, body: { address?: str, type?: str, model?: str|null, serial?: str|null, sort?: int, desc?: str|null }`
|
||||
- Returns: `200 { printer: T24 }`
|
||||
- Returns: `200 { printer: T26 }`
|
||||
#### Get live state for a printer — `GET /printer/{id}/state`
|
||||
- Params: `id: str`
|
||||
- Returns: `200 { printer: {} }`
|
||||
|
||||
### Section: /printeragent
|
||||
|
||||
#### List printer agents — `GET /printeragent`
|
||||
_none_
|
||||
- Returns: `200 { printeragents: T8[] }`
|
||||
- Returns: `200 { printeragents: T12[] }`
|
||||
#### Create printer agent — `PUT /printeragent`
|
||||
- Params: `body: { name: str, sort?: int }`
|
||||
- Returns: `200 { printeragent: T8 }`
|
||||
- Params: `body: { name: str, sort?: int, enabled?: bool }`
|
||||
- Returns: `200 { printeragent: T12 }`
|
||||
#### Delete printer agent — `DELETE /printeragent/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Update printer agent — `PATCH /printeragent/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int }`
|
||||
- Returns: `200 { printeragent: T8 }`
|
||||
- Params: `id: int, body: { name?: str, sort?: int, enabled?: bool }`
|
||||
- Returns: `200 { printeragent: T12 }`
|
||||
|
||||
### Section: /printerjob
|
||||
|
||||
#### List printer jobs — `GET /printerjob`
|
||||
- Params: `query: agentprinterId?: int, state?: str`
|
||||
- Returns: `200 { printerjobs: T22[] }`
|
||||
#### Create print job — `POST /printerjob`
|
||||
- Params: `body: { agentprinterId: int, data: str, type?: str }`
|
||||
- Returns: `201 { printerjob: T22 }`
|
||||
#### Cancel and delete printer job — `DELETE /printerjob/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { success: bool, deletedJob: T22 }`
|
||||
#### Get printer job — `GET /printerjob/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { printerjob: T22 }`
|
||||
#### Cancel printer job — `POST /printerjob/{id}/cancel`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { success: bool, printerjob: T22 }`
|
||||
|
||||
### Section: /propertyname
|
||||
|
||||
#### List property names — `GET /propertyname`
|
||||
_none_
|
||||
- Returns: `200 { propertynames: T29[] }`
|
||||
- Returns: `200 { propertynames: T31[] }`
|
||||
#### Create property name — `POST /propertyname`
|
||||
- Params: `body: { name: str, sort?: int, exclusive?: bool }`
|
||||
- Returns: `201 { propertyname: T29 }`
|
||||
- Returns: `201 { propertyname: T31 }`
|
||||
#### Reorder property names — `PUT /propertyname/order`
|
||||
- Params: `body: { propertynameIds: int[] }`
|
||||
- Returns: `200 { ok: bool }`
|
||||
@@ -890,7 +952,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: T29 }`
|
||||
- Returns: `200 { propertyname: T31 }`
|
||||
|
||||
### Section: /public
|
||||
|
||||
@@ -902,7 +964,7 @@ _none_
|
||||
- Returns: `200 { articles: { id: int, sku: str, name: str, slug: str, net: num|null, taxRate: num|null, gross: num|null, trackStock: bool, brand: { id: int|null, name: str }, fullHash: str|null, thumbW: int|null, thumbH: int|null, thumbHash: str|null }[] }`
|
||||
#### Get public article detail — `GET /public/{companyId}/articles/{sku}`
|
||||
- Params: `companyId: int, sku: str`
|
||||
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, description: str, unitamount: num|null, weight: num|null, trackStock: bool, unit: T27|null, brand: T26|null, price: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }, prices: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }[], categories: { id: int, name: str, slug: str }[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], isParent: bool, parentSku: str|null, variants: { id: int, sku: str, sort: int, name: str, slug: str, attributeValueId: int|null, attributeValue: str|null, unitamount: num|null, weight: num|null, trackStock: bool }[] } }`
|
||||
- Returns: `200 { article: { id: int, sku: str, name: str, slug: str, description: str, unitamount: num|null, weight: num|null, trackStock: bool, unit: T29|null, brand: T28|null, price: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }, prices: { minmultiplier: num, net: num|null, taxRate: num|null, gross: num|null }[], categories: { id: int, name: str, slug: str }[], photos: { sort: int, fullHash: str, fullW: int, fullH: int, fullSize: int, thumbW: int, thumbH: int, thumbHash: str }[], eans: str[], isParent: bool, parentSku: str|null, variants: { id: int, sku: str, sort: int, name: str, slug: str, attributeValueId: int|null, attributeValue: str|null, unitamount: num|null, weight: num|null, trackStock: bool }[] } }`
|
||||
#### List public categories — `GET /public/{companyId}/categories`
|
||||
- Params: `companyId: int`
|
||||
- Returns: `200 { categories: { id: int, parentId: int|null, sort: int, name: str, slug: str, fullHash: str|null, thumbW: int|null, thumbH: int|null, thumbHash: str|null }[] }`
|
||||
@@ -913,7 +975,7 @@ _none_
|
||||
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||
- Returns: `200 { quotes: T3[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Create quote — `POST /quote`
|
||||
- Params: `body: { number: str, date: str, lines: T20[], entityId?: int|null, externalNumber?: str|null }`
|
||||
- Params: `body: { number: str, date: str, lines: T21[], entityId?: int|null, externalNumber?: str|null }`
|
||||
- Returns: `201 { quote: T3 }`
|
||||
#### Delete quote — `DELETE /quote/{id}`
|
||||
- Params: `id: int`
|
||||
@@ -922,7 +984,7 @@ _none_
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { quote: T3 }`
|
||||
#### Update quote — `PATCH /quote/{id}`
|
||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T20[] }`
|
||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, externalNumber?: str|null, lines?: T21[] }`
|
||||
- Returns: `200 { quote: T3 }`
|
||||
#### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}`
|
||||
- Params: `id: int, fileId: int`
|
||||
@@ -931,11 +993,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?: T20[] }`
|
||||
- Params: `id: int, body?: { number?: str, date?: str, createdAt?: str, entityId?: int|null, externalNumber?: str|null, lines?: T21[] }`
|
||||
- Returns: `201 { order: T1 }`
|
||||
#### Generate and attach quote PDF — `POST /quote/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T11, fileUrl: str }`
|
||||
- Returns: `201 { attachment: T10, fileUrl: str }`
|
||||
|
||||
### Section: /retoure
|
||||
|
||||
@@ -1022,13 +1084,13 @@ _none_
|
||||
- Returns: `200 str`
|
||||
#### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf`
|
||||
- Params: `id: int`
|
||||
- Returns: `201 { attachment: T11, fileUrl: str }`
|
||||
- Returns: `201 { attachment: T10, fileUrl: str }`
|
||||
#### List packages of a shipment — `GET /shipment/{id}/shipping`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { shippings: T21[] }`
|
||||
- Returns: `200 { shippings: T23[] }`
|
||||
#### Create package for a shipment — `POST /shipment/{id}/shipping`
|
||||
- Params: `id: int, body: { carrierId: int, type: str, state: str, trackingCode?: str, warehouseId?: int }`
|
||||
- Returns: `201 { shipping: T21 }`
|
||||
- Returns: `201 { shipping: T23 }`
|
||||
|
||||
### Section: /shipping
|
||||
|
||||
@@ -1037,7 +1099,7 @@ _none_
|
||||
- Returns: `204 void`
|
||||
#### Update shipping — `PATCH /shipping/{id}`
|
||||
- Params: `id: int, body: { type?: str, state?: str, trackingCode?: str, carrierId?: int, warehouseId?: int }`
|
||||
- Returns: `200 { shipping: T21 }`
|
||||
- Returns: `200 { shipping: T23 }`
|
||||
#### 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 }[] }`
|
||||
@@ -1058,7 +1120,7 @@ _none_
|
||||
- Returns: `200`
|
||||
#### Adjust stock manually — `POST /stock/adjust`
|
||||
- Params: `body: { sku?: str, warehouseId?: int, amount?: num, text?: str|null, lines?: { sku: str, amount: num, warehouseId?: int, text?: str }[] }`
|
||||
- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T15[] }`
|
||||
- Returns: `201 { manifest: { id: int, number: str, type: str, date: str, createdAt: str }, lines: { manifestarticle_id: int, sku: str, name: str, warehouse_id: int, amount: num, text: str }[], ledger: T16[] }`
|
||||
#### Ship shipment manifest item(s) — `POST /stock/ship`
|
||||
- Params: `body: { manifestarticleId?: int, warehouseId?: int, items?: { manifestarticleId: int, warehouseId?: int }[] }`
|
||||
- Returns: `200`
|
||||
@@ -1067,40 +1129,40 @@ _none_
|
||||
|
||||
#### List subagent schedules — `GET /subagentschedule`
|
||||
_none_
|
||||
- Returns: `200 { schedules: T14[] }`
|
||||
- Returns: `200 { schedules: T15[] }`
|
||||
#### Create subagent schedule — `POST /subagentschedule`
|
||||
- Params: `body: { prompt: str, cron: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int }`
|
||||
- Returns: `201 { schedule: T14 }`
|
||||
- Returns: `201 { schedule: T15 }`
|
||||
#### Delete subagent schedule — `DELETE /subagentschedule/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Get subagent schedule by ID — `GET /subagentschedule/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { schedule: T14 }`
|
||||
- Returns: `200 { schedule: T15 }`
|
||||
#### Update subagent schedule — `PUT /subagentschedule/{id}`
|
||||
- Params: `id: int, body: { prompt?: str, cron?: str, desc?: str|null, skills?: str|null, lastagentnote?: str|null, max_itr?: int, lastrun_at?: str|null }`
|
||||
- Returns: `200 { schedule: T14 }`
|
||||
- Returns: `200 { schedule: T15 }`
|
||||
|
||||
### Section: /supplier
|
||||
|
||||
#### List suppliers — `GET /supplier`
|
||||
_none_
|
||||
- Returns: `200 { suppliers: T36[] }`
|
||||
- Returns: `200 { suppliers: T38[] }`
|
||||
#### Create supplier — `POST /supplier`
|
||||
- Params: `body: { name: str }`
|
||||
- Returns: `201 { supplier: T36 }`
|
||||
- Returns: `201 { supplier: T38 }`
|
||||
#### 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: T36 }`
|
||||
- Returns: `200 { supplier: T38 }`
|
||||
#### List supplier articles by SKUs — `GET /supplier/{id}/articles`
|
||||
- Params: `id: int, query: skus?: str`
|
||||
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T39|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T41|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||
#### Query supplier articles by SKUs (bulk) — `POST /supplier/{id}/articles`
|
||||
- Params: `id: int, body?: { skus?: str[] }`
|
||||
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T39|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||
- Returns: `200 { articles: { id: int, articleId: int, entityId: int, pu: num, sort: int, article: { id: int, sku: str, name?: str|null, slug?: str|null, photos?: { sort: int, fullHash: str, thumbW: int, thumbH: int, thumbHash: str }[], eans?: str[] }, supplierName?: str|null, sku?: str|null, stock?: num|null, priceId?: int|null, price?: T41|null, incoming?: { date: str, amount: num }[] }[] }`
|
||||
|
||||
### Section: /thumb
|
||||
|
||||
@@ -1115,10 +1177,10 @@ _none_
|
||||
- Returns: `204 void`
|
||||
#### List units — `GET /unit`
|
||||
_none_
|
||||
- Returns: `200 { units: T27[] }`
|
||||
- Returns: `200 { units: T29[] }`
|
||||
#### Upsert unit — `PUT /unit`
|
||||
- Params: `body: { si: str, basesi: str, basesifactor: num, symbol: str, name: str }`
|
||||
- Returns: `200 { unit: T27 }`
|
||||
- Returns: `200 { unit: T29 }`
|
||||
|
||||
### Section: /v1
|
||||
|
||||
@@ -1209,3 +1271,9 @@ _none_
|
||||
#### Update warehouse — `PUT /warehouse/{id}`
|
||||
- Params: `id: int, body: { name?: str, sort?: int }`
|
||||
- Returns: `200 { warehouse: { id: int, name: str, sort: int } }`
|
||||
|
||||
### Section: /zpljob
|
||||
|
||||
#### Create ZPL print job — `POST /zpljob`
|
||||
- Params: `body: { agentprinterId: int, data: str, copiescount?: int }`
|
||||
- Returns: `201 { printerjob: T22 }`
|
||||
|
||||
@@ -2,6 +2,67 @@ import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
const banMuiTooltipRule = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Ban MUI Tooltip component',
|
||||
},
|
||||
messages: {
|
||||
badDesign: '<Tooltip> is bad design',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const muiTooltipIdentifiers = new Set();
|
||||
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
if (node.source.value === '@mui/material/Tooltip') {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'badDesign',
|
||||
});
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.local?.name) {
|
||||
muiTooltipIdentifiers.add(specifier.local.name);
|
||||
}
|
||||
}
|
||||
} else if (node.source.value === '@mui/material') {
|
||||
for (const specifier of node.specifiers) {
|
||||
if (
|
||||
specifier.type === 'ImportSpecifier' &&
|
||||
specifier.imported?.name === 'Tooltip'
|
||||
) {
|
||||
context.report({
|
||||
node: specifier,
|
||||
messageId: 'badDesign',
|
||||
});
|
||||
if (specifier.local?.name) {
|
||||
muiTooltipIdentifiers.add(specifier.local.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
JSXOpeningElement(node) {
|
||||
let tagName = '';
|
||||
if (node.name.type === 'JSXIdentifier') {
|
||||
tagName = node.name.name;
|
||||
} else if (node.name.type === 'JSXMemberExpression') {
|
||||
tagName = node.name.property.name;
|
||||
}
|
||||
|
||||
if (muiTooltipIdentifiers.has(tagName) || tagName === 'Tooltip') {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'badDesign',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
@@ -32,10 +93,16 @@ export default [
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
react: {
|
||||
rules: {
|
||||
'no-mui-tooltip': banMuiTooltipRule,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'react/no-mui-tooltip': 'error',
|
||||
'no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"dev:pwa:https": "VITE_HTTPS=1 node scripts/dev.mjs",
|
||||
"cert": "./scripts/generate-cert.sh",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"preview:https": "VITE_HTTPS=1 vite preview",
|
||||
"rig:collect": "node test/collect.mjs",
|
||||
|
||||
69
src/api/agentprinter.js
Normal file
69
src/api/agentprinter.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { http } from './http.js';
|
||||
|
||||
export const agentprinter = {
|
||||
/**
|
||||
* GET /api/agentprinter
|
||||
* Query: ?printeragentId=<id>
|
||||
* → { agentprinters: Agentprinter[] }
|
||||
*/
|
||||
list(printeragentId) {
|
||||
const url =
|
||||
printeragentId != null
|
||||
? `/api/agentprinter?printeragentId=${encodeURIComponent(printeragentId)}`
|
||||
: '/api/agentprinter';
|
||||
return http(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/agentprinter/:id
|
||||
* → { agentprinter: Agentprinter }
|
||||
*/
|
||||
get(id) {
|
||||
return http(`/api/agentprinter/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* PATCH /api/agentprinter/:id
|
||||
* Body: { enabled?: boolean, sort?: number }
|
||||
* → { agentprinter: Agentprinter }
|
||||
*/
|
||||
update(id, patch = {}) {
|
||||
const body = {};
|
||||
if (patch.enabled !== undefined) body.enabled = Boolean(patch.enabled);
|
||||
if (patch.sort !== undefined && patch.sort !== null && patch.sort !== '') {
|
||||
body.sort = Number(patch.sort);
|
||||
}
|
||||
return http(`/api/agentprinter/${id}`, {
|
||||
method: 'PATCH',
|
||||
body,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* PUT /api/agentprinter/order
|
||||
* Body: { printeragentId?: number, agentprinterIds: number[] }
|
||||
* → { agentprinterIds: number[] }
|
||||
*/
|
||||
order({ printeragentId, agentprinterIds }) {
|
||||
const body = {
|
||||
agentprinterIds: Array.isArray(agentprinterIds) ? agentprinterIds.map(Number) : [],
|
||||
};
|
||||
if (printeragentId != null) {
|
||||
body.printeragentId = Number(printeragentId);
|
||||
}
|
||||
return http('/api/agentprinter/order', {
|
||||
method: 'PUT',
|
||||
body,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* DELETE /api/agentprinter/:id → 204
|
||||
*/
|
||||
delete(id) {
|
||||
return http(`/api/agentprinter/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ export { order } from './order.js';
|
||||
export { posterminal } from './posterminal.js';
|
||||
export { printer } from './printer.js';
|
||||
export { printeragent } from './printeragent.js';
|
||||
export { agentprinter } from './agentprinter.js';
|
||||
export { printerjob } from './printerjob.js';
|
||||
export { propertyname } from './propertyname.js';
|
||||
export { quote } from './quote.js';
|
||||
export { retoure } from './retoure.js';
|
||||
|
||||
@@ -51,4 +51,29 @@ export const printer = {
|
||||
return http('/api/printer/order', { method: 'PUT', body: { printerIds } });
|
||||
},
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* GET /api/printer/state
|
||||
* Query: ?serial=, ?agentId=
|
||||
* → { printers: LivePrinter[] }
|
||||
*/
|
||||
// #region GET /api/printer/state — live state
|
||||
state({ serial, agentId } = {}) {
|
||||
const params = new URLSearchParams();
|
||||
if (serial) params.set('serial', serial);
|
||||
if (agentId != null) params.set('agentId', agentId);
|
||||
const query = params.toString();
|
||||
return http(`/api/printer/state${query ? `?${query}` : ''}`);
|
||||
},
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* GET /api/printer/:id/state
|
||||
* → { printer: LivePrinter }
|
||||
*/
|
||||
// #region GET /api/printer/:id/state — single printer state
|
||||
printerState(id) {
|
||||
return http(`/api/printer/${id}/state`);
|
||||
},
|
||||
// #endregion
|
||||
};
|
||||
|
||||
@@ -28,8 +28,8 @@ export const printeragent = {
|
||||
|
||||
/**
|
||||
* PATCH /api/printeragent/:id
|
||||
* Body: { name?: string, sort?: number }
|
||||
* Updates name, sort, or both.
|
||||
* Body: { name?: string, sort?: number, enabled?: boolean }
|
||||
* Updates name, sort, enabled, or any combination.
|
||||
* → 200 { printeragent: Printeragent }
|
||||
*/
|
||||
update(id, patch = {}) {
|
||||
@@ -38,6 +38,7 @@ export const printeragent = {
|
||||
if (patch.sort !== undefined && patch.sort !== null && patch.sort !== '') {
|
||||
body.sort = Number(patch.sort);
|
||||
}
|
||||
if (patch.enabled !== undefined) body.enabled = Boolean(patch.enabled);
|
||||
return http(`/api/printeragent/${id}`, {
|
||||
method: 'PATCH',
|
||||
body,
|
||||
|
||||
100
src/api/printerjob.js
Normal file
100
src/api/printerjob.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { http } from './http.js';
|
||||
|
||||
export const printerjob = {
|
||||
/**
|
||||
* POST /api/zpljob
|
||||
* @param {{ agentprinterId: number, data: string, copiescount?: number }} payload
|
||||
* → { printerjob: PrinterJob }
|
||||
*/
|
||||
createZplJob({ agentprinterId, data, copiescount = 1 }) {
|
||||
return http('/api/zpljob', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
agentprinterId: Number(agentprinterId),
|
||||
data: String(data),
|
||||
copiescount: Number(copiescount) || 1,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a minimal ZPL test label to a specific agent printer.
|
||||
* @param {number} agentprinterId
|
||||
* @param {{ model?: string }} [options]
|
||||
* → { printerjob: PrinterJob }
|
||||
*/
|
||||
sendTestZpl(agentprinterId, { model = 'Zebra' } = {}) {
|
||||
const timeStr = new Date().toLocaleTimeString('de-DE');
|
||||
const safeModel = String(model || 'Drucker').replace(/[\^~]/g, '');
|
||||
const zpl = [
|
||||
'^XA',
|
||||
'^FO40,40^ADN,36,20^FDTestdruck OK^FS',
|
||||
`^FO40,85^ADN,18,10^FD${safeModel}^FS`,
|
||||
`^FO40,115^ADN,18,10^FDID: ${agentprinterId} - ${timeStr}^FS`,
|
||||
'^XZ',
|
||||
].join('\n');
|
||||
return this.createZplJob({
|
||||
agentprinterId,
|
||||
data: zpl,
|
||||
copiescount: 1,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/printerjob
|
||||
* @param {{ agentprinterId?: number, state?: string }} [query]
|
||||
* → { printerjobs: PrinterJob[] }
|
||||
*/
|
||||
list(query = {}) {
|
||||
const params = new URLSearchParams();
|
||||
if (query.agentprinterId != null) params.set('agentprinterId', String(query.agentprinterId));
|
||||
if (query.state != null) params.set('state', String(query.state));
|
||||
const qs = params.toString();
|
||||
return http(qs ? `/api/printerjob?${qs}` : '/api/printerjob');
|
||||
},
|
||||
|
||||
/**
|
||||
* GET /api/printerjob/:id
|
||||
* → { printerjob: PrinterJob }
|
||||
*/
|
||||
get(id) {
|
||||
return http(`/api/printerjob/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* POST /api/printerjob
|
||||
* @param {{ agentprinterId: number, type?: 'zpl'|'pdf'|'epos', data: string }} payload
|
||||
* → { printerjob: PrinterJob }
|
||||
*/
|
||||
create({ agentprinterId, type = 'zpl', data }) {
|
||||
return http('/api/printerjob', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
agentprinterId: Number(agentprinterId),
|
||||
type,
|
||||
data,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* POST /api/printerjob/:id/cancel
|
||||
* → { success: true, printerjob: PrinterJob }
|
||||
*/
|
||||
cancel(id) {
|
||||
return http(`/api/printerjob/${id}/cancel`, {
|
||||
method: 'POST',
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* DELETE /api/printerjob/:id
|
||||
* → { success: true, deletedJob: PrinterJob }
|
||||
*/
|
||||
delete(id) {
|
||||
return http(`/api/printerjob/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,7 +25,10 @@ import {
|
||||
company as companyApi,
|
||||
number as numberApi,
|
||||
posterminal as posterminalApi,
|
||||
printer as printerApi,
|
||||
printeragent as printeragentApi,
|
||||
agentprinter as agentprinterApi,
|
||||
printerjob as printerjobApi,
|
||||
supplier as supplierApi,
|
||||
unit as unitApi,
|
||||
warehouse as warehouseApi,
|
||||
@@ -178,6 +181,15 @@ const sortPrinterAgents = (agents) => {
|
||||
});
|
||||
};
|
||||
|
||||
const sortAgentPrinters = (printers) => {
|
||||
if (!Array.isArray(printers)) return [];
|
||||
return [...printers].sort((a, b) => {
|
||||
const sDiff = (Number(a.sort) || 0) - (Number(b.sort) || 0);
|
||||
if (sDiff !== 0) return sDiff;
|
||||
return (Number(a.id) || 0) - (Number(b.id) || 0);
|
||||
});
|
||||
};
|
||||
|
||||
const sortAccounts = (items) =>
|
||||
[...items].sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
||||
|
||||
@@ -247,12 +259,19 @@ const initialState = {
|
||||
terminalsStatus: 'idle',
|
||||
terminalsError: null,
|
||||
|
||||
// Printer agents
|
||||
// Printer agents & agent printers
|
||||
printeragents: [],
|
||||
printeragentsCompanyId: null,
|
||||
printeragentsStatus: 'idle',
|
||||
printeragentsError: null,
|
||||
|
||||
agentprinters: [],
|
||||
agentprintersCompanyId: null,
|
||||
agentprintersStatus: 'idle',
|
||||
agentprintersError: null,
|
||||
|
||||
livePrinters: [], // LivePrinter[] from /api/printer/state or SSE printer/printerstate
|
||||
|
||||
// 4. Suppliers
|
||||
suppliers: [],
|
||||
articlesBySupplier: {},
|
||||
@@ -320,6 +339,8 @@ let inflightCompany = null;
|
||||
let inflightNumbers = null;
|
||||
let inflightTerminals = null;
|
||||
let inflightPrinterAgents = null;
|
||||
let inflightAgentPrinters = null;
|
||||
let inflightLivePrinters = null;
|
||||
let inflightSuppliers = null;
|
||||
let inflightUnits = null;
|
||||
let inflightWarehouses = null;
|
||||
@@ -514,6 +535,8 @@ const actions = {
|
||||
actions.loadNumbers(),
|
||||
actions.loadTerminals(),
|
||||
actions.loadPrinterAgents(),
|
||||
actions.loadAgentPrinters(),
|
||||
actions.loadLivePrinters(),
|
||||
actions.loadSuppliers(),
|
||||
actions.loadUnits(),
|
||||
actions.loadWarehouses(),
|
||||
@@ -531,6 +554,8 @@ const actions = {
|
||||
inflightNumbers = null;
|
||||
inflightTerminals = null;
|
||||
inflightPrinterAgents = null;
|
||||
inflightAgentPrinters = null;
|
||||
inflightLivePrinters = null;
|
||||
inflightSuppliers = null;
|
||||
inflightUnits = null;
|
||||
inflightWarehouses = null;
|
||||
@@ -779,6 +804,110 @@ const actions = {
|
||||
await printeragentApi.delete(agentId);
|
||||
companyAutomat.setState({
|
||||
printeragents: companyAutomat.state.printeragents.filter((a) => a.id !== agentId),
|
||||
agentprinters: companyAutomat.state.agentprinters.filter((p) => p.printeragentId !== agentId),
|
||||
});
|
||||
},
|
||||
// #endregion
|
||||
|
||||
// #region Agent printers (agentprinter) & live telemetry
|
||||
// MARK: calls agentprinter.list from ../api/index.js
|
||||
async loadAgentPrinters(printeragentId) {
|
||||
const companyId =
|
||||
authAutomat.state.user?.selectedCompanyId ?? companyAutomat.state.current?.id;
|
||||
if (!companyId) return [];
|
||||
if (inflightAgentPrinters) return inflightAgentPrinters;
|
||||
companyAutomat.setState({ agentprintersStatus: 'loading', agentprintersError: null });
|
||||
inflightAgentPrinters = (async () => {
|
||||
try {
|
||||
const res = await agentprinterApi.list(printeragentId);
|
||||
const agentprinters = sortAgentPrinters(Array.isArray(res.agentprinters) ? res.agentprinters : []);
|
||||
companyAutomat.setState({
|
||||
agentprinters,
|
||||
agentprintersCompanyId: companyId,
|
||||
agentprintersStatus: 'succeeded',
|
||||
agentprintersError: null,
|
||||
});
|
||||
return agentprinters;
|
||||
} catch (err) {
|
||||
companyAutomat.setState({
|
||||
agentprintersStatus: 'failed',
|
||||
agentprintersError: err?.message ?? String(err),
|
||||
});
|
||||
throw err;
|
||||
} finally {
|
||||
inflightAgentPrinters = null;
|
||||
}
|
||||
})();
|
||||
return inflightAgentPrinters;
|
||||
},
|
||||
|
||||
reloadAgentPrinters(printeragentId) {
|
||||
return actions.loadAgentPrinters(printeragentId);
|
||||
},
|
||||
|
||||
async updateAgentPrinter(id, patch) {
|
||||
const res = await agentprinterApi.update(id, patch);
|
||||
const updated = res.agentprinter;
|
||||
companyAutomat.setState({
|
||||
agentprinters: sortAgentPrinters(
|
||||
companyAutomat.state.agentprinters.map((p) =>
|
||||
p.id === id ? { ...p, ...updated } : p,
|
||||
),
|
||||
),
|
||||
});
|
||||
return updated;
|
||||
},
|
||||
|
||||
async reorderAgentPrinters({ printeragentId, agentprinterIds }) {
|
||||
await agentprinterApi.order({ printeragentId, agentprinterIds });
|
||||
const idOrder = new Map(agentprinterIds.map((id, index) => [id, index]));
|
||||
companyAutomat.setState({
|
||||
agentprinters: sortAgentPrinters(
|
||||
companyAutomat.state.agentprinters.map((p) =>
|
||||
idOrder.has(p.id) ? { ...p, sort: idOrder.get(p.id) } : p,
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
async deleteAgentPrinter(id) {
|
||||
await agentprinterApi.delete(id);
|
||||
companyAutomat.setState({
|
||||
agentprinters: companyAutomat.state.agentprinters.filter((p) => p.id !== id),
|
||||
});
|
||||
},
|
||||
|
||||
async sendTestPrint(agentprinterId, options = {}) {
|
||||
return printerjobApi.sendTestZpl(agentprinterId, options);
|
||||
},
|
||||
|
||||
async createZplJob(payload) {
|
||||
return printerjobApi.createZplJob(payload);
|
||||
},
|
||||
|
||||
async loadLivePrinters() {
|
||||
const companyId =
|
||||
authAutomat.state.user?.selectedCompanyId ?? companyAutomat.state.current?.id;
|
||||
if (!companyId) return [];
|
||||
if (inflightLivePrinters) return inflightLivePrinters;
|
||||
inflightLivePrinters = (async () => {
|
||||
try {
|
||||
const res = await printerApi.state();
|
||||
const livePrinters = Array.isArray(res.printers) ? res.printers : [];
|
||||
companyAutomat.setState({ livePrinters });
|
||||
return livePrinters;
|
||||
} catch (err) {
|
||||
return [];
|
||||
} finally {
|
||||
inflightLivePrinters = null;
|
||||
}
|
||||
})();
|
||||
return inflightLivePrinters;
|
||||
},
|
||||
|
||||
setLivePrinters(printers) {
|
||||
companyAutomat.setState({
|
||||
livePrinters: Array.isArray(printers) ? printers : [],
|
||||
});
|
||||
},
|
||||
// #endregion
|
||||
@@ -1641,6 +1770,27 @@ sseAutomat.actions.on('posterminal', () => {
|
||||
sseAutomat.actions.on('printeragent', () => {
|
||||
void companyAutomat.actions.loadPrinterAgents().catch(() => {});
|
||||
});
|
||||
sseAutomat.actions.on('agentprinter', () => {
|
||||
void companyAutomat.actions.loadAgentPrinters().catch(() => {});
|
||||
});
|
||||
sseAutomat.actions.on('printerstate', (ev) => {
|
||||
try {
|
||||
const payload = typeof ev?.data === 'string' ? JSON.parse(ev.data || '{}') : (ev?.data || {});
|
||||
if (Array.isArray(payload.printers)) {
|
||||
companyAutomat.actions.setLivePrinters(payload.printers);
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
void companyAutomat.actions.loadLivePrinters().catch(() => {});
|
||||
});
|
||||
sseAutomat.actions.on('printer', (ev) => {
|
||||
try {
|
||||
const payload = typeof ev?.data === 'string' ? JSON.parse(ev.data || '{}') : (ev?.data || {});
|
||||
if (Array.isArray(payload.printers)) {
|
||||
companyAutomat.actions.setLivePrinters(payload.printers);
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
sseAutomat.actions.on('customergroup', () => {
|
||||
void companyAutomat.actions.reloadCustomergroups().catch(() => {});
|
||||
});
|
||||
@@ -1686,6 +1836,11 @@ export const selectPrinterAgents = () => companyAutomat.state.printeragents;
|
||||
export const selectPrinterAgentStatus = () => companyAutomat.state.printeragentsStatus;
|
||||
export const selectHasPrinterAgents = () => companyAutomat.state.printeragents.length > 0;
|
||||
|
||||
// Agent printers selectors
|
||||
export const selectAgentPrinters = () => companyAutomat.state.agentprinters;
|
||||
export const selectAgentPrinterStatus = () => companyAutomat.state.agentprintersStatus;
|
||||
export const selectLivePrinters = () => companyAutomat.state.livePrinters;
|
||||
|
||||
// Suppliers selectors
|
||||
export const selectSuppliers = () => companyAutomat.state.suppliers;
|
||||
export const selectSuppliersStatus = () => companyAutomat.state.suppliersStatus;
|
||||
|
||||
@@ -154,6 +154,9 @@ export {
|
||||
selectPrinterAgents,
|
||||
selectPrinterAgentStatus,
|
||||
selectHasPrinterAgents,
|
||||
selectAgentPrinters,
|
||||
selectAgentPrinterStatus,
|
||||
selectLivePrinters,
|
||||
selectSuppliers,
|
||||
selectSuppliersStatus,
|
||||
selectUnits,
|
||||
|
||||
@@ -8,6 +8,7 @@ import ButtonBase from '@mui/material/ButtonBase';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import UserMenu from './UserMenu';
|
||||
import OnlinePrinterIcons from './OnlinePrinterIcons';
|
||||
import HelpBadge from './HelpBadge';
|
||||
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
||||
import { artikelQueryPath, buildSearchUrl } from './article/artikelQuery';
|
||||
@@ -198,7 +199,18 @@ class AppSidebar extends PureComponent {
|
||||
})}
|
||||
</List>
|
||||
|
||||
<Box sx={{ ml: { xs: 'auto', lg: 0 }, flexShrink: 0, display: 'flex', alignItems: 'center' }}>
|
||||
<Box
|
||||
sx={{
|
||||
ml: { xs: 'auto', lg: 0 },
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'row', lg: 'column' },
|
||||
alignItems: { xs: 'center', lg: 'flex-start' },
|
||||
width: { xs: 'auto', lg: '100%' },
|
||||
gap: { xs: 0.5, lg: 0 },
|
||||
}}
|
||||
>
|
||||
<OnlinePrinterIcons />
|
||||
<UserMenu />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
290
src/components/OnlinePrinterIcons.jsx
Normal file
290
src/components/OnlinePrinterIcons.jsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import { PureComponent } from 'react';
|
||||
import Badge from '@mui/material/Badge';
|
||||
import Box from '@mui/material/Box';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import companyAutomat from '../automats/companyAutomat';
|
||||
import { notifyError } from '../utils/notify';
|
||||
|
||||
export default class OnlinePrinterIcons extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
agentprinters: Array.isArray(companyAutomat.state.agentprinters)
|
||||
? companyAutomat.state.agentprinters
|
||||
: [],
|
||||
livePrinters: Array.isArray(companyAutomat.state.livePrinters)
|
||||
? companyAutomat.state.livePrinters
|
||||
: [],
|
||||
menuAnchor: null,
|
||||
testingId: null,
|
||||
};
|
||||
this.handleOpenMenu = this.handleOpenMenu.bind(this);
|
||||
this.handleCloseMenu = this.handleCloseMenu.bind(this);
|
||||
this.handleTestPrint = this.handleTestPrint.bind(this);
|
||||
this.handleOpenPrinterSettings = this.handleOpenPrinterSettings.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsub = companyAutomat.subscribe(this, (state) => ({
|
||||
agentprinters: Array.isArray(state.agentprinters) ? state.agentprinters : [],
|
||||
livePrinters: Array.isArray(state.livePrinters) ? state.livePrinters : [],
|
||||
}));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsub?.();
|
||||
}
|
||||
|
||||
getOnlinePrinters() {
|
||||
const { agentprinters = [], livePrinters = [] } = this.state;
|
||||
const liveBySerial = new Map();
|
||||
for (const lp of livePrinters) {
|
||||
if (lp.serial) liveBySerial.set(String(lp.serial).toLowerCase(), lp);
|
||||
}
|
||||
|
||||
if (agentprinters.length > 0) {
|
||||
return agentprinters
|
||||
.filter((p) => {
|
||||
if (p.online === true) return true;
|
||||
const live =
|
||||
(p.serial ? liveBySerial.get(String(p.serial).toLowerCase()) : null) ??
|
||||
livePrinters.find(
|
||||
(lp) =>
|
||||
(lp.agentprinterId && lp.agentprinterId === p.id) ||
|
||||
(lp.agentId === p.printeragentId && lp.serial === p.serial),
|
||||
);
|
||||
if (!live) return false;
|
||||
return live.status !== 'offline' && !live.unreachable && !live.errors?.unreachable;
|
||||
})
|
||||
.map((p) => {
|
||||
const live =
|
||||
(p.serial ? liveBySerial.get(String(p.serial).toLowerCase()) : null) ??
|
||||
livePrinters.find(
|
||||
(lp) =>
|
||||
(lp.agentprinterId && lp.agentprinterId === p.id) ||
|
||||
(lp.agentId === p.printeragentId && lp.serial === p.serial),
|
||||
);
|
||||
return { ...p, live };
|
||||
});
|
||||
}
|
||||
|
||||
return livePrinters
|
||||
.filter((lp) => lp.status !== 'offline' && !lp.unreachable && !lp.errors?.unreachable)
|
||||
.map((lp) => ({
|
||||
id: lp.agentprinterId || lp.id || lp.serial,
|
||||
model: lp.model,
|
||||
serial: lp.serial,
|
||||
type: lp.type,
|
||||
online: true,
|
||||
open: Boolean(lp.errors?.headOpen || lp.headOpen),
|
||||
pause: Boolean(lp.errors?.pause || lp.pause),
|
||||
error: Boolean(lp.errors?.other || lp.error),
|
||||
labelLength: lp.labelLength,
|
||||
live: lp,
|
||||
}));
|
||||
}
|
||||
|
||||
handleOpenMenu(event, printer) {
|
||||
this.setState({
|
||||
menuAnchor: {
|
||||
el: event.currentTarget,
|
||||
printer,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
handleCloseMenu() {
|
||||
this.setState({ menuAnchor: null });
|
||||
}
|
||||
|
||||
async handleTestPrint(printer) {
|
||||
if (!printer?.id) return;
|
||||
this.setState({ testingId: printer.id });
|
||||
try {
|
||||
await companyAutomat.actions.sendTestPrint(printer.id, {
|
||||
model: printer.model || printer.type || 'Zebra',
|
||||
});
|
||||
enqueueSnackbar(
|
||||
`Testdruck für ${printer.model || 'Drucker'} (#${printer.id}) an Warteschlange gesendet.`,
|
||||
{ variant: 'success' },
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
} finally {
|
||||
this.setState({ testingId: null, menuAnchor: null });
|
||||
}
|
||||
}
|
||||
|
||||
handleOpenPrinterSettings() {
|
||||
this.handleCloseMenu();
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('open-settings', { detail: { tab: 'printeragent' } }),
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { menuAnchor, testingId } = this.state;
|
||||
const onlinePrinters = this.getOnlinePrinters();
|
||||
|
||||
if (onlinePrinters.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activePrinter = menuAnchor?.printer;
|
||||
const activeHasError = Boolean(
|
||||
activePrinter?.error ||
|
||||
activePrinter?.open ||
|
||||
activePrinter?.live?.errors?.other ||
|
||||
activePrinter?.live?.errors?.headOpen,
|
||||
);
|
||||
const activeIsPaused = Boolean(activePrinter?.pause || activePrinter?.live?.errors?.pause);
|
||||
const activeStatusText = activeHasError
|
||||
? activePrinter?.open || activePrinter?.live?.errors?.headOpen
|
||||
? 'Druckkopf offen'
|
||||
: 'Fehler'
|
||||
: activeIsPaused
|
||||
? 'Pausiert'
|
||||
: 'Bereit';
|
||||
|
||||
const anchorEl = menuAnchor?.el;
|
||||
const isTop = Boolean(
|
||||
anchorEl && typeof anchorEl.getBoundingClientRect === 'function' && anchorEl.getBoundingClientRect().top < 200
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'row', lg: 'column' },
|
||||
alignItems: { xs: 'center', lg: 'flex-start' },
|
||||
alignSelf: { xs: 'center', lg: 'flex-start' },
|
||||
px: { xs: 0.5, sm: 1, lg: 1.5 },
|
||||
py: { xs: 0.5, lg: 0.5 },
|
||||
gap: { xs: 0.5, lg: 1 },
|
||||
}}
|
||||
>
|
||||
{onlinePrinters.map((p) => {
|
||||
const hasError = Boolean(
|
||||
p.error || p.open || p.live?.errors?.other || p.live?.errors?.headOpen,
|
||||
);
|
||||
const isPaused = Boolean(p.pause || p.live?.errors?.pause);
|
||||
const label = p.model || 'Drucker';
|
||||
const statusText = hasError
|
||||
? p.open || p.live?.errors?.headOpen
|
||||
? 'Druckkopf offen'
|
||||
: 'Fehler'
|
||||
: isPaused
|
||||
? 'Pausiert'
|
||||
: 'Bereit';
|
||||
const tooltipTitle = `${label}${p.serial ? ` (${p.serial})` : ''} • ${statusText}`;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
onClick={(e) => this.handleOpenMenu(e, p)}
|
||||
sx={(theme) => ({
|
||||
p: 0,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: '50%',
|
||||
color: theme.nav?.item || 'inherit',
|
||||
'&:hover': {
|
||||
color: theme.nav?.itemHoverText || 'inherit',
|
||||
bgcolor: theme.nav?.itemHover || 'action.hover',
|
||||
},
|
||||
})}
|
||||
aria-label={`Drucker ${label}`}
|
||||
>
|
||||
<Badge
|
||||
variant="dot"
|
||||
color={hasError ? 'error' : isPaused ? 'warning' : 'success'}
|
||||
overlap="circular"
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
>
|
||||
<PrintOutlinedIcon sx={{ fontSize: 20 }} />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(menuAnchor)}
|
||||
onClose={this.handleCloseMenu}
|
||||
anchorOrigin={{
|
||||
vertical: isTop ? 'bottom' : 'top',
|
||||
horizontal: isTop ? 'right' : 'left',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: isTop ? 'top' : 'bottom',
|
||||
horizontal: isTop ? 'right' : 'left',
|
||||
}}
|
||||
>
|
||||
{activePrinter && (
|
||||
<Box sx={{ px: 2, py: 1, minWidth: 210 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||
<Typography variant="subtitle2" fontWeight={600} noWrap>
|
||||
{activePrinter.model || 'Drucker'}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={activeStatusText}
|
||||
size="small"
|
||||
color={activeHasError ? 'error' : activeIsPaused ? 'warning' : 'success'}
|
||||
variant="outlined"
|
||||
sx={{ fontSize: '0.65rem', height: 18, fontWeight: 600 }}
|
||||
/>
|
||||
</Box>
|
||||
{activePrinter.serial ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ fontFamily: 'monospace', display: 'block' }}
|
||||
>
|
||||
S/N: {activePrinter.serial}
|
||||
</Typography>
|
||||
) : null}
|
||||
{activePrinter.labelLength ? (
|
||||
<Typography variant="caption" color="text.secondary" display="block">
|
||||
Etikett: {activePrinter.labelLength} px
|
||||
</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<MenuItem
|
||||
onClick={() => void this.handleTestPrint(activePrinter)}
|
||||
disabled={testingId === activePrinter?.id}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<PrintOutlinedIcon fontSize="small" color="primary" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={testingId === activePrinter?.id ? 'Sendet Testdruck…' : 'Testdruck senden'}
|
||||
secondary="Minimales ZPL-Etikett"
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={this.handleOpenPrinterSettings}>
|
||||
<ListItemIcon>
|
||||
<SettingsIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Drucker-Einstellungen" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class UserMenu extends PureComponent {
|
||||
this.state = {
|
||||
anchorEl: null,
|
||||
settingsOpen: false,
|
||||
settingsInitialTab: null,
|
||||
user: authAutomat.state.user,
|
||||
helpEnabled: helpAutomat.state?.enabled ?? false,
|
||||
pwaUpdateAvailable: pwaAutomat.state.needRefresh,
|
||||
@@ -37,6 +38,7 @@ class UserMenu extends PureComponent {
|
||||
this.handleToggleHelp = this.handleToggleHelp.bind(this);
|
||||
this.handleOpenPwaUpdate = this.handleOpenPwaUpdate.bind(this);
|
||||
this.handleLogout = this.handleLogout.bind(this);
|
||||
this.handleOpenSettingsEvent = this.handleOpenSettingsEvent.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -47,12 +49,23 @@ class UserMenu extends PureComponent {
|
||||
this.unsubscribePwa = pwaAutomat.subscribe(this, (state) => ({
|
||||
pwaUpdateAvailable: Boolean(state?.needRefresh),
|
||||
}));
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('open-settings', this.handleOpenSettingsEvent);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeAuth?.();
|
||||
this.unsubscribeHelp?.();
|
||||
this.unsubscribePwa?.();
|
||||
if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('open-settings', this.handleOpenSettingsEvent);
|
||||
}
|
||||
}
|
||||
|
||||
handleOpenSettingsEvent(e) {
|
||||
const tab = e?.detail?.tab;
|
||||
this.setState({ settingsOpen: true, settingsInitialTab: tab || null });
|
||||
}
|
||||
|
||||
handleOpenPwaUpdate() {
|
||||
@@ -74,11 +87,11 @@ class UserMenu extends PureComponent {
|
||||
}
|
||||
|
||||
handleOpenSettings() {
|
||||
this.setState({ anchorEl: null, settingsOpen: true });
|
||||
this.setState({ anchorEl: null, settingsOpen: true, settingsInitialTab: null });
|
||||
}
|
||||
|
||||
handleCloseSettings() {
|
||||
this.setState({ settingsOpen: false });
|
||||
this.setState({ settingsOpen: false, settingsInitialTab: null });
|
||||
}
|
||||
|
||||
handleToggleHelp(e) {
|
||||
@@ -98,7 +111,7 @@ class UserMenu extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, anchorEl, settingsOpen, helpEnabled, pwaUpdateAvailable } = this.state;
|
||||
const { user, anchorEl, settingsOpen, settingsInitialTab, helpEnabled, pwaUpdateAvailable } = this.state;
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const pictureSrc = (() => {
|
||||
@@ -201,7 +214,7 @@ class UserMenu extends PureComponent {
|
||||
<ListItemText>Sign out</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<SettingsDialog open={settingsOpen} onClose={this.handleCloseSettings} />
|
||||
<SettingsDialog open={settingsOpen} initialTab={settingsInitialTab} onClose={this.handleCloseSettings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
@@ -11,13 +13,15 @@ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import LocalPrintshopOutlinedIcon from '@mui/icons-material/LocalPrintshopOutlined';
|
||||
import PrintOutlinedIcon from '@mui/icons-material/PrintOutlined';
|
||||
import CheckCircleOutlinedIcon from '@mui/icons-material/CheckCircleOutlined';
|
||||
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
|
||||
import QRCode from 'qrcode';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import QRCode from 'qrcode';
|
||||
import BackendError from '../BackendError';
|
||||
import companyAutomat from '../../automats/companyAutomat';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
@@ -114,6 +118,97 @@ class AgentQrCode extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
function renderPrinterDiagnostics(printer, live) {
|
||||
const isOnline = printer?.online ?? (live ? !live.unreachable && live.status !== 'offline' : false);
|
||||
const isOpen = printer?.open ?? (live?.errors?.headOpen || live?.headOpen || false);
|
||||
const isPaused = printer?.pause ?? (live?.errors?.pause || live?.pause || false);
|
||||
const isError = printer?.error ?? (live?.errors?.other || live?.error || false);
|
||||
const isUnreachable = !isOnline || (live?.errors?.unreachable || false);
|
||||
const labelLength = printer?.labelLength ?? live?.labelLength;
|
||||
|
||||
if (!isOnline) {
|
||||
return (
|
||||
<Chip
|
||||
label="Offline"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="default"
|
||||
sx={{ fontSize: '0.7rem', height: 20 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const errorChips = [];
|
||||
if (isOpen) {
|
||||
errorChips.push(
|
||||
<Chip
|
||||
key="headOpen"
|
||||
label="Druckkopf offen"
|
||||
size="small"
|
||||
color="error"
|
||||
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
if (isPaused) {
|
||||
errorChips.push(
|
||||
<Chip
|
||||
key="pause"
|
||||
label="Pausiert"
|
||||
size="small"
|
||||
color="warning"
|
||||
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
if (isUnreachable) {
|
||||
errorChips.push(
|
||||
<Chip
|
||||
key="unreachable"
|
||||
label="Nicht erreichbar"
|
||||
size="small"
|
||||
color="error"
|
||||
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
if (isError) {
|
||||
errorChips.push(
|
||||
<Chip
|
||||
key="other"
|
||||
label="Fehler"
|
||||
size="small"
|
||||
color="error"
|
||||
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (errorChips.length > 0) {
|
||||
return <Box sx={{ display: 'inline-flex', gap: 0.5, flexWrap: 'wrap' }}>{errorChips}</Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'inline-flex', gap: 0.5, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<Chip
|
||||
label="Bereit"
|
||||
size="small"
|
||||
color="success"
|
||||
variant="outlined"
|
||||
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
|
||||
/>
|
||||
{labelLength != null ? (
|
||||
<Chip
|
||||
label={`Etikett: ${labelLength} px`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ fontSize: '0.7rem', height: 20 }}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -121,6 +216,12 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
printeragents: Array.isArray(companyAutomat.state.printeragents)
|
||||
? companyAutomat.state.printeragents
|
||||
: [],
|
||||
agentprinters: Array.isArray(companyAutomat.state.agentprinters)
|
||||
? companyAutomat.state.agentprinters
|
||||
: [],
|
||||
livePrinters: Array.isArray(companyAutomat.state.livePrinters)
|
||||
? companyAutomat.state.livePrinters
|
||||
: [],
|
||||
error: companyAutomat.state.printeragentsError
|
||||
? backendErrorFrom(companyAutomat.state.printeragentsError)
|
||||
: null,
|
||||
@@ -131,26 +232,38 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
editingId: null,
|
||||
editNameDraft: '',
|
||||
sortDrafts: {},
|
||||
testingPrinterId: null,
|
||||
};
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
this.handleCreateAgent = this.handleCreateAgent.bind(this);
|
||||
this.handleDeleteAgent = this.handleDeleteAgent.bind(this);
|
||||
this.handleToggleAgentEnabled = this.handleToggleAgentEnabled.bind(this);
|
||||
this.handleStartRename = this.handleStartRename.bind(this);
|
||||
this.handleCancelRename = this.handleCancelRename.bind(this);
|
||||
this.handleSaveRename = this.handleSaveRename.bind(this);
|
||||
this.handleSortDraftChange = this.handleSortDraftChange.bind(this);
|
||||
this.handleSortSubmit = this.handleSortSubmit.bind(this);
|
||||
this.handleMoveAgent = this.handleMoveAgent.bind(this);
|
||||
this.handleTogglePrinterEnabled = this.handleTogglePrinterEnabled.bind(this);
|
||||
this.handleMovePrinter = this.handleMovePrinter.bind(this);
|
||||
this.handleDeletePrinter = this.handleDeletePrinter.bind(this);
|
||||
this.handleTestPrint = this.handleTestPrint.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsub = companyAutomat.subscribe(this, (state) => ({
|
||||
printeragents: Array.isArray(state.printeragents) ? state.printeragents : [],
|
||||
agentprinters: Array.isArray(state.agentprinters) ? state.agentprinters : [],
|
||||
livePrinters: Array.isArray(state.livePrinters) ? state.livePrinters : [],
|
||||
error: state.printeragentsError ? backendErrorFrom(state.printeragentsError) : null,
|
||||
}));
|
||||
if (companyAutomat.state.printeragentsStatus === 'idle') {
|
||||
void companyAutomat.actions.loadPrinterAgents().catch(() => { });
|
||||
}
|
||||
if (companyAutomat.state.agentprintersStatus === 'idle') {
|
||||
void companyAutomat.actions.loadAgentPrinters().catch(() => { });
|
||||
}
|
||||
void companyAutomat.actions.loadLivePrinters().catch(() => { });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -159,6 +272,8 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
|
||||
handleRetry() {
|
||||
void companyAutomat.actions.loadPrinterAgents().catch(notifyError);
|
||||
void companyAutomat.actions.loadAgentPrinters().catch(notifyError);
|
||||
void companyAutomat.actions.loadLivePrinters().catch(() => { });
|
||||
}
|
||||
|
||||
async handleCreateAgent() {
|
||||
@@ -200,6 +315,23 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async handleToggleAgentEnabled(agent, nextValue) {
|
||||
this.setState({ patchingAgentId: agent.id });
|
||||
try {
|
||||
await companyAutomat.actions.updatePrinterAgent(agent.id, {
|
||||
enabled: Boolean(nextValue),
|
||||
});
|
||||
enqueueSnackbar(
|
||||
nextValue ? 'Drucker-Agent aktiviert.' : 'Drucker-Agent deaktiviert.',
|
||||
{ variant: 'success' },
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
} finally {
|
||||
this.setState({ patchingAgentId: null });
|
||||
}
|
||||
}
|
||||
|
||||
handleStartRename(agent) {
|
||||
this.setState({ editingId: agent.id, editNameDraft: agent.name || '' });
|
||||
}
|
||||
@@ -291,16 +423,89 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async handleTogglePrinterEnabled(printer, nextValue) {
|
||||
try {
|
||||
await companyAutomat.actions.updateAgentPrinter(printer.id, {
|
||||
enabled: Boolean(nextValue),
|
||||
});
|
||||
enqueueSnackbar(
|
||||
nextValue ? 'Drucker aktiviert.' : 'Drucker deaktiviert.',
|
||||
{ variant: 'success' },
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async handleMovePrinter(printer, direction) {
|
||||
const { agentprinters } = this.state;
|
||||
const currentIndex = agentprinters.findIndex((p) => p.id === printer.id);
|
||||
if (currentIndex === -1) return;
|
||||
const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
||||
if (targetIndex < 0 || targetIndex >= agentprinters.length) return;
|
||||
|
||||
const reordered = [...agentprinters];
|
||||
const [moved] = reordered.splice(currentIndex, 1);
|
||||
reordered.splice(targetIndex, 0, moved);
|
||||
|
||||
try {
|
||||
await companyAutomat.actions.reorderAgentPrinters({
|
||||
agentprinterIds: reordered.map((p) => p.id),
|
||||
});
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async handleDeletePrinter(printer) {
|
||||
const isOnline = Boolean(printer?.online);
|
||||
if (isOnline) {
|
||||
enqueueSnackbar('Online-Drucker können nicht entfernt werden.', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
const label = printer.model || printer.serial || `#${printer.id}`;
|
||||
if (!window.confirm(`Möchten Sie den Drucker „${label}“ wirklich entfernen?`)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await companyAutomat.actions.deleteAgentPrinter(printer.id);
|
||||
enqueueSnackbar('Drucker entfernt.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async handleTestPrint(printer) {
|
||||
if (!printer?.id) return;
|
||||
this.setState({ testingPrinterId: printer.id });
|
||||
try {
|
||||
await companyAutomat.actions.sendTestPrint(printer.id, {
|
||||
model: printer.model || printer.type || 'Zebra',
|
||||
});
|
||||
enqueueSnackbar(
|
||||
`Testdruck für ${printer.model || 'Drucker'} (#${printer.id}) an Drucker-Warteschlange gesendet.`,
|
||||
{ variant: 'success' },
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
} finally {
|
||||
this.setState({ testingPrinterId: null });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
error,
|
||||
printeragents,
|
||||
agentprinters,
|
||||
livePrinters,
|
||||
newAgentName,
|
||||
creatingAgent,
|
||||
deletingId,
|
||||
patchingAgentId,
|
||||
editingId,
|
||||
editNameDraft,
|
||||
testingPrinterId,
|
||||
} = this.state;
|
||||
|
||||
if (error && printeragents.length === 0) {
|
||||
@@ -311,23 +516,26 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
// Build lookup maps for fast access
|
||||
const livePrintersBySerial = new Map();
|
||||
for (const lp of livePrinters) {
|
||||
if (lp.serial) livePrintersBySerial.set(lp.serial, lp);
|
||||
}
|
||||
const agentMap = new Map(printeragents.map((a) => [a.id, a]));
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, maxWidth: 900, mx: 'auto' }}>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="h6" fontWeight={600} gutterBottom>
|
||||
Drucker-Agenten
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Verwalten Sie externe Drucker-Agenten für Druckaufträge. Nach Erstellung eines Agenten
|
||||
geben Sie die 6-stellige PIN auf dem Agenten-Gerät ein, um die Kopplung abzuschließen.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Add new agent row */}
|
||||
<Box sx={{ display: 'flex', gap: 1.5, mb: 3, alignItems: 'center' }}>
|
||||
<TextField
|
||||
size="small"
|
||||
placeholder="Neuer Drucker-Agent Name (z.B. Bar Receipt Printer)"
|
||||
placeholder="Agent Name (z.B. Bar, Küche)"
|
||||
value={newAgentName}
|
||||
onChange={(e) => this.setState({ newAgentName: e.target.value })}
|
||||
onKeyDown={(e) => {
|
||||
@@ -346,7 +554,9 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
spellCheck: false,
|
||||
},
|
||||
input: {
|
||||
startAdornment: <LocalPrintshopOutlinedIcon sx={{ mr: 1, color: 'text.secondary' }} />,
|
||||
startAdornment: (
|
||||
<LocalPrintshopOutlinedIcon sx={{ mr: 1, color: 'text.secondary' }} />
|
||||
),
|
||||
},
|
||||
}}
|
||||
sx={{ flex: 1 }}
|
||||
@@ -370,12 +580,13 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
<Typography variant="body2">Keine Drucker-Agenten angelegt.</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
|
||||
{printeragents.map((agent, index) => {
|
||||
const isDeleting = deletingId === agent.id;
|
||||
const isPatching = patchingAgentId === agent.id;
|
||||
const isEditing = editingId === agent.id;
|
||||
const isPaired = agent.pairedAt != null;
|
||||
const isEnabled = agent.enabled !== false;
|
||||
const pairedDateText = formatDateTime(agent.pairedAt);
|
||||
const lastseenText = formatDateTime(agent.lastseenAt) ?? 'Nie';
|
||||
const updatedText = formatDateTime(agent.updatedAt) ?? '—';
|
||||
@@ -387,15 +598,20 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
border: '1px solid',
|
||||
borderColor: isPaired ? 'divider' : 'warning.light',
|
||||
borderColor: !isEnabled
|
||||
? 'action.disabledBackground'
|
||||
: isPaired
|
||||
? 'divider'
|
||||
: 'warning.light',
|
||||
bgcolor: 'background.paper',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1.5,
|
||||
transition: 'border-color 0.2s ease',
|
||||
opacity: isEnabled ? 1 : 0.72,
|
||||
transition: 'opacity 0.2s ease, border-color 0.2s ease',
|
||||
}}
|
||||
>
|
||||
{/* Header: Icon, Name (or Edit input), Status chip, Actions */}
|
||||
{/* Header: Icon, Name (or Edit input), Status chips, Enabled switch, Delete */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -407,7 +623,14 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flex: 1, minWidth: 260 }}>
|
||||
<LocalPrintshopOutlinedIcon
|
||||
sx={{ color: isPaired ? 'primary.main' : 'warning.main', fontSize: 24 }}
|
||||
sx={{
|
||||
color: !isEnabled
|
||||
? 'text.disabled'
|
||||
: isPaired
|
||||
? 'primary.main'
|
||||
: 'warning.main',
|
||||
fontSize: 24,
|
||||
}}
|
||||
/>
|
||||
|
||||
{isEditing ? (
|
||||
@@ -455,7 +678,11 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="subtitle1" fontWeight={600}>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
fontWeight={600}
|
||||
sx={{ color: isEnabled ? 'text.primary' : 'text.disabled' }}
|
||||
>
|
||||
{agent.name}
|
||||
</Typography>
|
||||
<IconButton
|
||||
@@ -469,6 +696,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Pairing status badge */}
|
||||
{isPaired ? (
|
||||
<Chip
|
||||
icon={<CheckCircleOutlinedIcon />}
|
||||
@@ -488,10 +716,46 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
sx={{ fontWeight: 600, fontSize: '0.75rem' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Agent version */}
|
||||
{agent.agentversion ? (
|
||||
<Chip
|
||||
label={`v${agent.agentversion}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="info"
|
||||
sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: '0.75rem' }}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
|
||||
{/* Delete control */}
|
||||
{/* Enabled Switch and Delete action */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
checked={isEnabled}
|
||||
disabled={isPatching || isDeleting}
|
||||
onChange={(e) => void this.handleToggleAgentEnabled(agent, e.target.checked)}
|
||||
color="success"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: 500,
|
||||
color: isEnabled ? 'text.primary' : 'text.disabled',
|
||||
}}
|
||||
>
|
||||
{isEnabled ? 'Aktiv' : 'Inaktiv'}
|
||||
</Typography>
|
||||
}
|
||||
sx={{ m: 0 }}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={`Drucker-Agent ${agent.name} löschen`}
|
||||
@@ -504,7 +768,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Body: Pairing details with QR code or Paired status info */}
|
||||
{/* Body: Pairing details with QR code or Discovered Printers */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -544,7 +808,15 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
Kopplungs-PIN:
|
||||
</Typography>
|
||||
<Chip
|
||||
icon={<ContentCopyIcon sx={{ fontSize: 14 }} />}
|
||||
label={agent.pairingpin || '—'}
|
||||
onClick={() => {
|
||||
if (agent.pairingpin) {
|
||||
navigator.clipboard?.writeText(agent.pairingpin);
|
||||
enqueueSnackbar('PIN in Zwischenablage kopiert.', { variant: 'info' });
|
||||
}
|
||||
}}
|
||||
clickable
|
||||
color="warning"
|
||||
variant="filled"
|
||||
sx={{
|
||||
@@ -567,51 +839,37 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
variant="outlined"
|
||||
sx={{ fontFamily: 'monospace', fontSize: '0.8rem' }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
navigator.clipboard?.writeText(qrPayload);
|
||||
enqueueSnackbar('QR-Daten kopiert.', { variant: 'info' });
|
||||
}}
|
||||
sx={{ p: 0.5 }}
|
||||
>
|
||||
<ContentCopyIcon sx={{ fontSize: 14 }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Scannen Sie den QR-Code mit dem Drucker-Agenten oder geben Sie Domain und PIN manuell ein.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})()
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}>
|
||||
{agent.clientserial ? (
|
||||
<Chip
|
||||
label={`S/N: ${agent.clientserial}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ fontFamily: 'monospace', fontSize: '0.75rem' }}
|
||||
/>
|
||||
) : null}
|
||||
{agent.clientmodel ? (
|
||||
<Chip
|
||||
label={`Modell: ${agent.clientmodel}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ fontSize: '0.75rem' }}
|
||||
/>
|
||||
) : null}
|
||||
{agent.agentversion ? (
|
||||
<Chip
|
||||
label={`v${agent.agentversion}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="info"
|
||||
sx={{ fontFamily: 'monospace', fontWeight: 600, fontSize: '0.75rem' }}
|
||||
/>
|
||||
) : null}
|
||||
{agent.clientstate ? (
|
||||
<Chip
|
||||
label={`Status: ${agent.clientstate}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ fontSize: '0.75rem' }}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{/* Row with Sort Order Controls */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 0.5, pt: 0.5 }}>
|
||||
{/* Agent sort order controls */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 0.5,
|
||||
pt: 0.5,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Reihenfolge:
|
||||
</Typography>
|
||||
@@ -625,7 +883,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
@@ -688,6 +945,187 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Separate Printers Section */}
|
||||
<Divider sx={{ my: 4 }} />
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="h6" fontWeight={600} gutterBottom>
|
||||
Drucker
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{agentprinters.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
py: 5,
|
||||
color: 'text.secondary',
|
||||
bgcolor: 'action.hover',
|
||||
borderRadius: 2,
|
||||
border: '1px dashed',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<PrintOutlinedIcon sx={{ fontSize: 44, mb: 1, opacity: 0.5 }} />
|
||||
<Typography variant="body2">
|
||||
Keine Drucker erkannt. Schließen Sie Drucker per USB an einen gekoppelten Drucker-Agenten an.
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||
{agentprinters.map((p, pIdx) => {
|
||||
const pEnabled = p.enabled !== false;
|
||||
const live =
|
||||
(p.serial ? livePrintersBySerial.get(p.serial) : null) ??
|
||||
livePrinters.find(
|
||||
(lp) => lp.agentId === p.printeragentId && lp.serial === p.serial,
|
||||
);
|
||||
const isOnline = Boolean(
|
||||
p.online ?? (live ? !live.unreachable && live.status !== 'offline' : false),
|
||||
);
|
||||
const parentAgent = agentMap.get(p.printeragentId);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={p.id}
|
||||
sx={{
|
||||
p: 1.5,
|
||||
borderRadius: 1.5,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
bgcolor: 'background.paper',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 1.5,
|
||||
flexWrap: 'wrap',
|
||||
opacity: pEnabled ? 1 : 0.65,
|
||||
transition: 'opacity 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
|
||||
<PrintOutlinedIcon sx={{ fontSize: 22, color: 'text.secondary' }} />
|
||||
<Typography variant="body2" fontWeight={600}>
|
||||
{p.model || 'Drucker'}
|
||||
</Typography>
|
||||
{p.type ? (
|
||||
<Chip
|
||||
label={p.type.toUpperCase()}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
sx={{ fontWeight: 600, fontSize: '0.7rem', height: 20 }}
|
||||
/>
|
||||
) : null}
|
||||
{p.serial ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ fontFamily: 'monospace' }}
|
||||
>
|
||||
({p.serial})
|
||||
</Typography>
|
||||
) : null}
|
||||
{parentAgent ? (
|
||||
<Chip
|
||||
label={`Agent: ${parentAgent.name}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ fontSize: '0.7rem', height: 20 }}
|
||||
/>
|
||||
) : null}
|
||||
{renderPrinterDiagnostics(p, live)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{/* Test print button */}
|
||||
<span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<PrintOutlinedIcon sx={{ fontSize: 15 }} />}
|
||||
disabled={testingPrinterId === p.id}
|
||||
onClick={() => void this.handleTestPrint(p)}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
fontSize: '0.75rem',
|
||||
py: 0.25,
|
||||
px: 1,
|
||||
minHeight: 26,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{testingPrinterId === p.id ? 'Druckt…' : 'Testdruck'}
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
{/* Enabled toggle switch */}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
checked={pEnabled}
|
||||
onChange={(e) =>
|
||||
void this.handleTogglePrinterEnabled(p, e.target.checked)
|
||||
}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography
|
||||
variant="caption"
|
||||
color={pEnabled ? 'text.primary' : 'text.disabled'}
|
||||
>
|
||||
{pEnabled ? 'Aktiv' : 'Inaktiv'}
|
||||
</Typography>
|
||||
}
|
||||
sx={{ m: 0 }}
|
||||
/>
|
||||
|
||||
{/* Reorder buttons across all printers */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={pIdx === 0}
|
||||
onClick={() => void this.handleMovePrinter(p, 'up')}
|
||||
sx={{ p: 0.25 }}
|
||||
>
|
||||
<ArrowUpwardIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={pIdx === agentprinters.length - 1}
|
||||
onClick={() => void this.handleMovePrinter(p, 'down')}
|
||||
sx={{ p: 0.25 }}
|
||||
>
|
||||
<ArrowDownwardIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
{/* Delete printer button: disabled when online! */}
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
disabled={isOnline}
|
||||
onClick={() => void this.handleDeletePrinter(p)}
|
||||
sx={{ p: 0.5 }}
|
||||
>
|
||||
<DeleteOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,9 @@ export default class SettingsDialog extends Component {
|
||||
if (open && !prevProps.open) {
|
||||
const currentVhost = Boolean(authAutomat.state.vhost);
|
||||
const updates = {};
|
||||
if (this.props.initialTab) {
|
||||
updates.tab = this.props.initialTab;
|
||||
}
|
||||
if (this.state.schemaVersion !== authAutomat.state.schemaVersion) {
|
||||
updates.schemaVersion = authAutomat.state.schemaVersion;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import CircularProgress from '@mui/material/CircularProgress';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
@@ -147,8 +146,8 @@ class GlobalHistoryRow extends PureComponent {
|
||||
onClick={
|
||||
isArticle
|
||||
? () => {
|
||||
void router.navigate(`/artikel/${encodeURIComponent(refStr)}`);
|
||||
}
|
||||
void router.navigate(`/artikel/${encodeURIComponent(refStr)}`);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
@@ -207,11 +206,9 @@ class GlobalHistoryRow extends PureComponent {
|
||||
|
||||
{/* 4. Meldung / Beschreibung */}
|
||||
<Box sx={{ gridColumn: 4, gridRow: 2, minWidth: 0 }}>
|
||||
<Tooltip title={item.text || item.value || ''} placement="top-start" enterDelay={500}>
|
||||
<Typography variant="body2" sx={ellipsisSx}>
|
||||
{item.text || item.value || '–'}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Typography variant="body2" sx={ellipsisSx}>
|
||||
{item.text || item.value || '–'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
);
|
||||
@@ -445,11 +442,11 @@ export default class GlobalHistoryPage extends PureComponent {
|
||||
}
|
||||
|
||||
handleReload() {
|
||||
void globalHistoryAutomat.actions.reload().catch(() => {});
|
||||
void globalHistoryAutomat.actions.reload().catch(() => { });
|
||||
}
|
||||
|
||||
handleRetry() {
|
||||
void globalHistoryAutomat.actions.requireChunk(0, 0, { immediate: true }).catch(() => {});
|
||||
void globalHistoryAutomat.actions.requireChunk(0, 0, { immediate: true }).catch(() => { });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
Reference in New Issue
Block a user