Forgot password?
Authenticated access only
v1.0 RC107 · Augeas Technologies
'; w.document.write(h);w.document.close(); setTimeout(function(){w.print()},400); } function allocateStockForWO(wo,bom,qty){ var lines=getLinesForBom(bom.id); var allocated=0,partial=0; lines.forEach(function(l){ if(l.fit==='DNP')return; var needed=(parseInt(l.qty)||1)*qty; var alreadyRes=getWOReserved(l.partId,wo.id); var remaining=needed-alreadyRes;if(remaining<=0)return; var available=getPartAvailable(l.partId); var toReserve=Math.min(remaining,available); if(toReserve>0){ reservePartForWO(l.partId,wo.id,toReserve); stockTx(l.partId,'RESERVE',toReserve,wo.id,'Reserved for '+wo.num); if(toReserve>=remaining)allocated++;else partial++; } }); saveAll(); toast(allocated+' fully reserved, '+partial+' partial'); addLog('ALLOCATE',wo.num+': '+allocated+' full, '+partial+' partial'); } function woChecklist(woId,clType){ if(!guardAction('checklist_entry','Complete Checklist'))return; var wo=(D.wo||[]).find(function(w){return w.id===woId});if(!wo)return; // Check if QC already completed for this WO var existing=(D.checklists||[]).find(function(c){return c.woId===woId&&c.type==='final'}); if(existing){ // Show the existing QC record instead of blocking var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); return; } // Find the correct final QC template — strict product designation, no loose fallback var productId=wo.productId; var projectId=wo.projectId; var templates=(D.clTemplates||[]).filter(function(t){return !t.archived}); var matched=null; // STRICT: Product builds use finalQCTemplateId ONLY — no name matching if(productId){ var prd=getProduct(productId); if(prd&&prd.finalQCTemplateId){ matched=templates.find(function(t){return t.id===prd.finalQCTemplateId}); } // NO name-based fallback for production products } // Board-only WOs: match by boardId on template if(!matched&&!productId&&wo.boardId){ matched=templates.find(function(t){return t.boardId===wo.boardId&&(t.category==='Final Product QC'||(t.name||'').toLowerCase().indexOf('final')>=0)}); } // NO loose fallback — if no match, tell the user to designate one if(!matched){ var h2=''; document.getElementById('MR').innerHTML=h2; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); return; } // Show confirmation with matched template var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); document.getElementById('qcComplete').addEventListener('click',function(){ var checks=document.querySelectorAll('.qcCheck'); var allChecked=true;var results=[]; checks.forEach(function(cb,i){ results.push({step:i,checked:cb.checked}); if(!cb.checked)allChecked=false; }); if(!allChecked){ if(!confirm('Not all steps are checked. Complete anyway with partial results?'))return; } var notes=(document.getElementById('qcNotes').value||'').trim(); var record={id:'CLI_'+uid(),woId:woId,templateId:matched.id,label:matched.name,type:'final', results:results,allPassed:allChecked,notes:notes,at:now(),by:U}; if(!D.checklists)D.checklists=[]; D.checklists.push(record); saveAllAsync('QC Complete',{critical:true}).then(function(res){ document.getElementById('MR').innerHTML='';renderPage(); if(!handleCriticalSaveResult(res,'QC Complete'))return; toast('QC '+(allChecked?'PASSED':'completed with exceptions'),'ok'); auditLog('QC COMPLETE','wo',woId,wo.num+' \u2014 '+(allChecked?'PASS':'PARTIAL')); }); }); } // Admin/Engineer-only: record "Passed with Exceptions" on a failed or partial QC record. // Requires notes + approver name. Creates audit trail. Without this explicit step, // the completion gate will remain blocked. function approveQCException(checklistId){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} if(!canDo('wo_complete')){toast('Permission denied: approve QC exception requires Engineer or Admin','err');return} var cl=(D.checklists||[]).find(function(c){return c.id===checklistId}); if(!cl){toast('Checklist not found','err');return} var notes=prompt('Required: notes explaining why this QC is approved despite exceptions (min 10 chars):',''); if(!notes||notes.trim().length<10){toast('Approval notes required (at least 10 characters)','warn');return} var approver=prompt('Required: approver name (Engineer or Admin signing off):',U||''); if(!approver||!approver.trim()){toast('Approver name required','warn');return} if(!confirm('Approve this QC with exceptions?\n\nThis will be recorded in the audit trail with your name, the notes, and the approver. Only proceed if the exceptions are genuinely acceptable.'))return; cl.adminOverride=true; cl.overrideNotes=notes.trim(); cl.overrideApprover=approver.trim(); cl.overrideAt=now(); cl.overrideBy=U; saveAllAsync('QC Exception Approved',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(res){ renderPage(); if(!handleCriticalSaveResult(res,'QC Exception Approved'))return; toast('QC exception approved — '+approver,'ok'); auditLog('QC EXCEPTION APPROVED','checklist',checklistId,'Approver: '+approver+' | Notes: '+notes.trim().slice(0,80)); }); } function pushToWO(ordId){ if(!guardAction('wo_edit','Push Order to WO'))return; var o=(D.orders||[]).find(function(x){return x.id===ordId});if(!o)return; var qty=parseInt(o.qty)||1; var wo={id:'WO_'+uid(),num:'WO-'+String((D.wo||[]).length+1).padStart(4,'0'),projectId:o.projectId||'',boardId:o.boardId||'',qty:String(qty),unitSN:'',assignee:U,linkedOrder:o.num,notes:'Auto-created from '+o.num,at:now(),status:'Draft',stages:{}}; // BOM revision snapshot if(wo.boardId){var _bom2=D.boms.find(function(b){return b.boardId===wo.boardId});if(_bom2){wo.bomId=_bom2.id;wo.bomRev=_bom2.rev||'1';wo.bomSnapshot=getLinesForBom(_bom2.id).map(function(l){return{partId:l.partId,qty:l.qty,des:l.des,fit:l.fit}})}} if(!D.wo)D.wo=[];D.wo.push(wo); o.status='In Progress'; saveAll();renderPage();toast('Work Order '+wo.num+' created from '+o.num);addLog('PUSH TO WO',o.num+' -> '+wo.num); } // ═══ JOB BIN / KIT BIN SYSTEM ═══ var STOCK_LOCS=['Main Stock','Incoming','Inspection','Job Bin','WIP','Finished Goods','Scrap','Quarantine']; var WO_TYPES=['Board Build','Assembly Build','Product Build','Rework']; function newUnifiedWO(){ if(!guardAction('wo_edit','Create Work Order'))return; // Build a custom modal with dependent dropdowns var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); nwTypeChanged(); // Set initial state document.getElementById('nwCreate').addEventListener('click',function(){doCreateWO()}); } function nwTypeChanged(){ var t=document.getElementById('nwType').value; var prodField=document.getElementById('nwProdField'); var boardField=document.getElementById('nwBoardField'); var info=document.getElementById('nwInfo'); if(t==='Product Build'){ prodField.style.display='';boardField.style.display='none'; info.style.display='block';info.innerHTML='Product Build: Select a product. Project will auto-set from the product definition. MBOM will be used for material planning.'; } else { prodField.style.display='none';boardField.style.display=''; document.getElementById('nwProd').value=''; info.style.display=(t==='Rework')?'block':'none'; if(t==='Rework')info.innerHTML='Rework: Select the board/assembly to rework.'; } } function nwProdChanged(){ var prodId=document.getElementById('nwProd').value; if(!prodId)return; var prd=(D.products||[]).find(function(p){return p.id===prodId}); if(prd&&prd.projectId){ document.getElementById('nwProj').value=prd.projectId; } } function nwProjChanged(){ var projId=document.getElementById('nwProj').value; var boardSel=document.getElementById('nwBoard'); // Filter board options to only show boards from selected project Array.from(boardSel.options).forEach(function(opt){ if(!opt.value){opt.style.display='';return} opt.style.display=(!projId||opt.dataset.proj===projId)?'':'none'; }); // If current selection is now hidden, reset var cur=boardSel.options[boardSel.selectedIndex]; if(cur&&cur.value&&cur.style.display==='none')boardSel.value=''; } function nwBoardChanged(){ var boardSel=document.getElementById('nwBoard'); var opt=boardSel.options[boardSel.selectedIndex]; if(opt&&opt.value&&opt.dataset.proj){ var projSel=document.getElementById('nwProj'); if(!projSel.value||projSel.value!==opt.dataset.proj){ projSel.value=opt.dataset.proj; } } } function doCreateWO(){if(!guardAction('wo_edit','Create WO'))return; var r={}; r.woType=document.getElementById('nwType').value; r.num=document.getElementById('nwNum').value.trim(); r.projectId=document.getElementById('nwProj').value; r.productId=document.getElementById('nwProd').value; r.boardId=document.getElementById('nwBoard').value; r.qty=document.getElementById('nwQty').value||'1'; r.assignee=document.getElementById('nwAssignee').value.trim(); r.linkedOrder=document.getElementById('nwLinkedPO').value.trim(); r.notes=document.getElementById('nwNotes').value.trim(); if(!r.num){toast('Enter a WO number','warn');return} // Validate based on type if(r.woType==='Product Build'&&!r.productId){toast('Select a product for Product Build','warn');return} if((r.woType==='Board Build'||r.woType==='Assembly Build')&&!r.boardId){toast('Select a board/assembly','warn');return} r.id='WO_'+uid();r.at=now();r.status='Draft';r.stages={};r.by=U; if(r.woType==='Product Build'){r.boardId=''} else{r.productId=''} // BOM snapshot if(r.boardId){var _bom=D.boms.find(function(b){return b.boardId===r.boardId});if(_bom){r.bomId=_bom.id;r.bomRev=_bom.rev||'1';r.bomSnapshot=getLinesForBom(_bom.id).map(function(l){return{partId:l.partId,qty:l.qty,des:l.des,fit:l.fit}})}} if(r.productId){var mbom=getMBOM(r.productId);r.mbomSnapshot=mbom.map(function(m){return{itemType:m.itemType,refId:m.refId,desc:m.desc,qty:m.qty}})} if(!D.wo)D.wo=[];D.wo.push(r);saveAll(); document.getElementById('MR').innerHTML='';renderPage(); toast('Created '+r.num+' ('+r.woType+')','ok');auditLog('NEW WO','wo',r.id,r.num+' '+r.woType); } function createPOFromShortages(woId){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} if(!guardAction('wo_edit','Create PO'))return; var wo=(D.wo||[]).find(function(w){return w.id===woId});if(!wo||!wo.productId)return; var prd=getProduct(wo.productId);if(!prd)return; var mbom=getMBOM(wo.productId); var buyItems=mbom.filter(function(m){return m.itemType==='purchased'}); var shortLines=[]; buyItems.forEach(function(m){ var needed=(parseInt(m.qty)||1)*(parseInt(wo.qty)||1); var stk=(D.stock||[]).find(function(s){return s.partId===m.refId}); var onHand=stk?parseInt(stk.qty)||0:0; var reserved=getPartReserved(m.refId); var avail=Math.max(0,onHand-reserved); var short=Math.max(0,needed-avail); if(short>0){var p=getPart(m.refId);shortLines.push({partId:m.refId,qty:String(short),desc:p?p.desc:'',mpn:p?p.mpn:'',unitCost:p?p.pr:'0',lineType:'Part'})} }); if(!shortLines.length){toast('No shortages — all purchased items are covered','ok');return} // Create PO with shortage lines var poNum='PO-'+String((D.orders||[]).length+1).padStart(4,'0'); var po={id:'PO_'+uid(),num:poNum,vendor:'',projectId:wo.projectId||prd.projectId||'',productId:wo.productId,status:'Draft',expectedDate:'',notes:'Auto-created from '+wo.num+' shortages',at:now(),by:U}; if(!D.orders)D.orders=[];D.orders.push(po); if(!D.poLines)D.poLines=[]; shortLines.forEach(function(sl){ D.poLines.push({id:'POL_'+uid(),poId:po.id,lineType:sl.lineType,partId:sl.partId,itemPN:sl.mpn,desc:sl.desc,qty:sl.qty,unitCost:sl.unitCost,recvQty:'0',accQty:'0',boardId:''}); }); saveAllAsync('Create PO from Shortages',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(__res){if(!handleCriticalSaveResult(__res,'Create PO from Shortages'))return;renderPage(); toast(poNum+' created with '+shortLines.length+' shortage lines','ok'); auditLog('CREATE PO FROM SHORTAGES','po',po.id,poNum+' from '+wo.num+' ('+shortLines.length+' lines)'); nav('orders'); }); } function createChildWO(parentWoId,boardId,qty){ if(!guardAction('wo_edit','Create Child WO'))return; var _lk='createChildWO_'+(boardId||''); return withActionLock(_lk,function(){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} var parentWO=(D.wo||[]).find(function(w){return w.id===parentWoId});if(!parentWO)return Promise.resolve({ok:false}); var bd=getBoard(boardId);if(!bd)return Promise.resolve({ok:false}); var woNum='WO-'+String((D.wo||[]).length+1).padStart(4,'0'); var _cType=(bd.type==='mech'||bd.type==='assy')?'Assembly Build':'Board Build'; var wo={id:'WO_'+uid(),num:woNum,projectId:parentWO.projectId||bd.projectId||'',boardId:boardId,productId:'',qty:qty||'1',unitSN:'',assignee:U,linkedOrder:parentWO.num,notes:'Child build for '+parentWO.num+': '+(bd.name||boardId),at:now(),status:'Draft',stages:{},woType:_cType,by:U,parentWoId:parentWoId}; var _bom=D.boms.find(function(b){return b.boardId===boardId}); if(_bom){wo.bomId=_bom.id;wo.bomRev=_bom.rev||'1';wo.bomSnapshot=getLinesForBom(_bom.id).map(function(bl){return{partId:bl.partId,qty:bl.qty,des:bl.des,fit:bl.fit}})} if(!D.wo)D.wo=[];D.wo.push(wo); return saveAllAsync('Create Child WO',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(res){ renderPage(); if(!handleCriticalSaveResult(res,'Create Child WO'))return res; toast('Child WO '+woNum+' created for '+bd.name,'ok'); auditLog('CREATE CHILD WO','wo',wo.id,woNum+' for '+bd.name+' (parent: '+parentWO.num+')'); return res; }); }); } function createJobBin(woId){ if(!guardAction('issue','Create Job Bin'))return; var _lk='createJobBin_'+(arguments[0]||''); return withActionLock(_lk,function(){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} var wo=D.wo.find(function(x){return x.id===woId});if(!wo)return Promise.resolve({ok:false}); var existing=(D.jobBins||[]).find(function(b){return b.woId===woId}); if(existing){toast('Job bin already exists: '+existing.binId,'warn');nav('jobbin');return Promise.resolve({ok:false})} var binId='BIN-'+wo.num.replace('WO-',''); var bin={id:'JB_'+uid(),binId:binId,woId:woId,woNum:wo.num,status:'Open',createdAt:now(),by:U}; if(!D.jobBins)D.jobBins=[]; D.jobBins.push(bin); populateBinLines(bin,wo); return saveAllAsync('Create Job Bin',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(__res){ if(!handleCriticalSaveResult(__res,'Create Job Bin'))return __res; renderPage(); toast('Job Bin '+binId+' created');addLog('CREATE BIN',binId+' for '+wo.num); nav('jobbin'); return __res; }); }); } function populateBinLines(bin,wo){ if(!D.binLines)D.binLines=[]; if(wo.woType==='Product Build'&&wo.productId){ var mbom=getMBOM(wo.productId); var qty=parseInt(wo.qty)||1; mbom.forEach(function(m){ var desc=m.desc;var refId=m.refId;var itemType=m.itemType; var need=(parseInt(m.qty)||1)*qty; D.binLines.push({id:'BL_'+uid(),binId:bin.id,itemType:itemType,refId:refId,desc:desc,needQty:need,pickedQty:0,issuedQty:0,returnedQty:0,status:'Pending'}); }); } else if(wo.boardId){ var bom=D.boms.find(function(b){return b.boardId===wo.boardId}); if(bom){ var qty=parseInt(wo.qty)||1; getLinesForBom(bom.id).forEach(function(l){ if(l.fit==='DNP')return; var p=getPart(l.partId);if(!p)return; var need=(parseInt(l.qty)||1)*qty; D.binLines.push({id:'BL_'+uid(),binId:bin.id,itemType:'part',refId:l.partId,desc:(p.mpn||'')+' '+(p.desc||'').substring(0,30),needQty:need,pickedQty:0,issuedQty:0,returnedQty:0,status:'Pending'}); }); } } } function pickToBin(binLineId,qty){ if(!guardAction('pick','Pick to Bin'))return; var _lk='pickToBin_'+(arguments[0]||''); return withActionLock(_lk,function(){ var bl=(D.binLines||[]).find(function(x){return x.id===binLineId}); if(!bl)return Promise.resolve({ok:false}); var bin=(D.jobBins||[]).find(function(b){return b.id===bl.binId}); if(!bin)return Promise.resolve({ok:false}); var remaining=bl.needQty-bl.pickedQty; if(qty>remaining)qty=remaining; // Validate stock availability BEFORE the mutation so early-exits cleanly release the lock var stockId=bl.refId; if(bl.itemType==='board'||bl.itemType==='mechanical')stockId='ASSY_'+bl.refId; var avail=getPartAvailable(stockId); if(avail=bl.needQty?'Picked':'Partial'; }, onSuccess:function(){toast('Picked '+qty+' to '+bin.binId,'ok')}, auditAction:['PICK','stock',bl.refId,qty+' to '+bin.binId] }); }); } function returnFromBin(binLineId){ if(!guardAction('pick','Return from Bin'))return; var _lk='returnFromBin_'+(arguments[0]||''); return withActionLock(_lk,function(){ var bl=(D.binLines||[]).find(function(x){return x.id===binLineId}); if(!bl)return Promise.resolve({ok:false}); var bin=(D.jobBins||[]).find(function(b){return b.id===bl.binId}); if(!bin)return Promise.resolve({ok:false}); var excess=bl.pickedQty-bl.issuedQty; if(excess<=0){toast('Nothing to return','warn');return Promise.resolve({ok:false})} var retQty=parseInt(prompt('Return qty (excess: '+excess+'):',excess)); if(!retQty||retQty<=0)return Promise.resolve({ok:false}); retQty=Math.min(retQty,excess); return performCriticalMutation({ actionLabel:'Return from Bin', lockKey:null, mutationFn:function(){ if(bl.itemType==='part'){ receiveStock(bl.refId,retQty,'Returned from '+bin.binId); stockTx(bl.refId,'RETURN',retQty,bin.woId,'Returned from '+bin.binId); } else if(bl.itemType==='board'||bl.itemType==='mechanical'){ var assyId='ASSY_'+bl.refId; receiveStock(assyId,retQty,'Returned from '+bin.binId); stockTx(assyId,'RETURN',retQty,bin.woId,'Returned from '+bin.binId); } else if(bl.itemType==='purchased'){ receiveStock(bl.refId,retQty,'Returned from '+bin.binId); stockTx(bl.refId,'RETURN',retQty,bin.woId,'Returned from '+bin.binId); } bl.pickedQty-=retQty; bl.returnedQty+=retQty; bl.status=bl.pickedQty>=bl.needQty?'Picked':bl.pickedQty>0?'Partial':'Pending'; }, onSuccess:function(){toast('Returned '+retQty+' from '+bin.binId,'ok')}, auditAction:['RETURN FROM BIN','stock',bl.refId,'qty='+retQty] }); }); } function issueBinLine(binLineId){ if(!guardAction('issue','Issue Bin Line'))return; var _lk='issueBinLine_'+(arguments[0]||''); return withActionLock(_lk,function(){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} var bl=(D.binLines||[]).find(function(x){return x.id===binLineId});if(!bl)return Promise.resolve({ok:false}); var toIssue=bl.pickedQty-bl.issuedQty; if(toIssue<=0){toast('Nothing picked to issue','warn');return Promise.resolve({ok:false})} bl.issuedQty+=toIssue; bl.status='Issued'; var bin=(D.jobBins||[]).find(function(b){return b.id===bl.binId}); if(bin){ if(bl.itemType==='part'||bl.itemType==='purchased')stockTx(bl.refId,'ISSUE',toIssue,bin.woId,'Consumed from '+bin.binId); else if(bl.itemType==='board'||bl.itemType==='mechanical')stockTx('ASSY_'+bl.refId,'ISSUE',toIssue,bin.woId,'Consumed from '+bin.binId); } return saveAllAsync('Issue Bin Line',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(res){ if(handleCriticalSaveResult(res,'Issue Bin Line'))auditLog('ISSUE BIN LINE','stock',bl.refId,'qty='+toIssue); return res; }); }); } function pickAllBinLines(binId){ if(!guardAction('pick','Pick All'))return; var _lk='pickAllBinLines_'+(arguments[0]||''); return withActionLock(_lk,function(){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} var bin=(D.jobBins||[]).find(function(b){return b.id===binId}); var woId=bin?bin.woId:''; var lines=(D.binLines||[]).filter(function(l){return l.binId===binId&&l.pickedQty0){ var cur=getPartStock(stockId); setPartStock(stockId,cur-qty); stockTx(stockId,'PICK',qty,woId,'Bulk pick to '+(bin?bin.binId:'bin')); bl.pickedQty+=qty; bl.status=bl.pickedQty>=bl.needQty?'Picked':'Partial'; picked++; } else if(remaining>0){skipped++} }); return saveAllAsync('Pick All',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(res){ renderPage(); if(!handleCriticalSaveResult(res,'Pick All'))return res; var msg=picked+' items picked'; if(skipped)msg+=', '+skipped+' skipped (no stock)'; toast(msg,picked?'ok':'warn'); auditLog('PICK ALL','bin',binId,picked+' picked, '+skipped+' skipped'); return res; }); }); } function completeBin(binId){ if(!guardAction('issue','Complete Bin'))return; var _lk='completeBin_'+(arguments[0]||''); return withActionLock(_lk,function(){ var bin=(D.jobBins||[]).find(function(b){return b.id===binId}); if(!bin)return Promise.resolve({ok:false}); var lines=(D.binLines||[]).filter(function(l){return l.binId===binId}); if(!confirm('Complete bin '+bin.binId+'?\n\nAll picked items will be issued to the build.\nThe WO will move to In Build.\n\nNote: This does NOT complete the WO.\nTuning, QC, and completion gates must still be satisfied separately.'))return Promise.resolve({ok:false}); var issuedCount=0; return performCriticalMutation({ actionLabel:'Complete Bin', lockKey:null, mutationFn:function(){ lines.forEach(function(bl){ var toIssue=bl.pickedQty-bl.issuedQty; if(toIssue>0){bl.issuedQty+=toIssue;bl.status='Issued';issuedCount++; var issueId=(bl.itemType==='board'||bl.itemType==='mechanical')?'ASSY_'+bl.refId:bl.refId; stockTx(issueId,'ISSUE',toIssue,bin.woId,'Consumed from '+bin.binId); } }); bin.status='Completed';bin.completedAt=now(); var wo=D.wo.find(function(w){return w.id===bin.woId}); if(wo&&wo.status!=='Ready to Stock'&&wo.status!=='Shipped')wo.status='In Build'; }, onSuccess:function(){toast('Bin '+bin.binId+' complete \u2014 '+issuedCount+' items issued. WO is now In Build.','ok')}, auditAction:['COMPLETE BIN','bin',bin.binId,'issued items, WO to In Build'] }); }); } // ═══ JOB BINS PAGE ═══ function pgJobBins(el){ if(!D.jobBins)D.jobBins=[];if(!D.binLines)D.binLines=[]; var h=''; // Bin list var openBins=D.jobBins.filter(function(b){return b.status!=='Completed'}); var doneBins=D.jobBins.filter(function(b){return b.status==='Completed'}); if(openBins.length){ h+='
Open Job Bins
'; openBins.forEach(function(bin){ var wo=D.wo.find(function(w){return w.id===bin.woId}); var lines=(D.binLines||[]).filter(function(l){return l.binId===bin.id}); var totalLines=lines.length; var pickedLines=lines.filter(function(l){return l.pickedQty>=l.needQty}).length; var issuedLines=lines.filter(function(l){return l.issuedQty>=l.needQty}).length; var shortLines=lines.filter(function(l){return l.pickedQty'; h+='
'; h+='
'+bin.binId+''; h+=' '+(wo?wo.num:'')+' \u00b7 '+(wo?wo.woType:'')+' \u00b7 '+(prd?prd.name:bd?bd.name:'')+'
'; h+=''+pct+'% picked
'; h+='
'; h+='
'; h+='Items: '+totalLines+''; h+='Picked: '+pickedLines+''; h+='Issued: '+issuedLines+''; if(shortLines)h+='Missing: '+shortLines+''; h+='Qty: '+(wo?wo.qty:'')+''; h+='
'; }); } if(doneBins.length){ h+='
Finished Bins
'; doneBins.forEach(function(bin){ var wo=D.wo.find(function(w){return w.id===bin.woId}); var lc=(D.binLines||[]).filter(function(l){return l.binId===bin.id}).length; h+=''; }); h+='
Bin IDWOItemsStatusCompleted
'+bin.binId+''+(wo?wo.num:'')+''+lc+''+bin.status+''+fD(bin.completedAt||bin.createdAt)+'
'; } if(!D.jobBins.length)h+='
No job bins yet
Create a Work Order first, then click "Create Job Bin" to start kitting.
'; el.innerHTML=h; } function viewJobBin(binId){ var bin=(D.jobBins||[]).find(function(b){return b.id===binId});if(!bin)return; var wo=D.wo.find(function(w){return w.id===bin.woId}); var lines=(D.binLines||[]).filter(function(l){return l.binId===binId}); var prd=wo&&wo.productId?getProduct(wo.productId):null; var bd=wo&&wo.boardId?getBoard(wo.boardId):null; var pr=wo&&wo.projectId?getProject(wo.projectId):null; var totalLines=lines.length; var fullyPicked=lines.filter(function(l){return l.pickedQty>=l.needQty}).length; var fullyIssued=lines.filter(function(l){return l.issuedQty>=l.needQty}).length; var shortLines=lines.filter(function(l){return l.pickedQty0}); var pctDone=totalLines?Math.round(fullyPicked/totalLines*100):0; var allPicked=fullyPicked===totalLines; var allIssued=fullyIssued===totalLines; // Determine current workflow step var step=1; if(bin.status==='Completed')step=9; else if(allIssued)step=7; else if(allPicked)step=6; else if(fullyPicked>0||lines.some(function(l){return l.pickedQty>0}))step=4; else if(shortLines'; h+='
'+(wo?wo.num+' \u00b7 '+wo.woType:'')+' \u00b7 '+(prd?prd.name:bd?bd.name:'')+' \u00b7 '+(pr?pr.name:'')+' \u00b7 Qty: '+(wo?wo.qty:'')+'
'; h+=''+bin.status+''; // ── WORKFLOW STEPPER ── var steps=['Create Job','Check Avail','Open Bin','Pick to Bin','Resolve Short','Build','Complete','Return Excess','Close']; h+='
'; steps.forEach(function(s,i){ var n=i+1;var active=n===step;var done=n'; h+='
'+n+'
'+s+'
'; }); h+=''; // ── SUMMARY CARDS ── h+='
'; h+='
'+totalLines+'
Required
'; h+='
'+fullyPicked+'
In Bin
'; h+='
'+fullyIssued+'
Issued
'; h+='
'+shortLines+'
Missing
'; h+='
'+pctDone+'%
Ready
'; h+='
'; // ── STOCK LOCATIONS LEGEND ── h+='
'; h+='\u25cf Main Stock'; h+='\u25cf Job Bin'; h+='\u25cf WIP / Issued'; if(hasReturns)h+='\u25cf Returned'; h+='
'; // ── PRODUCT BUILD BANNER + COLLAPSIBLE BOARD BOM DETAILS ── // For Product Build WOs, technicians pick major assemblies only (PCBAs, modules, // mechanical, fasteners). The individual resistors/capacitors/ICs inside each // PCBA are NOT shown in the bin. An engineer/purchasing reference view is // available via the collapsible section below. if(wo&&wo.woType==='Product Build'&&prd){ h+='
'; h+='
ℹ Product Build \u2014 Final Assembly Pick
'; h+='Detailed PCB components are hidden for Product Build work orders. Built PCBAs are picked as single items. To consume raw electronic components, create a PCBA Build work order for that board instead.'; h+='
'; // Collapsible Board BOM Details (engineering reference, not pickable) var _mbomBoards=getMBOM(wo.productId).filter(function(m){return m.itemType==='board'&&m.refId}); if(_mbomBoards.length){ h+='
'; h+=''; h+='▾ View Board BOM Details (Engineering Reference) — '+_mbomBoards.length+' board'+(_mbomBoards.length===1?'':'s')+''; h+='
'; h+='
Read-only. These components are consumed by PCBA Build WOs, not this Product Build.
'; _mbomBoards.forEach(function(mb){ var _bd=getBoard(mb.refId); var _bom=_bd?D.boms.find(function(b){return b.boardId===_bd.id&&!b.del}):null; var _blines=_bom?getLinesForBom(_bom.id):[]; h+='
'; h+='
'+(mb.desc||(_bd?_bd.name:mb.refId))+' — '+_blines.length+' component line'+(_blines.length===1?'':'s')+'
'; if(_blines.length){ h+='
'; _blines.slice(0,100).forEach(function(bl){ var _bp=getPart(bl.partId); h+=''; h+=''; h+=''; h+=''; }); if(_blines.length>100)h+=''; h+='
DesMPNDescriptionQty
'+(bl.des||'').substring(0,12)+''+((_bp&&_bp.mpn)||'').substring(0,20)+''+((_bp&&_bp.desc)||'').substring(0,40)+''+(bl.qty||1)+'
\u2026 and '+(_blines.length-100)+' more. Open the full BOM in BOM Explorer for details.
'; } else { h+='
No BOM on file for this board.
'; } h+='
'; }); h+='
'; } } // ── BIN CONTENTS TABLE ── h+='
Bin Contents
'; h+='
'; lines.forEach(function(bl){ var p=bl.itemType==='part'||bl.itemType==='purchased'?getPart(bl.refId):null; var bd2=bl.itemType==='board'||bl.itemType==='mechanical'?getBoard(bl.refId):null; var name=p?(p.mpn||p.id):bd2?bd2.name:bl.desc; var desc2=p?(p.desc||'').substring(0,28):bl.desc; var avail=0; if(bl.itemType==='part'||bl.itemType==='purchased')avail=getPartAvailable(bl.refId); else if(bl.itemType==='board'||bl.itemType==='mechanical')avail=getPartAvailable('ASSY_'+bl.refId); var missing=Math.max(0,bl.needQty-bl.pickedQty); var sc=bl.status==='Issued'?'pill-ok':bl.status==='Picked'?'pill-ok':bl.status==='Partial'?'pill-warn':'pill-gray'; var rowBg=missing>0&&avail'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; var shortType=missing>0?((bl.itemType==='board'||bl.itemType==='mechanical')?'Build':'Buy'):''; h+=''; h+=''; // ── ACTION BUTTONS ── h+=''; }); h+='
TypeItemDescriptionNeedIn BinIssuedIn StockMissStatusActions
'+(bl.itemType||'')+''+name+''+desc2+''+bl.needQty+''+bl.pickedQty+''+(bl.issuedQty||'\u2014')+''+avail+''+(missing?missing+' '+shortType+'':'\u2713')+''+bl.status+''; if(bin.status!=='Completed'){ var canPick=missing>0&&avail>0; var canIssue=bl.pickedQty>bl.issuedQty; var canReturn=bl.pickedQty>bl.issuedQty; var noStock=missing>0&&avail<=0; if(canPick)h+=' '; if(canIssue)h+=' '; if(canReturn)h+=' '; if(noStock&&(bl.itemType==='board'||bl.itemType==='mechanical'))h+=' '; if(noStock&&(bl.itemType==='part'||bl.itemType==='purchased'))h+=' '; } h+='
'; // ── RETURN SUMMARY ── var returned=lines.filter(function(l){return l.returnedQty>0}); if(returned.length){ h+='
'; h+='Returned to stock: '; returned.forEach(function(l,i){h+=(i?', ':'')+l.desc.substring(0,20)+' \u00d7'+l.returnedQty}); h+='
'; } // ── BOTTOM ACTIONS ── h+='
'; if(bin.status!=='Completed'){ h+=''; if(allPicked)h+=''; h+=''; } h+='
'; h+=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); } function createPOSuggestion(partId,qty){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){}if(!guardAction('wo_edit','Create PO Suggestion'))return; var p=getPart(partId);if(!p)return; var msg='Create a PO suggestion for:\n\n'+p.mpn+' - '+(p.desc||'').substring(0,40)+'\nQty needed: '+qty+'\nSupplier: '+(p.sup||'TBD')+'\n\nThis will create a draft PO with one line.'; if(!confirm(msg))return; // Create draft PO var po={id:'PO_'+uid(),num:'PO-'+String(D.orders.length+1).padStart(4,'0'),vendor:p.sup||'TBD',projectId:'',productId:'',status:'Draft',expectedDate:'',notes:'Auto-created from shortage',type:'Purchase Order',at:now(),by:U}; D.orders.push(po); // Create PO line var line={id:'POL_'+uid(),poId:po.id,lineType:'Part',itemPN:p.mpn,desc:p.desc||'',qty:String(qty),unitCost:p.pr||'0',spn:p.spn||'',boardId:'',partId:partId,recvQty:'0',accQty:'0',rejQty:'0',notes:'Shortage fill'}; if(!D.poLines)D.poLines=[];D.poLines.push(line); saveAllAsync('Create PO Suggestion',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(__res){if(!handleCriticalSaveResult(__res,'Create PO Suggestion'))return;toast('Draft '+po.num+' created for '+p.mpn+' x'+qty,'ok'); auditLog('PO SUGGESTION','po',po.num,p.mpn+' x'+qty+' shortage'); }); } function createChildWOFromBin(boardId,qty,parentWoId){ if(!guardAction('wo_edit','Create Child WO'))return; var _lk='createChildWOFromBin_'+(arguments[0]||''); return withActionLock(_lk,function(){ var bd=getBoard(boardId); if(!bd)return Promise.resolve({ok:false}); var _cType2=(bd.type==='mech'||bd.type==='assy')?'Assembly Build':'Board Build'; if(!confirm('Create '+_cType2+' WO for '+bd.name+' \u00d7 '+qty+'?'))return Promise.resolve({ok:false}); var parentWO=parentWoId?(D.wo||[]).find(function(w){return w.id===parentWoId}):null; var woNum='WO-'+String(D.wo.length+1).padStart(4,'0'); var newWO={ id:'WO_'+uid(),num:woNum, projectId:bd.projectId||'',boardId:boardId,productId:parentWO?parentWO.productId||'':'', qty:String(qty),unitSN:'',assignee:U, linkedOrder:parentWO?parentWO.num:'', notes:'Child WO for '+bd.name+(parentWO?' (parent: '+parentWO.num+')':''), at:now(),status:'Draft',stages:{},woType:_cType2,by:U, parentWoId:parentWoId||'' }; var _bom=D.boms.find(function(b){return b.boardId===boardId}); if(_bom){newWO.bomId=_bom.id;newWO.bomRev=_bom.rev||'1';newWO.bomSnapshot=getLinesForBom(_bom.id).map(function(l){return{partId:l.partId,qty:l.qty,des:l.des,fit:l.fit}})} return performCriticalMutation({ actionLabel:'Create Child WO from Bin', lockKey:null, mutationFn:function(){D.wo.push(newWO)}, onSuccess:function(){ toast('Created '+newWO.num+' for '+bd.name+' \u00d7 '+qty,'ok'); }, auditAction:['CHILD WO FROM BIN','wo',newWO.id,newWO.num+' '+bd.name+(parentWO?' parent:'+parentWO.num:'')] }); }); } // ═══ PRINT DOCUMENTS ═══ function printJobTraveler(woId){ var wo=D.wo.find(function(x){return x.id===woId});if(!wo)return; var pr=getProject(wo.projectId);var bd=getBoard(wo.boardId); var prd=wo.productId?getProduct(wo.productId):null; var jb=(D.jobBins||[]).find(function(b){return b.woId===wo.id}); var binLines=jb?(D.binLines||[]).filter(function(l){return l.binId===jb.id}):[]; var cl=(D.checklists||[]).find(function(c){return c.woId===wo.id}); var tun=wo.unitSN?(D.tuning||[]).find(function(t){return t.unitSN===wo.unitSN}):null; var gates=checkCompletionReadiness(wo); var _ds=new Date().toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"}); var w=window.open('','_blank','width=800,height=1000');if(!w){toast('Popup blocked','err');return} var h='Job Traveler - '+wo.num+''; h+='

AUGEAS TECHNOLOGIES \u2014 JOB TRAVELER

IonField Systems \u00b7 Manufacturing Document
'; // WO info h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
Work Order'+wo.num+'Date'+_ds+'
Type'+(wo.woType||'Build')+'Status'+wo.status+'
Product'+(prd?prd.name:'')+'Board/Assembly'+(bd?bd.name:'')+'
Project'+(pr?pr.name:'')+'BOM Rev'+(wo.bomRev||'\u2014')+'
Qty'+wo.qty+'Unit SN'+(wo.unitSN||'\u2014')+'
Assigned To'+(wo.assignee||'')+'Job Bin'+(jb?jb.binId:'\u2014')+'
'; // Completion gates h+='

Completion Readiness

'; gates.forEach(function(g){ h+='
'+(g.pass?'\u2713':'\u2717')+''+g.label+''+(g.required?'REQUIRED':'')+'
'; }); // Material list if(binLines.length){ h+='

Material List ('+binLines.length+' items)

'; h+=''; binLines.forEach(function(bl,i){ var bdl=bl.itemType==='board'?getBoard(bl.refId):null;var pl=getPart(bl.refId); h+=''; }); h+='
#TypeItemNeedPickedIssuedStatus
'+(i+1)+''+bl.itemType+''+(pl?pl.mpn:bdl?bdl.name:bl.desc)+''+bl.needQty+''+bl.pickedQty+''+bl.issuedQty+''+(bl.status||'')+'
'; } // Signature block h+='
'; h+='
Technician / Date
'; h+='
QC Inspector / Date
'; h+='
Reviewer / Date
'; h+='
'; h+=' '; w.document.write(h);w.document.close();setTimeout(function(){w.print()},400); } function printReceivingSheet(rcvId){ var r=(D.receipts||[]).find(function(x){return x.id===rcvId});if(!r)return; var p=getPart(r.partId); var _ds=new Date().toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"}); var w=window.open('','_blank','width=800,height=600');if(!w){toast('Popup blocked','err');return} var h='Receiving Sheet'; h+='

AUGEAS TECHNOLOGIES \u2014 RECEIVING / INSPECTION SHEET

IonField Systems
'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
Receipt ID'+r.id+'Date'+fD(r.at)+'
Part'+(p?p.mpn:'')+'Description'+(p?p.desc:'')+'
Supplier'+(r.sup||r.supRef||'')+'PO / Ref'+(r.po||'')+'
Lot / Batch'+(r.lotNum||'\u2014')+'Date Code'+(r.dateCode||'\u2014')+'
Supplier Batch'+(r.supBatch||'\u2014')+'Location'+(r.location||'Main Stock')+'
'; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
MetricQuantityNotes
Received'+r.recQty+'
Accepted'+r.accQty+'
Rejected'+r.rejQty+'
Quarantined'+r.quarQty+'
'; h+=''; h+=''; h+=''; h+=''; h+='
Inspection Status'+r.status+'
Inspector'+(r.inspector||'')+'
Notes'+(r.notes||'\u2014')+'
'; h+='
'; h+='
Received By / Date
'; h+='
Inspected By / Date
'; h+='
QA Approved By / Date
'; h+='
'; w.document.write(h);w.document.close();setTimeout(function(){w.print()},400); } function printPackingSlip(woId){ var wo=D.wo.find(function(x){return x.id===woId});if(!wo)return; var pr=getProject(wo.projectId);var bd=getBoard(wo.boardId); var prd=wo.productId?getProduct(wo.productId):null; var _ds=new Date().toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"}); var w=window.open('','_blank','width=800,height=600');if(!w){toast('Popup blocked','err');return} var h='Packing Slip'; h+='

AUGEAS TECHNOLOGIES \u2014 PACKING SLIP

'; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
Date'+_ds+'WO #'+wo.num+'
Product'+(prd?prd.name:'')+'Board'+(bd?bd.name:'')+'
Qty'+wo.qty+'Unit SN'+(wo.unitSN||'\u2014')+'
Ship To
'; h+='
Packed By / Date
Verified By / Date
'; w.document.write(h);w.document.close();setTimeout(function(){w.print()},400); } function printGenealogyReport(woId){ var wo=D.wo.find(function(x){return x.id===woId});if(!wo||!wo.unitSN)return; var pr=getProject(wo.projectId);var bd=getBoard(wo.boardId); var prd=wo.productId?getProduct(wo.productId):null; var txs=(D.stockTx||[]).filter(function(t){return t.ref===woId&&t.type==='ISSUE'}); var cl=(D.checklists||[]).find(function(c){return c.woId===woId}); var tun=wo.unitSN?(D.tuning||[]).find(function(t){return t.unitSN===wo.unitSN}):null; var _ds=new Date().toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"}); var w=window.open('','_blank','width=800,height=1000');if(!w){toast('Popup blocked','err');return} var h='Genealogy - '+wo.unitSN+''; h+='

AUGEAS TECHNOLOGIES \u2014 UNIT GENEALOGY / TEST CERTIFICATE

'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
Unit Serial Number'+wo.unitSN+'
Product'+(prd?prd.name+' Rev '+(prd.rev||'A'):'')+'
Board / Assembly'+(bd?bd.name:'')+'
Work Order'+wo.num+'
BOM Revision'+(wo.bomRev||'\u2014')+'
Build Qty'+wo.qty+'
Technician'+(wo.assignee||'')+'
Build Date'+fD(wo.at)+'
Completion Date'+fD(wo.closedAt||'')+'
QC Checklist'+(cl?'\u2713 Complete':'\u2014')+'
Tuning'+(tun?'\u2713 '+((tun.pts||[])[0]||{}).freq+' Hz':'\u2014')+'
'; if(txs.length){ h+='

Components Consumed ('+txs.length+')

'; h+=''; txs.forEach(function(t,i){ var p=getPart(t.partId); var bl=(D.binLines||[]).find(function(b){return b.refId===t.partId&&b.pickedLots&&b.pickedLots.length>0}); var lot='';if(bl&&bl.pickedLots&&bl.pickedLots[0]){var pl=bl.pickedLots[0].lot||{};lot=[pl.lotNum,pl.supBatch,pl.dateCode].filter(function(x){return x}).join(' / ')} h+=''; }); h+='
#PartMPNDescriptionQtyLot/BatchDate
'+(i+1)+''+t.partId+''+(p?p.mpn:'')+''+(p?(p.desc||'').substring(0,35):'')+''+t.qty+''+lot+''+fD(t.at)+'
'; } h+='
'; h+='
Technician Signature / Date
'; h+='
QC Reviewer Signature / Date
'; h+='
'; h+=' '; w.document.write(h);w.document.close();setTimeout(function(){w.print()},400); } function printTestCertificate(woId){ var wo=D.wo.find(function(x){return x.id===woId});if(!wo)return; var pr=getProject(wo.projectId);var bd=getBoard(wo.boardId); var prd=wo.productId?getProduct(wo.productId):null; var cl=(D.checklists||[]).find(function(c){return c.woId===woId}); var tun=wo.unitSN?(D.tuning||[]).find(function(t){return t.unitSN===wo.unitSN}):null; var gates=checkCompletionReadiness(wo); var _ds=new Date().toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"}); var w=window.open('','_blank','width=800,height=1000');if(!w){toast('Popup blocked','err');return} var h='Test Certificate - '+(wo.unitSN||wo.num)+''; h+='

AUGEAS TECHNOLOGIES \u2014 TEST / COMPLETION CERTIFICATE

IonField Systems \u00b7 Quality Assurance Document
'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
Certificate Date'+_ds+'Certificate #TC-'+wo.num.replace('WO-','')+'
Unit Serial Number'+(wo.unitSN||'\u2014')+'Work Order'+wo.num+'
Product'+(prd?prd.name+' Rev '+(prd.rev||'A'):'')+'Board / Assembly'+(bd?bd.name:'')+'
BOM Revision'+(wo.bomRev||'\u2014')+'Build Qty'+wo.qty+'
Technician'+(wo.assignee||'')+'Status'+wo.status+'
'; // Completion gates h+='

Test & Quality Gates

'; h+=''; gates.forEach(function(g){ h+=''; }); h+='
RequirementStatusRequired
'+g.label+''+(g.pass?'PASS':'FAIL')+''+(g.required?'Yes':'Optional')+'
'; // Tuning if(tun){ h+='

Tuning Summary

'; h+=''; h+=''; h+=''; h+='
Final Frequency'+(tun.finalFreq||'\u2014')+' Hz
Final DAC Setting'+(tun.finalDAC||'\u2014')+'
Reason'+(tun.reason||'\u2014')+'
'; } // QC summary if(cl){h+='

QC Checklist

Checklist completed: '+cl.label+' \u2014 '+fD(cl.at)+'
'} // Overall result var allPass=gates.filter(function(g){return g.required}).every(function(g){return g.pass}); h+='
'; h+='
'+(allPass?'CERTIFICATE: PASS':'CERTIFICATE: INCOMPLETE')+'
'; h+='
This unit has '+(allPass?'passed':'not yet passed')+' all required quality gates.
'; h+='
'; h+='
'; h+='
Technician Signature / Date
'; h+='
QC Inspector Signature / Date
'; h+='
Program Manager / Date
'; h+='
'; w.document.write(h);w.document.close();setTimeout(function(){w.print()},400); } // ═══ JOB PLANNER ═══ function pgPlanner(el){ var wos=(D.wo||[]).filter(function(w){return w.status!=='Shipped'&&w.status!=='Cancelled'&&w.status!=='Ready to Stock'}); var h='
Job Planner \u2014 What can I build today?
'; if(!wos.length){ h+='
No open work orders. Create one from the Work Orders page.
'; el.innerHTML=h;return; } h+='
'; wos.forEach(function(wo){ var prd=wo.productId?getProduct(wo.productId):null; var bd=wo.boardId?getBoard(wo.boardId):null; var bin=(D.jobBins||[]).find(function(b){return b.woId===wo.id&&b.status!=='Completed'}); var binLines=bin?(D.binLines||[]).filter(function(l){return l.binId===bin.id}):[]; var totalL=binLines.length; var pickedL=binLines.filter(function(l){return l.pickedQty>=l.needQty}).length; var shortL=binLines.filter(function(l){return l.pickedQtyCreate Bin'; else if(shortL>0&&pickedLPick / Resolve'; else if(pct===100)nextAction=''; else nextAction=''; var readyColor=pct===100?'var(--green)':pct>50?'var(--warn)':pct>0?'var(--err)':'var(--fg3)'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; }); h+='
WO #TypeProduct / BoardQtyBinReady %ShortStatusNext Action
'+wo.num+''+(wo.woType||'Build')+''+(prd?prd.name:bd?bd.name:'')+''+wo.qty+''+(bin?bin.binId:'\u2014')+''+pct+'%'+(shortL||'\u2713')+''+wo.status+''+nextAction+'
'; // Summary var ready=wos.filter(function(w){var b=(D.jobBins||[]).find(function(b2){return b2.woId===w.id});if(!b)return false;var bl=(D.binLines||[]).filter(function(l){return l.binId===b.id});return bl.length>0&&bl.every(function(l){return l.pickedQty>=l.needQty})}).length; var blocked=wos.filter(function(w){var b=(D.jobBins||[]).find(function(b2){return b2.woId===w.id});if(!b)return true;var bl=(D.binLines||[]).filter(function(l){return l.binId===b.id});return bl.some(function(l){return l.pickedQty'; h+='
Ready to Build
'+ready+'
'; h+='
Blocked / Short
'+blocked+'
'; h+='
No Bin Yet
'+noBin+'
'; h+=''; el.innerHTML=h; } // ═══ ALTERNATE PARTS ═══ function addAlternate(primaryId){ if(!guardAction('bom_edit','Manage Alternates'))return; var p=getPart(primaryId);if(!p)return; var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); window._altPartId=null; filterAltPicker(); document.getElementById('altSubmit').addEventListener('click',function(){ if(!window._altPartId){toast('Select an alternate part','err');return} if(window._altPartId===primaryId){toast('Cannot alternate with itself','err');return} var reason=document.getElementById('altReason').value||''; var approval=document.getElementById('altApproval').value; if(!D.alternates)D.alternates=[]; // Check for existing var exists=D.alternates.find(function(a){return a.primaryId===primaryId&&a.altId===window._altPartId}); if(exists){toast('Already registered','warn');return} D.alternates.push({id:'ALT_'+uid(),primaryId:primaryId,altId:window._altPartId,reason:reason,approval:approval,at:now(),by:U}); saveAll();document.getElementById('MR').innerHTML='';renderPage(); toast('Alternate added');addLog('ADD ALT',p.mpn+' alt: '+window._altPartId); }); } function filterAltPicker(){ var q=(document.getElementById('altSearch')||{}).value||''; var ql=q.toLowerCase(); var list=document.getElementById('altPickerList');if(!list)return; if(!ql){list.innerHTML='
Type to search...
';return} var matches=D.parts.filter(function(p){return(p.mpn||'').toLowerCase().indexOf(ql)>=0||(p.desc||'').toLowerCase().indexOf(ql)>=0}).slice(0,8); var h=''; matches.forEach(function(p){ h+='
'; h+=''+(p.mpn||p.id)+' '+(p.desc||'').substring(0,40)+'
'; }); list.innerHTML=h||'
No matches
'; } function selectAltPart(partId){ window._altPartId=partId; var p=getPart(partId);if(!p)return; var info=document.getElementById('altPartInfo');if(!info)return; info.style.display='block'; info.innerHTML='Selected: '+p.mpn+' \u2014 '+(p.desc||'').substring(0,50); } function delAlternate(altId){if(!guardAction('wo_edit','Delete Alternate'))return; if(!confirm('Remove this alternate?'))return; D.alternates=(D.alternates||[]).filter(function(a){return a.id!==altId}); saveAll();renderPage();toast('Alternate removed','warn'); } function getAlternates(partId){return(D.alternates||[]).filter(function(a){return a.primaryId===partId||a.altId===partId})} // ═══ TUNING ═══ var _tun={pts:[],timer:0,active:false,cool:false,best:null,analysis:null, startF:'6000',endF:'8000',stepF:'200',notes:'',title:'', unitSN:'',ctrlSN:'',operator:'',gap:'',dacSetup:'', plasmaStart:'',plasmaStop:'',plasmaMid:'', maxDT:'',maxPW:'',ctrlFreq:'',finalFreq:'',finalDAC:'', reason:'',sigTech:'',sigReviewer:''};var _tI=null; function pgTuning(el){ if(!D.tuning)D.tuning=[]; var h='
Tip Charger Tuning
Frequency sweep with DAC voltage control
'; // Saved sessions if(D.tuning.length){ h+='
Saved Sessions
'; D.tuning.forEach(function(s,i){ h+='
'+(s.title||'Session '+(i+1))+'
Unit: '+(s.unitSN||'\u2014')+' \u00b7 '+s.pts.length+' pts \u00b7 '+(s.analysis?s.analysis.verdict:'')+'
'; }); h+='
'; } if(!_tun.pts.length){ // ── SETUP FORM ── h+='
Session Setup
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; } else { // ── PRINT HEADER (hidden on screen) ── var _now=new Date();var _ds=_now.toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"});var _ts=_now.toLocaleTimeString("en-IN",{hour:"2-digit",minute:"2-digit"}); h+=''; // ── SESSION HEADER (screen only) ── h+='
'; h+=''+(_tun.title||'Tuning Session')+''; if(_tun.unitSN)h+='Unit: '+_tun.unitSN+''; if(_tun.ctrlSN)h+='Ctrl: '+_tun.ctrlSN+''; if(_tun.gap)h+='Gap: '+_tun.gap+''; if(_tun.dacSetup)h+='DAC: '+_tun.dacSetup+''; h+='Op: '+(_tun.operator||U)+'
'; // ── TIMER (hidden in print) ── var tBrd=(_tun.timer>0&&_tun.timer<=3&&_tun.active)?'3px solid var(--err)':'1px solid rgba(0,0,0,.06)'; var tClr=(_tun.timer<=3&&_tun.active)?'var(--err)':'var(--fg)'; h+='
'; h+='
'+_tun.timer+'
'; h+='
'+(_tun.cool?'COOLDOWN':_tun.active?'RUN ACTIVE':'READY')+'
'; h+='
'; h+=''; h+=''; h+=''; h+='
'; // ── ACTIONS (hidden in print) ── h+='
'; h+=''; h+=''; h+=''; h+=''; h+='
'; // ── DATA TABLE ── h+='
Sweep Data
'; h+='
'; _tun.pts.forEach(function(p,i){ var isBest=_tun.best&&_tun.best.f===p.f; var bg=isBest?'background:rgba(124,58,237,.06);':''; var dtVal=parseFloat(p.dT);var dtPass=isNaN(dtVal)||!parseFloat(_tun.maxDT)||dtVal<=parseFloat(_tun.maxDT); h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; }); h+='
#Freq (Hz)Init \u00b0CFinal \u00b0C\u0394T \u00b0CWattageDAC/VPlasmaNotes
'+(i+1)+''+(isBest?'\u25b6 ':'')+p.f+''+(p.dT||'\u2014')+'
'; // ── CHARTS ── h+='
'; h+='
Frequency vs \u0394T (\u00b0C)
'+svgChart(_tun.pts,'f','dT','#ff3b30')+'
'; h+='
Frequency vs Wattage
'+svgChart(_tun.pts,'f','pw','#7c3aed')+'
'; h+='
'; // ── NOTES ── h+='
Notes & Observations
'; // ── ANALYSIS ── if(_tun.analysis){ var a=_tun.analysis; var vc=a.verdict==='PASS'?'var(--green)':a.verdict==='MARGINAL'?'var(--warn)':'var(--err)'; h+='
'; h+='
Analysis Results
'+a.verdict+'
'; h+='
'; h+='
Recommended
'+a.bestCompromise.f+' Hz
\u0394T: '+a.bestCompromise.dT+'\u00b0C \u00b7 '+a.bestCompromise.pw+'W
'; h+='
Lowest \u0394T
'+a.minDT+'\u00b0C
at '+a.bestDT.f+' Hz
'; h+='
Lowest Power
'+a.minPW+'W
at '+a.bestPW.f+' Hz
'; h+='
Pass / Fail
'+a.passCount+' / '+a.failCount+'
of '+a.totalPts+'
'; h+='
'; } // ── CONCLUSION ── h+='
'; h+='
Tuning Conclusion
'; h+='
'; h+='
1. Recommended
'+((_tun.analysis&&_tun.analysis.bestCompromise)?_tun.analysis.bestCompromise.f:'\u2014')+' Hz
'; h+='
2. Controller Tuned
'; h+='
3. Final Approved
'; h+='
'; h+='
'; h+='
'; h+='
'; h+='
'; // ── SIGN-OFF ── h+='
'; h+='
Sign-Off
'; h+='
'; h+='
Technician
'; h+='
'; h+='
'; h+='
SignatureDate: ____/____/________
'; h+='
Reviewer
'; h+='
'; h+='
'; h+='
SignatureDate: ____/____/________
'; h+='
'; } el.innerHTML=h; } function genSweep(){ var sf=parseFloat(document.getElementById('tSF').value)||6000; var ef=parseFloat(document.getElementById('tEF').value)||8000; var st=parseFloat(document.getElementById('tST').value)||1000; if(ef<=sf){toast('End must be > Start','err');return} if(st<=0){toast('Step must be > 0','err');return} _tun.startF=String(sf);_tun.endF=String(ef);_tun.stepF=String(st); _tun.title=(document.getElementById('tTitle')||{}).value||''; _tun.unitSN=(document.getElementById('tUnitSN')||{}).value||''; _tun.ctrlSN=(document.getElementById('tCtrlSN')||{}).value||''; _tun.operator=(document.getElementById('tOp')||{}).value||U; _tun.gap=(document.getElementById('tGap')||{}).value||''; _tun.dacSetup=(document.getElementById('tDAC')||{}).value||''; _tun.maxDT=(document.getElementById('tMaxDT')||{}).value||''; _tun.maxPW=(document.getElementById('tMaxPW')||{}).value||''; // Auto-calc middle _tun.plasmaMid=String(((sf+ef)/2).toFixed(0)); var midInput=document.getElementById('tMid'); if(midInput&&midInput.value.trim())_tun.plasmaMid=midInput.value.trim(); _tun.pts=[];_tun.best=null;_tun.analysis=null;_tun.notes=''; for(var f=sf;f<=ef+0.001;f+=st){_tun.pts.push({f:f.toFixed(0),iT:'',eT:'',dT:'',pw:'',dv:'',pl:'',nt:''})} if(!_tun.pts.length)_tun.pts.push({f:sf.toFixed(0),iT:'',eT:'',dT:'',pw:'',dv:'',pl:'',nt:''}); renderPage();toast(_tun.pts.length+' sweep points generated'); } function updTunPt(i,k,v){ _tun.pts[i][k]=v; if(k==='iT'||k==='eT'){ var iT=parseFloat(_tun.pts[i].iT),eT=parseFloat(_tun.pts[i].eT); if(!isNaN(iT)&&!isNaN(eT)){ _tun.pts[i].dT=(eT-iT).toFixed(2); var cell=document.getElementById('tunDT_'+i); if(cell){var dtVal=parseFloat(_tun.pts[i].dT);var dtPass=isNaN(dtVal)||!parseFloat(_tun.maxDT)||dtVal<=parseFloat(_tun.maxDT);cell.textContent=_tun.pts[i].dT;cell.style.color=dtPass?'var(--green)':'var(--err)'} } } } function updateTimerDisplay(){ var tv=document.getElementById('tunTimerVal'); var tl=document.getElementById('tunTimerLabel'); if(!tv||!tl)return; tv.textContent=_tun.timer; tv.style.color=(_tun.timer<=3&&_tun.active)?'var(--err)':'var(--fg)'; tl.textContent=_tun.cool?'COOLDOWN':_tun.active?'RUN ACTIVE':'READY'; tl.style.color=_tun.cool?'var(--warn)':_tun.active?'var(--green)':'var(--fg3)'; var card=document.getElementById('tunTimerCard'); if(card)card.style.border=(_tun.timer>0&&_tun.timer<=3&&_tun.active)?'3px solid var(--err)':'1px solid rgba(0,0,0,.06)'; } function startTunRun(sec){_tun.timer=sec;_tun.active=true;_tun.cool=false;clearInterval(_tI);updateTimerDisplay();_tI=setInterval(function(){_tun.timer--;if(_tun.timer<=0){clearInterval(_tI);_tun.active=false;toast('Run complete','ok')}updateTimerDisplay()},1000)} function startTunCool(sec){_tun.timer=sec;_tun.active=true;_tun.cool=true;clearInterval(_tI);updateTimerDisplay();_tI=setInterval(function(){_tun.timer--;if(_tun.timer<=0){clearInterval(_tI);_tun.active=false;_tun.cool=false;toast('Cooldown complete','ok')}updateTimerDisplay()},1000)} function evalTunBest(){ var valid=_tun.pts.filter(function(p){return p.dT&&p.pw&&!isNaN(parseFloat(p.dT))&&!isNaN(parseFloat(p.pw))}); if(valid.length<2){toast('Need at least 2 complete data points','warn');return} var maxDT=parseFloat(_tun.maxDT)||Infinity,maxPW=parseFloat(_tun.maxPW)||Infinity; var minDT=Infinity,minPW=Infinity,bestDTpt=null,bestPWpt=null; valid.forEach(function(p){var d=parseFloat(p.dT),w=parseFloat(p.pw);if(d0?(passCount>=valid.length*0.5?'PASS':'MARGINAL'):'FAIL'}; _tun.best=bestCompromise;_tun.analysis=a; renderPage();toast('Analysis: '+a.verdict); } function saveTunSess(){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){}if(!guardAction('tuning_entry','Save Tuning Session'))return; if(!D.tuning)D.tuning=[]; D.tuning.push({pts:JSON.parse(JSON.stringify(_tun.pts)),best:_tun.best,analysis:_tun.analysis,startF:_tun.startF,endF:_tun.endF,stepF:_tun.stepF,notes:_tun.notes,title:_tun.title,unitSN:_tun.unitSN,ctrlSN:_tun.ctrlSN,operator:_tun.operator,gap:_tun.gap,dacSetup:_tun.dacSetup,plasmaMid:_tun.plasmaMid,maxDT:_tun.maxDT,maxPW:_tun.maxPW,ctrlFreq:_tun.ctrlFreq,finalFreq:_tun.finalFreq,finalDAC:_tun.finalDAC,reason:_tun.reason,sigTech:_tun.sigTech,sigReviewer:_tun.sigReviewer,at:now(),by:U}); saveAllAsync('Save Tuning Session',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(__res){if(!handleCriticalSaveResult(__res,'Save Tuning Session'))return;try{auditLog('SAVE TUNING','tuning',(_tun&&_tun.unitSN)||""||'','save tuning');}catch(e){}toast('Session saved');addLog('TUNING SAVE',_tun.title||''); }); } function loadTunSess(i){var s=D.tuning[i];var keys=['pts','best','analysis','startF','endF','stepF','notes','title','unitSN','ctrlSN','operator','gap','dacSetup','plasmaMid','maxDT','maxPW','ctrlFreq','finalFreq','finalDAC','reason','sigTech','sigReviewer'];keys.forEach(function(k){if(k==='pts')_tun.pts=JSON.parse(JSON.stringify(s.pts||[]));else _tun[k]=s[k]||''});_tun.analysis=s.analysis||null;_tun.best=s.best||null;renderPage();toast('Session loaded','ok')} function delTunSess(i){if(!guardAction('admin','Delete Tuning Session'))return;if(!confirm('Delete this session?'))return;D.tuning.splice(i,1);saveAll();renderPage();toast('Deleted','warn')} // ── PRINT REPORT (dedicated DOM approach) ── function printTuningReport(){ var w=window.open('','_blank','width=800,height=1100'); if(!w){toast('Popup blocked','err');return} var pts=_tun.pts.filter(function(p){return p.iT||p.eT||p.pw}); var a=_tun.analysis; var _ds=new Date().toLocaleDateString("en-IN",{day:"2-digit",month:"short",year:"numeric"}); var _ts=new Date().toLocaleTimeString("en-IN",{hour:"2-digit",minute:"2-digit"}); function plTxt(v){return v==='Y'?'\u2713 Yes':v==='S'?'\u2605 Strong':v==='W'?'~ Weak':v==='N'?'\u2717 No':''} var h='Tuning Report - '+(_tun.title||'')+''; // ═══ PAGE 1: Setup Snapshot + Sweep Table ═══ h+='
'; h+='

AUGEAS TECHNOLOGIES

IonField Systems \u00b7 Engineering Operations Platform
TIP CHARGER FREQUENCY TUNING REPORT
'; // Setup Snapshot box h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; if(parseFloat(_tun.maxDT)||parseFloat(_tun.maxPW)){h+=''} h+='
Session Title'+(_tun.title||'')+'Date / Time'+_ds+' '+_ts+'
Unit Serial #'+(_tun.unitSN||'\u2014')+'Controller Serial #'+(_tun.ctrlSN||'\u2014')+'
Operator'+(_tun.operator||U)+'Gap'+(_tun.gap||'\u2014')+'
DAC Voltage Setup'+(_tun.dacSetup||'\u2014')+'Plasma Range'+_tun.startF+' \u2192 '+_tun.endF+' Hz (step '+_tun.stepF+')
Middle Frequency'+(_tun.plasmaMid||'\u2014')+' HzData Points'+pts.length+' measured
\u0394T Limit'+(parseFloat(_tun.maxDT)?_tun.maxDT+'\u00b0C':'\u2014')+'Power Limit'+(parseFloat(_tun.maxPW)?_tun.maxPW+' W':'\u2014')+'
'; // Sweep data table (flows naturally, thead repeats per page) h+='

Sweep Data

'; h+=''; pts.forEach(function(p,i){ var isBest=_tun.best&&_tun.best.f===p.f; h+=''; h+=''; h+=''; h+=''; var nt=(p.nt||'').trim();h+=''; }); h+='
#Freq (Hz)Init \u00b0CFinal \u00b0C\u0394T \u00b0CWattageDAC/VPlasmaNotes
'+(i+1)+''+(isBest?'\u25b6 ':'')+p.f+''+(p.iT||'')+''+(p.eT||'')+''+(p.dT||'')+''+(p.pw||'')+''+(p.dv||'')+''+plTxt(p.pl)+''+(nt||'')+'
'; h+='
'; // ═══ PAGE 2: Charts + Analysis + Conclusion ═══ h+='
'; h+='
AUGEAS TECHNOLOGIES \u00b7 Tuning Report \u00b7 '+(_tun.unitSN||_tun.title||'')+' \u00b7 Page 2
'; h+='

Temperature Rise (\u0394T) vs Frequency

'; h+=svgChart(pts,'f','dT','#ff3b30'); h+='

Wattage vs Frequency

'; h+=svgChart(pts,'f','pw','#7c3aed'); // Analysis summary if(a){ h+='

Analysis Summary

'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
Verdict'+a.verdict+'
Recommended Frequency'+a.bestCompromise.f+' Hz
Best \u0394T'+a.minDT+'\u00b0C at '+a.bestDT.f+' Hz
Best Power'+a.minPW+'W at '+a.bestPW.f+' Hz
Pass / Fail'+a.passCount+' pass, '+a.failCount+' fail (of '+a.totalPts+' points)
'; } // ── FINAL TUNING SUMMARY (prominent box) ── h+='
'; h+='
Final Tuning Summary
'; h+='
Final Approved Frequency'+(_tun.finalFreq||'\u2014')+' Hz
'; h+='
Controller Tuned To'+(_tun.ctrlFreq||'\u2014')+' Hz
'; h+='
Recommended by Analysis'+((a&&a.bestCompromise)?a.bestCompromise.f:'\u2014')+' Hz
'; h+='
Final DAC Setting'+(_tun.finalDAC||'\u2014')+'
'; h+='
Gap Used'+(_tun.gap||'\u2014')+'
'; h+='
DAC Voltage Setup'+(_tun.dacSetup||'\u2014')+'
'; if(_tun.reason){h+='
Reason'+_tun.reason+'
'} h+='
'; // Notes (only if meaningful) if((_tun.notes||'').trim()){h+='

Notes & Observations

'+_tun.notes+'
'} // Signature block h+='

Sign-Off

'; h+='
'; h+='
Technician / Operator
'+(_tun.sigTech||_tun.operator||U)+'
SignatureDate: ____/____/________
'; h+='
Reviewed & Approved By
'+(_tun.sigReviewer||'')+'
SignatureDate: ____/____/________
'; h+='
'; h+='
'; h+=' '; w.document.write(h);w.document.close(); setTimeout(function(){w.print()},400); } // SVG Chart function svgChart(data,xKey,yKey,color){ var vals=data.map(function(d){return parseFloat(d[yKey])}).filter(function(v){return !isNaN(v)}); var xVals=data.map(function(d){return parseFloat(d[xKey])}).filter(function(v){return !isNaN(v)}); if(vals.length<2)return '
Enter at least 2 data points
'; var W=480,H=180,pad={t:14,r:14,b:32,l:48}; var w=W-pad.l-pad.r,ht=H-pad.t-pad.b; var xMin=Math.min.apply(null,xVals),xMax=Math.max.apply(null,xVals); var yMin=Math.min.apply(null,vals),yMax=Math.max.apply(null,vals); var yPad=(yMax-yMin)*0.15||1;yMin-=yPad;yMax+=yPad; var xR=xMax-xMin||1,yR=yMax-yMin||1; var pts=[];data.forEach(function(d){var x=parseFloat(d[xKey]),y=parseFloat(d[yKey]);if(!isNaN(x)&&!isNaN(y))pts.push({px:pad.l+((x-xMin)/xR)*w,py:pad.t+ht-((y-yMin)/yR)*ht,x:x,y:y})}); if(!pts.length)return ''; function fmtX(v){return v>=1000?Math.round(v).toString():v.toFixed(1)} function fmtY(v){return v>=100?Math.round(v).toString():v.toFixed(1)} var pathD=pts.map(function(p,i){return(i===0?'M':'L')+p.px.toFixed(1)+','+p.py.toFixed(1)}).join(' '); var area=pathD+' L'+pts[pts.length-1].px.toFixed(1)+','+(pad.t+ht)+' L'+pts[0].px.toFixed(1)+','+(pad.t+ht)+' Z'; var bestI=vals.indexOf(Math.min.apply(null,vals)); var isRec=_tun.best&&pts[bestI]&&parseFloat(_tun.best.f)===pts[bestI].x; var s=''; s+=''; for(var gi=0;gi<=4;gi++){var gy=pad.t+ht-(gi/4)*ht;var gv=yMin+(gi/4)*yR;s+='';s+=''+fmtY(gv)+''} var skip=pts.length>8?2:1; pts.forEach(function(p,i){if(i%skip===0||i===pts.length-1)s+=''+fmtX(p.x)+''}); s+=''; s+=''; pts.forEach(function(p,i){var r=i===bestI?5:3;s+=''}); if(pts[bestI]){var bx=pts[bestI].px,by=pts[bestI].py;var lbl=isRec?'\u2605 Rec':'Best';s+='';s+=''+lbl+''} s+='Frequency (Hz)'; s+=''; return s; } // ═══ BOM IMPORT ═══ function bomImport(){ if(!guardAction('bom_edit','Import BOM'))return; var pOpts=D.projects.filter(function(p){return !p.del}).map(function(p){return ''}).join(''); var bOpts=D.boards.filter(function(b){return !b.del}).map(function(b){return ''}).join(''); var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('biCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); document.getElementById('biFile').addEventListener('change',function(e){ var f=e.target.files[0];if(!f)return; var reader=new FileReader(); reader.onload=function(ev){ var text=ev.target.result; var lines=text.split('\n').filter(function(l){return l.trim()}); if(lines.length<2)return; var delim=lines[0].indexOf('\t')>=0?'\t':','; function parseCSVLine(line,d){var result=[],current='',inQuote=false;for(var ci=0;ci=0||h.indexOf('manufacture pn')>=0||h.indexOf('manufacturer pn')>=0))colMap.mpn=h; else if(!colMap.mpn&&(h==='part number'||h==='part no'||h==='pn'||h.indexOf('value')>=0))colMap.mpn=h; if(!colMap.desc&&h.indexOf('desc')>=0)colMap.desc=h; if(!colMap.qty&&(h.indexOf('qty')>=0||h.indexOf('quantity')>=0))colMap.qty=h; if(!colMap.des&&(h.indexOf('ref')>=0||h.indexOf('designat')>=0||h==='des'||h.indexOf('reference')>=0))colMap.des=h; if(!colMap.fit&&(h.indexOf('fit')>=0||h.indexOf('dnp')>=0||h.indexOf('exclude')>=0))colMap.fit=h; if(!colMap.mfr&&(h.indexOf('mfr')>=0||h.indexOf('manuf')>=0||h==='make'))colMap.mfr=h; if(!colMap.pkg&&(h.indexOf('foot')>=0||h.indexOf('pkg')>=0||h.indexOf('package')>=0))colMap.pkg=h; if(!colMap.sup&&(h==='supplier'||h==='sup'||h==='vendor'))colMap.sup=h; if(!colMap.spn&&(h==='supplier pn'||h==='supplier no'||h==='supplier no.'||h==='sup pn'||h==='spn'||h==='vendor pn'))colMap.spn=h; }); if(!colMap.mpn&&!colMap.ifsPN&&headers.length>1)colMap.mpn=headers[1]; if(!colMap.des&&headers.length>0)colMap.des=headers[0]; // Preview var valid=rows.filter(function(r){return (colMap.ifsPN&&r[colMap.ifsPN])||(colMap.mpn&&r[colMap.mpn])}); var dnpCount=rows.filter(function(r){var ft=r[colMap.fit||'']||'';return ft.toLowerCase()==='dnp'||ft.toLowerCase()==='yes'}).length; var ph='
'; ph+=''+rows.length+' rows found \u00b7 '+valid.length+' valid \u00b7 '+dnpCount+' DNP'; ph+='
Mapped: IFS PN='+(colMap.ifsPN||'\u2014')+' MPN='+(colMap.mpn||'?')+' Des='+(colMap.des||'?')+' Qty='+(colMap.qty||'?')+' Fit='+(colMap.fit||'?')+'
'; ph+='
'; rows.slice(0,20).forEach(function(r){ var ifsPN=r[colMap.ifsPN||'']||'';var mpn=r[colMap.mpn||'']||'';var des=r[colMap.des||'']||'';var qty=r[colMap.qty||'']||'1';var fit=r[colMap.fit||'']||'Fitted'; ph+=''; }); if(rows.length>20)ph+=''; ph+='
IFS PNMPN/ValueDesignatorQtyFitMfr
'+ifsPN+''+mpn+''+des+''+qty+''+fit+''+(r[colMap.mfr||'']||'')+'
...and '+(rows.length-20)+' more
'; document.getElementById('biPreview').innerHTML=ph; document.getElementById('biImport').disabled=false; window._biRows=rows;window._biColMap=colMap; document.getElementById('biImport').onclick=function(){ var bom=curBom;if(!bom){toast('No BOM selected','err');return} var imported=0,skipped=0,created=0,matchedByIfs=0,matchedByMpn=0,matchedBySpn=0; window._biRows.forEach(function(r){ var ifsPN=(r[window._biColMap.ifsPN||'']||'').trim(); var mpn=(r[window._biColMap.mpn||'']||'').trim(); var spn=(r[window._biColMap.spn||'']||'').trim(); if(!ifsPN&&!mpn&&!spn)return; var des=r[window._biColMap.des||'']||''; var qty=r[window._biColMap.qty||'']||'1'; var fit=r[window._biColMap.fit||'']||'Fitted'; if(fit.toLowerCase()==='dnp'||fit.toLowerCase()==='yes')fit='DNP';else fit='Fitted'; // Match priority: IFS PN → MPN → Supplier PN → create var part=null; if(ifsPN)part=D.parts.find(function(p){return(p.ifsPN||'').toLowerCase()===ifsPN.toLowerCase()}); if(part)matchedByIfs++; else if(mpn){part=D.parts.find(function(p){return(p.mpn||'').toLowerCase()===mpn.toLowerCase()});if(part)matchedByMpn++} if(!part&&spn){part=D.parts.find(function(p){return(p.spn||'').toLowerCase()===spn.toLowerCase()});if(part)matchedBySpn++} if(!part){ part={id:nextPN('ELC'),ifsPN:ifsPN,mpn:mpn,mfr:r[window._biColMap.mfr||'']||'',desc:r[window._biColMap.desc||'']||mpn||ifsPN,pkg:r[window._biColMap.pkg||'']||'',sup:r[window._biColMap.sup||'']||'',spn:spn,pr:'0',cat:'',status:'active',importSource:'CSV BOM Import',importedAt:now(),importedBy:U}; D.parts.push(part);created++; } else if(ifsPN&&!part.ifsPN){ // Back-fill IFS PN on existing part that didn't have one part.ifsPN=ifsPN; } // Check duplicate var exists=D.bomLines.find(function(l){return l.bomId===bom.id&&l.partId===part.id&&l.des===des}); if(exists){skipped++;return} D.bomLines.push({id:'L_'+uid(),bomId:bom.id,partId:part.id,qty:qty,des:des,fit:fit,notes:''}); imported++; }); saveAll();document.getElementById('MR').innerHTML='';renderPage(); var matchSummary=[]; if(matchedByIfs)matchSummary.push(matchedByIfs+' by IFS PN'); if(matchedByMpn)matchSummary.push(matchedByMpn+' by MPN'); if(matchedBySpn)matchSummary.push(matchedBySpn+' by SPN'); toast('Imported '+imported+' lines \u00b7 '+created+' new parts \u00b7 '+skipped+' skipped'+(matchSummary.length?' \u00b7 matched: '+matchSummary.join(', '):''),'ok'); auditLog('BOM IMPORT','bom',bom.id,imported+' lines imported'); }; }; reader.readAsText(f); }); } // ═══ ORDERS (MULTI-LINE PO) ═══ function pgOrders(el){ if(!D.orders)D.orders=[];if(!D.poLines)D.poLines=[]; var pOpts=[''].concat(D.projects.filter(function(p){return !p.del}).map(function(p){return p.id+'|'+p.name})); var prodOpts=[''].concat((D.products||[]).map(function(p){return p.id+'|'+p.name})); var h='
'; h+='Purchase Order Flow: '; h+='Select vendor \u2192 Add PO lines (boards, parts, mechanicals) \u2192 Submit \u2192 Receive & Inspect \u2192 Stock'; h+='
One PO can contain items for multiple boards and products. Use "Auto-fill from Product BOM" to populate lines from MBOM.'; h+='
'; h+='
'; // Summary stats var openPOs=D.orders.filter(function(o){return o.status!=='Closed'&&o.status!=='Cancelled'}).length; var totalLines=D.poLines.length; h+='
Purchase Orders
'+D.orders.length+'
Open POs
'+openPOs+'
Total Lines
'+totalLines+'
'; // PO list if(D.orders.length){ h+='
'; D.orders.forEach(function(o){ var pr=o.projectId?getProject(o.projectId):null; var prd=o.productId?getProduct(o.productId):null; var lc=(D.poLines||[]).filter(function(l){return l.poId===o.id}).length; var sc=o.status==='Closed'||o.status==='Received'?'pill-ok':o.status==='Cancelled'?'pill-err':'pill-warn'; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; }); h+='
PO #VendorProjectProductLinesStatusDateExpected
'+o.num+''+(o.vendor||'')+''+(pr?pr.name:'')+''+(prd?prd.name:'')+''+lc+''+o.status+''+fD(o.at)+''+(o.expectedDate||'\u2014')+'
'; } else { h+='
No purchase orders yet. Click "+ New Purchase Order" to create one.
'; } el.innerHTML=h; } function newPO(){if(!guardAction('wo_edit','New Purchase Order'))return; var pOpts=[''].concat(D.projects.filter(function(p){return !p.del}).map(function(p){return p.id+'|'+p.name})); var prodOpts=[''].concat((D.products||[]).map(function(p){return p.id+'|'+p.name})); showModal('New Purchase Order',[ {k:'num',l:'PO Number'}, {k:'vendor',l:'Vendor'}, {k:'projectId',l:'Project',t:'select',opts:pOpts}, {k:'productId',l:'Product (optional)',t:'select',opts:prodOpts}, {k:'status',l:'Status',t:'select',opts:['Draft','Submitted','Confirmed','Partial Receipt','Received','Closed','Cancelled']}, {k:'expectedDate',l:'Expected Date'}, {k:'notes',l:'Notes',t:'textarea'} ],{num:'PO-'+String(D.orders.length+1).padStart(4,'0'),status:'Draft'},function(r){ r.id='PO_'+uid();r.type='Purchase Order';r.at=now();r.by=U; D.orders.push(r);saveAll();try{auditLog('CREATE PO','po',r.num||r.id,'create po');}catch(e){}renderPage();toast('Created '+r.num);addLog('NEW PO',r.num); }); } function editPO(id){if(!guardAction('wo_edit','Edit Purchase Order'))return; var o=D.orders.find(function(x){return x.id===id});if(!o)return; var pOpts=[''].concat(D.projects.filter(function(p){return !p.del}).map(function(p){return p.id+'|'+p.name})); var prodOpts=[''].concat((D.products||[]).map(function(p){return p.id+'|'+p.name})); showModal('Edit '+o.num,[ {k:'num',l:'PO Number'}, {k:'vendor',l:'Vendor'}, {k:'projectId',l:'Project',t:'select',opts:pOpts}, {k:'productId',l:'Product',t:'select',opts:prodOpts}, {k:'status',l:'Status',t:'select',opts:['Draft','Submitted','Confirmed','Partial Receipt','Received','Closed','Cancelled']}, {k:'expectedDate',l:'Expected Date'}, {k:'notes',l:'Notes',t:'textarea'} ],o,function(r){Object.assign(o,r);saveAll();try{auditLog('EDIT PO','po',po.num,'edit po');}catch(e){}renderPage();toast('Updated','ok')}); } function delPO(id){if(!guardAction('admin','Delete PO'))return;if(!confirm('Delete this PO and all its lines?'))return;D.orders=D.orders.filter(function(x){return x.id!==id});D.poLines=(D.poLines||[]).filter(function(l){return l.poId!==id});saveAll();renderPage();toast('PO deleted','warn');auditLog('DELETE PO','po',id,'PO deleted')} function viewPO(poId){ var o=D.orders.find(function(x){return x.id===poId});if(!o)return; var pr=o.projectId?getProject(o.projectId):null; var prd=o.productId?getProduct(o.productId):null; var lines=(D.poLines||[]).filter(function(l){return l.poId===poId}); var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); } function addPOLine(poId){if(!guardAction('wo_edit','Add PO Line'))return; var bOpts=[''].concat(D.boards.filter(function(b){return !b.del}).map(function(b){return b.id+'|'+b.name})); showModal('Add PO Line',[ {k:'lineType',l:'Line Type',t:'select',opts:['Part','Board Assembly','Mechanical','Service','Consumable']}, {k:'itemPN',l:'Item / Part Number'}, {k:'desc',l:'Description'}, {k:'qty',l:'Quantity',t:'number',def:'1'}, {k:'unitCost',l:'Unit Cost (INR)',t:'number'}, {k:'spn',l:'Supplier Part Number'}, {k:'boardId',l:'Linked Board/Assembly (optional)',t:'select',opts:bOpts}, {k:'notes',l:'Notes'} ],{lineType:'Part',qty:'1'},function(r){ r.id='POL_'+uid();r.poId=poId;r.recvQty='0';r.accQty='0';r.rejQty='0'; // Try to link to master part if(r.itemPN){var mp=D.parts.find(function(p){return p.mpn===r.itemPN||p.id===r.itemPN});if(mp){r.partId=mp.id;if(!r.desc)r.desc=mp.desc}} if(!D.poLines)D.poLines=[];D.poLines.push(r); saveAll();try{auditLog('ADD PO LINE','po',poId,(r.itemPN||r.desc||r.id)+' qty='+(r.qty||''));}catch(e){}document.getElementById('MR').innerHTML='';viewPO(poId);toast('Line added'); }); } function editPOLine(lineId){if(!guardAction('wo_edit','Edit PO Line'))return; var l=(D.poLines||[]).find(function(x){return x.id===lineId});if(!l)return; var bOpts=[''].concat(D.boards.filter(function(b){return !b.del}).map(function(b){return b.id+'|'+b.name})); showModal('Edit Line',[ {k:'lineType',l:'Line Type',t:'select',opts:['Part','Board Assembly','Mechanical','Service','Consumable']}, {k:'itemPN',l:'Item / Part Number'}, {k:'desc',l:'Description'}, {k:'qty',l:'Quantity',t:'number'}, {k:'unitCost',l:'Unit Cost (INR)',t:'number'}, {k:'spn',l:'Supplier Part Number'}, {k:'boardId',l:'Linked Board/Assembly',t:'select',opts:bOpts}, {k:'recvQty',l:'Received Qty',t:'number'}, {k:'accQty',l:'Accepted Qty',t:'number'}, {k:'rejQty',l:'Rejected Qty',t:'number'}, {k:'notes',l:'Notes'} ],l,function(r){Object.assign(l,r);saveAll();try{auditLog('EDIT PO LINE','po_line',lineId,(l.itemPN||l.desc||lineId)+' edited');}catch(e){}document.getElementById('MR').innerHTML='';viewPO(l.poId);toast('Updated','ok')}); } function delPOLine(lineId){if(!guardAction('wo_edit','Delete PO Line'))return;if(!confirm('Delete line?'))return;var l=(D.poLines||[]).find(function(x){return x.id===lineId});var poId=l?l.poId:'';D.poLines=(D.poLines||[]).filter(function(x){return x.id!==lineId});saveAll();if(poId){document.getElementById('MR').innerHTML='';viewPO(poId)}toast('Line deleted','warn')} function autoFillPOFromProduct(poId){if(!guardAction('wo_edit','Auto-Fill PO'))return; var o=D.orders.find(function(x){return x.id===poId});if(!o||!o.productId)return; var mbom=getMBOM(o.productId);if(!mbom.length)return toast('No product BOM defined','warn'); var added=0; mbom.forEach(function(m){ // Check if already added var exists=(D.poLines||[]).find(function(l){return l.poId===poId&&l.desc===m.desc}); if(exists)return; var line={id:'POL_'+uid(),poId:poId,lineType:m.itemType==='board'?'Board Assembly':m.itemType==='purchased'?'Part':'Mechanical',itemPN:'',desc:m.desc,qty:m.qty||'1',unitCost:'0',spn:'',boardId:m.itemType==='board'||m.itemType==='mechanical'?m.refId:'',partId:m.itemType==='purchased'?m.refId:'',recvQty:'0',accQty:'0',rejQty:'0',notes:m.notes||''}; D.poLines.push(line);added++; }); saveAll();document.getElementById('MR').innerHTML='';viewPO(poId); toast(added+' lines added from product BOM'); } function pushPOToWOs(poId){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){}if(!guardAction('wo_edit','Push PO to WOs'))return; var o=D.orders.find(function(x){return x.id===poId});if(!o)return; var allLines=(D.poLines||[]).filter(function(l){return l.poId===poId}); // Identify lines that can generate WOs var boardLines=allLines.filter(function(l){return(l.lineType==='Board Assembly'||l.lineType==='Mechanical')&&l.boardId}); var productLine=o.productId?true:false; if(!boardLines.length&&!productLine){ toast('No board assembly lines or product context to create WOs from','warn');return; } var created=0; // Board assembly lines → Board Build WOs boardLines.forEach(function(l){ var bd=getBoard(l.boardId);if(!bd)return; var exists=D.wo.find(function(w){return w.linkedOrder===o.num&&w.boardId===l.boardId}); if(exists)return; var _poType=(bd.type==='mech'||bd.type==='assy')?'Assembly Build':'Board Build'; var wo={id:'WO_'+uid(),num:'WO-'+String(D.wo.length+1).padStart(4,'0'),projectId:o.projectId||bd.projectId||'',boardId:l.boardId,qty:l.qty||'1',unitSN:'',assignee:U,linkedOrder:o.num,notes:'From '+o.num+': '+bd.name,at:now(),status:'Draft',stages:{},woType:_poType,by:U}; var _bom=D.boms.find(function(b){return b.boardId===l.boardId}); if(_bom){wo.bomId=_bom.id;wo.bomRev=_bom.rev||'1';wo.bomSnapshot=getLinesForBom(_bom.id).map(function(bl){return{partId:bl.partId,qty:bl.qty,des:bl.des,fit:bl.fit}})} D.wo.push(wo);created++; }); // If PO has a product context and no product WO exists yet → offer Product Build WO if(o.productId){ var existsProd=D.wo.find(function(w){return w.linkedOrder===o.num&&w.productId===o.productId}); if(!existsProd){ var prd=getProduct(o.productId); if(prd&&confirm('Also create a Product Build WO for '+prd.name+'?')){ var wo={id:'WO_'+uid(),num:'WO-'+String(D.wo.length+1).padStart(4,'0'),projectId:o.projectId||prd.projectId||'',productId:o.productId,boardId:'',qty:'1',unitSN:'',assignee:U,linkedOrder:o.num,notes:'Product build from '+o.num,at:now(),status:'Draft',stages:{},woType:'Product Build',by:U}; var mbom=getMBOM(o.productId); wo.mbomSnapshot=mbom.map(function(m){return{itemType:m.itemType,refId:m.refId,desc:m.desc,qty:m.qty}}); D.wo.push(wo);created++; } } } saveAllAsync('Push PO to WOs',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(__res){if(!handleCriticalSaveResult(__res,'Push PO to WOs'))return;document.getElementById('MR').innerHTML='';renderPage(); if(created)toast(created+' work order'+(created>1?'s':'')+' created from '+o.num,'ok'); else toast('No new WOs to create (all already exist)','warn'); auditLog('PO TO WO','po',poId,o.num+': '+created+' WOs created'); }); } // ═══ CHECKLIST TEMPLATE SYSTEM ═══ // Seed the Tip Charger template on first load function togglePTItem(key,checked){try{if(checked)localStorage.setItem(key,'1');else localStorage.removeItem(key)}catch(e){}} // Generic copy-to-clipboard for any
...
block. function copyText(elId){ var el=document.getElementById(elId);if(!el)return; var txt=el.innerText||el.textContent; if(navigator.clipboard){ navigator.clipboard.writeText(txt).then(function(){toast('Copied to clipboard','ok')}) .catch(function(){toast('Copy failed \u2014 select text manually','warn')}); } else { toast('Clipboard unavailable \u2014 select text manually','warn'); } } // Invite User placeholder. Does NOT actually create a Supabase user (that would // require service_role in the frontend, which is a security no-no). Instead, it // generates the exact SQL the Admin can run after manually creating the Auth user. function inviteUserPlaceholder(){ if(!guardAction('admin','Invite User'))return; var email=((document.getElementById('invEmail')||{}).value||'').trim(); var role=((document.getElementById('invRole')||{}).value||'Viewer'); var out=document.getElementById('invResult');if(!out)return; if(!email||email.indexOf('@')<0){ out.innerHTML='
Enter a valid email address.
'; return; } var name=email.split('@')[0].replace(/[._-]/g,' ').replace(/\b\w/g,function(c){return c.toUpperCase()}); var snippet='-- Step 1: Create the user in Supabase Dashboard \u2192 Authentication \u2192 Users\n'+ '-- Enter email: '+email+'\n'+ '-- Copy the generated User UID.\n'+ '--\n'+ '-- Step 2: Run this SQL in Supabase SQL Editor, replacing the UID placeholder:\n'+ 'INSERT INTO public.user_profiles (id, email, display_name, role)\n'+ 'VALUES (\n'+ ' \'PASTE-AUTH-USER-UUID-HERE\',\n'+ ' \''+email+'\',\n'+ ' \''+name+'\',\n'+ ' \''+role+'\'\n'+ ');\n'+ '--\n'+ '-- Step 3: Share the email + starting password with the user so they can sign in.\n'+ '-- They can change their password via the Supabase Auth password reset flow.'; var sid='invSQL_'+Date.now(); out.innerHTML='
'+ '
Run this after creating the user in Supabase Auth:
'+ '
'+ '
'+snippet.replace(/'+
    ''+
    '
'; auditLog('INVITE USER SQL','user_profile',email,'Role='+role+' (SQL generated, not executed)'); } function copyRLSSQL(){ var el=document.getElementById('rlsSQL');if(!el)return; var txt=el.innerText||el.textContent; if(navigator.clipboard){navigator.clipboard.writeText(txt).then(function(){toast('SQL copied to clipboard','ok')}).catch(function(){toast('Copy failed — select and copy manually','warn')})} else{toast('Clipboard unavailable — select text manually','warn')} } function repairDefaultTemplates(){ if(!guardAction('admin','Repair Default Templates'))return; if(!confirm('Repair missing default checklist templates? This will ADD missing defaults only. User-edited templates will NOT be modified.'))return; var before=D.clTemplates.length; seedCLTemplates(true); var after=D.clTemplates.length; saveAll();renderPage(); toast('Added '+(after-before)+' missing default templates','ok'); auditLog('REPAIR TEMPLATES','system','',(after-before)+' templates added'); } function seedCLTemplates(safeBackfill){ if(!D.clTemplates)D.clTemplates=[]; var existingIds={};(D.clTemplates||[]).forEach(function(t){existingIds[t.id]=true}); // Full seed only if empty AND not called in backfill mode if(D.clTemplates.length&&!safeBackfill)return; // In backfill mode, skip templates that already exist (preserve user edits) var _wasEmpty=D.clTemplates.length===0; function _push(tpl){if(!existingIds[tpl.id])D.clTemplates.push(tpl)} // Tip Charger - Universal Controller 4.1 Final Check _push({ id:'CLT_001',category:'Final Product QC',name:'Tip Charger - Universal Controller 4.1 Final Check', projectId:'proj1',boardId:'B0003',type:'Final Check',rev:'1.0', equipment:['Multichannel oscilloscope with differential probes','Multimeter with AC & DC voltage settings','Outlet mounted watt meter'], headerFields:['docNum','serialNum','inspector','date','fwVer','fwRev','fwDate','dspVer','dspRev','dspDate','dispVer','dispRev','dispDate','overallResult'], steps:[ {id:'s01',sec:'Hardware Inspection',text:'Inspect ALL hardware is properly installed'}, {id:'s02',sec:'Hardware Inspection',text:'Inspect ALL connectors for proper seating and engagement'}, {id:'s03',sec:'Firmware Verification',text:'Record Firmware SW Version / Rev / Date Loaded'}, {id:'s04',sec:'Firmware Verification',text:'Record DSP SW Version / Rev / Date Loaded'}, {id:'s05',sec:'Firmware Verification',text:'Record Display SW Version / Rev / Date Loaded'}, {id:'s06',sec:'Initial Setup',text:'Adjust plasma DAC to lowest possible setting'}, {id:'s07',sec:'Initial Setup',text:'Securely connect the DUT 8 Channel Tip Charger to HV connector and airline'}, {id:'s08',sec:'Initial Setup',text:'Install ALL fuses: 5A time-delay Main, 2A fast-acting Transformer, and 8A time-delay Board'}, {id:'s09',sec:'Power-On',text:'Power on through outlet mounted watt meter and confirm display, 12V power supply, and all boards powered on'}, {id:'s10',sec:'Display & Controls',text:'Navigate to Settings on the display'}, {id:'s11',sec:'Display & Controls',text:'Adjust Screen Brightness via the slider'}, {id:'s12',sec:'Display & Controls',text:'Adjust Vacuum Pump Off Speed via slider and engage Off Pump at multiple settings'}, {id:'s13',sec:'Display & Controls',text:'Adjust Vacuum Pump On Speed via slider and engage On Pump at multiple settings'}, {id:'s14',sec:'Plasma Generation',text:'Adjust Plasma Timer and run at 5, 10, and 15 seconds to confirm functionality'}, {id:'s15',sec:'Plasma Generation',text:'Slowly adjust plasma potentiometer while firing until plasma is generated in all wells'}, {id:'s16',sec:'Plasma Generation',text:'Monitor oscilloscope after potentiometer adjustment'}, {id:'s17',sec:'Plasma Generation',text:'Confirm standing square wave output'}, {id:'s18',sec:'Plasma Generation',text:'Monitor wattage after potentiometer adjustment and record wattage'}, {id:'s19',sec:'Endurance Testing',text:'Cycle test the unit 60 times at: 30% On Pump, 70% Off Pump, 10 second Plasma Timer'}, {id:'s20',sec:'Endurance Testing',text:'Trigger thru beam sensor to see if plasma fires'}, {id:'s21',sec:'Final Assembly',text:'Attach cover ground ring lug with 6-32 Keps nut'}, {id:'s22',sec:'Final Assembly',text:'Attach cover with 6-32 x 1/4 flat head black oxide socket screws'}, {id:'s23',sec:'Safety Testing',text:'Grounding Continuity Test'}, {id:'s24',sec:'Safety Testing',text:'Dielectric Voltage Test 1400VAC, 2 sec'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // Tip Charger - Rectifier Bridge Incoming Inspection _push({ id:'CLT_002',category:'Incoming Inspection',name:'Tip Charger - Rectifier Bridge Incoming Inspection', projectId:'proj1',boardId:'B0001',type:'Incoming Inspection',rev:'1.0', equipment:['Multimeter','Visual inspection tools'],headerFields:['docNum','serialNum','inspector','date'], steps:[ {id:'s01',sec:'Visual Inspection',text:'Inspect PCB for solder defects or damage'}, {id:'s02',sec:'Visual Inspection',text:'Verify all components placed per BOM'}, {id:'s03',sec:'Visual Inspection',text:'Check connector alignment and seating'}, {id:'s04',sec:'Electrical Test',text:'Verify input voltage path continuity'}, {id:'s05',sec:'Electrical Test',text:'Verify rectified DC output voltage'}, {id:'s06',sec:'Electrical Test',text:'Check capacitor bank voltage rating'}, {id:'s07',sec:'Electrical Test',text:'Measure leakage current'}, {id:'s08',sec:'Documentation',text:'Record PCB revision and date code'}, {id:'s09',sec:'Documentation',text:'Attach incoming inspection label'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // Plasma StandAlone - Assembly Check _push({ id:'CLT_003',category:'Final Product QC',name:'Plasma StandAlone - Assembly Verification', projectId:'proj3',boardId:'B0007',type:'Assembly Check',rev:'1.0', equipment:['Torque wrench','Multimeter','Caliper'],headerFields:['docNum','serialNum','inspector','date'], steps:[ {id:'s01',sec:'Mechanical Assembly',text:'Verify linear rail assembly installed and aligned'}, {id:'s02',sec:'Mechanical Assembly',text:'Check carriage movement - smooth travel full length'}, {id:'s03',sec:'Mechanical Assembly',text:'Verify all bearings seated and preloaded'}, {id:'s04',sec:'Mechanical Assembly',text:'Check timing belt tension and alignment'}, {id:'s05',sec:'Mechanical Assembly',text:'Verify servo motor mount secure'}, {id:'s06',sec:'Mechanical Assembly',text:'Check limit switch mounting and function'}, {id:'s07',sec:'Plasma Assembly',text:'Verify plasma chassis assembly complete'}, {id:'s08',sec:'Plasma Assembly',text:'Check plasma knife base and cap alignment'}, {id:'s09',sec:'Plasma Assembly',text:'Verify electrode assembly and connections'}, {id:'s10',sec:'Plasma Assembly',text:'Check cooling fan and shroud installed'}, {id:'s11',sec:'Fluid System',text:'Verify all tubing connections - no leaks'}, {id:'s12',sec:'Fluid System',text:'Check quick disconnect couplings'}, {id:'s13',sec:'Fluid System',text:'Pressure test fluid circuit'}, {id:'s14',sec:'Electrical',text:'Verify all wiring harness connections'}, {id:'s15',sec:'Electrical',text:'Check power entry module installed'}, {id:'s16',sec:'Electrical',text:'Grounding continuity test'}, {id:'s17',sec:'Final',text:'Visual inspection - complete assembly'}, {id:'s18',sec:'Final',text:'Weight and dimension check'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // Nova Stove - Final Check _push({ id:'CLT_004',category:'Final Product QC',name:'Nova Stove - Board Final Check', projectId:'proj4',boardId:'B0008',type:'Final Check',rev:'1.0', equipment:['Multimeter','Oscilloscope','USB programming cable'],headerFields:['docNum','serialNum','inspector','date','fwVer'], steps:[ {id:'s01',sec:'Visual Inspection',text:'Inspect PCB for solder bridges or cold joints'}, {id:'s02',sec:'Visual Inspection',text:'Verify all SMD components placed correctly'}, {id:'s03',sec:'Visual Inspection',text:'Check ESP32-S3 module alignment'}, {id:'s04',sec:'Visual Inspection',text:'Verify thermal sensor placement'}, {id:'s05',sec:'Power System',text:'Apply USB power and verify 3.3V rail'}, {id:'s06',sec:'Power System',text:'Check battery charging circuit (BQ25606)'}, {id:'s07',sec:'Power System',text:'Verify battery gauge IC (BQ27441)'}, {id:'s08',sec:'Power System',text:'Test DC-DC converter output (TPS62822)'}, {id:'s09',sec:'Communication',text:'Flash firmware via USB'}, {id:'s10',sec:'Communication',text:'Verify WiFi/BLE connectivity'}, {id:'s11',sec:'Communication',text:'Test I2C bus - temperature sensor response'}, {id:'s12',sec:'Communication',text:'Test microphone input (PDM)'}, {id:'s13',sec:'Peripherals',text:'Verify WS2812 LED control'}, {id:'s14',sec:'Peripherals',text:'Test buzzer output'}, {id:'s15',sec:'Peripherals',text:'Check FPC display connector'}, {id:'s16',sec:'Peripherals',text:'Verify SD card read/write'}, {id:'s17',sec:'Thermal',text:'Run thermal sensor calibration'}, {id:'s18',sec:'Thermal',text:'IR sensor (MLX90640) functional test'}, {id:'s19',sec:'Final',text:'Full system boot test'}, {id:'s20',sec:'Final',text:'Record firmware version and flash date'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // Engineering - MCS Main Board QA Release _push({ id:'CLT_005',category:'Final Product QC',name:'MCS 3.3 DW - QA Release Check', projectId:'proj2',boardId:'B0006',type:'QA Release',rev:'1.0', equipment:['Multimeter','HiPot tester','Oscilloscope'],headerFields:['docNum','serialNum','inspector','date'], steps:[ {id:'s01',sec:'Documentation',text:'Verify BOM matches build revision'}, {id:'s02',sec:'Documentation',text:'Check ECO compliance - all changes implemented'}, {id:'s03',sec:'Documentation',text:'Review test data from functional test'}, {id:'s04',sec:'Electrical Safety',text:'Dielectric withstand test per spec'}, {id:'s05',sec:'Electrical Safety',text:'Ground bond test'}, {id:'s06',sec:'Electrical Safety',text:'Leakage current measurement'}, {id:'s07',sec:'Functional',text:'Power supply output verification'}, {id:'s08',sec:'Functional',text:'Display module communication test'}, {id:'s09',sec:'Functional',text:'Relay control verification'}, {id:'s10',sec:'Functional',text:'Air flow sensor reading validation'}, {id:'s11',sec:'Packaging',text:'Apply QA release label'}, {id:'s12',sec:'Packaging',text:'Include accessories and documentation'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // ── Additional Tip Charger templates ── _push({ id:'CLT_006',category:'In-Process Check',name:'Tip Charger - Assembly Check', projectId:'proj1',boardId:'B0003',type:'Assembly Check',rev:'1.0', equipment:['Torque driver','Caliper','Multimeter'],headerFields:['docNum','serialNum','inspector','date'], steps:[ {id:'s01',sec:'PCB Assembly',text:'Verify all SMD components placed per BOM'}, {id:'s02',sec:'PCB Assembly',text:'Inspect solder paste coverage and reflow quality'}, {id:'s03',sec:'PCB Assembly',text:'Check through-hole component insertion and wave solder'}, {id:'s04',sec:'PCB Assembly',text:'Verify IC orientation and pin 1 alignment'}, {id:'s05',sec:'Mechanical',text:'Verify standoff mounting and torque'}, {id:'s06',sec:'Mechanical',text:'Check enclosure fit and alignment'}, {id:'s07',sec:'Mechanical',text:'Verify heatsink attachment and thermal compound'}, {id:'s08',sec:'Wiring',text:'Verify wire harness routing and strain relief'}, {id:'s09',sec:'Wiring',text:'Check connector crimps and pull test'}, {id:'s10',sec:'Wiring',text:'Verify ground wire connections'}, {id:'s11',sec:'Labels',text:'Apply serial number label'}, {id:'s12',sec:'Labels',text:'Apply warning and compliance labels'} ], createdAt:new Date().toISOString(),createdBy:'System' }); _push({ id:'CLT_007',category:'Service / Rework',name:'Tip Charger - Service / Repair Check', projectId:'proj1',boardId:'',type:'Service / Repair',rev:'1.0', equipment:['Multimeter','Oscilloscope','Replacement parts kit'],headerFields:['docNum','serialNum','inspector','date','notes'], steps:[ {id:'s01',sec:'Intake',text:'Record unit serial number and customer complaint'}, {id:'s02',sec:'Intake',text:'Visual inspection for external damage'}, {id:'s03',sec:'Intake',text:'Photograph unit condition on arrival'}, {id:'s04',sec:'Diagnosis',text:'Power-on test and observe behavior'}, {id:'s05',sec:'Diagnosis',text:'Check fuses - Main, Transformer, Board'}, {id:'s06',sec:'Diagnosis',text:'Measure power supply voltages'}, {id:'s07',sec:'Diagnosis',text:'Test plasma generation'}, {id:'s08',sec:'Diagnosis',text:'Check display and UI responsiveness'}, {id:'s09',sec:'Repair',text:'Document replacement parts used'}, {id:'s10',sec:'Repair',text:'Perform repair and re-test'}, {id:'s11',sec:'Validation',text:'Full functional test post-repair'}, {id:'s12',sec:'Validation',text:'Run 10-cycle endurance test'}, {id:'s13',sec:'Validation',text:'Safety tests - ground continuity, dielectric'}, {id:'s14',sec:'Closeout',text:'Update firmware if applicable'}, {id:'s15',sec:'Closeout',text:'Clean unit and prepare for return'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // ── Additional Nova templates ── _push({ id:'CLT_008',category:'Incoming Inspection',name:'Nova Stove - Incoming Inspection', projectId:'proj4',boardId:'B0008',type:'Incoming Inspection',rev:'1.0', equipment:['Microscope','Multimeter'],headerFields:['docNum','serialNum','inspector','date'], steps:[ {id:'s01',sec:'Visual',text:'Inspect PCB for manufacturing defects'}, {id:'s02',sec:'Visual',text:'Verify component placement matches BOM'}, {id:'s03',sec:'Visual',text:'Check ESP32-S3 module soldering'}, {id:'s04',sec:'Visual',text:'Inspect FPC connector for damage'}, {id:'s05',sec:'Electrical',text:'Verify 3.3V rail under USB power'}, {id:'s06',sec:'Electrical',text:'Check battery connector polarity'}, {id:'s07',sec:'Documentation',text:'Record lot number and date code'}, {id:'s08',sec:'Documentation',text:'Apply incoming inspection label'} ], createdAt:new Date().toISOString(),createdBy:'System' }); _push({ id:'CLT_009',category:'In-Process Check',name:'Nova Stove - Board Bring-up', projectId:'proj4',boardId:'B0008',type:'Assembly Check',rev:'1.0', equipment:['USB cable','Oscilloscope','Logic analyzer'],headerFields:['docNum','serialNum','inspector','date','fwVer'], steps:[ {id:'s01',sec:'Power',text:'Apply USB 5V and verify 3.3V LDO output'}, {id:'s02',sec:'Power',text:'Verify battery charger IC initialization (BQ25606)'}, {id:'s03',sec:'Power',text:'Confirm DC-DC converter startup (TPS62822)'}, {id:'s04',sec:'Firmware',text:'Flash bootloader via USB-JTAG'}, {id:'s05',sec:'Firmware',text:'Flash application firmware'}, {id:'s06',sec:'Firmware',text:'Verify serial console output'}, {id:'s07',sec:'Peripherals',text:'Test I2C bus scan - identify all devices'}, {id:'s08',sec:'Peripherals',text:'Verify WS2812 LED - all colors'}, {id:'s09',sec:'Peripherals',text:'Test buzzer output tone'}, {id:'s10',sec:'Peripherals',text:'Verify PDM microphone data stream'}, {id:'s11',sec:'Communication',text:'WiFi scan test - RSSI check'}, {id:'s12',sec:'Communication',text:'BLE advertisement test'}, {id:'s13',sec:'Sensors',text:'Read temperature sensor (ambient verify)'}, {id:'s14',sec:'Sensors',text:'MLX90640 IR sensor initialization and frame capture'} ], createdAt:new Date().toISOString(),createdBy:'System' }); _push({ id:'CLT_010',category:'In-Process Check',name:'Nova Stove - Calibration', projectId:'proj4',boardId:'B0008',type:'Final Check',rev:'1.0', equipment:['Blackbody source','Thermocouple reference','Calibration jig'],headerFields:['docNum','serialNum','inspector','date','fwVer'], steps:[ {id:'s01',sec:'Temperature Cal',text:'Set blackbody to 100C and record IR reading'}, {id:'s02',sec:'Temperature Cal',text:'Set blackbody to 200C and record IR reading'}, {id:'s03',sec:'Temperature Cal',text:'Set blackbody to 300C and record IR reading'}, {id:'s04',sec:'Temperature Cal',text:'Calculate and apply offset coefficients'}, {id:'s05',sec:'Temperature Cal',text:'Verify ambient temp sensor vs thermocouple'}, {id:'s06',sec:'Battery Cal',text:'Calibrate battery gauge at full charge'}, {id:'s07',sec:'Battery Cal',text:'Calibrate battery gauge at empty'}, {id:'s08',sec:'Audio Cal',text:'Verify microphone sensitivity and noise floor'}, {id:'s09',sec:'Validation',text:'Run full self-test routine'}, {id:'s10',sec:'Validation',text:'Record calibration data to NVS'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // ── Additional Plasma StandAlone templates ── _push({ id:'CLT_011',category:'In-Process Check',name:'Plasma StandAlone - Electrical Validation', projectId:'proj3',boardId:'B0007',type:'Final Check',rev:'1.0', equipment:['Oscilloscope','HiPot tester','Power analyzer'],headerFields:['docNum','serialNum','inspector','date'], steps:[ {id:'s01',sec:'Power System',text:'Verify input power path and EMI filter'}, {id:'s02',sec:'Power System',text:'Measure DC bus voltage under load'}, {id:'s03',sec:'Power System',text:'Verify IGBT gate drive waveforms'}, {id:'s04',sec:'Power System',text:'Check dead-time between high/low side'}, {id:'s05',sec:'Plasma Circuit',text:'Verify resonant frequency matches design'}, {id:'s06',sec:'Plasma Circuit',text:'Measure plasma ignition voltage'}, {id:'s07',sec:'Plasma Circuit',text:'Confirm sustained plasma at rated power'}, {id:'s08',sec:'Plasma Circuit',text:'Record power consumption at operating point'}, {id:'s09',sec:'Control',text:'Verify servo motor response and homing'}, {id:'s10',sec:'Control',text:'Test limit switches - both ends of travel'}, {id:'s11',sec:'Control',text:'Verify E-stop function cuts all power'}, {id:'s12',sec:'Safety',text:'Dielectric withstand test'}, {id:'s13',sec:'Safety',text:'Ground continuity test'}, {id:'s14',sec:'Safety',text:'Thermal shutdown verification'} ], createdAt:new Date().toISOString(),createdBy:'System' }); // ── Additional Engineering templates ── _push({ id:'CLT_012',category:'In-Process Check',name:'Engineering - Prototype Validation', projectId:'proj2',boardId:'',type:'Assembly Check',rev:'1.0', equipment:['Full bench setup','Oscilloscope','Logic analyzer','Thermal camera'],headerFields:['docNum','inspector','date','notes'], steps:[ {id:'s01',sec:'Schematic Review',text:'Verify BOM matches schematic revision'}, {id:'s02',sec:'Schematic Review',text:'Check for errata or known issues'}, {id:'s03',sec:'Power Rails',text:'Measure all voltage rails vs spec'}, {id:'s04',sec:'Power Rails',text:'Check ripple and noise on each rail'}, {id:'s05',sec:'Digital',text:'Verify clock frequencies'}, {id:'s06',sec:'Digital',text:'Test communication buses (SPI, I2C, UART)'}, {id:'s07',sec:'Digital',text:'Verify GPIO pin mapping'}, {id:'s08',sec:'Analog',text:'Test ADC channels with known references'}, {id:'s09',sec:'Analog',text:'Verify DAC output range'}, {id:'s10',sec:'Thermal',text:'Thermal imaging under full load - identify hotspots'}, {id:'s11',sec:'Thermal',text:'Verify thermal shutdown thresholds'}, {id:'s12',sec:'Integration',text:'Full system integration test'}, {id:'s13',sec:'Documentation',text:'Record all deviations from expected behavior'} ], createdAt:new Date().toISOString(),createdBy:'System' }); _push({ id:'CLT_013',category:'In-Process Check',name:'Engineering - ECO Verification', projectId:'proj2',boardId:'',type:'QA Release',rev:'1.0', equipment:['Per ECO requirements'],headerFields:['docNum','inspector','date','notes'], steps:[ {id:'s01',sec:'ECO Review',text:'Verify ECO document is approved and signed'}, {id:'s02',sec:'ECO Review',text:'Confirm affected part numbers identified'}, {id:'s03',sec:'ECO Review',text:'Check BOM updated to new revision'}, {id:'s04',sec:'Implementation',text:'Verify physical change implemented correctly'}, {id:'s05',sec:'Implementation',text:'Check affected drawings updated'}, {id:'s06',sec:'Implementation',text:'Verify firmware updated if applicable'}, {id:'s07',sec:'Testing',text:'Run test plan specified in ECO'}, {id:'s08',sec:'Testing',text:'Compare results to acceptance criteria'}, {id:'s09',sec:'Closeout',text:'Update inventory status for affected units'}, {id:'s10',sec:'Closeout',text:'Archive old revision documents'} ], createdAt:new Date().toISOString(),createdBy:'System' }); } function pgChecklist(el){ if(!D.checklists)D.checklists=[];if(!D.clTemplates)D.clTemplates=[]; seedCLTemplates(); var h='
'; // Stats if(D.checklists.length){ var total=D.checklists.length; var complete=D.checklists.filter(function(cl){var tpl=D.clTemplates.find(function(t){return t.id===cl.templateId});return tpl&&tpl.steps.every(function(s){var r=cl.results[s.id];return r&&(r.status==='pass'||r.status==='na')})}).length; h+='
Checklists
'+total+'
Complete
'+complete+'
In Progress
'+(total-complete)+'
'; } // Templates overview grouped by CATEGORY if(D.clTemplates.length){ h+='
Templates ('+D.clTemplates.length+')
'; var _cats=['Final Product QC','Incoming Inspection','In-Process Check','Service / Rework']; var _catCol={'Final Product QC':'var(--green)','Incoming Inspection':'var(--ac)','In-Process Check':'var(--warn)','Service / Rework':'var(--err)'}; var byCat={};D.clTemplates.filter(function(t){return !t.archived}).forEach(function(t){var k=t.category||'Uncategorized';if(!byCat[k])byCat[k]=[];byCat[k].push(t)}); _cats.concat(Object.keys(byCat).filter(function(k){return _cats.indexOf(k)<0})).forEach(function(catKey){ var list=byCat[catKey];if(!list||!list.length)return; h+='
'+catKey+'
'; h+='
'; list.forEach(function(t){ var bd=getBoard(t.boardId);var pr=getProject(t.projectId); h+='
'; h+='
'+t.name+'
'; h+='
'; if(pr)h+=''+pr.name+''; if(bd)h+=''+bd.name+''; h+=''+t.steps.length+' steps'; h+='
'; }); h+='
'; }); } // Active checklists D.checklists.forEach(function(cl,ci){ var tpl=D.clTemplates.find(function(t){return t.id===cl.templateId}); if(!tpl)return; var wo=cl.woId?(D.wo||[]).find(function(w){return w.id===cl.woId}):null; var pr=getProject(tpl.projectId); var dn=tpl.steps.filter(function(s){var r=cl.results[s.id];return r&&(r.status==='pass'||r.status==='na')}).length; var pct=Math.round(dn/tpl.steps.length*100); var isComplete=dn===tpl.steps.length; var failCount=tpl.steps.filter(function(s){var r=cl.results[s.id];return r&&r.status==='fail'}).length; h+='
'; h+='
'+cl.label+''; if(wo)h+=' · WO: '+wo.num+''; h+=' · '+(pr?pr.name:'')+''; h+='
'; if(failCount)h+=''+failCount+' fail'; h+=''+dn+'/'+tpl.steps.length+''; h+=''; h+='
'; // Header fields if(cl.header){ h+='
'; if(cl.header.serialNum)h+='SN: '+cl.header.serialNum+''; if(cl.header.inspector)h+='Inspector: '+cl.header.inspector+''; if(cl.header.fwVer)h+='FW: '+cl.header.fwVer+''; h+='
'; } h+='
'; // Steps grouped by section var curSec=''; tpl.steps.forEach(function(step,si){ if(step.sec!==curSec){curSec=step.sec;h+='
'+curSec+'
'} var r=cl.results[step.id]||{}; var bg=r.status==='pass'?'rgba(52,199,89,.05)':r.status==='fail'?'rgba(255,59,48,.05)':r.status==='na'?'rgba(0,0,0,.02)':''; h+='
'; h+=''+(si+1)+''; h+=''+step.text+''; h+=''; h+=''; h+=''; h+='
'; }); if(isComplete&&!failCount)h+='
✓ All checks passed
'; if(failCount)h+='
✗ '+failCount+' step'+(failCount>1?'s':'')+' failed — review required
'; // Edit header h+='
'; h+='
'; }); if(!D.checklists.length)h+='
No active checklists. Create one from a template or from a Work Order.
'; el.innerHTML=h; } function setCLResult(ci,stepId,field,value){ if(!guardAction('checklist_entry','Set Checklist Result'))return; var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} if(!D.checklists[ci].results)D.checklists[ci].results={}; if(!D.checklists[ci].results[stepId])D.checklists[ci].results[stepId]={}; D.checklists[ci].results[stepId][field]=value; if(field==='status'){D.checklists[ci].results[stepId].by=U;D.checklists[ci].results[stepId].at=now()} saveAllAsync('Set Checklist Result',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(res){ renderPage(); if(!handleCriticalSaveResult(res,'Set Checklist Result'))return; // No toast for per-field edits — too noisy. Audit only on status changes. if(field==='status')auditLog('CHECKLIST STEP',D.checklists[ci].id,stepId,value); }); } function newCLFromTemplate(){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){}if(!guardAction('checklist_entry','New Checklist'))return; var active=D.clTemplates.filter(function(t){return !t.archived}); if(!active.length){toast('No active templates. Create one first.','warn');return} var opts=active.map(function(t){var pr=getProject(t.projectId);return t.id+'|'+(pr?pr.name+' > ':'')+t.name+' ('+t.type+')'}); var woOpts=[''].concat((D.wo||[]).filter(function(w){return w.status!=='Shipped'&&w.status!=='Cancelled'&&w.status!=='Ready to Stock'}).map(function(w){return w.id+'|'+w.num})); showModal('New Checklist from Template',[ {k:'templateId',l:'Template',t:'select',opts:[''].concat(opts)}, {k:'label',l:'Label / Unit SN'}, {k:'woId',l:'Work Order (optional)',t:'select',opts:woOpts} ],{label:'',templateId:active[0].id},function(r){ if(!r.templateId){toast('Select a template','warn');return} var tpl=D.clTemplates.find(function(t){return t.id===r.templateId}); D.checklists.push({id:'CLI_'+uid(),templateId:r.templateId,woId:r.woId||'',clType:tpl?tpl.type:'',label:r.label||(tpl?tpl.name:'Checklist'),header:{inspector:U,date:now()},results:{},at:now(),by:U}); saveAllAsync('New Checklist',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(__res){if(!handleCriticalSaveResult(__res,'New Checklist'))return;try{auditLog('CREATE CHECKLIST','checklist',r.templateId||'','create checklist');}catch(e){}renderPage();toast('Checklist created');addLog('NEW CHECKLIST',r.label||tpl.name); }); }); } function editCLHeader(ci){ if(!guardAction('checklist_entry','Edit Checklist Header'))return; var cl=D.checklists[ci];if(!cl)return; if(!cl.header)cl.header={}; showModal('Edit Checklist Header',[ {k:'serialNum',l:'Serial Number'},{k:'inspector',l:'Inspector'},{k:'docNum',l:'Document Number'}, {k:'fwVer',l:'Firmware Version'},{k:'fwRev',l:'Firmware Rev'},{k:'fwDate',l:'FW Date Loaded'}, {k:'dspVer',l:'DSP Version'},{k:'dspRev',l:'DSP Rev'},{k:'dspDate',l:'DSP Date Loaded'}, {k:'dispVer',l:'Display Version'},{k:'dispRev',l:'Display Rev'},{k:'dispDate',l:'Display Date Loaded'}, {k:'overallResult',l:'Overall Result',t:'select',opts:['','Pass','Fail','Conditional']}, {k:'notes',l:'Notes',t:'textarea'} ],cl.header,function(r){ var _snap=_deepClone(D),_snapV=_sbVersion,_snapLS=null;try{_snapLS=localStorage.getItem('ag5')}catch(e){} cl.header=r; saveAllAsync('Edit Checklist Header',{critical:true,snapshot:_snap,snapshotVer:_snapV,snapshotStorage:_snapLS}).then(function(res){ renderPage(); if(!handleCriticalSaveResult(res,'Edit Checklist Header'))return; toast('Header updated','ok'); auditLog('EDIT CHECKLIST HEADER','checklist',cl.id,'header updated'); }); }); } function manageCLTemplates(){ var active=D.clTemplates.filter(function(t){return !t.archived}); var archived=D.clTemplates.filter(function(t){return t.archived}); var h=''; document.getElementById('MR').innerHTML=h; document.getElementById('mCancel').addEventListener('click',function(){document.getElementById('MR').innerHTML=''}); document.getElementById('mBG').addEventListener('click',function(e){if(e.target.id==='mBG')document.getElementById('MR').innerHTML=''}); } function editCLTemplate(i){if(!guardAction('checklist_edit','Edit Template'))return; document.getElementById('MR').innerHTML=''; var t=D.clTemplates[i];if(!t)return; var pOpts=[''].concat(D.projects.filter(function(p){return !p.del}).map(function(p){return p.id+'|'+p.name})); var bOpts=[''].concat(D.boards.filter(function(b){return !b.del}).map(function(b){return b.id+'|'+b.name})); // Convert steps to editable text: "Section | Step text" per line var stepsText=t.steps.map(function(s){return s.sec+' | '+s.text}).join('\n'); showModal('Edit Template: '+t.name,[ {k:'name',l:'Template Name'},{k:'projectId',l:'Project',t:'select',opts:pOpts}, {k:'boardId',l:'Board / Product',t:'select',opts:bOpts}, {k:'type',l:'Type',t:'select',opts:['Final Check','Incoming Inspection','Assembly Check','Service / Repair','QA Release']}, {k:'rev',l:'Revision'}, {k:'equipmentText',l:'Equipment (one per line)',t:'textarea'}, {k:'stepsText',l:'Steps (Section | Step text, one per line)',t:'textarea'} ],{name:t.name,projectId:t.projectId,boardId:t.boardId,type:t.type,rev:t.rev||'1.0', equipmentText:(t.equipment||[]).join('\n'),stepsText:stepsText}, function(r){ t.name=r.name;t.projectId=r.projectId||'';t.boardId=r.boardId||'';t.type=r.type;t.rev=r.rev||'1.0'; t.equipment=(r.equipmentText||'').split('\n').filter(function(l){return l.trim()}); t.steps=(r.stepsText||'').split('\n').filter(function(l){return l.trim()}).map(function(l,idx){ var parts=l.split('|');var sec=parts.length>1?parts[0].trim():'General';var text=parts.length>1?parts.slice(1).join('|').trim():l.trim(); return{id:'s'+String(idx+1).padStart(2,'0'),sec:sec,text:text}; }); saveAll();renderPage();toast('Template updated');addLog('EDIT TEMPLATE',t.name); }); } function newCLTemplate(){if(!guardAction('checklist_edit','New Template'))return; document.getElementById('MR').innerHTML=''; var pOpts=[''].concat(D.projects.filter(function(p){return !p.del}).map(function(p){return p.id+'|'+p.name})); var bOpts=[''].concat(D.boards.filter(function(b){return !b.del}).map(function(b){return b.id+'|'+b.name})); showModal('New Checklist Template',[ {k:'name',l:'Template Name'}, {k:'projectId',l:'Project',t:'select',opts:pOpts}, {k:'boardId',l:'Board / Product',t:'select',opts:bOpts}, {k:'type',l:'Type',t:'select',opts:['Final Check','Incoming Inspection','Assembly Check','Service / Repair','QA Release'],def:'Final Check'}, {k:'rev',l:'Revision',def:'1.0'}, {k:'equipmentText',l:'Equipment (one per line)',t:'textarea'}, {k:'stepsText',l:'Steps (Section | Step text, one per line)',t:'textarea'} ],{name:'',type:'Final Check',rev:'1.0'},function(r){ var steps=(r.stepsText||'').split('\n').filter(function(l){return l.trim()}).map(function(l,i){ var parts=l.split('|');var sec=parts.length>1?parts[0].trim():'General';var text=parts.length>1?parts.slice(1).join('|').trim():l.trim(); return{id:'s'+String(i+1).padStart(2,'0'),sec:sec,text:text}; }); var equip=(r.equipmentText||'').split('\n').filter(function(l){return l.trim()}); D.clTemplates.push({id:'CLT_'+uid(),name:r.name,projectId:r.projectId||'',boardId:r.boardId||'',type:r.type,rev:r.rev||'1.0',steps:steps,headerFields:['docNum','serialNum','inspector','date'],equipment:equip,archived:false,createdAt:now(),createdBy:U}); saveAll();renderPage();toast('Template created');addLog('NEW TEMPLATE',r.name); }); } function cloneCLTemplate(i){if(!guardAction('checklist_edit','Clone Template'))return; var t=JSON.parse(JSON.stringify(D.clTemplates[i])); t.id='CLT_'+uid();t.name=t.name+' (Copy)';t.createdAt=now();t.createdBy=U; D.clTemplates.push(t);saveAll();renderPage(); document.getElementById('MR').innerHTML='';toast('Template cloned'); } function delCLTemplate(i){ if(!guardAction('checklist_edit','Delete Template'))return; var t=D.clTemplates[i];if(!t)return; if(t.archived){if(!confirm('Permanently delete archived template?'))return;D.clTemplates.splice(i,1);saveAll();renderPage();document.getElementById('MR').innerHTML='';toast('Permanently deleted','warn');return} if(!confirm('Archive this template? It will become inactive but can be restored.'))return; t.archived=true;t.archivedAt=now();t.archivedBy=U; saveAll();renderPage();document.getElementById('MR').innerHTML='';toast('Template archived','warn');addLog('ARCHIVE TEMPLATE',t.name); } function restoreCLTemplate(i){if(!guardAction('checklist_edit','Restore Template'))return;D.clTemplates[i].archived=false;delete D.clTemplates[i].archivedAt;saveAll();renderPage();document.getElementById('MR').innerHTML='';toast('Template restored')} function delCL(ci){if(!guardAction('admin','Delete Checklist'))return;if(!confirm('Delete this checklist?'))return;D.checklists.splice(ci,1);saveAll();renderPage();toast('Deleted','warn')} // ═══ LOGOUT & PROFILE ═══ function doLogout(){ if(!confirm('Sign out of Augeas Technologies platform?'))return; if(sb&&_sbReady)sb.auth.signOut().catch(function(){}); document.getElementById('appShell').style.display='none'; document.getElementById('LS').style.display=''; var eml=document.getElementById('lEmail');if(eml)eml.value=''; var pw=document.getElementById('lPass');if(pw)pw.value=''; var msg=document.getElementById('loginMsg');if(msg)msg.style.display='none'; _authUserId='';_authEmail='';U='';UR='Viewer'; } function editProfile(){ showModal('Edit Profile',[ {k:'n',l:'Display Name'} ],{n:U},function(res){ U=res.n||U; // Update server-side display name if(sb&&_sbReady&&_authUserId){ sb.from('user_profiles').update({display_name:U,updated_at:new Date().toISOString()}).eq('id',_authUserId).then(function(){}).catch(function(){}); } document.getElementById('userLabel').textContent=U; document.getElementById('userInitial').textContent=U.charAt(0).toUpperCase(); toast('Profile updated','ok');auditLog('EDIT PROFILE','user',_authUserId||'','Display name changed'); }); } // ═══ CSV TEMPLATE DOWNLOADS ═══ function exportBomCSV(bomId){ var bom=D.boms.find(function(b){return b.id===bomId});if(!bom){toast('BOM not found','err');return} var bd=getBoard(bom.boardId); var lines=getLinesForBom(bomId); function esc(v){v=String(v==null?'':v);if(v.indexOf(',')>=0||v.indexOf('"')>=0||v.indexOf('\n')>=0)return '"'+v.replace(/"/g,'""')+'"';return v} var rows=['IFS Part Number,MPN,Manufacturer,Description,Qty,Designator,Package,Supplier,Supplier PN,Unit Price,Line Total,Fitted,Part ID,Notes']; lines.forEach(function(l){ var p=getPart(l.partId)||{}; var lt=(parseFloat(p.pr)||0)*(parseFloat(l.qty)||1); rows.push([p.ifsPN||'',p.mpn||'',p.mfr||'',p.desc||'',l.qty||'1',l.des||'',p.pkg||'',p.sup||'',p.spn||'',p.pr||'0',Math.round(lt),l.fit||'Fitted',l.partId||'',l.notes||''].map(esc).join(',')); }); var csv=rows.join('\n'); var bl=new Blob([csv],{type:'text/csv'});var a=document.createElement('a');a.href=URL.createObjectURL(bl); var safeName=((bd&&bd.name)||'bom').replace(/[^a-zA-Z0-9._-]+/g,'_'); a.download='BOM_'+safeName+'_Rev'+(bom.rev||'1')+'.csv';a.click(); toast('Exported '+lines.length+' lines to CSV','ok'); } function downloadPartsTpl(){ var hdr='IFS Part Number,MPN,Description,Manufacturer,Package,Supplier,Supplier PN,Price,Category,Datasheet\n'; var ex1='IFS-CAP-0001,KGM21NR71H104KT,"CAP CER 0.1UF 50V X7R 0805",KYOCERA AVX,0805,Digi-Key,478-KGM21NR71H104KTCT-ND,5,Capacitor,\n'; var ex2='IFS-RES-0001,RC0805FR-071KL,"Chip Resistor 1K 1% 0805",Yageo,0805,Digi-Key,311-1.00KCRCT-ND,2,Resistor,\n'; var ex3='IFS-DIO-0001,GBPC1508W-E4/51,"BRIDGE RECT 1P 600V 35A",Vishay,,Digi-Key,GBPC1508W-E4/51GI-ND,35,Diode,\n'; var csv=hdr+ex1+ex2+ex3; var b=new Blob([csv],{type:'text/csv'});var a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='Augeas_Parts_Template.csv';a.click();toast('Template downloaded'); } function downloadBOMTpl(){ var hdr='IFS Part Number,MPN,Manufacturer,Description,Qty,Designator,Package,Supplier,Supplier PN,Fitted,Notes\n'; var ex1='IFS-CAP-0001,KGM21NR71H104KT,KYOCERA AVX,"CAP CER 0.1UF 50V X7R 0805",3,"C1, C7, C13",0805,Digi-Key,478-KGM21NR71H104KTCT-ND,Fitted,\n'; var ex2='IFS-RES-0001,RC0805FR-071KL,Yageo,"Chip Resistor 1K 1% 0805",2,"R4, R8",0805,Digi-Key,311-1.00KCRCT-ND,Fitted,Pull-up\n'; var ex3='IFS-DIO-0001,GBPC1508W-E4/51,Vishay,"BRIDGE RECT 1P 600V 35A",1,D1,,Digi-Key,GBPC1508W-E4/51GI-ND,Fitted,\n'; var ex4='IFS-SPK-0001,CMS-402811-28SP,CUI Inc,"SPEAKER 8OHM 2W",1,SP1,,Digi-Key,,DNP,Not fitted in Rev A\n'; var csv=hdr+ex1+ex2+ex3+ex4; var b=new Blob([csv],{type:'text/csv'});var a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='Augeas_BOM_Template.csv';a.click();toast('Template downloaded'); } // ── INIT ── function initApp(){ document.getElementById('userLabel').textContent=U; document.getElementById('roleLabel').textContent=UR; document.getElementById('userInitial').textContent=U.charAt(0).toUpperCase(); // Try cloud first, fall back to localStorage loadFromCloud(function(ok,who,when){ if(ok){ ensureArrays();autoCategorize();renderNav();renderPage(); toast('Synced from cloud'+(who?' ('+who+')':'')); } else { if(!loadAll())initDefaults();ensureArrays();autoCategorize(); renderNav();renderPage(); } updatePresence(); }); // Presence heartbeat every 30s setInterval(function(){updatePresence()},30000); // Pre-cache webhook URL from centralized config loadWebhookConfig(); } function showLoginError(msg){ var el=document.getElementById('loginMsg'); if(el){el.style.display='block';el.style.background='rgba(255,59,48,.1)';el.style.color='var(--err)';el.textContent=msg} } function fetchUserRole(userId,cb){ if(!sb||!_sbReady){cb(null,null,'Supabase not connected');return} sb.from('user_profiles').select('display_name,role').eq('id',userId).single() .then(function(res){ if(res.error){ // PGRST116 = no row. Any error here means no approved profile exists. cb(null,null,'no profile'); return; } if(res.data){cb(res.data.role||null,res.data.display_name||'',null)} else{cb(null,null,'no profile')} }) .catch(function(e){cb(null,null,(e&&e.message)||'lookup failed')}); } function enterPlatform(user,method){ fetchUserRole(user.id,function(role,displayName,errReason){ // BLOCK: authenticated but no approved user_profiles row → do NOT enter the app. if(!role||!PERMS[role]){ // Sign out so the session doesn't auto-login on refresh if(sb&&sb.auth){sb.auth.signOut().catch(function(){})} var msg='Account exists, but no approved user profile was found. Ask Admin to add this user to user_profiles.'; if(errReason==='Supabase not connected'){msg='Supabase not reachable. Please check your connection and try again.'} showLoginError(msg); // Ensure we remain on the login screen var ls=document.getElementById('LS');var app=document.getElementById('appShell'); if(ls)ls.style.display='flex'; if(app)app.style.display='none'; // Audit the blocked login attempt via login_events (anon insert is allowed) try{if(sb&&_sbReady){sb.from('login_events').insert({user_name:user.email||'(unknown)',user_role:'(blocked)',login_method:method||'blocked',user_agent:navigator.userAgent.slice(0,200),screen_info:screen.width+'x'+screen.height}).then(function(){})}}catch(e){} return; } U=displayName||user.email.split('@')[0]; UR=role; _authUserId=user.id; _authEmail=user.email; document.getElementById('LS').style.display='none'; document.getElementById('appShell').style.display='flex'; logLoginEvent(U,UR,method); initApp(); }); } var _authUserId='';var _authEmail=''; // First-run check: if there are no Admin profiles in user_profiles, show a banner // on the login screen pointing the operator to set one up. This runs BEFORE login. // Uses the has_admin_profile() SECURITY DEFINER RPC so the pre-login anon role // can get an honest answer despite RLS hiding user_profiles rows from anon. // If the RPC is not deployed (old installs), falls back to the table read, but // treats "no rows visible" as ambiguous — a missing RPC is a setup issue too. function checkFirstRunAdmin(){ if(!sb||!_sbReady)return; sb.rpc('has_admin_profile').then(function(r){ if(r.error){ // RPC missing (42883) or other error → fall back to direct read. var msg=String(r.error.message||'').toLowerCase(); if(msg.indexOf('function')>=0||msg.indexOf('does not exist')>=0||r.error.code==='42883'){ // Fall back to pre-RPC behavior: direct table count with head:true. sb.from('user_profiles').select('id',{count:'exact',head:true}).eq('role','Admin') .then(function(r2){ if(r2.error)return; // RLS blocked → stay silent if(r2.count===0)showFirstRunBanner(); }).catch(function(){}); return; } // Any other error — stay silent rather than lying about setup state. return; } if(r.data===false)showFirstRunBanner(); }).catch(function(){}); } function showFirstRunBanner(){ var ls=document.getElementById('LS'); if(!ls||document.getElementById('firstRunWarn'))return; var card=ls.querySelector('.login-card');if(!card)return; var warn=document.createElement('div'); warn.id='firstRunWarn'; warn.style.cssText='margin-top:14px;padding:12px;background:rgba(255,149,0,.1);border:1px solid rgba(255,149,0,.3);border-radius:8px;font-size:11px;color:#b45309;line-height:1.5;text-align:left'; warn.innerHTML='
⚠ No Admin profile found
Create the first Admin in Supabase Auth and user_profiles before using this hosted app. See setup guide in the Supabase project dashboard.'; card.appendChild(warn); } // Run the check once on app boot (non-blocking) if(sb&&_sbReady){setTimeout(checkFirstRunAdmin,300)} function doLogin(){ if(!sb||!_sbReady){showLoginError('Supabase not connected. Check your network and reload the page.');return} var email=(document.getElementById('lEmail')||{}).value; var pass=(document.getElementById('lPass')||{}).value; if(!email||!pass){showLoginError('Enter email and password');return} document.getElementById('loginBtn').disabled=true; document.getElementById('loginBtn').textContent='Signing in...'; sb.auth.signInWithPassword({email:email,password:pass}) .then(function(res){ document.getElementById('loginBtn').disabled=false; document.getElementById('loginBtn').textContent='Sign In'; if(res.error){showLoginError(res.error.message);return} if(res.data&&res.data.user){enterPlatform(res.data.user,'manual')} else{showLoginError('Login failed')} }) .catch(function(e){ document.getElementById('loginBtn').disabled=false; document.getElementById('loginBtn').textContent='Sign In'; showLoginError(e.message||'Login failed'); }); } document.getElementById('loginBtn').addEventListener('click',doLogin); document.getElementById('lEmail').addEventListener('keydown',function(e){if(e.key==='Enter')document.getElementById('lPass').focus()}); document.getElementById('lPass').addEventListener('keydown',function(e){if(e.key==='Enter')doLogin()}); // Auto-login from existing Supabase session if(sb&&_sbReady){ sb.auth.getSession().then(function(res){ if(res.data&&res.data.session&&res.data.session.user){ enterPlatform(res.data.session.user,'auto'); } }).catch(function(){}); } // ═══ LOGIN EVENT LOGGING ═══ // Fire-and-forget: if insert fails, login still works. var _cachedWebhookUrl=''; function loadWebhookConfig(){ if(!sb||!_sbReady)return; sb.from('platform_config').select('value').eq('key','webhook_url').single() .then(function(res){if(res.data&&res.data.value)_cachedWebhookUrl=res.data.value}) .catch(function(){}); } function logLoginEvent(name,role,method){ if(!sb||!_sbReady)return; var sessionId=''; try{sessionId=sessionStorage.getItem('ag_sid');if(!sessionId){sessionId='S'+Date.now()+Math.random().toString(36).slice(2,8);sessionStorage.setItem('ag_sid',sessionId)}}catch(e){} var ua='';try{ua=navigator.userAgent.substring(0,200)}catch(e){} var screen='';try{screen=window.screen.width+'x'+window.screen.height+(window.devicePixelRatio?' @'+window.devicePixelRatio+'x':'')}catch(e){} sb.from('login_events').insert({ user_name:name, user_role:role, user_agent:ua, screen_info:screen, session_id:sessionId, login_method:method||'manual' }).then(function(){}).catch(function(){}); // Webhook notification from centralized config // First try cached value; if empty, do a quick fetch (covers first-login-ever case) var doWebhook=function(whUrl){ if(!whUrl)return; fetch(whUrl,{method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({event:'login',user:name,role:role,method:method,time:new Date().toISOString(),screen:screen}) }).catch(function(){}); }; if(_cachedWebhookUrl){doWebhook(_cachedWebhookUrl)} else if(sb&&_sbReady){ sb.from('platform_config').select('value').eq('key','webhook_url').single() .then(function(res){if(res.data&&res.data.value){_cachedWebhookUrl=res.data.value;doWebhook(_cachedWebhookUrl)}}) .catch(function(){}); } }