Add delivery and supplier HTTP APIs with expanded response docs.
Wire store-backed CRUD for goods receipts and suppliers, and teach the API doc generator to expand $ref/allOf/array samples so route markdown shows full JSON shapes.
This commit is contained in:
@@ -65,6 +65,11 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/company/create.md`](routes/company/create.md) | `POST /company` |
|
||||
| [`routes/company/delete.md`](routes/company/delete.md) | `DELETE /company/{id}` |
|
||||
| [`routes/company/update.md`](routes/company/update.md) | `PUT /company/{id}` |
|
||||
| [`routes/delivery/list.md`](routes/delivery/list.md) | `GET /delivery` |
|
||||
| [`routes/delivery/create.md`](routes/delivery/create.md) | `POST /delivery` |
|
||||
| [`routes/delivery/delete.md`](routes/delivery/delete.md) | `DELETE /delivery/{id}` |
|
||||
| [`routes/delivery/get.md`](routes/delivery/get.md) | `GET /delivery/{id}` |
|
||||
| [`routes/delivery/update.md`](routes/delivery/update.md) | `PATCH /delivery/{id}` |
|
||||
| [`routes/events.md`](routes/events.md) | `GET /events` |
|
||||
| [`routes/health.md`](routes/health.md) | `GET /health` |
|
||||
| [`routes/image/get.md`](routes/image/get.md) | `GET /image/{fullHash}` |
|
||||
@@ -75,6 +80,10 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/posterminal/setOnAll.md`](routes/posterminal/setOnAll.md) | `PUT /posterminal/{id}/all` |
|
||||
| [`routes/posterminal/setOff.md`](routes/posterminal/setOff.md) | `DELETE /posterminal/{id}/category/{categoryId}` |
|
||||
| [`routes/posterminal/setOn.md`](routes/posterminal/setOn.md) | `PUT /posterminal/{id}/category/{categoryId}` |
|
||||
| [`routes/supplier/list.md`](routes/supplier/list.md) | `GET /supplier` |
|
||||
| [`routes/supplier/create.md`](routes/supplier/create.md) | `POST /supplier` |
|
||||
| [`routes/supplier/delete.md`](routes/supplier/delete.md) | `DELETE /supplier/{id}` |
|
||||
| [`routes/supplier/update.md`](routes/supplier/update.md) | `PATCH /supplier/{id}` |
|
||||
| [`routes/thumb/get.md`](routes/thumb/get.md) | `GET /thumb/{fullHash}` |
|
||||
| [`routes/unit/delete.md`](routes/unit/delete.md) | `DELETE /unit` |
|
||||
| [`routes/unit/list.md`](routes/unit/list.md) | `GET /unit` |
|
||||
|
||||
@@ -44,9 +44,21 @@
|
||||
"name": "Brand",
|
||||
"description": "Company-scoped brands"
|
||||
},
|
||||
{
|
||||
"name": "Supplier",
|
||||
"description": "Company-scoped suppliers (Lieferanten)"
|
||||
},
|
||||
{
|
||||
"name": "Delivery",
|
||||
"description": "Company-scoped goods receipts (Lieferungen)"
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"description": "Content-addressed AVIF by fullHash"
|
||||
},
|
||||
{
|
||||
"name": "Posterminal",
|
||||
"description": "Company-scoped POS terminals"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
@@ -314,6 +326,343 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/supplier": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Supplier"
|
||||
],
|
||||
"summary": "List suppliers",
|
||||
"description": "Lists suppliers (`entity` rows in the company `Lieferanten` customergroup)\nfor the authenticated user's selected company.\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "supplier/list.md",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Supplier list",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suppliers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Supplier"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "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": "Company not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Supplier"
|
||||
],
|
||||
"summary": "Create supplier",
|
||||
"description": "Creates a supplier in the company `Lieferanten` group (created on first use).\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "supplier/create.md",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"name": "Großhandel Müller"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created supplier",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"supplier": {
|
||||
"$ref": "#/components/schemas/Supplier"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Name required 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": "Company not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/supplier/{id}": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"Supplier"
|
||||
],
|
||||
"summary": "Update supplier",
|
||||
"description": "Renames a supplier for the selected company.",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "supplier/update.md",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"name": "Großhandel Müller GmbH"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Updated supplier",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"supplier": {
|
||||
"$ref": "#/components/schemas/Supplier"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Name required, 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 or company not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Supplier"
|
||||
],
|
||||
"summary": "Delete supplier",
|
||||
"description": "Deletes the supplier entity for the selected company.\nRelated `entityhistory` cascades; `manifest.entity_id` is set null.\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "supplier/delete.md",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Deleted"
|
||||
},
|
||||
"400": {
|
||||
"description": "No company selected or invalid id",
|
||||
"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 not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/posterminal": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1202,6 +1551,461 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/delivery": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Delivery"
|
||||
],
|
||||
"summary": "List deliveries",
|
||||
"description": "Lists goods receipts (`manifest` rows with `type = 'delivery'`)\nfor the authenticated user's selected company.\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "delivery/list.md",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Delivery list",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deliveries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeliverySummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "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": "Company not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Delivery"
|
||||
],
|
||||
"summary": "Create delivery",
|
||||
"description": "Creates a goods receipt with header and lines in one transaction.\nEach line requires `sku`, `amount`, `net`, and `tax` (rate).\n`rebate` defaults to `0`; `transactedAmount` defaults to `amount`.\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "delivery/create.md",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"number",
|
||||
"date",
|
||||
"lines"
|
||||
],
|
||||
"properties": {
|
||||
"number": {
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"supplierId": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
},
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeliveryLineInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"number": "DHL-123",
|
||||
"date": "2026-08-10",
|
||||
"supplierId": 1,
|
||||
"lines": [
|
||||
{
|
||||
"sku": "1",
|
||||
"amount": 10,
|
||||
"net": 99.5,
|
||||
"tax": 19
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created delivery",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Delivery"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid delivery, invalid lines, 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": "Company, supplier, or article not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/delivery/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Delivery"
|
||||
],
|
||||
"summary": "Get delivery",
|
||||
"description": "Returns a goods receipt with lines for the selected company.",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "delivery/get.md",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Delivery",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Delivery"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "No company selected or invalid id",
|
||||
"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": "Delivery or company not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"tags": [
|
||||
"Delivery"
|
||||
],
|
||||
"summary": "Update delivery",
|
||||
"description": "Patches header fields that are present. If `lines` is present, replaces\nall lines; if omitted, leaves lines unchanged. Returns the full delivery.\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "delivery/update.md",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"number": {
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"supplierId": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
},
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeliveryLineInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"number": "DHL-124",
|
||||
"lines": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Updated delivery",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Delivery"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid delivery, invalid lines, 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": "Delivery, supplier, article, or company not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Delivery"
|
||||
],
|
||||
"summary": "Delete delivery",
|
||||
"description": "Deletes the goods receipt (`manifestarticle` rows cascade).\n",
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"x-markdown": "delivery/delete.md",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Deleted"
|
||||
},
|
||||
"400": {
|
||||
"description": "No company selected or invalid id",
|
||||
"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": "Delivery not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/company": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -4564,6 +5368,168 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Supplier": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeliverySummary": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"number",
|
||||
"type",
|
||||
"supplierId",
|
||||
"date"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"number": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"delivery"
|
||||
]
|
||||
},
|
||||
"supplierId": {
|
||||
"type": "integer",
|
||||
"nullable": true,
|
||||
"description": "manifest.entity_id"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeliveryLine": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"articleId",
|
||||
"sku",
|
||||
"amount",
|
||||
"net",
|
||||
"transactedAmount",
|
||||
"rebate",
|
||||
"tax",
|
||||
"comment"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"articleId": {
|
||||
"type": "integer",
|
||||
"nullable": true
|
||||
},
|
||||
"sku": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"net": {
|
||||
"type": "number"
|
||||
},
|
||||
"transactedAmount": {
|
||||
"type": "number"
|
||||
},
|
||||
"rebate": {
|
||||
"type": "number"
|
||||
},
|
||||
"tax": {
|
||||
"type": "number",
|
||||
"description": "tax.rate"
|
||||
},
|
||||
"comment": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeliveryLineInput": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"sku",
|
||||
"amount",
|
||||
"net",
|
||||
"tax"
|
||||
],
|
||||
"properties": {
|
||||
"sku": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"net": {
|
||||
"type": "number"
|
||||
},
|
||||
"tax": {
|
||||
"type": "number",
|
||||
"description": "Tax rate (resolved via tax.ensure)"
|
||||
},
|
||||
"rebate": {
|
||||
"type": "number",
|
||||
"description": "Defaults to 0"
|
||||
},
|
||||
"transactedAmount": {
|
||||
"type": "number",
|
||||
"description": "Defaults to amount"
|
||||
},
|
||||
"comment": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delivery": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DeliverySummary"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"lines"
|
||||
],
|
||||
"properties": {
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeliveryLine"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ArticleImage": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -30,7 +30,21 @@ Image stored and linked
|
||||
|
||||
```json
|
||||
{
|
||||
"image": { … }
|
||||
"image": {
|
||||
"picId": 1,
|
||||
"sort": 1,
|
||||
"full": {
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"size": 1
|
||||
},
|
||||
"thumb": {
|
||||
"w": 1,
|
||||
"h": 1
|
||||
},
|
||||
"hash": "…",
|
||||
"thumbHash": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,12 @@ Created
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {
|
||||
"id": 1,
|
||||
"parentId": 1,
|
||||
"name": "…",
|
||||
"sku": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ New order
|
||||
|
||||
```json
|
||||
{
|
||||
"fullHashes": []
|
||||
"fullHashes": [
|
||||
"…"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Updated article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ Updated article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Updated article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Updated article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Updated article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Updated article detail
|
||||
|
||||
```json
|
||||
{
|
||||
"article": { … }
|
||||
"article": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,19 @@ Search index
|
||||
|
||||
```json
|
||||
{
|
||||
"articles": []
|
||||
"articles": [
|
||||
{
|
||||
"id": 1,
|
||||
"sku": "…",
|
||||
"name": "…",
|
||||
"eans": [
|
||||
"…"
|
||||
],
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -24,7 +24,12 @@ Login successful; `token` cookie set
|
||||
```json
|
||||
{
|
||||
"message": "Login successful",
|
||||
"user": { … }
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"selectedCompanyId": 1,
|
||||
"picture": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,12 @@ Authenticated
|
||||
|
||||
```json
|
||||
{
|
||||
"user": { … },
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"selectedCompanyId": 1,
|
||||
"picture": "…"
|
||||
},
|
||||
"googleClientId": "…"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -20,7 +20,12 @@ Updated user
|
||||
|
||||
```json
|
||||
{
|
||||
"user": { … }
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"selectedCompanyId": 1,
|
||||
"picture": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -12,7 +12,13 @@ Brand list
|
||||
|
||||
```json
|
||||
{
|
||||
"brands": []
|
||||
"brands": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"gsprpinfo": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ Upserted brand
|
||||
|
||||
```json
|
||||
{
|
||||
"brand": { … }
|
||||
"brand": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"gsprpinfo": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -30,7 +30,12 @@ Image stored and linked
|
||||
|
||||
```json
|
||||
{
|
||||
"category": { … }
|
||||
"category": {
|
||||
"id": 1,
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ Articles list
|
||||
|
||||
```json
|
||||
{
|
||||
"articles": []
|
||||
"articles": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,12 @@ Created
|
||||
|
||||
```json
|
||||
{
|
||||
"category": { … }
|
||||
"category": {
|
||||
"id": 1,
|
||||
"parentId": 1,
|
||||
"sort": 1,
|
||||
"name": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -24,7 +24,12 @@ Image removed
|
||||
|
||||
```json
|
||||
{
|
||||
"category": { … }
|
||||
"category": {
|
||||
"id": 1,
|
||||
"thumbW": 1,
|
||||
"thumbH": 1,
|
||||
"thumbHash": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ New sibling order
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryIds": []
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ Tree and articles for the selected company
|
||||
```json
|
||||
{
|
||||
"languageId": 1,
|
||||
"tree": [],
|
||||
"articles": []
|
||||
"tree": [
|
||||
{}
|
||||
],
|
||||
"articles": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -34,7 +34,13 @@ Updated category (renamed and/or moved)
|
||||
|
||||
```json
|
||||
{
|
||||
"category": { … }
|
||||
"category": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"slug": "…",
|
||||
"parentId": 1,
|
||||
"sort": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -23,8 +23,19 @@ Created; selected company updated
|
||||
|
||||
```json
|
||||
{
|
||||
"company": { … },
|
||||
"user": { … }
|
||||
"company": {
|
||||
"id": 1,
|
||||
"iso639": "…",
|
||||
"name": "…",
|
||||
"taxrate": 1,
|
||||
"reducedtaxrate": 1
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "user@example.com",
|
||||
"selectedCompanyId": 1,
|
||||
"picture": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -12,7 +12,15 @@ Company list
|
||||
|
||||
```json
|
||||
{
|
||||
"companies": []
|
||||
"companies": [
|
||||
{
|
||||
"id": 1,
|
||||
"iso639": "…",
|
||||
"name": "…",
|
||||
"taxrate": 1,
|
||||
"reducedtaxrate": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -29,7 +29,13 @@ Updated
|
||||
|
||||
```json
|
||||
{
|
||||
"company": { … }
|
||||
"company": {
|
||||
"id": 1,
|
||||
"iso639": "…",
|
||||
"name": "…",
|
||||
"taxrate": 1,
|
||||
"reducedtaxrate": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
70
docs/api/routes/delivery/create.md
Normal file
70
docs/api/routes/delivery/create.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# `POST /delivery`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create delivery
|
||||
|
||||
Creates a goods receipt with header and lines in one transaction.
|
||||
Each line requires `sku`, `amount`, `net`, and `tax` (rate).
|
||||
`rebate` defaults to `0`; `transactedAmount` defaults to `amount`.
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": "DHL-123",
|
||||
"date": "2026-08-10",
|
||||
"supplierId": 1,
|
||||
"lines": [
|
||||
{
|
||||
"sku": "1",
|
||||
"amount": 10,
|
||||
"net": 99.5,
|
||||
"tax": 19
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created delivery
|
||||
|
||||
```json
|
||||
{
|
||||
"delivery": {
|
||||
"id": 1,
|
||||
"number": "…",
|
||||
"type": "delivery",
|
||||
"supplierId": 1,
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"lines": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"amount": 1,
|
||||
"net": 1,
|
||||
"transactedAmount": 1,
|
||||
"rebate": 1,
|
||||
"tax": 1,
|
||||
"comment": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid delivery, invalid lines, or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company, supplier, or article not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
33
docs/api/routes/delivery/delete.md
Normal file
33
docs/api/routes/delivery/delete.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# `DELETE /delivery/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete delivery
|
||||
|
||||
Deletes the goods receipt (`manifestarticle` rows cascade).
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Response `204`
|
||||
|
||||
Deleted
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected or invalid id — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Delivery not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
55
docs/api/routes/delivery/get.md
Normal file
55
docs/api/routes/delivery/get.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# `GET /delivery/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Get delivery
|
||||
|
||||
Returns a goods receipt with lines for the selected company.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Response `200`
|
||||
|
||||
Delivery
|
||||
|
||||
```json
|
||||
{
|
||||
"delivery": {
|
||||
"id": 1,
|
||||
"number": "…",
|
||||
"type": "delivery",
|
||||
"supplierId": 1,
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"lines": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"amount": 1,
|
||||
"net": 1,
|
||||
"transactedAmount": 1,
|
||||
"rebate": 1,
|
||||
"tax": 1,
|
||||
"comment": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected or invalid id — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Delivery or company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
40
docs/api/routes/delivery/list.md
Normal file
40
docs/api/routes/delivery/list.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# `GET /delivery`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List deliveries
|
||||
|
||||
Lists goods receipts (`manifest` rows with `type = 'delivery'`)
|
||||
for the authenticated user's selected company.
|
||||
|
||||
|
||||
## Response `200`
|
||||
|
||||
Delivery list
|
||||
|
||||
```json
|
||||
{
|
||||
"deliveries": [
|
||||
{
|
||||
"id": 1,
|
||||
"number": "…",
|
||||
"type": "delivery",
|
||||
"supplierId": 1,
|
||||
"date": "2026-08-10T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
66
docs/api/routes/delivery/update.md
Normal file
66
docs/api/routes/delivery/update.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# `PATCH /delivery/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Update delivery
|
||||
|
||||
Patches header fields that are present. If `lines` is present, replaces
|
||||
all lines; if omitted, leaves lines unchanged. Returns the full delivery.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": "DHL-124",
|
||||
"lines": []
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated delivery
|
||||
|
||||
```json
|
||||
{
|
||||
"delivery": {
|
||||
"id": 1,
|
||||
"number": "…",
|
||||
"type": "delivery",
|
||||
"supplierId": 1,
|
||||
"date": "2026-08-10T00:00:00.000Z",
|
||||
"lines": [
|
||||
{
|
||||
"id": 1,
|
||||
"articleId": 1,
|
||||
"sku": "…",
|
||||
"amount": 1,
|
||||
"net": 1,
|
||||
"transactedAmount": 1,
|
||||
"rebate": 1,
|
||||
"tax": 1,
|
||||
"comment": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid delivery, invalid lines, invalid id, or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Delivery, supplier, article, or company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -12,7 +12,9 @@ Posterminal list
|
||||
|
||||
```json
|
||||
{
|
||||
"posterminals": []
|
||||
"posterminals": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ Updated category IDs for this terminal
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"categoryIds": []
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ Updated category IDs for this terminal
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"categoryIds": []
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ Updated category IDs for this terminal
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"categoryIds": []
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ Updated category IDs for this terminal
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"categoryIds": []
|
||||
"categoryIds": [
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Created posterminal with generated pairingpin
|
||||
|
||||
```json
|
||||
{
|
||||
"posterminal": { … }
|
||||
"posterminal": {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
44
docs/api/routes/supplier/create.md
Normal file
44
docs/api/routes/supplier/create.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# `POST /supplier`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create supplier
|
||||
|
||||
Creates a supplier in the company `Lieferanten` group (created on first use).
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Großhandel Müller"
|
||||
}
|
||||
```
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created supplier
|
||||
|
||||
```json
|
||||
{
|
||||
"supplier": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"createdAt": "2026-08-10T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-10T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Name required or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
34
docs/api/routes/supplier/delete.md
Normal file
34
docs/api/routes/supplier/delete.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# `DELETE /supplier/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete supplier
|
||||
|
||||
Deletes the supplier entity for the selected company.
|
||||
Related `entityhistory` cascades; `manifest.entity_id` is set null.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Response `204`
|
||||
|
||||
Deleted
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected or invalid id — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Supplier not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
39
docs/api/routes/supplier/list.md
Normal file
39
docs/api/routes/supplier/list.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# `GET /supplier`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List suppliers
|
||||
|
||||
Lists suppliers (`entity` rows in the company `Lieferanten` customergroup)
|
||||
for the authenticated user's selected company.
|
||||
|
||||
|
||||
## Response `200`
|
||||
|
||||
Supplier list
|
||||
|
||||
```json
|
||||
{
|
||||
"suppliers": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"createdAt": "2026-08-10T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-10T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
49
docs/api/routes/supplier/update.md
Normal file
49
docs/api/routes/supplier/update.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# `PATCH /supplier/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Update supplier
|
||||
|
||||
Renames a supplier for the selected company.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Großhandel Müller GmbH"
|
||||
}
|
||||
```
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated supplier
|
||||
|
||||
```json
|
||||
{
|
||||
"supplier": {
|
||||
"id": 1,
|
||||
"name": "…",
|
||||
"createdAt": "2026-08-10T00:00:00.000Z",
|
||||
"updatedAt": "2026-08-10T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Name required, invalid id, or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized — `{"error":"…"}` |
|
||||
| 404 | Supplier or company not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -12,7 +12,15 @@ Unit list
|
||||
|
||||
```json
|
||||
{
|
||||
"units": []
|
||||
"units": [
|
||||
{
|
||||
"si": "…",
|
||||
"basesi": "…",
|
||||
"basesifactor": 1,
|
||||
"symbol": "…",
|
||||
"name": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -26,7 +26,13 @@ Upserted unit
|
||||
|
||||
```json
|
||||
{
|
||||
"unit": { … }
|
||||
"unit": {
|
||||
"si": "…",
|
||||
"basesi": "…",
|
||||
"basesifactor": 1,
|
||||
"symbol": "…",
|
||||
"name": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -36,7 +36,10 @@ const options = {
|
||||
{ name: 'Company', description: 'Company CRUD' },
|
||||
{ name: 'Unit', description: 'Company-scoped units of measure' },
|
||||
{ name: 'Brand', description: 'Company-scoped brands' },
|
||||
{ name: 'Supplier', description: 'Company-scoped suppliers (Lieferanten)' },
|
||||
{ name: 'Delivery', description: 'Company-scoped goods receipts (Lieferungen)' },
|
||||
{ name: 'Image', description: 'Content-addressed AVIF by fullHash' },
|
||||
{ name: 'Posterminal', description: 'Company-scoped POS terminals' },
|
||||
],
|
||||
},
|
||||
apis: [
|
||||
@@ -64,41 +67,68 @@ function schemaRefName(schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatSchema(schema, components, indent = 0) {
|
||||
if (!schema) return '_none_';
|
||||
/**
|
||||
* Build a sample JSON value from an OpenAPI schema (expands $ref / allOf / array items).
|
||||
* @param {object} schema
|
||||
* @param {object} [components]
|
||||
* @param {number} [depth]
|
||||
* @returns {*}
|
||||
*/
|
||||
function toExample(schema, components, depth = 0) {
|
||||
if (!schema || depth > 8) return null;
|
||||
|
||||
if (schema.example !== undefined) return schema.example;
|
||||
|
||||
if (schema.$ref) {
|
||||
const name = schemaRefName(schema);
|
||||
const resolved = components?.schemas?.[name];
|
||||
if (resolved) {
|
||||
return formatSchema({ ...resolved, title: name }, components, indent);
|
||||
const resolved = components?.schemas?.[schemaRefName(schema)];
|
||||
return resolved ? toExample(resolved, components, depth) : {};
|
||||
}
|
||||
|
||||
if (Array.isArray(schema.allOf) && schema.allOf.length) {
|
||||
const out = {};
|
||||
for (const part of schema.allOf) {
|
||||
const sample = toExample(part, components, depth + 1);
|
||||
if (sample && typeof sample === 'object' && !Array.isArray(sample)) {
|
||||
Object.assign(out, sample);
|
||||
}
|
||||
}
|
||||
return `\`${name}\``;
|
||||
}
|
||||
if (schema.example !== undefined) {
|
||||
return '```json\n' + JSON.stringify(schema.example, null, 2) + '\n```';
|
||||
}
|
||||
if (schema.type === 'object' && schema.properties) {
|
||||
const lines = ['```json', '{'];
|
||||
const keys = Object.keys(schema.properties);
|
||||
keys.forEach((key, i) => {
|
||||
const prop = schema.properties[key];
|
||||
const comma = i < keys.length - 1 ? ',' : '';
|
||||
let value = '…';
|
||||
if (prop.type === 'string') value = prop.format === 'email' ? '"user@example.com"' : '"…"';
|
||||
else if (prop.type === 'integer' || prop.type === 'number') value = prop.nullable ? 'null' : '1';
|
||||
else if (prop.type === 'boolean') value = 'true';
|
||||
else if (prop.type === 'array') value = '[]';
|
||||
else if (prop.type === 'object' || prop.$ref) value = '{ … }';
|
||||
if (prop.example !== undefined) value = JSON.stringify(prop.example);
|
||||
lines.push(` "${key}": ${value}${comma}`);
|
||||
});
|
||||
lines.push('}', '```');
|
||||
return lines.join('\n');
|
||||
return out;
|
||||
}
|
||||
|
||||
if (schema.type === 'array') {
|
||||
return '```json\n[]\n```';
|
||||
if (schema.items) {
|
||||
return [toExample(schema.items, components, depth + 1)];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
return '```json\n' + JSON.stringify(schema, null, 2) + '\n```';
|
||||
|
||||
if (schema.type === 'object' || schema.properties) {
|
||||
const out = {};
|
||||
for (const [key, prop] of Object.entries(schema.properties || {})) {
|
||||
out[key] = toExample(prop, components, depth + 1);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if (schema.type === 'string') {
|
||||
if (schema.enum?.length) return schema.enum[0];
|
||||
if (schema.format === 'email') return 'user@example.com';
|
||||
if (schema.format === 'date-time') return '2026-08-10T00:00:00.000Z';
|
||||
if (schema.format === 'date') return '2026-08-10';
|
||||
return '…';
|
||||
}
|
||||
|
||||
if (schema.type === 'integer') return 1;
|
||||
if (schema.type === 'number') return 1;
|
||||
if (schema.type === 'boolean') return true;
|
||||
if (schema.nullable) return null;
|
||||
return '…';
|
||||
}
|
||||
|
||||
function formatSchema(schema, components) {
|
||||
if (!schema) return '_none_';
|
||||
const example = toExample(schema, components);
|
||||
return '```json\n' + JSON.stringify(example, null, 2) + '\n```';
|
||||
}
|
||||
|
||||
function contentSchema(content) {
|
||||
|
||||
@@ -110,6 +110,96 @@
|
||||
* gsprpinfo:
|
||||
* type: string
|
||||
* nullable: true
|
||||
* Supplier:
|
||||
* type: object
|
||||
* required: [id, name, createdAt, updatedAt]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* name:
|
||||
* type: string
|
||||
* createdAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updatedAt:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* DeliverySummary:
|
||||
* type: object
|
||||
* required: [id, number, type, supplierId, date]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* number:
|
||||
* type: string
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [delivery]
|
||||
* supplierId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
* description: manifest.entity_id
|
||||
* date:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* DeliveryLine:
|
||||
* type: object
|
||||
* required: [id, articleId, sku, amount, net, transactedAmount, rebate, tax, comment]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* articleId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
* sku:
|
||||
* type: string
|
||||
* nullable: true
|
||||
* amount:
|
||||
* type: number
|
||||
* net:
|
||||
* type: number
|
||||
* transactedAmount:
|
||||
* type: number
|
||||
* rebate:
|
||||
* type: number
|
||||
* tax:
|
||||
* type: number
|
||||
* description: tax.rate
|
||||
* comment:
|
||||
* type: string
|
||||
* nullable: true
|
||||
* DeliveryLineInput:
|
||||
* type: object
|
||||
* required: [sku, amount, net, tax]
|
||||
* properties:
|
||||
* sku:
|
||||
* type: string
|
||||
* amount:
|
||||
* type: number
|
||||
* net:
|
||||
* type: number
|
||||
* tax:
|
||||
* type: number
|
||||
* description: Tax rate (resolved via tax.ensure)
|
||||
* rebate:
|
||||
* type: number
|
||||
* description: Defaults to 0
|
||||
* transactedAmount:
|
||||
* type: number
|
||||
* description: Defaults to amount
|
||||
* comment:
|
||||
* type: string
|
||||
* nullable: true
|
||||
* Delivery:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/DeliverySummary'
|
||||
* - type: object
|
||||
* required: [lines]
|
||||
* properties:
|
||||
* lines:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/DeliveryLine'
|
||||
* ArticleImage:
|
||||
* type: object
|
||||
* required: [picId, sort, full, thumb, hash, thumbHash]
|
||||
|
||||
429
src/api/routes/delivery.js
Normal file
429
src/api/routes/delivery.js
Normal file
@@ -0,0 +1,429 @@
|
||||
import * as db from '../../db/delivery.js';
|
||||
|
||||
function requireSelectedCompany(req) {
|
||||
const companyId = req.user.selectedCompanyId;
|
||||
if (companyId == null) {
|
||||
return { error: 'No company selected' };
|
||||
}
|
||||
return { companyId };
|
||||
}
|
||||
|
||||
function deliveryErrorStatus(error) {
|
||||
if (error.message === 'Not authorized') return 403;
|
||||
if (error.message === 'Company not found') return 404;
|
||||
if (error.message === 'Delivery not found') return 404;
|
||||
if (error.message === 'Supplier not found') return 404;
|
||||
if (error.message === 'Article not found') return 404;
|
||||
if (error.message === 'Invalid delivery') return 400;
|
||||
if (error.message === 'Invalid lines') return 400;
|
||||
return 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers delivery (goods receipt) routes for the authenticated user's selected company.
|
||||
* @param {import('express').Application} app
|
||||
* @param {string} [basePath='']
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @openapi
|
||||
* /delivery:
|
||||
* get:
|
||||
* tags: [Delivery]
|
||||
* summary: List deliveries
|
||||
* description: |
|
||||
* Lists goods receipts (`manifest` rows with `type = 'delivery'`)
|
||||
* for the authenticated user's selected company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: delivery/list.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Delivery list
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* deliveries:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/DeliverySummary'
|
||||
* 400:
|
||||
* description: 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: Company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* post:
|
||||
* tags: [Delivery]
|
||||
* summary: Create delivery
|
||||
* description: |
|
||||
* Creates a goods receipt with header and lines in one transaction.
|
||||
* Each line requires `sku`, `amount`, `net`, and `tax` (rate).
|
||||
* `rebate` defaults to `0`; `transactedAmount` defaults to `amount`.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: delivery/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [number, date, lines]
|
||||
* properties:
|
||||
* number:
|
||||
* type: string
|
||||
* date:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* supplierId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
* lines:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/DeliveryLineInput'
|
||||
* example:
|
||||
* number: DHL-123
|
||||
* date: '2026-08-10'
|
||||
* supplierId: 1
|
||||
* lines:
|
||||
* - sku: '1'
|
||||
* amount: 10
|
||||
* net: 99.5
|
||||
* tax: 19
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created delivery
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* delivery:
|
||||
* $ref: '#/components/schemas/Delivery'
|
||||
* 400:
|
||||
* description: Invalid delivery, invalid lines, 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: Company, supplier, or article not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/delivery`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const deliveries = await db.list(req.user.id, selected.companyId);
|
||||
res.status(200).json({ deliveries });
|
||||
} catch (error) {
|
||||
const status = deliveryErrorStatus(error);
|
||||
if (status === 500) console.error('List deliveries failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post(`${basePath}/delivery`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const { number, date, supplierId, lines } = req.body ?? {};
|
||||
|
||||
try {
|
||||
const delivery = await db.create(req.user.id, selected.companyId, {
|
||||
number,
|
||||
date,
|
||||
supplierId,
|
||||
lines,
|
||||
});
|
||||
res.status(201).json({ delivery });
|
||||
} catch (error) {
|
||||
const status = deliveryErrorStatus(error);
|
||||
if (status === 500) console.error('Create delivery failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /delivery/{id}:
|
||||
* get:
|
||||
* tags: [Delivery]
|
||||
* summary: Get delivery
|
||||
* description: Returns a goods receipt with lines for the selected company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: delivery/get.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Delivery
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* delivery:
|
||||
* $ref: '#/components/schemas/Delivery'
|
||||
* 400:
|
||||
* description: No company selected or invalid id
|
||||
* 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: Delivery or company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* patch:
|
||||
* tags: [Delivery]
|
||||
* summary: Update delivery
|
||||
* description: |
|
||||
* Patches header fields that are present. If `lines` is present, replaces
|
||||
* all lines; if omitted, leaves lines unchanged. Returns the full delivery.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: delivery/update.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* number:
|
||||
* type: string
|
||||
* date:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* supplierId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
* lines:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/DeliveryLineInput'
|
||||
* example:
|
||||
* number: DHL-124
|
||||
* lines: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Updated delivery
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* delivery:
|
||||
* $ref: '#/components/schemas/Delivery'
|
||||
* 400:
|
||||
* description: Invalid delivery, invalid lines, 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: Delivery, supplier, article, or company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* delete:
|
||||
* tags: [Delivery]
|
||||
* summary: Delete delivery
|
||||
* description: |
|
||||
* Deletes the goods receipt (`manifestarticle` rows cascade).
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: delivery/delete.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Deleted
|
||||
* 400:
|
||||
* description: No company selected or invalid id
|
||||
* 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: Delivery not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/delivery/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const id = Number(req.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return res.status(400).json({ error: 'id is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const delivery = await db.getById(req.user.id, selected.companyId, id);
|
||||
res.status(200).json({ delivery });
|
||||
} catch (error) {
|
||||
const status = deliveryErrorStatus(error);
|
||||
if (status === 500) console.error('Get delivery failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.patch(`${basePath}/delivery/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const id = Number(req.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return res.status(400).json({ error: 'id is required' });
|
||||
}
|
||||
|
||||
const body = req.body ?? {};
|
||||
const patch = {};
|
||||
if (Object.prototype.hasOwnProperty.call(body, 'number')) {
|
||||
patch.number = body.number;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(body, 'date')) {
|
||||
patch.date = body.date;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(body, 'supplierId')) {
|
||||
patch.supplierId = body.supplierId;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(body, 'lines')) {
|
||||
patch.lines = body.lines;
|
||||
}
|
||||
|
||||
try {
|
||||
const delivery = await db.update(req.user.id, selected.companyId, id, patch);
|
||||
res.status(200).json({ delivery });
|
||||
} catch (error) {
|
||||
const status = deliveryErrorStatus(error);
|
||||
if (status === 500) console.error('Update delivery failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete(`${basePath}/delivery/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const id = Number(req.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return res.status(400).json({ error: 'id is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
await db.remove(req.user.id, selected.companyId, id);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
const status = deliveryErrorStatus(error);
|
||||
if (status === 500) console.error('Delete delivery failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
310
src/api/routes/supplier.js
Normal file
310
src/api/routes/supplier.js
Normal file
@@ -0,0 +1,310 @@
|
||||
import * as db from '../../db/supplier.js';
|
||||
|
||||
function requireSelectedCompany(req) {
|
||||
const companyId = req.user.selectedCompanyId;
|
||||
if (companyId == null) {
|
||||
return { error: 'No company selected' };
|
||||
}
|
||||
return { companyId };
|
||||
}
|
||||
|
||||
function supplierErrorStatus(error) {
|
||||
if (error.message === 'Not authorized') return 403;
|
||||
if (error.message === 'Company not found') return 404;
|
||||
if (error.message === 'Supplier not found') return 404;
|
||||
if (error.message === 'Name required') return 400;
|
||||
return 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers supplier routes for the authenticated user's selected company.
|
||||
* @param {import('express').Application} app
|
||||
* @param {string} [basePath='']
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @openapi
|
||||
* /supplier:
|
||||
* get:
|
||||
* tags: [Supplier]
|
||||
* summary: List suppliers
|
||||
* description: |
|
||||
* Lists suppliers (`entity` rows in the company `Lieferanten` customergroup)
|
||||
* for the authenticated user's selected company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: supplier/list.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Supplier list
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* suppliers:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Supplier'
|
||||
* 400:
|
||||
* description: 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: Company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* post:
|
||||
* tags: [Supplier]
|
||||
* summary: Create supplier
|
||||
* description: |
|
||||
* Creates a supplier in the company `Lieferanten` group (created on first use).
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: supplier/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [name]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* example:
|
||||
* name: Großhandel Müller
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created supplier
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* supplier:
|
||||
* $ref: '#/components/schemas/Supplier'
|
||||
* 400:
|
||||
* description: Name required 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: Company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/supplier`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const suppliers = await db.list(req.user.id, selected.companyId);
|
||||
res.status(200).json({ suppliers });
|
||||
} catch (error) {
|
||||
const status = supplierErrorStatus(error);
|
||||
if (status === 500) console.error('List suppliers failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post(`${basePath}/supplier`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const { name } = req.body ?? {};
|
||||
|
||||
try {
|
||||
const supplier = await db.create(req.user.id, selected.companyId, { name });
|
||||
res.status(201).json({ supplier });
|
||||
} catch (error) {
|
||||
const status = supplierErrorStatus(error);
|
||||
if (status === 500) console.error('Create supplier failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /supplier/{id}:
|
||||
* patch:
|
||||
* tags: [Supplier]
|
||||
* summary: Update supplier
|
||||
* description: Renames a supplier for the selected company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: supplier/update.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [name]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* example:
|
||||
* name: Großhandel Müller GmbH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Updated supplier
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* supplier:
|
||||
* $ref: '#/components/schemas/Supplier'
|
||||
* 400:
|
||||
* description: Name required, 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 or company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* delete:
|
||||
* tags: [Supplier]
|
||||
* summary: Delete supplier
|
||||
* description: |
|
||||
* Deletes the supplier entity for the selected company.
|
||||
* Related `entityhistory` cascades; `manifest.entity_id` is set null.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: supplier/delete.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Deleted
|
||||
* 400:
|
||||
* description: No company selected or invalid id
|
||||
* 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 not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.patch(`${basePath}/supplier/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const id = Number(req.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return res.status(400).json({ error: 'id is required' });
|
||||
}
|
||||
|
||||
const { name } = req.body ?? {};
|
||||
|
||||
try {
|
||||
const supplier = await db.update(req.user.id, selected.companyId, id, { name });
|
||||
res.status(200).json({ supplier });
|
||||
} catch (error) {
|
||||
const status = supplierErrorStatus(error);
|
||||
if (status === 500) console.error('Update supplier failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete(`${basePath}/supplier/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
const id = Number(req.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return res.status(400).json({ error: 'id is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
await db.remove(req.user.id, selected.companyId, id);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
const status = supplierErrorStatus(error);
|
||||
if (status === 500) console.error('Delete supplier failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
26
src/db/delivery.js
Normal file
26
src/db/delivery.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { getStore } from './store.js';
|
||||
|
||||
export async function list(googleAuthId, companyId) {
|
||||
const store = await getStore();
|
||||
return store.delivery.list(googleAuthId, companyId);
|
||||
}
|
||||
|
||||
export async function getById(googleAuthId, companyId, id) {
|
||||
const store = await getStore();
|
||||
return store.delivery.getById(googleAuthId, companyId, id);
|
||||
}
|
||||
|
||||
export async function create(googleAuthId, companyId, data) {
|
||||
const store = await getStore();
|
||||
return store.delivery.create(googleAuthId, companyId, data);
|
||||
}
|
||||
|
||||
export async function update(googleAuthId, companyId, id, data) {
|
||||
const store = await getStore();
|
||||
return store.delivery.update(googleAuthId, companyId, id, data);
|
||||
}
|
||||
|
||||
export async function remove(googleAuthId, companyId, id) {
|
||||
const store = await getStore();
|
||||
return store.delivery.delete(googleAuthId, companyId, id);
|
||||
}
|
||||
@@ -19,6 +19,8 @@ const STORE_NAMESPACES = [
|
||||
'tax',
|
||||
'google_auth',
|
||||
'posterminal',
|
||||
'supplier',
|
||||
'delivery',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
21
src/db/supplier.js
Normal file
21
src/db/supplier.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { getStore } from './store.js';
|
||||
|
||||
export async function list(googleAuthId, companyId) {
|
||||
const store = await getStore();
|
||||
return store.supplier.list(googleAuthId, companyId);
|
||||
}
|
||||
|
||||
export async function create(googleAuthId, companyId, data) {
|
||||
const store = await getStore();
|
||||
return store.supplier.create(googleAuthId, companyId, data);
|
||||
}
|
||||
|
||||
export async function update(googleAuthId, companyId, id, data) {
|
||||
const store = await getStore();
|
||||
return store.supplier.update(googleAuthId, companyId, id, data);
|
||||
}
|
||||
|
||||
export async function remove(googleAuthId, companyId, id) {
|
||||
const store = await getStore();
|
||||
return store.supplier.delete(googleAuthId, companyId, id);
|
||||
}
|
||||
Reference in New Issue
Block a user