Files
sap-erp/public/receipts-history.html
T
John eead8f5ffd
SAP-ERP Portal CI/CD / build (push) Successful in 3m57s
sale order
2026-10-05 18:45:17 +05:30

190 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/auth-guard.js?v=4"></script>
<script src="/company-list.js"></script>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title>Receipts History — 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&family=Bebas+Neue&display=swap" rel="stylesheet"/>
<style>
:root{--ink:#080e18;--ink2:#0d1626;--ink3:#111f35;--purple:#9f5afd;--blue:#1a6fff;--amber:#f5a623;--green:#0dbb8a;--red:#e53e3e;--teal:#06b6d4;--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;--display:'Bebas Neue',sans-serif;--shadow:0 8px 32px rgba(0,0,0,.5)}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font);background:var(--ink);color:var(--text);min-height:100vh;-webkit-font-smoothing:antialiased}
nav{position:sticky;top:0;z-index:200;background:rgba(8,14,24,.92);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);padding:0 12px;height:50px;display:flex;align-items:center;justify-content:space-between;gap:6px}
.nav-logo{background:var(--green);color:#fff;font-family:var(--display);font-size:15px;letter-spacing:2px;padding:3px 8px;border-radius:5px}
.nav-links{display:flex;gap:2px;overflow-x:auto;flex:1;justify-content:center}
.nav-link{padding:4px 8px;border-radius:5px;font-size:10px;font-weight:600;color:var(--text2);text-decoration:none;white-space:nowrap;font-family:var(--font)}.nav-link:hover{color:var(--text);background:rgba(255,255,255,.05)}.nav-link.active{color:var(--green);background:rgba(13,187,138,.1)}
.btn-sm{padding:4px 8px;border-radius:5px;font-size:10px;font-weight:700;border:1px solid var(--border2);color:var(--text3);background:none;cursor:pointer;font-family:var(--font)}.btn-sm:hover{border-color:var(--red);color:var(--red)}
main{padding:12px;max-width:1100px;margin:0 auto}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:12px;overflow:hidden}
.card-hd{background:var(--surface3);padding:10px 14px;font-size:12px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--border)}
.card-hd .right{margin-left:auto;font-size:11px;color:var(--text3);font-weight:600}
.card-bd{padding:12px 14px}
.fg{margin-bottom:10px}
.fl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:4px;display:block}
.fi{width:100%;padding:8px 10px;border:1.5px solid var(--border2);border-radius:7px;font-size:13px;font-family:var(--font);background:var(--surface2);color:var(--text);outline:none;height:36px}.fi:focus{border-color:var(--green);box-shadow:0 0 0 2px rgba(13,187,138,.15)}.fi::placeholder{color:var(--text3)}
select.fi{cursor:pointer;appearance:none;background:var(--surface2) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%233d5470'/%3E%3C/svg%3E") no-repeat right 10px center;padding-right:26px}
.fi-mono{font-family:var(--mono);font-size:12px}
.btn{padding:10px 20px;border-radius:8px;border:none;font-family:var(--font);font-size:13px;font-weight:700;cursor:pointer;transition:all .18s;display:inline-flex;align-items:center;gap:6px;justify-content:center}.btn:disabled{opacity:.4;cursor:not-allowed}
.empty-msg{text-align:center;padding:24px;color:var(--text3);font-size:12px}
.sp{width:14px;height:14px;border:2px solid rgba(255,255,255,.25);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;display:inline-block}
@keyframes spin{to{transform:rotate(360deg)}}
.toast-c{position:fixed;top:56px;right:10px;z-index:9999;display:flex;flex-direction:column;gap:6px;pointer-events:none}
.toast{padding:9px 13px;border-radius:8px;font-size:12px;font-weight:600;box-shadow:var(--shadow);max-width:280px}
.t-ok{background:var(--green);color:#fff}.t-err{background:var(--red);color:#fff}
/* Receipts table */
.rh-wrap{overflow-x:auto}
table.rh{width:100%;border-collapse:separate;border-spacing:0;font-size:11.5px;min-width:820px}
table.rh th{text-align:left;padding:8px 10px;font-size:9.5px;font-weight:700;text-transform:uppercase;color:var(--text3);border-bottom:2px solid var(--border2);white-space:nowrap}
table.rh td{padding:9px 10px;border-bottom:1px solid var(--border);vertical-align:middle}
table.rh tr:hover td{background:rgba(13,187,138,.05)}
.rh-mono{font-family:var(--mono)}
.rh-doc{font-family:var(--mono);font-weight:700;color:var(--green)}
.rh-pwo{font-family:var(--mono);color:var(--teal)}
.rh-item{font-family:var(--mono);color:var(--text)}
.rh-desc{color:var(--text2);max-width:280px;white-space:normal}
.tt-pill{font-size:9px;font-weight:700;padding:1px 6px;border-radius:3px}
.tt-c{background:rgba(13,187,138,.12);color:var(--green)}
.tt-r{background:rgba(229,62,62,.12);color:var(--red)}
</style>
<link rel="stylesheet" href="/theme.css?v=3"/>
</head>
<body>
<div class="toast-c" id="toast-c"></div>
<nav>
<span class="nav-logo">SAP</span>
<div class="nav-links">
<a href="/" class="nav-link">Home</a><a href="/production" class="nav-link">Production</a>
<a href="/issue-production" class="nav-link">Issue</a>
<a href="/receipt-production" class="nav-link">Receipt</a>
<a href="/receipts-history" class="nav-link active">Receipts History</a>
<a href="/close-production" class="nav-link">Close</a>
</div>
<button class="btn-sm" id="btn-logout">Logout</button>
</nav>
<main id="app"></main>
<script>
'use strict';
let _tok=null,_user=null;
const BE='/api';
(function(){try{_tok=sessionStorage.getItem('portal_token');const u=sessionStorage.getItem('portal_user');if(u)_user=JSON.parse(u);}catch(_e){}})();
if(!_tok)window.location.href='/';
document.getElementById('btn-logout').onclick=()=>{sessionStorage.clear();document.cookie='portal_token=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';document.cookie='portal_user=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';;localStorage.removeItem('token');localStorage.removeItem('portal_user');window.location.href='/';};
let _co=window.__DEFAULT_COMPANY__;
let _companies=[{value:_co,label:_co}];
function coOptions(sel){return _companies.map(c=>`<option value="${esc(c.value)}" ${sel===c.value?'selected':''}>${esc(c.label)}</option>`).join('');}
let _rows=[];
let _loading=false;
let _search='';
let _page=0,_pageSize=20,_total=null,_rawCount=0;
function esc(v){return(v||'').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function q(s){return document.querySelector(s);}
function mk(c){const d=document.createElement('div');d.className=c;return d;}
const _MON=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
function fmtDate(iso){if(!iso)return '—';const d=new Date(iso);if(isNaN(d))return '—';return `${String(d.getUTCDate()).padStart(2,'0')} ${_MON[d.getUTCMonth()]} ${d.getUTCFullYear()}`;}
function fmtQty(n){return(Math.round((parseFloat(n)||0)*1000)/1000).toString();}
async function api(m,p,b=null){
const h={'Content-Type':'application/json'};if(_tok)h['Authorization']='Bearer '+_tok;
const o={method:m.toUpperCase(),headers:h};if(b)o.body=JSON.stringify(b);
let r,d;try{r=await fetch(BE+p,o);d=await r.json();}catch(_e){throw new Error('Cannot reach server');}
if(r.status===401){window.location.href='/';throw new Error('Session expired');}
if(!r.ok)throw new Error(d?.message||'HTTP '+r.status);return d;
}
function toast(msg,type='ok'){const tc=document.getElementById('toast-c');const el=document.createElement('div');el.className=`toast t-${type}`;el.textContent=(type==='ok'?'OK ':'ERR ')+msg;tc.appendChild(el);setTimeout(()=>el.remove(),4500);}
async function load(resetPage){
if(resetPage)_page=0;
_loading=true;render();
const searchQ=_search.trim()?`&search=${encodeURIComponent(_search.trim())}`:'';
try{
const r=await api('GET',`/sap/receipts-history?top=${_pageSize}&skip=${_page*_pageSize}&company=${encodeURIComponent(_co)}${searchQ}`);
_rows=r.data||[];_rawCount=_rows.length;_total=(typeof r.total==='number')?r.total:null;
}catch(e){toast(e.message,'err');_rows=[];_rawCount=0;_total=null;}
_loading=false;render();
}
function hasNext(){ if(_total!=null) return (_page+1)*_pageSize<_total; return _rawCount>=_pageSize; }
function render(){try{_doRender();}catch(e){console.error(e);document.getElementById('app').innerHTML=`<div style="padding:20px;color:var(--red)">Error: ${e.message}</div>`;}}
function _doRender(){
const app=document.getElementById('app');app.innerHTML='';
const c1=mk('card');
c1.innerHTML=`<div class="card-hd"><span>Receipts History</span><span class="right">Posted Receipt from Production documents</span></div>
<div class="card-bd">
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
<div class="fg" style="margin:0;min-width:150px"><label class="fl">Company</label><select class="fi" id="sel-co">${coOptions(_co)}</select></div>
<div class="fg" style="margin:0;flex:1;min-width:240px"><label class="fl">Search</label>
<input class="fi fi-mono" id="rh-search" value="${esc(_search)}" placeholder="PWO No, receipt doc no, item code / description or batch…"/></div>
<button class="btn" id="rh-search-btn" style="background:var(--green);color:#fff;height:36px">Search</button>
</div>
<div style="font-size:10px;color:var(--text3);margin-top:6px">Newest first. Search matches PWO No, receipt document number, item code/description, or batch number.</div>
</div>`;
app.appendChild(c1);
const c2=mk('card');
let body;
if(_loading){
body=`<div class="empty-msg"><span class="sp" style="border-color:rgba(13,187,138,.25);border-top-color:var(--green)"></span> Loading receipts…</div>`;
}else if(!_rows.length){
body=`<div class="empty-msg">No receipts found${_search.trim()?` for "${esc(_search.trim())}"`:''}.</div>`;
}else{
const trs=_rows.map(r=>`<tr>
<td class="rh-doc">${esc(r.receiptDocNum)}</td>
<td class="rh-mono" style="white-space:nowrap">${fmtDate(r.docDate)}</td>
<td class="rh-pwo">${esc(r.pwoNum||r.pwoAbsEntry||'-')}</td>
<td class="rh-item">${esc(r.itemCode)}</td>
<td class="rh-desc">${esc(r.description||'-')}</td>
<td class="rh-mono" style="text-align:right">${fmtQty(r.quantity)}</td>
<td><span class="tt-pill ${r.transType==='R'?'tt-r':'tt-c'}">${r.transType==='R'?'REJECT':'COMPLETE'}</span></td>
<td class="rh-mono">${esc(r.warehouse||'-')}</td>
<td class="rh-mono" style="color:var(--amber)">${esc(r.batch||'-')}</td>
</tr>`).join('');
body=`<div class="rh-wrap"><table class="rh">
<thead><tr>
<th>Receipt Doc No</th><th>Date</th><th>PWO No</th><th>Item</th><th>Description</th>
<th style="text-align:right">Qty</th><th>Type</th><th>Whs</th><th>Batch</th>
</tr></thead>
<tbody>${trs}</tbody>
</table></div>`;
}
const totalPages=_total!=null?Math.max(1,Math.ceil(_total/_pageSize)):null;
const pager=_loading?'':`<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px">
<button class="btn" id="rh-prev" ${_page===0?'disabled':''} style="padding:6px 14px;font-size:11px;background:var(--surface2);color:var(--text2);border:1px solid var(--border2)${_page===0?';opacity:.45;cursor:not-allowed':''}">‹ Prev</button>
<span style="font-size:11px;color:var(--text3)">${_total!=null?`Page ${_page+1} of ${totalPages} · ${_total} line${_total===1?'':'s'}`:`Page ${_page+1}`}</span>
<button class="btn" id="rh-next" ${hasNext()?'':'disabled'} style="padding:6px 14px;font-size:11px;background:var(--surface2);color:var(--text2);border:1px solid var(--border2)${hasNext()?'':';opacity:.45;cursor:not-allowed'}">Next ›</button>
</div>`;
c2.innerHTML=`<div class="card-hd"><span>Receipts</span><span class="right">${_rows.length} on this page</span></div><div class="card-bd">${body}${pager}</div>`;
app.appendChild(c2);
setTimeout(()=>{
q('#sel-co')&&(q('#sel-co').onchange=function(){_co=this.value;load(true);});
q('#rh-search')&&(q('#rh-search').oninput=function(){_search=this.value;});
q('#rh-search')&&(q('#rh-search').onkeydown=function(e){if(e.key==='Enter'){e.preventDefault();load(true);}});
q('#rh-search-btn')&&(q('#rh-search-btn').onclick=()=>load(true));
q('#rh-prev')&&(q('#rh-prev').onclick=()=>{if(_page>0){_page--;load();}});
q('#rh-next')&&(q('#rh-next').onclick=()=>{if(hasNext()){_page++;load();}});
},0);
}
async function init(){
try{
const hash=window.location.hash;
const coMatch=hash.match(/company=([^&]+)/);
if(coMatch)_co=decodeURIComponent(coMatch[1]);
try{const list=await window.fetchCompanyList();if(list.length)_companies=list;}catch(_e){}
render();
load();
}catch(e){console.error('Init error:',e);render();}
}
init();
</script>
<script src="/sidebar.js?v=31"></script>
</body>
</html>