From 8c1938890142aa7d1390736ef00a2c2fdc975281 Mon Sep 17 00:00:00 2001 From: seb Date: Fri, 18 Sep 2026 05:20:03 +0200 Subject: [PATCH] file meta --- docs/api/openapi.json | 162 ++++++++++++++++++++++++++++- docs/api/routes/delivery/create.md | 29 +++++- docs/api/routes/delivery/get.md | 18 ++++ docs/api/routes/delivery/update.md | 20 +++- src/api/openapi.js | 87 +++++++++++++++- src/api/routes/delivery.js | 30 +++++- 6 files changed, 334 insertions(+), 12 deletions(-) diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 95466e1..7e5a4a1 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -4477,6 +4477,13 @@ "items": { "$ref": "#/components/schemas/DeliveryLineInput" } + }, + "files": { + "type": "array", + "description": "Attached documents (pdf or txt) stored with the delivery\nand returned in `files` on every delivery response.\n", + "items": { + "$ref": "#/components/schemas/DeliveryFileInput" + } } }, "example": { @@ -4492,6 +4499,13 @@ "net": 99.5, "taxrate": 19 } + ], + "files": [ + { + "mimetype": "application/pdf", + "name": "delivery-note.pdf", + "content": "" + } ] } } @@ -4515,7 +4529,7 @@ } }, "400": { - "description": "Invalid delivery, invalid lines, or no company selected", + "description": "Invalid delivery, invalid lines or files, or no company selected", "content": { "application/json": { "schema": { @@ -4545,7 +4559,7 @@ } }, "404": { - "description": "Company, supplier, or article not found", + "description": "Company or supplier not found", "content": { "application/json": { "schema": { @@ -4685,6 +4699,13 @@ "items": { "$ref": "#/components/schemas/DeliveryLineInput" } + }, + "files": { + "type": "array", + "description": "Appends attachments (pdf or txt) to the delivery.\nOmit to leave existing files unchanged.\n", + "items": { + "$ref": "#/components/schemas/DeliveryFileInput" + } } }, "example": { @@ -4742,7 +4763,7 @@ } }, "404": { - "description": "Delivery, supplier, article, or company not found", + "description": "Delivery, supplier, or company not found", "content": { "application/json": { "schema": { @@ -10003,7 +10024,8 @@ { "type": "object", "required": [ - "lines" + "lines", + "files" ], "properties": { "lines": { @@ -10011,11 +10033,143 @@ "items": { "$ref": "#/components/schemas/DeliveryLine" } + }, + "files": { + "type": "array", + "description": "Attached documents (pdf/txt) for the delivery", + "items": { + "$ref": "#/components/schemas/DeliveryFile" + } } } } ] }, + "DeliveryFile": { + "type": "object", + "description": "Attachment metadata (binary content is served via the attachment content endpoints)", + "required": [ + "id", + "manifestId", + "fileId", + "sort", + "hash", + "mimetype", + "name", + "size" + ], + "properties": { + "id": { + "type": "integer", + "description": "manifestattachment id" + }, + "manifestId": { + "type": "integer" + }, + "fileId": { + "type": "integer" + }, + "sort": { + "type": "integer" + }, + "hash": { + "type": "string", + "description": "SHA-256 of the file content" + }, + "mimetype": { + "type": "string", + "description": "application/pdf or text/plain" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "thumbavif": { + "type": "string", + "format": "byte", + "nullable": true + }, + "thumbx": { + "type": "integer", + "nullable": true + }, + "thumby": { + "type": "integer", + "nullable": true + }, + "meta": { + "type": "object", + "nullable": true, + "description": "String key/value metadata from the file row", + "additionalProperties": { + "type": "string" + } + } + } + }, + "DeliveryFileInput": { + "type": "object", + "required": [ + "mimetype", + "name", + "content" + ], + "description": "Attachment input. Only application/pdf and text/plain are accepted.", + "properties": { + "mimetype": { + "type": "string", + "enum": [ + "application/pdf", + "text/plain" + ] + }, + "name": { + "type": "string" + }, + "content": { + "type": "string", + "format": "byte", + "description": "File content (base64-encoded in JSON)" + }, + "sort": { + "type": "integer", + "description": "Optional sort order (defaults to appending after existing files)" + }, + "size": { + "type": "integer", + "description": "Optional size in bytes (defaults to content length)" + }, + "hash": { + "type": "string", + "description": "Optional SHA-256 (auto-computed if omitted)" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "meta": { + "type": "object", + "description": "Optional string key/value metadata stored with the file", + "additionalProperties": { + "type": "string" + } + } + } + }, "ManifestLine": { "type": "object", "required": [ diff --git a/docs/api/routes/delivery/create.md b/docs/api/routes/delivery/create.md index 0ce285a..d8fb4b9 100644 --- a/docs/api/routes/delivery/create.md +++ b/docs/api/routes/delivery/create.md @@ -27,6 +27,13 @@ The manifest `number` is allocated automatically via "net": 99.5, "taxrate": 19 } + ], + "files": [ + { + "mimetype": "application/pdf", + "name": "delivery-note.pdf", + "content": "" + } ] } ``` @@ -56,6 +63,24 @@ Created delivery "taxrate": 1, "comment": "…" } + ], + "files": [ + { + "id": 1, + "manifestId": 1, + "fileId": 1, + "sort": 1, + "hash": "…", + "mimetype": "…", + "name": "…", + "size": 1, + "created": "2026-08-10T00:00:00.000Z", + "modified": "2026-08-10T00:00:00.000Z", + "thumbavif": "…", + "thumbx": 1, + "thumby": 1, + "meta": {} + } ] } } @@ -65,10 +90,10 @@ Created delivery | Status | Description | |--------|-------------| -| 400 | Invalid delivery, invalid lines, or no company selected — `{"error":"…"}` | +| 400 | Invalid delivery, invalid lines or files, or no company selected — `{"error":"…"}` | | 401 | Unauthorized — `{"error":"…"}` | | 403 | Not authorized — `{"error":"…"}` | -| 404 | Company, supplier, or article not found — `{"error":"…"}` | +| 404 | Company or supplier not found — `{"error":"…"}` | --- diff --git a/docs/api/routes/delivery/get.md b/docs/api/routes/delivery/get.md index f09ae75..9c819d6 100644 --- a/docs/api/routes/delivery/get.md +++ b/docs/api/routes/delivery/get.md @@ -37,6 +37,24 @@ Delivery "taxrate": 1, "comment": "…" } + ], + "files": [ + { + "id": 1, + "manifestId": 1, + "fileId": 1, + "sort": 1, + "hash": "…", + "mimetype": "…", + "name": "…", + "size": 1, + "created": "2026-08-10T00:00:00.000Z", + "modified": "2026-08-10T00:00:00.000Z", + "thumbavif": "…", + "thumbx": 1, + "thumby": 1, + "meta": {} + } ] } } diff --git a/docs/api/routes/delivery/update.md b/docs/api/routes/delivery/update.md index 4bc0677..e1677fb 100644 --- a/docs/api/routes/delivery/update.md +++ b/docs/api/routes/delivery/update.md @@ -50,6 +50,24 @@ Updated delivery "taxrate": 1, "comment": "…" } + ], + "files": [ + { + "id": 1, + "manifestId": 1, + "fileId": 1, + "sort": 1, + "hash": "…", + "mimetype": "…", + "name": "…", + "size": 1, + "created": "2026-08-10T00:00:00.000Z", + "modified": "2026-08-10T00:00:00.000Z", + "thumbavif": "…", + "thumbx": 1, + "thumby": 1, + "meta": {} + } ] } } @@ -62,7 +80,7 @@ Updated delivery | 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":"…"}` | +| 404 | Delivery, supplier, or company not found — `{"error":"…"}` | --- diff --git a/src/api/openapi.js b/src/api/openapi.js index a113301..523f474 100644 --- a/src/api/openapi.js +++ b/src/api/openapi.js @@ -422,12 +422,97 @@ * allOf: * - $ref: '#/components/schemas/DeliverySummary' * - type: object - * required: [lines] + * required: [lines, files] * properties: * lines: * type: array * items: * $ref: '#/components/schemas/DeliveryLine' + * files: + * type: array + * description: Attached documents (pdf/txt) for the delivery + * items: + * $ref: '#/components/schemas/DeliveryFile' + * DeliveryFile: + * type: object + * description: Attachment metadata (binary content is served via the attachment content endpoints) + * required: [id, manifestId, fileId, sort, hash, mimetype, name, size] + * properties: + * id: + * type: integer + * description: manifestattachment id + * manifestId: + * type: integer + * fileId: + * type: integer + * sort: + * type: integer + * hash: + * type: string + * description: SHA-256 of the file content + * mimetype: + * type: string + * description: application/pdf or text/plain + * name: + * type: string + * size: + * type: integer + * created: + * type: string + * format: date-time + * modified: + * type: string + * format: date-time + * thumbavif: + * type: string + * format: byte + * nullable: true + * thumbx: + * type: integer + * nullable: true + * thumby: + * type: integer + * nullable: true + * meta: + * type: object + * nullable: true + * description: String key/value metadata from the file row + * additionalProperties: + * type: string + * DeliveryFileInput: + * type: object + * required: [mimetype, name, content] + * description: Attachment input. Only application/pdf and text/plain are accepted. + * properties: + * mimetype: + * type: string + * enum: [application/pdf, text/plain] + * name: + * type: string + * content: + * type: string + * format: byte + * description: File content (base64-encoded in JSON) + * sort: + * type: integer + * description: Optional sort order (defaults to appending after existing files) + * size: + * type: integer + * description: Optional size in bytes (defaults to content length) + * hash: + * type: string + * description: Optional SHA-256 (auto-computed if omitted) + * created: + * type: string + * format: date-time + * modified: + * type: string + * format: date-time + * meta: + * type: object + * description: Optional string key/value metadata stored with the file + * additionalProperties: + * type: string * ManifestLine: * type: object * required: [id, sku, name, pu, amount, net, rebate, taxrate, comment] diff --git a/src/api/routes/delivery.js b/src/api/routes/delivery.js index 1d65ced..bb6ef6c 100644 --- a/src/api/routes/delivery.js +++ b/src/api/routes/delivery.js @@ -107,6 +107,13 @@ export function register(app, basePath = '') { * type: array * items: * $ref: '#/components/schemas/DeliveryLineInput' + * files: + * type: array + * description: | + * Attached documents (pdf or txt) stored with the delivery + * and returned in `files` on every delivery response. + * items: + * $ref: '#/components/schemas/DeliveryFileInput' * example: * externalNumber: DHL-123 * date: '2026-08-10' @@ -118,6 +125,10 @@ export function register(app, basePath = '') { * amount: 10 * net: 99.5 * taxrate: 19 + * files: + * - mimetype: application/pdf + * name: delivery-note.pdf + * content: '' * responses: * 201: * description: Created delivery @@ -129,7 +140,7 @@ export function register(app, basePath = '') { * delivery: * $ref: '#/components/schemas/Delivery' * 400: - * description: Invalid delivery, invalid lines, or no company selected + * description: Invalid delivery, invalid lines or files, or no company selected * content: * application/json: * schema: @@ -147,7 +158,7 @@ export function register(app, basePath = '') { * schema: * $ref: '#/components/schemas/Error' * 404: - * description: Company, supplier, or article not found + * description: Company or supplier not found * content: * application/json: * schema: @@ -175,7 +186,7 @@ export function register(app, basePath = '') { return res.status(400).json({ error: selected.error }); } - const { externalNumber, date, supplierId, lines } = req.body ?? {}; + const { externalNumber, date, supplierId, lines, files } = req.body ?? {}; try { const delivery = await db.create(req.user.id, { @@ -183,6 +194,7 @@ export function register(app, basePath = '') { date, supplierId, lines, + files, }); res.status(201).json({ delivery }); } catch (error) { @@ -280,6 +292,13 @@ export function register(app, basePath = '') { * type: array * items: * $ref: '#/components/schemas/DeliveryLineInput' + * files: + * type: array + * description: | + * Appends attachments (pdf or txt) to the delivery. + * Omit to leave existing files unchanged. + * items: + * $ref: '#/components/schemas/DeliveryFileInput' * example: * externalNumber: DHL-124 * lines: [] @@ -312,7 +331,7 @@ export function register(app, basePath = '') { * schema: * $ref: '#/components/schemas/Error' * 404: - * description: Delivery, supplier, article, or company not found + * description: Delivery, supplier, or company not found * content: * application/json: * schema: @@ -405,6 +424,9 @@ export function register(app, basePath = '') { if (Object.prototype.hasOwnProperty.call(body, 'lines')) { patch.lines = body.lines; } + if (Object.prototype.hasOwnProperty.call(body, 'files')) { + patch.files = body.files; + } try { const delivery = await db.update(req.user.id, id, patch);