2 lines
13 KiB
JavaScript
2 lines
13 KiB
JavaScript
frappe.pages["makap-materials"].on_page_load=function(wrapper){const page=frappe.ui.make_app_page({parent:wrapper,title:"Materials & Inventory Control Center",single_column:true});page.main.html(`<section class="makap-mat-shell"><header class="makap-mat-head"><div><div class="makap-mat-eyebrow">Materials & inventory</div><h1>Materials & Inventory Control Center</h1><p>Track demand, supplier commitments, receipts, and material exceptions in one supply-readiness workspace.</p></div><div class="makap-mat-actions"><button class="btn btn-default btn-sm" data-action="refresh"><i class="fa fa-refresh"></i> Refresh</button><button class="btn btn-primary btn-sm" data-action="new"><i class="fa fa-plus"></i> <span data-new-label>New Material Request</span></button></div></header><section class="makap-mat-metrics"><div><span>Open requests</span><strong data-metric="open_requests">—</strong><small>Demand awaiting fulfilment</small></div><div><span>Due requests</span><strong data-metric="due_requests">—</strong><small>Needs date attention</small></div><div class="makap-mat-alert"><span>Open purchase orders</span><strong data-metric="open_orders">—</strong><small>Supplier commitments in flight</small></div><div><span>Receipts recorded</span><strong data-metric="receipts">—</strong><small>Inbound material documents</small></div></section><nav class="makap-mat-tabs"><button data-tab="requests"><i class="fa fa-list-alt"></i> Material Requests</button><button data-tab="orders"><i class="fa fa-truck"></i> Purchase Orders</button><button data-tab="receipts"><i class="fa fa-inbox"></i> Purchase Receipts</button></nav><div class="makap-mat-workspace"><aside class="makap-mat-list-panel"><div class="makap-mat-list-head"><div><strong data-list-title>Material request queue</strong><span data-count>0 records</span></div><div class="makap-mat-search"><i class="fa fa-search"></i><input type="search" data-search placeholder="Search material requests..."></div><select data-filter><option>All</option><option>Draft</option><option>Pending</option><option>Submitted</option><option>Stopped</option><option>Completed</option><option>Cancelled</option><option>To Receive</option><option>To Receive and Bill</option></select></div><div class="makap-mat-list" data-list><div class="makap-mat-empty">Loading records...</div></div></aside><article class="makap-mat-detail" data-detail><div class="makap-mat-empty makap-mat-detail-empty"><div class="makap-mat-empty-icon"><i class="fa fa-truck"></i></div><strong>Select a supply record</strong><span>Choose a request, purchase order, or receipt to inspect its material context.</span></div></article></div></section>`);const root=page.main[0],list=root.querySelector("[data-list]"),detail=root.querySelector("[data-detail]"),search=root.querySelector("[data-search]"),filter=root.querySelector("[data-filter]"),count=root.querySelector("[data-count]"),title=root.querySelector("[data-list-title]"),newLabel=root.querySelector("[data-new-label]");let tab=new URLSearchParams(location.search).get("tab")||"requests",active=null,timer;const esc=v=>frappe.utils.escape_html(String(v==null?"":v)),num=v=>new Intl.NumberFormat().format(Number(v||0)),money=v=>Number(v||0).toLocaleString(undefined,{maximumFractionDigits:0}),date=v=>v?frappe.datetime.str_to_user(v):"Not set",initials=v=>(String(v||"?").split(/\s+/).slice(0,2).map(x=>x[0]).join("")||"?").toUpperCase(),call=(m,a={})=>new Promise((ok,no)=>frappe.call({method:m,args:a,callback:r=>r.exc?no(r.exc):ok(r.message)})),tone=s=>({Completed:"complete",Submitted:"ready",Pending:"pending",Draft:"draft",Stopped:"blocked",Cancelled:"blocked","To Receive":"pending","To Receive and Bill":"pending"}[s]||"draft");const cfg={requests:["Material request queue","New Material Request","Search material requests..."],orders:["Purchase order queue","New Purchase Order","Search purchase orders..."],receipts:["Purchase receipt register","New Purchase Receipt","Search receipts..."]};function metrics(s){["open_requests","due_requests","open_orders","receipts"].forEach(k=>root.querySelector(`[data-metric="${k}"]`).textContent=num(s[k]))}function setTab(t){tab=t;active=null;root.querySelectorAll("[data-tab]").forEach(e=>e.classList.toggle("is-active",e.dataset.tab===t));let c=cfg[t];title.textContent=c[0];newLabel.textContent=c[1];search.placeholder=c[2];filter.innerHTML=t==="requests"?`<option>All</option><option>Draft</option><option>Pending</option><option>Submitted</option><option>Stopped</option><option>Completed</option><option>Cancelled</option>`:t==="orders"?`<option>All</option><option>To Receive</option><option>To Receive and Bill</option><option>Completed</option><option>Cancelled</option>`:`<option>All</option><option>Draft</option><option>Submitted</option><option>Completed</option><option>Cancelled</option>`;load()}function row(r){let id=r.name,party=r.supplier||r.company||r.requested_by||"No party",sub=tab==="requests"?(r.material_request_type||"Material demand"):(r.status||"Draft"),meta=tab==="requests"?`${r.schedule_date?`Need ${date(r.schedule_date)}`:"No need date"} · ${r.set_warehouse||"No warehouse"}`:`${r.posting_date||r.transaction_date?date(r.posting_date||r.transaction_date):"No date"} · ${party}`,bad=r.status||"Draft";return `<button class="makap-mat-row ${id===active?"is-active":""}" data-name="${esc(id)}"><span class="makap-mat-avatar ${tone(bad)}">${esc(initials(id))}</span><span class="makap-mat-row-copy"><strong>${esc(id)}</strong><span>${esc(party)} · ${esc(sub)}</span><small>${esc(meta)}</small></span><b class="makap-mat-badge ${tone(bad)}">${esc(bad)}</b></button>`}function render(rows){count.textContent=`${rows.length} record${rows.length===1?"":"s"}`;list.innerHTML=rows.length?rows.map(row).join(""):`<div class="makap-mat-empty"><strong>No records found</strong><span>Try another search or status filter.</span></div>`;list.querySelectorAll("[data-name]").forEach(e=>e.addEventListener("click",()=>select(e.dataset.name)))}function head(kind,name,party,href,bad){return `<div class="makap-mat-detail-hero"><div class="makap-mat-detail-avatar ${tone(bad)}">${esc(initials(name))}</div><div class="makap-mat-detail-identity"><span class="makap-mat-kicker">${esc(kind)}</span><h2>${esc(name)}</h2><p>${esc(party||"No supplier or company")}</p></div><a class="btn btn-default btn-xs" href="${href}">Open ERPNext form</a></div>`}function items(rows,type){return rows?.length?rows.map(r=>`<div class="makap-mat-mini-row"><span><strong>${esc(r.item_name||r.item_code||"Item")}</strong><small>${esc(r.item_code||"")} · ${esc(r.uom||"")}${r.makap_coverage_mode?` · ${esc(r.makap_coverage_mode)}`:""}</small></span><b>${money(r.qty||r.received_qty)}</b></div>`).join(""):`<div class="makap-mat-note">No line items available.</div>`}function renderRequest(o){active=o.name;detail.innerHTML=head("Material Request",o.name,o.company,`/app/material-request/${encodeURIComponent(o.name)}`,o.status)+`<div class="makap-mat-detail-body"><section class="makap-mat-readiness-card ${tone(o.status)}"><div><span>Material demand</span><strong>${esc(o.status||"Draft")}</strong><small>${esc(o.material_request_type||"Purchase") } · Need date ${date(o.schedule_date)}</small></div><i class="fa fa-list-alt"></i></section><section class="makap-mat-detail-section"><div class="makap-mat-section-title">Request context</div><div class="makap-mat-fields"><div><span>Company</span><strong>${esc(o.company||"Not set")}</strong></div><div><span>Requested by</span><strong>${esc(o.requested_by||"Not set")}</strong></div><div><span>Warehouse</span><strong>${esc(o.set_warehouse||"Not set")}</strong></div><div><span>Transaction date</span><strong>${date(o.transaction_date)}</strong></div></div></section><section class="makap-mat-detail-section"><div class="makap-mat-section-title">Material lines</div>${items(o.items)}</section></div>`;paint()}function renderOrder(o){active=o.name;let total=(o.items||[]).reduce((a,r)=>a+Number(r.qty||0),0),received=(o.items||[]).reduce((a,r)=>a+Number(r.received_qty||0),0),pct=total?Math.min(100,Math.round(received/total*100)):0;detail.innerHTML=head("Purchase Order",o.name,o.supplier,`/app/purchase-order/${encodeURIComponent(o.name)}`,o.status)+`<div class="makap-mat-detail-body"><section class="makap-mat-readiness-card ${tone(o.status)}"><div><span>Supplier commitment</span><strong>${esc(o.status||"Draft")}</strong><small>${esc(o.supplier||"No supplier")} · Schedule ${date(o.schedule_date)}</small></div><i class="fa fa-truck"></i></section><section class="makap-mat-detail-section"><div class="makap-mat-section-title">Receipt progress</div><div class="makap-mat-progress-label"><span>${money(received)} of ${money(total)} units received</span><b>${pct}%</b></div><div class="makap-mat-progress"><i style="width:${pct}%"></i></div><div class="makap-mat-fields mat-top"><div><span>Supplier</span><strong>${esc(o.supplier||"Not set")}</strong></div><div><span>Order value</span><strong>${esc(o.currency||"")} ${money(o.grand_total)}</strong></div><div><span>Order date</span><strong>${date(o.transaction_date)}</strong></div><div><span>Schedule date</span><strong>${date(o.schedule_date)}</strong></div></div></section><section class="makap-mat-detail-section"><div class="makap-mat-section-title">Supply lines and controls</div>${items(o.items)}</section></div>`;paint()}function renderReceipt(o){active=o.name;detail.innerHTML=head("Purchase Receipt",o.name,o.supplier,`/app/purchase-receipt/${encodeURIComponent(o.name)}`,o.status)+`<div class="makap-mat-detail-body"><section class="makap-mat-readiness-card ${tone(o.status)}"><div><span>Inbound receipt</span><strong>${esc(o.status||"Draft")}</strong><small>${esc(o.supplier||"No supplier")} · Posted ${date(o.posting_date)}</small></div><i class="fa fa-inbox"></i></section><section class="makap-mat-detail-section"><div class="makap-mat-section-title">Receipt context</div><div class="makap-mat-fields"><div><span>Supplier</span><strong>${esc(o.supplier||"Not set")}</strong></div><div><span>Company</span><strong>${esc(o.company||"Not set")}</strong></div><div><span>Posting date</span><strong>${date(o.posting_date)}</strong></div><div><span>Receipt value</span><strong>${esc(o.currency||"")} ${money(o.grand_total)}</strong></div></div></section><section class="makap-mat-detail-section"><div class="makap-mat-section-title">Received lines and exceptions</div>${(o.items||[]).map(r=>`<div class="makap-mat-mini-row"><span><strong>${esc(r.item_name||r.item_code||"Item")}</strong><small>${esc(r.item_code||"")} · PO ${esc(r.purchase_order||"Not linked")}</small></span><b>${esc(r.makap_receipt_exception_type||"None")}</b></div>`).join("")||`<div class="makap-mat-note">No receipt lines available.</div>`}</section></div>`;paint()}function paint(){root.querySelectorAll(".makap-mat-row").forEach(e=>e.classList.toggle("is-active",e.dataset.name===active))}async function select(name){active=name;detail.innerHTML=`<div class="makap-mat-empty makap-mat-detail-empty"><div class="makap-loading-ring"></div><strong>Loading record</strong><span>Fetching the latest supply details.</span></div>`;try{if(tab==="requests")renderRequest(await call("makap_backend.materials_api.get_material_request",{name}));if(tab==="orders")renderOrder(await call("makap_backend.materials_api.get_purchase_order",{name}));if(tab==="receipts")renderReceipt(await call("makap_backend.materials_api.get_purchase_receipt",{name}))}catch(e){detail.innerHTML=`<div class="makap-mat-empty makap-mat-detail-empty"><strong>Could not load record</strong><span>${esc(e.message||"Please try again.")}</span></div>`}}async function load(){list.innerHTML=`<div class="makap-mat-empty"><div class="makap-loading-ring"></div><span>Loading records...</span></div>`;try{let rows=[];if(tab==="requests")rows=await call("makap_backend.materials_api.list_material_requests",{search:search.value,status:filter.value});if(tab==="orders")rows=await call("makap_backend.materials_api.list_purchase_orders",{search:search.value,status:filter.value});if(tab==="receipts")rows=await call("makap_backend.materials_api.list_purchase_receipts",{search:search.value,status:filter.value});render(rows||[]);if(!active&&rows?.[0])select(rows[0].name)}catch(e){list.innerHTML=`<div class="makap-mat-empty"><strong>Supply records unavailable</strong><span>${esc(e.message||"Check your permissions and try again.")}</span></div>`}}root.querySelectorAll("[data-tab]").forEach(e=>e.addEventListener("click",()=>setTab(e.dataset.tab)));search.addEventListener("input",()=>{clearTimeout(timer);timer=setTimeout(load,250)});filter.addEventListener("change",load);root.querySelector('[data-action="refresh"]').addEventListener("click",async()=>{metrics(await call("makap_backend.materials_api.get_materials_summary"));load()});root.querySelector('[data-action="new"]').addEventListener("click",()=>frappe.new_doc(tab==="requests"?"Material Request":tab==="orders"?"Purchase Order":"Purchase Receipt"));(async()=>{try{metrics(await call("makap_backend.materials_api.get_materials_summary"))}catch(e){}setTab(tab)})();};
|