Add supplies workflow demonstration seed

This commit is contained in:
Cauê Faleiros
2026-08-27 19:01:32 -03:00
parent e62793273b
commit d623688263
2 changed files with 544 additions and 0 deletions

View File

@@ -0,0 +1,543 @@
const { pool, initDB } = require('../db');
// This seed is deliberately isolated from Tiny/Olist data. It only creates or
// refreshes records with LOCAL-DEMO / DEMONSTRAÇÃO identifiers, so it is safe
// to run again in a local environment when the operational screens need data.
const DEMO = 'DEMONSTRAÇÃO';
const normalizeMaterialKey = (value) => String(value || '')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim()
.replace(/\s+/g, ' ');
const today = new Date().toISOString().slice(0, 10);
const offsetDate = (days) => {
const date = new Date();
date.setDate(date.getDate() + days);
return date.toISOString().slice(0, 10);
};
const getCategoryId = async (client, name) => {
const result = await client.query('SELECT id FROM catalog_categories WHERE name = $1', [name]);
return result.rows[0]?.id || null;
};
const upsertCatalogProduct = async (client, product) => {
const categoryId = await getCategoryId(client, product.category);
const result = await client.query(`
INSERT INTO catalog_products (type, sku, name, category_id, color, sizes, notes, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP)
ON CONFLICT (sku) DO UPDATE
SET type = EXCLUDED.type,
name = EXCLUDED.name,
category_id = EXCLUDED.category_id,
color = EXCLUDED.color,
sizes = EXCLUDED.sizes,
notes = EXCLUDED.notes,
updated_at = CURRENT_TIMESTAMP
RETURNING id;
`, [
product.type,
product.sku,
product.name,
categoryId,
product.color || null,
product.sizes || [],
`${DEMO} — registro local para visualizar o fluxo de Suprimentos.`
]);
return Number(result.rows[0].id);
};
const findCatalogProduct = async (client, sku) => {
const result = await client.query('SELECT id FROM catalog_products WHERE sku = $1', [sku]);
return result.rows[0] ? Number(result.rows[0].id) : null;
};
const upsertConsumptionReference = async (client, reference) => {
const existing = await client.query(`
SELECT id
FROM consumption_references
WHERE product_id = $1
AND COALESCE(material_product_id, 0) = COALESCE($2, 0)
AND COALESCE(color, '') = COALESCE($3, '')
AND COALESCE(source, 'manual') = 'demonstration'
LIMIT 1;
`, [reference.productId, reference.materialProductId, reference.color || null]);
const values = [
reference.productId,
reference.materialProductId,
reference.color || null,
reference.generalYield || null,
reference.consumptionQuantity || null,
reference.consumptionUnit || null
];
if (existing.rows[0]) {
await client.query(`
UPDATE consumption_references
SET general_yield = $4,
consumption_quantity = $5,
consumption_unit = $6,
updated_at = CURRENT_TIMESTAMP
WHERE id = $7;
`, [...values, existing.rows[0].id]);
return;
}
await client.query(`
INSERT INTO consumption_references (
product_id, material_product_id, color, general_yield,
consumption_quantity, consumption_unit, source
) VALUES ($1, $2, $3, $4, $5, $6, 'demonstration');
`, values);
};
const upsertPurchaseOrder = async (client, order) => {
const result = await client.query(`
INSERT INTO supply_purchase_orders (number, supplier, status, expected_date, notes, updated_at)
VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
ON CONFLICT (number) DO UPDATE
SET supplier = EXCLUDED.supplier,
status = EXCLUDED.status,
expected_date = EXCLUDED.expected_date,
notes = EXCLUDED.notes,
updated_at = CURRENT_TIMESTAMP
RETURNING id;
`, [order.number, order.supplier, order.status, order.expectedDate, order.notes]);
const id = Number(result.rows[0].id);
for (const item of order.items) {
await client.query(`
INSERT INTO supply_purchase_order_items (purchase_order_id, material, quantity, unit, unit_cost)
SELECT $1, $2, $3, $4, $5
WHERE NOT EXISTS (
SELECT 1 FROM supply_purchase_order_items
WHERE purchase_order_id = $1 AND material = $2
);
`, [id, item.material, item.quantity, item.unit, item.unitCost]);
}
return id;
};
const upsertReceipt = async (client, receipt) => {
const existing = await client.query('SELECT id FROM supply_receipts WHERE invoice = $1 LIMIT 1', [receipt.invoice]);
if (existing.rows[0]) {
await client.query(`
UPDATE supply_receipts
SET category = $1, product = $2, quantity = $3, unit = $4,
supplier = $5, notes = $6, status = $7, purchase_order_id = $8,
approved_at = CASE WHEN $7 = 'approved' THEN COALESCE(approved_at, CURRENT_TIMESTAMP) ELSE NULL END,
updated_at = CURRENT_TIMESTAMP
WHERE id = $9;
`, [receipt.category, receipt.product, receipt.quantity, receipt.unit, receipt.supplier, receipt.notes, receipt.status, receipt.purchaseOrderId, existing.rows[0].id]);
return Number(existing.rows[0].id);
}
const result = await client.query(`
INSERT INTO supply_receipts (
category, product, quantity, unit, supplier, invoice, notes, status,
purchase_order_id, approved_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9,
CASE WHEN $8 = 'approved' THEN CURRENT_TIMESTAMP ELSE NULL END)
RETURNING id;
`, [receipt.category, receipt.product, receipt.quantity, receipt.unit, receipt.supplier, receipt.invoice, receipt.notes, receipt.status, receipt.purchaseOrderId]);
return Number(result.rows[0].id);
};
const ensureLotAndMovement = async (client, receiptId) => {
const receipt = await client.query(`
SELECT id, category, product, quantity, unit, supplier, invoice
FROM supply_receipts
WHERE id = $1 AND status = 'approved';
`, [receiptId]);
if (!receipt.rows[0]) return null;
const row = receipt.rows[0];
const lot = await client.query(`
INSERT INTO supply_stock_lots (receipt_id, category, product, quantity, unit, supplier, invoice, status)
SELECT $1, $2, $3, $4, $5, $6, $7, 'active'
WHERE NOT EXISTS (SELECT 1 FROM supply_stock_lots WHERE receipt_id = $1)
RETURNING id;
`, [row.id, row.category, row.product, row.quantity, row.unit, row.supplier, row.invoice]);
const lotId = lot.rows[0]?.id || (await client.query('SELECT id FROM supply_stock_lots WHERE receipt_id = $1', [row.id])).rows[0]?.id;
if (!lotId) return null;
await client.query(`
INSERT INTO supply_movements (receipt_id, lot_id, type, category, product, quantity, unit, reason)
SELECT $1, $2, 'receipt', $3, $4, $5, $6, $7
WHERE NOT EXISTS (
SELECT 1 FROM supply_movements
WHERE receipt_id = $1 AND type = 'receipt'
);
`, [row.id, lotId, row.category, row.product, row.quantity, row.unit, `${DEMO} — entrada aprovada para visualização.`]);
return Number(lotId);
};
const upsertProductionPlan = async (client, plan, responsibleUserId) => {
const result = await client.query(`
INSERT INTO production_plans (
number, name, status, contractor, responsible_user_id, responsible_name,
planned_start_date, expected_date, notes, source
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'cut_plan')
ON CONFLICT (number) DO UPDATE
SET name = EXCLUDED.name,
status = EXCLUDED.status,
contractor = EXCLUDED.contractor,
responsible_user_id = EXCLUDED.responsible_user_id,
responsible_name = EXCLUDED.responsible_name,
planned_start_date = EXCLUDED.planned_start_date,
expected_date = EXCLUDED.expected_date,
notes = EXCLUDED.notes,
updated_at = CURRENT_TIMESTAMP
RETURNING id;
`, [
plan.number, plan.name, plan.status, plan.contractor, responsibleUserId,
plan.responsibleName, plan.plannedStartDate, plan.expectedDate, plan.notes
]);
return Number(result.rows[0].id);
};
const upsertProductionOrder = async (client, order) => {
const existing = await client.query('SELECT id FROM production_orders WHERE number = $1 LIMIT 1', [order.number]);
const values = [
order.status, order.planId, order.contractor, order.orderReference, order.issueDate,
order.expectedDate, order.productSku, order.productDescription, order.quantity, order.unit,
order.notes, order.supplier, order.lotCode, order.rollQuantity, order.fabricKg,
order.ribKg, order.yieldPiecesPerKg, order.actualCutQuantity, order.rejectedQuantity,
order.finishedQuantity, order.qualityNotes, order.cutLayers, order.markerReference
];
if (existing.rows[0]) {
await client.query(`
UPDATE production_orders
SET status = $1, production_plan_id = $2, contractor = $3, order_reference = $4,
issue_date = $5, expected_date = $6, product_sku = $7, product_description = $8,
quantity = $9, unit = $10, notes = $11, supplier = $12, lot_code = $13,
roll_quantity = $14, fabric_kg = $15, rib_kg = $16, yield_pieces_per_kg = $17,
actual_cut_quantity = $18, rejected_quantity = $19, finished_quantity = $20,
quality_notes = $21, cut_layers = $22, marker_reference = $23,
updated_at = CURRENT_TIMESTAMP
WHERE id = $24;
`, [...values, existing.rows[0].id]);
return Number(existing.rows[0].id);
}
const result = await client.query(`
INSERT INTO production_orders (
number, status, production_plan_id, contractor, order_reference, issue_date,
expected_date, product_sku, product_description, quantity, unit, notes, supplier,
lot_code, roll_quantity, fabric_kg, rib_kg, yield_pieces_per_kg,
actual_cut_quantity, rejected_quantity, finished_quantity, quality_notes,
cut_layers, marker_reference
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13,
$14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
RETURNING id;
`, [order.number, ...values]);
return Number(result.rows[0].id);
};
const ensureOrderDetails = async (client, orderId, details) => {
for (const marker of details.markers) {
await client.query(`
INSERT INTO production_order_markers (production_order_id, label, color)
SELECT $1, $2, $3
WHERE NOT EXISTS (
SELECT 1 FROM production_order_markers WHERE production_order_id = $1 AND label = $2
);
`, [orderId, marker.label, marker.color]);
}
for (const component of details.components) {
await client.query(`
INSERT INTO production_order_components (production_order_id, component_sku, component_name, quantity_per_unit, total_quantity, unit)
SELECT $1, $2, $3, $4, $5, $6
WHERE NOT EXISTS (
SELECT 1 FROM production_order_components
WHERE production_order_id = $1 AND component_name = $3
);
`, [orderId, component.sku, component.name, component.quantityPerUnit, component.totalQuantity, component.unit]);
}
for (const step of details.steps) {
await client.query(`
INSERT INTO production_order_steps (production_order_id, step_number, name, start_date, end_date, status, color)
SELECT $1, $2, $3, $4, $5, $6, $7
WHERE NOT EXISTS (
SELECT 1 FROM production_order_steps
WHERE production_order_id = $1 AND step_number = $2
);
`, [orderId, step.number, step.name, step.startDate, step.endDate, step.status, step.color]);
}
};
async function main() {
await initDB();
const client = await pool.connect();
try {
await client.query('BEGIN');
const materials = {
fabric: await upsertCatalogProduct(client, {
type: 'raw_material', sku: 'LOCAL-DEMO-MAT-MALHA-PRETA', category: 'Malha',
name: 'DEMO-MALHA-300 MALHA 30/1 COR PRETO', color: 'Preto'
}),
rib: await upsertCatalogProduct(client, {
type: 'raw_material', sku: 'LOCAL-DEMO-MAT-RIBANA-PRETA', category: 'Aviamentos',
name: 'DEMO-0201 RIBANA 2X1 COR PRETO', color: 'Preto'
}),
hoodieFabric: await upsertCatalogProduct(client, {
type: 'raw_material', sku: 'LOCAL-DEMO-MAT-MOLETOM-GRAFITE', category: 'Malha',
name: 'DEMO-MOLETOM 3 CABOS COR GRAFITE', color: 'Grafite'
}),
labelM: await upsertCatalogProduct(client, {
type: 'raw_material', sku: 'LOCAL-DEMO-MAT-ETIQUETA-M', category: 'Embalagens',
name: 'DEMO-ETIQUETA TAMANHO M', sizes: ['M']
}),
labelG: await upsertCatalogProduct(client, {
type: 'raw_material', sku: 'LOCAL-DEMO-MAT-ETIQUETA-G', category: 'Embalagens',
name: 'DEMO-ETIQUETA TAMANHO G', sizes: ['G']
})
};
const finishedProducts = {
teeM: await findCatalogProduct(client, 'LOCAL-DEMO-UX-TEE-M'),
teeG: await findCatalogProduct(client, 'LOCAL-DEMO-UX-TEE-G'),
hoodieM: await findCatalogProduct(client, 'LOCAL-DEMO-UX-HOODIE-M')
};
if (!Object.values(finishedProducts).every(Boolean)) {
throw new Error('Os produtos locais de demonstração esperados não foram encontrados.');
}
await Promise.all([
upsertConsumptionReference(client, { productId: finishedProducts.teeM, materialProductId: materials.fabric, color: 'Preto', consumptionQuantity: 0.185, consumptionUnit: 'kg' }),
upsertConsumptionReference(client, { productId: finishedProducts.teeM, materialProductId: materials.rib, color: 'Preto', consumptionQuantity: 0.01, consumptionUnit: 'kg' }),
upsertConsumptionReference(client, { productId: finishedProducts.teeM, materialProductId: materials.labelM, consumptionQuantity: 1, consumptionUnit: 'un.' }),
upsertConsumptionReference(client, { productId: finishedProducts.teeG, materialProductId: materials.fabric, color: 'Preto', consumptionQuantity: 0.2, consumptionUnit: 'kg' }),
upsertConsumptionReference(client, { productId: finishedProducts.teeG, materialProductId: materials.rib, color: 'Preto', consumptionQuantity: 0.011, consumptionUnit: 'kg' }),
upsertConsumptionReference(client, { productId: finishedProducts.teeG, materialProductId: materials.labelG, consumptionQuantity: 1, consumptionUnit: 'un.' }),
upsertConsumptionReference(client, { productId: finishedProducts.hoodieM, materialProductId: materials.hoodieFabric, color: 'Grafite', consumptionQuantity: 0.75, consumptionUnit: 'kg' }),
upsertConsumptionReference(client, { productId: finishedProducts.hoodieM, materialProductId: materials.rib, color: 'Grafite', consumptionQuantity: 0.045, consumptionUnit: 'kg' }),
upsertConsumptionReference(client, { productId: finishedProducts.hoodieM, materialProductId: materials.labelM, consumptionQuantity: 1, consumptionUnit: 'un.' })
]);
const supplier = `${DEMO} Têxteis`;
const materialDetails = [
['DEMO-MALHA-300 MALHA 30/1 COR PRETO', supplier, 'Malha reservada para a semana de demonstração.'],
['DEMO-0201 RIBANA 2X1 COR PRETO', supplier, 'Aviamento de gola e acabamento.'],
['DEMO-MOLETOM 3 CABOS COR GRAFITE', `${DEMO} Malharia`, 'Material para o plano de moletom.']
];
for (const [material, preferredSupplier, notes] of materialDetails) {
await client.query(`
INSERT INTO supply_material_details (material_key, display_name, preferred_supplier, notes, updated_at)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
ON CONFLICT (material_key) DO UPDATE
SET display_name = EXCLUDED.display_name,
preferred_supplier = EXCLUDED.preferred_supplier,
notes = EXCLUDED.notes,
updated_at = CURRENT_TIMESTAMP;
`, [normalizeMaterialKey(material), material, preferredSupplier, notes]);
}
const fabricPlans = [
['DEMO-MALHA-300 MALHA 30/1 COR PRETO', 'Preto', 120, supplier, 'Crítico'],
['DEMO-0201 RIBANA 2X1 COR PRETO', 'Preto', 24, supplier, 'Atenção'],
['DEMO-MOLETOM 3 CABOS COR GRAFITE', 'Grafite', 90, `${DEMO} Malharia`, 'Normal']
];
for (const [material, color, quantity, planSupplier, priority] of fabricPlans) {
await client.query(`
INSERT INTO supply_fabric_plans (material, color, quantity_kg, supplier, priority, status, updated_at)
SELECT $1, $2, $3, $4, $5, 'active', CURRENT_TIMESTAMP
WHERE NOT EXISTS (
SELECT 1 FROM supply_fabric_plans
WHERE material = $1 AND COALESCE(color, '') = COALESCE($2, '') AND status = 'active'
);
`, [material, color, quantity, planSupplier, priority]);
}
const poApproved = await upsertPurchaseOrder(client, {
number: 'OC-DEMO-0001', supplier, status: 'approved', expectedDate: offsetDate(-2),
notes: `${DEMO} — compra aprovada; o recebimento está disponível para conferência.`,
items: [{ material: 'DEMO-MALHA-300 MALHA 30/1 COR PRETO', quantity: 120, unit: 'kg', unitCost: 29.9 }]
});
const poSent = await upsertPurchaseOrder(client, {
number: 'OC-DEMO-0002', supplier, status: 'sent', expectedDate: offsetDate(4),
notes: `${DEMO} — pedido enviado ao fornecedor e aguardando chegada.`,
items: [{ material: 'DEMO-0201 RIBANA 2X1 COR PRETO', quantity: 24, unit: 'kg', unitCost: 34.5 }]
});
const poFinancial = await upsertPurchaseOrder(client, {
number: 'OC-DEMO-0003', supplier: `${DEMO} Etiquetas`, status: 'pending_financial', expectedDate: offsetDate(6),
notes: `${DEMO} — aguardando aprovação financeira; não libera saldo físico.`,
items: [{ material: 'DEMO-ETIQUETA TAMANHO M', quantity: 500, unit: 'un.', unitCost: 0.28 }]
});
const receiptIds = [];
receiptIds.push(await upsertReceipt(client, {
category: 'Malha', product: 'DEMO-MALHA-300 MALHA 30/1 COR PRETO', quantity: 120, unit: 'kg',
supplier, invoice: 'DEMO-NF-004', status: 'approved', purchaseOrderId: poApproved,
notes: `${DEMO} — recebimento conferido e saldo liberado.`
}));
receiptIds.push(await upsertReceipt(client, {
category: 'Aviamentos', product: 'DEMO-0201 RIBANA 2X1 COR PRETO', quantity: 24, unit: 'kg',
supplier, invoice: 'DEMO-NF-005', status: 'pending', purchaseOrderId: poSent,
notes: `${DEMO} — aguardando conferência física e aprovação da compra.`
}));
receiptIds.push(await upsertReceipt(client, {
category: 'Embalagens', product: 'DEMO-ETIQUETA TAMANHO M', quantity: 500, unit: 'un.',
supplier: `${DEMO} Etiquetas`, invoice: 'DEMO-NF-006', status: 'pending', purchaseOrderId: poFinancial,
notes: `${DEMO} — saldo bloqueado até a aprovação financeira da OC.`
}));
const lotIds = [];
for (const receiptId of receiptIds) {
const lotId = await ensureLotAndMovement(client, receiptId);
if (lotId) lotIds.push(lotId);
}
const transformations = [
['DEMO-LT-2408-01', 'tingimento', 'DEMO-MALHA-300 CRUA', 260, 'DEMO-MALHA-300 MALHA 30/1', 'Preto', 2, 254.8, `${DEMO} Tinturaria`, 'Atenção', 'in_progress', `${DEMO} — lote em tingimento.`],
['DEMO-LT-2408-02', 'acabamento', 'DEMO-MOLETOM 3 CABOS', 96, 'DEMO-MOLETOM 3 CABOS', 'Grafite', 1.5, 94.56, `${DEMO} Malharia`, 'Normal', 'planned', `${DEMO} — acabamento programado.`],
['DEMO-LT-2408-00', 'malharia', 'DEMO-FIO 30/1 PRETO', 180, 'DEMO-MALHA-300 MALHA 30/1', 'Preto', 3, 174.6, `${DEMO} Malharia`, 'Normal', 'completed', `${DEMO} — lote concluído para rastreabilidade.`]
];
for (const transformation of transformations) {
await client.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
) SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, CURRENT_TIMESTAMP
WHERE NOT EXISTS (
SELECT 1 FROM supply_material_transformations
WHERE batch_code = $1 AND stage = $2
);
`, transformation);
}
const user = await client.query(`
SELECT id, name FROM app_users WHERE is_active = TRUE ORDER BY id LIMIT 1;
`);
const responsibleUserId = user.rows[0]?.id || null;
const responsibleName = user.rows[0]?.name || 'Operação Graphs';
const planCut = await upsertProductionPlan(client, {
number: 'PP-DEMO-0001', name: `${DEMO} | Corte preto — semana operacional`, status: 'cutting',
contractor: `${DEMO} Confecção`, responsibleName, plannedStartDate: offsetDate(-1), expectedDate: offsetDate(3),
notes: `${DEMO} — plano liberado com lote e risco definidos.`
}, responsibleUserId);
const planHoodie = await upsertProductionPlan(client, {
number: 'PP-DEMO-0002', name: `${DEMO} | Moletom grafite — reposição`, status: 'open',
contractor: `${DEMO} Confecção`, responsibleName, plannedStartDate: offsetDate(3), expectedDate: offsetDate(8),
notes: `${DEMO} — aguardando liberação da etapa de malha.`
}, responsibleUserId);
await client.query(`
INSERT INTO production_plan_reviews (production_plan_id, decision, checklist, notes, reviewed_by, reviewed_at)
VALUES ($1, 'approved', $2::jsonb, $3, $4, CURRENT_TIMESTAMP)
ON CONFLICT (production_plan_id) DO UPDATE
SET decision = EXCLUDED.decision,
checklist = EXCLUDED.checklist,
notes = EXCLUDED.notes,
reviewed_by = EXCLUDED.reviewed_by,
reviewed_at = EXCLUDED.reviewed_at,
updated_at = CURRENT_TIMESTAMP;
`, [planCut, JSON.stringify({ material: true, yield: true, responsible: true, deadline: true }), `${DEMO} — checklist operacional concluído.`, responsibleName]);
const orderM = await upsertProductionOrder(client, {
number: 'OP-DEMO-0001', status: 'cutting', planId: planCut, contractor: `${DEMO} Confecção`,
orderReference: 'DEMO-CORTE-35', issueDate: offsetDate(-1), expectedDate: offsetDate(2),
productSku: 'LOCAL-DEMO-UX-TEE-M', productDescription: 'DEMO UX CAMISETA PRETA TAMANHO M',
quantity: 180, unit: 'UN', notes: `${DEMO} — acompanhar aproveitamento de risco.`, supplier,
lotCode: 'DEMO-NF-004', rollQuantity: 3, fabricKg: 33.3, ribKg: 1.8, yieldPiecesPerKg: 5.4,
actualCutQuantity: 150, rejectedQuantity: 2, finishedQuantity: 0,
qualityNotes: `${DEMO} — dois recortes separados para revisão de tecido.`, cutLayers: 12,
markerReference: 'RISCO-DEMO-M-12'
});
const orderG = await upsertProductionOrder(client, {
number: 'OP-DEMO-0002', status: 'sewing', planId: planCut, contractor: `${DEMO} Confecção`,
orderReference: 'DEMO-CORTE-35', issueDate: today, expectedDate: offsetDate(3),
productSku: 'LOCAL-DEMO-UX-TEE-G', productDescription: 'DEMO UX CAMISETA PRETA TAMANHO G',
quantity: 140, unit: 'UN', notes: `${DEMO} — lote encaminhado para costura.`, supplier,
lotCode: 'DEMO-NF-004', rollQuantity: 2, fabricKg: 28, ribKg: 1.54, yieldPiecesPerKg: 5,
actualCutQuantity: 140, rejectedQuantity: 1, finishedQuantity: 92,
qualityNotes: `${DEMO} — conferir gola nas primeiras peças.`, cutLayers: 9,
markerReference: 'RISCO-DEMO-G-09'
});
const orderHoodie = await upsertProductionOrder(client, {
number: 'OP-DEMO-0003', status: 'review', planId: planHoodie, contractor: `${DEMO} Confecção`,
orderReference: 'DEMO-MOLETOM-35', issueDate: offsetDate(2), expectedDate: offsetDate(8),
productSku: 'LOCAL-DEMO-UX-HOODIE-M', productDescription: 'DEMO UX MOLETOM GRAFITE TAMANHO M',
quantity: 60, unit: 'UN', notes: `${DEMO} — amostra segue para revisão antes da liberação total.`, supplier: `${DEMO} Malharia`,
lotCode: 'DEMO-LT-2408-02', rollQuantity: 2, fabricKg: 45, ribKg: 2.7, yieldPiecesPerKg: 1.33,
actualCutQuantity: 60, rejectedQuantity: 2, finishedQuantity: 52,
qualityNotes: `${DEMO} — registrar avarias por posto de trabalho.`, cutLayers: 6,
markerReference: 'RISCO-DEMO-MOLETOM-06'
});
await ensureOrderDetails(client, orderM, {
markers: [{ label: 'Risco M — 12 camadas', color: '#fbbf24' }],
components: [
{ sku: 'LOCAL-DEMO-MAT-MALHA-PRETA', name: 'DEMO-MALHA-300 MALHA 30/1 COR PRETO', quantityPerUnit: 0.185, totalQuantity: 33.3, unit: 'kg' },
{ sku: 'LOCAL-DEMO-MAT-RIBANA-PRETA', name: 'DEMO-0201 RIBANA 2X1 COR PRETO', quantityPerUnit: 0.01, totalQuantity: 1.8, unit: 'kg' }
],
steps: [
{ number: 1, name: 'Separação do lote', startDate: offsetDate(-1), endDate: offsetDate(-1), status: 'finished', color: '#22c55e' },
{ number: 2, name: 'Corte', startDate: today, endDate: offsetDate(1), status: 'in_progress', color: '#fbbf24' },
{ number: 3, name: 'Costura', startDate: offsetDate(1), endDate: offsetDate(2), status: 'pending', color: '#38bdf8' }
]
});
await ensureOrderDetails(client, orderG, {
markers: [{ label: 'Risco G — 9 camadas', color: '#38bdf8' }],
components: [{ sku: 'LOCAL-DEMO-MAT-MALHA-PRETA', name: 'DEMO-MALHA-300 MALHA 30/1 COR PRETO', quantityPerUnit: 0.2, totalQuantity: 28, unit: 'kg' }],
steps: [
{ number: 1, name: 'Corte', startDate: offsetDate(-1), endDate: today, status: 'finished', color: '#22c55e' },
{ number: 2, name: 'Costura', startDate: today, endDate: offsetDate(2), status: 'in_progress', color: '#38bdf8' },
{ number: 3, name: 'Revisão', startDate: offsetDate(2), endDate: offsetDate(3), status: 'pending', color: '#a78bfa' }
]
});
await ensureOrderDetails(client, orderHoodie, {
markers: [{ label: 'Risco moletom — 6 camadas', color: '#a78bfa' }],
components: [{ sku: 'LOCAL-DEMO-MAT-MOLETOM-GRAFITE', name: 'DEMO-MOLETOM 3 CABOS COR GRAFITE', quantityPerUnit: 0.75, totalQuantity: 45, unit: 'kg' }],
steps: [
{ number: 1, name: 'Corte', startDate: offsetDate(2), endDate: offsetDate(3), status: 'finished', color: '#22c55e' },
{ number: 2, name: 'Costura', startDate: offsetDate(3), endDate: offsetDate(6), status: 'finished', color: '#38bdf8' },
{ number: 3, name: 'Revisão', startDate: offsetDate(6), endDate: offsetDate(8), status: 'in_progress', color: '#a78bfa' }
]
});
const lotForReservation = lotIds[0];
if (lotForReservation) {
await client.query(`
INSERT INTO cut_plan_lot_reservations (
cut_plan_reference, production_plan_id, supply_lot_id, material_key,
quantity, consumed_quantity, status
) SELECT 'DEMO-CORTE-35', $1, $2, $3, 61.3, 0, 'active'
WHERE NOT EXISTS (
SELECT 1 FROM cut_plan_lot_reservations
WHERE cut_plan_reference = 'DEMO-CORTE-35' AND supply_lot_id = $2
);
`, [planCut, lotForReservation, normalizeMaterialKey('DEMO-MALHA-300 MALHA 30/1 COR PRETO')]);
}
const workstations = await client.query('SELECT id, code FROM production_workstations');
const workstationByCode = new Map(workstations.rows.map(row => [row.code, row.id]));
const defects = [
[orderM, workstationByCode.get('cutting'), 'Furo na malha', 'Tecido', 2, 'DEMO-NF-004', `${DEMO} — conferir rolo antes de seguir o corte.`],
[orderG, workstationByCode.get('sewing'), 'Costura desalinhada', 'Costura', 1, 'DEMO-NF-004', `${DEMO} — ajuste solicitado ao posto de costura.`],
[orderHoodie, workstationByCode.get('review'), 'Mancha de acabamento', 'Acabamento', 2, 'DEMO-LT-2408-02', `${DEMO} — lote separado para avaliação de qualidade.`]
];
for (const defect of defects) {
await client.query(`
INSERT INTO production_order_defects (
production_order_id, workstation_id, defect_type, origin, quantity, lot_code, notes
) SELECT $1, $2, $3, $4, $5, $6, $7
WHERE NOT EXISTS (
SELECT 1 FROM production_order_defects
WHERE production_order_id = $1 AND defect_type = $3 AND lot_code = $6
);
`, defect);
}
await client.query('COMMIT');
console.log('Demonstração de Suprimentos criada/atualizada com sucesso.');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
await pool.end();
}
}
main().catch((error) => {
console.error('Não foi possível criar os dados de demonstração:', error.message);
process.exitCode = 1;
});