21 lines
11 KiB
JavaScript
21 lines
11 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;
|
||
const states={prepared:'Preparado',reviewed:'Revisto',refused:'Recusado',pending:'Pendente',determined_qa:'Determinado QA'};let state,page=1,selected=null,preview=null,operationKey=null,preparing=false,reading=false,readGeneration=0,dirty=false;
|
||
const el=(tag,text,parent)=>{const e=document.createElement(tag);if(text!==null)e.textContent=text;parent.append(e);return e;},error=e=>$('message').textContent=e.message;
|
||
const money=v=>{if(v===null||v===undefined)return 'indeterminado';const n=BigInt(v),a=n<0n?-n:n;return(n<0n?'-':'')+a/100n+'.'+String(a%100n).padStart(2,'0');};
|
||
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/financial-indicators/receipts/'+operationKey);if(old.status===200)return{status:200,value:{...old.value.result,replayed:true}};if(old.status!==404)return old;}return api('/api/financial-indicators/confirm',body);});
|
||
function locked(){const b=Boolean(preparing||reading||preview||op.pending);$('fields').disabled=b||!state||(!state.capabilities.prepare&&!state.capabilities.review);$('filters').disabled=b;$('confirm').hidden=!preview||op.uncertain;$('retry').hidden=!op.uncertain;$('cancel').hidden=!preview||op.pending;}
|
||
function input(id,label,type='text',value=''){const l=el('label',label,$('inputs')),e=el(type==='textarea'?'textarea':'input',null,l);e.id=id;e.required=true;if(type!=='textarea')e.type=type;e.value=value;return e;}
|
||
function fields(){if(preview||op.pending)return;$('inputs').replaceChildren();const action=$('action').value;if(action==='prepare'){const l=el('label','Critério a preparar',$('inputs')),s=el('select',null,l);s.id='metric';s.required=true;s.append(new Option('Escolher…',''));for(const [key,t]of Object.entries(state.templates))s.append(new Option(t.label,key));const detail=el('p','Escolha explicitamente o indicador e as suas fontes.',$('inputs'));s.onchange=()=>{const t=state.templates[s.value];if(t)detail.textContent=t.label+' · retrato '+t.as_of+' · MZN · '+(s.value==='cash_coverage_single_payable'?'Caixa / saldo credor QA-FORNECEDOR; sem valor absoluto.':'Despesa / entradas da mesma Caixa ×100; não margem de resultados.');};input('scopeDescription','Âmbito e limites deste critério','textarea');input('evidence','Evidência da conferência das fontes','textarea');}
|
||
if(action==='review'){el('p',selected?'Versão escolhida: '+selected.definition.label+' · v'+selected.version+' (actual '+selected.current_version+')':'Escolha uma versão do critério no catálogo.',$('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…'],['reviewed','Rever favoravelmente'],['refused','Recusar']])s.append(new Option(t,v));}
|
||
if(action==='calculate'){el('p',selected?'Critério escolhido: '+selected.definition.label+' · versão '+selected.version:'Sem critério escolhido: o resultado será uma pendência, nunca zero.',$('inputs'));input('asOf','Data explícita do retrato','date');}}
|
||
function actions(){const prior=$('action').value;$('action').replaceChildren(new Option('Escolher…',''));for(const [value,label,right]of [['prepare','Preparar critério','prepare'],['review','Rever versão escolhida','review'],['calculate','Calcular pelo critério escolhido','prepare']])if(state.capabilities[right])$('action').append(new Option(label,value));if([...$('action').children].some(o=>o.value===prior))$('action').value=prior;fields();}
|
||
async function choose(id,type){if(preview||op.pending||preparing||reading)return;try{const r=await api('/api/financial-indicators/records/'+id+'?type='+type);if(r.status!==200)throw Error(r.value.message??'Registo indisponível.');$('detail').textContent=JSON.stringify(r.value,null,2);$('download').replaceChildren();if(type==='criterion'){selected=r.value;$('selection').textContent='Escolhida '+selected.definition.label+' · versão '+selected.version+' · '+states[selected.state];}else{selected=null;$('selection').textContent='Resultado histórico escolhido; critério não adoptado automaticamente.';const a=el('a','Descarregar relatório interno deste retrato',$('download'));a.href='/api/financial-indicators/results/'+id+'/report';}if(['calculate','review'].includes($('action').value))fields();}catch(e){error(e);}}
|
||
async function load(){if(preview||op.pending)throw Error('Conclua ou cancele a conferência em curso.');const generation=++readGeneration,oldCaps=JSON.stringify(state?.capabilities);reading=true;locked();try{const view=$('view').value,q={view,page,per_page:$('size').value};if(view!=='results'){if($('filterMetric').value)q.metric=$('filterMetric').value;if($('filterState').value)q.state=$('filterState').value;if($('search').value)q.search=$('search').value;}if(view==='history'&&!q.metric)throw Error('Escolha o indicador para consultar o histórico.');const r=await api('/api/financial-indicators?'+new URLSearchParams(q));if(generation!==readGeneration)return;if(r.status!==200)throw Error(r.value.message??'Consulta indisponível.');state=r.value;$('summary').replaceChildren();if(state.source.available){for(const [label,key]of [['Caixa QA','cash_balance_minor'],['Saldo credor QA-FORNECEDOR','supplier_credit_minor'],['Entradas da Caixa','cash_receipts_minor'],['Despesa da Caixa','cash_expenses_minor']])el('p',label+': '+money(state.source[key])+' MZN',$('summary'));el('p','Retrato '+state.source.as_of+' · '+(state.source.kind==='composed_qa_sources'?'fontes efectivas conciliadas do QA':'origem de ensaio')+'. Sem novos movimentos.',$('summary'));}else el('p','Fontes indisponíveis/não conciliadas. Montantes indeterminados; não são zero.',$('summary'));
|
||
if($('filterMetric').children.length===1)for(const [key,t]of Object.entries(state.templates))$('filterMetric').append(new Option(t.label,key));$('events').replaceChildren();for(const row of state.records.data){const isResult=view==='results',tr=el('tr',null,$('events'));el('td',isResult?'Resultado #'+row.id:row.definition.label,tr);el('td',isResult?states[row.result.state]:'v'+row.version+' · '+states[row.state],tr);el('td',isResult?(row.result.value??'indeterminado')+' '+(row.result.unit??'')+' · '+row.input.as_of:row.definition.as_of+' · '+row.definition.currency,tr);const td=el('td',null,tr);el('button',isResult?'Consultar resultado':'Escolher esta versão',td).onclick=()=>choose(row.id,isResult?'result':'criterion');}const p=state.records;$('page').textContent='Página '+p.current_page+' de '+p.last_page+' · '+p.total+' registos';$('previous').disabled=page<=1;$('next').disabled=page>=p.last_page;if(oldCaps!==JSON.stringify(state.capabilities))actions();}catch(e){if(generation===readGeneration){state=null;selected=null;$('summary').textContent=e.message;$('events').replaceChildren();$('download').replaceChildren();$('detail').textContent='';}throw e;}finally{if(generation===readGeneration){reading=false;locked();}}}
|
||
$('operation').onsubmit=async e=>{e.preventDefault();if(preparing)return;preparing=true;locked();try{const action=$('action').value;if(!action)throw Error('Escolha a acção.');let body={action,version:0,data:{},reason:$('reason').value};if(action==='prepare'){const metric=$('metric').value;if(!state.templates[metric])throw Error('Escolha o indicador.');const version=await api('/api/financial-indicators?'+new URLSearchParams({view:'history',metric,per_page:25}));if(version.status!==200)throw Error(version.value.message??'Versão não conferida.');const {label,...template}=state.templates[metric];body={...body,metric,version:version.value.metric_version,data:{...template,scope_description:$('scopeDescription').value,evidence:$('evidence').value}};}else if(action==='review'){if(!selected)throw Error('Escolha a versão do critério.');body={...body,metric:selected.metric,version:selected.current_version,data:{decision:$('decision').value}};}else body.data={criterion_id:selected?.id??null,as_of:$('asOf').value};operationKey=crypto.randomUUID();const r=await api('/api/financial-indicators/preview',body,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;if(i.result){const v=i.result;for(const [key,value]of [['Estado',states[v.state]],['Numerador MZN',money(v.numerator_minor)],['Denominador MZN',money(v.denominator_minor)],['Fracção exacta',v.exact_fraction??'indeterminada'],['Representação',(v.value??'indeterminada')+' '+(v.unit==='percent'?'%':v.unit==='times'?'vezes':'')],['Diferença preservada',v.representation_difference_fraction??'indeterminada'],['Série anterior/tendência','Indeterminadas']])el('p',key+': '+value,$('impact'));for(const reason of v.pending)el('p',reason,$('impact'));}else{el('p',i.definition.label+' · estado anterior '+(states[i.before_state]??'sem preparação')+' → '+states[i.state]+' · versão '+i.after_version,$('impact'));el('p','Fontes explicitamente escolhidas: '+i.definition.numerator+' / '+i.definition.denominator+' · direcção '+i.definition.denominator_direction,$('impact'));}el('p','Novos efeitos financeiros: nenhum. Critério da empresa real/recomendação de pagamento: não.',$('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;dirty=false;selected=null;$('selection').textContent='Escolha outra versão para a próxima operação.';$('impact').replaceChildren();$('receipt').textContent=(r.type==='result'?'Resultado #'+r.id:'Critério versão '+r.version)+' · '+states[r.state]+(r.replayed?' · recuperado sem duplicação':' · registado');fields();await load();}catch(e){error(e);if(!op.pending){preview=null;$('impact').replaceChildren();}}finally{locked();}}
|
||
$('recovery').onsubmit=async e=>{e.preventDefault();try{const key=$('referenceKey').value.trim().toLowerCase(),r=await api('/api/financial-indicators/receipts/'+encodeURIComponent(key));if(r.status!==200)throw Error(r.value.message??'Sem recibo desta função para a referência.');$('receipt').textContent='Recibo histórico '+r.value.result.type+' #'+r.value.result.id+'. Nenhuma operação repetida.';if(op.pending&&key===operationKey)await finish(true);}catch(e){error(e);}};
|
||
$('action').onchange=fields;$('operation').oninput=()=>{dirty=true;};$('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);};window.addEventListener('beforeunload',e=>{if(dirty||preview||op.pending){e.preventDefault();e.returnValue='';}});load().catch(error);
|