This commit is contained in:
seb
2026-10-14 23:26:09 +02:00
parent 0ea7da4f3a
commit 507dffa567
18 changed files with 552 additions and 9 deletions

View File

@@ -134,6 +134,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/delivery/delete.md`](routes/delivery/delete.md) | `DELETE /delivery/{id}` | | [`routes/delivery/delete.md`](routes/delivery/delete.md) | `DELETE /delivery/{id}` |
| [`routes/delivery/get.md`](routes/delivery/get.md) | `GET /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/delivery/update.md`](routes/delivery/update.md) | `PATCH /delivery/{id}` |
| [`routes/delivery/_id/file/_fileId.delete.md`](routes/delivery/_id/file/_fileId.delete.md) | `DELETE /delivery/{id}/file/{fileId}` |
| [`routes/delivery/file.md`](routes/delivery/file.md) | `GET /delivery/{id}/file/{fileId}` | | [`routes/delivery/file.md`](routes/delivery/file.md) | `GET /delivery/{id}/file/{fileId}` |
| [`routes/delivery/_id/pdf.md`](routes/delivery/_id/pdf.md) | `POST /delivery/{id}/pdf` | | [`routes/delivery/_id/pdf.md`](routes/delivery/_id/pdf.md) | `POST /delivery/{id}/pdf` |
| [`routes/entity/list.md`](routes/entity/list.md) | `GET /entity` | | [`routes/entity/list.md`](routes/entity/list.md) | `GET /entity` |
@@ -188,7 +189,8 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/order/delete.md`](routes/order/delete.md) | `DELETE /order/{id}` | | [`routes/order/delete.md`](routes/order/delete.md) | `DELETE /order/{id}` |
| [`routes/order/get.md`](routes/order/get.md) | `GET /order/{id}` | | [`routes/order/get.md`](routes/order/get.md) | `GET /order/{id}` |
| [`routes/order/update.md`](routes/order/update.md) | `PATCH /order/{id}` | | [`routes/order/update.md`](routes/order/update.md) | `PATCH /order/{id}` |
| [`routes/order/_id/file/_fileId.md`](routes/order/_id/file/_fileId.md) | `GET /order/{id}/file/{fileId}` | | [`routes/order/_id/file/_fileId.delete.md`](routes/order/_id/file/_fileId.delete.md) | `DELETE /order/{id}/file/{fileId}` |
| [`routes/order/_id/file/_fileId.get.md`](routes/order/_id/file/_fileId.get.md) | `GET /order/{id}/file/{fileId}` |
| [`routes/order/_id/payment.get.md`](routes/order/_id/payment.get.md) | `GET /order/{id}/payment` | | [`routes/order/_id/payment.get.md`](routes/order/_id/payment.get.md) | `GET /order/{id}/payment` |
| [`routes/order/_id/payment.post.md`](routes/order/_id/payment.post.md) | `POST /order/{id}/payment` | | [`routes/order/_id/payment.post.md`](routes/order/_id/payment.post.md) | `POST /order/{id}/payment` |
| [`routes/order/_id/pdf.md`](routes/order/_id/pdf.md) | `POST /order/{id}/pdf` | | [`routes/order/_id/pdf.md`](routes/order/_id/pdf.md) | `POST /order/{id}/pdf` |
@@ -227,13 +229,15 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/quote/delete.md`](routes/quote/delete.md) | `DELETE /quote/{id}` | | [`routes/quote/delete.md`](routes/quote/delete.md) | `DELETE /quote/{id}` |
| [`routes/quote/get.md`](routes/quote/get.md) | `GET /quote/{id}` | | [`routes/quote/get.md`](routes/quote/get.md) | `GET /quote/{id}` |
| [`routes/quote/update.md`](routes/quote/update.md) | `PATCH /quote/{id}` | | [`routes/quote/update.md`](routes/quote/update.md) | `PATCH /quote/{id}` |
| [`routes/quote/_id/file/_fileId.md`](routes/quote/_id/file/_fileId.md) | `GET /quote/{id}/file/{fileId}` | | [`routes/quote/_id/file/_fileId.delete.md`](routes/quote/_id/file/_fileId.delete.md) | `DELETE /quote/{id}/file/{fileId}` |
| [`routes/quote/_id/file/_fileId.get.md`](routes/quote/_id/file/_fileId.get.md) | `GET /quote/{id}/file/{fileId}` |
| [`routes/quote/_id/pdf.md`](routes/quote/_id/pdf.md) | `POST /quote/{id}/pdf` | | [`routes/quote/_id/pdf.md`](routes/quote/_id/pdf.md) | `POST /quote/{id}/pdf` |
| [`routes/serper/image.md`](routes/serper/image.md) | `POST /serper/image` | | [`routes/serper/image.md`](routes/serper/image.md) | `POST /serper/image` |
| [`routes/serper/images.md`](routes/serper/images.md) | `POST /serper/images` | | [`routes/serper/images.md`](routes/serper/images.md) | `POST /serper/images` |
| [`routes/shipment/_id.delete.md`](routes/shipment/_id.delete.md) | `DELETE /shipment/{id}` | | [`routes/shipment/_id.delete.md`](routes/shipment/_id.delete.md) | `DELETE /shipment/{id}` |
| [`routes/shipment/_id.get.md`](routes/shipment/_id.get.md) | `GET /shipment/{id}` | | [`routes/shipment/_id.get.md`](routes/shipment/_id.get.md) | `GET /shipment/{id}` |
| [`routes/shipment/_id/file/_fileId.md`](routes/shipment/_id/file/_fileId.md) | `GET /shipment/{id}/file/{fileId}` | | [`routes/shipment/_id/file/_fileId.delete.md`](routes/shipment/_id/file/_fileId.delete.md) | `DELETE /shipment/{id}/file/{fileId}` |
| [`routes/shipment/_id/file/_fileId.get.md`](routes/shipment/_id/file/_fileId.get.md) | `GET /shipment/{id}/file/{fileId}` |
| [`routes/shipment/_id/pdf.md`](routes/shipment/_id/pdf.md) | `POST /shipment/{id}/pdf` | | [`routes/shipment/_id/pdf.md`](routes/shipment/_id/pdf.md) | `POST /shipment/{id}/pdf` |
| [`routes/shipment/_id/shipping.get.md`](routes/shipment/_id/shipping.get.md) | `GET /shipment/{id}/shipping` | | [`routes/shipment/_id/shipping.get.md`](routes/shipment/_id/shipping.get.md) | `GET /shipment/{id}/shipping` |
| [`routes/shipment/_id/shipping.post.md`](routes/shipment/_id/shipping.post.md) | `POST /shipment/{id}/shipping` | | [`routes/shipment/_id/shipping.post.md`](routes/shipment/_id/shipping.post.md) | `POST /shipment/{id}/shipping` |
@@ -288,16 +292,16 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
| `attributename` | `POST /attributename`, `PUT /attributename/order`, `DELETE /attributename/{id}`, `PATCH /attributename/{id}`, `DELETE /attributevalue/{id}/image`, `POST /attributevalue/{id}/image` | | `attributename` | `POST /attributename`, `PUT /attributename/order`, `DELETE /attributename/{id}`, `PATCH /attributename/{id}`, `DELETE /attributevalue/{id}/image`, `POST /attributevalue/{id}/image` |
| `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` | | `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` |
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` | | `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` |
| `delivery` | `POST /delivery/{id}/pdf` | | `delivery` | `DELETE /delivery/{id}/file/{fileId}`, `POST /delivery/{id}/pdf` |
| `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` | | `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` |
| `ledger` | `POST /stock/adjust` | | `ledger` | `POST /stock/adjust` |
| `modelconversation` | `POST /modelconversation`, `DELETE /modelconversation/{id}` | | `modelconversation` | `POST /modelconversation`, `DELETE /modelconversation/{id}` |
| `modelconversation-<id>` | `POST /modelconversation/{id}/message` | | `modelconversation-<id>` | `POST /modelconversation/{id}/message` |
| `order` | `POST /order/{id}/pdf` | | `order` | `DELETE /order/{id}/file/{fileId}`, `POST /order/{id}/pdf`, `DELETE /shipment/{id}/file/{fileId}` |
| `printer` | `POST /printer`, `PUT /printer/order`, `DELETE /printer/{id}`, `PATCH /printer/{id}`, `PUT /printer/{id}` | | `printer` | `POST /printer`, `PUT /printer/order`, `DELETE /printer/{id}`, `PATCH /printer/{id}`, `PUT /printer/{id}` |
| `propertyname` | `POST /propertyname`, `PUT /propertyname/order`, `DELETE /propertyname/{id}`, `PATCH /propertyname/{id}` | | `propertyname` | `POST /propertyname`, `PUT /propertyname/order`, `DELETE /propertyname/{id}`, `PATCH /propertyname/{id}` |
| `quote` | `POST /quote/{id}/pdf` | | `quote` | `DELETE /quote/{id}/file/{fileId}`, `POST /quote/{id}/pdf` |
| `shipment` | `POST /shipment/{id}/pdf` | | `shipment` | `DELETE /shipment/{id}/file/{fileId}`, `POST /shipment/{id}/pdf` |
| `stock` | `POST /stock/adjust` | | `stock` | `POST /stock/adjust` |
| `vfs` | `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` | | `vfs` | `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` |
| `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` | | `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` |

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview (All) # Dash API Routes Overview (All)
Overview of all 242 API endpoints across all modules. Overview of all 246 API endpoints across all modules.
### Notation ### Notation
@@ -419,6 +419,9 @@ _none_
#### Update delivery — `PATCH /delivery/{id}` #### Update delivery — `PATCH /delivery/{id}`
- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T34[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` - Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T34[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `200 T6` - Returns: `200 T6`
#### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}` #### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}`
- Params: `id: int, fileId: int, query: download?: bool` - Params: `id: int, fileId: int, query: download?: bool`
- Returns: `200 str` - Returns: `200 str`
@@ -632,6 +635,9 @@ _none_
#### Update order — `PATCH /order/{id}` #### Update order — `PATCH /order/{id}`
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }`
- Returns: `200 T1` - Returns: `200 T1`
#### Delete order file attachment — `DELETE /order/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Get order attachment file content — `GET /order/{id}/file/{fileId}` #### Get order attachment file content — `GET /order/{id}/file/{fileId}`
- Params: `id: int, fileId: int, query: download?: bool` - Params: `id: int, fileId: int, query: download?: bool`
- Returns: `200 str` - Returns: `200 str`
@@ -770,6 +776,9 @@ _none_
#### Update quote — `PATCH /quote/{id}` #### Update quote — `PATCH /quote/{id}`
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }`
- Returns: `200 T8` - Returns: `200 T8`
#### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Get quote attachment file content — `GET /quote/{id}/file/{fileId}` #### Get quote attachment file content — `GET /quote/{id}/file/{fileId}`
- Params: `id: int, fileId: int, query: download?: bool` - Params: `id: int, fileId: int, query: download?: bool`
- Returns: `200 str` - Returns: `200 str`
@@ -800,6 +809,9 @@ _none_
#### Get shipment by ID — `GET /shipment/{id}` #### Get shipment by ID — `GET /shipment/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { shipment: T5 }` - Returns: `200 { shipment: T5 }`
#### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Get shipment attachment file content — `GET /shipment/{id}/file/{fileId}` #### Get shipment attachment file content — `GET /shipment/{id}/file/{fileId}`
- Params: `id: int, fileId: int, query: download?: bool` - Params: `id: int, fileId: int, query: download?: bool`
- Returns: `200 str` - Returns: `200 str`

View File

@@ -1,6 +1,6 @@
# Dash API Routes Overview # Dash API Routes Overview
Overview of all 167 API endpoints across all modules. Overview of all 171 API endpoints across all modules.
### Notation ### Notation
@@ -325,6 +325,9 @@ _none_
#### Update delivery — `PATCH /delivery/{id}` #### Update delivery — `PATCH /delivery/{id}`
- Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T28[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` - Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T28[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }`
- Returns: `200 T6` - Returns: `200 T6`
#### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf` #### Generate and attach delivery note PDF (goods receipt) — `POST /delivery/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T11` - Returns: `201 T11`
@@ -436,6 +439,9 @@ _none_
#### Update order — `PATCH /order/{id}` #### Update order — `PATCH /order/{id}`
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T28[] }` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T28[] }`
- Returns: `200 T1` - Returns: `200 T1`
#### Delete order file attachment — `DELETE /order/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### List payments of an order — `GET /order/{id}/payment` #### List payments of an order — `GET /order/{id}/payment`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { payments: T37[] }` - Returns: `200 { payments: T37[] }`
@@ -529,6 +535,9 @@ _none_
#### Update quote — `PATCH /quote/{id}` #### Update quote — `PATCH /quote/{id}`
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T28[] }` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T28[] }`
- Returns: `200 T8` - Returns: `200 T8`
#### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Generate and attach quote PDF — `POST /quote/{id}/pdf` #### Generate and attach quote PDF — `POST /quote/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T11` - Returns: `201 T11`
@@ -541,6 +550,9 @@ _none_
#### Get shipment by ID — `GET /shipment/{id}` #### Get shipment by ID — `GET /shipment/{id}`
- Params: `id: int` - Params: `id: int`
- Returns: `200 { shipment: T5 }` - Returns: `200 { shipment: T5 }`
#### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}`
- Params: `id: int, fileId: int`
- Returns: `204 void`
#### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf` #### Generate and attach shipment PDF (delivery note) — `POST /shipment/{id}/pdf`
- Params: `id: int` - Params: `id: int`
- Returns: `201 T11` - Returns: `201 T11`

View File

@@ -0,0 +1,38 @@
# `DELETE /delivery/{id}/file/{fileId}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete delivery file attachment
Removes a file attachment from the delivery and deletes the underlying file.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Delivery ID |
| `fileId` | yes | integer | File ID or attachment ID |
## Signals
On success, emits SSE signal: `delivery` (see [`GET /events`](../events.md)).
## Response `204`
Attachment deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid parameters or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Delivery or attachment not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,38 @@
# `DELETE /order/{id}/file/{fileId}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete order file attachment
Removes a file attachment from the order and deletes the underlying file.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Order ID |
| `fileId` | yes | integer | File ID or attachment ID |
## Signals
On success, emits SSE signal: `order` (see [`GET /events`](../events.md)).
## Response `204`
Attachment deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid parameters or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Order or attachment not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,38 @@
# `DELETE /quote/{id}/file/{fileId}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete quote file attachment
Removes a file attachment from the quote and deletes the underlying file.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Quote ID |
| `fileId` | yes | integer | File ID or attachment ID |
## Signals
On success, emits SSE signal: `quote` (see [`GET /events`](../events.md)).
## Response `204`
Attachment deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid parameters or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Quote or attachment not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,38 @@
# `DELETE /shipment/{id}/file/{fileId}`
Protected. Requires a valid session cookie (`credentials: 'include'`).
Delete shipment file attachment
Removes a file attachment from the shipment and deletes the underlying file.
## Path parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `id` | yes | integer | Shipment ID |
| `fileId` | yes | integer | File ID or attachment ID |
## Signals
On success, emits SSE signals: `shipment`, `order` (see [`GET /events`](../events.md)).
## Response `204`
Attachment deleted
_No content_
## Errors
| Status | Description |
|--------|-------------|
| 400 | Invalid parameters or no company selected — `{"error":"…"}` |
| 401 | Unauthorized — `{"error":"…"}` |
| 403 | Not authorized — `{"error":"…"}` |
| 404 | Shipment or attachment not found — `{"error":"…"}` |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -2,6 +2,7 @@ import multer from 'multer';
import * as db from '../../db/delivery.js'; import * as db from '../../db/delivery.js';
import { createAndAttachManifestPdf } from '../../services/manifestPdf.js'; import { createAndAttachManifestPdf } from '../../services/manifestPdf.js';
import { writeErrorLog } from '../../util/errorLog.js'; import { writeErrorLog } from '../../util/errorLog.js';
import { emit } from '../sse.js';
// #region Helpers // #region Helpers
@@ -995,5 +996,89 @@ export function register(app, basePath = '') {
}); });
// #endregion // #endregion
/**
* @openapi
* /delivery/{id}/file/{fileId}:
* delete:
* tags: [Delivery]
* summary: Delete delivery file attachment
* description: |
* Removes a file attachment from the delivery and deletes the underlying file.
* security:
* - cookieAuth: []
* x-signals: [delivery]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: Delivery ID
* - in: path
* name: fileId
* required: true
* schema:
* type: integer
* description: File ID or attachment ID
* responses:
* 204:
* description: Attachment deleted
* 400:
* description: Invalid parameters 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 or attachment not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
// #region DELETE /delivery/:id/file/:fileId
app.delete(`${basePath}/delivery/:id/file/:fileId`, 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 fileId = Number(req.params.fileId);
if (!Number.isInteger(fileId) || fileId <= 0) {
return res.status(400).json({ error: 'fileId is required' });
}
try {
await db.removeAttachment(req.user.id, id, fileId);
emit('delivery', { companyId: selected.companyId });
res.sendStatus(204);
} catch (error) {
const status = deliveryErrorStatus(error);
if (status === 500) console.error('Delete delivery attachment failed:', error);
res.status(status).json({ error: error.message });
}
});
// #endregion
} }

View File

@@ -1,5 +1,6 @@
import * as db from '../../db/order.js'; import * as db from '../../db/order.js';
import { createAndAttachManifestPdf } from '../../services/manifestPdf.js'; import { createAndAttachManifestPdf } from '../../services/manifestPdf.js';
import { emit } from '../sse.js';
// #region Helpers // #region Helpers
@@ -779,4 +780,88 @@ export function register(app, basePath = '') {
}); });
// #endregion // #endregion
/**
* @openapi
* /order/{id}/file/{fileId}:
* delete:
* tags: [Order]
* summary: Delete order file attachment
* description: |
* Removes a file attachment from the order and deletes the underlying file.
* security:
* - cookieAuth: []
* x-signals: [order]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: Order ID
* - in: path
* name: fileId
* required: true
* schema:
* type: integer
* description: File ID or attachment ID
* responses:
* 204:
* description: Attachment deleted
* 400:
* description: Invalid parameters 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: Order or attachment not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
// #region DELETE /order/:id/file/:fileId
app.delete(`${basePath}/order/:id/file/:fileId`, 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 fileId = Number(req.params.fileId);
if (!Number.isInteger(fileId) || fileId <= 0) {
return res.status(400).json({ error: 'fileId is required' });
}
try {
await db.removeAttachment(req.user.id, id, fileId);
emit('order', { companyId: selected.companyId });
res.sendStatus(204);
} catch (error) {
const status = orderErrorStatus(error);
if (status === 500) console.error('Delete order attachment failed:', error);
res.status(status).json({ error: error.message });
}
});
// #endregion
} }

View File

@@ -1,5 +1,6 @@
import * as db from '../../db/quote.js'; import * as db from '../../db/quote.js';
import { createAndAttachManifestPdf } from '../../services/manifestPdf.js'; import { createAndAttachManifestPdf } from '../../services/manifestPdf.js';
import { emit } from '../sse.js';
// #region Helpers // #region Helpers
@@ -758,4 +759,88 @@ export function register(app, basePath = '') {
}); });
// #endregion // #endregion
/**
* @openapi
* /quote/{id}/file/{fileId}:
* delete:
* tags: [Quote]
* summary: Delete quote file attachment
* description: |
* Removes a file attachment from the quote and deletes the underlying file.
* security:
* - cookieAuth: []
* x-signals: [quote]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: Quote ID
* - in: path
* name: fileId
* required: true
* schema:
* type: integer
* description: File ID or attachment ID
* responses:
* 204:
* description: Attachment deleted
* 400:
* description: Invalid parameters 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: Quote or attachment not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
// #region DELETE /quote/:id/file/:fileId
app.delete(`${basePath}/quote/:id/file/:fileId`, 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 fileId = Number(req.params.fileId);
if (!Number.isInteger(fileId) || fileId <= 0) {
return res.status(400).json({ error: 'fileId is required' });
}
try {
await db.removeAttachment(req.user.id, id, fileId);
emit('quote', { companyId: selected.companyId });
res.sendStatus(204);
} catch (error) {
const status = quoteErrorStatus(error);
if (status === 500) console.error('Delete quote attachment failed:', error);
res.status(status).json({ error: error.message });
}
});
// #endregion
} }

View File

@@ -591,5 +591,89 @@ export function register(app, basePath = '') {
}); });
// #endregion // #endregion
/**
* @openapi
* /shipment/{id}/file/{fileId}:
* delete:
* tags: [Shipment]
* summary: Delete shipment file attachment
* description: |
* Removes a file attachment from the shipment and deletes the underlying file.
* security:
* - cookieAuth: []
* x-signals: [shipment, order]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: Shipment ID
* - in: path
* name: fileId
* required: true
* schema:
* type: integer
* description: File ID or attachment ID
* responses:
* 204:
* description: Attachment deleted
* 400:
* description: Invalid parameters 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: Shipment or attachment not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
// #region DELETE /shipment/:id/file/:fileId
app.delete(`${basePath}/shipment/:id/file/:fileId`, 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 fileId = Number(req.params.fileId);
if (!Number.isInteger(fileId) || fileId <= 0) {
return res.status(400).json({ error: 'fileId is required' });
}
try {
await db.removeAttachment(req.user.id, id, fileId);
emitShipmentEvents(selected.companyId);
res.sendStatus(204);
} catch (error) {
const status = shipmentErrorStatus(error);
if (status === 500) console.error('Delete shipment attachment failed:', error);
res.status(status).json({ error: error.message });
}
});
// #endregion
} }

View File

@@ -40,3 +40,9 @@ export async function listAttachments(googleAuthId, deliveryId) {
return store.manifestattachment.listByManifestId(googleAuthId, deliveryId); return store.manifestattachment.listByManifestId(googleAuthId, deliveryId);
} }
export async function removeAttachment(googleAuthId, deliveryId, fileId) {
const store = await getStore();
return store.manifestattachment.remove(googleAuthId, deliveryId, fileId);
}

View File

@@ -173,4 +173,10 @@ export async function listAttachments(googleAuthId, orderId) {
return store.manifestattachment.listByManifestId(googleAuthId, orderId); return store.manifestattachment.listByManifestId(googleAuthId, orderId);
} }
export async function removeAttachment(googleAuthId, orderId, fileId) {
const store = await getStore();
return store.manifestattachment.remove(googleAuthId, orderId, fileId);
}

View File

@@ -103,3 +103,9 @@ export async function listAttachments(googleAuthId, quoteId) {
return store.manifestattachment.listByManifestId(googleAuthId, quoteId); return store.manifestattachment.listByManifestId(googleAuthId, quoteId);
} }
export async function removeAttachment(googleAuthId, quoteId, fileId) {
const store = await getStore();
return store.manifestattachment.remove(googleAuthId, quoteId, fileId);
}

View File

@@ -43,3 +43,9 @@ export async function listAttachments(googleAuthId, shipmentId) {
return store.manifestattachment.listByManifestId(googleAuthId, shipmentId); return store.manifestattachment.listByManifestId(googleAuthId, shipmentId);
} }
export async function removeAttachment(googleAuthId, shipmentId, fileId) {
const store = await getStore();
return store.manifestattachment.remove(googleAuthId, shipmentId, fileId);
}