sale order
SAP-ERP Portal CI/CD / build (push) Successful in 3m57s

This commit is contained in:
John
2026-10-05 18:45:17 +05:30
parent e725a6571b
commit eead8f5ffd
129 changed files with 14252 additions and 452 deletions
+234
View File
@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/auth-guard.js?v=4"></script>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title>Sample Requests — SAP B1</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"/>
<style>
:root{--ink:#080e18;--blue:#1a6fff;--amber:#f5a623;--green:#0dbb8a;--red:#e53e3e;--border:#1c2e48;--border2:#243650;--text:#c8d6e8;--text2:#7a9ab8;--text3:#3d5470;--surface:#0f1d30;--surface2:#142338;--surface3:#0b1520;--radius:10px;--font:'Space Grotesk',sans-serif;--mono:'JetBrains Mono',monospace}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font);background:var(--ink);color:var(--text);min-height:100vh}
main{padding:12px;max-width:1500px;margin:0 auto}
.page-head{display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap}.page-title{font-size:16px;font-weight:700}
.head-actions{margin-left:auto;display:flex;gap:8px}
.tabs{display:flex;gap:4px;margin-bottom:10px;border-bottom:1px solid var(--border)}
.tab{padding:8px 14px;font-size:12.5px;font-weight:700;color:var(--text2);cursor:pointer;border-bottom:2px solid transparent}.tab.on{color:var(--blue);border-bottom-color:var(--blue)}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:12px;overflow:hidden}
.card-bd{padding:12px 14px}
.filters{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end}.filters>div{min-width:150px}
.lst{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
.lst th{text-align:left;padding:8px 10px;font-size:9px;font-weight:700;text-transform:uppercase;color:var(--text3);border-bottom:2px solid var(--border2);white-space:nowrap;background:var(--surface3);position:sticky;top:0}
.lst td{padding:7px 10px;border-bottom:1px solid var(--border);white-space:nowrap}
.lst tbody tr{cursor:pointer}.lst tbody tr:hover td{background:rgba(26,111,255,.06)}
.tbl-wrap{overflow:auto;max-height:70vh}
.mono{font-family:var(--mono)}.empty{text-align:center;padding:26px;color:var(--text3);font-size:12px}
.ac{position:relative}.ac-list{position:absolute;z-index:50;left:0;right:0;top:100%;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;max-height:260px;overflow:auto}
.ac-list div{padding:7px 10px;font-size:12px;cursor:pointer;border-bottom:1px solid var(--border)}.ac-list div:hover{background:rgba(26,111,255,.12)}
</style>
<link rel="stylesheet" href="/theme.css?v=3"/>
<script src="/sales-common.js?v=4"></script>
</head>
<body>
<main id="app"><div class="empty"><span class="sc-spin"></span></div></main>
<script>
'use strict';
const { esc, num, date, dt, q, qa, toast, api } = SC;
let ME = null, TAB = 'domestic';
const cap = (k, p) => ME.caps[k] && ME.caps[k].includes(p);
const divName = id => ((ME.allDivisions || ME.divisions).find(d => d.id === id) || {}).name || id; // incl. disabled categories (existing orders)
const SCOL = { 1: 3, 2: 7, 3: 5, 4: 10, 5: 1, 6: 6, 7: 2, 8: 8, 9: 9 }; // reuse order pill colours
const ECOL = { 1: 3, 2: 2, 3: 6, 4: 8 };
async function init() {
ME = await api('/me');
if (new URLSearchParams(location.search).get('tab') === 'export' || (!cap('sales_sample:view', 'view') && cap('sales_export_sample:view', 'view'))) TAB = 'export';
render();
}
function render() {
const d = cap('sales_sample:view', 'view'), e = cap('sales_export_sample:view', 'view');
q('#app').innerHTML = `<div class="page-head"><div class="page-title">Sample Requests</div><div class="head-actions">
${TAB === 'domestic' && cap('sales_sample:create', 'add') ? '<button class="sc-btn sc-b-primary" id="b-new">+ New sample request</button>' : ''}
${TAB === 'export' && cap('sales_export_sample:create', 'add') ? '<button class="sc-btn sc-b-primary" id="b-new">+ New export sample request</button>' : ''}</div></div>
<div class="tabs">${d ? `<div class="tab ${TAB === 'domestic' ? 'on' : ''}" data-t="domestic">Domestic</div>` : ''}${e ? `<div class="tab ${TAB === 'export' ? 'on' : ''}" data-t="export">Export</div>` : ''}</div>
${!d && !e ? '<div class="sc-err">You are not assigned any Sample Request step.</div>' : ''}
<div class="card"><div class="card-bd"><div class="filters">
<div><label class="sc-lbl">Status</label><select class="sc-in" id="f-st"><option value="">All</option>${Object.entries(TAB === 'export' ? ME.exportStatusLabels : ME.sampleStatusLabels).map(([k, v]) => `<option value="${k}">${esc(v)}</option>`).join('')}</select></div>
<div style="flex:1;min-width:220px"><label class="sc-lbl">Search</label><input class="sc-in" id="f-q" placeholder="Ref / customer"></div>
<div style="min-width:auto"><button class="sc-btn sc-b-primary" id="f-go">Search</button></div></div></div></div>
<div class="card"><div class="tbl-wrap" id="tbl"></div></div>`;
qa('.tab').forEach(t => { t.onclick = () => { TAB = t.dataset.t; render(); }; });
if (q('#b-new')) q('#b-new').onclick = () => (TAB === 'export' ? exportForm() : sampleForm());
q('#f-go').onclick = load; q('#f-q').onkeydown = ev => { if (ev.key === 'Enter') load(); };
if (d || e) load();
}
async function load() {
const p = new URLSearchParams({ statuses: q('#f-st').value, search: q('#f-q').value.trim() });
q('#tbl').innerHTML = '<div class="empty"><span class="sc-spin"></span></div>';
try {
if (TAB === 'export') {
const rows = await api('/export-samples?' + p);
q('#tbl').innerHTML = rows.length ? `<table class="lst"><thead><tr><th>Ref no.</th><th>Date</th><th>Customer</th><th>Country</th><th>Region</th><th>Raised by</th><th>Status</th><th>Courier</th></tr></thead><tbody>
${rows.map(r => `<tr data-id="${r.id}"><td class="mono">${esc(r.refNo)}</td><td>${date(r.reqDate || r.createdAt)}</td><td>${esc(r.custName)}</td><td>${esc(r.country)}</td><td>${esc(r.region)}</td><td>${esc(r.createdByName)}</td>
<td>${SC.pill(ECOL[r.status], r.statusLabel)}</td><td>${esc(r.courierNo)}</td></tr>`).join('')}</tbody></table>` : '<div class="empty">No export sample requests.</div>';
qa('#tbl tr[data-id]').forEach(tr => { tr.onclick = () => openExport(tr.dataset.id); });
} else {
const rows = await api('/samples?' + p);
q('#tbl').innerHTML = rows.length ? `<table class="lst"><thead><tr><th>Ref no.</th><th>Date</th><th>Customer</th><th>Category</th><th>Blood bank</th><th>Raised by</th><th>Status</th><th>QA due</th><th>Courier</th></tr></thead><tbody>
${rows.map(r => `<tr data-id="${r.id}"><td class="mono">${esc(r.refNo)}</td><td>${date(r.reqDate || r.createdAt)}</td><td>${esc(r.cardName)}<div class="sc-muted">${esc(r.cardCode)}</div></td><td>${esc(divName(r.divisionId))}</td>
<td>${esc(r.bbName)}</td><td>${esc(r.createdByName)}</td><td>${SC.pill(SCOL[r.status], r.statusLabel)}</td><td>${r.status === 6 ? date(r.qaDueDate) : ''}</td><td>${esc(r.courierNo)}</td></tr>`).join('')}</tbody></table>` : '<div class="empty">No sample requests.</div>';
qa('#tbl tr[data-id]').forEach(tr => { tr.onclick = () => openSample(tr.dataset.id); });
}
} catch (e) { q('#tbl').innerHTML = `<div class="sc-err">${esc(e.message)}</div>`; }
}
function itemsTable(items, editBatch) {
return `<table class="sc-tbl"><thead><tr><th>Item</th><th>Description</th><th class="n">Qty</th><th>Batch no.</th></tr></thead><tbody>
${items.map(i => `<tr><td class="mono">${esc(i.displayCode)}</td><td>${esc(i.description)}</td><td class="n">${num(i.qty)}</td>
<td>${editBatch ? `<input class="sc-in" data-batch="${i.id}" value="${esc(i.batchNo)}" style="width:160px">` : esc(i.batchNo || '—')}</td></tr>`).join('')}</tbody></table>`;
}
function logHtml(log, labels) { return `<div class="sc-tl">${log.map(l => `<div><b>${dt(l.at)}</b> — ${esc(l.actorName)}: ${esc(l.action.replace(/_/g, ' '))}${l.toStatus != null && l.toStatus !== l.fromStatus ? ` → <i>${esc(labels[l.toStatus] || l.toStatus)}</i>` : ''}${l.remarks ? ` — <span class="sc-muted">${esc(l.remarks)}</span>` : ''}</div>`).join('')}</div>`; }
const batchMap = root => Object.fromEntries(qa('[data-batch]', root).map(i => [i.dataset.batch, i.value]));
// ── Domestic ────────────────────────────────────────────────────────────────
async function openSample(id) {
let s; try { s = await api('/samples/' + id); } catch (e) { return toast(e.message); }
const A = s.actions;
const actHtml = A.length ? `<div class="sc-sec">Take action</div>
${A.includes('buh_approve') ? `<div class="sc-row"><div><label class="sc-lbl">Decision</label><select class="sc-in" id="x-buh"><option value="buh_approve">Approve</option><option value="buh_modify">Request modification</option><option value="buh_reject">Reject</option></select></div></div>
<div style="margin-top:8px"><label class="sc-lbl">Remarks (required for modification / rejection)</label><textarea class="sc-in" id="x-rm"></textarea></div>` : ''}
${A.includes('stock') ? `<div class="sc-row"><div><label class="sc-lbl">Stock available?</label><select class="sc-in" id="x-stock"><option value="">— Select —</option><option value="1">Yes — enter batch nos., send to QA</option><option value="0">No — goes to production</option></select></div></div>
<div class="sc-muted" style="margin-top:6px">Enter batch nos. in the items table above when stock is available.</div>` : ''}
${A.includes('dispatch') ? `<div class="sc-row"><div><label class="sc-lbl">Challan no. *</label><input class="sc-in" id="x-ch"></div><div><label class="sc-lbl">Challan date *</label><input class="sc-in" type="date" id="x-cd" value="${SC.today()}"></div>
<div><label class="sc-lbl">Courier no. *</label><input class="sc-in" id="x-co"></div></div><div style="margin-top:8px"><label class="sc-lbl">Dispatched qty & batch details</label><input class="sc-in" id="x-di"></div>
<div class="sc-muted" style="margin-top:6px">Submitting marks QA verification done and the sample as shipped.</div>` : ''}
${A.includes('feedback') ? `<label class="sc-lbl">Customer feedback *</label><textarea class="sc-in" id="x-fb"></textarea>` : ''}
${A.includes('resubmit') ? `<div class="sc-warn">BUH asked for modification: ${esc(s.modificationRemarks)}</div>` : ''}` : '';
const editBatch = A.includes('stock');
const m = SC.modal({ title: `Sample ${esc(s.refNo)} — ${esc(s.cardName)}`, wide: true, body: `
<div class="sc-grid"><div class="sc-kv"><b>Status</b>${SC.pill(SCOL[s.status], s.statusLabel)}</div><div class="sc-kv"><b>Customer</b><span>${esc(s.cardName)} (${esc(s.cardCode)})</span></div>
<div class="sc-kv"><b>Category</b><span>${esc(divName(s.divisionId))}</span></div><div class="sc-kv"><b>Request date</b><span>${date(s.reqDate)}</span></div>
<div class="sc-kv"><b>Required in (days)</b><span>${esc(s.deliveryDays || '—')}</span></div><div class="sc-kv"><b>Blood bank</b><span>${esc(s.bbName || '—')}</span></div>
<div class="sc-kv"><b>BB licence no.</b><span>${esc(s.bbLicenceNo || '—')}</span></div><div class="sc-kv"><b>Raised by</b><span>${esc(s.createdByName)} · ${dt(s.createdAt)}</span></div>
${s.qaDueDate ? `<div class="sc-kv"><b>QA due</b><span>${date(s.qaDueDate)}</span></div>` : ''}${s.courierNo ? `<div class="sc-kv"><b>Challan / courier</b><span>${esc(s.challanNo)} · ${date(s.challanDate)} · ${esc(s.courierNo)}</span></div>` : ''}</div>
<div class="sc-kv" style="margin-top:8px"><b>Address</b><span>${esc(s.address)}</span></div>
<div class="sc-sec">Items</div>${itemsTable(s.items, editBatch)}
${s.internalRemarks || s.modificationRemarks || s.rejectionRemarks || s.feedback ? `<div class="sc-sec">Remarks</div><div class="sc-row">
${s.internalRemarks ? `<div><label class="sc-lbl">BUH remarks</label><div class="sc-remarks">${esc(s.internalRemarks)}</div></div>` : ''}
${s.modificationRemarks ? `<div><label class="sc-lbl">Modification</label><div class="sc-remarks">${esc(s.modificationRemarks)}</div></div>` : ''}
${s.rejectionRemarks ? `<div><label class="sc-lbl">Rejection</label><div class="sc-remarks">${esc(s.rejectionRemarks)}</div></div>` : ''}
${s.feedback ? `<div><label class="sc-lbl">Feedback</label><div class="sc-remarks">${esc(s.feedback)}</div></div>` : ''}</div>` : ''}
${actHtml}<div class="sc-sec">History</div>${logHtml(s.log, ME.sampleStatusLabels)}`,
buttons: [{ label: 'Close' }, ...(A.includes('resubmit') ? [{ label: 'Modify & resubmit', cls: 'sc-b-amber', onClick: () => { sampleForm(s); } }] : []),
...(A.includes('qa_extend') ? [{ label: 'Extend QA due date (+2 days)', onClick: async () => { await api(`/samples/${s.id}/action`, { method: 'POST', body: { action: 'qa_extend' } }); toast('QA due date extended', 'ok'); load(); } }] : []),
...(A.some(a => ['buh_approve', 'stock', 'dispatch', 'feedback'].includes(a)) ? [{ label: 'Submit', cls: 'sc-b-primary', onClick: ({ root }) => sampleSubmit(s, root) }] : [])] });
}
async function sampleSubmit(s, R) {
const A = s.actions; let body;
if (A.includes('buh_approve')) { const a = q('#x-buh', R).value; const rm = q('#x-rm', R).value; body = { action: a, internalRemarks: rm, modificationRemarks: rm, rejectionRemarks: rm }; }
else if (A.includes('stock')) body = { action: 'stock', stockAvailable: q('#x-stock', R).value, batchNos: batchMap(R) };
else if (A.includes('dispatch')) body = { action: 'dispatch', challanNo: q('#x-ch', R).value, challanDate: q('#x-cd', R).value, courierNo: q('#x-co', R).value, dispatchInfo: q('#x-di', R).value };
else if (A.includes('feedback')) body = { action: 'feedback', feedback: q('#x-fb', R).value };
const r = await api(`/samples/${s.id}/action`, { method: 'POST', body });
toast(`Sample ${r.refNo}: ${r.statusLabel}`, 'ok'); load();
}
async function sampleForm(ex) {
const myDivs = ME.divisions.filter(d => ME.profile.divisions.includes(d.id));
const S = { cust: ex ? { cardCode: ex.cardCode, cardName: ex.cardName } : null, cart: null };
const m = SC.modal({ title: ex ? `Modify sample ${esc(ex.refNo)}` : 'New sample request', wide: true, body: `
<div class="sc-row"><div class="ac" style="flex:2"><label class="sc-lbl">Customer *</label><input class="sc-in" id="s-cust" ${ex ? `value="${esc(ex.cardName)} (${esc(ex.cardCode)})" disabled` : ''} placeholder="Type code or name…" autocomplete="off"><div class="ac-list" id="s-ac" style="display:none"></div></div>
<div><label class="sc-lbl">Product category *</label><select class="sc-in" id="s-div" ${ex ? 'disabled' : ''}><option value="">— Select —</option>${myDivs.map(d => `<option value="${d.id}" ${ex && ex.divisionId === d.id ? 'selected' : ''}>${esc(d.name)}</option>`).join('')}</select></div></div>
<div class="sc-row" style="margin-top:8px"><div><label class="sc-lbl">Blood bank name</label><input class="sc-in" id="s-bb" value="${esc(ex ? ex.bbName : '')}"></div>
<div><label class="sc-lbl">BB licence no.</label><input class="sc-in" id="s-lic" value="${esc(ex ? ex.bbLicenceNo : '')}"></div>
<div><label class="sc-lbl">Required within (days)</label><input class="sc-in" type="number" min="1" id="s-days" value="${esc(ex ? ex.deliveryDays : '')}"></div></div>
<div style="margin-top:8px"><label class="sc-lbl">Delivery address</label><textarea class="sc-in" id="s-addr">${esc(ex ? ex.address : '')}</textarea></div>
<div class="sc-sec">Products</div><div id="s-cart" class="sc-muted">Select a product category.</div>
${ex ? '' : '<div style="margin-top:8px"><label class="sc-lbl">Remarks</label><textarea class="sc-in" id="s-rm"></textarea></div>'}`,
buttons: [{ label: 'Cancel' }, { label: ex ? 'Resubmit' : 'Submit request', cls: 'sc-b-primary', onClick: async () => {
if (!S.cust || !S.cart) throw new Error('Select a customer and product category');
const body = { cardCode: S.cust.cardCode, cardName: S.cust.cardName, divisionId: +q('#s-div', R).value, bbName: q('#s-bb', R).value, bbLicenceNo: q('#s-lic', R).value,
deliveryDays: q('#s-days', R).value, address: q('#s-addr', R).value, remarks: q('#s-rm', R) ? q('#s-rm', R).value : '', lines: S.cart.getLines() };
if (ex) await api(`/samples/${ex.id}/action`, { method: 'POST', body: { action: 'resubmit', ...body } }); else await api('/samples', { method: 'POST', body });
toast(ex ? 'Sample request resubmitted' : 'Sample request submitted', 'ok'); load(); } }] });
const R = m.root; let t;
q('#s-cust', R).oninput = () => { clearTimeout(t); const v = q('#s-cust', R).value.trim(); const ac = q('#s-ac', R); if (v.length < 2) { ac.style.display = 'none'; return; }
t = setTimeout(async () => { const list = await api('/customers?q=' + encodeURIComponent(v)).catch(e => { toast(e.message); return []; });
ac.innerHTML = list.map(c => `<div data-cc="${esc(c.cardCode)}" data-n="${esc(c.cardName)}">${esc(c.cardName)} <span class="sc-muted">${esc(c.cardCode)}</span></div>`).join('') || '<div class="sc-muted">No match</div>'; ac.style.display = 'block';
qa('[data-cc]', ac).forEach(d => { d.onclick = async () => { ac.style.display = 'none'; q('#s-cust', R).value = d.textContent; S.cust = { cardCode: d.dataset.cc, cardName: d.dataset.n };
try { const c = await api('/customers/' + encodeURIComponent(d.dataset.cc)); const a = c.shipTo[0] || c.billTo[0]; if (a && !q('#s-addr', R).value) q('#s-addr', R).value = a.text; } catch (_e) {} }; }); }, 300); };
// Product list = only the selected category's products (server-filtered).
let lastDiv = ex ? String(ex.divisionId) : '';
const loadCart = async () => { const div = q('#s-div', R).value;
if (!div) { S.cart = null; q('#s-cart', R).className = 'sc-muted'; q('#s-cart', R).textContent = 'Select a product category.'; return; }
q('#s-cart', R).innerHTML = '<span class="sc-spin"></span>';
const prods = await api('/products?divisionId=' + div); q('#s-cart', R).className = '';
S.cart = SC.cartEditor(q('#s-cart', R), prods, { lines: ex ? ex.items.map(i => ({ key: i.productId ? `P:${i.productId}` : `S:${i.itemCode}`, qty: i.qty })) : [], settings: {}, categoryName: divName(+div) }); };
q('#s-div', R).onchange = async () => {
if (S.cart && S.cart.count() && !(await SC.confirmBox('Change product category?', 'Products already added will be removed.', 'Change category', 'sc-b-amber'))) { q('#s-div', R).value = lastDiv; return; }
lastDiv = q('#s-div', R).value; loadCart(); };
if (ex) loadCart();
}
// ── Export ──────────────────────────────────────────────────────────────────
async function openExport(id) {
let e; try { e = await api('/export-samples/' + id); } catch (er) { return toast(er.message); }
const A = e.actions;
const m = SC.modal({ title: `Export sample ${esc(e.refNo)} — ${esc(e.custName)}`, wide: true, body: `
<div class="sc-grid"><div class="sc-kv"><b>Status</b>${SC.pill(ECOL[e.status], e.statusLabel)}</div><div class="sc-kv"><b>Customer</b><span>${esc(e.custName)}</span></div>
<div class="sc-kv"><b>Country / region</b><span>${esc(e.country)} ${e.region ? '· ' + esc(e.region) : ''}</span></div><div class="sc-kv"><b>Sample against</b><span>${esc(e.sampleAgainst || '—')}</span></div>
<div class="sc-kv"><b>Contact</b><span>${esc(e.contactPerson)}<br>${esc(e.contactEmail)}<br>${esc(e.contactNo)}</span></div>
<div class="sc-kv"><b>Courier</b><span>${e.courierByCustomer ? 'Customer courier account: ' + esc(e.accountDetails) : 'MIPL courier'}</span></div>
<div class="sc-kv"><b>Request date</b><span>${date(e.reqDate)}</span></div><div class="sc-kv"><b>Expected delivery</b><span>${date(e.deliveryDate)}</span></div>
<div class="sc-kv"><b>Raised by</b><span>${esc(e.createdByName)} · ${dt(e.createdAt)}</span></div>
${e.courierNo ? `<div class="sc-kv"><b>Dispatch</b><span>${esc(e.challanNo)} ${date(e.challanDate)} · AWB ${esc(e.courierNo)}</span></div>` : ''}</div>
<div class="sc-kv" style="margin-top:8px"><b>Shipping address</b><span>${esc(e.shippingAddress)}</span></div>
<div class="sc-row" style="margin-top:8px"><div><label class="sc-lbl">Remarks for QA</label><div class="sc-remarks">${esc(e.remarksQa || '—')}</div></div><div><label class="sc-lbl">Remarks for logistics</label><div class="sc-remarks">${esc(e.remarksLogistics || '—')}</div></div></div>
<div class="sc-sec">Items</div>${itemsTable(e.items, A.includes('stock'))}
${e.docs.length ? `<div class="sc-sec">Documents</div>${e.docs.map(d => `<div><span class="sc-link" data-doc="${d.id}">📄 ${esc(d.title || d.origName)}</span> <span class="sc-muted">${esc(d.docType)} · ${dt(d.createdAt)}</span></div>`).join('')}` : ''}
${A.includes('stock') ? `<div class="sc-sec">Stock</div><div class="sc-row"><div><label class="sc-lbl">Stock available?</label><select class="sc-in" id="e-stock"><option value="">— Select —</option><option value="1">Yes — QA verification</option><option value="0">No — production</option></select></div>
<div><label class="sc-lbl">Expected delivery date</label><input class="sc-in" type="date" id="e-dd" value="${SC.iso(e.deliveryDate)}"></div></div>` : ''}
${A.includes('dispatch') ? `<div class="sc-sec">Dispatch</div><div class="sc-row"><div><label class="sc-lbl">Invoice / challan no.</label><input class="sc-in" id="e-ch"></div><div><label class="sc-lbl">Date</label><input class="sc-in" type="date" id="e-cd" value="${SC.today()}"></div>
<div><label class="sc-lbl">Courier / AWB no. *</label><input class="sc-in" id="e-co"></div></div><div style="margin-top:8px"><label class="sc-lbl">Dispatch details</label><input class="sc-in" id="e-di"></div>
<div class="sc-row" style="margin-top:8px"><div><label class="sc-lbl">Invoice copy</label><input class="sc-in" type="file" id="e-inv" accept=".pdf,.jpg,.jpeg,.png"></div><div><label class="sc-lbl">Courier receipt</label><input class="sc-in" type="file" id="e-cour" accept=".pdf,.jpg,.jpeg,.png"></div></div>` : ''}
<div class="sc-sec">History</div>${logHtml(e.log, ME.exportStatusLabels)}`,
buttons: [{ label: 'Close' }, ...(A.includes('edit') ? [{ label: 'Edit', onClick: () => exportForm(e) }] : []),
...(A.includes('stock') || A.includes('dispatch') ? [{ label: 'Submit', cls: 'sc-b-primary', onClick: async ({ root }) => {
let body;
if (A.includes('stock')) body = { action: 'stock', stockAvailable: q('#e-stock', root).value, deliveryDate: q('#e-dd', root).value, batchNos: batchMap(root) };
else {
for (const [sel, type, title] of [['#e-inv', 'invoice', 'Invoice copy'], ['#e-cour', 'courier', 'Courier receipt']]) { const f = q(sel, root).files[0]; if (f) await SC.upload(f, { entity: 'export_sample', entityId: e.id, docType: type, title }); }
body = { action: 'dispatch', challanNo: q('#e-ch', root).value, challanDate: q('#e-cd', root).value, courierNo: q('#e-co', root).value, dispatchDetails: q('#e-di', root).value };
}
const r = await api(`/export-samples/${e.id}/action`, { method: 'POST', body }); toast(`${r.refNo}: ${r.statusLabel}`, 'ok'); load(); } }] : [])] });
qa('[data-doc]', m.root).forEach(el => { el.onclick = () => SC.openFile('/docs/' + el.dataset.doc); });
}
async function exportForm(ex) {
const prods = await api('/products?catalog=export');
const v = f => esc(ex ? ex[f] : '');
let cart;
const m = SC.modal({ title: ex ? `Edit export sample ${esc(ex.refNo)}` : 'New export sample request', wide: true, body: `
<div class="sc-row"><div><label class="sc-lbl">Customer name *</label><input class="sc-in" id="x-custName" value="${v('custName')}"></div><div><label class="sc-lbl">Country *</label><input class="sc-in" id="x-country" value="${v('country')}"></div>
<div><label class="sc-lbl">Region</label><input class="sc-in" id="x-region" value="${v('region')}"></div><div><label class="sc-lbl">Sample against</label><input class="sc-in" id="x-sampleAgainst" value="${v('sampleAgainst')}" placeholder="Tender / enquiry ref."></div></div>
<div style="margin-top:8px"><label class="sc-lbl">Shipping address *</label><textarea class="sc-in" id="x-shippingAddress">${v('shippingAddress')}</textarea></div>
<div class="sc-row" style="margin-top:8px"><div><label class="sc-lbl">Contact person *</label><input class="sc-in" id="x-contactPerson" value="${v('contactPerson')}"></div>
<div><label class="sc-lbl">Contact email *</label><input class="sc-in" id="x-contactEmail" value="${v('contactEmail')}"></div><div><label class="sc-lbl">Contact no. *</label><input class="sc-in" id="x-contactNo" value="${v('contactNo')}"></div></div>
<div class="sc-row" style="margin-top:8px"><div><label class="sc-lbl">Courier</label><select class="sc-in" id="x-cbc"><option value="0">MIPL courier</option><option value="1" ${ex && ex.courierByCustomer ? 'selected' : ''}>Customer's courier account</option></select></div>
<div><label class="sc-lbl">Courier account details</label><input class="sc-in" id="x-accountDetails" value="${v('accountDetails')}"></div><div><label class="sc-lbl">Required within (days)</label><input class="sc-in" type="number" id="x-days" value="${v('deliveryDays')}"></div></div>
<div class="sc-row" style="margin-top:8px"><div><label class="sc-lbl">Remarks for QA</label><textarea class="sc-in" id="x-remarksQa">${v('remarksQa')}</textarea></div><div><label class="sc-lbl">Remarks for logistics</label><textarea class="sc-in" id="x-remarksLogistics">${v('remarksLogistics')}</textarea></div></div>
<div class="sc-sec">Products (export catalog)</div><div id="x-cart"></div>
${ex ? '' : '<div style="margin-top:8px"><label class="sc-lbl">Attachment (enquiry / PI)</label><input class="sc-in" type="file" id="x-file" accept=".pdf,.jpg,.jpeg,.png"></div>'}`,
buttons: [{ label: 'Cancel' }, { label: ex ? 'Save' : 'Submit request', cls: 'sc-b-primary', onClick: async ({ root }) => {
const body = { id: ex ? ex.id : undefined, courierByCustomer: q('#x-cbc', root).value === '1', deliveryDays: q('#x-days', root).value, lines: cart.getLines() };
['custName', 'country', 'region', 'sampleAgainst', 'shippingAddress', 'contactPerson', 'contactEmail', 'contactNo', 'accountDetails', 'remarksQa', 'remarksLogistics'].forEach(f => { body[f] = q('#x-' + f, root).value; });
const res = await api('/export-samples', { method: 'POST', body });
const f = q('#x-file', root) && q('#x-file', root).files[0];
if (f) await SC.upload(f, { entity: 'export_sample', entityId: res, docType: 'enquiry', title: 'Enquiry / PI' }).catch(er => toast('Saved, but upload failed: ' + er.message));
toast('Export sample request saved', 'ok'); load(); } }] });
cart = SC.cartEditor(q('#x-cart', m.root), prods, { lines: ex ? ex.items.map(i => ({ key: `P:${i.productId}`, qty: i.qty })) : [], settings: {} });
}
init().catch(e => { q('#app').innerHTML = `<div class="sc-err">${esc(e.message)}</div>`; });
</script>
<script src="/sidebar.js?v=31"></script>
</body>
</html>