+96
-26
@@ -200,8 +200,8 @@ let _devScrapWh=''; // Admin → System Settings → Deviation Scrap Wareho
|
||||
let _devShowRemoveOldLineBtn=true; // Admin → System Settings → Deviation "Show Remove from SAP Button" (fetched once)
|
||||
let _devOpen=false;
|
||||
let _devType='SHORTAGE';
|
||||
let _devEnabledTypes=['SHORTAGE','SUBSTITUTION','DAMAGE']; // Admin → System Settings → Deviation Enabled Types (fetched once)
|
||||
const _devTypeLabels={SHORTAGE:'Shortage — top up qty of the same item',SUBSTITUTION:'Substitution — issue a different item instead',DAMAGE:'Damage/Loss — write off a damaged qty'};
|
||||
let _devEnabledTypes=['SHORTAGE','SUBSTITUTION','DAMAGE','ADDITION']; // Admin → System Settings → Deviation Enabled Types (fetched once)
|
||||
const _devTypeLabels={SHORTAGE:'Shortage — top up qty of the same item',SUBSTITUTION:'Substitution — issue a different item instead',DAMAGE:'Damage/Loss — write off a damaged qty',ADDITION:'Addition — add a new item/component'};
|
||||
let _devItemCode='';
|
||||
let _devNewItemCode='',_devNewItemName='';
|
||||
let _devWarehouse='';
|
||||
@@ -261,6 +261,11 @@ const CONSUMABLE_ONLY_USER=CAN_ANY_CONSUMABLE&&!(CAN_CREATE_PO||CAN_MANUAL_CREAT
|
||||
// bypassing every normal gate below (step assignment, full-issuance,
|
||||
// full-qty, sequencing) — a deliberate override for these two roles only.
|
||||
const IS_PO_ADMIN_OVERRIDE=!!(_user&&['admin','system_admin'].includes(_user.role));
|
||||
// Previously Cancel had NO assignable step at all — admin/system_admin
|
||||
// only. Now also grantable to a regular user via 'approve' on this step
|
||||
// (Admin → Edit User → Approval Steps), same shape as every other
|
||||
// production_order permission.
|
||||
const CAN_CANCEL_PO=hasStepPerm('production_order:cancel','approve');
|
||||
const CAN_RAISE_DEVIATION=hasStepPerm('production_order:deviation','add');
|
||||
const CAN_APPROVE_DEVIATION=hasStepPerm('production_order:deviation','approve');
|
||||
const CAN_VIEW_DEVIATION=hasStepAssigned('production_order:deviation');
|
||||
@@ -281,6 +286,7 @@ let _orders=[];
|
||||
let _ordersEverLoaded=false;
|
||||
let _viewLoading=false;
|
||||
let _viewFilter='';
|
||||
let _viewDateFrom='',_viewDateTo=''; // filters the loaded list by Due Date — client-side, same scope as the search box above
|
||||
let _viewOrderType=CONSUMABLE_ONLY_USER?'consumable':''; // '' = all, 'consumable' = Consumable Orders only, 'regular' = everything else
|
||||
let _viewSearch='';
|
||||
let _page=0; // View Orders pagination (0-based)
|
||||
@@ -604,7 +610,7 @@ function formCardsHtml(){
|
||||
if(_bomLoading)ch='<div class="bom-msg"><span class="sp" style="border-color:rgba(159,90,253,.25);border-top-color:var(--purple)"></span> Loading BOM...</div>';
|
||||
else if(!_lines.length)ch=`<div class="bom-msg">${_hdr.type==='bopotSpecial'?'Add components manually using the button below':'Select a product to auto-load BOM components'}</div>`;
|
||||
else ch=`<div class="tbl-wrap"><table class="comp-tbl">
|
||||
<thead><tr><th style="width:28px">#</th><th>Item</th><th style="width:100px">Base Qty</th><th style="width:110px">Planned Qty</th><th style="width:90px">Available</th><th style="width:60px"></th></tr></thead>
|
||||
<thead><tr><th style="width:28px">#</th><th>Item</th><th style="width:100px">Base Qty</th><th style="width:110px">Planned Qty</th><th style="width:200px">Warehouse</th><th style="width:90px">Available</th><th style="width:60px"></th></tr></thead>
|
||||
<tbody>${_lines.map((l,i)=>{
|
||||
const isManual=!l.itemNo;
|
||||
const avail=l.itemNo&&l.type!=='Resource'?stockFor(l):null;
|
||||
@@ -618,6 +624,7 @@ function formCardsHtml(){
|
||||
:`<span class="comp-type ${l.type==='Resource'?'resource':'item'}">${l.type}</span> <span class="comp-item">${esc(l.itemNo)}</span><div class="comp-desc">${esc(l.desc)}</div>`}</td>
|
||||
<td>${l.itemNo?`<input class="comp-inp" data-i="${i}" data-f="baseQty" type="number" min="0" step="0.001" value="${l.baseQty}" style="width:90px"/>`:''}</td>
|
||||
<td>${l.itemNo?`<input class="comp-inp" data-i="${i}" data-f="plannedQty" type="number" min="0" step="0.001" value="${l.plannedQty}" style="width:100px"/>`:''}</td>
|
||||
<td>${l.type==='Resource'?'':`<select class="comp-inp" data-i="${i}" data-f="warehouse" style="width:100%;min-width:140px"><option value="" ${l.warehouse?'':'selected'}>— Select —</option>${_wh.map(w=>`<option value="${esc(w.code)}" ${l.warehouse===w.code?'selected':''}>${esc(w.code)} — ${esc(w.name||'')}</option>`).join('')}</select>`}</td>
|
||||
<td style="text-align:right">${availCell}</td>
|
||||
<td style="text-align:right"><button class="btn" data-rm="${i}" style="padding:3px 10px;font-size:10px;background:rgba(229,62,62,.08);color:var(--red);border:1px solid rgba(229,62,62,.25);border-radius:5px">Remove</button></td>
|
||||
</tr>`;
|
||||
@@ -760,6 +767,13 @@ function renderView(app){
|
||||
<button class="btn" id="v-load" style="padding:6px 16px;font-size:11px;background:var(--purple);color:#fff;white-space:nowrap">Load</button>
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--text3);margin:-6px 0 10px">Type to filter loaded orders, or press <b>Load</b> / Enter to search all production orders on the server.</div>
|
||||
<div class="fg" style="display:flex;gap:6px;align-items:center;margin-bottom:8px">
|
||||
<span style="font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.4px">Due Date</span>
|
||||
<input class="fi" id="v-datefrom" type="date" value="${esc(_viewDateFrom)}" style="font-size:12px;height:30px"/>
|
||||
<span style="font-size:11px;color:var(--text3)">to</span>
|
||||
<input class="fi" id="v-dateto" type="date" value="${esc(_viewDateTo)}" style="font-size:12px;height:30px"/>
|
||||
${(_viewDateFrom||_viewDateTo)?`<button class="btn b-cancel" id="v-dateclear" style="padding:5px 10px;font-size:11px">✕ Clear</button>`:''}
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;margin-bottom:8px;flex-wrap:wrap">
|
||||
${['','Planned','Released','Closed'].map(s=>`<button class="btn" style="padding:6px 12px;font-size:11px;${_viewFilter===s?'background:var(--purple);color:#fff':'background:var(--surface2);color:var(--text3);border:1px solid var(--border2)'}" data-vf="${s}">${s||'All'}</button>`).join('')}
|
||||
<button class="btn b-view" style="margin-left:auto;padding:6px 12px;font-size:11px" id="v-refresh">Refresh</button>
|
||||
@@ -778,6 +792,9 @@ function renderView(app){
|
||||
q('#v-co').onchange=function(){_co=this.value;loadOrders(true);};
|
||||
q('#v-refresh').onclick=()=>loadOrders();
|
||||
q('#v-search').oninput=function(){_viewSearch=this.value;const el=q('#v-list');if(el)el.innerHTML=renderOrderList();wirePoCards();};
|
||||
q('#v-datefrom').onchange=function(){_viewDateFrom=this.value;render();};
|
||||
q('#v-dateto').onchange=function(){_viewDateTo=this.value;render();};
|
||||
if(q('#v-dateclear'))q('#v-dateclear').onclick=function(){_viewDateFrom='';_viewDateTo='';render();};
|
||||
// Enter or the Load button runs a server-side search across ALL orders
|
||||
// (resets to the first page), not just the ones already loaded.
|
||||
q('#v-search').onkeydown=function(e){if(e.key==='Enter'){e.preventDefault();loadOrders(true);}};
|
||||
@@ -862,7 +879,25 @@ function renderOrderList(){
|
||||
let list=_orders;
|
||||
if(_viewSearch.trim()){
|
||||
const s=_viewSearch.trim().toUpperCase();
|
||||
list=list.filter(o=>(o.ItemNo||'').toUpperCase().includes(s)||(o.ProductDescription||'').toUpperCase().includes(s)||(String(o.DocumentNumber)||'').includes(s)||(String(o.AbsoluteEntry)||'').includes(s)||(o.WoNo||'').toUpperCase().includes(s)||(o.BatchNumber||'').toUpperCase().includes(s));
|
||||
// Bidirectional on WoNo/BatchNumber: after Enter/Load triggers a SERVER
|
||||
// search (which matches the fuller OWOR.Comments text, e.g. "SOL
|
||||
// BB2609327 CPDA"), this same client-side re-filter runs again over the
|
||||
// now-narrowed _orders — a plain field.includes(s) would immediately
|
||||
// drop that just-fetched match again, since the clean BatchNumber shown
|
||||
// ("BB2609327") doesn't contain the longer typed text. s.includes(field)
|
||||
// covers that case while field.includes(s) still covers the normal
|
||||
// "type a partial batch/WO no. to filter the loaded list" case.
|
||||
const bidi=(field,s)=>{ field=(field||'').toUpperCase(); return !!field&&(field.includes(s)||s.includes(field)); };
|
||||
list=list.filter(o=>(o.ItemNo||'').toUpperCase().includes(s)||(o.ProductDescription||'').toUpperCase().includes(s)||(String(o.DocumentNumber)||'').includes(s)||(String(o.AbsoluteEntry)||'').includes(s)||bidi(o.WoNo,s)||bidi(o.BatchNumber,s));
|
||||
}
|
||||
if(_viewDateFrom||_viewDateTo){
|
||||
list=list.filter(o=>{
|
||||
if(!o.DueDate)return false;
|
||||
const d=o.DueDate.slice(0,10);
|
||||
if(_viewDateFrom&&d<_viewDateFrom)return false;
|
||||
if(_viewDateTo&&d>_viewDateTo)return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if(!list.length)return'<div class="po-empty">No production orders found</div>';
|
||||
return `<div class="po-grid">${list.map(o=>{
|
||||
@@ -1187,7 +1222,7 @@ async function openDetail(entry){
|
||||
const isOverride=!normalCloseOk&&(consumableCloseOk||IS_PO_ADMIN_OVERRIDE);
|
||||
return `<div style="margin-top:8px"><button class="btn" style="width:100%;background:rgba(245,166,35,.1);color:var(--amber);border:1px solid rgba(245,166,35,.3)" id="m-close-order">Close Order${isOverride?' (Override)':''}</button></div>`;
|
||||
})()}
|
||||
${(st==='Planned'||st==='Released')&&IS_PO_ADMIN_OVERRIDE?`<div style="margin-top:8px"><button class="btn" style="width:100%;background:rgba(229,62,62,.08);color:var(--red);border:1px solid rgba(229,62,62,.25)" id="m-cancel-order">✕ Cancel Order (Admin override)</button></div>`:''}
|
||||
${(st==='Planned'||st==='Released')&&(IS_PO_ADMIN_OVERRIDE||CAN_CANCEL_PO)?`<div style="margin-top:8px"><button class="btn" style="width:100%;background:rgba(229,62,62,.08);color:var(--red);border:1px solid rgba(229,62,62,.25)" id="m-cancel-order">✕ Cancel Order${IS_PO_ADMIN_OVERRIDE?' (Admin override)':''}</button></div>`:''}
|
||||
${(d.CompletedQuantity||0)>=(d.PlannedQuantity||1)?`<div class="alert al-green" style="margin-top:8px">Receipt complete — all ${d.PlannedQuantity} qty received.</div>`:''}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -1270,8 +1305,8 @@ async function loadDevList(entry){
|
||||
try{const r=await api('GET',`/sap/deviations?sapAbsEntry=${entry}&company=${encodeURIComponent(_co)}`);_devList=r.data||[];}
|
||||
catch(_e){_devList=[];}
|
||||
}
|
||||
function devTypeLabel(t){return t==='SHORTAGE'?'Shortage':t==='SUBSTITUTION'?'Substitution':t==='DAMAGE'?'Damage/Loss':t;}
|
||||
function devTypeColor(t){return t==='SHORTAGE'?'var(--amber)':t==='SUBSTITUTION'?'var(--extra)':'var(--extrasevere)';}
|
||||
function devTypeLabel(t){return t==='SHORTAGE'?'Shortage':t==='SUBSTITUTION'?'Substitution':t==='DAMAGE'?'Damage/Loss':t==='ADDITION'?'Addition':t;}
|
||||
function devTypeColor(t){return t==='SHORTAGE'?'var(--amber)':t==='SUBSTITUTION'?'var(--extra)':t==='ADDITION'?'var(--green)':'var(--extrasevere)';}
|
||||
function fmtDevDate(v){if(!v)return'—';try{return new Date(v).toLocaleString('en-IN',{day:'2-digit',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});}catch{return v;}}
|
||||
|
||||
function renderDevSection(){
|
||||
@@ -1283,7 +1318,7 @@ function renderDevSection(){
|
||||
dv.qaStatus==='PENDING'?'<span style="font-size:8.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:rgba(245,166,35,.15);color:var(--amber)">QA PENDING</span>'
|
||||
:dv.qaStatus==='APPROVED'?'<span style="font-size:8.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:rgba(13,187,138,.15);color:var(--green)">QA APPROVED</span>'
|
||||
:'<span style="font-size:8.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:rgba(229,62,62,.15);color:var(--red)">QA REJECTED</span>');
|
||||
const deferrable=dv.type==='SHORTAGE'||dv.type==='SUBSTITUTION';
|
||||
const deferrable=dv.type==='SHORTAGE'||dv.type==='SUBSTITUTION'||dv.type==='ADDITION';
|
||||
let postedTxt;
|
||||
if(dv.postedToSap) postedTxt=`<span style="color:var(--green)">✓ Posted — Doc #${esc(String(dv.sapDocNumPosted||dv.sapDocEntry||''))}</span>`;
|
||||
else if(deferrable&&dv.qaStatus==='PENDING') postedTxt=`<span style="color:var(--text3)">⏳ Pending QA approval — nothing added to the Production Order yet</span>`;
|
||||
@@ -1483,8 +1518,14 @@ function closeDevModal(){document.getElementById('dev-modal').classList.remove('
|
||||
// whenever the item, substitute item, warehouse, or quantity changes — SAP
|
||||
// rejects a batch-managed item's issue/transfer with -4014 "Cannot add row
|
||||
// without complete selection of batch/serial numbers" if none is supplied.
|
||||
// Substitution and Addition both work off the SAME "search and pick a not-
|
||||
// currently-a-component item" widget (_devNewItemCode/_devNewItemName) —
|
||||
// Substitution just also has an "Affected Component" being replaced,
|
||||
// Addition doesn't. Kept as one helper so every place that needs to branch
|
||||
// on this stays in sync.
|
||||
function devUsesNewItemPicker(){ return _devType==='SUBSTITUTION'||_devType==='ADDITION'; }
|
||||
async function loadDevBatches(){
|
||||
const itemCode=_devType==='SUBSTITUTION'?_devNewItemCode:_devItemCode;
|
||||
const itemCode=devUsesNewItemPicker()?_devNewItemCode:_devItemCode;
|
||||
_devBatches=[];_devSelectedBatches=[];
|
||||
if(!itemCode||!_devWarehouse)return;
|
||||
try{
|
||||
@@ -1522,11 +1563,12 @@ function renderDevModal(){
|
||||
${_devEnabledTypes.map(t=>`<option value="${t}" ${_devType===t?'selected':''}>${esc(_devTypeLabels[t]||t)}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
${_devType==='ADDITION'?'':`
|
||||
<div class="fg"><label class="fl">${_devType==='DAMAGE'?'Damaged Component':'Affected Component'}</label>
|
||||
<select class="fi" id="dev-item">${devComponentOptions(_devItemCode)}</select>
|
||||
</div>
|
||||
${_devType==='SUBSTITUTION'?`
|
||||
<div class="fg" style="position:relative"><label class="fl">Substitute Item</label>
|
||||
</div>`}
|
||||
${devUsesNewItemPicker()?`
|
||||
<div class="fg" style="position:relative"><label class="fl">${_devType==='ADDITION'?'New Item':'Substitute Item'}</label>
|
||||
<input class="fi" id="dev-newitem-search" placeholder="Search item code / name…" value="${_devNewItemCode?esc(_devNewItemCode+' — '+_devNewItemName):''}" autocomplete="off"/>
|
||||
<div id="dev-newitem-dd" class="sd" style="display:none;position:absolute;left:0;right:0;top:100%;z-index:20;max-height:200px;overflow-y:auto;background:var(--ink2);border:1px solid var(--border2);border-radius:8px;margin-top:2px"></div>
|
||||
</div>`:''}
|
||||
@@ -1588,8 +1630,18 @@ function refreshDevBatchUI(){
|
||||
function wireDevModal(){
|
||||
document.getElementById('dev-modal-x').onclick=closeDevModal;
|
||||
document.getElementById('dev-cancel').onclick=closeDevModal;
|
||||
document.getElementById('dev-type').onchange=async function(){_devType=this.value;await loadDevBatches();renderDevModal();};
|
||||
document.getElementById('dev-item').onchange=async function(){
|
||||
document.getElementById('dev-type').onchange=async function(){
|
||||
_devType=this.value;
|
||||
// Switching type changes which item field(s) are even shown — stale
|
||||
// picks from the previous type would otherwise silently carry over
|
||||
// (e.g. an "Affected Component" picked under Shortage staying set, but
|
||||
// invisible, after switching to Addition).
|
||||
_devItemCode='';_devNewItemCode='';_devNewItemName='';_devQty='';
|
||||
await loadDevBatches();
|
||||
renderDevModal();
|
||||
};
|
||||
const itemSel=document.getElementById('dev-item');
|
||||
if(itemSel)itemSel.onchange=async function(){
|
||||
_devItemCode=this.value;
|
||||
const opt=this.options[this.selectedIndex];
|
||||
if(opt&&opt.dataset.wh)_devWarehouse=opt.dataset.wh;
|
||||
@@ -1648,12 +1700,15 @@ function wireDevModal(){
|
||||
}
|
||||
async function submitDeviation(){
|
||||
if(!_devOrder)return;
|
||||
if(!_devItemCode){toast('Select the affected component','err');return;}
|
||||
// Addition has no "Affected Component" at all — it's a brand-new item, so
|
||||
// its own itemCode comes from the New Item search picker instead (see
|
||||
// devUsesNewItemPicker()/the payload building below), never this dropdown.
|
||||
if(_devType!=='ADDITION'&&!_devItemCode){toast('Select the affected component','err');return;}
|
||||
const qty=parseFloat(_devQty)||0;
|
||||
if(qty<=0){toast('Quantity must be > 0','err');return;}
|
||||
if(!_devWarehouse){toast('Select a warehouse','err');return;}
|
||||
if(!_devReason.trim()){toast('A reason is required','err');return;}
|
||||
if(_devType==='SUBSTITUTION'&&!_devNewItemCode){toast('Search and pick a substitute item','err');return;}
|
||||
if(devUsesNewItemPicker()&&!_devNewItemCode){toast(_devType==='ADDITION'?'Search and pick the new item':'Search and pick a substitute item','err');return;}
|
||||
if(_devType==='DAMAGE'&&!_devDestWarehouse){toast('Select a scrap/destination warehouse','err');return;}
|
||||
// Item is batch-managed — the selected batches must add up to the full
|
||||
// quantity being posted (same rule as Issue for Production).
|
||||
@@ -1665,7 +1720,10 @@ async function submitDeviation(){
|
||||
const itemOpt=document.getElementById('dev-item');
|
||||
const selOpt=itemOpt?itemOpt.options[itemOpt.selectedIndex]:null;
|
||||
const lineNum=selOpt?selOpt.dataset.linenum:null;
|
||||
const itemName=(_devOrder.lines||[]).find(l=>l.ItemNo===_devItemCode)?.ItemName||'';
|
||||
// Addition's own item IS the New Item picker's pick — there's no existing
|
||||
// Production Order line to read a name off of, unlike every other type.
|
||||
const itemCode=_devType==='ADDITION'?_devNewItemCode:_devItemCode;
|
||||
const itemName=_devType==='ADDITION'?_devNewItemName:((_devOrder.lines||[]).find(l=>l.ItemNo===_devItemCode)?.ItemName||'');
|
||||
|
||||
if(!requireSapLogin())return;
|
||||
_devSubmitting=true;renderDevModal();
|
||||
@@ -1673,8 +1731,9 @@ async function submitDeviation(){
|
||||
const payload={
|
||||
company:_co, branchId:2,
|
||||
sapAbsEntry:_devOrder.entry, sapDocNum:_devOrder.docNum,
|
||||
type:_devType, itemCode:_devItemCode, itemName, lineNum,
|
||||
newItemCode:_devNewItemCode, newItemName:_devNewItemName,
|
||||
type:_devType, itemCode, itemName, lineNum,
|
||||
newItemCode:_devType==='SUBSTITUTION'?_devNewItemCode:undefined,
|
||||
newItemName:_devType==='SUBSTITUTION'?_devNewItemName:undefined,
|
||||
quantity:qty, warehouse:_devWarehouse,
|
||||
destWarehouse:_devType==='DAMAGE'?_devDestWarehouse:undefined,
|
||||
postToSap:_devType==='DAMAGE'?_devPostToSap:undefined,
|
||||
@@ -1701,7 +1760,7 @@ function wireSearch(){
|
||||
clearTimeout(_hst);
|
||||
_hst=setTimeout(async()=>{
|
||||
dd.innerHTML='<div class="sd-msg"><span class="sp" style="border-color:rgba(159,90,253,.25);border-top-color:var(--purple)"></span></div>';dd.style.display='block';
|
||||
const items=await fetchItems(v,false,_srcConsumable?CONSUMABLE_ITEM_GROUP:(_manualPoGroups.length?_manualPoGroups:null));
|
||||
const items=await fetchItems(v,true,_srcConsumable?CONSUMABLE_ITEM_GROUP:(_manualPoGroups.length?_manualPoGroups:null));
|
||||
if(!items.length){dd.innerHTML='<div class="sd-msg">No items</div>';return;}
|
||||
dd.innerHTML='';items.forEach(it=>{
|
||||
const r=document.createElement('div');r.className='sd-row';
|
||||
@@ -1918,7 +1977,7 @@ function wireComp(){
|
||||
// back to the top on every edit (confusing on a long component list).
|
||||
document.querySelectorAll('.comp-inp').forEach(el=>{el.onchange=function(){
|
||||
const i=parseInt(this.dataset.i),f=this.dataset.f;
|
||||
if(f==='warehouse'){_lines[i][f]=this.value;return;}
|
||||
if(f==='warehouse'){_lines[i][f]=this.value;loadStockForLines();return;}
|
||||
const val=parseFloat(this.value)||0;
|
||||
_lines[i][f]=val;
|
||||
const hq=_hdr.qty||1;
|
||||
@@ -1946,7 +2005,7 @@ function wireComp(){
|
||||
clearTimeout(_cst[i]);
|
||||
_cst[i]=setTimeout(async()=>{
|
||||
dd.innerHTML='<div class="sd-msg"><span class="sp" style="border-color:rgba(159,90,253,.25);border-top-color:var(--purple)"></span></div>';dd.style.display='block';
|
||||
const items=await fetchItems(v);
|
||||
const items=await fetchItems(v,true);
|
||||
if(!items.length){dd.innerHTML='<div class="sd-msg">No items</div>';return;}
|
||||
dd.innerHTML='';items.forEach(it=>{
|
||||
const r=document.createElement('div');r.className='sd-row';
|
||||
@@ -1992,7 +2051,18 @@ async function doPlanned(){
|
||||
if(_preWoReviewEnabled&&_srcWorkOrderId){await doStagePrePwo();return;}
|
||||
if(!requireSapLogin())return;
|
||||
const btn=q('#btn-planned'),sp=q('#sp-planned');btn.disabled=true;sp.style.display='inline-block';
|
||||
const payload={company:_co,ItemNo:_hdr.itemNo.trim().toUpperCase(),PlannedQuantity:parseFloat(_hdr.qty)||1,ProductionOrderStatus:'boposPlanned',ProductionOrderType:_hdr.type};
|
||||
// Captured ONCE and reused for both calls below (the SAP create, then the
|
||||
// local stage-tracking link) — _co is a live global the user can change
|
||||
// from the Company dropdown at any time, including during the first
|
||||
// call's network round trip. Reading it fresh for the second call let the
|
||||
// two writes land under DIFFERENT companies if the user switched company
|
||||
// mid-submit: the real SAP order under the old company, the local
|
||||
// ZPRODUCTION_ORDERS tracking row under the new one — invisible to every
|
||||
// company-scoped list/lookup afterward even though creation itself
|
||||
// "succeeded" (confirmed live: a Consumable Order whose local row ended up
|
||||
// in a different database than its real SAP document).
|
||||
const submitCo=_co;
|
||||
const payload={company:submitCo,ItemNo:_hdr.itemNo.trim().toUpperCase(),PlannedQuantity:parseFloat(_hdr.qty)||1,ProductionOrderStatus:'boposPlanned',ProductionOrderType:_hdr.type};
|
||||
// Tells the server which of the two Create permissions applies (from-WO
|
||||
// vs standalone) — stripped before the payload is forwarded to SAP.
|
||||
if(_srcWorkOrderId)payload.workOrderId=_srcWorkOrderId;
|
||||
@@ -2021,7 +2091,7 @@ async function doPlanned(){
|
||||
await api('POST','/production-orders',{
|
||||
workOrderId:_srcWorkOrderId||null, sapAbsEntry:_created.entry, sapDocNum:_created.docNum,
|
||||
itemCode:_hdr.itemNo, itemName:_hdr.desc, plannedQty:_hdr.qty,
|
||||
batchNumber:_srcBatchNumber, warehouse:_hdr.warehouse, company:_co,
|
||||
batchNumber:_srcBatchNumber, warehouse:_hdr.warehouse, company:submitCo,
|
||||
refWoNo:_refWoNo, refBatchNumber:_refBatchNumber,
|
||||
fromComponent:_srcFromComponent, refParentEntry:_srcFromComponentInfo?.entry||null,
|
||||
fromConsumable:_srcConsumable, department:_srcConsumable?_consumableDept:'',
|
||||
@@ -2289,7 +2359,7 @@ function wirePrePwoModal(){
|
||||
clearTimeout(_preWoSearchTimer[i]);
|
||||
_preWoSearchTimer[i]=setTimeout(async()=>{
|
||||
dd.innerHTML='<div class="sd-msg"><span class="sp" style="border-color:rgba(159,90,253,.25);border-top-color:var(--purple)"></span></div>';dd.style.display='block';
|
||||
const items=await fetchItems(v);
|
||||
const items=await fetchItems(v,true);
|
||||
if(!items.length){dd.innerHTML='<div class="sd-msg">No items</div>';return;}
|
||||
dd.innerHTML='';items.forEach(it=>{
|
||||
const r=document.createElement('div');r.className='sd-row';
|
||||
@@ -2339,6 +2409,6 @@ function resetForm(){_created=null;_preWoStaged=null;_lines=[];_hdr={itemNo:'',d
|
||||
|
||||
init();
|
||||
</script>
|
||||
<script src="/sidebar.js?v=25"></script>
|
||||
<script src="/sidebar.js?v=31"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user