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:
seb
2026-08-06 05:32:48 +02:00
parent a748bd5f5c
commit 1860f38640
52 changed files with 2515 additions and 66 deletions

View File

@@ -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": [