16 lines
9.7 KiB
JavaScript
16 lines
9.7 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 kindNames={open:'Abertura',receipt:'Classificação de recebimento',expense:'Despesa',count:'Contagem'},actionNames={prepare:'Preparar',review:'Conferir proposta',close:'Fechar Caixa'},states={proposed:'Proposto',approved:'Aprovado',refused:'Recusado',closed:'Fechado'};let state,selected=null,page=1,preview=null,preparing=false,dirty=false,operationKey=null;const money=n=>(n/100).toFixed(2)+' MZN';
|
|
function el(tag,text,parent){const n=document.createElement(tag);if(text!==null)n.textContent=text;parent.append(n);return n;}function 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/cash/receipts/'+operationKey);if(old.status===200)return {status:200,value:{...old.value.result,replayed:true}};if(old.status!==404)return old;}return api('/api/cash/confirm',body);});function locked(){const b=Boolean(preparing||preview||op.pending);$('fields').disabled=b||Boolean(state?.blocked)||Boolean(state?.basis?.closed);$('filters').disabled=b;$('refresh').disabled=b;$('confirm').hidden=!preview||op.uncertain;$('retry').hidden=!op.uncertain;$('cancel').hidden=!preview||op.pending;}
|
|
function input(name,label,type='text',value=''){const l=el('label',label,$('inputs')),i=el('input',null,l);i.id='field-'+name;i.type=type;i.required=true;i.value=value;return i;}
|
|
function form(){const action=$('action').value,kind=$('kind').value;$('inputs').replaceChildren();$('selection').textContent=selected?`Versão ${selected.version}, autor ${selected.actor_id}: ${selected.reason}`:'';if(action==='prepare'&&kind){input('date','Data do facto','date');input('evidence','Comprovativo fictício da declaração');if(kind==='open'){input('label','Identificação da Caixa QA');input('opening','Saldo inicial explícito (MZN)','text','0.00');}if(kind==='receipt'){const l=el('label','Recebimento efectivo de origem', $('inputs')),s=el('select',null,l);s.id='field-receipt_id';s.required=true;el('option','Escolher…',s).value='';el('option',`Recebimento ${state.binding.receipt_id} · factura ${state.binding.invoice_id} · ${money(state.binding.amount_minor)}`,s).value=String(state.binding.receipt_id);const l2=el('label','Meio de custódia explicitamente conferido', $('inputs')),m=el('select',null,l2);m.id='field-medium';m.required=true;el('option','Escolher…',m).value='';el('option','Numerário de teste',m).value='cash';}if(kind==='expense'){input('amount','Despesa QA (MZN)','text','50.00');input('beneficiary','Beneficiário fictício');input('description','Descrição da despesa');}if(kind==='count')input('counted','Numerário contado (MZN)');}else if(action==='review'){const l=el('label','Decisão explícita', $('inputs')),s=el('select',null,l);s.id='field-decision';s.required=true;for(const [v,t]of [['','Escolher…'],['approved','Aprovar'],['refused','Recusar']])el('option',t,s).value=v;}if(selected&&selected.dependencies?.balance_minor!==undefined){const before=selected.dependencies.balance_minor,delta=selected.action==='review'&&selected.state==='approved'?(selected.kind==='receipt'?selected.data.amount_minor:selected.kind==='expense'?-selected.data.amount_minor:0):0;el('p','Saldo antes desta decisão: '+money(before),$('selection'));el('p','Saldo após esta decisão: '+money(before+delta),$('selection'));}if(selected){for(const [k,v]of Object.entries(selected.data)){const titles={date:'Data',evidence:'Comprovativo',label:'Caixa',opening:'Saldo inicial',receipt_id:'Recebimento',medium:'Meio de custódia',amount:'Despesa',amount_minor:'Montante',beneficiary:'Beneficiário',description:'Descrição',counted:'Contagem declarada',counted_minor:'Contagem'};el('p',(titles[k]??k)+': '+(k.endsWith('_minor')?money(v):v==='cash'?'Numerário de teste':String(v)),$('selection'));}}}
|
|
function actions(row=null){selected=row;$('action').replaceChildren();$('kind').replaceChildren();el('option','Escolher…',$('action')).value='';el('option','Escolher…',$('kind')).value='';const b=state.basis,c=state.capabilities;if(!b||b.closed){form();locked();return;}if(!row){if(c.prepare){el('option','Preparar', $('action')).value='prepare';for(const k of b.open?['receipt','expense','count']:['open']){if(k==='receipt'&&b.received||k==='expense'&&b.expense_recorded)continue;el('option',kindNames[k],$('kind')).value=k;}}}else{el('option',kindNames[row.kind],$('kind')).value=row.kind;$('kind').value=row.kind;if(c.review&&row.action==='prepare'&&!row.decision&&row.actor_id!==state.actor_id)el('option','Conferir esta proposta', $('action')).value='review';if(c.close&&row.action==='review'&&row.kind==='count'&&row.state==='approved')el('option','Fechar pela contagem revista', $('action')).value='close';}form();locked();}
|
|
async function load(){const q=new URLSearchParams({page,per_page:$('size').value});for(const [id,key]of [['filterKind','kind'],['from','from'],['to','to']])if($(id).value)q.set(key,$(id).value);const r=await api('/api/cash?'+q);if(r.status!==200)throw Error(r.value.message??'Consulta recusada.');state=r.value;$('summary').textContent=state.blocked??`${state.basis.closed?'Fechada':state.basis.open?'Aberta':'Abertura por confirmar'} · saldo registado ${money(state.basis.balance_minor)} · versão ${state.basis.revision}. Origem permitida: recebimento ${state.binding.receipt_id}, ${money(state.binding.amount_minor)}. Sem saldo bancário ou dinheiro real.`;const p=state.period;$('period').textContent=p?`Intervalo: abertura ${money(p.opening_minor)}; entradas ${money(p.receipts_minor)}; despesas ${money(p.expenses_minor)}; fecho ${money(p.closing_minor)}.`:'Resumo por conferir: origem bloqueada.';$('events').replaceChildren();for(const row of state.events.data){const tr=el('tr',null,$('events'));for(const value of [row.version+' · '+(row.data.date??row.created_at),kindNames[row.kind]+' / '+actionNames[row.action],states[row.state],money(row.data.counted_minor??row.data.amount_minor??0),row.actor_id+' · '+row.reason])el('td',value,tr);const td=el('td',null,tr);el('button','Escolher esta versão',td).onclick=()=>actions(row);if(row.decision)el('p','Decisão: '+states[row.decision.state],td);if(row.command_reference)el('small','Referência: '+row.command_reference,td);}const pg=state.events;$('page').textContent=`Página ${pg.current_page} de ${pg.last_page} · ${pg.total} eventos`;$('previous').disabled=page<=1;$('next').disabled=page>=pg.last_page;actions();}
|
|
function data(){const read=k=>$('field-'+k)?.value??'';if($('action').value==='close')return{};if($('action').value==='review')return{decision:read('decision')};const d={date:read('date'),evidence:read('evidence')};for(const k of {open:['label','opening'],receipt:['receipt_id','medium'],expense:['amount','beneficiary','description'],count:['counted']}[$('kind').value]??[])d[k]=k==='receipt_id'?Number(read(k)):read(k);return d;}
|
|
$('operation').onsubmit=async e=>{e.preventDefault();if(preparing)return;preparing=true;locked();try{$('message').textContent='';const key=crypto.randomUUID();const r=await api('/api/cash/preview',{action:$('action').value,kind:$('kind').value,proposal_id:selected?.id??null,version:state.basis.revision,data:data(),reason:$('reason').value},key);if(r.status!==200)throw Error(r.value.message??'Prévia recusada.');preview=r.value;operationKey=key;$('referenceKey').value=key;$('impact').replaceChildren();for(const [t,v]of [['Acção',actionNames[preview.impact.action]],['Tipo',kindNames[preview.impact.kind]],['Estado posterior',states[preview.impact.state]],['Saldo anterior',money(preview.impact.before_minor)],['Saldo posterior',money(preview.impact.after_minor)],['Variação',money(preview.impact.change_minor)],['Dinheiro real','Não'],['Nova factura ou recibo','Não'],['Lançamento automático','Não']])el('p',t+': '+v,$('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;$('impact').replaceChildren();$('receipt').textContent=`Versão ${r.version} confirmada: ${states[r.state]}. ${r.replayed?'Recuperada sem duplicação.':''}`;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/cash/receipts/'+encodeURIComponent(key));if(r.status!==200)throw Error(r.value.message||'Ainda não há recibo confirmado para esta referência e função.');$('receipt').textContent='Recibo histórico recuperado: versão '+r.value.result.version+', '+states[r.value.result.state]+'. Nenhuma operação repetida.';if(op.pending&&key===operationKey)await finish(true);}catch(e){error(e);}};
|
|
$('operation').oninput=()=>{dirty=true;};$('action').onchange=$('kind').onchange=form;$('confirm').onclick=()=>finish();$('retry').onclick=()=>finish(true);$('cancel').onclick=()=>{if(op.pending)return;preview=null;$('impact').replaceChildren();locked();};$('refresh').onclick=()=>load().catch(error);$('apply').onclick=()=>{page=1;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);
|