16 lines
9.5 KiB
JavaScript
16 lines
9.5 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,states={prepared:'Preparado',approved:'Aprovado',refused:'Recusado'};
|
|
let state,page=1,selected=null,recordKey=null,version=0,preview=null,operationKey=null,reading=false,preparing=false,generation=0,dirty=false,activeAction='';
|
|
const el=(tag,text,parent)=>{const n=document.createElement(tag);if(text!==null)n.textContent=text;parent.append(n);return n;},error=e=>$('message').textContent=e.message;
|
|
async function api(path,body,key){const r=await fetch(path,{credentials:'same-origin',headers:{Accept:'application/json','Content-Type':'application/json','X-CSRF-TOKEN':csrf,'X-SGF-Context':context,...(key?{'Idempotency-Key':key}:{})},...(body?{method:'POST',body:JSON.stringify(body)}:{})});return{status:r.status,value:await r.json()};}
|
|
const op=confirmation(async body=>{if(operationKey){const old=await api('/api/stock-catalogue/receipts/'+operationKey);if(old.status===200)return{status:200,value:{...old.value.result,replayed:true}};if(old.status!==404)return old;}return api('/api/stock-catalogue/confirm',body);});
|
|
function locked(){const busy=Boolean(reading||preparing||preview||op.pending);$('filters').disabled=busy;$('fields').disabled=busy||!state||(!state.capabilities.prepare&&!state.capabilities.review);$('confirm').hidden=!preview||op.uncertain;$('cancel').hidden=!preview||op.pending;$('retry').hidden=!op.uncertain;$('new').hidden=!state?.capabilities.prepare;$('discard').disabled=busy;}
|
|
function fields(){if(preview||op.pending)return;$('inputs').replaceChildren();activeAction=$('action').value;if($('action').value==='prepare'){for(const [id,label,value]of [['code','Código',selected?.data?.code??''],['name','Designação',selected?.data?.name??''],['unit','Unidade explícita',selected?.data?.unit??'']]){const l=el('label',label,$('inputs')),i=el('input',null,l);i.id=id;i.value=value;i.required=true;i.maxLength={code:30,name:150,unit:12}[id];i.readOnly=Boolean(selected?.stock_item_id&&id!=='name');}el('p','Código e unidade explícitos; sem quantidade, preço ou saldo inicial.',$('inputs'));}else if($('action').value==='review'){el('p','Conferir a preparação v'+version+': '+JSON.stringify(selected.data),$('inputs'));const l=el('label','Decisão explícita',$('inputs')),s=el('select',null,l);s.id='decision';s.required=true;for(const [v,t]of [['','Escolher…'],['approved','Aprovar e aplicar ao catálogo'],['refused','Recusar sem alterar o catálogo']])s.append(new Option(t,v));}}
|
|
function actions(){const prior=$('action').value;$('action').replaceChildren(new Option('Escolher…',''));if(recordKey&&state?.capabilities.prepare)$('action').append(new Option('Preparar artigo ou nova designação','prepare'));if(selected?.state==='prepared'&&state?.capabilities.review)$('action').append(new Option('Rever preparação escolhida','review'));if([...$('action').children].some(o=>o.value===prior))$('action').value=prior;fields();}
|
|
async function choose(row){if(reading||preparing||preview||op.pending)return;if(dirty){error(Error('Conclua a preparação ou descarte explicitamente os campos antes de escolher outro registo.'));return;}try{$('detail').textContent=JSON.stringify(row,null,2);if(!row.record_key){selected=null;recordKey=null;version=0;$('selection').textContent='Artigo anterior: apenas consulta, sem alterar ou associar automaticamente.';actions();return;}reading=true;locked();const r=await api('/api/stock-catalogue?view=history&record_key='+row.record_key+'&per_page=25');if(r.status!==200)throw Error(r.value.message??'Processo indisponível.');selected=r.value.records.data[0];if(!selected)throw Error('Sem versão actual conferida.');recordKey=selected.record_key;version=selected.version;$('selection').textContent='Processo escolhido '+recordKey+' · versão actual '+version+' · '+states[selected.state];$('detail').textContent=JSON.stringify(selected,null,2);actions();}catch(e){selected=null;recordKey=null;version=0;actions();error(e);}finally{reading=false;locked();}}
|
|
async function load(){if(preview||op.pending)throw Error('Conclua ou descarte a conferência em curso.');const current=++generation;reading=true;locked();try{const view=$('view').value,q={view,page,per_page:$('size').value};if($('search').value)q.search=$('search').value;if(view!=='items'&&$('state').value)q.state=$('state').value;if(view==='history'){if(!recordKey)throw Error('Escolha primeiro um processo com histórico.');q.record_key=recordKey;}const r=await api('/api/stock-catalogue?'+new URLSearchParams(q));if(current!==generation)return;if(r.status!==200)throw Error(r.value.message??'Consulta indisponível.');const old=JSON.stringify(state?.capabilities);state=r.value;$('records').replaceChildren();for(const row of state.records.data){const tr=el('tr',null,$('records')),d=row.data??row;el('td',d.code+' · '+d.name,tr);el('td',d.unit,tr);el('td',row.record_key?(states[row.state]??'Artigo deste percurso'):'Artigo anterior — só consulta',tr);el('td',row.version??'Sem versão neste percurso',tr);el('button',row.record_key?'Conferir processo actual':'Consultar artigo',el('td',null,tr)).onclick=()=>choose(row);}const p=state.records;$('page').textContent=p.total+' registo(s) · página '+p.current_page+' de '+p.last_page;$('previous').disabled=page<=1;$('next').disabled=page>=p.last_page;if(old!==JSON.stringify(state.capabilities))actions();}catch(e){if(current===generation){state=null;selected=null;recordKey=null;$('records').replaceChildren();$('selection').textContent='Consulta não conferida. Reabra a função autorizada.';}throw e;}finally{if(current===generation){reading=false;locked();}}}
|
|
$('new').onclick=()=>{if(reading||preview||op.pending||preparing)return;if(dirty){error(Error('Descarte os campos não guardados antes de iniciar outro artigo.'));return;}$('reason').value='';selected=null;recordKey=crypto.randomUUID();version=0;$('detail').textContent='';$('selection').textContent='Novo artigo a preparar; ainda não existe no catálogo.';actions();$('action').value='prepare';fields();};$('action').onchange=()=>{if(dirty){$('action').value=activeAction;error(Error('Descarte os campos antes de mudar de acção.'));return;}fields();};$('operation').oninput=e=>{if(e.target?.id!=='action')dirty=true;};
|
|
$('operation').onsubmit=async e=>{e.preventDefault();if(preparing||preview||op.pending)return;preparing=true;locked();try{const action=$('action').value;if(!recordKey||!action)throw Error('Escolha um processo ou prepare um novo artigo.');const data=action==='prepare'?{code:$('code').value,name:$('name').value,unit:$('unit').value}:{decision:$('decision').value};operationKey=crypto.randomUUID();const r=await api('/api/stock-catalogue/preview',{action,record_key:recordKey,version,data,reason:$('reason').value},operationKey);if(r.status!==200)throw Error(r.value.message??'Prévia recusada.');preview=r.value;$('referenceKey').value=operationKey;$('impact').replaceChildren();el('h3','Impacto antes da confirmação',$('impact'));const i=preview.impact;el('p','Estado anterior: '+(states[i.before.state]??'sem processo')+' · versão '+i.before.version,$('impact'));el('p','Estado posterior: '+states[i.after.state]+' · versão '+i.after.version,$('impact'));el('pre',JSON.stringify({artigo_anterior:i.before.item,dados_conferidos:i.after.data},null,2),$('impact'));el('p',i.creates_article?'Será criado um artigo sem saldo inicial.':i.renames_owned_article?'Será alterada apenas a designação deste artigo; código/unidade mantidos.':'A intenção/decisão será conservada sem alterar o catálogo físico.',$('impact'));el('p','Movimentos:0 · lançamentos:0 · preço/custo/lote: nenhum.',$('impact'));}catch(e){error(e);}finally{preparing=false;locked();}};
|
|
async function finish(retry=false){try{const r=await(retry?op.retry():op.run({preview:preview.preview}));preview=null;selected=null;recordKey=null;version=0;dirty=false;$('reason').value='';$('impact').replaceChildren();$('detail').textContent='';$('receipt').textContent='Versão '+r.version+' · '+states[r.state]+(r.replayed?' · recuperada sem duplicação':' · registada');$('selection').textContent='Escolha explicitamente o próximo artigo/processo.';actions();await load();}catch(e){error(e);if(!op.pending){preview=null;$('impact').replaceChildren();}}finally{locked();}}
|
|
$('discard').onclick=()=>{if(preview||op.pending||reading||preparing)return;dirty=false;selected=null;recordKey=null;version=0;$('reason').value='';$('detail').textContent='';$('selection').textContent='Campos descartados; escolha um processo ou prepare um novo artigo.';$('action').value='';actions();locked();};$('confirm').onclick=()=>finish();$('retry').onclick=()=>finish(true);$('cancel').onclick=()=>{if(op.pending)return;preview=null;$('impact').replaceChildren();locked();};$('apply').onclick=()=>{page=1;load().catch(error);};$('refresh').onclick=()=>load().catch(error);$('previous').onclick=()=>{page--;load().catch(error);};$('next').onclick=()=>{page++;load().catch(error);};$('recovery').onsubmit=async e=>{e.preventDefault();try{const key=$('referenceKey').value.trim().toLowerCase(),r=await api('/api/stock-catalogue/receipts/'+encodeURIComponent(key));if(r.status!==200)throw Error(r.value.message??'Sem recibo próprio nesta função.');$('receipt').textContent='Recibo histórico v'+r.value.result.version+'. Nenhuma operação repetida.';if(op.pending&&key===operationKey)await finish(true);}catch(e){error(e);}};window.addEventListener('beforeunload',e=>{if(dirty||preview||op.pending){e.preventDefault();e.returnValue='';}});load().catch(error);
|