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
+94 -13
View File
@@ -309,6 +309,7 @@ td select{appearance:none;cursor:pointer}
<option value="">All Status</option>
<option value="bost_Open">Open</option>
<option value="bost_Close">Closed</option>
<option value="drafts">Pending SAP Approval</option>
</select>
<button class="btn b-primary btn-sm" onclick="loadList()">Search</button>
</div>
@@ -438,7 +439,7 @@ let _page = 0;
const PAGE_SIZE = 20;
const ah = () => ({ Authorization: `Bearer ${_tok}` });
const esc = s => String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
const esc = s => String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
const dt = s => s ? s.slice(0,10) : '—';
// ── Toast ──────────────────────────────────────────────────────
@@ -545,9 +546,23 @@ function vendorAc(val){
const r = await fetch(`/api/sap/lookup/vendors?q=${encodeURIComponent(val)}&company=${_co}`, { headers: ah() });
const d = await r.json();
const items = d.data || [];
dd.innerHTML = items.length
? items.map(v => `<div class="sdd-row" onclick="pickVendor('${esc(v.CardCode)}','${esc(v.CardName.replace(/'/g,"\'"))}')"><span class="sdd-code">${esc(v.CardCode)}</span><span class="sdd-name">${esc(v.CardName)}</span></div>`).join('')
: '<div class="sdd-msg">No vendors found</div>';
// Built as real DOM elements with a bound click handler, not an
// inline onclick="..." string — a vendor name containing a quote
// character (e.g. an apostrophe) would otherwise terminate that
// string/attribute early and silently break the click entirely, same
// bug class as pickItem() below.
dd.innerHTML = '';
if (items.length) {
items.forEach(v => {
const row = document.createElement('div');
row.className = 'sdd-row';
row.innerHTML = `<span class="sdd-code">${esc(v.CardCode)}</span><span class="sdd-name">${esc(v.CardName)}</span>`;
row.onclick = () => pickVendor(v.CardCode, v.CardName);
dd.appendChild(row);
});
} else {
dd.innerHTML = '<div class="sdd-msg">No vendors found</div>';
}
dd.classList.add('open');
} catch {}
}, 300);
@@ -573,12 +588,26 @@ function itemAc(inp, lineId){
if (val.length < 2){ dd.classList.remove('open'); dd.innerHTML=''; return; }
_itemTimers[lineId] = setTimeout(async () => {
try {
const r = await fetch(`/api/sap/lookup/items?q=${encodeURIComponent(val)}&company=${_co}`, { headers: ah() });
const r = await fetch(`/api/sap/lookup/items?q=${encodeURIComponent(val)}&company=${_co}&activeOnly=1`, { headers: ah() });
const d = await r.json();
const items = d.data || [];
dd.innerHTML = items.length
? items.map(i => `<div class="sdd-row" onclick="pickItem(${lineId},'${esc(i.ItemCode)}','${esc((i.ItemName||'').replace(/'/g,"\'"))}','${esc(i.UoM||'')}','${esc(i.Warehouse||'')}')"><span class="sdd-code">${esc(i.ItemCode)}</span><span class="sdd-name">${esc(i.ItemName)}</span></div>`).join('')
: '<div class="sdd-msg">No items found</div>';
// Built as real DOM elements with a bound click handler, not an
// inline onclick="..." string — an item description containing a
// quote character (e.g. an inch mark, "NYLON RUBBER BAND 1"") would
// otherwise terminate that HTML attribute early, silently corrupting
// the click handler so picking the item did nothing at all.
dd.innerHTML = '';
if (items.length) {
items.forEach(i => {
const row = document.createElement('div');
row.className = 'sdd-row';
row.innerHTML = `<span class="sdd-code">${esc(i.ItemCode)}</span><span class="sdd-name">${esc(i.ItemName)}</span>`;
row.onclick = () => pickItem(lineId, i.ItemCode, i.ItemName || '', i.UoM || '', i.Warehouse || '');
dd.appendChild(row);
});
} else {
dd.innerHTML = '<div class="sdd-msg">No items found</div>';
}
dd.classList.add('open');
const rect = inp.getBoundingClientRect();
dd.style.top = (rect.bottom + window.scrollY + 2) + 'px';
@@ -615,7 +644,7 @@ function addLine(){
<div class="sdd item-dd" style="position:fixed;z-index:9999"></div>
</td>
<td><input class="desc-inp" type="text" placeholder="Description" readonly style="min-width:140px"></td>
<td><input class="rd-inp" type="date" value="${dueDate}" style="min-width:110px;font-size:10px;font-family:var(--mono)"></td>
<td><input class="rd-inp" type="date" value="${dueDate}" oninput="this.dataset.manual='1'" style="min-width:110px;font-size:10px;font-family:var(--mono)"></td>
<td class="nc"><input class="qty-inp" type="number" value="1" min="0" step="any" oninput="recalc()" style="width:65px"></td>
<td><input class="unit-inp" type="text" readonly style="width:45px;font-size:10px;color:var(--text3)"></td>
<td class="nc"><input class="price-inp" type="number" value="0" min="0" step="any" oninput="recalc()" style="width:90px"></td>
@@ -630,6 +659,15 @@ function addLine(){
recalc();
}
// Header "Required By" flows down to every line date the user hasn't set by hand
function syncLineDates(){
const due = document.getElementById('docDueDate').value;
if (!due) return;
document.querySelectorAll('#linesBody .rd-inp').forEach(inp => {
if (!inp.dataset.manual) inp.value = due;
});
}
function removeLine(id){ document.getElementById('lr-'+id)?.remove(); recalc(); }
function clearLines(){ document.getElementById('linesBody').innerHTML=''; _seq=0; recalc(); }
@@ -732,7 +770,7 @@ async function confirmApproval(){
});
const d = await r.json();
if (!r.ok || !d.success) throw new Error(d.message||'SAP error');
toast(`Purchase Request #${d.data?.DocEntry || ''} submitted — pending approval`, 'ok');
toast(`Purchase Request #${d.data?.DocEntry || ''} submitted. If it needed SAP approval it won't show under Open/Closed yet — check "Pending SAP Approval" in the status filter.`, 'ok');
resetForm();
_pendingPayload = null;
} catch(err){ toast(err.message,'err'); }
@@ -749,11 +787,20 @@ function debounceSearch(){
_srchTimer = setTimeout(() => { _page=0; loadList(); }, 400);
}
// A Purchase Request that triggers one of SAP's own Approval Procedures
// isn't created as a real PurchaseRequests document at all — DI API creates
// it as a SAP Draft instead, sitting there until an SAP approver acts on it
// (see "SAP Approval" for that). It has no DocumentStatus (Open/Closed), so
// it never showed up here at all — the requester submitted it and then had
// no way to even confirm it existed. "Pending SAP Approval" queries the
// dedicated GET /purchase-request/drafts endpoint instead of the normal
// PurchaseRequests list.
async function loadList(){
const wrap = document.getElementById('listWrap');
wrap.innerHTML = '<div class="pr-empty">Loading…</div>';
const q = document.getElementById('srchQ').value.trim();
const status = document.getElementById('srchStatus').value;
if (status === 'drafts'){ await loadDraftList(q); return; }
const skip = _page * PAGE_SIZE;
let url = `/api/sap/purchase-requests?company=${_co}&$top=${PAGE_SIZE}&$skip=${skip}`;
if (q) url += `&q=${encodeURIComponent(q)}`;
@@ -762,6 +809,7 @@ async function loadList(){
const r = await fetch(url, { headers: ah() });
const d = await r.json();
const rows = d.data || [];
const total = typeof d.total === 'number' ? d.total : null;
if (!rows.length){ wrap.innerHTML='<div class="pr-empty">No purchase requests found.</div>'; document.getElementById('pgBar').style.display='none'; return; }
wrap.innerHTML = `<div class="pr-list">${rows.map(pr => `
<div class="pr-card" onclick="openDoc(${pr.DocEntry})">
@@ -781,11 +829,43 @@ async function loadList(){
const pgBar = document.getElementById('pgBar');
pgBar.style.display = 'flex';
document.getElementById('pgPrev').disabled = _page === 0;
document.getElementById('pgNext').disabled = rows.length < PAGE_SIZE;
document.getElementById('pgInfo').textContent = `Page ${_page+1}`;
document.getElementById('pgNext').disabled = total!=null ? (_page+1)*PAGE_SIZE >= total : rows.length < PAGE_SIZE;
document.getElementById('pgInfo').textContent = total!=null
? `Page ${_page+1} of ${Math.max(1,Math.ceil(total/PAGE_SIZE))} · ${total} request${total===1?'':'s'}`
: `Page ${_page+1}`;
} catch(err){ wrap.innerHTML=`<div class="pr-empty" style="color:var(--red)">${esc(err.message)}</div>`; }
}
async function loadDraftList(q){
const wrap = document.getElementById('listWrap');
document.getElementById('pgBar').style.display = 'none';
try {
const r = await fetch(`/api/sap/purchase-request/drafts?company=${_co}`, { headers: ah() });
const d = await r.json();
let rows = d.data || [];
if (q){
const needle = q.toUpperCase();
rows = rows.filter(pr => [pr.CardCode, pr.CardName, pr.Comments].some(v => (v||'').toUpperCase().includes(needle)));
}
if (!rows.length){ wrap.innerHTML = '<div class="pr-empty">No purchase requests pending SAP approval.</div>'; return; }
wrap.innerHTML = `<div class="pr-list">${rows.map(pr => `
<div class="pr-card" style="cursor:default">
<div class="pr-card-top">
<span class="pr-docnum">Draft #${esc(String(pr.DocEntry))}</span>
<span class="badge s-draft">⏳ Pending SAP Approval</span>
</div>
<div class="pr-icon">📝</div>
${pr.Comments ? `<div class="pr-comment" title="${esc(pr.Comments)}">${esc(pr.Comments)}</div>` : ''}
<div class="pr-meta">
<span>Date <b>${dt(pr.DocDate)}</b></span>
<span>Required <b>${dt(pr.RequriedDate||pr.DocDueDate)}</b></span>
${pr.CardCode ? `<span>Vendor <b>${esc(pr.CardCode)}</b></span>` : ''}
${(pr.U_Depart||pr.U_Department) ? `<span>Dept <b>${esc(pr.U_Depart||pr.U_Department)}</b></span>` : ''}
</div>
</div>`).join('')}</div>`;
} catch(err){ wrap.innerHTML = `<div class="pr-empty" style="color:var(--red)">${esc(err.message)}</div>`; }
}
function pgMove(dir){ _page = Math.max(0, _page+dir); loadList(); }
function statusBadge(s){
@@ -901,12 +981,13 @@ async function docAction(action){
const today = new Date().toISOString().slice(0,10);
document.getElementById('docDate').value = today;
document.getElementById('docDueDate').value = today;
document.getElementById('docDueDate').addEventListener('change', syncLineDates);
await loadWarehouses();
await loadDepartments();
addLine();
})();
</script>
<script src="/sidebar.js?v=25"></script>
<script src="/sidebar.js?v=31"></script>
</body>
</html>