Add production workstation management API

This commit is contained in:
Cauê Faleiros
2026-08-26 16:30:36 -03:00
parent 7e65feac37
commit 2c15719b13
2 changed files with 96 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
const express = require('express'); const express = require('express');
const { authenticateAPIKey, verifyToken } = require('../auth'); const { authenticateAPIKey, verifyToken } = require('../auth');
const { addProductionOrderDefect, createProductionOrders, importTinyProductCompositionExport, listProductionOrders, listProductionPlans, listProductionWorkstations, updateProductionOrderExecution, updateProductionOrderStatus, updateProductionPlan, updateProductionPlanStatus, upsertTinyProductionOrderDetail } = require('../services/productionOrderService'); const { addProductionOrderDefect, createProductionOrders, createProductionWorkstation, importTinyProductCompositionExport, listProductionOrders, listProductionPlans, listProductionWorkstations, updateProductionOrderExecution, updateProductionOrderStatus, updateProductionPlan, updateProductionPlanStatus, updateProductionWorkstation, upsertTinyProductionOrderDetail } = require('../services/productionOrderService');
const router = express.Router(); const router = express.Router();
@@ -35,12 +35,28 @@ router.get('/production-plans', verifyToken, async (req, res) => {
router.get('/production-workstations', verifyToken, async (req, res) => { router.get('/production-workstations', verifyToken, async (req, res) => {
try { try {
res.json(await listProductionWorkstations()); res.json(await listProductionWorkstations({ includeInactive: req.query?.includeInactive === 'true' }));
} catch (error) { } catch (error) {
res.status(500).json({ error: 'Internal Server Error' }); res.status(500).json({ error: 'Internal Server Error' });
} }
}); });
router.post('/production-workstations', verifyToken, async (req, res) => {
try {
res.status(201).json(await createProductionWorkstation(req.body || {}));
} catch (error) {
res.status(error.statusCode || 500).json({ error: error.message || 'Internal Server Error' });
}
});
router.put('/production-workstations/:id', verifyToken, async (req, res) => {
try {
res.json(await updateProductionWorkstation(req.params.id, req.body || {}));
} catch (error) {
res.status(error.statusCode || 500).json({ error: error.message || 'Internal Server Error' });
}
});
router.put('/production-plans/:id', verifyToken, async (req, res) => { router.put('/production-plans/:id', verifyToken, async (req, res) => {
try { try {
res.json(await updateProductionPlan(req.params.id, req.body || {})); res.json(await updateProductionPlan(req.params.id, req.body || {}));

View File

@@ -51,6 +51,14 @@ const formatDate = (value) => {
const normalizeText = (value) => String(value || '').trim(); const normalizeText = (value) => String(value || '').trim();
const normalizeWorkstationCode = (value) => normalizeText(value)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 60);
const normalizeQuantity = (value) => { const normalizeQuantity = (value) => {
const normalizedValue = typeof value === 'string' && value.includes(',') const normalizedValue = typeof value === 'string' && value.includes(',')
? value.replace(/\./g, '').replace(',', '.') ? value.replace(/\./g, '').replace(',', '.')
@@ -1826,14 +1834,79 @@ const updateProductionOrderExecution = async (id, payload = {}) => {
return getOrderById(id); return getOrderById(id);
}; };
const listProductionWorkstations = async () => { const mapWorkstation = (row) => ({
id: Number(row.id),
code: row.code || '',
name: row.name || '',
color: row.color || '#38bdf8',
sortOrder: Number(row.sort_order || 0),
isActive: Boolean(row.is_active)
});
const listProductionWorkstations = async ({ includeInactive = false } = {}) => {
const result = await pool.query(` const result = await pool.query(`
SELECT id, code, name, color, sort_order, is_active SELECT id, code, name, color, sort_order, is_active
FROM production_workstations FROM production_workstations
WHERE is_active = TRUE ${includeInactive ? '' : 'WHERE is_active = TRUE'}
ORDER BY sort_order, name; ORDER BY sort_order, name;
`); `);
return result.rows.map(row => ({ id: Number(row.id), code: row.code, name: row.name, color: row.color, sortOrder: Number(row.sort_order), isActive: Boolean(row.is_active) })); return result.rows.map(mapWorkstation);
};
const normalizeWorkstationPayload = (payload = {}) => {
const name = normalizeText(payload.name).slice(0, 160);
const code = normalizeWorkstationCode(payload.code || name);
const color = /^#[0-9a-f]{6}$/i.test(normalizeText(payload.color)) ? normalizeText(payload.color) : '#38bdf8';
const sortOrder = Number.isFinite(Number(payload.sortOrder)) ? Math.max(0, Math.trunc(Number(payload.sortOrder))) : 0;
const isActive = payload.isActive !== false;
if (!name || !code) {
const error = new Error('Informe o nome do posto de trabalho.');
error.statusCode = 400;
throw error;
}
return { name, code, color, sortOrder, isActive };
};
const createProductionWorkstation = async (payload = {}) => {
const workstation = normalizeWorkstationPayload(payload);
try {
const result = await pool.query(`
INSERT INTO production_workstations (code, name, color, sort_order, is_active, updated_at)
VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
RETURNING id, code, name, color, sort_order, is_active;
`, [workstation.code, workstation.name, workstation.color, workstation.sortOrder, workstation.isActive]);
return mapWorkstation(result.rows[0]);
} catch (error) {
if (error?.code === '23505') {
error.message = 'Já existe um posto de trabalho com este código.';
error.statusCode = 409;
}
throw error;
}
};
const updateProductionWorkstation = async (id, payload = {}) => {
const workstation = normalizeWorkstationPayload(payload);
try {
const result = await pool.query(`
UPDATE production_workstations
SET code = $1, name = $2, color = $3, sort_order = $4, is_active = $5, updated_at = CURRENT_TIMESTAMP
WHERE id = $6
RETURNING id, code, name, color, sort_order, is_active;
`, [workstation.code, workstation.name, workstation.color, workstation.sortOrder, workstation.isActive, id]);
if (!result.rowCount) {
const error = new Error('Posto de trabalho não encontrado.');
error.statusCode = 404;
throw error;
}
return mapWorkstation(result.rows[0]);
} catch (error) {
if (error?.code === '23505') {
error.message = 'Já existe um posto de trabalho com este código.';
error.statusCode = 409;
}
throw error;
}
}; };
const addProductionOrderDefect = async (id, payload = {}) => { const addProductionOrderDefect = async (id, payload = {}) => {
@@ -1864,6 +1937,7 @@ const addProductionOrderDefect = async (id, payload = {}) => {
module.exports = { module.exports = {
createProductionOrders, createProductionOrders,
createProductionWorkstation,
deleteManualProductComposition, deleteManualProductComposition,
getProductComposition, getProductComposition,
importTinyProductCompositionExport, importTinyProductCompositionExport,
@@ -1877,6 +1951,7 @@ module.exports = {
upsertTinyProductionOrderDetail, upsertTinyProductionOrderDetail,
updateProductionPlan, updateProductionPlan,
updateProductionPlanStatus, updateProductionPlanStatus,
updateProductionWorkstation,
addProductionOrderDefect, addProductionOrderDefect,
updateProductionOrderExecution, updateProductionOrderExecution,
updateProductionOrderStatus updateProductionOrderStatus