Files
SGF/public/sgf-indicators.mjs
2026-11-11 09:00:17 +02:00

21 lines
11 KiB
JavaScript
Raw Permalink 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.
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);