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

@@ -2,6 +2,7 @@ import multer from 'multer';
import * as db from '../../db/delivery.js';
import { createAndAttachManifestPdf } from '../../services/manifestPdf.js';
import { writeErrorLog } from '../../util/errorLog.js';
import { emit } from '../sse.js';
// #region Helpers
@@ -995,5 +996,89 @@ export function register(app, basePath = '') {
});
// #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 { createAndAttachManifestPdf } from '../../services/manifestPdf.js';
import { emit } from '../sse.js';
// #region Helpers
@@ -779,4 +780,88 @@ export function register(app, basePath = '') {
});
// #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 { createAndAttachManifestPdf } from '../../services/manifestPdf.js';
import { emit } from '../sse.js';
// #region Helpers
@@ -758,4 +759,88 @@ export function register(app, basePath = '') {
});
// #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
/**
* @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);
}
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);
}
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);
}
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);
}
export async function removeAttachment(googleAuthId, shipmentId, fileId) {
const store = await getStore();
return store.manifestattachment.remove(googleAuthId, shipmentId, fileId);
}