Add production defect and workstation foundation
This commit is contained in:
@@ -236,6 +236,43 @@ const initDB = async () => {
|
||||
);
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS production_workstations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code VARCHAR(60) NOT NULL UNIQUE,
|
||||
name VARCHAR(160) NOT NULL,
|
||||
color VARCHAR(40) NOT NULL DEFAULT '#38bdf8',
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
await pool.query(`
|
||||
INSERT INTO production_workstations (code, name, color, sort_order)
|
||||
VALUES
|
||||
('cutting', 'Corte', '#fbbf24', 10),
|
||||
('sewing', 'Costura', '#38bdf8', 20),
|
||||
('review', 'Revisão', '#a78bfa', 30),
|
||||
('sac', 'SAC', '#fb7185', 40)
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS production_order_defects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
production_order_id INTEGER NOT NULL REFERENCES production_orders(id) ON DELETE CASCADE,
|
||||
workstation_id INTEGER REFERENCES production_workstations(id) ON DELETE SET NULL,
|
||||
defect_type VARCHAR(160) NOT NULL,
|
||||
origin VARCHAR(160),
|
||||
quantity NUMERIC(14, 4) NOT NULL CHECK (quantity > 0),
|
||||
lot_code VARCHAR(120),
|
||||
notes TEXT,
|
||||
occurred_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS product_compositions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require('express');
|
||||
const { authenticateAPIKey, verifyToken } = require('../auth');
|
||||
const { createProductionOrders, importTinyProductCompositionExport, listProductionOrders, listProductionPlans, updateProductionOrderExecution, updateProductionOrderStatus, updateProductionPlan, updateProductionPlanStatus, upsertTinyProductionOrderDetail } = require('../services/productionOrderService');
|
||||
const { addProductionOrderDefect, createProductionOrders, importTinyProductCompositionExport, listProductionOrders, listProductionPlans, listProductionWorkstations, updateProductionOrderExecution, updateProductionOrderStatus, updateProductionPlan, updateProductionPlanStatus, upsertTinyProductionOrderDetail } = require('../services/productionOrderService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -33,6 +33,14 @@ router.get('/production-plans', verifyToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/production-workstations', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.json(await listProductionWorkstations());
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/production-plans/:id', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.json(await updateProductionPlan(req.params.id, req.body || {}));
|
||||
@@ -87,4 +95,12 @@ router.patch('/production-orders/:id/execution', verifyToken, async (req, res) =
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/production-orders/:id/defects', verifyToken, async (req, res) => {
|
||||
try {
|
||||
res.status(201).json(await addProductionOrderDefect(req.params.id, req.body || {}));
|
||||
} catch (error) {
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -267,6 +267,18 @@ const mapLotReservation = (reservation) => ({
|
||||
lotSupplier: reservation.lot_supplier || ''
|
||||
});
|
||||
|
||||
const mapDefect = (defect) => ({
|
||||
id: Number(defect.id),
|
||||
workstationId: defect.workstation_id === null ? null : Number(defect.workstation_id),
|
||||
workstationName: defect.workstation_name || '',
|
||||
defectType: defect.defect_type || '',
|
||||
origin: defect.origin || '',
|
||||
quantity: Number(defect.quantity || 0),
|
||||
lotCode: defect.lot_code || '',
|
||||
notes: defect.notes || '',
|
||||
occurredAt: defect.occurred_at || null
|
||||
});
|
||||
|
||||
const mapProductionOrderRow = (row) => {
|
||||
const status = normalizeStatus(row.status);
|
||||
|
||||
@@ -301,6 +313,7 @@ const mapProductionOrderRow = (row) => {
|
||||
rejectedQuantity: Number(row.rejected_quantity || 0),
|
||||
finishedQuantity: row.finished_quantity === null ? null : Number(row.finished_quantity),
|
||||
qualityNotes: row.quality_notes || '',
|
||||
defects: Array.isArray(row.defects) ? row.defects.map(mapDefect) : [],
|
||||
lotReservations: Array.isArray(row.lot_reservations) ? row.lot_reservations.map(mapLotReservation) : [],
|
||||
markers: Array.isArray(row.markers) ? row.markers.filter(Boolean) : [],
|
||||
components: Array.isArray(row.components) ? row.components.map(mapComponent) : [],
|
||||
@@ -502,7 +515,26 @@ const getOrderById = async (id, client = pool) => {
|
||||
WHERE step.production_order_id = po.id
|
||||
),
|
||||
'[]'::json
|
||||
) as steps
|
||||
) as steps,
|
||||
COALESCE(
|
||||
(
|
||||
SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
||||
'id', defect.id,
|
||||
'workstation_id', defect.workstation_id,
|
||||
'workstation_name', workstation.name,
|
||||
'defect_type', defect.defect_type,
|
||||
'origin', defect.origin,
|
||||
'quantity', defect.quantity,
|
||||
'lot_code', defect.lot_code,
|
||||
'notes', defect.notes,
|
||||
'occurred_at', defect.occurred_at
|
||||
) ORDER BY defect.occurred_at DESC, defect.id DESC)
|
||||
FROM production_order_defects defect
|
||||
LEFT JOIN production_workstations workstation ON workstation.id = defect.workstation_id
|
||||
WHERE defect.production_order_id = po.id
|
||||
),
|
||||
'[]'::json
|
||||
) as defects
|
||||
FROM production_orders po
|
||||
LEFT JOIN production_plans plan ON plan.id = po.production_plan_id
|
||||
WHERE po.id = $1;
|
||||
@@ -621,7 +653,26 @@ const baseProductionOrderSelect = `
|
||||
WHERE step.production_order_id = po.id
|
||||
),
|
||||
'[]'::json
|
||||
) as steps
|
||||
) as steps,
|
||||
COALESCE(
|
||||
(
|
||||
SELECT JSON_AGG(JSON_BUILD_OBJECT(
|
||||
'id', defect.id,
|
||||
'workstation_id', defect.workstation_id,
|
||||
'workstation_name', workstation.name,
|
||||
'defect_type', defect.defect_type,
|
||||
'origin', defect.origin,
|
||||
'quantity', defect.quantity,
|
||||
'lot_code', defect.lot_code,
|
||||
'notes', defect.notes,
|
||||
'occurred_at', defect.occurred_at
|
||||
) ORDER BY defect.occurred_at DESC, defect.id DESC)
|
||||
FROM production_order_defects defect
|
||||
LEFT JOIN production_workstations workstation ON workstation.id = defect.workstation_id
|
||||
WHERE defect.production_order_id = po.id
|
||||
),
|
||||
'[]'::json
|
||||
) as defects
|
||||
FROM production_orders po
|
||||
LEFT JOIN production_plans plan ON plan.id = po.production_plan_id
|
||||
`;
|
||||
@@ -1739,12 +1790,49 @@ const updateProductionOrderExecution = async (id, payload = {}) => {
|
||||
return getOrderById(id);
|
||||
};
|
||||
|
||||
const listProductionWorkstations = async () => {
|
||||
const result = await pool.query(`
|
||||
SELECT id, code, name, color, sort_order, is_active
|
||||
FROM production_workstations
|
||||
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) }));
|
||||
};
|
||||
|
||||
const addProductionOrderDefect = async (id, payload = {}) => {
|
||||
const defectType = normalizeText(payload.defectType);
|
||||
const quantity = normalizeQuantity(payload.quantity);
|
||||
const workstationId = normalizeInteger(payload.workstationId);
|
||||
if (!defectType || !quantity) {
|
||||
const error = new Error('Informe o tipo e a quantidade do defeito.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
if (workstationId) {
|
||||
const workstation = await pool.query('SELECT id FROM production_workstations WHERE id = $1 AND is_active = TRUE;', [workstationId]);
|
||||
if (!workstation.rowCount) {
|
||||
const error = new Error('Posto de trabalho inválido.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const result = await pool.query(`
|
||||
INSERT INTO production_order_defects (production_order_id, workstation_id, defect_type, origin, quantity, lot_code, notes)
|
||||
VALUES ($1, $2, $3, NULLIF($4, ''), $5, NULLIF($6, ''), NULLIF($7, ''))
|
||||
RETURNING id;
|
||||
`, [id, workstationId, defectType, normalizeText(payload.origin), quantity, normalizeText(payload.lotCode), normalizeText(payload.notes)]);
|
||||
if (!result.rowCount) throw new Error('Não foi possível registrar o defeito.');
|
||||
return getOrderById(id);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createProductionOrders,
|
||||
deleteManualProductComposition,
|
||||
getProductComposition,
|
||||
importTinyProductCompositionExport,
|
||||
listProductionPlans,
|
||||
listProductionWorkstations,
|
||||
listProductCompositions,
|
||||
listProductionOrders,
|
||||
normalizeStatus,
|
||||
@@ -1753,6 +1841,7 @@ module.exports = {
|
||||
upsertTinyProductionOrderDetail,
|
||||
updateProductionPlan,
|
||||
updateProductionPlanStatus,
|
||||
addProductionOrderDefect,
|
||||
updateProductionOrderExecution,
|
||||
updateProductionOrderStatus
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user