Add supplies workflow demonstration seed
This commit is contained in:
543
backend/scripts/seedSuppliesDemo.js
Normal file
543
backend/scripts/seedSuppliesDemo.js
Normal 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;
|
||||
});
|
||||
Reference in New Issue
Block a user