import * as db from '../../db/retoure.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 retoureErrorStatus(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 === 'Retoure not found') return 404; if (error.message === 'Manifest not found') return 404; if (error.message === 'Entity not found') return 404; if (error.message === 'Article not found') return 404; if (error.message === 'No entity selected') return 400; if (error.message === 'Retoure number already exists') return 409; if (error.message === 'Invalid entityId') return 400; if (error.message === 'Invalid retoure') return 400; if (error.message === 'Invalid lines') return 400; if (error.message === 'Invalid offset') return 400; if (error.message === 'Invalid limit') return 400; if (error.message === 'Invalid seekMonths') return 400; if (error.message?.includes('parent article') || error.message?.includes('A parent article')) return 400; return 500; } // #endregion /** * Registers retoure (customer return) routes for the authenticated user's selected company. * @param {import('express').Application} app * @param {string} [basePath=''] */ export function register(app, basePath = '') { /** * @openapi * /retoure: * get: * tags: [Retoure] * summary: List retoures * description: | * Lists customer return manifests (`manifest` rows with `type = 'retoure'`) * for the authenticated user's selected company. * security: * - cookieAuth: [] * parameters: * - in: query * name: offset * schema: * type: integer * default: 0 * - in: query * name: limit * schema: * type: integer * default: 50 * - in: query * name: search * schema: * type: string * - in: query * name: onlyUnpaid * schema: * type: boolean * - in: query * name: onlyUndelivered * schema: * type: boolean * - in: query * name: entityId * schema: * type: integer * - in: query * name: seekMonths * schema: * type: integer * responses: * 200: * description: List of retoures * 400: * description: Validation error * 403: * description: Not authorized */ app.get(`${basePath}/retoure`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } const opts = {}; if (req.query.offset !== undefined) opts.offset = req.query.offset; if (req.query.limit !== undefined) opts.limit = req.query.limit; if (typeof req.query.search === 'string') opts.search = req.query.search; if (req.query.onlyUnpaid !== undefined) opts.onlyUnpaid = req.query.onlyUnpaid === 'true' || req.query.onlyUnpaid === '1'; if (req.query.onlyUndelivered !== undefined) opts.onlyUndelivered = req.query.onlyUndelivered === 'true' || req.query.onlyUndelivered === '1'; if (req.query.entityId !== undefined) opts.entityId = req.query.entityId; if (req.query.seekMonths !== undefined) opts.seekMonths = Number(req.query.seekMonths); try { const result = await db.list(req.user.id, opts); res.status(200).json(result); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('List retoures failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /retoure/{id}: * get: * tags: [Retoure] * summary: Get retoure by ID * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * responses: * 200: * description: Retoure details * 404: * description: Retoure not found */ app.get(`${basePath}/retoure/:id`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } const id = Number(req.params.id); if (!Number.isInteger(id) || id <= 0) { return res.status(400).json({ error: 'id must be an integer' }); } try { const retoure = await db.getById(req.user.id, id); res.status(200).json({ retoure }); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('Get retoure failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /retoure: * post: * tags: [Retoure] * summary: Create retoure * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [date] * properties: * number: * type: string * date: * type: string * entityId: * type: integer * externalNumber: * type: string * lines: * type: array * responses: * 201: * description: Retoure created */ app.post(`${basePath}/retoure`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } try { const retoure = await db.create(req.user.id, req.body); emit('retoure', { companyId: companyCheck.companyId }); res.status(201).json({ retoure }); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('Create retoure failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /retoure/{id}: * put: * tags: [Retoure] * summary: Update retoure * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * requestBody: * required: true * content: * application/json: * schema: * type: object * responses: * 200: * description: Retoure updated */ app.put(`${basePath}/retoure/:id`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } const id = Number(req.params.id); if (!Number.isInteger(id) || id <= 0) { return res.status(400).json({ error: 'id must be an integer' }); } try { const retoure = await db.update(req.user.id, id, req.body); emit('retoure', { companyId: companyCheck.companyId }); res.status(200).json({ retoure }); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('Update retoure failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /retoure/{id}: * delete: * tags: [Retoure] * summary: Delete retoure * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * responses: * 204: * description: Retoure deleted */ app.delete(`${basePath}/retoure/:id`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } const id = Number(req.params.id); if (!Number.isInteger(id) || id <= 0) { return res.status(400).json({ error: 'id must be an integer' }); } try { await db.remove(req.user.id, id); emit('retoure', { companyId: companyCheck.companyId }); res.sendStatus(204); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('Delete retoure failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /retoure/{id}/shipment: * post: * tags: [Retoure] * summary: Create shipment for retoure * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * responses: * 201: * description: Shipment created */ app.post(`${basePath}/retoure/:id/shipment`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } const id = Number(req.params.id); if (!Number.isInteger(id) || id <= 0) { return res.status(400).json({ error: 'id must be an integer' }); } try { const shipment = await db.createShipment(req.user.id, id, req.body); emit('retoure', { companyId: companyCheck.companyId }); emit('shipment', { companyId: companyCheck.companyId }); res.status(201).json({ shipment }); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('Create shipment for retoure failed:', error); res.status(status).json({ error: error.message }); } }); /** * @openapi * /retoure/rebuild-summaries: * post: * tags: [Retoure] * summary: Rebuild retoure summaries * security: * - cookieAuth: [] * responses: * 200: * description: Summaries rebuilt */ app.post(`${basePath}/retoure/rebuild-summaries`, async (req, res) => { const companyCheck = requireSelectedCompany(req); if (companyCheck.error) { return res.status(400).json({ error: companyCheck.error }); } try { const result = await db.rebuildSummaries(req.user.id); res.status(200).json(result); } catch (error) { const status = retoureErrorStatus(error); if (status === 500) console.error('Rebuild retoure summaries failed:', error); res.status(status).json({ error: error.message }); } }); }