360 lines
12 KiB
JavaScript
360 lines
12 KiB
JavaScript
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 });
|
|
}
|
|
});
|
|
}
|