u
This commit is contained in:
@@ -43,6 +43,10 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
|||||||
| [`routes/article/delete-image.md`](routes/article/delete-image.md) | `DELETE /article/{sku}/image/{fullHash}` |
|
| [`routes/article/delete-image.md`](routes/article/delete-image.md) | `DELETE /article/{sku}/image/{fullHash}` |
|
||||||
| [`routes/article/order-images.md`](routes/article/order-images.md) | `PUT /article/{sku}/images/order` |
|
| [`routes/article/order-images.md`](routes/article/order-images.md) | `PUT /article/{sku}/images/order` |
|
||||||
| [`routes/article/set-prices.md`](routes/article/set-prices.md) | `PUT /article/{sku}/prices` |
|
| [`routes/article/set-prices.md`](routes/article/set-prices.md) | `PUT /article/{sku}/prices` |
|
||||||
|
| [`routes/article/supplier/list.md`](routes/article/supplier/list.md) | `GET /article/{sku}/supplier` |
|
||||||
|
| [`routes/article/supplier/create.md`](routes/article/supplier/create.md) | `POST /article/{sku}/supplier` |
|
||||||
|
| [`routes/article/supplier/delete.md`](routes/article/supplier/delete.md) | `DELETE /article/{sku}/supplier/{id}` |
|
||||||
|
| [`routes/article/supplier/update.md`](routes/article/supplier/update.md) | `PATCH /article/{sku}/supplier/{id}` |
|
||||||
| [`routes/article/set-weight.md`](routes/article/set-weight.md) | `PUT /article/{sku}/weight` |
|
| [`routes/article/set-weight.md`](routes/article/set-weight.md) | `PUT /article/{sku}/weight` |
|
||||||
| [`routes/articleIndex.md`](routes/articleIndex.md) | `GET /articleIndex` |
|
| [`routes/articleIndex.md`](routes/articleIndex.md) | `GET /articleIndex` |
|
||||||
| [`routes/auth/login.md`](routes/auth/login.md) | `POST /auth/login` |
|
| [`routes/auth/login.md`](routes/auth/login.md) | `POST /auth/login` |
|
||||||
@@ -132,7 +136,7 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
|
|||||||
|
|
||||||
| Signal | Emitted by |
|
| Signal | Emitted by |
|
||||||
|--------|------------|
|
|--------|------------|
|
||||||
| `article-<sku>` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/weight` |
|
| `article-<sku>` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `POST /article/{sku}/supplier`, `DELETE /article/{sku}/supplier/{id}`, `PATCH /article/{sku}/supplier/{id}`, `PUT /article/{sku}/weight` |
|
||||||
| `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}` |
|
| `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}` |
|
||||||
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
|
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
|
||||||
|
|
||||||
|
|||||||
@@ -762,7 +762,7 @@
|
|||||||
"Quote"
|
"Quote"
|
||||||
],
|
],
|
||||||
"summary": "Create quote",
|
"summary": "Create quote",
|
||||||
"description": "Creates a quote with header and lines in one transaction.\n`date` is the document date (valid-until).\n`entityId` must be a company customer (not Lieferanten/POS), or null.\nEach line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.\n",
|
"description": "Creates a quote with header and lines in one transaction.\n`date` is the document date (valid-until).\n`entityId` must be a company customer (not Lieferanten/POS), or null.\nEach line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.\n",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"cookieAuth": []
|
"cookieAuth": []
|
||||||
@@ -812,7 +812,7 @@
|
|||||||
"pu": 1,
|
"pu": 1,
|
||||||
"amount": 10,
|
"amount": 10,
|
||||||
"net": 99.5,
|
"net": 99.5,
|
||||||
"tax": 19
|
"taxrate": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2014,7 +2014,7 @@
|
|||||||
"Order"
|
"Order"
|
||||||
],
|
],
|
||||||
"summary": "Create order",
|
"summary": "Create order",
|
||||||
"description": "Creates a sales order with header and lines in one transaction.\n`date` is the document date (expected delivery).\n`entityId` must be a company customer (not Lieferanten/POS), or null.\nEach line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.\n",
|
"description": "Creates a sales order with header and lines in one transaction.\n`date` is the document date (expected delivery).\n`entityId` must be a company customer (not Lieferanten/POS), or null.\nEach line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.\n",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"cookieAuth": []
|
"cookieAuth": []
|
||||||
@@ -2064,7 +2064,7 @@
|
|||||||
"pu": 1,
|
"pu": 1,
|
||||||
"amount": 10,
|
"amount": 10,
|
||||||
"net": 99.5,
|
"net": 99.5,
|
||||||
"tax": 19
|
"taxrate": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -4227,7 +4227,7 @@
|
|||||||
"Delivery"
|
"Delivery"
|
||||||
],
|
],
|
||||||
"summary": "Create delivery",
|
"summary": "Create delivery",
|
||||||
"description": "Creates a goods receipt with header and lines in one transaction.\nThe manifest `number` is allocated automatically via\n`nextNumber('delivery')`; the user-supplied reference is stored as\n`externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and\n`tax` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.\n",
|
"description": "Creates a goods receipt with header and lines in one transaction.\nThe manifest `number` is allocated automatically via\n`nextNumber('delivery')`; the user-supplied reference is stored as\n`externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and\n`taxrate` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.\n",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"cookieAuth": []
|
"cookieAuth": []
|
||||||
@@ -4276,7 +4276,7 @@
|
|||||||
"pu": 1,
|
"pu": 1,
|
||||||
"amount": 10,
|
"amount": 10,
|
||||||
"net": 99.5,
|
"net": 99.5,
|
||||||
"tax": 19
|
"taxrate": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -6735,6 +6735,460 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/article/{sku}/supplier": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Article"
|
||||||
|
],
|
||||||
|
"summary": "List article suppliers",
|
||||||
|
"description": "Fetches all suppliers linked to the specified article SKU.",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"cookieAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-markdown": "article/supplier/list.md",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "sku",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Article SKU"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "List of suppliers linked to the article",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"suppliers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ArticleSupplier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "No company selected or invalid sku",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Not authorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Article or company not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Article"
|
||||||
|
],
|
||||||
|
"summary": "Assign supplier to article",
|
||||||
|
"description": "Assigns a supplier to an article with optional purchase price, stock, and supplier SKU.\n",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"cookieAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-signals": [
|
||||||
|
"article-<sku>"
|
||||||
|
],
|
||||||
|
"x-markdown": "article/supplier/create.md",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "sku",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Article SKU"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"entityId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"entityId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Supplier entity ID (in Lieferanten group)"
|
||||||
|
},
|
||||||
|
"sku": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Supplier's item number"
|
||||||
|
},
|
||||||
|
"pu": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 1,
|
||||||
|
"description": "Packaging unit (VPE)"
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Supplier stock"
|
||||||
|
},
|
||||||
|
"netPrice": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Net purchase price"
|
||||||
|
},
|
||||||
|
"taxId": {
|
||||||
|
"type": "integer",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Tax ID for purchase price"
|
||||||
|
},
|
||||||
|
"taxRate": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Tax rate in percent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"entityId": 88,
|
||||||
|
"sku": "MET-44910",
|
||||||
|
"pu": 1,
|
||||||
|
"stock": 140,
|
||||||
|
"netPrice": 4.2,
|
||||||
|
"taxId": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created supplier assignment",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"supplier": {
|
||||||
|
"$ref": "#/components/schemas/ArticleSupplier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "entityId required, invalid pu, invalid stock, invalid price, or no company selected",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Not authorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Article, supplier, or company not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "Supplier already assigned",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/article/{sku}/supplier/{id}": {
|
||||||
|
"patch": {
|
||||||
|
"tags": [
|
||||||
|
"Article"
|
||||||
|
],
|
||||||
|
"summary": "Update article supplier assignment",
|
||||||
|
"description": "Updates an existing article-supplier association (supplier SKU, packaging unit, stock, purchase price).\n",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"cookieAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-signals": [
|
||||||
|
"article-<sku>"
|
||||||
|
],
|
||||||
|
"x-markdown": "article/supplier/update.md",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "sku",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Article SKU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"description": "articlesupplier assignment ID"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"sku": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"pu": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"netPrice": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"taxId": {
|
||||||
|
"type": "integer",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"taxRate": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"sku": "MET-44910-B",
|
||||||
|
"pu": 6,
|
||||||
|
"stock": 95,
|
||||||
|
"netPrice": 22.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Updated supplier assignment",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"supplier": {
|
||||||
|
"$ref": "#/components/schemas/ArticleSupplier"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid pu, invalid stock, invalid price, invalid id, or no company selected",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Not authorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Supplier assignment, article, or company not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Article"
|
||||||
|
],
|
||||||
|
"summary": "Remove supplier assignment from article",
|
||||||
|
"description": "Removes a supplier assignment from the article and cleans up its associated purchase price.\n",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"cookieAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"x-signals": [
|
||||||
|
"article-<sku>"
|
||||||
|
],
|
||||||
|
"x-markdown": "article/supplier/delete.md",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "sku",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Article SKU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"description": "articlesupplier assignment ID"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "Deleted"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid id, invalid sku, or no company selected",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Not authorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Supplier assignment or article not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/article": {
|
"/article": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -8680,6 +9134,74 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ArticleSupplierPrice": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"net",
|
||||||
|
"taxId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"net": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Net purchase price"
|
||||||
|
},
|
||||||
|
"taxId": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Tax ID"
|
||||||
|
},
|
||||||
|
"taxRate": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Tax rate in percent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ArticleSupplier": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"articleId",
|
||||||
|
"entityId",
|
||||||
|
"pu"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"articleId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"entityId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"supplierName": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"sku": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Supplier-specific SKU (Lieferanten-Art.-Nr.)"
|
||||||
|
},
|
||||||
|
"pu": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Packaging unit (Verpackungseinheit / VPE)"
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Supplier stock"
|
||||||
|
},
|
||||||
|
"priceId": {
|
||||||
|
"type": "integer",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"$ref": "#/components/schemas/ArticleSupplierPrice",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Brand": {
|
"Brand": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -8952,7 +9474,7 @@
|
|||||||
"amount",
|
"amount",
|
||||||
"net",
|
"net",
|
||||||
"rebate",
|
"rebate",
|
||||||
"tax",
|
"taxrate",
|
||||||
"comment"
|
"comment"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -8981,9 +9503,9 @@
|
|||||||
"rebate": {
|
"rebate": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"tax": {
|
"taxrate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "tax.rate (manifestarticle.taxrate)"
|
"description": "Tax rate in percent (e.g. 19)"
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -8996,7 +9518,7 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"amount",
|
"amount",
|
||||||
"net",
|
"net",
|
||||||
"tax"
|
"taxrate"
|
||||||
],
|
],
|
||||||
"description": "Line item input. Requires `sku` or `name`.",
|
"description": "Line item input. Requires `sku` or `name`.",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -9020,9 +9542,9 @@
|
|||||||
"net": {
|
"net": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"tax": {
|
"taxrate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Tax rate (resolved via tax.ensure)"
|
"description": "Tax rate in percent (resolved via tax.ensure)"
|
||||||
},
|
},
|
||||||
"rebate": {
|
"rebate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -9065,7 +9587,7 @@
|
|||||||
"amount",
|
"amount",
|
||||||
"net",
|
"net",
|
||||||
"rebate",
|
"rebate",
|
||||||
"tax",
|
"taxrate",
|
||||||
"comment"
|
"comment"
|
||||||
],
|
],
|
||||||
"description": "Line shape for quote and order",
|
"description": "Line shape for quote and order",
|
||||||
@@ -9095,9 +9617,9 @@
|
|||||||
"rebate": {
|
"rebate": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"tax": {
|
"taxrate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "tax.rate (manifestarticle.taxrate)"
|
"description": "Tax rate in percent (e.g. 19)"
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -9110,7 +9632,7 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"amount",
|
"amount",
|
||||||
"net",
|
"net",
|
||||||
"tax"
|
"taxrate"
|
||||||
],
|
],
|
||||||
"description": "Line item input. Requires `sku` or `name`.",
|
"description": "Line item input. Requires `sku` or `name`.",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -9134,9 +9656,9 @@
|
|||||||
"net": {
|
"net": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"tax": {
|
"taxrate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Tax rate (resolved via tax.ensure)"
|
"description": "Tax rate in percent (resolved via tax.ensure)"
|
||||||
},
|
},
|
||||||
"rebate": {
|
"rebate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
|||||||
69
docs/api/routes/article/supplier/create.md
Normal file
69
docs/api/routes/article/supplier/create.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# `POST /article/{sku}/supplier`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Assign supplier to article
|
||||||
|
|
||||||
|
Assigns a supplier to an article with optional purchase price, stock, and supplier SKU.
|
||||||
|
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `sku` | yes | string | Article SKU |
|
||||||
|
|
||||||
|
## Body (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"entityId": 88,
|
||||||
|
"sku": "MET-44910",
|
||||||
|
"pu": 1,
|
||||||
|
"stock": 140,
|
||||||
|
"netPrice": 4.2,
|
||||||
|
"taxId": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
|
## Response `201`
|
||||||
|
|
||||||
|
Created supplier assignment
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"supplier": {
|
||||||
|
"id": 1,
|
||||||
|
"articleId": 1,
|
||||||
|
"entityId": 1,
|
||||||
|
"supplierName": "…",
|
||||||
|
"sku": "…",
|
||||||
|
"pu": 1,
|
||||||
|
"stock": 1,
|
||||||
|
"priceId": 1,
|
||||||
|
"price": {
|
||||||
|
"net": 1,
|
||||||
|
"taxId": 1,
|
||||||
|
"taxRate": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | entityId required, invalid pu, invalid stock, invalid price, or no company selected — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized — `{"error":"…"}` |
|
||||||
|
| 404 | Article, supplier, or company not found — `{"error":"…"}` |
|
||||||
|
| 409 | Supplier already assigned — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
38
docs/api/routes/article/supplier/delete.md
Normal file
38
docs/api/routes/article/supplier/delete.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# `DELETE /article/{sku}/supplier/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Remove supplier assignment from article
|
||||||
|
|
||||||
|
Removes a supplier assignment from the article and cleans up its associated purchase price.
|
||||||
|
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `sku` | yes | string | Article SKU |
|
||||||
|
| `id` | yes | integer | articlesupplier assignment ID |
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
|
## Response `204`
|
||||||
|
|
||||||
|
Deleted
|
||||||
|
|
||||||
|
_No content_
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | Invalid id, invalid sku, or no company selected — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized — `{"error":"…"}` |
|
||||||
|
| 404 | Supplier assignment or article not found — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
52
docs/api/routes/article/supplier/list.md
Normal file
52
docs/api/routes/article/supplier/list.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# `GET /article/{sku}/supplier`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
List article suppliers
|
||||||
|
|
||||||
|
Fetches all suppliers linked to the specified article SKU.
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `sku` | yes | string | Article SKU |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
List of suppliers linked to the article
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"suppliers": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"articleId": 1,
|
||||||
|
"entityId": 1,
|
||||||
|
"supplierName": "…",
|
||||||
|
"sku": "…",
|
||||||
|
"pu": 1,
|
||||||
|
"stock": 1,
|
||||||
|
"priceId": 1,
|
||||||
|
"price": {
|
||||||
|
"net": 1,
|
||||||
|
"taxId": 1,
|
||||||
|
"taxRate": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | No company selected or invalid sku — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized — `{"error":"…"}` |
|
||||||
|
| 404 | Article or company not found — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
67
docs/api/routes/article/supplier/update.md
Normal file
67
docs/api/routes/article/supplier/update.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# `PATCH /article/{sku}/supplier/{id}`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Update article supplier assignment
|
||||||
|
|
||||||
|
Updates an existing article-supplier association (supplier SKU, packaging unit, stock, purchase price).
|
||||||
|
|
||||||
|
|
||||||
|
## Path parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `sku` | yes | string | Article SKU |
|
||||||
|
| `id` | yes | integer | articlesupplier assignment ID |
|
||||||
|
|
||||||
|
## Body (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sku": "MET-44910-B",
|
||||||
|
"pu": 6,
|
||||||
|
"stock": 95,
|
||||||
|
"netPrice": 22.5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
On success, emits SSE signal: `article-<sku>` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Updated supplier assignment
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"supplier": {
|
||||||
|
"id": 1,
|
||||||
|
"articleId": 1,
|
||||||
|
"entityId": 1,
|
||||||
|
"supplierName": "…",
|
||||||
|
"sku": "…",
|
||||||
|
"pu": 1,
|
||||||
|
"stock": 1,
|
||||||
|
"priceId": 1,
|
||||||
|
"price": {
|
||||||
|
"net": 1,
|
||||||
|
"taxId": 1,
|
||||||
|
"taxRate": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | Invalid pu, invalid stock, invalid price, invalid id, or no company selected — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized — `{"error":"…"}` |
|
||||||
|
| 404 | Supplier assignment, article, or company not found — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
@@ -8,7 +8,7 @@ Creates a goods receipt with header and lines in one transaction.
|
|||||||
The manifest `number` is allocated automatically via
|
The manifest `number` is allocated automatically via
|
||||||
`nextNumber('delivery')`; the user-supplied reference is stored as
|
`nextNumber('delivery')`; the user-supplied reference is stored as
|
||||||
`externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and
|
`externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and
|
||||||
`tax` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.
|
`taxrate` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.
|
||||||
|
|
||||||
|
|
||||||
## Body (JSON)
|
## Body (JSON)
|
||||||
@@ -25,7 +25,7 @@ The manifest `number` is allocated automatically via
|
|||||||
"pu": 1,
|
"pu": 1,
|
||||||
"amount": 10,
|
"amount": 10,
|
||||||
"net": 99.5,
|
"net": 99.5,
|
||||||
"tax": 19
|
"taxrate": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ Created delivery
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Delivery
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Updated delivery
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Create order
|
|||||||
Creates a sales order with header and lines in one transaction.
|
Creates a sales order with header and lines in one transaction.
|
||||||
`date` is the document date (expected delivery).
|
`date` is the document date (expected delivery).
|
||||||
`entityId` must be a company customer (not Lieferanten/POS), or null.
|
`entityId` must be a company customer (not Lieferanten/POS), or null.
|
||||||
Each line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
||||||
|
|
||||||
|
|
||||||
## Body (JSON)
|
## Body (JSON)
|
||||||
@@ -24,7 +24,7 @@ Each line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defa
|
|||||||
"pu": 1,
|
"pu": 1,
|
||||||
"amount": 10,
|
"amount": 10,
|
||||||
"net": 99.5,
|
"net": 99.5,
|
||||||
"tax": 19
|
"taxrate": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ Created order
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Order
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Updated order
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Create quote
|
|||||||
Creates a quote with header and lines in one transaction.
|
Creates a quote with header and lines in one transaction.
|
||||||
`date` is the document date (valid-until).
|
`date` is the document date (valid-until).
|
||||||
`entityId` must be a company customer (not Lieferanten/POS), or null.
|
`entityId` must be a company customer (not Lieferanten/POS), or null.
|
||||||
Each line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
||||||
|
|
||||||
|
|
||||||
## Body (JSON)
|
## Body (JSON)
|
||||||
@@ -24,7 +24,7 @@ Each line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defa
|
|||||||
"pu": 1,
|
"pu": 1,
|
||||||
"amount": 10,
|
"amount": 10,
|
||||||
"net": 99.5,
|
"net": 99.5,
|
||||||
"tax": 19
|
"taxrate": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ Created quote
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Quote
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Updated quote
|
|||||||
"amount": 1,
|
"amount": 1,
|
||||||
"net": 1,
|
"net": 1,
|
||||||
"rebate": 1,
|
"rebate": 1,
|
||||||
"tax": 1,
|
"taxrate": 1,
|
||||||
"comment": "…"
|
"comment": "…"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
54
package-lock.json
generated
54
package-lock.json
generated
@@ -59,9 +59,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alcalzone/ansi-tokenize": {
|
"node_modules/@alcalzone/ansi-tokenize": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.1.tgz",
|
||||||
"integrity": "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==",
|
"integrity": "sha512-cUD0PT0YX+rImRhfn7BfMRduP/tX/Zqt/N0dHlAyn2RlcJssCb5KsCJkhln61eh9vuJDkH8wKn3L8zJzzWXPFg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^6.2.1",
|
"ansi-styles": "^6.2.1",
|
||||||
@@ -342,33 +342,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/msal-browser": {
|
"node_modules/@azure/msal-browser": {
|
||||||
"version": "5.21.0",
|
"version": "5.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.22.0.tgz",
|
||||||
"integrity": "sha512-80OcuXDErmcEDAIH9pBtSqBsed2sPT/IWmbG3xHLoPMl5zc8TINd6SlJAbVSmN5huGa3xGAg5qR7VnpaIEK0Zw==",
|
"integrity": "sha512-5kgu9xeEKgGc2JeidxAtU15NJTqiH/CMCRRQAJ4Rac56kB7KVg91vbNmn+z3RO1vNomPN69UvjKG9h1Pghx6dQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-common": "16.14.0"
|
"@azure/msal-common": "16.14.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/msal-common": {
|
"node_modules/@azure/msal-common": {
|
||||||
"version": "16.14.0",
|
"version": "16.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.14.1.tgz",
|
||||||
"integrity": "sha512-A4rb55hI86Q9tBl/+jBj7TMz7iX2RFgQs/nExFzcAtoI/BFRVdaH5SL/MivrYD7qvweMpN8AgVvVMHV8UBYxew==",
|
"integrity": "sha512-Or6xhPNyi4zHW25158yxBoyxuCqNSPa5YBVqfF1J5Ks4MJWBo/USXdp05DQIPu1Zli00YZu6t0+h6KvHJealxQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/msal-node": {
|
"node_modules/@azure/msal-node": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-6.0.1.tgz",
|
||||||
"integrity": "sha512-PU0l3CsEZVm2K8vZNFxvgmM5d0Yx/sVL/axWW0hJBoeyiB6Q/uv7hXORb7EdzlNxvehsWEF82/o+/sXlIo+UIw==",
|
"integrity": "sha512-ixSO1Y/kCVRthRs+hSx/5qkwaunX1/RAePhlMN0wIpIQ4WEZ6AREGGnGd1AP0qspHVsAwzWQKGubpjh50JyJIQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-common": "16.14.0",
|
"@azure/msal-common": "16.14.1",
|
||||||
"jsonwebtoken": "^9.0.0"
|
"jsonwebtoken": "^9.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -969,12 +969,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "26.5.1",
|
"version": "22.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz",
|
||||||
"integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==",
|
"integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~8.9.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/readable-stream": {
|
"node_modules/@types/readable-stream": {
|
||||||
@@ -1855,9 +1855,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.1.7",
|
"version": "3.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
|
||||||
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
|
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2087,9 +2087,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/google-auth-library": {
|
"node_modules/google-auth-library": {
|
||||||
"version": "11.0.2",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.1.0.tgz",
|
||||||
"integrity": "sha512-vzpgPutxrghPsnjrjpzLX2bdv8IOL719Rh0oEjGnQu8YCIbnbMuTTQ5zU9LcKvLdOPgCxBwppbvnhgW90Qna5Q==",
|
"integrity": "sha512-YIBq5KNoHQrZ9PM4PeodDBqqBGlSTIxh1zibN8u3zNIO9UJg8gvKuCpdKV7fWXhsZ2zRkU6FhGrgKNgLJ2IRgw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-js": "^1.3.0",
|
"base64-js": "^1.3.0",
|
||||||
@@ -3715,9 +3715,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "8.9.0",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
|
|||||||
@@ -108,6 +108,50 @@
|
|||||||
* format: date-time
|
* format: date-time
|
||||||
* text:
|
* text:
|
||||||
* type: string
|
* type: string
|
||||||
|
* ArticleSupplierPrice:
|
||||||
|
* type: object
|
||||||
|
* required: [net, taxId]
|
||||||
|
* properties:
|
||||||
|
* net:
|
||||||
|
* type: number
|
||||||
|
* description: Net purchase price
|
||||||
|
* taxId:
|
||||||
|
* type: integer
|
||||||
|
* description: Tax ID
|
||||||
|
* taxRate:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* description: Tax rate in percent
|
||||||
|
* ArticleSupplier:
|
||||||
|
* type: object
|
||||||
|
* required: [id, articleId, entityId, pu]
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* articleId:
|
||||||
|
* type: integer
|
||||||
|
* entityId:
|
||||||
|
* type: integer
|
||||||
|
* supplierName:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* sku:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* description: Supplier-specific SKU (Lieferanten-Art.-Nr.)
|
||||||
|
* pu:
|
||||||
|
* type: number
|
||||||
|
* description: Packaging unit (Verpackungseinheit / VPE)
|
||||||
|
* stock:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* description: Supplier stock
|
||||||
|
* priceId:
|
||||||
|
* type: integer
|
||||||
|
* nullable: true
|
||||||
|
* price:
|
||||||
|
* $ref: '#/components/schemas/ArticleSupplierPrice'
|
||||||
|
* nullable: true
|
||||||
* Brand:
|
* Brand:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [id, name]
|
* required: [id, name]
|
||||||
@@ -264,7 +308,7 @@
|
|||||||
* format: date-time
|
* format: date-time
|
||||||
* DeliveryLine:
|
* DeliveryLine:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [id, sku, name, pu, amount, net, rebate, tax, comment]
|
* required: [id, sku, name, pu, amount, net, rebate, taxrate, comment]
|
||||||
* properties:
|
* properties:
|
||||||
* id:
|
* id:
|
||||||
* type: integer
|
* type: integer
|
||||||
@@ -284,15 +328,15 @@
|
|||||||
* type: number
|
* type: number
|
||||||
* rebate:
|
* rebate:
|
||||||
* type: number
|
* type: number
|
||||||
* tax:
|
* taxrate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: tax.rate (manifestarticle.taxrate)
|
* description: Tax rate in percent (e.g. 19)
|
||||||
* comment:
|
* comment:
|
||||||
* type: string
|
* type: string
|
||||||
* nullable: true
|
* nullable: true
|
||||||
* DeliveryLineInput:
|
* DeliveryLineInput:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [amount, net, tax]
|
* required: [amount, net, taxrate]
|
||||||
* description: Line item input. Requires `sku` or `name`.
|
* description: Line item input. Requires `sku` or `name`.
|
||||||
* properties:
|
* properties:
|
||||||
* sku:
|
* sku:
|
||||||
@@ -310,9 +354,9 @@
|
|||||||
* type: number
|
* type: number
|
||||||
* net:
|
* net:
|
||||||
* type: number
|
* type: number
|
||||||
* tax:
|
* taxrate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Tax rate (resolved via tax.ensure)
|
* description: Tax rate in percent (resolved via tax.ensure)
|
||||||
* rebate:
|
* rebate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Defaults to 0
|
* description: Defaults to 0
|
||||||
@@ -331,7 +375,7 @@
|
|||||||
* $ref: '#/components/schemas/DeliveryLine'
|
* $ref: '#/components/schemas/DeliveryLine'
|
||||||
* ManifestLine:
|
* ManifestLine:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [id, sku, name, pu, amount, net, rebate, tax, comment]
|
* required: [id, sku, name, pu, amount, net, rebate, taxrate, comment]
|
||||||
* description: Line shape for quote and order
|
* description: Line shape for quote and order
|
||||||
* properties:
|
* properties:
|
||||||
* id:
|
* id:
|
||||||
@@ -352,15 +396,15 @@
|
|||||||
* type: number
|
* type: number
|
||||||
* rebate:
|
* rebate:
|
||||||
* type: number
|
* type: number
|
||||||
* tax:
|
* taxrate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: tax.rate (manifestarticle.taxrate)
|
* description: Tax rate in percent (e.g. 19)
|
||||||
* comment:
|
* comment:
|
||||||
* type: string
|
* type: string
|
||||||
* nullable: true
|
* nullable: true
|
||||||
* ManifestLineInput:
|
* ManifestLineInput:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [amount, net, tax]
|
* required: [amount, net, taxrate]
|
||||||
* description: Line item input. Requires `sku` or `name`.
|
* description: Line item input. Requires `sku` or `name`.
|
||||||
* properties:
|
* properties:
|
||||||
* sku:
|
* sku:
|
||||||
@@ -378,9 +422,9 @@
|
|||||||
* type: number
|
* type: number
|
||||||
* net:
|
* net:
|
||||||
* type: number
|
* type: number
|
||||||
* tax:
|
* taxrate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Tax rate (resolved via tax.ensure)
|
* description: Tax rate in percent (resolved via tax.ensure)
|
||||||
* rebate:
|
* rebate:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Defaults to 0
|
* description: Defaults to 0
|
||||||
|
|||||||
487
src/api/routes/articlesupplier.js
Normal file
487
src/api/routes/articlesupplier.js
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
import * as db from '../../db/articlesupplier.js';
|
||||||
|
import { emit } from '../sse.js';
|
||||||
|
|
||||||
|
function requireSelectedCompany(req) {
|
||||||
|
const companyId = req.user?.selectedCompanyId;
|
||||||
|
if (companyId == null) {
|
||||||
|
return { error: 'No company selected' };
|
||||||
|
}
|
||||||
|
return { companyId };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSku(value) {
|
||||||
|
const sku = typeof value === 'string' ? value.trim() : '';
|
||||||
|
if (!sku) return { error: 'sku is required' };
|
||||||
|
return { sku: decodeURIComponent(sku) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePositiveInt(value, field) {
|
||||||
|
const id = Number(value);
|
||||||
|
if (!Number.isInteger(id) || id <= 0) {
|
||||||
|
return { error: `${field} is required` };
|
||||||
|
}
|
||||||
|
return { id };
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitArticleSignal(req, sku) {
|
||||||
|
const companyId = req.user?.selectedCompanyId;
|
||||||
|
if (companyId == null) return;
|
||||||
|
emit(`article-${sku}`, { companyId });
|
||||||
|
}
|
||||||
|
|
||||||
|
function articleSupplierErrorStatus(error) {
|
||||||
|
if (error.message === 'No company selected') return 400;
|
||||||
|
if (error.message === 'Not authorized') return 403;
|
||||||
|
if (error.message === 'Company not found') return 404;
|
||||||
|
if (error.message === 'Article not found') return 404;
|
||||||
|
if (error.message === 'Supplier not found') return 404;
|
||||||
|
if (error.message === 'Supplier assignment not found') return 404;
|
||||||
|
if (error.message === 'Supplier already assigned') return 409;
|
||||||
|
if (error.message === 'Supplier required' || error.message === 'entityId is required') return 400;
|
||||||
|
if (error.message === 'Invalid pu' || error.message === 'pu must be greater than 0') return 400;
|
||||||
|
if (error.message === 'Invalid stock') return 400;
|
||||||
|
if (error.message === 'Invalid price') return 400;
|
||||||
|
if (error.message === 'sku is required') return 400;
|
||||||
|
if (error.message === 'id is required') return 400;
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers article supplier routes under `/article/:sku/supplier`.
|
||||||
|
* @param {import('express').Application} app
|
||||||
|
* @param {string} [basePath='']
|
||||||
|
*/
|
||||||
|
export function register(app, basePath = '') {
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /article/{sku}/supplier:
|
||||||
|
* get:
|
||||||
|
* tags: [Article]
|
||||||
|
* summary: List article suppliers
|
||||||
|
* description: Fetches all suppliers linked to the specified article SKU.
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* x-markdown: article/supplier/list.md
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: sku
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Article SKU
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: List of suppliers linked to the article
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* suppliers:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* $ref: '#/components/schemas/ArticleSupplier'
|
||||||
|
* 400:
|
||||||
|
* description: No company selected or invalid sku
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 403:
|
||||||
|
* description: Not authorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 404:
|
||||||
|
* description: Article or company not found
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* post:
|
||||||
|
* tags: [Article]
|
||||||
|
* summary: Assign supplier to article
|
||||||
|
* description: |
|
||||||
|
* Assigns a supplier to an article with optional purchase price, stock, and supplier SKU.
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* x-signals: [article-<sku>]
|
||||||
|
* x-markdown: article/supplier/create.md
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: sku
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Article SKU
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required: [entityId]
|
||||||
|
* properties:
|
||||||
|
* entityId:
|
||||||
|
* type: integer
|
||||||
|
* description: Supplier entity ID (in Lieferanten group)
|
||||||
|
* sku:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* description: Supplier's item number
|
||||||
|
* pu:
|
||||||
|
* type: number
|
||||||
|
* default: 1
|
||||||
|
* description: Packaging unit (VPE)
|
||||||
|
* stock:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* description: Supplier stock
|
||||||
|
* netPrice:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* description: Net purchase price
|
||||||
|
* taxId:
|
||||||
|
* type: integer
|
||||||
|
* nullable: true
|
||||||
|
* description: Tax ID for purchase price
|
||||||
|
* taxRate:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* description: Tax rate in percent
|
||||||
|
* example:
|
||||||
|
* entityId: 88
|
||||||
|
* sku: MET-44910
|
||||||
|
* pu: 1
|
||||||
|
* stock: 140
|
||||||
|
* netPrice: 4.20
|
||||||
|
* taxId: 1
|
||||||
|
* responses:
|
||||||
|
* 201:
|
||||||
|
* description: Created supplier assignment
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* supplier:
|
||||||
|
* $ref: '#/components/schemas/ArticleSupplier'
|
||||||
|
* 400:
|
||||||
|
* description: entityId required, invalid pu, invalid stock, invalid price, or no company selected
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 403:
|
||||||
|
* description: Not authorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 404:
|
||||||
|
* description: Article, supplier, or company not found
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 409:
|
||||||
|
* description: Supplier already assigned
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
*/
|
||||||
|
app.get(`${basePath}/article/:sku/supplier`, async (req, res) => {
|
||||||
|
const selected = requireSelectedCompany(req);
|
||||||
|
if (selected.error) {
|
||||||
|
return res.status(400).json({ error: selected.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const skuParsed = parseSku(req.params.sku);
|
||||||
|
if (skuParsed.error) {
|
||||||
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const suppliers = await db.listByArticleSku(req.user.id, skuParsed.sku);
|
||||||
|
res.status(200).json({ suppliers });
|
||||||
|
} catch (error) {
|
||||||
|
const status = articleSupplierErrorStatus(error);
|
||||||
|
if (status === 500) console.error('List article suppliers failed:', error);
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post(`${basePath}/article/:sku/supplier`, async (req, res) => {
|
||||||
|
const selected = requireSelectedCompany(req);
|
||||||
|
if (selected.error) {
|
||||||
|
return res.status(400).json({ error: selected.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const skuParsed = parseSku(req.params.sku);
|
||||||
|
if (skuParsed.error) {
|
||||||
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entityId, sku: supplierSku, pu, stock, netPrice, taxId, taxRate } = req.body ?? {};
|
||||||
|
if (entityId == null || !Number.isFinite(entityId)) {
|
||||||
|
return res.status(400).json({ error: 'entityId is required' });
|
||||||
|
}
|
||||||
|
if (pu !== undefined && pu !== null && (!Number.isFinite(pu) || pu <= 0)) {
|
||||||
|
return res.status(400).json({ error: 'pu must be greater than 0' });
|
||||||
|
}
|
||||||
|
if (stock !== undefined && stock !== null && !Number.isFinite(stock)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid stock' });
|
||||||
|
}
|
||||||
|
if (netPrice !== undefined && netPrice !== null && !Number.isFinite(netPrice)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid price' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supplier = await db.create(req.user.id, skuParsed.sku, {
|
||||||
|
entityId,
|
||||||
|
sku: supplierSku,
|
||||||
|
pu,
|
||||||
|
stock,
|
||||||
|
netPrice,
|
||||||
|
taxId,
|
||||||
|
taxRate,
|
||||||
|
});
|
||||||
|
emitArticleSignal(req, skuParsed.sku);
|
||||||
|
res.status(201).json({ supplier });
|
||||||
|
} catch (error) {
|
||||||
|
const status = articleSupplierErrorStatus(error);
|
||||||
|
if (status === 500) console.error('Create article supplier failed:', error);
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /article/{sku}/supplier/{id}:
|
||||||
|
* patch:
|
||||||
|
* tags: [Article]
|
||||||
|
* summary: Update article supplier assignment
|
||||||
|
* description: |
|
||||||
|
* Updates an existing article-supplier association (supplier SKU, packaging unit, stock, purchase price).
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* x-signals: [article-<sku>]
|
||||||
|
* x-markdown: article/supplier/update.md
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: sku
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Article SKU
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* description: articlesupplier assignment ID
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* sku:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* pu:
|
||||||
|
* type: number
|
||||||
|
* stock:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* netPrice:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* taxId:
|
||||||
|
* type: integer
|
||||||
|
* nullable: true
|
||||||
|
* taxRate:
|
||||||
|
* type: number
|
||||||
|
* nullable: true
|
||||||
|
* example:
|
||||||
|
* sku: MET-44910-B
|
||||||
|
* pu: 6
|
||||||
|
* stock: 95
|
||||||
|
* netPrice: 22.50
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Updated supplier assignment
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* supplier:
|
||||||
|
* $ref: '#/components/schemas/ArticleSupplier'
|
||||||
|
* 400:
|
||||||
|
* description: Invalid pu, invalid stock, invalid price, invalid id, or no company selected
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 403:
|
||||||
|
* description: Not authorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 404:
|
||||||
|
* description: Supplier assignment, article, or company not found
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* delete:
|
||||||
|
* tags: [Article]
|
||||||
|
* summary: Remove supplier assignment from article
|
||||||
|
* description: |
|
||||||
|
* Removes a supplier assignment from the article and cleans up its associated purchase price.
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* x-signals: [article-<sku>]
|
||||||
|
* x-markdown: article/supplier/delete.md
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: sku
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Article SKU
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* description: articlesupplier assignment ID
|
||||||
|
* responses:
|
||||||
|
* 204:
|
||||||
|
* description: Deleted
|
||||||
|
* 400:
|
||||||
|
* description: Invalid id, invalid sku, or no company selected
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 403:
|
||||||
|
* description: Not authorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 404:
|
||||||
|
* description: Supplier assignment or article not found
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
*/
|
||||||
|
app.patch(`${basePath}/article/:sku/supplier/:id`, async (req, res) => {
|
||||||
|
const selected = requireSelectedCompany(req);
|
||||||
|
if (selected.error) {
|
||||||
|
return res.status(400).json({ error: selected.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const skuParsed = parseSku(req.params.sku);
|
||||||
|
if (skuParsed.error) {
|
||||||
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const idParsed = parsePositiveInt(req.params.id, 'id');
|
||||||
|
if (idParsed.error) {
|
||||||
|
return res.status(400).json({ error: idParsed.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = req.body ?? {};
|
||||||
|
const patch = {};
|
||||||
|
if (body.sku !== undefined) patch.sku = body.sku;
|
||||||
|
if (body.pu !== undefined) {
|
||||||
|
if (body.pu === null || !Number.isFinite(body.pu) || body.pu <= 0) {
|
||||||
|
return res.status(400).json({ error: 'pu must be greater than 0' });
|
||||||
|
}
|
||||||
|
patch.pu = body.pu;
|
||||||
|
}
|
||||||
|
if (body.stock !== undefined) {
|
||||||
|
if (body.stock !== null && !Number.isFinite(body.stock)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid stock' });
|
||||||
|
}
|
||||||
|
patch.stock = body.stock;
|
||||||
|
}
|
||||||
|
if (body.netPrice !== undefined) {
|
||||||
|
if (body.netPrice !== null && !Number.isFinite(body.netPrice)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid price' });
|
||||||
|
}
|
||||||
|
patch.netPrice = body.netPrice;
|
||||||
|
}
|
||||||
|
if (body.taxId !== undefined) patch.taxId = body.taxId;
|
||||||
|
if (body.taxRate !== undefined) patch.taxRate = body.taxRate;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supplier = await db.update(req.user.id, skuParsed.sku, idParsed.id, patch);
|
||||||
|
emitArticleSignal(req, skuParsed.sku);
|
||||||
|
res.status(200).json({ supplier });
|
||||||
|
} catch (error) {
|
||||||
|
const status = articleSupplierErrorStatus(error);
|
||||||
|
if (status === 500) console.error('Update article supplier failed:', error);
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete(`${basePath}/article/:sku/supplier/:id`, async (req, res) => {
|
||||||
|
const selected = requireSelectedCompany(req);
|
||||||
|
if (selected.error) {
|
||||||
|
return res.status(400).json({ error: selected.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const skuParsed = parseSku(req.params.sku);
|
||||||
|
if (skuParsed.error) {
|
||||||
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const idParsed = parsePositiveInt(req.params.id, 'id');
|
||||||
|
if (idParsed.error) {
|
||||||
|
return res.status(400).json({ error: idParsed.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.remove(req.user.id, skuParsed.sku, idParsed.id);
|
||||||
|
emitArticleSignal(req, skuParsed.sku);
|
||||||
|
res.sendStatus(204);
|
||||||
|
} catch (error) {
|
||||||
|
const status = articleSupplierErrorStatus(error);
|
||||||
|
if (status === 500) console.error('Delete article supplier failed:', error);
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -82,7 +82,7 @@ export function register(app, basePath = '') {
|
|||||||
* The manifest `number` is allocated automatically via
|
* The manifest `number` is allocated automatically via
|
||||||
* `nextNumber('delivery')`; the user-supplied reference is stored as
|
* `nextNumber('delivery')`; the user-supplied reference is stored as
|
||||||
* `externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and
|
* `externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and
|
||||||
* `tax` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.
|
* `taxrate` (rate). `pu` defaults to `1`; `rebate` defaults to `0`.
|
||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: delivery/create.md
|
* x-markdown: delivery/create.md
|
||||||
@@ -117,7 +117,7 @@ export function register(app, basePath = '') {
|
|||||||
* pu: 1
|
* pu: 1
|
||||||
* amount: 10
|
* amount: 10
|
||||||
* net: 99.5
|
* net: 99.5
|
||||||
* tax: 19
|
* taxrate: 19
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Created delivery
|
* description: Created delivery
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function register(app, basePath = '') {
|
|||||||
* Creates a sales order with header and lines in one transaction.
|
* Creates a sales order with header and lines in one transaction.
|
||||||
* `date` is the document date (expected delivery).
|
* `date` is the document date (expected delivery).
|
||||||
* `entityId` must be a company customer (not Lieferanten/POS), or null.
|
* `entityId` must be a company customer (not Lieferanten/POS), or null.
|
||||||
* Each line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
* Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: order/create.md
|
* x-markdown: order/create.md
|
||||||
@@ -117,7 +117,7 @@ export function register(app, basePath = '') {
|
|||||||
* pu: 1
|
* pu: 1
|
||||||
* amount: 10
|
* amount: 10
|
||||||
* net: 99.5
|
* net: 99.5
|
||||||
* tax: 19
|
* taxrate: 19
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Created order
|
* description: Created order
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function register(app, basePath = '') {
|
|||||||
* Creates a quote with header and lines in one transaction.
|
* Creates a quote with header and lines in one transaction.
|
||||||
* `date` is the document date (valid-until).
|
* `date` is the document date (valid-until).
|
||||||
* `entityId` must be a company customer (not Lieferanten/POS), or null.
|
* `entityId` must be a company customer (not Lieferanten/POS), or null.
|
||||||
* Each line requires `sku` or `name`, `amount`, `net`, and `tax` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
* Each line requires `sku` or `name`, `amount`, `net`, and `taxrate` (rate); `pu` defaults to `1`; `rebate` defaults to `0`.
|
||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: quote/create.md
|
* x-markdown: quote/create.md
|
||||||
@@ -117,7 +117,7 @@ export function register(app, basePath = '') {
|
|||||||
* pu: 1
|
* pu: 1
|
||||||
* amount: 10
|
* amount: 10
|
||||||
* net: 99.5
|
* net: 99.5
|
||||||
* tax: 19
|
* taxrate: 19
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Created quote
|
* description: Created quote
|
||||||
|
|||||||
21
src/db/articlesupplier.js
Normal file
21
src/db/articlesupplier.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { getStore } from './store.js';
|
||||||
|
|
||||||
|
export async function listByArticleSku(googleAuthId, sku) {
|
||||||
|
const store = await getStore();
|
||||||
|
return store.articlesupplier.listByArticleSku(googleAuthId, sku);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(googleAuthId, sku, data) {
|
||||||
|
const store = await getStore();
|
||||||
|
return store.articlesupplier.create(googleAuthId, sku, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update(googleAuthId, sku, id, data) {
|
||||||
|
const store = await getStore();
|
||||||
|
return store.articlesupplier.update(googleAuthId, sku, id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(googleAuthId, sku, id) {
|
||||||
|
const store = await getStore();
|
||||||
|
return store.articlesupplier.delete(googleAuthId, sku, id);
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ const STORE_NAMESPACES = [
|
|||||||
'order',
|
'order',
|
||||||
'numbers',
|
'numbers',
|
||||||
'companysetting',
|
'companysetting',
|
||||||
|
'articlesupplier',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user