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 === 'No company selected') return 400; 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. * The manifest `number` is allocated automatically via * `nextNumber('delivery')`; the user-supplied reference is stored as * `externalNumber`. Each line requires `sku` or `name`, `amount`, `net`, and * `taxrate` (rate). `pu` defaults to `1`; `rebate` defaults to `0`. * security: * - cookieAuth: [] * x-markdown: delivery/create.md * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [externalNumber, date, lines] * properties: * externalNumber: * type: string * description: User-supplied reference number * date: * type: string * format: date-time * supplierId: * type: integer * nullable: true * lines: * type: array * items: * $ref: '#/components/schemas/DeliveryLineInput' * example: * externalNumber: DHL-123 * date: '2026-08-10' * supplierId: 1 * lines: * - sku: '1' * name: 'Sample Article' * pu: 1 * amount: 10 * net: 99.5 * taxrate: 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); 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 { externalNumber, date, supplierId, lines } = req.body ?? {}; try { const delivery = await db.create(req.user.id, { externalNumber, 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. The manifest `number` is * auto-allocated and cannot be patched; only the user-supplied * `externalNumber` reference is editable. If `lines` is present, * replaces all lines; if omitted, leaves lines unchanged. * 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: * externalNumber: * type: string * nullable: true * description: User-supplied reference number (empty/null clears it) * date: * type: string * format: date-time * supplierId: * type: integer * nullable: true * lines: * type: array * items: * $ref: '#/components/schemas/DeliveryLineInput' * example: * externalNumber: 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, 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, 'externalNumber')) { patch.externalNumber = body.externalNumber; } 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, 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, 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 }); } }); }