403Webshell
Server IP : 121.121.20.254  /  Your IP : 216.73.217.51
Web Server : Microsoft-IIS/10.0
System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64
User : IUSR ( 0)
PHP Version : 8.3.28
Disable Function : NONE
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  C:/inetpub/wwwroot/AIWEBSTATION/wp-content/novamira-sandbox/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/inetpub/wwwroot/AIWEBSTATION/wp-content/novamira-sandbox/wms-workspace-pro.js
(()=>{const root=document.getElementById('akwms-pro');if(!root)return;wp.apiFetch.use(wp.apiFetch.createNonceMiddleware(AKWMS_PRO.nonce));const api=(path,opts={})=>wp.apiFetch({path:'/wms/v1/'+path,...opts});const $=s=>root.querySelector(s),$$=s=>[...root.querySelectorAll(s)];let state={master:null,dashboard:null,inventory:[],inventoryMeta:{page:1,pages:1,total:0},orders:[],ordersMeta:{page:1,pages:1,total:0},waves:[],allocated:[],tasks:[],receipts:[],exceptions:{},counts:[],putaway:[],packing:[],returns:[],integrations:null,returnOrders:[]};let invQuery='';let orderQuery='';let invSearchTimer=null;let orderSearchTimer=null;let guideReplayStep=Math.max(0,Math.min(8,parseInt(sessionStorage.getItem('akwmsGuideReplayStep')||'0',10)||0));let guideEnabled=localStorage.getItem('akwmsGuideEnabled')!=='off';const esc=v=>String(v??'').replace(/[&<>'"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[c]));const msg=(t,bad=false)=>{const e=$('#akwms-msg');e.textContent=t||'';e.className='akwms-msg'+(t?(bad?' is-error':' is-ok'):'');if(t)setTimeout(()=>{if(e.textContent===t){e.textContent='';e.className='akwms-msg'}},5000)};const formData=f=>Object.fromEntries(new FormData(f).entries());const statusLabel=s=>({ALLOCATED:'Reserved',STOCK_EXCEPTION:'Not enough stock',SHORT_PICK:'Missing stock',BACKORDER:'Waiting for stock',SHIPPED:'Shipped',COMPLETED:'Completed',AVAILABLE:'Available',PENDING:'Waiting',IN_PROGRESS:'In progress',PLANNED:'Planned',RELEASED:'Ready to work'}[s]||String(s||'').replaceAll('_',' ').toLowerCase().replace(/^./,c=>c.toUpperCase()));const badge=s=>`<span class="akwms-badge ${['SHIPPED','COMPLETED','AVAILABLE','ALLOCATED'].includes(s)?'ok':['STOCK_EXCEPTION','SHORT_PICK','BACKORDER'].includes(s)?'warn':''}">${esc(statusLabel(s))}</span>`;async function loadAll(){try{const boot=await api('workspace/bootstrap');const inventoryPack=boot.inventory||{};const ordersPack=boot.orders||{};const waves=boot.waves||{};state={...state,master:boot.master||null,dashboard:boot.dashboard||null,inventory:inventoryPack.rows||[],inventoryMeta:inventoryPack,orders:ordersPack.rows||[],ordersMeta:ordersPack,waves:waves.waves||[],allocated:waves.allocated_orders||[],tasks:boot.tasks||[],receipts:boot.receipts||[],exceptions:boot.exceptions||{}};invQuery='';orderQuery='';renderAll()}catch(e){msg(e.message||'Could not load WMS data',true)}finally{loading(false)}}
async function loadInventoryPage(page=1){const pack=await api(`workspace/inventory-list?per_page=50&page=${page}&q=${encodeURIComponent(invQuery)}`);state.inventory=pack.rows||[];state.inventoryMeta=pack;renderInventory()}
async function loadOrdersPage(page=1){const pack=await api(`workspace/orders-list?per_page=50&page=${page}&q=${encodeURIComponent(orderQuery)}`);state.orders=pack.rows||[];state.ordersMeta=pack;renderOrders();renderGuide()}
function pager(meta,kind){const page=Number(meta?.page||1),pages=Number(meta?.pages||1),total=Number(meta?.total||0);if(total===0)return '';return `<div class="akwms-pager"><span>Showing page ${page} of ${pages} · ${total} total</span><div>${page>1?`<button class="akwms-btn akwms-secondary" data-${kind}-page="${page-1}">Previous</button>`:''}${page<pages?`<button class="akwms-btn akwms-secondary" data-${kind}-page="${page+1}">Next</button>`:''}</div></div>`}function optionData(type){return state.master?.[type]||[]}function fillOptions(){const maps={warehouses:x=>[x.id,`${x.code} — ${x.name}`],locations:x=>[x.id,`${x.code} · ${x.location_type}`],skus:x=>[x.id,`${x.sku} — ${x.name}`],lpns:x=>[x.id,`${x.code} · ${x.lpn_type}`]};$$('[data-options]').forEach(sel=>{const type=sel.dataset.options;const current=sel.value;const first=sel.querySelector('option[value=""]')?.outerHTML||'';sel.innerHTML=first+optionData(type).map(x=>{const [v,l]=maps[type](x);return `<option value="${esc(v)}">${esc(l)}</option>`}).join('');if(current)sel.value=current});const receiveLoc=$('#akwms-receive-form [name="location_id"]');if(receiveLoc){const currentLoc=(state.master?.locations||[]).find(l=>String(l.id)===String(receiveLoc.value));const preferred=(state.master?.locations||[]).find(l=>l.status==='ACTIVE'&&Number(l.is_allocatable));if((!currentLoc||!Number(currentLoc.is_allocatable))&&preferred)receiveLoc.value=String(preferred.id)}}function renderAll(){fillOptions();renderGuide();renderMetrics();renderExceptions();renderSetup();renderInventory();renderReceipts();renderOrders();renderWaves();renderTasks();renderOverviewTasks();ensureOrderLine()}function syncGuideToggle(){const btn=$('[data-guide-toggle]');if(!btn)return;btn.textContent='Guided Mode: '+(guideEnabled?'On':'Off');btn.setAttribute('aria-pressed',guideEnabled?'true':'false');btn.classList.toggle('is-off',!guideEnabled)}function menuGuideHtml(){const items=[['Overview','Your daily control screen. See stock totals, problems and open warehouse work.','Use every day'],['Setup','Create warehouses, products/SKUs, storage locations and pallets/LPNs.','Use when setting up or adding master data'],['Inventory','See where stock is, how much is on hand/reserved/available, move it, adjust it or start a stock count.','Use for stock checks and corrections'],['Receive','Record stock that physically arrives at the warehouse.','Use when goods arrive'],['Putaway','Move newly received stock from a receiving area into its proper storage bin.','Use when receiving into a receiving area'],['Orders','Create or view customer orders, reserve stock, see details, cancel safely and ship packed orders.','Use for customer orders'],['Waves','Group reserved orders into a work batch and create warehouse picking tasks.','Use to release picking work'],['Tasks','Warehouse worker queue. Start picks, complete them, or report Missing stock.','Use while picking'],['Pack','Verify picked orders before shipping. Shipping is blocked until packing is complete.','Use after picking'],['Count','Review and complete physical stock counts and differences.','Use for stock accuracy'],['Returns','Record stock coming back from shipped customer orders and decide whether to restock, damage or hold it.','Use for customer returns'],['Integrations','Connection status and processing queue for WooCommerce, Shopee, Lazada and future sales channels.','Use after seller API credentials are connected']];return `<details class="akwms-menu-guide"><summary>What does each menu do?</summary><div class="akwms-menu-guide-grid">${items.map(([name,text,when])=>`<button type="button" class="akwms-menu-guide-card" data-guide-tab="${name==='Pack'?'packing':name.toLowerCase()}"><strong>${esc(name)}</strong><span>${esc(text)}</span><small>${esc(when)}</small></button>`).join('')}</div><p class="akwms-field-help">Normal order flow: <strong>Receive → Putaway if needed → Order → Reserve stock → Waves → Tasks → Pack → Ship</strong>. Count and Returns are exception/maintenance workflows. Integrations can be ignored until seller APIs are connected.</p></details>`}function renderGuide(){const guide=$('#akwms-guide');syncGuideToggle();if(!guide)return;if(!guideEnabled){guide.hidden=true;guide.innerHTML='';return}guide.hidden=false;const tutorial=[{title:'Set up your test item',text:'Use the Setup page to prepare the item and storage used in this simulation.',tab:'setup',label:'Setup',do:['Click Setup in the top menu.','Scroll below the three Add forms until you see Current setup. In that box, look under SKUs, Storage locations, and Pallets / LPNs.','If TEST-SKU-001 is not listed under SKUs, use the Add SKU form above: SKU = TEST-SKU-001, Name = Test Product, Stock picking rule = FIFO, then click Create SKU.','If STOR-A01 is not listed under Storage locations, use Add location: Warehouse = WH01 — Main Warehouse, Code = STOR-A01, Type = STORAGE_BIN, then click Create location.','If PLT-TEST-001 is not listed under Pallets / LPNs, use Add pallet / LPN: Code = PLT-TEST-001, Type = PALLET, Warehouse = WH01 — Main Warehouse, Location = STOR-A01, then click Create LPN.','Scroll back to Current setup and confirm all three names are now listed before pressing Next step.'],expect:'Current setup should show at least 1 SKU, 1 LPN and an allocatable STORAGE_BIN location.'},{title:'Receive 100 units into storage',text:'Use the Receive page to record the stock that physically arrived.',tab:'receive',label:'Receive',do:['Click Receive in the top menu.','In the Receive stock form, set Warehouse = WH01 — Main Warehouse.','Open Location and choose STOR-A01 · STORAGE_BIN. Do not choose PACK-01.','Click Product / SKU and choose TEST-SKU-001 — Test Product from the searchable list.','In Pallet / LPN, click the field and choose PLT-TEST-001. You can also type to search or create a new pallet code.','Enter Quantity = 100. Leave Batch and Expiry blank. Keep Status = AVAILABLE.','Click Record receipt.','Then click Inventory in the top menu and confirm the TEST-SKU-001 row shows Location STOR-A01, On hand 100, Reserved 0, Available 100. Only then press Next step.'],expect:'Inventory should show TEST-SKU-001 at STOR-A01 with On hand 100, Reserved 0 and Available 100.'},{title:'Create one customer order',text:'Use the Orders page to simulate one customer buying 5 units.',tab:'orders',label:'Order',do:['Click Orders in the top menu.','In the Orders page, click the Create manual order bar to expand the form.','Leave Channel = MANUAL and External ID blank.','Enter Customer = Test Customer. Keep Priority = NORMAL and Warehouse = Auto.','Under Order lines, click Product / SKU, choose TEST-SKU-001 — Test Product from the searchable list, then enter Quantity = 5.','Click Create order.','Look at the order table below the form. Confirm a Test Customer row appears with Qty 5.0000 and Reserved 0.0000 before pressing Next step.'],expect:'A new order row should appear with Qty 5.0000 and Reserved 0.0000.'},{title:'Reserve 5 units for that order',text:'This prevents another order from using the same 5 units.',tab:'orders',label:'Reserve stock',do:['Stay on the Orders page.','In the order table, find the row where Customer = Test Customer and Qty = 5.0000.','In that same row, click Reserve stock.','Wait for the row to update. Confirm Reserved = 5.0000 and Status = Reserved.','If the row says Not enough stock, click Inventory in the top menu. Find TEST-SKU-001 and check that its Location is STOR-A01 and Available is at least 5. If it is in another location, use Move to send it to STOR-A01, then return to Orders and click Reserve stock again.'],expect:'The order should show Reserved 5.0000 and Status Reserved. Inventory should show On hand 100, Reserved 5 and Available 95.'},{title:'Create a work batch',text:'Use the Waves page to turn the reserved order into warehouse work.',tab:'waves',label:'Work batch',do:['Click Waves in the top menu.','At the top of the Waves page, keep Warehouse = WH01 — Main Warehouse.','Under Reserved orders, find the checkbox row for the Test Customer order and tick that checkbox.','Click Create wave.','Look at the Waves table below. Find the newest row.','If its Action column shows Generate tasks, click Generate tasks. If the row already shows Status = Ready to work and Tasks = 1, do not create another wave or another task.','Press Next step only after the wave row shows 1 order and 1 task.'],expect:'A work batch/wave should exist for 1 order and have 1 warehouse task.'},{title:'Complete the warehouse task',text:'Use the Tasks page to simulate the worker physically picking the 5 units.',tab:'tasks',label:'Pick task',do:['Click Tasks in the top menu.','Find the task card for TEST-SKU-001. It should tell you to pick 5 units from STOR-A01 using pallet PLT-TEST-001.','If the task shows Waiting, click Start. The buttons should change so you can finish the task.','For this tutorial, pretend you are the warehouse worker: walk to STOR-A01, find pallet PLT-TEST-001, count 5 units of TEST-SKU-001, and take those 5 units off the shelf. Then click Complete to tell the WMS the pick was successful.','Only click Missing stock when you are intentionally testing a shortage.','If this test task already shows Missing stock from our earlier run, click Retry task first, then click Complete.','Return to Overview and confirm Open Tasks = 0 before pressing Next step.'],expect:'The task should no longer be open. Open Tasks should become 0.'},{title:'Pack and verify the order',text:'Use the Pack page to verify the picked order before shipping.',tab:'packing',label:'Pack',do:['Click Pack in the top menu.','Find the Test Customer order. It should appear after its warehouse pick task is completed.','Click Verify & pack.','Wait for the order status to become Packed.','Only packed orders are allowed to ship.'],expect:'The Test Customer order should have Status = Packed and be ready to ship.'},{title:'Ship the order and verify stock',text:'Return to Orders and finish the simulation.',tab:'orders',label:'Ship',do:['Click Orders in the top menu.','Find the Test Customer row with Status = Packed.','Click Ship in the Action column for that row.','When the confirmation box asks whether to ship and deduct reserved inventory, confirm it.','Wait for the order row to change to Status = Shipped.','Click Inventory in the top menu. Find TEST-SKU-001 and confirm On hand = 95, Reserved = 0, Available = 95.','That completes the single-order simulation.'],expect:'Order status should be Shipped. Inventory should finish at On hand 95, Reserved 0 and Available 95.'}];const testSku=(state.master?.skus||[]).find(x=>x.sku==='TEST-SKU-001');const testLoc=(state.master?.locations||[]).find(x=>x.code==='STOR-A01'&&x.location_type==='STORAGE_BIN'&&Number(x.is_allocatable));const testLpn=(state.master?.lpns||[]).find(x=>x.code==='PLT-TEST-001'&&(!testLoc||String(x.location_id)===String(testLoc.id)));const testOrder=state.orders.find(o=>o.source_channel==='MANUAL'&&o.customer_name==='Test Customer');const testInventory=state.inventory.find(r=>r.sku==='TEST-SKU-001'&&r.location_code==='STOR-A01');const testWave=state.waves.find(w=>Number(w.test_order_count)>0);const testOpenTask=state.tasks.find(t=>t.sku==='TEST-SKU-001');const completed=[Boolean(testSku&&testLoc&&testLpn),Boolean((testInventory&&Number(testInventory.on_hand)>0)||testOrder),Boolean(testOrder),Boolean(testOrder&&(['ALLOCATED','PICKED','PACKED','SHIPPED'].includes(testOrder.status)||Number(testOrder.allocated_qty)>=5)),Boolean(testWave),Boolean(testWave&&Number(testWave.completed_task_count)>0),Boolean(testOrder&&['PACKED','SHIPPED'].includes(testOrder.status)),Boolean(testOrder&&testOrder.status==='SHIPPED')];const firstIncomplete=completed.findIndex(x=>!x)+1;const detectedStep=firstIncomplete>0?firstIncomplete:8;if(guideReplayStep>0){const item=tutorial[guideReplayStep-1];const doneCount=completed.filter(Boolean).length;guide.innerHTML=`<div class="akwms-guide-head"><div><span class="akwms-guide-label">Tutorial Replay · ${doneCount}/8 completed</span><h2>Step ${guideReplayStep}: ${esc(item.title)}</h2><p>${esc(item.text)}</p></div><div class="akwms-guide-actions"><button class="akwms-btn akwms-secondary" data-guide-tab="${esc(item.tab)}">Open this area</button>${guideReplayStep>1?'<button class="akwms-btn akwms-secondary" data-guide-prev>Previous step</button>':''}${guideReplayStep<8?'<button class="akwms-btn" data-guide-next>Next step</button>':'<button class="akwms-btn" data-guide-live>Back to live guide</button>'}</div></div><div class="akwms-guide-detail"><div><strong>Do this on this page</strong><ol>${item.do.map(x=>`<li>${esc(x)}</li>`).join('')}</ol></div><div class="akwms-guide-expect"><strong>${completed[guideReplayStep-1]?'Completed':'Expected result'}</strong><p>${completed[guideReplayStep-1]?'The WMS has detected that this step is already complete. You can still review it before moving on.':esc(item.expect)}</p></div></div><div class="akwms-guide-steps">${tutorial.map((s,i)=>`<span class="${completed[i]?'is-done':i+1===guideReplayStep?'is-current':''}"><b>${completed[i]?'✓':i+1}</b>${esc(s.label)}</span>`).join('')}</div>${menuGuideHtml()}<button class="akwms-guide-reset-link" data-guide-live>Back to live guide</button>`;return}const skus=state.master?.skus||[],locations=state.master?.locations||[];const allocInv=state.inventory.filter(r=>Number(r.available_qty)>0&&Number(locations.find(l=>String(l.id)===String(r.location_id))?.is_allocatable));const nonAllocInv=state.inventory.filter(r=>Number(r.available_qty)>0&&!Number(locations.find(l=>String(l.id)===String(r.location_id))?.is_allocatable));const openOrder=state.orders.find(o=>['NEW','STOCK_EXCEPTION'].includes(o.status));const reservedOrder=state.orders.find(o=>o.status==='ALLOCATED');const packedOrder=state.orders.find(o=>o.status==='PACKED');const shippedOrder=state.orders.find(o=>o.status==='SHIPPED');const plannedWave=state.waves.find(w=>w.status==='PLANNED');const openTask=state.tasks.find(t=>['PENDING','IN_PROGRESS','SHORT_PICK'].includes(t.status));let current={n:1,title:'Set up your items',text:'Create at least one SKU and a storage location before receiving stock.',tab:'setup',button:'Go to Setup'};if(skus.length&&locations.some(l=>Number(l.is_allocatable)))current={n:2,title:'Receive stock',text:'Add physical stock into the warehouse so orders have something to use.',tab:'receive',button:'Go to Receive'};if(state.inventory.some(r=>Number(r.on_hand)>0))current={n:3,title:'Create an order',text:'Enter what the customer wants to buy.',tab:'orders',button:'Go to Orders'};if(openOrder){if(allocInv.length)current={n:4,title:'Reserve stock for the order',text:'Set aside the required quantity so another order cannot use it.',tab:'orders',button:'Reserve stock'};else if(nonAllocInv.length)current={n:3,title:'Move stock into storage',text:'This stock is in a work area. Move it to a storage location first.',tab:'inventory',button:'Go to Inventory'};else current={n:2,title:'Receive usable stock',text:'There is not enough stock in a storage location for this order.',tab:'receive',button:'Go to Receive'}}if(reservedOrder)current={n:5,title:'Create a work batch',text:'Group the reserved order into warehouse work so staff can start picking it.',tab:'waves',button:'Go to Work Batches'};if(plannedWave)current={n:6,title:'Create warehouse tasks',text:'Turn the work batch into a task for the warehouse operator.',tab:'waves',button:'Generate tasks'};if(openTask)current={n:6,title:'Complete the warehouse task',text:'Go to Tasks, start the job, then complete it after the stock is picked.',tab:'tasks',button:'Go to Tasks'};if(reservedOrder&&!openTask&&state.waves.some(w=>w.status==='RELEASED'))current={n:7,title:'Pack and verify the order',text:'Picking is complete. Verify the order in Pack before shipping.',tab:'packing',button:'Go to Pack'};if(packedOrder)current={n:8,title:'Ship the order',text:'The packed order is ready to ship and deduct its reserved stock.',tab:'orders',button:'Go to Orders'};if(shippedOrder)current={n:8,title:'Simulation complete',text:'The order has shipped. Check Inventory to confirm final stock.',tab:'inventory',button:'View Inventory'};const labels=['Setup','Receive','Order','Reserve stock','Work batch','Pick task','Pack','Ship'];guide.innerHTML=`<div class="akwms-guide-head"><div><span class="akwms-guide-label">Guided Mode</span><h2>Step ${current.n}: ${esc(current.title)}</h2><p>${esc(current.text)}</p></div><div class="akwms-guide-actions"><button class="akwms-btn" data-guide-tab="${esc(current.tab)}">${esc(current.button)}</button><button class="akwms-btn akwms-secondary" data-guide-restart>Restart guide</button><button class="akwms-btn akwms-danger" data-reset-test>Reset test</button></div></div><div class="akwms-guide-steps">${labels.map((s,i)=>`<span class="${i+1<current.n?'is-done':i+1===current.n?'is-current':''}"><b>${i+1}</b>${esc(s)}</span>`).join('')}</div>${menuGuideHtml()}`}function renderMetrics(){const d=state.dashboard||{};$('#akwms-metrics').innerHTML=[['Active SKUs',d.skus],['On hand',d.on_hand],['Reserved',d.reserved],['Available',d.available],['Open orders',d.open_orders],['Open tasks',d.open_tasks]].map(([l,v])=>`<div class="akwms-metric"><span>${esc(l)}</span><strong>${esc(v??0)}</strong></div>`).join('')}function renderExceptions(){const box=$('#akwms-exceptions');if(!box)return;const x=state.exceptions||{};const cards=[['Orders waiting for stock',x.waiting_stock||0,'orders'],['Missing-stock tasks',x.missing_stock||0,'tasks'],['Tasks in progress over 4h',x.stuck_tasks||0,'tasks'],['Stock in non-storage areas',x.stock_wrong_area||0,'inventory']];box.innerHTML=cards.map(([label,value,tab])=>`<button class="akwms-exception-card ${Number(value)>0?'has-problem':''}" data-guide-tab="${tab}"><span>${esc(label)}</span><strong>${esc(value)}</strong><small>${Number(value)>0?'Needs attention':'Clear'}</small></button>`).join('')}
function renderSetup(){const m=state.master||{};const storage=(m.locations||[]).filter(l=>l.location_type==='STORAGE_BIN');const skuTotal=Number(m.counts?.skus??m.skus?.length??0),lpnTotal=Number(m.counts?.lpns??m.lpns?.length??0);const skuShown=(m.skus||[]).slice(0,20),lpnShown=(m.lpns||[]).slice(0,20);$('#akwms-setup-summary').innerHTML=`<div class="akwms-setup-counts">${[['Warehouses',m.warehouses?.length||0],['Locations',m.locations?.length||0],['SKUs',skuTotal],['LPNs',lpnTotal]].map(([l,v])=>`<span>${esc(l)}: ${esc(v)}</span>`).join('')}</div><div class="akwms-setup-details"><div><strong>Warehouses</strong><p>${(m.warehouses||[]).length?(m.warehouses||[]).map(x=>esc(x.code)+' — '+esc(x.name)).join('<br>'):'None yet'}</p></div><div><strong>SKUs</strong><p>${skuShown.length?skuShown.map(x=>esc(x.sku)+' — '+esc(x.name)).join('<br>'):'None yet'}${skuTotal>skuShown.length?`<br><small>Showing ${skuShown.length} recent items of ${skuTotal}. Use SKU search in Receive or Orders for the full catalog.</small>`:''}</p></div><div><strong>Storage locations</strong><p>${storage.length?storage.map(x=>esc(x.code)+' — '+esc(x.location_type)).join('<br>'):'None yet'}</p></div><div><strong>Pallets / LPNs</strong><p>${lpnShown.length?lpnShown.map(x=>esc(x.code)+(x.location_code?' — '+esc(x.location_code):' — No location')).join('<br>'):'None yet'}${lpnTotal>lpnShown.length?`<br><small>Showing ${lpnShown.length} recent pallets/LPNs of ${lpnTotal}. Use pallet search in Receive for the full list.</small>`:''}</p></div></div>`}function invRows(rows){if(!rows.length)return '<div class="akwms-empty">No inventory found. Create a SKU and receive stock to begin.</div>';return `<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>SKU</th><th>LPN</th><th>Location</th><th>Batch / Expiry</th><th>Status</th><th>On hand</th><th>Reserved</th><th>Available</th><th>Action</th></tr></thead><tbody>${rows.map(r=>`<tr><td><strong>${esc(r.sku)}</strong><br><small>${esc(r.sku_name)}</small></td><td>${esc(r.lpn_code||'—')}</td><td>${esc(r.location_code)}<br><small>${esc(r.location_type||'')}</small></td><td>${esc(r.batch_no||'—')}<br><small>${esc(r.expiry_date||'')}</small></td><td>${badge(r.inventory_status)}</td><td>${esc(r.on_hand)}</td><td>${esc(r.reserved)}</td><td><strong>${esc(r.available_qty)}</strong></td><td><div class="akwms-task-actions"><button class="akwms-btn akwms-secondary" data-inventory-move="${r.id}">Move</button><button class="akwms-btn akwms-secondary" data-inventory-adjust="${r.id}">Adjust stock</button><button class="akwms-btn akwms-secondary" data-inventory-count="${r.id}">Count stock</button></div></td></tr>`).join('')}</tbody></table></div>`}function renderInventory(){const box=$('#akwms-inventory');box.innerHTML=invRows(state.inventory)+pager(state.inventoryMeta,'inventory')}function renderReceipts(){const box=$('#akwms-receipts');if(!box)return;const rows=state.receipts||[];box.innerHTML=!rows.length?'<div class="akwms-empty">No receipts recorded yet.</div>':`<div class="akwms-table-wrap"><table class="akwms-table akwms-receipts-table"><thead><tr><th>When</th><th>SKU</th><th>Qty</th><th>Warehouse / Location</th><th>LPN / Pallet</th><th>Batch / Expiry</th><th>Status</th><th>Recorded by</th></tr></thead><tbody>${rows.map(r=>`<tr><td>${esc(r.created_at)}</td><td><strong>${esc(r.sku)}</strong><br><small>${esc(r.sku_name||'')}</small></td><td><strong>${esc(r.quantity)}</strong></td><td>${esc(r.warehouse_code||'—')}<br><small>${esc(r.location_code||'—')}</small></td><td>${esc(r.lpn_code||'—')}</td><td>${esc(r.batch_no||'—')}<br><small>${esc(r.expiry_date||'')}</small></td><td>${badge(r.inventory_status||'AVAILABLE')}</td><td>${esc(r.user_name||'—')}</td></tr>`).join('')}</tbody></table></div>`}function renderOrders(){const rows=state.orders;const box=$('#akwms-orders');box.innerHTML=(!rows.length?'<div class="akwms-empty">No orders found. Create a manual order above or change your search.</div>':`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>Order</th><th>Channel</th><th>Customer</th><th>Qty</th><th>Reserved</th><th>Status</th><th>Action</th></tr></thead><tbody>${rows.map(r=>`<tr><td><strong>${esc(r.order_no)}</strong><br><small>${esc(r.external_order_id||'')}</small></td><td>${esc(r.source_channel)}</td><td>${esc(r.customer_name||'—')}</td><td>${esc(r.ordered_qty)}</td><td>${esc(r.allocated_qty)}</td><td>${badge(r.status)}</td><td><div class="akwms-task-actions"><button class="akwms-btn akwms-secondary" data-order-details="${r.id}">Details</button>${['NEW','STOCK_EXCEPTION'].includes(r.status)?`<button class="akwms-btn akwms-secondary" data-order-allocate="${r.id}">Reserve stock</button>`:''}${r.status==='PACKED'?`<button class="akwms-btn" data-order-ship="${r.id}">Ship</button>`:''}${!['SHIPPED','CANCELLED'].includes(r.status)?`<button class="akwms-btn akwms-secondary" data-order-cancel="${r.id}">Cancel order</button>`:''}</div></td></tr>`).join('')}</tbody></table></div>`)+pager(state.ordersMeta,'orders')}function renderWaves(){const list=$('#akwms-wave-orders');list.innerHTML=state.allocated.length?state.allocated.map(o=>`<label class="akwms-check"><input type="checkbox" name="order_ids" value="${o.id}"><span><strong>${esc(o.order_no)}</strong> · ${esc(o.source_channel)} · ${esc(o.customer_name||'No customer')}</span></label>`).join(''):'<div class="akwms-empty">No reserved orders waiting for a work batch.</div>';$('#akwms-waves').innerHTML=!state.waves.length?'<div class="akwms-empty">No waves yet.</div>':`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>Wave</th><th>Warehouse</th><th>Orders</th><th>Tasks</th><th>Status</th><th>Action</th></tr></thead><tbody>${state.waves.map(w=>`<tr><td><strong>${esc(w.wave_no)}</strong></td><td>${esc(w.warehouse_code)}</td><td>${esc(w.order_count)}</td><td>${esc(w.task_count)}</td><td>${badge(w.status)}</td><td>${w.status==='PLANNED'?`<button class="akwms-btn" data-wave-generate="${w.id}">Generate tasks</button>`:'—'}</td></tr>`).join('')}</tbody></table></div>`}async function loadOpsTab(tab){
 if(tab==='count'){state.counts=await api('workspace/counts');renderCounts();return}
 if(tab==='putaway'){state.putaway=await api('workspace/putaway');renderPutaway();return}
 if(tab==='packing'){state.packing=await api('workspace/packing');renderPacking();return}
 if(tab==='returns'){const [rows,orders]=await Promise.all([api('workspace/returns'),api('workspace/orders-list?status=SHIPPED&per_page=50')]);state.returns=rows||[];state.returnOrders=orders.rows||[];renderReturns();fillReturnOrders();return}
 if(tab==='integrations'){state.integrations=await api('workspace/integrations');renderIntegrations();return}
}
function renderCounts(){const box=$('#akwms-counts');if(!box)return;const rows=state.counts||[];box.innerHTML=!rows.length?'<div class="akwms-empty">No stock counts yet. Go to Inventory and click Count stock on a row.</div>':`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>SKU</th><th>Location</th><th>System qty</th><th>Counted qty</th><th>Difference</th><th>Reason</th><th>Status</th><th>Action</th></tr></thead><tbody>${rows.map(r=>`<tr><td><strong>${esc(r.sku)}</strong><br><small>${esc(r.sku_name||'')}</small></td><td>${esc(r.location_code)}<br><small>${esc(r.lpn_code||'No pallet')}</small></td><td>${esc(r.system_qty)}</td><td>${r.counted_qty===null?'—':esc(r.counted_qty)}</td><td>${r.variance_qty===null?'—':esc(r.variance_qty)}</td><td>${esc(statusLabel(r.reason))}</td><td>${badge(r.status)}</td><td>${r.status==='OPEN'?`<button class="akwms-btn" data-count-complete="${r.id}">Enter count</button>`:'—'}</td></tr>`).join('')}</tbody></table></div>`}
function renderPutaway(){const box=$('#akwms-putaway');if(!box)return;const rows=state.putaway||[];box.innerHTML=!rows.length?'<div class="akwms-empty">Nothing is waiting in a receiving area.</div>':`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>SKU</th><th>Receiving location</th><th>Pallet / LPN</th><th>Qty</th><th>Storage destination</th><th>Action</th></tr></thead><tbody>${rows.map(r=>{const locs=(state.master?.locations||[]).filter(l=>String(l.warehouse_id)===String(r.warehouse_id)&&l.location_type==='STORAGE_BIN'&&l.status==='ACTIVE');return `<tr><td><strong>${esc(r.sku)}</strong><br><small>${esc(r.sku_name||'')}</small></td><td>${esc(r.location_code)}</td><td>${esc(r.lpn_code||'—')}</td><td><input class="akwms-inline-qty" data-putaway-qty="${r.id}" type="number" min="0.0001" max="${esc(r.available_qty)}" step="0.0001" value="${esc(r.available_qty)}"></td><td><select data-putaway-dest="${r.id}">${locs.map(l=>`<option value="${esc(l.id)}">${esc(l.code)}</option>`).join('')}</select></td><td><button class="akwms-btn" data-putaway="${r.id}" ${locs.length?'':'disabled'}>Put away</button></td></tr>`}).join('')}</tbody></table></div>`}
function renderPacking(){const box=$('#akwms-packing');if(!box)return;const rows=state.packing||[];box.innerHTML=!rows.length?'<div class="akwms-empty">No picked orders are waiting for packing.</div>':`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>Order</th><th>Customer</th><th>Qty</th><th>Pick tasks</th><th>Status</th><th>Action</th></tr></thead><tbody>${rows.map(r=>`<tr><td><strong>${esc(r.order_no)}</strong><br><small>${esc(r.source_channel)}</small></td><td>${esc(r.customer_name||'—')}</td><td>${esc(r.ordered_qty)}</td><td>${esc(r.task_count)} completed</td><td>${badge(r.status)}</td><td><div class="akwms-task-actions"><button class="akwms-btn akwms-secondary" data-order-details="${r.id}">Details</button>${r.status==='PACKED'?'<span class="akwms-field-help">Ready to ship</span>':`<button class="akwms-btn" data-pack-order="${r.id}">Verify & pack</button>`}</div></td></tr>`).join('')}</tbody></table></div>`}
function fillReturnOrders(){const sel=$('#akwms-return-order');if(!sel)return;const current=sel.value;sel.innerHTML='<option value="">Choose shipped order</option>'+state.returnOrders.map(o=>`<option value="${esc(o.id)}">${esc(o.order_no)} — ${esc(o.customer_name||o.source_channel)}</option>`).join('');if(current)sel.value=current}
function renderReturns(){const box=$('#akwms-returns');if(!box)return;const rows=state.returns||[];box.innerHTML=!rows.length?'<div class="akwms-empty">No customer returns recorded yet.</div>':`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>Return</th><th>Order</th><th>SKU</th><th>Qty</th><th>Result</th><th>Location</th><th>Reason</th><th>When</th></tr></thead><tbody>${rows.map(r=>`<tr><td><strong>${esc(r.return_no)}</strong></td><td>${esc(r.order_no)}</td><td><strong>${esc(r.sku)}</strong><br><small>${esc(r.sku_name||'')}</small></td><td>${esc(r.quantity)}</td><td>${esc(statusLabel(r.disposition))}</td><td>${esc(r.location_code)}</td><td>${esc(statusLabel(r.reason))}</td><td>${esc(r.created_at)}</td></tr>`).join('')}</tbody></table></div>`}
function renderIntegrations(){const box=$('#akwms-integrations');if(!box)return;const d=state.integrations||{};box.innerHTML=`<div class="akwms-panel"><h3>Integration framework</h3><p>${esc(d.message||'Integration framework ready.')}</p><div class="akwms-setup-counts"><span>Pending queue: ${esc(d.queue?.pending||0)}</span><span>Failed queue: ${esc(d.queue?.failed||0)}</span></div></div><div class="akwms-integration-grid">${(d.channels||[]).map(x=>`<div class="akwms-panel"><strong>${esc(x.code)}</strong><p>${esc(x.status)}</p><small class="akwms-field-help">Connect here later after seller/API credentials are supplied.</small></div>`).join('')}</div>`}
function countDialog(row){return new Promise(resolve=>{const d=document.createElement('dialog');d.className='akwms-move-dialog';d.innerHTML=`<form method="dialog" class="akwms-panel akwms-form"><h3>Enter physical stock count</h3><p><strong>${esc(row.sku)}</strong> at <strong>${esc(row.location_code)}</strong></p><div class="akwms-adjust-summary"><span>WMS quantity <strong>${esc(row.system_qty)}</strong></span><span>Reserved <strong>${esc(row.reserved||0)}</strong></span></div><label>Actual quantity counted<input name="counted_qty" type="number" min="0" step="0.0001" required autofocus></label><label>Reason<select name="reason"><option value="COUNT_VARIANCE">Routine count</option><option value="DAMAGE_FOUND">Damaged stock found</option><option value="STOCK_FOUND">Extra stock found</option><option value="WRONG_LOCATION">Stock in wrong location</option><option value="DATA_CORRECTION">Data correction</option></select></label><div class="akwms-actions"><button value="cancel" class="akwms-btn akwms-secondary">Cancel</button><button value="save" class="akwms-btn">Save count</button></div></form>`;document.body.appendChild(d);d.addEventListener('close',()=>{const result=d.returnValue==='save'?{counted_qty:+d.querySelector('[name="counted_qty"]').value,reason:d.querySelector('[name="reason"]').value}:null;d.remove();resolve(result)},{once:true});d.showModal()})}function taskCard(t){return `<div class="akwms-task"><div><strong>${esc(t.task_no)}</strong><small>${esc(t.task_type)} · ${esc(t.sku||'')}</small><small class="akwms-pick-instruction">Pick ${esc(t.requested_qty||'—')} units from ${esc(t.from_code||'—')}${t.lpn_code?' / pallet '+esc(t.lpn_code):''}</small></div><div><strong>${esc(t.from_code||'—')}</strong><small>Location · LPN ${esc(t.lpn_code||'—')}</small></div><div><strong>${esc(t.requested_qty||'—')}</strong><small>Quantity · ${esc(statusLabel(t.status))}</small></div><div class="akwms-task-actions">${t.status==='PENDING'?`<button class="akwms-btn akwms-secondary" data-task-status="${t.id}" data-status="IN_PROGRESS">Start</button>`:''}${t.status==='IN_PROGRESS'?`<button class="akwms-btn" data-task-status="${t.id}" data-status="COMPLETED">Complete</button><button class="akwms-btn akwms-secondary" data-task-status="${t.id}" data-status="SHORT_PICK">Missing stock</button>`:''}${t.status==='SHORT_PICK'?`<button class="akwms-btn" data-resolve-short="${t.id}">Resolve missing stock</button><button class="akwms-btn akwms-secondary" data-task-status="${t.id}" data-status="IN_PROGRESS">Retry task</button><button class="akwms-btn akwms-secondary" data-task-status="${t.id}" data-status="CANCELLED">Cancel task</button>`:''}</div></div>`}function renderTasks(){ $('#akwms-tasks').innerHTML=state.tasks.length?state.tasks.map(taskCard).join(''):'<div class="akwms-empty">No open warehouse tasks.</div>'}function renderOverviewTasks(){const rows=state.tasks.slice(0,5);$('#akwms-overview-tasks').innerHTML=rows.length?rows.map(taskCard).join(''):'<div class="akwms-empty">Operational queue is clear.</div>'}function updateOrderLineState(){const lines=$$('#akwms-order-lines .akwms-order-line');lines.forEach(line=>{const del=line.querySelector('[data-remove-line]');if(del){del.hidden=lines.length===1;del.disabled=lines.length===1}});const count=$('#akwms-order-line-count');if(count)count.textContent=lines.length===1?'1 order line':`${lines.length} order lines`}
function ensureOrderLine(){if(!$('#akwms-order-lines .akwms-order-line'))addOrderLine();else updateOrderLineState()}
function addOrderLine(){const box=$('#akwms-order-lines');const d=document.createElement('div');d.className='akwms-order-line';d.innerHTML=`<div class="akwms-product-picker"><input type="search" class="line-sku-search" data-catalog-lookup="sku" autocomplete="off" placeholder="Search SKU, product or barcode" aria-label="Product or SKU"><input type="hidden" class="line-sku" value=""><div class="akwms-sku-results" hidden></div></div><input class="line-qty" aria-label="Quantity" type="number" min="0.0001" step="0.0001" value="1" required><button type="button" class="akwms-icon-btn akwms-icon-danger" data-remove-line aria-label="Delete order line" title="Delete line"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-2 6h10l-.7 11H7.7L7 9Zm3 2v7h2v-7h-2Zm4 0v7h2v-7h-2Z"/></svg></button>`;box.appendChild(d);updateOrderLineState();d.querySelector('.line-sku-search')?.focus()}function receiveComboRows(type,q=''){
 const rows=type==='sku'?(state.master?.skus||[]):(state.master?.lpns||[]);
 const term=String(q||'').trim().toLowerCase();
 if(!term)return rows.slice(0,30);
 return rows.filter(x=>type==='sku'?[x.sku,x.name,x.barcode].some(v=>String(v||'').toLowerCase().includes(term)):[x.code,x.lpn_type,x.location_code].some(v=>String(v||'').toLowerCase().includes(term))).slice(0,30)
}
function renderReceiveCombo(combo,rows,type,q=''){
 const box=combo.querySelector('.akwms-combo-results');if(!box)return;
 const term=String(q||'').trim();
 const items=rows.map(x=>type==='sku'?`<button type="button" data-receive-pick="sku" data-id="${esc(x.id)}" data-label="${esc(x.sku)} — ${esc(x.name)}"><strong>${esc(x.sku)}</strong><span>${esc(x.name||'')}</span>${x.barcode?`<small>${esc(x.barcode)}</small>`:''}</button>`:`<button type="button" data-receive-pick="lpn" data-id="${esc(x.id)}" data-label="${esc(x.code)}"><strong>${esc(x.code)}</strong><span>${esc(x.lpn_type||'PALLET')}${x.location_code?' · '+esc(x.location_code):''}</span></button>`).join('');
 const create=term?`<button type="button" class="akwms-combo-create" data-receive-create="${type}" data-value="${esc(term)}">+ Create new ${type==='sku'?'SKU':'pallet / LPN'} “${esc(term)}”</button>`:'';
 const none=type==='lpn'?'<button type="button" class="akwms-combo-none" data-receive-none>No pallet / LPN</button>':'';
 box.innerHTML=items+create+none+(items||create||none?'':'<div class="akwms-sku-no-result">No matches.</div>');box.hidden=false
}
async function loadReceiveCombo(combo,forceServer=false){
 const type=combo.dataset.receiveCombo,input=combo.querySelector('.akwms-combo-input'),q=input.value.trim();
 let rows=receiveComboRows(type,q);renderReceiveCombo(combo,rows,type,q);
 if(!forceServer)return;
 try{const fresh=await api(`workspace/catalog-search?type=${encodeURIComponent(type)}&q=${encodeURIComponent(q)}`);renderReceiveCombo(combo,fresh||[],type,q)}catch(err){msg(err.message||'Could not search catalog',true)}
}
function newSkuDialog(code){return new Promise(resolve=>{
 const d=document.createElement('dialog');d.className='akwms-move-dialog';
 d.innerHTML=`<form class="akwms-panel akwms-form"><h3>Create new SKU</h3><label>SKU<input name="sku" value="${esc(code.toUpperCase())}" required></label><label>Product name<input name="name" placeholder="Product name" required></label><label>Barcode<input name="barcode" placeholder="Optional"></label><label>Stock picking rule<select name="allocation_strategy"><option>FIFO</option><option>FEFO</option><option>MANUAL</option></select></label><div class="akwms-actions"><button type="button" class="akwms-btn akwms-secondary" data-dialog-cancel>Cancel</button><button type="submit" class="akwms-btn">Create SKU</button></div></form>`;
 document.body.appendChild(d);let settled=false;
 const finish=out=>{if(settled)return;settled=true;if(d.open)d.close();d.remove();resolve(out)};
 d.querySelector('[data-dialog-cancel]').addEventListener('click',()=>finish(null));
 d.querySelector('form').addEventListener('submit',e=>{e.preventDefault();const f=e.currentTarget;if(!f.reportValidity())return;finish({sku:f.sku.value.trim(),name:f.name.value.trim(),barcode:f.barcode.value.trim(),allocation_strategy:f.allocation_strategy.value})});
 d.addEventListener('cancel',e=>{e.preventDefault();finish(null)});
 d.addEventListener('click',e=>{if(e.target!==d)return;const r=d.getBoundingClientRect();const inside=e.clientX>=r.left&&e.clientX<=r.right&&e.clientY>=r.top&&e.clientY<=r.bottom;if(!inside)finish(null)});
 d.showModal();setTimeout(()=>d.querySelector('[name="name"]')?.focus(),0)
})}function moveDialog(row){return new Promise(resolve=>{const locs=(state.master?.locations||[]).filter(l=>String(l.warehouse_id)===String(row.warehouse_id)&&String(l.id)!==String(row.location_id)&&l.status==='ACTIVE');if(!locs.length){msg('No other active destination locations are available',true);resolve(null);return}const d=document.createElement('dialog');d.className='akwms-move-dialog';d.innerHTML=`<form method="dialog" class="akwms-panel akwms-form"><h3>Move inventory</h3><p><strong>${esc(row.sku)}</strong> from <strong>${esc(row.location_code)}</strong></p><label>Destination<select name="to_location_id" required>${locs.map(l=>`<option value="${esc(l.id)}">${esc(l.code)} · ${esc(l.location_type)}${Number(l.is_allocatable)?' · Allocatable':''}</option>`).join('')}</select></label><label>Quantity<input name="quantity" type="number" min="0.0001" max="${esc(row.available_qty)}" step="0.0001" value="${esc(row.available_qty)}" required></label><div class="akwms-actions"><button value="cancel" class="akwms-btn akwms-secondary">Cancel</button><button value="move" class="akwms-btn">Move stock</button></div></form>`;document.body.appendChild(d);d.addEventListener('close',()=>{const result=d.returnValue==='move'?{to_location_id:+d.querySelector('[name="to_location_id"]').value,quantity:+d.querySelector('[name="quantity"]').value}:null;d.remove();resolve(result)},{once:true});d.showModal()})}function adjustDialog(row){return new Promise(resolve=>{const d=document.createElement('dialog');d.className='akwms-move-dialog';d.innerHTML=`<form method="dialog" class="akwms-panel akwms-form"><h3>Adjust stock</h3><p><strong>${esc(row.sku)}</strong> at <strong>${esc(row.location_code)}</strong>${row.lpn_code?` · ${esc(row.lpn_code)}`:''}</p><div class="akwms-adjust-summary"><span>System quantity <strong>${esc(row.on_hand)}</strong></span><span>Reserved <strong>${esc(row.reserved)}</strong></span></div><label>Actual quantity counted<input name="new_qty" type="number" min="${esc(row.reserved)}" step="0.0001" value="${esc(row.on_hand)}" required></label><small class="akwms-field-help">You cannot set this below ${esc(row.reserved)} because that quantity is already reserved for active orders.</small><label>Reason<select name="reason"><option value="COUNT_VARIANCE">Count is different</option><option value="DAMAGED_STOCK">Damaged stock</option><option value="FOUND_STOCK">Found extra stock</option><option value="DATA_CORRECTION">Data correction</option></select></label><div class="akwms-actions"><button value="cancel" class="akwms-btn akwms-secondary">Cancel</button><button value="save" class="akwms-btn">Save adjustment</button></div></form>`;document.body.appendChild(d);d.addEventListener('close',()=>{const result=d.returnValue==='save'?{new_qty:+d.querySelector('[name="new_qty"]').value,reason:d.querySelector('[name="reason"]').value}:null;d.remove();resolve(result)},{once:true});d.showModal()})}
function resolveShortDialog(task){return new Promise(resolve=>{const row=state.inventory.find(r=>String(r.sku_id)===String(task.sku_id)&&String(r.location_id)===String(task.from_location_id)&&(!task.lpn_id||String(r.lpn_id)===String(task.lpn_id)));const d=document.createElement('dialog');d.className='akwms-move-dialog';const sys=row?Number(row.on_hand):null;const res=row?Number(row.reserved):null;d.innerHTML=`<form method="dialog" class="akwms-panel akwms-form akwms-resolve-form"><h3>Resolve missing stock</h3><p>You were asked to pick <strong>${esc(task.requested_qty)}</strong> × <strong>${esc(task.sku)}</strong> from <strong>${esc(task.from_code||'the storage location')}</strong>${task.lpn_code?` / pallet <strong>${esc(task.lpn_code)}</strong>`:''}.</p>${row?`<div class="akwms-adjust-summary"><span>System says on hand <strong>${esc(row.on_hand)}</strong></span><span>Reserved <strong>${esc(row.reserved)}</strong></span><span>Available <strong>${esc(row.available_qty)}</strong></span></div>`:'<p class="akwms-field-help">The matching inventory row could not be found automatically.</p>'}<div class="akwms-resolve-options"><button value="adjust" class="akwms-btn akwms-secondary" ${row?'':'disabled'}>Count is wrong → Adjust stock</button><button value="receive" class="akwms-btn akwms-secondary">Need more stock → Receive</button><button value="inventory" class="akwms-btn akwms-secondary">Stock is elsewhere → Inventory</button><button value="retry" class="akwms-btn">Problem fixed → Retry task</button></div><small class="akwms-field-help">If the actual stock is below the reserved amount${res!==null?` (${esc(row.reserved)})`:''}, do not force the count lower. Receive or move enough stock first, then retry the task.</small><button value="cancel" class="akwms-guide-reset-link">Close</button></form>`;document.body.appendChild(d);d.addEventListener('close',()=>{const result={action:d.returnValue,row};d.remove();resolve(result)},{once:true});d.showModal()})}async function showOrderDetails(id){loading(true,'Loading order details...');try{const d=await api(`workspace/orders/${id}/details`);const o=d.order||{};const history=[...(d.audit||[]).map(x=>({when:x.created_at,title:statusLabel(x.event_type),text:x.message||x.event_type,user:x.user_name||''})),...(d.transactions||[]).map(x=>({when:x.created_at,title:statusLabel(x.transaction_type),text:`${x.sku||'Stock'} ${Number(x.quantity)>0?'+':''}${x.quantity} · ${x.reason||''}`,user:''}))].sort((a,b)=>String(b.when).localeCompare(String(a.when))).slice(0,100);const dialog=document.createElement('dialog');dialog.className='akwms-order-dialog';dialog.innerHTML=`<div class="akwms-panel akwms-order-detail"><div class="akwms-section-head"><div><span class="akwms-guide-label">Order details</span><h2>${esc(o.order_no||'Order')}</h2><p>${esc(o.source_channel||'')} ${o.external_order_id?'· External ID '+esc(o.external_order_id):''} · ${esc(o.customer_name||'No customer')}</p></div><div>${badge(o.status)}</div></div><div class="akwms-detail-grid"><div><strong>Created</strong><span>${esc(o.created_at||'—')}</span></div><div><strong>Warehouse</strong><span>${esc(o.warehouse_id||'Auto')}</span></div><div><strong>Priority</strong><span>${esc(o.priority||'Normal')}</span></div><div><strong>Last updated</strong><span>${esc(o.updated_at||'—')}</span></div></div><h3>Items</h3>${(d.items||[]).length?`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>SKU</th><th>Ordered</th><th>Reserved</th><th>Picked</th><th>Packed</th><th>Shipped</th></tr></thead><tbody>${d.items.map(i=>`<tr><td><strong>${esc(i.sku)}</strong><br><small>${esc(i.name||'')}</small></td><td>${esc(i.ordered_qty)}</td><td>${esc(i.allocated_qty)}</td><td>${esc(i.picked_qty)}</td><td>${esc(i.packed_qty)}</td><td>${esc(i.shipped_qty)}</td></tr>`).join('')}</tbody></table></div>`:'<div class="akwms-empty">No order items.</div>'}<h3>Reserved stock</h3>${(d.reservations||[]).length?`<div class="akwms-table-wrap"><table class="akwms-table"><thead><tr><th>SKU</th><th>Location</th><th>Pallet / LPN</th><th>Reserved</th><th>Used</th><th>Status</th></tr></thead><tbody>${d.reservations.map(r=>`<tr><td>${esc(r.sku||'—')}</td><td>${esc(r.location_code||'—')}</td><td>${esc(r.lpn_code||'—')}</td><td>${esc(r.quantity)}</td><td>${esc(r.consumed_qty)}</td><td>${esc(statusLabel(r.status))}</td></tr>`).join('')}</tbody></table></div>`:'<div class="akwms-empty">No stock reservations for this order.</div>'}<h3>Warehouse work</h3>${(d.tasks||[]).length?`<div class="akwms-detail-list">${d.tasks.map(t=>`<div><strong>${esc(t.task_no)}</strong><span>${esc(t.sku||'')} · Pick ${esc(t.requested_qty)} from ${esc(t.from_code||'—')} · ${esc(statusLabel(t.status))}</span></div>`).join('')}</div>`:'<div class="akwms-empty">No warehouse tasks yet.</div>'}<h3>Activity history</h3>${history.length?`<div class="akwms-history">${history.map(h=>`<div><time>${esc(h.when||'')}</time><strong>${esc(h.title)}</strong><span>${esc(h.text||'')}${h.user?' · '+esc(h.user):''}</span></div>`).join('')}</div>`:'<div class="akwms-empty">No activity recorded yet.</div>'}<div class="akwms-actions"><button class="akwms-btn" data-close-order-dialog>Close</button></div></div>`;document.body.appendChild(dialog);dialog.addEventListener('click',e=>{if(e.target.closest('[data-close-order-dialog]'))dialog.close()});dialog.addEventListener('close',()=>dialog.remove(),{once:true});dialog.showModal()}catch(err){msg(err.message||'Could not load order details',true)}finally{loading(false)}}
function actionText(path){if(path==='workspace/warehouses')return 'Creating warehouse...';if(path==='workspace/skus')return 'Creating SKU...';if(path==='workspace/locations')return 'Creating location...';if(path==='workspace/lpns')return 'Creating pallet / LPN...';if(path==='inventory/receive'||path==='workspace/receive-commit')return 'Recording stock receipt...';if(path.includes('/move'))return 'Moving stock...';if(path.includes('/adjust'))return 'Adjusting stock...';if(path==='workspace/counts')return 'Opening stock count...';if(path.includes('/counts/')&&path.includes('/complete'))return 'Saving physical count...';if(path.includes('/putaway/'))return 'Putting stock away...';if(path.includes('/pack'))return 'Packing order...';if(path==='workspace/returns')return 'Recording return...';if(path.includes('/allocate'))return 'Reserving stock...';if(path==='orders')return 'Creating order...';if(path==='workspace/waves')return 'Creating work batch...';if(path.includes('/generate'))return 'Creating warehouse tasks...';if(path.includes('/status'))return 'Updating warehouse task...';if(path.includes('/ship'))return 'Shipping order...';if(path.includes('/cancel'))return 'Cancelling order...';if(path==='workspace/reset-test')return 'Resetting test simulation...';return 'Saving changes...'}function loading(show,text='Working...'){let el=document.getElementById('akwms-loading');if(show){if(!el){el=document.createElement('div');el.id='akwms-loading';el.className='akwms-loading';el.setAttribute('role','status');el.setAttribute('aria-live','assertive');el.innerHTML='<div class="akwms-loading-card"><span class="akwms-spinner" aria-hidden="true"></span><div><strong id="akwms-loading-title"></strong><small>Please wait. Do not click again.</small></div></div>';document.body.appendChild(el)}const title=el.querySelector('#akwms-loading-title');if(title)title.textContent=text;el.hidden=false;document.body.classList.add('akwms-is-busy')}else if(el){el.hidden=true;document.body.classList.remove('akwms-is-busy')}}async function submitJson(path,data){loading(true,actionText(path));try{return await api(path,{method:'POST',data})}catch(e){loading(false);throw e}}root.addEventListener('click',async e=>{const tab=e.target.closest('[data-tab]');if(tab){$$('[data-tab]').forEach(x=>x.classList.toggle('is-active',x===tab));$$('.akwms-pane').forEach(x=>x.classList.toggle('is-active',x.dataset.pane===tab.dataset.tab));if(['count','putaway','packing','returns','integrations'].includes(tab.dataset.tab)){try{await loadOpsTab(tab.dataset.tab)}catch(err){msg(err.message||'Could not load this area',true)}}return}const guide=e.target.closest('[data-guide-tab]');if(guide){const target=guide.dataset.guideTab;$$('[data-tab]').forEach(x=>x.classList.toggle('is-active',x.dataset.tab===target));$$('.akwms-pane').forEach(x=>x.classList.toggle('is-active',x.dataset.pane===target));if(['count','putaway','packing','returns','integrations'].includes(target)){try{await loadOpsTab(target)}catch(err){msg(err.message||'Could not load this area',true)}}root.querySelector('.akwms-nav')?.scrollIntoView({behavior:'smooth',block:'start'});return}if(e.target.closest('[data-guide-toggle]')){guideEnabled=!guideEnabled;localStorage.setItem('akwmsGuideEnabled',guideEnabled?'on':'off');renderGuide();return}if(e.target.closest('[data-guide-restart]')){guideReplayStep=1;sessionStorage.setItem('akwmsGuideReplayStep','1');renderGuide();return}if(e.target.closest('[data-guide-prev]')){guideReplayStep=Math.max(1,guideReplayStep-1);sessionStorage.setItem('akwmsGuideReplayStep',String(guideReplayStep));renderGuide();return}if(e.target.closest('[data-guide-next]')){guideReplayStep=Math.min(8,guideReplayStep+1);sessionStorage.setItem('akwmsGuideReplayStep',String(guideReplayStep));renderGuide();return}if(e.target.closest('[data-guide-live]')){guideReplayStep=0;sessionStorage.removeItem('akwmsGuideReplayStep');renderGuide();return}if(e.target.closest('[data-reset-test]')){const ok=confirm('Reset the tutorial simulation?\n\nThis removes TEST-SKU-001, PLT-TEST-001, its test inventory, manual Test Customer orders, work batches and tasks.\n\nWH01 and the default warehouse locations will be kept.');if(!ok)return;const btn=e.target.closest('[data-reset-test]');btn.disabled=true;try{await submitJson('workspace/reset-test',{});guideReplayStep=1;sessionStorage.setItem('akwmsGuideReplayStep','1');msg('Test simulation reset. Warehouse and default locations were kept.');await loadAll()}catch(err){msg(err.message||'Could not reset test simulation',true)}finally{btn.disabled=false}return}if(e.target.closest('[data-action="add-line"]')){const lines=$$('#akwms-order-lines .akwms-order-line');const last=lines[lines.length-1];if(last&&!last.querySelector('.line-sku')?.value){msg('Select a product on the current line before adding another.',true);last.querySelector('.line-sku-search')?.focus();return}addOrderLine();return}const pick=e.target.closest('[data-sku-pick]');if(pick){const line=pick.closest('.akwms-order-line');line.querySelector('.line-sku').value=pick.dataset.skuPick;line.querySelector('.line-sku-search').value=pick.dataset.skuLabel||pick.textContent.trim();line.querySelector('.akwms-sku-results').hidden=true;return}if(e.target.closest('[data-remove-line]')){e.target.closest('.akwms-order-line').remove();ensureOrderLine();updateOrderLineState();return}const receivePick=e.target.closest('[data-receive-pick]');if(receivePick){const combo=receivePick.closest('.akwms-combo');combo._akwmsPendingNew=null;combo.classList.remove('is-pending-new');combo.querySelector('.akwms-combo-id').value=receivePick.dataset.id;combo.querySelector('.akwms-combo-input').value=receivePick.dataset.label||'';combo.querySelector('.akwms-combo-results').hidden=true;return}
if(e.target.closest('[data-receive-none]')){const combo=e.target.closest('.akwms-combo');combo._akwmsPendingNew=null;combo.classList.remove('is-pending-new');combo.querySelector('.akwms-combo-id').value='';combo.querySelector('.akwms-combo-input').value='';combo.querySelector('.akwms-combo-results').hidden=true;return}
const receiveCreate=e.target.closest('[data-receive-create]');if(receiveCreate){const combo=receiveCreate.closest('.akwms-combo');const type=receiveCreate.dataset.receiveCreate;const value=(receiveCreate.dataset.value||'').trim();if(!value)return;if(type==='sku'){const data=await newSkuDialog(value);if(!data)return;combo._akwmsPendingNew={type:'sku',data:{...data,sku:data.sku.toUpperCase()}};combo.querySelector('.akwms-combo-id').value='';combo.querySelector('.akwms-combo-input').value=`${data.sku.toUpperCase()} — ${data.name}`;combo.classList.add('is-pending-new');combo.querySelector('.akwms-combo-results').hidden=true;msg(`New SKU ${data.sku.toUpperCase()} is staged. It will only be created when you click Record receipt.`);return}const f=$('#akwms-receive-form');const wid=+f.querySelector('[name="warehouse_id"]').value,loc=+f.querySelector('[name="location_id"]').value;if(!wid||!loc){msg('Choose a warehouse and location before staging a new pallet / LPN.',true);return}combo._akwmsPendingNew={type:'lpn',data:{code:value.toUpperCase()}};combo.querySelector('.akwms-combo-id').value='';combo.querySelector('.akwms-combo-input').value=value.toUpperCase();combo.classList.add('is-pending-new');combo.querySelector('.akwms-combo-results').hidden=true;msg(`New pallet / LPN ${value.toUpperCase()} is staged. It will only be created when you click Record receipt.`);return}
const countStart=e.target.closest('[data-inventory-count]');if(countStart){const row=state.inventory.find(x=>String(x.id)===String(countStart.dataset.inventoryCount));if(!row){msg('Inventory row not found',true);return}countStart.disabled=true;try{await submitJson('workspace/counts',{inventory_id:+row.id,reason:'MANUAL_CHECK'});msg(`Stock count opened for ${row.sku} at ${row.location_code}.`);$$('[data-tab]').forEach(x=>x.classList.toggle('is-active',x.dataset.tab==='count'));$$('.akwms-pane').forEach(x=>x.classList.toggle('is-active',x.dataset.pane==='count'));await loadAll();await loadOpsTab('count')}catch(err){msg(err.message||'Could not open stock count',true)}finally{loading(false);countStart.disabled=false}return}
const countComplete=e.target.closest('[data-count-complete]');if(countComplete){const row=state.counts.find(x=>String(x.id)===String(countComplete.dataset.countComplete));if(!row){msg('Stock count not found',true);return}const data=await countDialog(row);if(!data)return;countComplete.disabled=true;try{await submitJson(`workspace/counts/${row.id}/complete`,data);msg('Stock count saved. Any approved difference has been applied to inventory.');await loadAll();await loadOpsTab('count')}catch(err){msg(err.message||'Could not save stock count',true)}finally{loading(false);countComplete.disabled=false}return}
const put=e.target.closest('[data-putaway]');if(put){const id=put.dataset.putaway;const qty=+$(`[data-putaway-qty="${id}"]`)?.value;const dest=+$(`[data-putaway-dest="${id}"]`)?.value;if(!qty||!dest){msg('Choose a storage destination and quantity',true);return}put.disabled=true;try{await submitJson(`workspace/putaway/${id}`,{quantity:qty,to_location_id:dest});msg('Stock put away into storage.');await loadAll();await loadOpsTab('putaway')}catch(err){msg(err.message||'Could not put stock away',true)}finally{loading(false);put.disabled=false}return}
const pack=e.target.closest('[data-pack-order]');if(pack){pack.disabled=true;try{await submitJson(`workspace/orders/${pack.dataset.packOrder}/pack`,{});msg('Order verified and packed. It is now ready to ship.');await loadAll();await loadOpsTab('packing')}catch(err){msg(err.message||'Could not pack order',true)}finally{loading(false);pack.disabled=false}return}const invPage=e.target.closest('[data-inventory-page]');if(invPage){invPage.disabled=true;try{await loadInventoryPage(+invPage.dataset.inventoryPage)}catch(err){msg(err.message||'Could not load inventory page',true)}return}const ordPage=e.target.closest('[data-orders-page]');if(ordPage){ordPage.disabled=true;try{await loadOrdersPage(+ordPage.dataset.ordersPage)}catch(err){msg(err.message||'Could not load orders page',true)}return}const od=e.target.closest('[data-order-details]');if(od){await showOrderDetails(+od.dataset.orderDetails);return}const oc=e.target.closest('[data-order-cancel]');if(oc){const row=state.orders.find(x=>String(x.id)===String(oc.dataset.orderCancel));const label=row?.order_no||'this order';if(!confirm(`Cancel ${label}?\n\nAny active stock reservation will be released back to available stock. Dedicated warehouse tasks for this order will also be cancelled.`))return;oc.disabled=true;try{await submitJson(`workspace/orders/${oc.dataset.orderCancel}/cancel`,{});msg(`${label} cancelled and its reserved stock released.`);await loadAll()}catch(err){msg(err.message||'Could not cancel order',true)}finally{oc.disabled=false}return}const adj=e.target.closest('[data-inventory-adjust]');if(adj){const row=state.inventory.find(x=>String(x.id)===String(adj.dataset.inventoryAdjust));if(!row){msg('Inventory row not found',true);return}const data=await adjustDialog(row);if(!data)return;adj.disabled=true;try{await submitJson(`inventory/${row.id}/adjust`,data);msg(`Stock adjusted: ${row.sku} at ${row.location_code} is now ${data.new_qty}`);await loadAll()}catch(err){msg(err.message||'Could not adjust stock',true)}finally{adj.disabled=false}return}
const resolve=e.target.closest('[data-resolve-short]');if(resolve){const task=state.tasks.find(x=>String(x.id)===String(resolve.dataset.resolveShort));if(!task){msg('Task not found',true);return}const choice=await resolveShortDialog(task);if(!choice||choice.action==='cancel'||!choice.action)return;if(choice.action==='adjust'&&choice.row){const data=await adjustDialog(choice.row);if(!data)return;try{await submitJson(`inventory/${choice.row.id}/adjust`,data);msg('Stock count updated. Review the task and retry when ready.');await loadAll()}catch(err){msg(err.message||'Could not adjust stock',true)}return}if(choice.action==='receive'){const target='receive';$$('[data-tab]').forEach(x=>x.classList.toggle('is-active',x.dataset.tab===target));$$('.akwms-pane').forEach(x=>x.classList.toggle('is-active',x.dataset.pane===target));root.querySelector('.akwms-nav')?.scrollIntoView({behavior:'smooth',block:'start'});msg('Receive enough stock, then return to Tasks and retry the task.');return}if(choice.action==='inventory'){const target='inventory';$$('[data-tab]').forEach(x=>x.classList.toggle('is-active',x.dataset.tab===target));$$('.akwms-pane').forEach(x=>x.classList.toggle('is-active',x.dataset.pane===target));root.querySelector('.akwms-nav')?.scrollIntoView({behavior:'smooth',block:'start'});msg('Find the stock and use Move if it is stored elsewhere. Then return to Tasks and retry.');return}if(choice.action==='retry'){try{await submitJson(`tasks/${task.id}/status`,{status:'IN_PROGRESS'});msg('Task reopened. Try the pick again, then click Complete if you find the stock.');await loadAll()}catch(err){msg(err.message||'Could not retry task',true)}return}}const m=e.target.closest('[data-inventory-move]');if(m){const row=state.inventory.find(x=>String(x.id)===String(m.dataset.inventoryMove));if(!row){msg('Inventory row not found',true);return}const move=await moveDialog(row);if(!move)return;m.disabled=true;try{await submitJson(`inventory/${row.id}/move`,{to_location_id:move.to_location_id,quantity:move.quantity,reason:'WORKSPACE_MOVE'});msg('Inventory moved successfully');await loadAll()}catch(err){msg(err.message||'Could not move inventory',true)}finally{m.disabled=false}return}const a=e.target.closest('[data-order-allocate]');if(a){a.disabled=true;try{await submitJson(`orders/${a.dataset.orderAllocate}/allocate`,{});msg('Stock reserved for this order');await loadAll()}catch(err){msg(err.message,true)}return}const s=e.target.closest('[data-order-ship]');if(s){if(!confirm('Ship this order and deduct its reserved inventory?'))return;s.disabled=true;try{await submitJson(`workspace/orders/${s.dataset.orderShip}/ship`,{});msg('Order shipped and inventory deducted');await loadAll()}catch(err){msg(err.message,true)}return}const g=e.target.closest('[data-wave-generate]');if(g){g.disabled=true;try{const r=await submitJson(`workspace/waves/${g.dataset.waveGenerate}/generate`,{});msg(`${r.generated} warehouse task(s) generated`);await loadAll()}catch(err){msg(err.message,true)}return}const t=e.target.closest('[data-task-status]');if(t){t.disabled=true;try{const payload={status:t.dataset.status};if(t.dataset.status==='COMPLETED'){const task=state.tasks.find(x=>String(x.id)===String(t.dataset.taskStatus));payload.confirmed_qty=task?.requested_qty||0}await submitJson(`tasks/${t.dataset.taskStatus}/status`,payload);msg(`Task marked ${t.dataset.status.replace('_',' ').toLowerCase()}`);await loadAll()}catch(err){msg(err.message,true)}return}});const skuResultHtml=rows=>rows.length?rows.map(x=>`<button type="button" data-sku-pick="${esc(x.id)}" data-sku-label="${esc(x.sku)} — ${esc(x.name)}"><strong>${esc(x.sku)}</strong><span>${esc(x.name||'')}</span>${x.barcode?`<small>${esc(x.barcode)}</small>`:''}</button>`).join(''):'<div class="akwms-sku-no-result">No matching products found.</div>';
function closeSkuDropdowns(except=null){$$('.akwms-sku-results').forEach(r=>{if(r!==except)r.hidden=true})}
root.addEventListener('focusin',e=>{const comboInput=e.target.closest('.akwms-combo-input');if(comboInput){const combo=comboInput.closest('.akwms-combo');$$('.akwms-combo-results').forEach(r=>{if(r!==combo.querySelector('.akwms-combo-results'))r.hidden=true});loadReceiveCombo(combo,true);return}const input=e.target.closest('.line-sku-search');if(!input)return;const line=input.closest('.akwms-order-line');const results=line?.querySelector('.akwms-sku-results');if(!results)return;closeSkuDropdowns(results);const cached=(state.master?.skus||[]).slice(0,30);results.innerHTML=skuResultHtml(cached);results.hidden=false;clearTimeout(input._akwmsFocusRefresh);input._akwmsFocusRefresh=setTimeout(async()=>{try{const selected=line.querySelector('.line-sku')?.value;const focusQuery=selected?'':input.value.trim();const rows=await api(`workspace/catalog-search?type=sku&q=${encodeURIComponent(focusQuery)}`);if(document.activeElement===input||line.matches(':focus-within')){results.innerHTML=skuResultHtml(rows);results.hidden=false}}catch(err){}},0)});
document.addEventListener('pointerdown',e=>{if(!e.target.closest('.akwms-product-picker'))closeSkuDropdowns();if(!e.target.closest('.akwms-combo'))$$('.akwms-combo-results').forEach(r=>r.hidden=true)});
root.addEventListener('keydown',e=>{const input=e.target.closest('.line-sku-search');if(!input)return;const results=input.closest('.akwms-order-line')?.querySelector('.akwms-sku-results');if(!results)return;if(e.key==='ArrowDown'&&!results.hidden){const first=results.querySelector('[data-sku-pick]');if(first){e.preventDefault();first.focus()}}if(e.key==='Escape'){results.hidden=true}});root.addEventListener('input',e=>{const comboInput=e.target.closest('.akwms-combo-input');if(comboInput){const combo=comboInput.closest('.akwms-combo');combo._akwmsPendingNew=null;combo.classList.remove('is-pending-new');combo.querySelector('.akwms-combo-id').value='';clearTimeout(comboInput._akwmsComboTimer);renderReceiveCombo(combo,receiveComboRows(combo.dataset.receiveCombo,comboInput.value),combo.dataset.receiveCombo,comboInput.value);comboInput._akwmsComboTimer=setTimeout(()=>loadReceiveCombo(combo,true),180);return}if(e.target.id==='akwms-inv-search'){invQuery=e.target.value.trim();clearTimeout(invSearchTimer);invSearchTimer=setTimeout(()=>loadInventoryPage(1).catch(err=>msg(err.message||'Could not search inventory',true)),300)}if(e.target.id==='akwms-order-search'){orderQuery=e.target.value.trim();clearTimeout(orderSearchTimer);orderSearchTimer=setTimeout(()=>loadOrdersPage(1).catch(err=>msg(err.message||'Could not search orders',true)),300)}const type=e.target.dataset.catalogLookup;if(type){clearTimeout(e.target._akwmsCatalogTimer);const input=e.target;const line=input.closest('.akwms-order-line');if(line){line.querySelector('.line-sku').value=''}e.target._akwmsCatalogTimer=setTimeout(async()=>{try{const rows=await api(`workspace/catalog-search?type=${encodeURIComponent(type)}&q=${encodeURIComponent(input.value.trim())}`);if(line&&type==='sku'){const results=line.querySelector('.akwms-sku-results');results.innerHTML=rows.length?rows.map(x=>`<button type="button" data-sku-pick="${esc(x.id)}" data-sku-label="${esc(x.sku)} — ${esc(x.name)}"><strong>${esc(x.sku)}</strong><span>${esc(x.name||'')}</span>${x.barcode?`<small>${esc(x.barcode)}</small>`:''}</button>`).join(''):'<div class="akwms-sku-no-result">No matching SKU found.</div>';results.hidden=false;return}const select=input.closest('form')?.querySelector(type==='sku'?'[name="sku_id"]':'[name="lpn_id"]');if(!select)return;const blank=type==='lpn'?'<option value="">No LPN</option>':'<option value="">Select SKU</option>';select.innerHTML=blank+rows.map(x=>type==='sku'?`<option value="${esc(x.id)}">${esc(x.sku)} — ${esc(x.name)}</option>`:`<option value="${esc(x.id)}">${esc(x.code)} · ${esc(x.lpn_type)}${x.location_code?' · '+esc(x.location_code):''}</option>`).join('');const q=input.value.trim().toUpperCase();const exact=rows.find(x=>String(type==='sku'?x.sku:x.code).toUpperCase()===q);if(exact)select.value=String(exact.id);else if(rows.length===1)select.value=String(rows[0].id)}catch(err){msg(err.message||'Could not search catalog',true)}},250)}});root.addEventListener('change',async e=>{if(e.target.id==='akwms-return-order'){const skuSel=$('#akwms-return-sku');skuSel.innerHTML='<option value="">Loading products…</option>';if(!e.target.value){skuSel.innerHTML='<option value="">Choose order first</option>';return}try{const d=await api(`workspace/orders/${e.target.value}/details`);skuSel.innerHTML='<option value="">Choose product</option>'+(d.items||[]).map(i=>`<option value="${esc(i.sku_id)}">${esc(i.sku)} — ${esc(i.name)} · shipped ${esc(i.shipped_qty)}</option>`).join('')}catch(err){skuSel.innerHTML='<option value="">Could not load products</option>';msg(err.message||'Could not load order products',true)}}});async function handleForm(id,path,transform){const f=$(id);if(!f)return;f.addEventListener('submit',async e=>{e.preventDefault();const btn=f.querySelector('button[type="submit"],button:not([type])');if(btn)btn.disabled=true;loading(true,actionText(path));try{const raw=formData(f),data=transform?transform(raw,f):raw;await submitJson(path,data);f.reset();msg('Saved successfully');await loadAll()}catch(err){msg(err.message||'Could not save',true)}finally{loading(false);if(btn)btn.disabled=false}})}handleForm('#akwms-warehouse-form','workspace/warehouses');handleForm('#akwms-sku-form','workspace/skus');handleForm('#akwms-location-form','workspace/locations');handleForm('#akwms-lpn-form','workspace/lpns');const receiveForm=$('#akwms-receive-form');if(receiveForm)receiveForm.addEventListener('submit',async e=>{e.preventDefault();const f=e.currentTarget;const btn=f.querySelector('button[type="submit"],button:not([type])');if(btn)btn.disabled=true;const raw=formData(f);const skuCombo=f.querySelector('[data-receive-combo="sku"]'),lpnCombo=f.querySelector('[data-receive-combo="lpn"]');const newSku=skuCombo?._akwmsPendingNew?.type==='sku'?skuCombo._akwmsPendingNew.data:null;const newLpn=lpnCombo?._akwmsPendingNew?.type==='lpn'?lpnCombo._akwmsPendingNew.data:null;if(!raw.sku_id&&!newSku){msg('Choose an existing Product / SKU or stage a new SKU first.',true);if(btn)btn.disabled=false;return}const loc=optionData('locations').find(x=>String(x.id)===String(raw.location_id));const skuText=skuCombo?.querySelector('.akwms-combo-input')?.value||'SKU';const lpnText=lpnCombo?.querySelector('.akwms-combo-input')?.value||'';loading(true,'Recording stock receipt...');try{await submitJson('workspace/receive-commit',{warehouse_id:+raw.warehouse_id,location_id:+raw.location_id,sku_id:raw.sku_id?+raw.sku_id:null,lpn_id:raw.lpn_id?+raw.lpn_id:null,new_sku:newSku,new_lpn:newLpn,quantity:+raw.quantity,batch_no:raw.batch_no||'',expiry_date:raw.expiry_date||null,inventory_status:raw.inventory_status||'AVAILABLE'});const summary=`Received ${raw.quantity} × ${skuText} into ${loc?.code||'location'}${lpnText?' on '+lpnText:''}.`;f.reset();if(skuCombo){skuCombo._akwmsPendingNew=null;skuCombo.classList.remove('is-pending-new')}if(lpnCombo){lpnCombo._akwmsPendingNew=null;lpnCombo.classList.remove('is-pending-new')}await loadAll();msg(summary)}catch(err){msg(err.message||'Could not record receipt. No staged SKU or pallet was created.',true)}finally{loading(false);if(btn)btn.disabled=false}});$('#akwms-order-form').addEventListener('submit',async e=>{e.preventDefault();const f=e.currentTarget;const d=formData(f);const items=$$('#akwms-order-lines .akwms-order-line').map(line=>({sku_id:+line.querySelector('.line-sku').value,quantity:+line.querySelector('.line-qty').value})).filter(x=>x.sku_id&&x.quantity>0);if(!items.length){msg('Add at least one valid order line',true);return}const btn=f.querySelector('button[type="submit"],button:not([type])');if(btn)btn.disabled=true;try{await submitJson('orders',{source_channel:d.source_channel||'MANUAL',external_order_id:d.external_order_id||null,customer_name:d.customer_name||'',priority:d.priority||'NORMAL',warehouse_id:d.warehouse_id?+d.warehouse_id:null,items});f.reset();$('#akwms-order-lines').innerHTML='';ensureOrderLine();msg('Order created');await loadAll()}catch(err){msg(err.message,true)}finally{if(btn)btn.disabled=false}});const returnForm=$('#akwms-return-form');if(returnForm)returnForm.addEventListener('submit',async e=>{e.preventDefault();const f=e.currentTarget;const d=formData(f);const btn=f.querySelector('button[type="submit"],button:not([type])');if(btn)btn.disabled=true;try{await submitJson('workspace/returns',{order_id:+d.order_id,sku_id:+d.sku_id,quantity:+d.quantity,disposition:d.disposition,reason:d.reason,notes:d.notes||''});f.reset();$('#akwms-return-sku').innerHTML='<option value="">Choose order first</option>';msg('Return recorded and stock routed according to the selected result.');await loadAll();await loadOpsTab('returns')}catch(err){msg(err.message||'Could not record return',true)}finally{loading(false);if(btn)btn.disabled=false}});$('#akwms-wave-form').addEventListener('submit',async e=>{e.preventDefault();const f=e.currentTarget;const ids=[...f.querySelectorAll('input[name="order_ids"]:checked')].map(x=>+x.value);const wid=+f.querySelector('[name="warehouse_id"]').value;if(!ids.length){msg('Select at least one reserved order',true);return}try{await submitJson('workspace/waves',{warehouse_id:wid,order_ids:ids});msg('Wave created');await loadAll()}catch(err){msg(err.message,true)}});loadAll()})();

Youez - 2016 - github.com/yon3zu
LinuXploit