23 lines
8.1 KiB
JavaScript
23 lines
8.1 KiB
JavaScript
import {confirmation} from './sgf-stock-final.mjs';
|
|
const $=id=>document.getElementById(id),csrf=document.querySelector('meta[name="csrf-token"]').content,context=document.querySelector('meta[name="sgf-context"]').content;
|
|
let state,selected=null,page=1,preview=null,historyKey=null;
|
|
async function api(path,body){const r=await fetch(path,{credentials:'same-origin',headers:{Accept:'application/json','Content-Type':'application/json','X-CSRF-TOKEN':csrf,'X-SGF-Context':context},...(body?{method:'POST',body:JSON.stringify(body)}:{})});return {status:r.status,value:await r.json()};}
|
|
const op=confirmation(body=>api('/api/stock-completion/confirm',body));
|
|
function error(e){$('message').textContent=e.message;}
|
|
function el(tag,text,parent){const n=document.createElement(tag);if(text!==null)n.textContent=text;parent.append(n);return n;}
|
|
function locked(){const busy=Boolean(preview||op.pending);$('filters').disabled=busy;$('fields').disabled=busy;$('confirm').hidden=!preview||op.uncertain;$('retry').hidden=!op.uncertain;$('cancel').hidden=!preview||op.pending;}
|
|
function field(name,label,type='text',value='',required=true){const l=el('label',label,$('inputs')),i=el('input',null,l);i.id='f-'+name;i.type=type;i.required=required;i.value=value;if(type==='number'){i.min=0;i.step='0.001';}return i;}
|
|
function form(){if(!state)return;const a=$('action').value;$('inputs').replaceChildren();if(a==='save'&&$('family').value==='policy'){
|
|
const d=selected?.data??{};for(const [key,label,initial] of [['safety_milli','Segurança (UN)',2],['minimum_stock_milli','Mínimo físico (UN)',5],['target_milli','Alvo (UN)',10],['maximum_milli','Máximo (UN)',20],['daily_milli','Consumo diário conferido (UN)',1],['minimum_milli','Encomenda mínima (UN)',1],['pack_milli','Embalagem (UN)',1]])field(key,label,'number',d[key]===undefined?initial:d[key]/1000);
|
|
for(const [key,label,initial] of [['lead_days','Prazo de fornecimento (dias)',2],['approval_days','Prazo de aprovação (dias)',1],['horizon_days','Horizonte (dias)',14]]){const i=field(key,label,'number',d[key]??initial);i.step='1';}
|
|
field('basis','Evidência dos parâmetros','text',d.basis??'');
|
|
}else if(a==='save'){field('quantity','Quantidade a devolver (UN)','number',selected?.data?.quantity_milli?selected.data.quantity_milli/1000:'');field('reference','Referência da guia','text',selected?.data?.reference??'');}
|
|
else if(a==='execute'){field('event_on','Data efectiva da saída','date','');field('evidence','Comprovativo da saída','text','');}}
|
|
function choose(row){selected=row;$('selected').textContent=row?`Processo ${row.record_key}, versão ${row.version}, estado ${row.state}.`:'Novo processo: a origem é a recepção QA indicada acima.';if(row){for(const [key,label]of [['safety_milli','Segurança UN'],['minimum_stock_milli','Mínimo UN'],['target_milli','Alvo UN'],['maximum_milli','Máximo UN'],['daily_milli','Consumo diário UN'],['lead_days','Prazo dias'],['horizon_days','Horizonte dias'],['basis','Evidência'],['quantity_milli','Quantidade UN'],['reference','Guia']])if(row.data[key]!==undefined)el('span',' · '+label+': '+(key.endsWith('_milli')?row.data[key]/1000:row.data[key]),$('selected'));el('span',' · Autor '+row.created_by+' · '+row.created_at+' · '+row.reason,$('selected'));}$('action').replaceChildren();el('option','Escolher…',$('action')).value='';
|
|
const allowed=[];if(!state.reviewer&&(!row||['draft','returned'].includes(row.state)))allowed.push(['save','Guardar preparação']);if(state.reviewer&&row){if(['draft','returned'].includes(row.state))allowed.push(['approve','Aprovar versão']);if(['draft','returned','approved'].includes(row.state))allowed.push(['return','Devolver para revisão']);if(row.state==='approved'&&$('family').value==='policy')allowed.push(['suspend','Suspender política']);}if(!state.reviewer&&row?.state==='approved'&&$('family').value==='supplier_return')allowed.push(['execute','Registar saída física']);for(const [v,t]of allowed)el('option',t,$('action')).value=v;form();}
|
|
async function load(){const q=new URLSearchParams({family:$('family').value,page,per_page:$('size').value});if($('state').value)q.set('state',$('state').value);if(historyKey)q.set('record_key',historyKey);const r=await api('/api/stock-completion?'+q);if(r.status!==200)throw Error(r.value.message??'Consulta recusada.');state=r.value;$('summary').textContent=`${state.basis.item.code} — ${state.basis.item.name}; ${state.basis.warehouse.name}. Saldo físico: ${state.basis.balance.quantity/1000} ${state.basis.item.unit}. Linha de recepção: ${state.receipt_item_id}.`;$('records').replaceChildren();for(const row of state.records.data){const tr=el('tr',null,$('records'));for(const value of [row.record_key,row.version,row.state])el('td',String(value),tr);const td=el('td',null,tr),b=el('button','Escolher esta versão',td);b.onclick=()=>choose(row);const h=el('button','Ver versões',td);h.onclick=()=>{historyKey=row.record_key;page=1;load().catch(error);};}const p=state.records;$('page').textContent=`Página ${p.current_page} de ${p.last_page} · ${p.total} processos`;$('previous').disabled=page<=1;$('next').disabled=page>=p.last_page;$('new').hidden=state.reviewer;choose(null);}
|
|
function data(){const read=k=>$('f-'+k)?.value??'';if($('action').value==='save'&&$('family').value==='policy'){const d={stock_item_id:state.basis.item.id,warehouse_id:state.basis.warehouse.id,forecast_method:'manual',purpose:'internal',origin:'local',basis:read('basis')};for(const k of ['safety_milli','minimum_stock_milli','target_milli','maximum_milli','daily_milli','minimum_milli','pack_milli']){const raw=read(k);if(!/^\d{1,9}(\.\d{1,3})?$/.test(raw))throw Error('Use quantidades exactas até três casas decimais.');const [whole,fraction='']=raw.split('.');d[k]=Number(whole)*1000+Number(fraction.padEnd(3,'0'));};for(const k of ['lead_days','approval_days','horizon_days'])d[k]=Number(read(k));return d;}if($('action').value==='save')return {kind:'supplier_return',stock_item_id:state.basis.item.id,warehouse_id:state.basis.warehouse.id,receipt_item_id:state.receipt_item_id,quantity:read('quantity'),reference:read('reference')};if($('action').value==='execute')return {event_on:read('event_on'),evidence:read('evidence')};return {};}
|
|
$('operation').onsubmit=async e=>{e.preventDefault();try{$('message').textContent='';const r=await api('/api/stock-completion/preview',{family:$('family').value,action:$('action').value,operation_key:crypto.randomUUID(),record_key:selected?.record_key??null,version:selected?.version??0,revision:state.revision,reason:$('reason').value,data:data()});if(r.status!==200)throw Error(r.value.message??'Prévia recusada.');preview=r.value;$('impact').replaceChildren();const dl=el('dl',null,$('impact'));for(const [label,value]of [['Acção',preview.impact.action],['Estado posterior',preview.impact.state],['Físico anterior',preview.impact.before_milli/1000],['Físico posterior',preview.impact.after_milli/1000],['Variação física',preview.impact.physical_change_milli/1000],['Crédito financeiro','Não criado'],['Contabilidade','Conferência separada no mesmo diário']]){el('dt',label,dl);el('dd',String(value),dl);}locked();}catch(e){error(e);}};
|
|
async function finish(retry=false){try{locked();const v=await(retry?op.retry():op.run({preview:preview.preview}));preview=null;$('receipt').textContent=`Confirmado: ${v.record_key}, versão ${v.version}. ${v.replayed?'Recibo recuperado, sem repetir operação.':''}`;$('impact').replaceChildren();await load();}catch(e){error(e);if(!op.pending){preview=null;$('impact').replaceChildren();}}finally{locked();}}
|
|
$('confirm').onclick=()=>finish();$('retry').onclick=()=>finish(true);$('cancel').onclick=()=>{if(op.pending)return;preview=null;$('impact').replaceChildren();locked();};$('action').onchange=form;$('new').onclick=()=>choose(null);$('all').onclick=()=>{historyKey=null;page=1;load().catch(error);};$('refresh').onclick=()=>load().catch(error);for(const id of ['family','state','size'])$(id).onchange=()=>{page=1;historyKey=null;load().catch(error);};$('previous').onclick=()=>{page--;load().catch(error);};$('next').onclick=()=>{page++;load().catch(error);};window.addEventListener('beforeunload',e=>{if(preview||op.pending){e.preventDefault();e.returnValue='';}});load().catch(error);
|