← Back to Dashboard

🏭 COMPLETE STOCK MANAGEMENT SYSTEM WITH LPO GENERATION

🟒 CONNECTED
Last update: --:--:-- Total LPOs: 0 Total Stock Items: 0 Available: 0 Used: 0 Total Value: AED 0
0
Material Ordered
0
Material Ready
0
Material Received
0
Stock Items
πŸ“‹ LPO Management
πŸ“¦ Stock Management
πŸ” Barcode Scanner
πŸ—οΈ Rack Management
πŸ“‹ Master List
πŸ’° Opening/Closing Stock
πŸ“‹ Copy/Paste Stock
🏒 Supplier Master

βž• ADD NEW MATERIAL ORDER / LPO

Format: AMC/YYYY/##### (e.g., AMC/2026/10735)
SS Sheet 304
Type: SS
Finish: BRUSHED
Size: 1219*2438mm
Thick: 1.0mm
Price: AED 450
MS Plate
Type: MS
Finish: PAINTED
Size: 1500*3000mm
Thick: 2.5mm
Price: AED 320

πŸ“¦ Detailed Material Items

β–Ό

⚑ QUICK ACTIONS

πŸ“‹ PURCHASE ORDERS (LPOs)

LPO # Supplier AMC Job Material Details Qty Status Value (AED) Actions
Loading material orders...
Showing 0 of 0 orders
`; // Create Blob and download const blob = new Blob([fullHtml], { type: 'application/msword' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `LPO_${document.getElementById('lpoNumber').value.replace(/\//g, '-')}_${new Date().toISOString().slice(0, 10)}.doc`; a.click(); showMessage('βœ… Word document generated from preview'); addLog('Generated Word LPO from preview'); closeLpoPreview(); } function exportLPOList() { if (orders.length === 0) { showMessage('❌ No LPOs to export'); return; } let csv = 'LPO Number,Supplier,AMC Job,Order Date,Ready Date,Received Date,Total Ordered,Total Received,Total Pending,Subtotal (AED),VAT (AED),Grand Total (AED),Status\n'; orders.forEach(order => { const subtotal = order.material_items.reduce((sum, item) => sum + ((item.unit_price || 0) * item.ordered_qty), 0); const vat = subtotal * 0.05; // Assuming 5% VAT const grandTotal = subtotal + vat; csv += `"${order.lpo}","${order.supplier}","${order.job_number || 'N/A'}","${order.order_date || ''}",`; csv += `"${order.ready_date || ''}","${order.received_date || ''}",${order.total_ordered || 0},`; csv += `${order.total_received || 0},${order.total_pending || 0},${subtotal.toFixed(2)},`; csv += `${vat.toFixed(2)},${grandTotal.toFixed(2)},"${order.status}"\n`; }); const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `lpo_list_${new Date().toISOString().slice(0,10)}.csv`; a.click(); showMessage(`βœ… Exported ${orders.length} LPOs`); } // ========== LOW STOCK LPO GENERATION ========== function addFromLowStock() { const lowStockItems = checkLowStock(true); // silent mode if (lowStockItems.length === 0) { showMessage('βœ… No low stock items found'); return; } const modal = document.getElementById('lowStockSelectModal'); const body = document.getElementById('lowStockSelectBody'); let html = `
⚠️ ${lowStockItems.length} items below minimum stock level
`; lowStockItems.forEach((item, index) => { html += ` `; }); html += `
Material Finish Size Thickness Grade Available Threshold Min Order
${item.type} ${item.finish} ${item.size} ${item.thickness} ${item.grade} ${item.quantity} ${item.threshold}
`; body.innerHTML = html; selectedLowStockItems = lowStockItems; modal.style.display = 'flex'; } function toggleAllLowStock(checkbox) { const checkboxes = document.querySelectorAll('.lowstock-checkbox'); checkboxes.forEach(cb => cb.checked = checkbox.checked); } function addSelectedLowStockToLPO() { const checkboxes = document.querySelectorAll('.lowstock-checkbox:checked'); if (checkboxes.length === 0) { showMessage('❌ No items selected'); return; } // Clear existing material items materialItems = []; // Add each selected low stock item checkboxes.forEach(cb => { const index = parseInt(cb.value); const item = selectedLowStockItems[index]; const orderQty = parseInt(document.getElementById(`orderqty_${index}`).value) || item.threshold * 2; // Find matching master item for price const masterItem = masterList.find(m => m.type === item.type && m.finish === item.finish && m.thickness === item.thickness.replace(' mm', '') ); const unitPrice = masterItem ? masterItem.last_price : 0; const vat = masterItem ? masterItem.vat : 5; let totalPrice = unitPrice * orderQty; if (vat > 0) { totalPrice = totalPrice * (1 + vat / 100); } const materialItem = { id: Date.now().toString() + Math.random().toString(36).substr(2, 9) + index, s_no: materialItems.length + 1, type: item.type, finish: item.finish, size: item.size, thickness: item.thickness, grade: item.grade, afp: masterItem ? masterItem.afp : 'NON_AFP', ordered_qty: orderQty, received_qty: 0, pending_qty: orderQty, unit_price: unitPrice, vat: vat, total_price: totalPrice, remarks: `Auto-generated from low stock alert (Available: ${item.quantity}, Threshold: ${item.threshold})`, status: 'ORDERED', added: new Date().toISOString() }; materialItems.push(materialItem); }); // Update display updateMaterialItemsDisplay(); // Close modal closeLowStockSelectModal(); // Switch to orders tab and open detailed section switchTab('orders'); const detailSection = document.getElementById('detailedMaterial'); if (detailSection.style.display === 'none' || detailSection.style.display === '') { toggleDetailedMaterial(); } showMessage(`βœ… Added ${materialItems.length} items from low stock alert`); } function closeLowStockSelectModal() { document.getElementById('lowStockSelectModal').style.display = 'none'; selectedLowStockItems = []; } // ========== PRICE CALCULATION FUNCTIONS ========== function updateItemCalculations() { const ordered = parseInt(document.getElementById('matOrderedQty').value) || 0; const received = parseInt(document.getElementById('matReceivedQty').value) || 0; const unitPrice = parseFloat(document.getElementById('matUnitPrice').value) || 0; const vat = parseFloat(document.getElementById('matVAT').value) || 0; const pending = ordered - received; document.getElementById('matPendingQty').value = pending > 0 ? pending : 0; // Calculate total price with VAT let totalPrice = unitPrice * ordered; if (vat > 0) { totalPrice = totalPrice * (1 + vat / 100); } document.getElementById('matTotalPrice').value = totalPrice.toFixed(2); } function updateOldStockTotal() { const qty = parseInt(document.getElementById('oldStockQty').value) || 0; const price = parseFloat(document.getElementById('oldStockPrice').value) || 0; const total = qty * price; document.getElementById('oldStockTotal').value = 'AED ' + total.toFixed(2); } // ========== BARCODE PRINT FUNCTIONS ========== function showBarcodePrintOptions() { const modal = document.getElementById('barcodePrintOptionsModal'); const body = document.getElementById('barcodePrintOptionsBody'); const availableStock = stockItems.filter(s => s.status === 'available'); if (availableStock.length === 0) { body.innerHTML = `
🏷️
No available stock to print barcodes

Only received items that are still available can be printed.

`; modal.style.display = 'flex'; return; } // Group by LPO for selection const lpoGroups = {}; const materialGroups = {}; availableStock.forEach(stock => { if (!lpoGroups[stock.order_lpo]) { lpoGroups[stock.order_lpo] = { lpo: stock.order_lpo, items: [], count: 0, totalValue: 0 }; } lpoGroups[stock.order_lpo].items.push(stock); lpoGroups[stock.order_lpo].count++; lpoGroups[stock.order_lpo].totalValue += (stock.unit_price || 0); const materialKey = `${stock.material_type}|${stock.material_finish}|${stock.material_thickness}`; if (!materialGroups[materialKey]) { materialGroups[materialKey] = { type: stock.material_type, finish: stock.material_finish, thickness: stock.material_thickness, items: [], count: 0, totalValue: 0 }; } materialGroups[materialKey].items.push(stock); materialGroups[materialKey].count++; materialGroups[materialKey].totalValue += (stock.unit_price || 0); }); let html = `

Select Barcode Print Option

Total Available Items: ${availableStock.length}
Total Value: AED ${availableStock.reduce((sum, s) => sum + (s.unit_price || 0), 0).toFixed(2)}
Unique LPOs: ${Object.keys(lpoGroups).length}
Unique Materials: ${Object.keys(materialGroups).length}
`; body.innerHTML = html; modal.style.display = 'flex'; // Initialize with 'all' option updatePrintOptionDetails('all'); } function selectPrintOption(option) { currentPrintOption = option; document.querySelectorAll('input[name="printOption"]').forEach(radio => { if (radio.value === option) { radio.checked = true; } }); updatePrintOptionDetails(option); } function updatePrintFormat() { currentPrintFormat = document.getElementById('printFormatSelect').value; updatePrintPreview(); } function updatePrintOptionDetails(option) { const detailsDiv = document.getElementById('printOptionDetails'); if (!detailsDiv) return; const availableStock = stockItems.filter(s => s.status === 'available'); let html = ''; switch(option) { case 'all': html = `
πŸ“‹ Printing ALL available barcodes
Total Items: ${availableStock.length}
Total Value: AED ${availableStock.reduce((sum, s) => sum + (s.unit_price || 0), 0).toFixed(2)}
Unique LPOs: ${[...new Set(availableStock.map(s => s.order_lpo))].length}
Unique Materials: ${[...new Set(availableStock.map(s => s.material_type))].length}
`; break; case 'byLPO': const lpoGroups = {}; availableStock.forEach(stock => { if (!lpoGroups[stock.order_lpo]) { lpoGroups[stock.order_lpo] = { items: [], count: 0, value: 0 }; } lpoGroups[stock.order_lpo].items.push(stock); lpoGroups[stock.order_lpo].count++; lpoGroups[stock.order_lpo].value += (stock.unit_price || 0); }); html = `
`; break; case 'byMaterial': const materialGroups = {}; availableStock.forEach(stock => { const key = `${stock.material_type} - ${stock.material_finish} - ${stock.material_thickness}`; if (!materialGroups[key]) { materialGroups[key] = { type: stock.material_type, finish: stock.material_finish, thickness: stock.material_thickness, items: [], count: 0, value: 0 }; } materialGroups[key].items.push(stock); materialGroups[key].count++; materialGroups[key].value += (stock.unit_price || 0); }); html = `
`; break; case 'byRack': const rackGroups = {}; availableStock.forEach(stock => { if (!rackGroups[stock.rack_number]) { rackGroups[stock.rack_number] = { items: [], count: 0, value: 0 }; } rackGroups[stock.rack_number].items.push(stock); rackGroups[stock.rack_number].count++; rackGroups[stock.rack_number].value += (stock.unit_price || 0); }); html = `
`; break; case 'selected': html = `
${availableStock.map(stock => `
`).join('')}
`; break; } detailsDiv.innerHTML = html; updatePrintPreview(); } function updatePrintPreview() { const option = document.querySelector('input[name="printOption"]:checked')?.value || 'all'; const previewDiv = document.getElementById('printPreview'); const format = document.getElementById('printFormatSelect')?.value || 'labels'; if (!previewDiv) return; const availableStock = stockItems.filter(s => s.status === 'available'); let items = []; let title = ''; switch(option) { case 'all': items = availableStock; title = `All Available Barcodes (${items.length} items)`; break; case 'byLPO': const lpo = document.getElementById('lpoSelect')?.value; if (lpo) { items = availableStock.filter(s => s.order_lpo === lpo); title = `LPO: ${lpo} (${items.length} items)`; const stats = document.getElementById('lpoStats'); if (stats) { const totalValue = items.reduce((sum, i) => sum + (i.unit_price || 0), 0); stats.innerHTML = ` Total Items: ${items.length} | Total Value: AED ${totalValue.toFixed(2)} `; } } break; case 'byMaterial': const material = document.getElementById('materialSelect')?.value; if (material) { const [type, finish, thickness] = material.split(' - '); items = availableStock.filter(s => s.material_type === type && s.material_finish === finish && s.material_thickness === thickness ); title = `Material: ${material} (${items.length} items)`; const stats = document.getElementById('materialStats'); if (stats) { const totalValue = items.reduce((sum, i) => sum + (i.unit_price || 0), 0); stats.innerHTML = ` Total Items: ${items.length} | Total Value: AED ${totalValue.toFixed(2)} `; } } break; case 'byRack': const rack = document.getElementById('rackSelect')?.value; if (rack) { items = availableStock.filter(s => s.rack_number === rack); title = `Rack: ${rack} (${items.length} items)`; const stats = document.getElementById('rackStats'); if (stats) { const totalValue = items.reduce((sum, i) => sum + (i.unit_price || 0), 0); stats.innerHTML = ` Total Items: ${items.length} | Total Value: AED ${totalValue.toFixed(2)} `; } } break; case 'selected': const selectedIds = Array.from(document.querySelectorAll('.barcode-select:checked')).map(cb => cb.value); items = availableStock.filter(s => selectedIds.includes(s.id)); title = `Selected Barcodes (${items.length} items)`; break; } if (!items || items.length === 0) { previewDiv.innerHTML = '
No items selected for printing
'; return; } let previewHtml = `
Preview - ${title} Total: AED ${items.reduce((sum, i) => sum + (i.unit_price || 0), 0).toFixed(2)}
`; items.slice(0, 6).forEach(item => { previewHtml += `
*${item.barcode}*
${item.barcode}
${item.material_type} - ${item.material_finish}
${item.material_size} | ${item.material_thickness}
AED ${(item.unit_price || 0).toFixed(2)}
`; }); if (items.length > 6) { previewHtml += `
... and ${items.length - 6} more items
`; } previewHtml += '
'; previewDiv.innerHTML = previewHtml; } function selectAllBarcodes() { document.querySelectorAll('.barcode-select').forEach(cb => cb.checked = true); updatePrintPreview(); } function deselectAllBarcodes() { document.querySelectorAll('.barcode-select').forEach(cb => cb.checked = false); updatePrintPreview(); } function printSelectedBarcodesWithOptions() { const option = document.querySelector('input[name="printOption"]:checked')?.value || 'all'; const format = document.getElementById('printFormatSelect')?.value || 'labels'; const availableStock = stockItems.filter(s => s.status === 'available'); let items = []; switch(option) { case 'all': items = availableStock; break; case 'byLPO': const lpo = document.getElementById('lpoSelect')?.value; if (lpo) { items = availableStock.filter(s => s.order_lpo === lpo); } break; case 'byMaterial': const material = document.getElementById('materialSelect')?.value; if (material) { const [type, finish, thickness] = material.split(' - '); items = availableStock.filter(s => s.material_type === type && s.material_finish === finish && s.material_thickness === thickness ); } break; case 'byRack': const rack = document.getElementById('rackSelect')?.value; if (rack) { items = availableStock.filter(s => s.rack_number === rack); } break; case 'selected': const selectedIds = Array.from(document.querySelectorAll('.barcode-select:checked')).map(cb => cb.value); items = availableStock.filter(s => selectedIds.includes(s.id)); break; } if (items.length === 0) { showMessage('❌ No items selected for printing'); return; } printBarcodesWithFormat(items, format); closeBarcodePrintOptions(); } function printBarcodesWithFormat(items, format) { const printWindow = window.open('', '_blank'); let content = ''; if (format === 'labels') { content = ` Barcode Labels
`; items.forEach(item => { content += `
*${item.barcode}*
${item.barcode}
AED ${(item.unit_price || 0).toFixed(2)}
${item.material_type} - ${item.material_finish}
Job: ${item.job_number}
Rack: ${item.rack_number}
`; }); content += `
`; } else if (format === 'detailed') { content = ` Detailed Barcode Labels
`; items.forEach(item => { content += `
*${item.barcode}*
${item.barcode}
AED ${(item.unit_price || 0).toFixed(2)}
Material: ${item.material_type} - ${item.material_finish}
Size: ${item.material_size}
Thickness: ${item.material_thickness}
Grade: ${item.material_grade}
Job: ${item.job_number}
Sheet: ${item.sheet_name}
Rack: ${item.rack_number}
Date: ${new Date(item.received_date).toLocaleDateString()}
`; }); content += `
`; } else { // Compact format content = ` Compact Barcode Labels
`; items.forEach(item => { content += `
*${item.barcode}*
${item.barcode}
AED ${(item.unit_price || 0).toFixed(2)}
${item.material_type}
`; }); content += `
`; } printWindow.document.write(content); printWindow.document.close(); setTimeout(() => printWindow.print(), 500); } function closeBarcodePrintOptions() { document.getElementById('barcodePrintOptionsModal').style.display = 'none'; } // ========== MASTER LIST FUNCTIONS ========== function initializeMasterList() { masterList = [ { id: 'master1', type: 'SS', finish: 'BRUSHED', size: '1219*2438', thickness: '1.0', grade: '304', afp: 'NON_AFP', min_order_qty: 10, supplier: 'Standard SS Suppliers', lead_time_days: 7, last_price: 450, low_stock_level: 5, vat: 5 }, { id: 'master2', type: 'SS', finish: 'BRUSHED', size: '1219*2438', thickness: '1.5', grade: '304', afp: 'NON_AFP', min_order_qty: 10, supplier: 'Standard SS Suppliers', lead_time_days: 7, last_price: 520, low_stock_level: 5, vat: 5 }, { id: 'master3', type: 'SS', finish: 'BRUSHED', size: '1219*2438', thickness: '2.0', grade: '304', afp: 'NON_AFP', min_order_qty: 10, supplier: 'Standard SS Suppliers', lead_time_days: 7, last_price: 610, low_stock_level: 5, vat: 5 }, { id: 'master4', type: 'SS', finish: 'BRUSHED', size: '1500*2438', thickness: '1.0', grade: '316', afp: 'NON_AFP', min_order_qty: 15, supplier: 'Premium SS Suppliers', lead_time_days: 10, last_price: 500, low_stock_level: 3, vat: 5 }, { id: 'master5', type: 'SS', finish: 'BRUSHED', size: '1500*3000', thickness: '1.0', grade: '316', afp: 'NON_AFP', min_order_qty: 5, supplier: 'Premium SS Suppliers', lead_time_days: 10, last_price: 590, low_stock_level: 3, vat: 5 }, { id: 'master6', type: 'MS', finish: 'PAINTED', size: '1500*3000', thickness: '2.5', grade: 'A36', afp: 'NON_AFP', min_order_qty: 5, supplier: 'MS Steel Suppliers', lead_time_days: 5, last_price: 320, low_stock_level: 5, vat: 5 }, { id: 'master7', type: 'MS', finish: 'GALVANIZED', size: '1250*2500', thickness: '1.2', grade: 'A36', afp: 'NON_AFP', min_order_qty: 10, supplier: 'MS Steel Suppliers', lead_time_days: 5, last_price: 280, low_stock_level: 8, vat: 5 }, { id: 'master8', type: 'AL', finish: 'MILL', size: '1200*2400', thickness: '2.0', grade: '6061', afp: 'NON_AFP', min_order_qty: 8, supplier: 'Aluminium Suppliers', lead_time_days: 6, last_price: 550, low_stock_level: 4, vat: 5 } ]; filteredMaster = [...masterList]; } function addMasterItem() { const type = document.getElementById('masterNewType').value.trim(); const finish = document.getElementById('masterNewFinish').value.trim(); const size = document.getElementById('masterNewSize').value.trim(); const thickness = document.getElementById('masterNewThickness').value.trim(); const grade = document.getElementById('masterNewGrade').value.trim(); const afp = document.getElementById('masterNewAFP').value; const minOrderQty = parseInt(document.getElementById('masterNewMinQty').value) || 10; const supplier = document.getElementById('masterNewSupplier').value.trim(); const leadTime = parseInt(document.getElementById('masterNewLeadTime').value) || 7; const price = parseFloat(document.getElementById('masterNewPrice').value) || 0; const lowStock = parseInt(document.getElementById('masterNewLowStock').value) || 5; const vat = parseFloat(document.getElementById('masterNewVAT').value) || 5; if (!type || !finish || !size || !thickness || !grade) { showMessage('❌ Please fill all required fields'); return; } const newItem = { id: 'master' + Date.now() + Math.random().toString(36).substr(2, 5), type: type, finish: finish, size: size, thickness: thickness, grade: grade, afp: afp, min_order_qty: minOrderQty, supplier: supplier || 'Not specified', lead_time_days: leadTime, last_price: price, low_stock_level: lowStock, vat: vat }; masterList.push(newItem); filteredMaster = [...masterList]; clearMasterForm(); loadMasterList(); updateMasterDisplay(); showMessage('βœ… Master item added successfully'); } function clearMasterForm() { document.getElementById('masterNewType').value = ''; document.getElementById('masterNewFinish').value = ''; document.getElementById('masterNewSize').value = ''; document.getElementById('masterNewThickness').value = ''; document.getElementById('masterNewGrade').value = ''; document.getElementById('masterNewAFP').value = 'NON_AFP'; document.getElementById('masterNewMinQty').value = '10'; document.getElementById('masterNewSupplier').value = ''; document.getElementById('masterNewLeadTime').value = '7'; document.getElementById('masterNewPrice').value = '0'; document.getElementById('masterNewLowStock').value = '5'; document.getElementById('masterNewVAT').value = '5'; } function deleteSelectedMaster() { if (selectedMasterItems.length === 0) { showMessage('❌ Please select items to delete'); return; } if (confirm(`Delete ${selectedMasterItems.length} selected master items?`)) { masterList = masterList.filter(item => !selectedMasterItems.includes(item)); selectedMasterItems = []; filteredMaster = [...masterList]; loadMasterList(); updateMasterDisplay(); showMessage('βœ… Selected items deleted'); } } function updateMasterDisplay() { const masterBody = document.getElementById('masterBody'); const totalTypes = document.getElementById('masterTotalTypes'); const totalStock = document.getElementById('masterTotalStock'); const totalOrders = document.getElementById('masterTotalOrders'); const lowStock = document.getElementById('masterLowStock'); // Calculate master statistics from stock const materialStats = {}; let lowStockCount = 0; stockItems.forEach(item => { const key = `${item.material_type}|${item.material_finish}|${item.material_size}|${item.material_thickness}|${item.material_grade}`; if (!materialStats[key]) { materialStats[key] = { type: item.material_type, finish: item.material_finish, size: item.material_size, thickness: item.material_thickness, grade: item.material_grade, total: 0, available: 0, used: 0, total_value: 0, unit_price: item.unit_price || 0, last_received: item.received_date, threshold: lowStockThreshold }; } materialStats[key].total++; if (item.status === 'available') { materialStats[key].available++; } else { materialStats[key].used++; } materialStats[key].total_value += (item.unit_price || 0); if (new Date(item.received_date) > new Date(materialStats[key].last_received)) { materialStats[key].last_received = item.received_date; } }); // Apply master thresholds masterList.forEach(master => { Object.keys(materialStats).forEach(key => { const parts = key.split('|'); if (parts[0] === master.type && parts[1] === master.finish && parts[4] === master.grade) { materialStats[key].threshold = master.low_stock_level || lowStockThreshold; } }); }); // Count low stock items Object.values(materialStats).forEach(stat => { if (stat.available < stat.threshold) lowStockCount++; }); totalTypes.textContent = Object.keys(materialStats).length; totalStock.textContent = stockItems.length; totalOrders.textContent = orders.length; lowStock.textContent = lowStockCount; // Apply filters const typeFilter = document.getElementById('masterTypeFilter').value; const finishFilter = document.getElementById('masterFinishFilter').value; const sizeFilter = document.getElementById('masterSizeFilter').value; const thicknessFilter = document.getElementById('masterThicknessFilter').value; const gradeFilter = document.getElementById('masterGradeFilter').value; const statusFilter = document.getElementById('masterStatusFilter').value; const searchFilter = document.getElementById('masterDisplaySearch').value.toLowerCase(); let statsArray = Object.values(materialStats); if (typeFilter) { statsArray = statsArray.filter(s => s.type === typeFilter); } if (finishFilter) { statsArray = statsArray.filter(s => s.finish === finishFilter); } if (sizeFilter) { statsArray = statsArray.filter(s => s.size === sizeFilter); } if (thicknessFilter) { statsArray = statsArray.filter(s => s.thickness === thicknessFilter); } if (gradeFilter) { statsArray = statsArray.filter(s => s.grade === gradeFilter); } if (statusFilter === 'low') { statsArray = statsArray.filter(s => s.available < (s.threshold || lowStockThreshold)); } else if (statusFilter === 'medium') { statsArray = statsArray.filter(s => s.available >= 10 && s.available <= 50); } else if (statusFilter === 'high') { statsArray = statsArray.filter(s => s.available > 50); } if (searchFilter) { statsArray = statsArray.filter(s => s.type.toLowerCase().includes(searchFilter) || s.finish.toLowerCase().includes(searchFilter) || s.grade.toLowerCase().includes(searchFilter) || s.size.toLowerCase().includes(searchFilter) ); } if (statsArray.length === 0) { masterBody.innerHTML = `
Loading master list...
`; setTimeout(() => { if (statsArray.length === 0) { masterBody.innerHTML = `
πŸ“‹
No master items found
Add materials to stock to see them here
`; } }, 500); } else { masterBody.innerHTML = statsArray.map(stat => { const isLowStock = stat.available < (stat.threshold || lowStockThreshold); return ` ${stat.type} ${stat.finish} ${stat.size} ${stat.thickness} ${stat.grade} ${stat.total} ${stat.available} ${stat.used} AED ${stat.unit_price.toFixed(2)} AED ${stat.total_value.toFixed(2)} ${new Date(stat.last_received).toLocaleDateString()} `}).join(''); } // Update filter dropdowns updateMasterFilterDropdowns(materialStats); } function updateMasterFilterDropdowns(stats) { const finishSet = new Set(); const sizeSet = new Set(); const thicknessSet = new Set(); const gradeSet = new Set(); Object.values(stats).forEach(stat => { finishSet.add(stat.finish); sizeSet.add(stat.size); thicknessSet.add(stat.thickness); gradeSet.add(stat.grade); }); const finishFilter = document.getElementById('masterFinishFilter'); finishFilter.innerHTML = ''; Array.from(finishSet).sort().forEach(finish => { const option = document.createElement('option'); option.value = finish; option.textContent = finish; finishFilter.appendChild(option); }); const sizeFilter = document.getElementById('masterSizeFilter'); sizeFilter.innerHTML = ''; Array.from(sizeSet).sort().forEach(size => { const option = document.createElement('option'); option.value = size; option.textContent = size; sizeFilter.appendChild(option); }); const thicknessFilter = document.getElementById('masterThicknessFilter'); thicknessFilter.innerHTML = ''; Array.from(thicknessSet).sort().forEach(thickness => { const option = document.createElement('option'); option.value = thickness; option.textContent = thickness; thicknessFilter.appendChild(option); }); const gradeFilter = document.getElementById('masterGradeFilter'); gradeFilter.innerHTML = ''; Array.from(gradeSet).sort().forEach(grade => { const option = document.createElement('option'); option.value = grade; option.textContent = grade; gradeFilter.appendChild(option); }); } function refreshMasterList() { updateMasterDisplay(); showMessage('βœ… Master list refreshed'); } function exportMasterList() { const materialStats = {}; stockItems.forEach(item => { const key = `${item.material_type}-${item.material_finish}-${item.material_size}-${item.material_thickness}-${item.material_grade}`; if (!materialStats[key]) { materialStats[key] = { type: item.material_type, finish: item.material_finish, size: item.material_size, thickness: item.material_thickness, grade: item.material_grade, total: 0, available: 0, used: 0, total_value: 0 }; } materialStats[key].total++; if (item.status === 'available') { materialStats[key].available++; } else { materialStats[key].used++; } materialStats[key].total_value += (item.unit_price || 0); }); let csv = 'Material Type,Finish,Size,Thickness,Grade,Total Stock,Available,Used,Total Value (AED)\n'; Object.values(materialStats).forEach(stat => { csv += `"${stat.type}","${stat.finish}","${stat.size}","${stat.thickness}","${stat.grade}",`; csv += `${stat.total},${stat.available},${stat.used},AED ${stat.total_value.toFixed(2)}\n`; }); const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `master_list_${new Date().toISOString().slice(0,10)}.csv`; a.click(); showMessage(`βœ… Exported master list`); } function filterMasterDisplay() { updateMasterDisplay(); } function generateLPOFromMasterItem(type, finish, size, thickness, grade) { document.getElementById('matType').value = type; document.getElementById('matFinish').value = finish; document.getElementById('matSize').value = size.replace(' mm', ''); document.getElementById('matThickness').value = thickness.replace(' mm', ''); document.getElementById('matGrade').value = grade; const masterItem = masterList.find(m => m.type === type && m.finish === finish && m.thickness === thickness.replace(' mm', '') ); if (masterItem) { document.getElementById('matOrderedQty').value = masterItem.min_order_qty || 10; document.getElementById('matUnitPrice').value = masterItem.last_price || 0; document.getElementById('matVAT').value = masterItem.vat || 5; updateItemCalculations(); } switchTab('orders'); const detailSection = document.getElementById('detailedMaterial'); if (detailSection.style.display === 'none' || detailSection.style.display === '') { toggleDetailedMaterial(); } showMessage(`βœ… Master item loaded for LPO generation`); } function viewMasterHistory(type, finish, size, thickness, grade) { const relevantStock = stockItems.filter(s => s.material_type === type && s.material_finish === finish && s.material_size === size && s.material_thickness === thickness && s.material_grade === grade ); const relevantOrders = orders.filter(order => order.material_items.some(item => item.type === type && item.finish === finish && item.size === size && item.thickness === thickness && item.grade === grade ) ); const totalValue = relevantStock.reduce((sum, item) => sum + (item.unit_price || 0), 0); let historyHtml = `

Material History: ${type} - ${finish} - ${thickness}

Total Stock: ${relevantStock.length}
Available: ${relevantStock.filter(s => s.status === 'available').length}
Used: ${relevantStock.filter(s => s.status === 'used').length}
Total Value: AED ${totalValue.toFixed(2)}
Avg Price: AED ${(totalValue / (relevantStock.length || 1)).toFixed(2)}
Total Orders: ${relevantOrders.length}
Last Ordered: ${relevantOrders.length > 0 ? new Date(relevantOrders[0].order_date).toLocaleDateString() : 'Never'}
Recent Orders:
`; if (relevantOrders.length === 0) { historyHtml += '

No order history found.

'; } else { historyHtml += ''; historyHtml += ''; relevantOrders.slice(0, 5).forEach(order => { const item = order.material_items.find(i => i.type === type && i.finish === finish && i.thickness === thickness ); const itemTotal = (item ? (item.unit_price || 0) * (item.received_qty || 0) : 0); historyHtml += ` `; }); historyHtml += '
LPODateSupplierQtyReceivedUnit PriceTotal (AED)
${order.lpo} ${new Date(order.order_date).toLocaleDateString()} ${order.supplier} ${item ? item.ordered_qty : 0} ${item ? item.received_qty : 0} AED ${(item ? item.unit_price || 0 : 0).toFixed(2)} AED ${itemTotal.toFixed(2)}
'; } const modal = document.getElementById('barcodeModal'); document.getElementById('barcodeModalTitle').textContent = 'Material History'; document.getElementById('barcodeModalBody').innerHTML = historyHtml; modal.style.display = 'flex'; } function loadMasterList() { const masterListDiv = document.getElementById('masterList'); let html = ''; filteredMaster.forEach(item => { html += `
${item.type} - ${item.finish}
Size: ${item.size}mm | Thick: ${item.thickness}mm | Grade: ${item.grade} AFP: ${item.afp} | Supplier: ${item.supplier}
Min Order: ${item.min_order_qty} Lead Time: ${item.lead_time_days} days Price: AED ${item.last_price} Low Stock: ${item.low_stock_level}
`; }); masterListDiv.innerHTML = html; } function filterMasterList() { const filter = document.getElementById('masterFilter').value; const search = document.getElementById('masterSearch').value.toLowerCase(); filteredMaster = masterList.filter(item => { const matchesFilter = !filter || item.type === filter; const matchesSearch = !search || item.type.toLowerCase().includes(search) || item.finish.toLowerCase().includes(search) || item.grade.toLowerCase().includes(search) || item.supplier.toLowerCase().includes(search); return matchesFilter && matchesSearch; }); loadMasterList(); } function selectMasterItem(id) { const item = masterList.find(m => m.id === id); if (!item) return; const element = document.getElementById(`master-${id}`); if (element.classList.contains('selected')) { element.classList.remove('selected'); selectedMasterItems = selectedMasterItems.filter(i => i.id !== id); } else { element.classList.add('selected'); selectedMasterItems.push(item); } } function generateLPOFromMaster() { if (selectedMasterItems.length === 0) { showMessage('❌ Please select at least one item from master list'); return; } materialItems = []; selectedMasterItems.forEach((item, index) => { const unitPrice = item.last_price || 0; const vat = item.vat || 5; let totalPrice = unitPrice * item.min_order_qty; if (vat > 0) { totalPrice = totalPrice * (1 + vat / 100); } const materialItem = { id: Date.now().toString() + Math.random().toString(36).substr(2, 9) + index, s_no: index + 1, type: item.type, finish: item.finish, size: item.size + ' mm', thickness: item.thickness + ' mm', grade: item.grade, afp: item.afp, ordered_qty: item.min_order_qty, received_qty: 0, pending_qty: item.min_order_qty, unit_price: unitPrice, vat: vat, total_price: totalPrice, remarks: `From master list - Lead time: ${item.lead_time_days} days`, status: 'ORDERED', added: new Date().toISOString() }; materialItems.push(materialItem); }); updateMaterialItemsDisplay(); selectedMasterItems = []; document.querySelectorAll('.master-item.selected').forEach(el => { el.classList.remove('selected'); }); switchTab('orders'); const detailSection = document.getElementById('detailedMaterial'); if (detailSection.style.display === 'none' || detailSection.style.display === '') { toggleDetailedMaterial(); } showMessage(`βœ… Added ${materialItems.length} items from master list`); } function quickAddFromMaster() { const modal = document.getElementById('masterSelectModal'); const list = document.getElementById('masterSelectList'); let html = ''; masterList.forEach(item => { html += `
${item.type} - ${item.finish}
Size: ${item.size}mm | Thick: ${item.thickness}mm | Grade: ${item.grade} AFP: ${item.afp}
Min Order: ${item.min_order_qty} Price: AED ${item.last_price}
`; }); list.innerHTML = html; modal.style.display = 'flex'; } function selectQuickMasterItem(id) { const item = masterList.find(m => m.id === id); if (!item) return; const unitPrice = item.last_price || 0; const vat = item.vat || 5; let totalPrice = unitPrice * item.min_order_qty; if (vat > 0) { totalPrice = totalPrice * (1 + vat / 100); } const materialItem = { id: Date.now().toString() + Math.random().toString(36).substr(2, 9), s_no: materialItems.length + 1, type: item.type, finish: item.finish, size: item.size + ' mm', thickness: item.thickness + ' mm', grade: item.grade, afp: item.afp, ordered_qty: item.min_order_qty, received_qty: 0, pending_qty: item.min_order_qty, unit_price: unitPrice, vat: vat, total_price: totalPrice, remarks: `From master list`, status: 'ORDERED', added: new Date().toISOString() }; materialItems.push(materialItem); updateMaterialItemsDisplay(); closeMasterModal(); showMessage(`βœ… Added ${item.type} to material list`); } function filterMasterSelect() { const search = document.getElementById('masterSelectSearch').value.toLowerCase(); const list = document.getElementById('masterSelectList'); const filtered = masterList.filter(item => item.type.toLowerCase().includes(search) || item.finish.toLowerCase().includes(search) || item.grade.toLowerCase().includes(search) ); let html = ''; filtered.forEach(item => { html += `
${item.type} - ${item.finish}
Size: ${item.size}mm | Thick: ${item.thickness}mm | Grade: ${item.grade} AFP: ${item.afp}
Min Order: ${item.min_order_qty} Price: AED ${item.last_price}
`; }); list.innerHTML = html; } function closeMasterModal() { document.getElementById('masterSelectModal').style.display = 'none'; } // ========== OPENING/CLOSING STOCK FUNCTIONS ========== function calculateOpeningStock() { const openingDate = document.getElementById('openingStockDate').value; if (!openingDate) { showMessage('❌ Please select opening stock date'); return; } const openingTimestamp = new Date(openingDate).getTime(); const openingItems = stockItems.filter(item => new Date(item.received_date).getTime() <= openingTimestamp ); openingStock = {}; let totalValue = 0; openingItems.forEach(item => { const key = `${item.material_type}|${item.material_finish}|${item.material_size}|${item.material_thickness}|${item.material_grade}`; if (!openingStock[key]) { openingStock[key] = { type: item.material_type, finish: item.material_finish, size: item.material_size, thickness: item.material_thickness, grade: item.material_grade, quantity: 0, value: 0, unit_price: item.unit_price || 0 }; } if (item.status === 'available' || (item.status === 'used' && new Date(item.used_date).getTime() > openingTimestamp)) { openingStock[key].quantity++; const price = item.unit_price || 0; openingStock[key].value += price; totalValue += price; } }); document.getElementById('openingValue').textContent = 'AED ' + totalValue.toFixed(2); document.getElementById('openingItems').textContent = openingItems.length + ' items'; document.getElementById('displayOpeningValue').textContent = 'AED ' + totalValue.toFixed(2); document.getElementById('displayOpeningItems').textContent = openingItems.length + ' items'; showMessage(`βœ… Opening stock calculated: AED ${totalValue.toFixed(2)}`); addLog(`Opening stock calculated: AED ${totalValue.toFixed(2)}`); checkLowStock(); return { openingStock, totalValue }; } function calculateClosingStock() { const closingDate = document.getElementById('closingStockDate').value; if (!closingDate) { showMessage('❌ Please select closing stock date'); return; } const closingTimestamp = new Date(closingDate).getTime(); const closingItems = stockItems.filter(item => new Date(item.received_date).getTime() <= closingTimestamp ); closingStock = {}; let totalValue = 0; closingItems.forEach(item => { const key = `${item.material_type}|${item.material_finish}|${item.material_size}|${item.material_thickness}|${item.material_grade}`; if (!closingStock[key]) { closingStock[key] = { type: item.material_type, finish: item.material_finish, size: item.material_size, thickness: item.material_thickness, grade: item.material_grade, quantity: 0, value: 0, unit_price: item.unit_price || 0 }; } if (item.status === 'available' || (item.status === 'used' && new Date(item.used_date).getTime() > closingTimestamp)) { closingStock[key].quantity++; const price = item.unit_price || 0; closingStock[key].value += price; totalValue += price; } }); document.getElementById('closingValue').textContent = 'AED ' + totalValue.toFixed(2); document.getElementById('closingItems').textContent = closingItems.length + ' items'; document.getElementById('displayClosingValue').textContent = 'AED ' + totalValue.toFixed(2); document.getElementById('displayClosingItems').textContent = closingItems.length + ' items'; if (Object.keys(openingStock).length > 0) { const openingValue = parseFloat(document.getElementById('openingValue').textContent.replace('AED ', '')) || 0; const movement = totalValue - openingValue; const movementClass = movement >= 0 ? 'price-positive' : 'price-negative'; document.getElementById('stockMovement').innerHTML = `AED ${movement.toFixed(2)}`; document.getElementById('displayMovement').innerHTML = `AED ${movement.toFixed(2)}`; const changedItems = Math.abs(Object.keys(closingStock).length - Object.keys(openingStock).length); document.getElementById('movementItems').textContent = changedItems + ' items changed'; document.getElementById('displayMovementItems').textContent = changedItems + ' items changed'; } showMessage(`βœ… Closing stock calculated: AED ${totalValue.toFixed(2)}`); addLog(`Closing stock calculated: AED ${totalValue.toFixed(2)}`); return { closingStock, totalValue }; } function loadPeriodStock() { calculateOpeningStock(); calculateClosingStock(); } function checkLowStock(silent = false) { const lowStockItems = []; const materialGroups = {}; stockItems.forEach(item => { if (item.status === 'available') { const key = `${item.material_type}|${item.material_finish}|${item.material_size}|${item.material_thickness}|${item.material_grade}`; if (!materialGroups[key]) { materialGroups[key] = { type: item.material_type, finish: item.material_finish, size: item.material_size, thickness: item.material_thickness, grade: item.material_grade, quantity: 0, threshold: lowStockThreshold }; } materialGroups[key].quantity++; } }); masterList.forEach(master => { Object.keys(materialGroups).forEach(key => { const parts = key.split('|'); if (parts[0] === master.type && parts[1] === master.finish && parts[4] === master.grade) { materialGroups[key].threshold = master.low_stock_level || lowStockThreshold; } }); }); Object.values(materialGroups).forEach(group => { if (group.quantity < group.threshold) { lowStockItems.push(group); } }); document.getElementById('lowStockCount').textContent = lowStockItems.length; document.getElementById('masterLowStock').textContent = lowStockItems.length; if (!silent && lowStockItems.length > 0) { showLowStockAlert(lowStockItems); } return lowStockItems; } function showLowStockAlert(items) { const modal = document.getElementById('lowStockModal'); const body = document.getElementById('lowStockBody'); let html = `
⚠️ ${items.length} items are below minimum stock level!
`; items.forEach(item => { html += ` `; }); html += '
Material Finish Size Thickness Grade Available Threshold Action
${item.type} ${item.finish} ${item.size} ${item.thickness} ${item.grade} ${item.quantity} ${item.threshold}
'; body.innerHTML = html; modal.style.display = 'flex'; } function quickOrderFromAlert(type, finish, size, thickness, grade) { closeLowStockModal(); switchTab('orders'); document.getElementById('matType').value = type; document.getElementById('matFinish').value = finish; document.getElementById('matSize').value = size.replace(' mm', ''); document.getElementById('matThickness').value = thickness.replace(' mm', ''); document.getElementById('matGrade').value = grade; const masterItem = masterList.find(m => m.type === type && m.finish === finish && m.thickness === thickness.replace(' mm', '') ); if (masterItem) { document.getElementById('matOrderedQty').value = masterItem.min_order_qty || 10; document.getElementById('matUnitPrice').value = masterItem.last_price || 0; document.getElementById('matVAT').value = masterItem.vat || 5; updateItemCalculations(); } const detailSection = document.getElementById('detailedMaterial'); if (detailSection.style.display === 'none' || detailSection.style.display === '') { toggleDetailedMaterial(); } showMessage('βœ… Quick order form filled from low stock alert'); } function closeLowStockModal() { document.getElementById('lowStockModal').style.display = 'none'; } // ========== FULLSCREEN VIEW FUNCTIONS ========== function fullscreenStockView() { const modal = document.getElementById('fullscreenModal'); const body = document.getElementById('fullscreenModalBody'); updateFullscreenFilters(); buildFullscreenView(); modal.style.display = 'flex'; } function buildFullscreenView() { const body = document.getElementById('fullscreenModalBody'); let html = `
`; const materialFilter = document.getElementById('fsMaterialFilter')?.value || ''; const finishFilter = document.getElementById('fsFinishFilter')?.value || ''; const sizeFilter = document.getElementById('fsSizeFilter')?.value || ''; const thicknessFilter = document.getElementById('fsThicknessFilter')?.value || ''; const gradeFilter = document.getElementById('fsGradeFilter')?.value || ''; const statusFilter = document.getElementById('fsStatusFilter')?.value || ''; const rackFilter = document.getElementById('fsRackFilter')?.value || ''; const searchFilter = document.getElementById('fsSearch')?.value?.toLowerCase() || ''; let filtered = stockItems; if (materialFilter) filtered = filtered.filter(s => s.material_type === materialFilter); if (finishFilter) filtered = filtered.filter(s => s.material_finish === finishFilter); if (sizeFilter) filtered = filtered.filter(s => s.material_size === sizeFilter); if (thicknessFilter) filtered = filtered.filter(s => s.material_thickness === thicknessFilter); if (gradeFilter) filtered = filtered.filter(s => s.material_grade === gradeFilter); if (statusFilter) filtered = filtered.filter(s => s.status === statusFilter); if (rackFilter) filtered = filtered.filter(s => s.rack_number === rackFilter); if (searchFilter) { filtered = filtered.filter(s => s.barcode.toLowerCase().includes(searchFilter) || s.job_number.toLowerCase().includes(searchFilter) || s.sheet_name.toLowerCase().includes(searchFilter) || s.material_type.toLowerCase().includes(searchFilter) ); } if (filtered.length === 0) { html += ` `; } else { filtered.forEach(item => { const totalValue = (item.unit_price || 0) * 1; html += ` `; }); } html += `
Barcode Job Sheet Rack Material Finish Size Thickness Grade Status Qty Unit Price Total Received
πŸ”
No stock items found
${item.barcode} ${item.job_number} ${item.sheet_name} ${item.rack_number} ${item.material_type} ${item.material_finish} ${item.material_size} ${item.material_thickness} ${item.material_grade} ${item.status} 1 AED ${(item.unit_price || 0).toFixed(2)} AED ${totalValue.toFixed(2)} ${new Date(item.received_date).toLocaleDateString()}
Total Items: ${filtered.length}
Total Value: AED ${filtered.reduce((sum, item) => sum + (item.unit_price || 0), 0).toFixed(2)}
Available: ${filtered.filter(s => s.status === 'available').length}
Used: ${filtered.filter(s => s.status === 'used').length}
`; body.innerHTML = html; updateFullscreenFilters(); } function updateFullscreenFilters() { const materialSet = new Set(); const finishSet = new Set(); const sizeSet = new Set(); const thicknessSet = new Set(); const gradeSet = new Set(); const rackSet = new Set(); stockItems.forEach(item => { materialSet.add(item.material_type); finishSet.add(item.material_finish); sizeSet.add(item.material_size); thicknessSet.add(item.material_thickness); gradeSet.add(item.material_grade); rackSet.add(item.rack_number); }); const materialFilter = document.getElementById('fsMaterialFilter'); if (materialFilter) { materialFilter.innerHTML = ''; Array.from(materialSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; materialFilter.appendChild(option); }); } const finishFilter = document.getElementById('fsFinishFilter'); if (finishFilter) { finishFilter.innerHTML = ''; Array.from(finishSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; finishFilter.appendChild(option); }); } const sizeFilter = document.getElementById('fsSizeFilter'); if (sizeFilter) { sizeFilter.innerHTML = ''; Array.from(sizeSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; sizeFilter.appendChild(option); }); } const thicknessFilter = document.getElementById('fsThicknessFilter'); if (thicknessFilter) { thicknessFilter.innerHTML = ''; Array.from(thicknessSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; thicknessFilter.appendChild(option); }); } const gradeFilter = document.getElementById('fsGradeFilter'); if (gradeFilter) { gradeFilter.innerHTML = ''; Array.from(gradeSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; gradeFilter.appendChild(option); }); } const rackFilter = document.getElementById('fsRackFilter'); if (rackFilter) { rackFilter.innerHTML = ''; Array.from(rackSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; rackFilter.appendChild(option); }); } } function filterFullscreenView() { buildFullscreenView(); } function exportFullscreenView() { const filtered = getFilteredStockForFullscreen(); let csv = 'Barcode,Job Number,Sheet Name,Rack Number,Material Type,Finish,Size,Thickness,Grade,Status,Quantity,Unit Price (AED),Total Value (AED),Received Date\n'; filtered.forEach(item => { const totalValue = (item.unit_price || 0) * 1; csv += `"${item.barcode}","${item.job_number}","${item.sheet_name}","${item.rack_number}",`; csv += `"${item.material_type}","${item.material_finish}","${item.material_size}",`; csv += `"${item.material_thickness}","${item.material_grade}","${item.status}",`; csv += `1,${item.unit_price || 0},${totalValue.toFixed(2)},"${item.received_date}"\n`; }); const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `fullscreen_stock_${new Date().toISOString().slice(0,10)}.csv`; a.click(); showMessage(`βœ… Exported ${filtered.length} items`); } function getFilteredStockForFullscreen() { const materialFilter = document.getElementById('fsMaterialFilter')?.value || ''; const finishFilter = document.getElementById('fsFinishFilter')?.value || ''; const sizeFilter = document.getElementById('fsSizeFilter')?.value || ''; const thicknessFilter = document.getElementById('fsThicknessFilter')?.value || ''; const gradeFilter = document.getElementById('fsGradeFilter')?.value || ''; const statusFilter = document.getElementById('fsStatusFilter')?.value || ''; const rackFilter = document.getElementById('fsRackFilter')?.value || ''; const searchFilter = document.getElementById('fsSearch')?.value?.toLowerCase() || ''; let filtered = stockItems; if (materialFilter) filtered = filtered.filter(s => s.material_type === materialFilter); if (finishFilter) filtered = filtered.filter(s => s.material_finish === finishFilter); if (sizeFilter) filtered = filtered.filter(s => s.material_size === sizeFilter); if (thicknessFilter) filtered = filtered.filter(s => s.material_thickness === thicknessFilter); if (gradeFilter) filtered = filtered.filter(s => s.material_grade === gradeFilter); if (statusFilter) filtered = filtered.filter(s => s.status === statusFilter); if (rackFilter) filtered = filtered.filter(s => s.rack_number === rackFilter); if (searchFilter) { filtered = filtered.filter(s => s.barcode.toLowerCase().includes(searchFilter) || s.job_number.toLowerCase().includes(searchFilter) || s.sheet_name.toLowerCase().includes(searchFilter) || s.material_type.toLowerCase().includes(searchFilter) ); } return filtered; } function closeFullscreenModal() { document.getElementById('fullscreenModal').style.display = 'none'; } // ========== COPY/PASTE FUNCTIONS ========== function processPasteData() { const pasteData = document.getElementById('copyPasteArea').value.trim(); if (!pasteData) { showMessage('❌ No data to process'); return; } const format = document.getElementById('pasteFormat').value; const hasHeader = document.getElementById('hasHeader').value === 'yes'; let rows; if (format === 'csv') { rows = pasteData.split('\n').map(row => row.split(',')); } else if (format === 'tab') { rows = pasteData.split('\n').map(row => row.split('\t')); } else { const firstLine = pasteData.split('\n')[0]; if (firstLine.includes('\t')) { rows = pasteData.split('\n').map(row => row.split('\t')); } else if (firstLine.includes(',')) { rows = pasteData.split('\n').map(row => row.split(',')); } else { rows = pasteData.split('\n').map(row => row.split(/\s+/)); } } if (hasHeader) { rows = rows.slice(1); } rows = rows.filter(row => row.length >= 8 && row.some(cell => cell.trim())); if (rows.length === 0) { showMessage('❌ No valid data found'); return; } let addedCount = 0; let errorCount = 0; const errors = []; rows.forEach((row, index) => { try { const jobNumber = (row[0] || '').trim(); const sheetName = (row[1] || '').trim(); const rackNumber = (row[2] || '').trim(); const materialType = (row[3] || '').trim(); const finish = (row[4] || '').trim(); const size = (row[5] || '').trim(); const thickness = (row[6] || '').trim(); const grade = (row[7] || '').trim(); const quantity = parseInt(row[8]) || 1; const unitPrice = parseFloat(row[9]) || 0; if (!jobNumber || !sheetName || !rackNumber || !materialType || !size || !thickness) { errors.push(`Row ${index + 1}: Missing required fields`); errorCount++; return; } const tempOrderId = `PASTE-${Date.now()}-${index}`; const tempItemId = `ITEM-${Date.now()}-${index}`; for (let i = 0; i < quantity; i++) { const timestamp = Date.now().toString().slice(-6); const sequence = (i + 1).toString().padStart(3, '0'); const random = Math.floor(Math.random() * 90 + 10).toString(); const barcodeNumber = `PSTE${timestamp}${sequence}${random}`; const stockItem = { id: `STOCK-${Date.now()}-${i}-${Math.random().toString(36).substr(2, 4)}`, barcode: barcodeNumber, display_barcode: barcodeNumber, order_id: tempOrderId, order_lpo: 'PASTE IMPORT', item_id: tempItemId, job_number: jobNumber, sheet_name: sheetName, rack_number: rackNumber, material_type: materialType, material_finish: finish || 'Not specified', material_size: size.includes('mm') ? size : size + ' mm', material_thickness: thickness.includes('mm') ? thickness : thickness + ' mm', material_grade: grade || 'Not specified', unit_price: unitPrice, received_date: new Date().toISOString(), status: 'available', used_date: null, used_by: null, used_for: null, used_in_job: null, used_in_sheet: null }; stockItems.push(stockItem); addedCount++; } } catch (e) { errors.push(`Row ${index + 1}: ${e.message}`); errorCount++; } }); document.getElementById('pasteStats').innerHTML = `
βœ… Added: ${addedCount} items
${errorCount > 0 ? `
❌ Errors: ${errorCount}
` : ''} `; if (errors.length > 0) { document.getElementById('pasteErrors').innerHTML = errors.map(e => `β€’ ${e}`).join('
'); } else { document.getElementById('pasteErrors').innerHTML = ''; } if (addedCount > 0) { saveData(); updateStockDisplay(); updateMasterDisplay(); calculateOpeningStock(); calculateClosingStock(); showMessage(`βœ… Added ${addedCount} items from paste data`); addLog(`Added ${addedCount} items via copy/paste`); } } function clearPasteArea() { document.getElementById('copyPasteArea').value = ''; document.getElementById('pasteStats').innerHTML = 'No data pasted yet'; document.getElementById('pasteErrors').innerHTML = ''; } function uploadStockFile() { document.getElementById('stockFileUpload').click(); } document.getElementById('stockFileUpload').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { document.getElementById('copyPasteArea').value = e.target.result; showMessage('βœ… File loaded. Click "Process & Add to Stock" to import.'); }; reader.readAsText(file); }); function exportStockValuation() { const openingValue = document.getElementById('openingValue').textContent; const closingValue = document.getElementById('closingValue').textContent; const movement = document.getElementById('stockMovement').innerHTML.replace(/<[^>]*>/g, ''); let csv = 'Stock Valuation Report\n'; csv += `Generated on,${new Date().toLocaleString()}\n`; csv += `Opening Stock Value,${openingValue}\n`; csv += `Closing Stock Value,${closingValue}\n`; csv += `Stock Movement,${movement}\n\n`; csv += 'Barcode,Job Number,Sheet Name,Rack,Material Type,Finish,Size,Thickness,Grade,Status,Unit Price (AED),Total Value (AED),Received Date\n'; stockItems.forEach(item => { csv += `"${item.barcode}","${item.job_number}","${item.sheet_name}","${item.rack_number}",`; csv += `"${item.material_type}","${item.material_finish}","${item.material_size}",`; csv += `"${item.material_thickness}","${item.material_grade}","${item.status}",`; csv += `${item.unit_price || 0},${item.unit_price || 0},"${item.received_date}"\n`; }); const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `stock_valuation_${new Date().toISOString().slice(0,10)}.csv`; a.click(); showMessage(`βœ… Stock valuation exported`); } // ========== BARCODE SCANNER FUNCTIONS ========== function setupBarcodeScanner() { const scanInput = document.getElementById('barcodeScan'); scanInput.removeEventListener('keypress', handleScannerKeyPress); scanInput.removeEventListener('keydown', handleScannerKeyDown); scanInput.addEventListener('keypress', handleScannerKeyPress); scanInput.addEventListener('keydown', handleScannerKeyDown); scanInput.focus(); } function handleScannerKeyPress(e) { const currentTime = new Date().getTime(); if (currentTime - lastScanTime < 50) { scanBuffer += e.key; } else { scanBuffer = e.key; } lastScanTime = currentTime; if (scanTimer) { clearTimeout(scanTimer); } scanTimer = setTimeout(() => { if (scanBuffer.length >= 3) { processScannedBarcode(scanBuffer); scanBuffer = ''; } }, 100); } function handleScannerKeyDown(e) { if (e.key === 'Enter') { e.preventDefault(); if (scanBuffer.length >= 3) { processScannedBarcode(scanBuffer); scanBuffer = ''; } else { const barcode = document.getElementById('barcodeScan').value.trim(); if (barcode) { processScannedBarcode(barcode); } } if (scanTimer) { clearTimeout(scanTimer); scanTimer = null; } document.getElementById('barcodeScan').value = ''; } } function processScannedBarcode(barcode) { let cleanBarcode = barcode.replace(/[^A-Z0-9]/gi, '').toUpperCase(); if (!cleanBarcode || cleanBarcode.length < 3) { showMessage('❌ Invalid barcode format'); document.getElementById('barcodeScan').focus(); return; } let stockItem = stockItems.find(item => item.barcode === cleanBarcode); if (!stockItem) { stockItem = stockItems.find(item => cleanBarcode.includes(item.barcode) || item.barcode.includes(cleanBarcode) ); } if (!stockItem) { showMessage('❌ Barcode not found in system'); addScanToHistory(cleanBarcode, 'NOT FOUND'); document.getElementById('barcodeScan').value = ''; document.getElementById('barcodeScan').focus(); return; } displayScanResult(stockItem); addScanToHistory(stockItem.barcode, stockItem.status === 'used' ? 'USED' : 'AVAILABLE'); document.getElementById('barcodeScan').value = ''; document.getElementById('barcodeScan').focus(); } function scanBarcode() { const barcodeInput = document.getElementById('barcodeScan'); const barcode = barcodeInput.value.trim().toUpperCase(); if (!barcode) { showMessage('❌ Please enter a barcode'); return; } processScannedBarcode(barcode); } function clearScan() { document.getElementById('barcodeScan').value = ''; document.getElementById('scanResult').style.display = 'none'; document.getElementById('scanResult').innerHTML = ''; document.getElementById('barcodeScan').focus(); } function displayScanResult(stockItem) { const resultDiv = document.getElementById('scanResult'); resultDiv.style.display = 'block'; const totalValue = (stockItem.unit_price || 0).toFixed(2); if (stockItem.status === 'used') { resultDiv.innerHTML = `

⚠️ WARNING - STOCK ALREADY USED

Barcode: ${stockItem.barcode}

Material: ${stockItem.material_type} - ${stockItem.material_finish}

Unit Price: AED ${totalValue}

Used in Job: ${stockItem.used_in_job || 'Not specified'}

Used in Sheet: ${stockItem.used_in_sheet || 'Not specified'}

Used on: ${new Date(stockItem.used_date).toLocaleString()}

Used for: ${stockItem.used_for || 'Not specified'}

Used by: ${stockItem.used_by || 'Unknown'}

`; } else { resultDiv.innerHTML = `

βœ… STOCK AVAILABLE

Barcode: ${stockItem.barcode}

Material: ${stockItem.material_type} - ${stockItem.material_finish}

Unit Price: AED ${totalValue}

Original Job: ${stockItem.job_number}

Original Sheet: ${stockItem.sheet_name}

Current Rack: ${stockItem.rack_number}

Received: ${new Date(stockItem.received_date).toLocaleDateString()}

πŸ“‹ USAGE DETAILS
`; } } function markStockUsed(stockId) { const reason = document.getElementById('useReason')?.value || 'Production use'; const usedBy = document.getElementById('usedBy')?.value || 'System User'; const jobNumber = document.getElementById('jobNumberInput')?.value || ''; const sheetName = document.getElementById('sheetNameInput')?.value || ''; const stockItem = stockItems.find(item => item.id === stockId); if (!stockItem) { showMessage('❌ Stock item not found'); return; } if (!jobNumber) { const jobPrompt = prompt('⚠️ JOB NUMBER is required!\n\nEnter the JOB NUMBER where this material is being used:', stockItem.job_number || ''); if (!jobPrompt) { showMessage('❌ Job number is required to mark stock as used'); return; } stockItem.used_in_job = jobPrompt; } else { stockItem.used_in_job = jobNumber; } if (!sheetName) { const sheetPrompt = prompt('⚠️ SHEET NAME is required!\n\nEnter the SHEET NAME where this material is being used:', stockItem.sheet_name || ''); if (!sheetPrompt) { showMessage('❌ Sheet name is required to mark stock as used'); return; } stockItem.used_in_sheet = sheetPrompt; } else { stockItem.used_in_sheet = sheetName; } stockItem.status = 'used'; stockItem.used_date = new Date().toISOString(); stockItem.used_for = reason; stockItem.used_by = usedBy; saveData(); updateStockDisplay(); updateMasterDisplay(); calculateOpeningStock(); calculateClosingStock(); showMessage(`βœ… Stock marked as used in Job: ${stockItem.used_in_job}`); addLog(`Stock used: ${stockItem.barcode} - Job: ${stockItem.used_in_job} - ${reason}`); const resultDiv = document.getElementById('scanResult'); resultDiv.innerHTML = `

⚠️ STOCK MARKED AS USED

Barcode: ${stockItem.barcode}

Material: ${stockItem.material_type} - ${stockItem.material_finish}

Used in Job: ${stockItem.used_in_job}

Used in Sheet: ${stockItem.used_in_sheet}

Used for: ${reason}

Used by: ${usedBy}

Time: ${new Date().toLocaleString()}

`; addScanToHistory(stockItem.barcode, 'USED'); } function addScanToHistory(barcode, status) { const scanRecord = { id: Date.now().toString(), barcode: barcode, status: status, timestamp: new Date().toISOString() }; scanHistory.unshift(scanRecord); if (scanHistory.length > 50) { scanHistory = scanHistory.slice(0, 50); } updateScanHistory(); } function updateScanHistory() { const container = document.getElementById('recentScans'); if (scanHistory.length === 0) { container.innerHTML = `
πŸ“
No recent scans
`; return; } let html = ''; scanHistory.forEach(scan => { const statusColor = scan.status === 'AVAILABLE' ? '#10b981' : scan.status === 'NOT FOUND' ? '#ef4444' : scan.status === 'USED' ? '#991b1b' : '#f59e0b'; html += `
${scan.barcode} ${scan.status}
${new Date(scan.timestamp).toLocaleString()}
`; }); container.innerHTML = html; } // ========== BARCODE GENERATION FUNCTIONS ========== function generateBarcode(orderId, itemId, qtyReceived) { const order = orders.find(o => o.id === orderId); if (!order) return []; const item = order.material_items.find(i => i.id === itemId); if (!item) return []; const barcodes = []; for (let i = 0; i < qtyReceived; i++) { const lpoClean = order.lpo.replace(/[^A-Z0-9]/g, '').toUpperCase().slice(-4).padStart(4, '0'); const itemClean = item.s_no.toString().padStart(2, '0'); const seqClean = (i + 1).toString().padStart(3, '0'); const randomClean = Math.floor(Math.random() * 90 + 10).toString(); const barcodeNumber = lpoClean + itemClean + seqClean + randomClean; const stockItem = { id: `STOCK-${Date.now()}-${i}-${Math.random().toString(36).substr(2, 4)}`, barcode: barcodeNumber, display_barcode: barcodeNumber, order_id: orderId, order_lpo: order.lpo, item_id: itemId, job_number: order.job_number || 'N/A', sheet_name: order.sheet_name || 'N/A', rack_number: order.rack_number || 'N/A', material_type: item.type, material_finish: item.finish, material_size: item.size, material_thickness: item.thickness, material_grade: item.grade, unit_price: item.unit_price || 0, received_date: new Date().toISOString(), status: 'available', used_date: null, used_by: null, used_for: null, used_in_job: null, used_in_sheet: null }; stockItems.push(stockItem); barcodes.push(stockItem); addLog(`Generated barcode: ${barcodeNumber} for ${item.type} (AED ${item.unit_price || 0})`); } return barcodes; } // ========== STOCK MANAGEMENT FUNCTIONS ========== function addOldStock() { const jobNumber = document.getElementById('oldStockJob').value.trim(); const sheetName = document.getElementById('oldStockSheet').value.trim(); const rackNumber = document.getElementById('oldStockRack').value.trim(); const materialType = document.getElementById('oldStockType').value; const finish = document.getElementById('oldStockFinish').value; const size = document.getElementById('oldStockSize').value.trim(); const thickness = document.getElementById('oldStockThickness').value.trim(); const grade = document.getElementById('oldStockGrade').value.trim(); const quantity = parseInt(document.getElementById('oldStockQty').value) || 0; const unitPrice = parseFloat(document.getElementById('oldStockPrice').value) || 0; const remarks = document.getElementById('oldStockRemarks').value.trim(); if (!jobNumber || !sheetName || !rackNumber) { showMessage('❌ Please fill Job Number, Sheet Name, and Rack Number'); return; } if (!materialType || !size || !thickness || !quantity) { showMessage('❌ Please fill material details and quantity'); return; } const tempOrderId = `OLD-${Date.now()}`; const tempItemId = `ITEM-${Date.now()}`; const tempItem = { id: tempItemId, s_no: 1, type: materialType, finish: finish || 'Not specified', size: size + ' mm', thickness: thickness + ' mm', grade: grade || 'Not specified', afp: 'NON_AFP', ordered_qty: quantity, received_qty: quantity, pending_qty: 0, unit_price: unitPrice, remarks: remarks || 'Old stock addition' }; for (let i = 0; i < quantity; i++) { const timestamp = Date.now().toString().slice(-6); const sequence = (i + 1).toString().padStart(3, '0'); const random = Math.floor(Math.random() * 90 + 10).toString(); const barcodeNumber = `OLD${timestamp}${sequence}${random}`; const stockItem = { id: `STOCK-${Date.now()}-${i}`, barcode: barcodeNumber, display_barcode: barcodeNumber, order_id: tempOrderId, order_lpo: 'OLD STOCK', item_id: tempItemId, job_number: jobNumber, sheet_name: sheetName, rack_number: rackNumber, material_type: materialType, material_finish: finish || 'Not specified', material_size: size + ' mm', material_thickness: thickness + ' mm', material_grade: grade || 'Not specified', unit_price: unitPrice, received_date: new Date().toISOString(), status: 'available', used_date: null, used_by: null, used_for: null, used_in_job: null, used_in_sheet: null }; stockItems.push(stockItem); } saveData(); updateStockDisplay(); updateMasterDisplay(); calculateOpeningStock(); calculateClosingStock(); document.getElementById('oldStockJob').value = ''; document.getElementById('oldStockSheet').value = ''; document.getElementById('oldStockRack').value = ''; document.getElementById('oldStockType').value = ''; document.getElementById('oldStockFinish').value = ''; document.getElementById('oldStockSize').value = ''; document.getElementById('oldStockThickness').value = ''; document.getElementById('oldStockGrade').value = ''; document.getElementById('oldStockQty').value = ''; document.getElementById('oldStockPrice').value = ''; document.getElementById('oldStockRemarks').value = ''; document.getElementById('oldStockTotal').value = ''; showMessage(`βœ… Added ${quantity} old stock items with barcodes (Total: AED ${(quantity * unitPrice).toFixed(2)})`); addLog(`Added ${quantity} old stock items to rack ${rackNumber}`); } function updateStockDisplay() { const stockBody = document.getElementById('stockBody'); const totalStockItems = document.getElementById('totalStockItems'); const availableStockItems = document.getElementById('availableStockItems'); const usedStockItems = document.getElementById('usedStockItems'); const uniqueMaterials = document.getElementById('uniqueMaterials'); const totalValue = document.getElementById('totalValue'); if (stockItems.length === 0) { stockBody.innerHTML = `
Loading stock...
`; setTimeout(() => { if (stockItems.length === 0) { stockBody.innerHTML = `
πŸ“¦
No stock items found
Add stock via "Stock Management" tab or create an LPO with received quantities
`; } }, 500); return; } const available = stockItems.filter(s => s.status === 'available').length; const used = stockItems.filter(s => s.status === 'used').length; const unique = [...new Set(stockItems.map(s => `${s.material_type}-${s.material_finish}-${s.material_thickness}` ))].length; const totalStockValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); totalStockItems.textContent = stockItems.length; availableStockItems.textContent = available; usedStockItems.textContent = used; uniqueMaterials.textContent = unique; totalValue.textContent = 'AED ' + totalStockValue.toFixed(2); updateStockFilters(); const materialFilter = document.getElementById('filterMaterial').value; const finishFilter = document.getElementById('filterFinish').value; const sizeFilter = document.getElementById('filterSize').value; const thicknessFilter = document.getElementById('filterThickness').value; const gradeFilter = document.getElementById('filterGrade').value; const statusFilter = document.getElementById('filterStockStatus').value; const rackFilter = document.getElementById('filterRack').value; const searchFilter = document.getElementById('stockSearch').value.toLowerCase(); let filtered = stockItems; if (materialFilter) { filtered = filtered.filter(s => s.material_type === materialFilter); } if (finishFilter) { filtered = filtered.filter(s => s.material_finish === finishFilter); } if (sizeFilter) { filtered = filtered.filter(s => s.material_size === sizeFilter); } if (thicknessFilter) { filtered = filtered.filter(s => s.material_thickness === thicknessFilter); } if (gradeFilter) { filtered = filtered.filter(s => s.material_grade === gradeFilter); } if (statusFilter) { filtered = filtered.filter(s => s.status === statusFilter); } if (rackFilter) { filtered = filtered.filter(s => s.rack_number === rackFilter); } if (searchFilter) { filtered = filtered.filter(s => s.barcode.toLowerCase().includes(searchFilter) || s.job_number.toLowerCase().includes(searchFilter) || s.sheet_name.toLowerCase().includes(searchFilter) || (s.used_in_job && s.used_in_job.toLowerCase().includes(searchFilter)) ); } if (filtered.length === 0) { stockBody.innerHTML = `
πŸ”
No items match filters
`; return; } const groupedStock = {}; filtered.forEach(stock => { const key = `${stock.material_type}|${stock.material_finish}|${stock.material_size}|${stock.material_thickness}|${stock.material_grade}`; if (!groupedStock[key]) { groupedStock[key] = { type: stock.material_type, finish: stock.material_finish, size: stock.material_size, thickness: stock.material_thickness, grade: stock.material_grade, items: [], total: 0, available: 0, used: 0, total_value: 0, unit_price: stock.unit_price || 0, last_received: stock.received_date, racks: new Set() }; } groupedStock[key].items.push(stock); groupedStock[key].total++; if (stock.status === 'available') { groupedStock[key].available++; } else { groupedStock[key].used++; } groupedStock[key].total_value += (stock.unit_price || 0); groupedStock[key].racks.add(stock.rack_number); if (new Date(stock.received_date) > new Date(groupedStock[key].last_received)) { groupedStock[key].last_received = stock.received_date; } }); stockBody.innerHTML = Object.values(groupedStock).map(group => `
${group.items[0].barcode}
+${group.items.length-1} more
Job: ${group.items[0].job_number}
Sheet: ${group.items[0].sheet_name}
Racks: ${Array.from(group.racks).join(', ')}
${group.items[0].used_in_job ? `
Used In: ${group.items[0].used_in_job}
` : ''} ${group.type} ${group.finish} ${group.size} ${group.thickness} ${group.grade} ${group.total} ${group.available} ${group.used} AED ${group.unit_price.toFixed(2)} AED ${group.total_value.toFixed(2)} ${new Date(group.last_received).toLocaleDateString()} `).join(''); } function updateStockFilters() { const materialSet = new Set(); const finishSet = new Set(); const sizeSet = new Set(); const thicknessSet = new Set(); const gradeSet = new Set(); const rackSet = new Set(); stockItems.forEach(item => { materialSet.add(item.material_type); finishSet.add(item.material_finish); sizeSet.add(item.material_size); thicknessSet.add(item.material_thickness); gradeSet.add(item.material_grade); rackSet.add(item.rack_number); }); const materialFilter = document.getElementById('filterMaterial'); materialFilter.innerHTML = ''; Array.from(materialSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; materialFilter.appendChild(option); }); const finishFilter = document.getElementById('filterFinish'); finishFilter.innerHTML = ''; Array.from(finishSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; finishFilter.appendChild(option); }); const sizeFilter = document.getElementById('filterSize'); sizeFilter.innerHTML = ''; Array.from(sizeSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; sizeFilter.appendChild(option); }); const thicknessFilter = document.getElementById('filterThickness'); thicknessFilter.innerHTML = ''; Array.from(thicknessSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; thicknessFilter.appendChild(option); }); const gradeFilter = document.getElementById('filterGrade'); gradeFilter.innerHTML = ''; Array.from(gradeSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; gradeFilter.appendChild(option); }); const rackFilter = document.getElementById('filterRack'); rackFilter.innerHTML = ''; Array.from(rackSet).sort().forEach(val => { const option = document.createElement('option'); option.value = val; option.textContent = val; rackFilter.appendChild(option); }); } function filterStockTable() { updateStockDisplay(); } function viewStockDetails(stockId) { const stock = stockItems.find(s => s.id === stockId); if (!stock) return; const modal = document.getElementById('barcodeModal'); const body = document.getElementById('barcodeModalBody'); body.innerHTML = `
*${stock.barcode}*
${stock.barcode}

Stock Details

${stock.status === 'used' ? ` ` : ''}
Job Number: ${stock.job_number}
Sheet Name: ${stock.sheet_name}
Rack Number: ${stock.rack_number}
Material: ${stock.material_type}
Finish: ${stock.material_finish}
Size: ${stock.material_size}
Thickness: ${stock.material_thickness}
Grade: ${stock.material_grade}
Unit Price: AED ${(stock.unit_price || 0).toFixed(2)}
Status: ${stock.status}
Received Date: ${new Date(stock.received_date).toLocaleString()}
Used Date: ${new Date(stock.used_date).toLocaleString()}
Used In Job: ${stock.used_in_job}
Used In Sheet: ${stock.used_in_sheet}
Used For: ${stock.used_for}
Used By: ${stock.used_by}
`; modal.style.display = 'flex'; } function printGroupBarcodes(type, finish, size, thickness, grade) { const groupStock = stockItems.filter(s => s.material_type === type && s.material_finish === finish && s.material_size === size && s.material_thickness === thickness && s.material_grade === grade && s.status === 'available' ); if (groupStock.length === 0) { showMessage('❌ No available stock found'); return; } const printWindow = window.open('', '_blank'); printWindow.document.write(` Barcode Labels - ${type} ${finish}
`); groupStock.forEach(stock => { const barcodeWithStars = `*${stock.barcode}*`; printWindow.document.write(`
${barcodeWithStars}
${stock.barcode}
${stock.material_type} - ${stock.material_finish}
${stock.material_size} | ${stock.material_thickness}
Job: ${stock.job_number}
Sheet: ${stock.sheet_name}
Rack: ${stock.rack_number}
AED ${(stock.unit_price || 0).toFixed(2)}
`); }); printWindow.document.write(`
`); printWindow.document.close(); setTimeout(() => printWindow.print(), 500); } function printSingleBarcode(stockId) { const stock = stockItems.find(s => s.id === stockId); if (!stock) return; const printWindow = window.open('', '_blank'); printWindow.document.write(` Barcode - ${stock.barcode}
*${stock.barcode}*
${stock.barcode}
AED ${(stock.unit_price || 0).toFixed(2)}
Job: ${stock.job_number}
Sheet: ${stock.sheet_name}
Rack: ${stock.rack_number}
Material: ${stock.material_type} - ${stock.material_finish}
Size: ${stock.material_size}
Thickness: ${stock.material_thickness}
Grade: ${stock.material_grade}
Date: ${new Date(stock.received_date).toLocaleDateString()}
`); printWindow.document.close(); setTimeout(() => printWindow.print(), 500); } function printBarcodes() { showBarcodePrintOptions(); } function printOrderBarcodes(orderId) { const orderStock = stockItems.filter(s => s.order_id === orderId && s.status === 'available'); if (orderStock.length === 0) { showMessage('❌ No available barcodes for this order'); return; } printBarcodesWithFormat(orderStock, 'labels'); } function closeBarcodeModal() { document.getElementById('barcodeModal').style.display = 'none'; } function closePrintModal() { document.getElementById('printBarcodeModal').style.display = 'none'; } function closeLpoPreview() { document.getElementById('lpoPreviewModal').style.display = 'none'; } function refreshStock() { updateStockDisplay(); showMessage('βœ… Stock display refreshed'); } // ========== RACK MANAGEMENT ========== function addRack() { const rackNumber = document.getElementById('newRackNumber').value.trim(); const rackLocation = document.getElementById('newRackLocation').value.trim(); const rackDesc = document.getElementById('newRackDesc').value.trim(); if (!rackNumber || !rackLocation) { showMessage('❌ Please fill Rack Number and Location'); return; } if (racks.some(r => r.number === rackNumber)) { showMessage('❌ Rack number already exists'); return; } const newRack = { id: `rack-${Date.now()}`, number: rackNumber, location: rackLocation, description: rackDesc || 'No description' }; racks.push(newRack); saveData(); updateRacksDisplay(); updateRackFilter(); document.getElementById('newRackNumber').value = ''; document.getElementById('newRackLocation').value = ''; document.getElementById('newRackDesc').value = ''; showMessage(`βœ… Rack added: ${rackNumber}`); addLog(`Added new rack: ${rackNumber}`); } function updateRacksDisplay() { const container = document.getElementById('racksList'); if (racks.length === 0) { container.innerHTML = `
πŸ—οΈ
No racks added yet
`; return; } const rackStockCount = {}; const rackStockValue = {}; stockItems.forEach(stock => { if (stock.rack_number) { rackStockCount[stock.rack_number] = (rackStockCount[stock.rack_number] || 0) + 1; rackStockValue[stock.rack_number] = (rackStockValue[stock.rack_number] || 0) + (stock.unit_price || 0); } }); container.innerHTML = racks.map(rack => { const stockCount = rackStockCount[rack.number] || 0; const availableCount = stockItems.filter(s => s.rack_number === rack.number && s.status === 'available' ).length; const stockValue = rackStockValue[rack.number] || 0; return `
${rack.number}
Location: ${rack.location}
${rack.description}
πŸ“¦ ${stockCount} βœ… ${availableCount}
Value: AED ${stockValue.toFixed(2)}
`; }).join(''); } function selectRack(rackNumber) { switchTab('stock'); document.getElementById('filterRack').value = rackNumber; filterStockTable(); } function updateRackFilter() { const filter = document.getElementById('filterRack'); filter.innerHTML = ''; racks.forEach(rack => { const option = document.createElement('option'); option.value = rack.number; option.textContent = rack.number; filter.appendChild(option); }); } // ========== MATERIAL ITEMS FUNCTIONS ========== function toggleDetailedMaterial() { const detailSection = document.getElementById('detailedMaterial'); const toggleIcon = document.getElementById('detailToggle'); if (detailSection.style.display === 'none' || detailSection.style.display === '') { detailSection.style.display = 'block'; toggleIcon.textContent = 'β–²'; } else { detailSection.style.display = 'none'; toggleIcon.textContent = 'β–Ό'; } } function applyTemplateSS() { document.getElementById('matType').value = 'SS'; document.getElementById('matFinish').value = 'BRUSHED'; document.getElementById('matSize').value = '1219*2438'; document.getElementById('matThickness').value = '1.0'; document.getElementById('matGrade').value = '304'; document.getElementById('matAFP').value = 'NON_AFP'; document.getElementById('matUnitPrice').value = '450'; document.getElementById('matVAT').value = '5'; updateItemCalculations(); showMessage('βœ… SS Sheet template applied (AED 450)'); } function applyTemplateMS() { document.getElementById('matType').value = 'MS'; document.getElementById('matFinish').value = 'PAINTED'; document.getElementById('matSize').value = '1500*3000'; document.getElementById('matThickness').value = '2.5'; document.getElementById('matGrade').value = 'A36'; document.getElementById('matAFP').value = 'NON_AFP'; document.getElementById('matUnitPrice').value = '320'; document.getElementById('matVAT').value = '5'; updateItemCalculations(); showMessage('βœ… MS Plate template applied (AED 320)'); } function checkTemplate() { const type = document.getElementById('matType').value; if (type === 'SS') { applyTemplateSS(); } else if (type === 'MS') { applyTemplateMS(); } } function addMaterialItem() { const type = document.getElementById('matType').value.trim(); const finish = document.getElementById('matFinish').value.trim(); const size = document.getElementById('matSize').value.trim(); const thickness = document.getElementById('matThickness').value.trim(); const grade = document.getElementById('matGrade').value.trim(); const afp = document.getElementById('matAFP').value; const orderedQty = parseInt(document.getElementById('matOrderedQty').value) || 0; const receivedQty = parseInt(document.getElementById('matReceivedQty').value) || 0; const unitPrice = parseFloat(document.getElementById('matUnitPrice').value) || 0; const vat = parseFloat(document.getElementById('matVAT').value) || 0; const remarks = document.getElementById('matRemarks').value.trim(); if (!type || !size || !thickness || !orderedQty) { showMessage('❌ Please fill Type, Size, Thickness, and Ordered Quantity'); return; } const pendingQty = orderedQty - receivedQty; let totalPrice = unitPrice * orderedQty; if (vat > 0) { totalPrice = totalPrice * (1 + vat / 100); } const item = { id: Date.now().toString() + Math.random().toString(36).substr(2, 9), s_no: materialItems.length + 1, type: type, finish: finish || 'Not specified', size: size + ' mm', thickness: thickness + ' mm', grade: grade || 'Not specified', afp: afp || 'Not specified', ordered_qty: orderedQty, received_qty: receivedQty, pending_qty: pendingQty > 0 ? pendingQty : 0, unit_price: unitPrice, vat: vat, total_price: totalPrice, remarks: remarks || 'No remarks', status: receivedQty === 0 ? 'ORDERED' : receivedQty === orderedQty ? 'RECEIVED' : 'PARTIAL', added: new Date().toISOString() }; materialItems.push(item); updateMaterialItemsDisplay(); clearMaterialInputs(); document.getElementById('matType').focus(); showMessage('βœ… Item added to list'); } function deleteMaterialItem(id) { materialItems = materialItems.filter(item => item.id !== id); materialItems.forEach((item, index) => { item.s_no = index + 1; }); updateMaterialItemsDisplay(); } function clearMaterialInputs() { document.getElementById('matType').value = ''; document.getElementById('matFinish').value = ''; document.getElementById('matSize').value = ''; document.getElementById('matThickness').value = ''; document.getElementById('matGrade').value = ''; document.getElementById('matAFP').value = ''; document.getElementById('matOrderedQty').value = ''; document.getElementById('matReceivedQty').value = ''; document.getElementById('matUnitPrice').value = ''; document.getElementById('matVAT').value = '5'; document.getElementById('matTotalPrice').value = ''; document.getElementById('matRemarks').value = ''; } function clearAllMaterialItems() { if (materialItems.length === 0) return; if (confirm(`Clear all ${materialItems.length} material items? This cannot be undone.`)) { materialItems = []; updateMaterialItemsDisplay(); showMessage('βœ… All material items cleared'); addLog('Cleared all material items'); } } function updateMaterialItemsDisplay() { const container = document.getElementById('itemsContainer'); const emptyState = document.getElementById('emptyItems'); if (materialItems.length === 0) { container.innerHTML = ''; container.appendChild(emptyState); emptyState.style.display = 'block'; return; } emptyState.style.display = 'none'; let html = ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; materialItems.forEach(item => { html += ``; }); html += '
S.NoTypeFinishSizeThicknessGradeAFPOrderedReceivedPendingUnit PriceTotal (AED)Action
${item.s_no} ${item.type} ${item.finish} ${item.size} ${item.thickness} ${item.grade} ${item.afp} ${item.ordered_qty} ${item.received_qty} ${item.pending_qty} AED ${item.unit_price.toFixed(2)} AED ${item.total_price.toFixed(2)}
'; container.innerHTML = html; } // ========== ORDER FUNCTIONS ========== function addOrder() { const lpoInput = document.getElementById('lpoNumber'); const supplierInput = document.getElementById('supplier'); const lpo = lpoInput.value.trim(); const supplier = supplierInput.value.trim(); const jobNumber = document.getElementById('jobNumber').value.trim(); const sheetName = document.getElementById('sheetName').value.trim(); const rackNumber = document.getElementById('rackNumber').value.trim(); const oldStockOption = document.getElementById('oldStockOption').value; const orderDate = document.getElementById('orderDate').value; const readyDate = document.getElementById('readyDate').value; const receivedDate = document.getElementById('receivedDate').value; const deliveryLocation = document.getElementById('deliveryLocation').value; const paymentTerms = document.getElementById('paymentTerms').value; const shipmentPref = document.getElementById('shipmentPref').value; const partialDelivery = document.getElementById('partialDelivery').value; const ourTRN = document.getElementById('ourTRN').value; if (!lpo) { showMessage('❌ Please enter LPO Number'); lpoInput.focus(); return; } if (!supplier) { showMessage('❌ Please enter Supplier Name'); supplierInput.focus(); return; } if (materialItems.length === 0) { showMessage('❌ Please add at least one material item'); return; } if (orders.some(order => order.lpo === lpo)) { showMessage('❌ LPO number already exists'); lpoInput.focus(); lpoInput.select(); return; } let totalOrdered = 0; let totalReceived = 0; let totalPending = 0; let totalValue = 0; let subtotal = 0; materialItems.forEach(item => { totalOrdered += item.ordered_qty; totalReceived += item.received_qty; totalPending += item.pending_qty; totalValue += item.total_price || 0; subtotal += (item.unit_price || 0) * item.ordered_qty; }); let overallStatus = 'ORDERED'; if (totalReceived === totalOrdered) { overallStatus = 'RECEIVED'; } else if (totalReceived > 0) { overallStatus = 'PARTIAL'; } const newOrder = { id: Date.now().toString() + Math.random().toString(36).substr(2, 9), lpo: lpo, supplier: supplier, job_number: jobNumber, sheet_name: sheetName, rack_number: rackNumber, old_stock_option: oldStockOption, order_date: orderDate, ready_date: readyDate, received_date: receivedDate, delivery_location: deliveryLocation, payment_terms: paymentTerms, shipment_pref: shipmentPref, partial_delivery: partialDelivery, our_trn: ourTRN, material_items: JSON.parse(JSON.stringify(materialItems)), total_ordered: totalOrdered, total_received: totalReceived, total_pending: totalPending, total_value: totalValue, subtotal: subtotal, status: overallStatus, created: new Date().toISOString(), updated: new Date().toISOString() }; orders.unshift(newOrder); filteredOrders = [...orders]; if (totalReceived > 0) { newOrder.material_items.forEach(item => { if (item.received_qty > 0) { const barcodes = generateBarcode(newOrder.id, item.id, item.received_qty); addLog(`Generated ${barcodes.length} barcodes for ${item.type} (Total: AED ${(item.unit_price * item.received_qty).toFixed(2)})`); } }); } updateDisplay(); updateStats(); updateJobFilter(); updateMasterDisplay(); calculateOpeningStock(); calculateClosingStock(); saveData(); clearForm(); showMessage(`βœ… Material order saved: ${lpo} (Total: AED ${totalValue.toFixed(2)})`); addLog(`Added material order: ${lpo} - ${supplier}`); } function clearForm() { document.getElementById('lpoNumber').value = ''; document.getElementById('supplier').value = ''; document.getElementById('supplierSelect').value = ''; document.getElementById('supplierDetails').style.display = 'none'; document.getElementById('jobNumber').value = ''; document.getElementById('sheetName').value = ''; document.getElementById('rackNumber').value = ''; document.getElementById('orderDate').value = new Date().toISOString().split('T')[0]; document.getElementById('readyDate').value = ''; document.getElementById('receivedDate').value = ''; materialItems = []; updateMaterialItemsDisplay(); document.getElementById('detailedMaterial').style.display = 'none'; document.getElementById('detailToggle').textContent = 'β–Ό'; clearMaterialInputs(); document.getElementById('lpoNumber').focus(); } // ========== QUANTITY UPDATE FUNCTIONS ========== function updateReceivedQuantity(orderId) { currentOrderId = orderId; const order = orders.find(o => o.id === orderId); if (!order) return; currentOrder = order; let content = ''; let totalOrdered = 0; let totalReceived = 0; let totalValue = 0; order.material_items.forEach((item, index) => { totalOrdered += item.ordered_qty; totalReceived += item.received_qty; totalValue += (item.unit_price || 0) * item.received_qty; content += `

${item.type} - ${item.finish}

Size: ${item.size} | Thick: ${item.thickness} | Grade: ${item.grade} Unit Price: AED ${(item.unit_price || 0).toFixed(2)}
Ordered: ${item.ordered_qty} | Received: ${item.received_qty} | Pending: ${item.pending_qty}
/ ${item.ordered_qty}
`; }); document.getElementById('qtyModalContent').innerHTML = content; document.getElementById('totalOrderedQty').textContent = totalOrdered; document.getElementById('totalReceivedQty').textContent = totalReceived; document.getElementById('totalPendingQty').textContent = totalOrdered - totalReceived; document.getElementById('qtyModal').style.display = 'flex'; } function updateQtyTotal() { if (!currentOrder) return; let totalOrdered = 0; let totalReceived = 0; currentOrder.material_items.forEach(item => { const input = document.getElementById(`qty_${item.id}`); const receivedQty = parseInt(input.value) || 0; totalOrdered += item.ordered_qty; totalReceived += receivedQty; }); document.getElementById('totalReceivedQty').textContent = totalReceived; document.getElementById('totalPendingQty').textContent = totalOrdered - totalReceived; } function saveQuantityUpdates() { if (!currentOrder) return; let changesMade = false; let totalNewReceived = 0; let totalNewValue = 0; currentOrder.material_items.forEach(item => { const input = document.getElementById(`qty_${item.id}`); const newReceivedQty = parseInt(input.value) || 0; if (newReceivedQty !== item.received_qty) { const additionalQty = newReceivedQty - item.received_qty; if (additionalQty > 0) { const barcodes = generateBarcode(currentOrder.id, item.id, additionalQty); const additionalValue = (item.unit_price || 0) * additionalQty; totalNewValue += additionalValue; showMessage(`βœ… Generated ${barcodes.length} barcodes for ${item.type} (Value: AED ${additionalValue.toFixed(2)})`); } item.received_qty = newReceivedQty; item.pending_qty = item.ordered_qty - newReceivedQty; item.status = newReceivedQty === 0 ? 'ORDERED' : newReceivedQty === item.ordered_qty ? 'RECEIVED' : 'PARTIAL'; changesMade = true; } }); if (changesMade) { currentOrder.total_received = currentOrder.material_items.reduce((sum, item) => sum + item.received_qty, 0); currentOrder.total_pending = currentOrder.total_ordered - currentOrder.total_received; if (currentOrder.total_received === currentOrder.total_ordered) { currentOrder.status = 'RECEIVED'; if (!currentOrder.received_date) { currentOrder.received_date = new Date().toISOString().split('T')[0]; } } else if (currentOrder.total_received > 0) { currentOrder.status = 'PARTIAL'; } else { currentOrder.status = 'ORDERED'; } currentOrder.updated = new Date().toISOString(); saveData(); updateDisplay(); updateStats(); updateStockDisplay(); updateMasterDisplay(); calculateOpeningStock(); calculateClosingStock(); showMessage(`βœ… Quantities updated for ${currentOrder.lpo} (Added value: AED ${totalNewValue.toFixed(2)})`); addLog(`Updated quantities for ${currentOrder.lpo}`); closeQtyModal(); } else { showMessage('⚠️ No changes made'); } } function closeQtyModal() { document.getElementById('qtyModal').style.display = 'none'; currentOrderId = null; currentOrder = null; } function updateOrderStatus(id, newStatus) { const order = orders.find(o => o.id === id); if (order) { const oldStatus = order.status; order.status = newStatus; order.updated = new Date().toISOString(); if (newStatus === 'READY' && !order.ready_date) { order.ready_date = new Date().toISOString().split('T')[0]; } else if (newStatus === 'RECEIVED' && !order.received_date) { order.received_date = new Date().toISOString().split('T')[0]; } saveData(); updateDisplay(); showMessage(`βœ… Status updated: ${oldStatus} β†’ ${newStatus}`); addLog(`Updated order ${order.lpo}: ${oldStatus} β†’ ${newStatus}`); } } function deleteOrder(id) { const order = orders.find(o => o.id === id); if (!order) return; if (confirm(`Are you sure you want to delete order:\n\n${order.lpo} - ${order.supplier}\nJob: ${order.job_number}\nTotal Value: AED ${order.total_value?.toFixed(2) || '0.00'}\n\nThis action cannot be undone!`)) { orders = orders.filter(o => o.id !== id); filteredOrders = filteredOrders.filter(o => o.id !== id); saveData(); updateDisplay(); updateStats(); updateJobFilter(); updateMasterDisplay(); calculateOpeningStock(); calculateClosingStock(); showMessage(`βœ… Order deleted: ${order.lpo}`); addLog(`Deleted order: ${order.lpo} - ${order.supplier}`); } } // ========== DISPLAY FUNCTIONS ========== function filterOrders() { const search = document.getElementById('search').value.toLowerCase(); const jobFilter = document.getElementById('jobFilter').value; const statusFilter = document.getElementById('statusFilter').value; filteredOrders = orders.filter(order => { const matchesSearch = !search || order.lpo.toLowerCase().includes(search) || order.supplier.toLowerCase().includes(search) || (order.job_number && order.job_number.toLowerCase().includes(search)) || (order.sheet_name && order.sheet_name.toLowerCase().includes(search)) || order.material_items.some(item => item.type.toLowerCase().includes(search) || item.finish.toLowerCase().includes(search) ); const matchesJob = !jobFilter || order.job_number === jobFilter; const matchesStatus = !statusFilter || order.status === statusFilter; return matchesSearch && matchesJob && matchesStatus; }); updateDisplay(); } function updateDisplay() { const tbody = document.getElementById('ordersBody'); const filterCount = document.getElementById('filterCount'); if (filteredOrders.length === 0) { tbody.innerHTML = `
Loading material orders...
`; setTimeout(() => { if (filteredOrders.length === 0) { tbody.innerHTML = `
πŸ“­
No material orders found
${orders.length === 0 ? 'Try adding your first material order using the form on the left!' : 'Try changing your filters'}
`; } }, 500); } else { tbody.innerHTML = filteredOrders.map(order => { let statusClass, statusText, statusIcon; switch(order.status) { case 'RECEIVED': statusClass = 'delivered'; statusText = 'Received'; statusIcon = 'βœ…'; break; case 'READY': statusClass = 'ready'; statusText = 'Ready'; statusIcon = 'πŸ“¦'; break; case 'PARTIAL': statusClass = 'partial'; statusText = 'Partial'; statusIcon = '⚠️'; break; default: statusClass = 'pending'; statusText = 'Ordered'; statusIcon = '⏳'; } const locationInfo = `
Job: ${order.job_number || 'N/A'} Sheet: ${order.sheet_name || 'N/A'} Rack: ${order.rack_number || 'N/A'} Old Stock: ${order.old_stock_option === 'yes' ? 'Yes' : 'No'}
`; const materialSummary = order.material_items.slice(0, 2).map(item => `
${item.type} - ${item.finish} | ${item.size} | ${item.thickness} | ${item.received_qty}/${item.ordered_qty} AED ${(item.unit_price || 0).toFixed(2)}
`).join(''); const moreItems = order.material_items.length > 2 ? `
+${order.material_items.length - 2} more items
` : ''; const orderStockCount = stockItems.filter(s => s.order_id === order.id).length; return `
${order.lpo}
${new Date(order.created).toLocaleDateString()}
${order.supplier}
${order.payment_terms ? `
Terms: ${order.payment_terms}
` : ''} ${locationInfo} ${materialSummary} ${moreItems} πŸ“– View All (${order.material_items.length}) ${orderStockCount > 0 ? `
πŸ“¦ ${orderStockCount} barcodes
` : ''}
Ordered: ${order.total_ordered}
Received: ${order.total_received} (Click to update)
Pending: ${order.total_pending}
AED ${(order.total_value || 0).toFixed(2)}
Sub: AED ${(order.subtotal || 0).toFixed(2)}
`; }).join(''); } filterCount.textContent = `Showing ${filteredOrders.length} of ${orders.length} orders`; } function viewLPO(orderId) { const order = orders.find(o => o.id === orderId); if (!order) return; document.getElementById('lpoNumber').value = order.lpo; document.getElementById('supplier').value = order.supplier; document.getElementById('jobNumber').value = order.job_number || ''; document.getElementById('sheetName').value = order.sheet_name || ''; document.getElementById('rackNumber').value = order.rack_number || ''; document.getElementById('orderDate').value = order.order_date || ''; document.getElementById('readyDate').value = order.ready_date || ''; document.getElementById('receivedDate').value = order.received_date || ''; document.getElementById('deliveryLocation').value = order.delivery_location || 'Our factory in Al Ghusais Ind Area #4'; document.getElementById('paymentTerms').value = order.payment_terms || '30 days'; document.getElementById('shipmentPref').value = order.shipment_pref || 'By Road'; document.getElementById('partialDelivery').value = order.partial_delivery || 'Not Acceptable'; document.getElementById('ourTRN').value = order.our_trn || '100339770800003'; materialItems = JSON.parse(JSON.stringify(order.material_items)); updateMaterialItemsDisplay(); switchTab('orders'); const detailSection = document.getElementById('detailedMaterial'); if (detailSection.style.display === 'none' || detailSection.style.display === '') { toggleDetailedMaterial(); } showMessage(`πŸ“„ Loaded LPO: ${order.lpo}`); } function updateStats() { let totalOrdered = 0; let totalReceived = 0; let totalPending = 0; let orderedCount = 0; let readyCount = 0; let receivedCount = 0; let totalValue = 0; orders.forEach(order => { totalOrdered += order.total_ordered || 0; totalReceived += order.total_received || 0; totalPending += order.total_pending || 0; totalValue += order.total_value || 0; if (order.status === 'ORDERED') orderedCount++; else if (order.status === 'READY') readyCount++; else if (order.status === 'RECEIVED') receivedCount++; }); const stockTotalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); document.getElementById('statPending').textContent = orderedCount; document.getElementById('statReady').textContent = readyCount; document.getElementById('statDelivered').textContent = receivedCount; document.getElementById('statStock').textContent = stockItems.length; document.getElementById('totalOrders').textContent = `Total LPOs: ${orders.length}`; document.getElementById('totalStock').textContent = `Total Stock Items: ${stockItems.length}`; document.getElementById('availableStock').textContent = `Available: ${stockItems.filter(s => s.status === 'available').length}`; document.getElementById('usedStock').textContent = `Used: ${stockItems.filter(s => s.status === 'used').length}`; document.getElementById('totalValue').textContent = `Total Value: AED ${stockTotalValue.toFixed(2)}`; } function updateJobFilter() { const jobFilter = document.getElementById('jobFilter'); const jobs = [...new Set(orders.map(order => order.job_number).filter(job => job))]; jobFilter.innerHTML = ''; jobs.forEach(job => { const option = document.createElement('option'); option.value = job; option.textContent = job; jobFilter.appendChild(option); }); } // ========== TAB SWITCHING ========== function switchTab(tab) { document.getElementById('tab-orders').className = tab === 'orders' ? 'tab active' : 'tab'; document.getElementById('tab-stock').className = tab === 'stock' ? 'tab active' : 'tab'; document.getElementById('tab-barcode').className = tab === 'barcode' ? 'tab active' : 'tab'; document.getElementById('tab-racks').className = tab === 'racks' ? 'tab active' : 'tab'; document.getElementById('tab-master').className = tab === 'master' ? 'tab active' : 'tab'; document.getElementById('tab-openingclosing').className = tab === 'openingclosing' ? 'tab active' : 'tab'; document.getElementById('tab-copy').className = tab === 'copy' ? 'tab active' : 'tab'; document.getElementById('tab-suppliers').className = tab === 'suppliers' ? 'tab active' : 'tab'; document.getElementById('lpo-section').style.display = tab === 'orders' ? 'block' : 'none'; document.getElementById('stock-section').style.display = tab === 'stock' ? 'block' : 'none'; document.getElementById('barcode-section').style.display = tab === 'barcode' ? 'block' : 'none'; document.getElementById('racks-section').style.display = tab === 'racks' ? 'block' : 'none'; document.getElementById('master-section').style.display = tab === 'master' ? 'block' : 'none'; document.getElementById('openingclosing-section').style.display = tab === 'openingclosing' ? 'block' : 'none'; document.getElementById('copy-section').style.display = tab === 'copy' ? 'block' : 'none'; document.getElementById('suppliers-section').style.display = tab === 'suppliers' ? 'block' : 'none'; document.getElementById('lpo-display').style.display = tab === 'orders' ? 'block' : 'none'; document.getElementById('stock-display').style.display = tab === 'stock' ? 'block' : 'none'; document.getElementById('master-display').style.display = tab === 'master' ? 'block' : 'none'; document.getElementById('openingclosing-display').style.display = tab === 'openingclosing' ? 'block' : 'none'; document.getElementById('suppliers-display').style.display = tab === 'suppliers' ? 'block' : 'none'; if (tab === 'stock') { updateStockDisplay(); updateRackFilter(); } if (tab === 'racks') { updateRacksDisplay(); } if (tab === 'barcode') { document.getElementById('barcodeScan').focus(); } if (tab === 'master') { loadMasterList(); updateMasterDisplay(); } if (tab === 'openingclosing') { calculateOpeningStock(); calculateClosingStock(); } if (tab === 'suppliers') { filteredSuppliers = [...suppliers]; updateSupplierDisplay(); } } // ========== MODAL FUNCTIONS ========== function viewMaterialDetails(orderId) { const order = orders.find(o => o.id === orderId); if (!order) return; let content = `

Order Information

${order.lpo}
${order.supplier}
${order.job_number || 'Not specified'}
${order.sheet_name || 'Not specified'}
${order.rack_number || 'Not specified'}
${order.old_stock_option === 'yes' ? 'Yes' : 'No'}
Delivery Timeline
${order.order_date ? formatDate(order.order_date) : 'Not set'}
${order.ready_date ? formatDate(order.ready_date) : 'Not set'}
${order.received_date ? formatDate(order.received_date) : 'Not set'}
`; if (order.material_items && order.material_items.length > 0) { content += `

Material Items (${order.material_items.length})

${order.material_items.map(item => { const statusColor = item.status === 'RECEIVED' ? '#10b981' : item.status === 'PARTIAL' ? '#f59e0b' : '#ef4444'; const itemTotal = (item.unit_price || 0) * item.received_qty; return ` `; }).join('')}
S.No Type Finish Size Thickness Grade AFP Ordered Received Pending Unit Price Total (AED) Status
${item.s_no} ${item.type} ${item.finish} ${item.size} ${item.thickness} ${item.grade} ${item.afp} ${item.ordered_qty} ${item.received_qty} ${item.pending_qty} AED ${(item.unit_price || 0).toFixed(2)} AED ${itemTotal.toFixed(2)} ${item.status}
Total Ordered: ${order.total_ordered}
Total Received: ${order.total_received}
Total Pending: ${order.total_pending}
Total Value: AED ${(order.total_value || 0).toFixed(2)}

Generated Barcodes

${stockItems.filter(s => s.order_id === order.id).map(stock => ` `).join('')} ${stockItems.filter(s => s.order_id === order.id).length === 0 ? ` ` : ''}
Barcode Material Status Unit Price Used In Job Action
${stock.barcode} ${stock.material_type} - ${stock.material_finish} ${stock.status} AED ${(stock.unit_price || 0).toFixed(2)} ${stock.used_in_job || '-'}
No barcodes generated yet
`; } content += `
`; document.getElementById('barcodeModalTitle').textContent = `Material Details - ${order.lpo}`; document.getElementById('barcodeModalBody').innerHTML = content; document.getElementById('barcodeModal').style.display = 'flex'; } function closeModal() { document.getElementById('barcodeModal').style.display = 'none'; } // ========== REPORT FUNCTIONS ========== function showStockReport() { const available = stockItems.filter(s => s.status === 'available').length; const used = stockItems.filter(s => s.status === 'used').length; const totalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); const materialsByType = {}; stockItems.forEach(item => { const key = `${item.material_type}-${item.material_finish}-${item.material_thickness}`; if (!materialsByType[key]) { materialsByType[key] = { type: item.material_type, finish: item.material_finish, thickness: item.material_thickness, total: 0, available: 0, used: 0, value: 0 }; } materialsByType[key].total++; if (item.status === 'available') { materialsByType[key].available++; } else { materialsByType[key].used++; } materialsByType[key].value += (item.unit_price || 0); }); const racksReport = {}; stockItems.forEach(item => { if (!racksReport[item.rack_number]) { racksReport[item.rack_number] = { total: 0, available: 0, used: 0, value: 0 }; } racksReport[item.rack_number].total++; racksReport[item.rack_number].value += (item.unit_price || 0); if (item.status === 'available') { racksReport[item.rack_number].available++; } else { racksReport[item.rack_number].used++; } }); let report = `πŸ“Š COMPLETE STOCK REPORT\n\n`; report += `πŸ“¦ TOTAL STOCK: ${stockItems.length}\n`; report += `βœ… AVAILABLE: ${available}\n`; report += `⚠️ USED: ${used}\n`; report += `πŸ’° TOTAL VALUE: AED ${totalValue.toFixed(2)}\n\n`; report += `━━━━━━━━━━━━━━━━━━━━━━━━━\n`; report += `πŸ“‹ MATERIAL BREAKDOWN\n\n`; Object.values(materialsByType).forEach(mat => { report += `${mat.type} - ${mat.finish} (${mat.thickness})\n`; report += ` Total: ${mat.total} | Available: ${mat.available} | Used: ${mat.used} | Value: AED ${mat.value.toFixed(2)}\n\n`; }); report += `━━━━━━━━━━━━━━━━━━━━━━━━━\n`; report += `πŸ—οΈ RACK WISE STOCK\n\n`; Object.entries(racksReport).forEach(([rack, data]) => { report += `${rack}: Total ${data.total} (Available: ${data.available}, Used: ${data.used}) - Value: AED ${data.value.toFixed(2)}\n`; }); alert(report); } function showAllTemplates() { const templates = [ { name: 'SS Sheet 304', details: 'Type: SS, Finish: BRUSHED, Size: 1219*2438mm, Thick: 1.0mm, Grade: 304, Price: AED 450' }, { name: 'SS Sheet 316', details: 'Type: SS, Finish: BRUSHED, Size: 1500*2438mm, Thick: 1.0mm, Grade: 316, Price: AED 500' }, { name: 'SS Sheet 316', details: 'Type: SS, Finish: BRUSHED, Size: 1500*3000mm, Thick: 1.0mm, Grade: 316, Price: AED 590' }, { name: 'MS Plate', details: 'Type: MS, Finish: PAINTED, Size: 1500*3000mm, Thick: 2.5mm, Grade: A36, Price: AED 320' }, { name: 'MS Sheet', details: 'Type: MS, Finish: GALVANIZED, Size: 1250*2500mm, Thick: 1.2mm, Grade: A36, Price: AED 280' }, { name: 'Aluminium Sheet', details: 'Type: AL, Finish: MILL, Size: 1200*2400mm, Thick: 2.0mm, Grade: 6061, Price: AED 550' } ]; let templateList = 'πŸ“‹ AVAILABLE TEMPLATES\n\n'; templates.forEach(t => { templateList += `${t.name}\n${t.details}\n\n`; }); alert(templateList); } function formatDate(dateStr) { if (!dateStr) return 'Not set'; const date = new Date(dateStr); return date.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); } // ========== UTILITY FUNCTIONS ========== function addLog(message) { console.log(`[${new Date().toLocaleTimeString()}] ${message}`); } function showMessage(text) { const notify = document.getElementById('notify'); notify.textContent = text; notify.style.display = 'block'; if (text.includes('❌') || text.includes('ERROR')) { notify.style.borderLeftColor = '#ef4444'; } else if (text.includes('⚠️')) { notify.style.borderLeftColor = '#f59e0b'; } else { notify.style.borderLeftColor = '#10b981'; } setTimeout(() => { notify.style.display = 'none'; }, 3000); } function toggleAutoRefresh() { autoRefresh = !autoRefresh; const btn = document.getElementById('autoRefreshBtn'); btn.innerHTML = autoRefresh ? '⏰ Auto Refresh: ON' : '⏰ Auto Refresh: OFF'; btn.className = autoRefresh ? 'success' : 'danger'; showMessage(autoRefresh ? 'βœ… Auto refresh enabled' : '⏸️ Auto refresh disabled'); addLog(`Auto refresh ${autoRefresh ? 'enabled' : 'disabled'}`); } function setOnlineStatus(online) { isOnline = online; const statusElem = document.getElementById('status'); const statusIcon = document.getElementById('statusIcon'); const statusText = document.getElementById('statusText'); if (online) { statusElem.className = 'status'; statusIcon.textContent = '🟒'; statusText.textContent = 'CONNECTED'; } else { statusElem.className = 'status offline'; statusIcon.textContent = 'πŸ”΄'; statusText.textContent = 'OFFLINE'; } } function showHelp() { const helpText = ` πŸ“‹ LPO GENERATION & STOCK MANAGEMENT SYSTEM - USER GUIDE πŸ“„ LPO GENERATION FEATURES: β€’ Generate LPO numbers automatically (AMC/YYYY/#####) β€’ Select suppliers from master list with auto-fill details β€’ Add material items manually or from master list β€’ Add multiple items from low stock alerts β€’ Preview LPO before saving β€’ Generate PDF with proper formatting β€’ Generate Word document (.doc) with exact format matching sample β€’ LPO includes: Header line, Footer line, Supplier details, TRN numbers, VAT calculation, Grand Total β€’ View, edit, and re-print existing LPOs 🏒 SUPPLIER MASTER: β€’ Store supplier details: Name, Contact, Email, Phone, PO Box, Location, TRN β€’ Auto-fill LPO form when supplier selected β€’ Manage multiple suppliers β€’ Search and filter suppliers ⚠️ LOW STOCK LPO GENERATION: β€’ Automatically detect low stock items β€’ Select multiple low stock items to order β€’ Set order quantities per item β€’ Generate LPO directly from low stock alert πŸ“¦ STOCK MANAGEMENT: β€’ Receive stock with barcode generation β€’ Scan barcodes to mark stock as used β€’ Track stock by AMC Job Number, Sheet Name, Rack β€’ Opening/Closing stock valuation β€’ Stock movement tracking πŸ–¨οΈ BARCODE PRINTING: β€’ Print all available barcodes β€’ Print by LPO, Material, Rack, or Selected β€’ Multiple print formats β€’ Prices included on barcode labels ⚑ KEYBOARD SHORTCUTS: β€’ Ctrl+S - Save data β€’ Escape - Close any modal πŸ’° CURRENCY: β€’ All prices in AED β€’ VAT calculation included β€’ Grand Total with VAT πŸ“ DATA MANAGEMENT: β€’ Export to CSV β€’ Backup to JSON β€’ Copy/Paste stock import β€’ Auto-save to localStorage ⚠️ LOW STOCK THRESHOLDS: β€’ Default: 5 units β€’ Set per material in Master List β€’ Visual alerts when below threshold β€’ Quick LPO generation from alert πŸ“‹ SAMPLE LPO FORMAT MATCHES: β€’ Date format: 2 January 2026 β€’ LPO Ref: AMC/2026/10735 β€’ Supplier: M/s Hidayath Trading CO. LLC β€’ TRN numbers included β€’ VAT 5% calculation β€’ Header line and Footer line β€’ Signature block included `.trim(); alert(helpText); } // ========== DATA FUNCTIONS ========== function loadData(silent = false) { const refreshBtn = document.getElementById('refreshBtn'); const refreshIcon = document.querySelector('#refreshBtn span'); if (!silent) { refreshBtn.disabled = true; refreshIcon.textContent = '⏳'; } setTimeout(() => { try { const savedData = localStorage.getItem('stock_system_data'); if (savedData) { const data = JSON.parse(savedData); orders = data.orders || []; stockItems = data.stockItems || []; racks = data.racks || []; scanHistory = data.scanHistory || []; masterList = data.masterList || masterList; suppliers = data.suppliers || suppliers; } } catch (error) { console.error('Error loading data:', error); } filteredOrders = [...orders]; filteredSuppliers = [...suppliers]; updateDisplay(); updateStats(); updateJobFilter(); updateStockDisplay(); updateRacksDisplay(); updateRackFilter(); updateScanHistory(); updateMasterDisplay(); updateSupplierDisplay(); updateSupplierDropdown(); calculateOpeningStock(); calculateClosingStock(); setOnlineStatus(true); const now = new Date(); document.getElementById('lastUpdate').textContent = `Last update: ${now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'})}`; if (!silent) { const totalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); showMessage(`βœ… Data loaded (${orders.length} orders, ${stockItems.length} stock items, Value: AED ${totalValue.toFixed(2)})`); addLog(`Data loaded: ${orders.length} orders, ${stockItems.length} stock items`); } if (!silent) { setTimeout(() => { refreshBtn.disabled = false; refreshIcon.textContent = 'πŸ”„'; }, 1000); } }, 500); } function saveData() { const saveBtn = document.getElementById('saveBtn'); const originalText = saveBtn.innerHTML; saveBtn.disabled = true; saveBtn.innerHTML = '⏳ Saving...'; const totalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); const dataToSave = { orders: orders, stockItems: stockItems, racks: racks, scanHistory: scanHistory, masterList: masterList, suppliers: suppliers, saved_at: new Date().toISOString(), total_orders: orders.length, total_stock: stockItems.length, total_value: totalValue, version: '4.0' }; setTimeout(() => { try { localStorage.setItem('stock_system_data', JSON.stringify(dataToSave)); localStorage.setItem('stock_last_save', new Date().toISOString()); showMessage('βœ… Data saved successfully'); addLog(`Data saved: ${orders.length} orders, ${stockItems.length} stock items, Value: AED ${totalValue.toFixed(2)}`); localStorage.setItem('stock_system_backup', JSON.stringify(dataToSave)); } catch (error) { console.error('Save error:', error); showMessage('❌ Save failed'); addLog('Save failed'); } finally { setTimeout(() => { saveBtn.disabled = false; saveBtn.innerHTML = originalText; }, 500); } }, 500); } function backupData() { const totalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); const data = { orders: orders, stockItems: stockItems, racks: racks, scanHistory: scanHistory, masterList: masterList, suppliers: suppliers, exported_at: new Date().toISOString(), total_orders: orders.length, total_stock: stockItems.length, total_value: totalValue, version: '4.0' }; const dataStr = JSON.stringify(data, null, 2); const blob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `stock_system_backup_${new Date().toISOString().slice(0,10)}.json`; a.click(); showMessage(`βœ… Backup downloaded (${orders.length} orders, ${stockItems.length} stock items, Value: AED ${totalValue.toFixed(2)})`); addLog('Downloaded backup file'); } function exportCSV() { if (stockItems.length === 0 && orders.length === 0) { showMessage('❌ No data to export'); return; } let csv = 'BARCODE,JOB NUMBER,SHEET NAME,RACK NUMBER,MATERIAL TYPE,FINISH,SIZE,THICKNESS,GRADE,STATUS,UNIT PRICE (AED),TOTAL VALUE (AED),RECEIVED DATE,USED DATE,USED IN JOB,USED IN SHEET,USED FOR,USED BY\n'; stockItems.forEach(stock => { const totalValue = stock.unit_price || 0; csv += `"${stock.barcode}","${stock.job_number}","${stock.sheet_name}","${stock.rack_number}",`; csv += `"${stock.material_type}","${stock.material_finish}","${stock.material_size}",`; csv += `"${stock.material_thickness}","${stock.material_grade}","${stock.status}",`; csv += `${stock.unit_price || 0},${totalValue.toFixed(2)},"${stock.received_date}","${stock.used_date || ''}","${stock.used_in_job || ''}","${stock.used_in_sheet || ''}","${stock.used_for || ''}","${stock.used_by || ''}"\n`; }); const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `stock_report_${new Date().toISOString().slice(0,10)}.csv`; a.click(); const totalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); showMessage(`βœ… Exported ${stockItems.length} stock items to CSV (Total Value: AED ${totalValue.toFixed(2)})`); addLog('Exported data to CSV'); } // Save to localStorage before page closes window.addEventListener('beforeunload', function() { if (orders.length > 0 || stockItems.length > 0) { const totalValue = stockItems.reduce((sum, item) => sum + (item.unit_price || 0), 0); const data = { orders: orders, stockItems: stockItems, racks: racks, scanHistory: scanHistory, masterList: masterList, suppliers: suppliers, total_value: totalValue }; localStorage.setItem('stock_system_backup', JSON.stringify(data)); } });