88 lines
3.5 KiB
JavaScript
88 lines
3.5 KiB
JavaScript
// BOM & Supply Chain API Client
|
|
let liveBomData = [];
|
|
|
|
async function loadBomData() {
|
|
try {
|
|
const res = await fetch('/api/bom');
|
|
if (!res.ok) throw new Error("Failed to load BOM data");
|
|
liveBomData = await res.json();
|
|
|
|
renderBomTable();
|
|
updateBOMCalculations();
|
|
} catch (err) {
|
|
console.error("BOM Loading Error:", err);
|
|
}
|
|
}
|
|
|
|
function renderBomTable() {
|
|
const tbody = document.getElementById('bom-table-body');
|
|
if (!tbody) return;
|
|
tbody.innerHTML = '';
|
|
|
|
liveBomData.forEach(item => {
|
|
const tr = document.createElement('tr');
|
|
|
|
let statusBadge = '<span style="color:var(--accent-emerald); font-weight:600;"><i class="fa-solid fa-check"></i> In Stock</span>';
|
|
if (item.status === 'ordered') {
|
|
statusBadge = `<span style="color:var(--accent-amber); font-weight:600;"><i class="fa-solid fa-clock"></i> Ordered (${item.lead_time_days}d)</span>`;
|
|
} else if (item.status === 'backordered') {
|
|
statusBadge = `<span style="color:var(--accent-rose); font-weight:600;"><i class="fa-solid fa-triangle-exclamation"></i> Backorder (${item.lead_time_days}d)</span>`;
|
|
}
|
|
|
|
tr.innerHTML = `
|
|
<td>
|
|
<strong>${item.component_name}</strong>
|
|
<div style="font-size:0.65rem; color:var(--text-muted);">${item.notes || ''}</div>
|
|
</td>
|
|
<td>
|
|
${item.vendor}
|
|
<div>${statusBadge}</div>
|
|
</td>
|
|
<td><code style="color:var(--accent-cyan);">${item.mpn}</code></td>
|
|
<td style="font-weight:700; color:var(--accent-emerald);">$${parseFloat(item.unit_price).toFixed(2)}</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function updateBOMCalculations() {
|
|
const batchSlider = document.getElementById('batch-slider');
|
|
const batchQty = batchSlider ? parseInt(batchSlider.value) : 100;
|
|
const batchLabel = document.getElementById('batch-qty-label');
|
|
if (batchLabel) batchLabel.innerText = `${batchQty} Units`;
|
|
|
|
let discountFactor = 1.0;
|
|
if (batchQty >= 1000) discountFactor = 0.85;
|
|
else if (batchQty >= 100) discountFactor = 0.92;
|
|
else if (batchQty >= 10) discountFactor = 0.96;
|
|
|
|
const baseTotal = liveBomData.length > 0
|
|
? liveBomData.reduce((acc, i) => acc + (parseFloat(i.unit_price) * (i.quantity || 1)), 0)
|
|
: 241.20;
|
|
|
|
const unitCost = baseTotal * discountFactor;
|
|
const totalBatch = unitCost * batchQty;
|
|
|
|
const unitEl = document.getElementById('rollup-unit-cost');
|
|
const batchEl = document.getElementById('rollup-total-cost');
|
|
if (unitEl) unitEl.innerText = `$${unitCost.toFixed(2)}`;
|
|
if (batchEl) batchEl.innerText = `$${totalBatch.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}`;
|
|
}
|
|
|
|
function exportBOMCSV() {
|
|
let csv = "Component,MPN,Supplier,Quantity,UnitPriceUSD,Status,Notes\n";
|
|
liveBomData.forEach(i => {
|
|
csv += `"${i.component_name}","${i.mpn}","${i.vendor}",${i.quantity || 1},${parseFloat(i.unit_price).toFixed(2)},"${i.status}","${i.notes || ''}"\n`;
|
|
});
|
|
const blob = new Blob([csv], { type: 'text/csv' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.setAttribute('href', url);
|
|
a.setAttribute('download', '6U_Storage_Array_Master_PO.csv');
|
|
a.click();
|
|
}
|
|
|
|
window.loadBomData = loadBomData;
|
|
window.updateBOMCalculations = updateBOMCalculations;
|
|
window.exportBOMCSV = exportBOMCSV;
|