From 2c15719b133de4d0e9582f9ca2e956a62da91fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 26 Aug 2026 16:30:36 -0300 Subject: [PATCH] Add production workstation management API --- backend/routes/productionOrderRoutes.js | 20 +++++- backend/services/productionOrderService.js | 81 +++++++++++++++++++++- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/backend/routes/productionOrderRoutes.js b/backend/routes/productionOrderRoutes.js index a03b5b1..ccebc86 100644 --- a/backend/routes/productionOrderRoutes.js +++ b/backend/routes/productionOrderRoutes.js @@ -1,6 +1,6 @@ const express = require('express'); 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(); @@ -35,12 +35,28 @@ router.get('/production-plans', verifyToken, async (req, res) => { router.get('/production-workstations', verifyToken, async (req, res) => { try { - res.json(await listProductionWorkstations()); + res.json(await listProductionWorkstations({ includeInactive: req.query?.includeInactive === 'true' })); } catch (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) => { try { res.json(await updateProductionPlan(req.params.id, req.body || {})); diff --git a/backend/services/productionOrderService.js b/backend/services/productionOrderService.js index f89c341..f725ef6 100644 --- a/backend/services/productionOrderService.js +++ b/backend/services/productionOrderService.js @@ -51,6 +51,14 @@ const formatDate = (value) => { 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 normalizedValue = typeof value === 'string' && value.includes(',') ? value.replace(/\./g, '').replace(',', '.') @@ -1826,14 +1834,79 @@ const updateProductionOrderExecution = async (id, payload = {}) => { 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(` SELECT id, code, name, color, sort_order, is_active FROM production_workstations - WHERE is_active = TRUE + ${includeInactive ? '' : 'WHERE is_active = TRUE'} 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 = {}) => { @@ -1864,6 +1937,7 @@ const addProductionOrderDefect = async (id, payload = {}) => { module.exports = { createProductionOrders, + createProductionWorkstation, deleteManualProductComposition, getProductComposition, importTinyProductCompositionExport, @@ -1877,6 +1951,7 @@ module.exports = { upsertTinyProductionOrderDetail, updateProductionPlan, updateProductionPlanStatus, + updateProductionWorkstation, addProductionOrderDefect, updateProductionOrderExecution, updateProductionOrderStatus