import * as db from '../../db/warehouse.js'; import { emit } from '../sse.js'; // #region Helpers function requireSelectedCompany(req) { const companyId = req.user?.selectedCompanyId; if (companyId == null) { return { error: 'No company selected' }; } return { companyId }; } function parsePositiveInt(val, field = 'id') { const id = Number(val); if (!Number.isInteger(id) || id <= 0) { return { error: `${field} is required` }; } return { id }; } function warehouseErrorStatus(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 === 'Warehouse not found') return 404; if (error.message === 'Name required') return 400; if (error.message === 'Warehouse already exists') return 409; if (error.message === 'Warehouse in use in ledger') return 409; if (error.message === 'Invalid warehouse order') return 400; if (error.message === 'Invalid sort') return 400; return 500; } // #endregion /** * Registers warehouse routes for the authenticated user's selected company. * @param {import('express').Application} app * @param {string} [basePath=''] */ export function register(app, basePath = '') { /** * @openapi * /warehouse: * get: * tags: [Warehouse] * summary: List warehouses * description: Lists warehouses for the selected company, ordered by sort and name. * security: * - cookieAuth: [] * x-markdown: warehouse/list.md * responses: * 200: * description: List of warehouses * content: * application/json: * schema: * type: object * properties: * warehouses: * type: array * items: * $ref: '#/components/schemas/Warehouse' * 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' */ // #region GET /warehouse app.get(`${basePath}/warehouse`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } try { const warehouses = await db.list(req.user.id); res.status(200).json({ warehouses }); } catch (error) { const status = warehouseErrorStatus(error); if (status === 500) console.error('List warehouses failed:', error); res.status(status).json({ error: error.message }); } }); // #endregion /** * @openapi * /warehouse/order: * put: * tags: [Warehouse] * summary: Reorder warehouses * description: | * Reorders warehouses for the selected company. * `warehouseIds` must include all existing warehouse IDs exactly once. * security: * - cookieAuth: [] * x-signals: [warehouse] * x-markdown: warehouse/order.md * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [warehouseIds] * properties: * warehouseIds: * type: array * items: * type: integer * responses: * 200: * description: Applied warehouse order * content: * application/json: * schema: * type: object * properties: * warehouseIds: * type: array * items: * type: integer * 400: * description: Invalid warehouse order 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' */ // #region PUT /warehouse/order app.put(`${basePath}/warehouse/order`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const { warehouseIds } = req.body ?? {}; try { const result = await db.order(req.user.id, { warehouseIds }); emit('warehouse', { companyId: selected.companyId }); res.status(200).json(result); } catch (error) { const status = warehouseErrorStatus(error); if (status === 500) console.error('Order warehouses failed:', error); res.status(status).json({ error: error.message }); } }); // #endregion /** * @openapi * /warehouse/{id}: * get: * tags: [Warehouse] * summary: Get warehouse by ID * description: Retrieves a single warehouse for the authenticated user's selected company. * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: Warehouse ID * x-markdown: warehouse/get.md * responses: * 200: * description: Warehouse details * content: * application/json: * schema: * type: object * properties: * warehouse: * $ref: '#/components/schemas/Warehouse' * 400: * description: 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: Warehouse not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ // #region GET /warehouse/:id app.get(`${basePath}/warehouse/:id`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const parsedId = parsePositiveInt(req.params.id, 'id'); if (parsedId.error) { return res.status(400).json({ error: parsedId.error }); } try { const warehouse = await db.getById(req.user.id, parsedId.id); if (!warehouse) { return res.status(404).json({ error: 'Warehouse not found' }); } res.status(200).json({ warehouse }); } catch (error) { const status = warehouseErrorStatus(error); if (status === 500) console.error('Get warehouse failed:', error); res.status(status).json({ error: error.message }); } }); // #endregion /** * @openapi * /warehouse: * post: * tags: [Warehouse] * summary: Create warehouse * description: | * Creates a new warehouse for the selected company. * Sort order will be automatically appended if omitted. * security: * - cookieAuth: [] * x-signals: [warehouse] * x-markdown: warehouse/create.md * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/WarehouseCreateInput' * responses: * 201: * description: Created warehouse * content: * application/json: * schema: * type: object * properties: * warehouse: * $ref: '#/components/schemas/Warehouse' * 400: * description: Invalid input or missing fields * 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' * 409: * description: Warehouse already exists * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ // #region POST /warehouse app.post(`${basePath}/warehouse`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const { name, sort } = req.body ?? {}; try { const warehouse = await db.create(req.user.id, { name, sort }); emit('warehouse', { companyId: selected.companyId }); res.status(201).json({ warehouse }); } catch (error) { const status = warehouseErrorStatus(error); if (status === 500) console.error('Create warehouse failed:', error); res.status(status).json({ error: error.message }); } }); // #endregion async function handleUpdate(req, res) { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const parsedId = parsePositiveInt(req.params.id, 'id'); if (parsedId.error) { return res.status(400).json({ error: parsedId.error }); } const { name, sort } = req.body ?? {}; try { const warehouse = await db.update(req.user.id, parsedId.id, { name, sort }); emit('warehouse', { companyId: selected.companyId }); res.status(200).json({ warehouse }); } catch (error) { const status = warehouseErrorStatus(error); if (status === 500) console.error('Update warehouse failed:', error); res.status(status).json({ error: error.message }); } } /** * @openapi * /warehouse/{id}: * patch: * tags: [Warehouse] * summary: Update warehouse (partial) * description: Updates specified fields of an existing warehouse for the selected company. * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: Warehouse ID * x-markdown: warehouse/update.md * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/WarehouseUpdateInput' * responses: * 200: * description: Updated warehouse * content: * application/json: * schema: * type: object * properties: * warehouse: * $ref: '#/components/schemas/Warehouse' * 400: * description: Invalid input * 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: Warehouse not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 409: * description: Warehouse name conflict * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * put: * tags: [Warehouse] * summary: Update warehouse * description: Updates specified fields of an existing warehouse for the selected company. * security: * - cookieAuth: [] * x-signals: [warehouse] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: Warehouse ID * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/WarehouseUpdateInput' * responses: * 200: * description: Updated warehouse * content: * application/json: * schema: * type: object * properties: * warehouse: * $ref: '#/components/schemas/Warehouse' * 400: * description: Invalid input * 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: Warehouse not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 409: * description: Warehouse name conflict * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ // #region PATCH /warehouse/:id app.patch(`${basePath}/warehouse/:id`, handleUpdate); // #endregion // #region PUT /warehouse/:id app.put(`${basePath}/warehouse/:id`, handleUpdate); // #endregion /** * @openapi * /warehouse/{id}: * delete: * tags: [Warehouse] * summary: Delete warehouse * description: Deletes a warehouse if not in use in the ledger. * security: * - cookieAuth: [] * x-signals: [warehouse] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: Warehouse ID * x-markdown: warehouse/delete.md * responses: * 204: * description: Warehouse deleted * 400: * description: 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: Warehouse not found * content: * application/json: * schema: * $ref: '#/components/schemas/Error' * 409: * description: Warehouse in use in ledger * content: * application/json: * schema: * $ref: '#/components/schemas/Error' */ // #region DELETE /warehouse/:id app.delete(`${basePath}/warehouse/:id`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); } const parsedId = parsePositiveInt(req.params.id, 'id'); if (parsedId.error) { return res.status(400).json({ error: parsedId.error }); } try { await db.remove(req.user.id, parsedId.id); emit('warehouse', { companyId: selected.companyId }); res.sendStatus(204); } catch (error) { const status = warehouseErrorStatus(error); if (status === 500) console.error('Delete warehouse failed:', error); res.status(status).json({ error: error.message }); } }); // #endregion }