Add material transformation planning

This commit is contained in:
Cauê Faleiros
2026-08-27 10:44:49 -03:00
parent da6f3b2955
commit 442b23dc62
6 changed files with 325 additions and 3 deletions

View File

@@ -92,6 +92,24 @@ const mapFabricPlan = (row) => ({
updatedAt: row.updated_at
});
const mapMaterialTransformation = (row) => ({
id: row.id,
batchCode: row.batch_code || '',
stage: row.stage,
inputMaterial: row.input_material,
inputQuantityKg: Number(row.input_quantity_kg),
outputMaterial: row.output_material,
outputColor: row.output_color || '',
expectedLossPercent: Number(row.expected_loss_percent || 0),
expectedOutputKg: Number(row.expected_output_kg || 0),
supplier: row.supplier || '',
priority: row.priority || 'Normal',
status: row.status || 'planned',
notes: row.notes || '',
createdAt: row.created_at,
updatedAt: row.updated_at
});
const mapMaterialDetails = (row) => ({
materialKey: row.material_key,
displayName: row.display_name || '',
@@ -182,6 +200,19 @@ const listFabricPlans = async () => {
return result.rows.map(mapFabricPlan);
};
const listMaterialTransformations = async () => {
const result = await pool.query(`
SELECT id, batch_code, stage, input_material, input_quantity_kg, output_material, output_color,
expected_loss_percent, expected_output_kg, supplier, priority, status, notes, created_at, updated_at
FROM supply_material_transformations
ORDER BY
CASE status WHEN 'in_progress' THEN 1 WHEN 'planned' THEN 2 WHEN 'completed' THEN 3 ELSE 4 END,
CASE priority WHEN 'Crítico' THEN 1 WHEN 'Atenção' THEN 2 ELSE 3 END,
created_at DESC, id DESC
`);
return result.rows.map(mapMaterialTransformation);
};
const listMaterialDetails = async () => {
const result = await pool.query(`
SELECT material_key, display_name, preferred_supplier, notes, created_at, updated_at
@@ -1032,6 +1063,49 @@ const createFabricPlan = async (payload) => {
return mapFabricPlan(result.rows[0]);
};
const TRANSFORMATION_STAGES = new Set(['knitting', 'dyeing', 'finishing']);
const TRANSFORMATION_STATUSES = new Set(['planned', 'in_progress', 'completed', 'canceled']);
const createMaterialTransformation = async (payload = {}) => {
const stage = normalizeText(payload.stage).toLowerCase();
const inputMaterial = normalizeText(payload.inputMaterial);
const outputMaterial = normalizeText(payload.outputMaterial);
const inputQuantityKg = normalizeNumber(payload.inputQuantityKg);
const expectedLossPercent = normalizeNonNegativeNumber(payload.expectedLossPercent) ?? 0;
if (!TRANSFORMATION_STAGES.has(stage)) throw createValidationError('Etapa de transformação inválida.');
if (!inputMaterial || !outputMaterial) throw createValidationError('Material de entrada e saída são obrigatórios.');
if (!inputQuantityKg) throw createValidationError('Quantidade de entrada deve ser maior que zero.');
if (expectedLossPercent >= 100) throw createValidationError('A perda esperada deve ser menor que 100%.');
const expectedOutputKg = Number((inputQuantityKg * (1 - (expectedLossPercent / 100))).toFixed(4));
const result = await pool.query(`
INSERT INTO supply_material_transformations (
batch_code, stage, input_material, input_quantity_kg, output_material, output_color,
expected_loss_percent, expected_output_kg, supplier, priority, status, notes, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'planned', $11, CURRENT_TIMESTAMP)
RETURNING id, batch_code, stage, input_material, input_quantity_kg, output_material, output_color,
expected_loss_percent, expected_output_kg, supplier, priority, status, notes, created_at, updated_at
`, [
normalizeText(payload.batchCode) || null, stage, inputMaterial, inputQuantityKg, outputMaterial,
normalizeText(payload.outputColor) || null, expectedLossPercent, expectedOutputKg,
normalizeText(payload.supplier) || null, normalizeText(payload.priority) || 'Normal', normalizeText(payload.notes) || null
]);
return mapMaterialTransformation(result.rows[0]);
};
const updateMaterialTransformationStatus = async (id, status) => {
const normalizedStatus = normalizeText(status).toLowerCase();
if (!TRANSFORMATION_STATUSES.has(normalizedStatus)) throw createValidationError('Status da transformação inválido.');
const result = await pool.query(`
UPDATE supply_material_transformations
SET status = $2, updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING id, batch_code, stage, input_material, input_quantity_kg, output_material, output_color,
expected_loss_percent, expected_output_kg, supplier, priority, status, notes, created_at, updated_at
`, [id, normalizedStatus]);
if (!result.rows[0]) throw createValidationError('Transformação não encontrada.');
return mapMaterialTransformation(result.rows[0]);
};
const deleteFabricPlan = async (id) => {
await pool.query(`
UPDATE supply_fabric_plans
@@ -1243,6 +1317,7 @@ module.exports = {
approveReceipt,
consumeLotForProduction,
createFabricPlan,
createMaterialTransformation,
createPurchaseOrder,
createReceipt,
deleteFabricPlan,
@@ -1251,6 +1326,7 @@ module.exports = {
buildProjectPurchaseNeeds,
buildPurchaseNeeds,
listFabricPlans,
listMaterialTransformations,
listMaterialDetails,
listKnownSuppliers,
listPurchaseOrders,
@@ -1259,5 +1335,6 @@ module.exports = {
listPurchaseNeeds,
listReceipts,
saveMaterialDetails,
updateMaterialTransformationStatus,
updatePurchaseOrderStatus
};