Files
SGF/public/supplier-obligation-workspace.mjs
2026-11-04 09:37:20 +02:00

12 lines
7.5 KiB
JavaScript

import {rememberFocus} from './purchasing-focus.mjs';
import {evidence as renderEvidence,purchasingStatus} from './purchasing-presenter.mjs';
import {mountObligationEvidence} from './obligation-evidence.mjs';
import {beginWorkspace,focusHref,appendStageLinks} from './purchasing-focus.mjs';
const root=document.querySelector('#supplier-obligations'),status=document.querySelector('#status'),documents=document.querySelector('#documents'),detail=document.querySelector('#detail');let page=1,last=1,turn=0,busy=false,filter='';
const n=(tag,text)=>{const e=document.createElement(tag);if(text!==undefined)e.textContent=text;return e;},money=v=>v===null?'Indeterminado':(Number(v)/100).toFixed(2)+' MZN';
async function api(path,data){const r=await fetch('/api/purchasing-obligations'+path,{method:data===undefined?'GET':'POST',headers:{Accept:'application/json','X-SGF-Context':root.dataset.context,'X-CSRF-TOKEN':root.dataset.csrf,...(data===undefined?{}:{'Content-Type':'application/json'})},body:data===undefined?undefined:JSON.stringify(data)});const d=await r.json();if(!r.ok){const error=Error(Object.values(d.errors||{}).flat().join(' ')||d.message||(d.state==='conflict'?'A origem mudou; exige nova conferência.':'Não foi possível concluir.'));error.status=r.status;throw error;}return d;}
async function load(){const own=++turn;detail.replaceChildren();try{const d=await api('?page='+page+filter);if(own!==turn)return;documents.replaceChildren();for(const doc of d.data){const b=n('button',doc.number+' · '+doc.status+' · '+money(doc.total_minor));b.onclick=()=>open(doc.id);documents.append(b);}if(!d.data.length)documents.append(n('p','Nenhuma factura nesta página e contexto.'));last=d.last_page;document.querySelector('#page').textContent=`Página ${page} de ${last}`;document.querySelector('#previous').disabled=page<=1;document.querySelector('#next').disabled=page>=last;}catch(e){status.textContent=e.message;}}
function command(id,revision,action,proposal=0,container=detail){const own=turn,form=n('form'),label=n('label','Fundamento individual'),reason=n('textarea'),button=n('button',action==='prepare'?'Conferir preparação':'Conferir reconhecimento'),impact=n('section'),confirm=n('button','Confirmar esta decisão');let cut=null,key=crypto.randomUUID(),version=0;reason.required=true;reason.minLength=10;reason.maxLength=2000;label.append(reason);confirm.type='button';confirm.disabled=true;impact.hidden=true;form.oninput=()=>{version++;key=crypto.randomUUID();cut=null;confirm.disabled=true;impact.hidden=true;};form.onsubmit=async e=>{e.preventDefault();if(busy||!form.isConnected||form.dataset.stale==='true')return;busy=true;button.disabled=true;const observed=version;try{const p=await api('/'+id+'/preview',{action,proposal_id:proposal,operation_key:key,revision,reason:reason.value});if(own!==turn||observed!==version||!form.isConnected||form.dataset.stale==='true')return;cut=p.preview;impact.replaceChildren(n('h3','Impacto conferido'),n('p','Resultado: '+p.impact.result.state+' · principal documental '+money(p.impact.principal_minor)),n('p','Vencimento desconhecido; atraso indeterminado.'),n('p','Fundamento: '+p.impact.reason),renderEvidence(p.impact.result),n('p','Preparar preserva a fotografia. Só a validação distinta reconhece a obrigação. Sem pagamento, lançamento, consumo orçamental ou movimento físico.'));impact.hidden=false;confirm.disabled=p.impact.result.state==='conflict';}catch(e){status.textContent=e.message;}finally{busy=false;button.disabled=form.dataset.stale==='true';}};confirm.onclick=async()=>{if(busy||!cut||!form.isConnected||form.dataset.stale==='true')return;busy=true;confirm.disabled=true;try{await api('/'+id+'/confirm',{preview:cut});if(own===turn)await open(id);}catch(e){status.textContent=e.message+' Se perdeu a resposta, repita esta confirmação para recuperar sem duplicar.';confirm.disabled=form.dataset.stale==='true';}finally{busy=false;}};form.append(label,button,impact,confirm);container.append(form);}
async function open(id,proposalPage=1,eventPage=1){const own=++turn;detail.replaceChildren();status.textContent='A consultar origem e responsabilidade…';try{const d=await api('/'+id+'?proposal_page='+proposalPage+'&event_page='+eventPage);if(own!==turn)return;status.textContent='';rememberFocus('obligations',{invoice:id});const doc=d.documents[0],summary=n('section');summary.append(n('h2',doc.number),n('p',doc.supplier_name+' · '+money(doc.total_minor)+' · '+doc.status),n('p','Origem: ordem #'+doc.order_id+' · validação #'+(doc.validation_audit_id??'pendente')),n('p','Revisão: '+d.revision.slice(0,12)),renderEvidence({pendencias:doc.missing,linhas:doc.lines}));detail.append(summary);appendStageLinks(root,summary,[{href:focusHref('invoices',{order:doc.order_id,invoice:id}),label:'Consultar factura de origem'},{href:focusHref('receipts',{order:doc.order_id}),label:'Consultar recepções da ordem de origem'}]);for(const ob of d.obligations){const box=n('section');box.append(n('h2','Obrigação #'+ob.id),n('p','Principal '+money(ob.principal_minor)+' · saldo '+money(ob.balance_minor)),n('p','Vencimento desconhecido · atraso indeterminado'),n('p',ob.source_conflict?'Origem divergente: exige conferência.':'Responsabilidade única reconhecida.'),renderEvidence(ob));detail.append(box);}if(d.permissions.prepare&&!d.obligations.length)command(id,d.revision,'prepare');if(root.dataset.evidenceEnabled==='true'){const evidence=n('section');detail.append(evidence);mountObligationEvidence({id,data:d,container:evidence,api,active:()=>own===turn&&evidence.isConnected,canSelect:()=>!busy,makeDecision:(proposal,box)=>command(id,d.revision,'validate',proposal.id,box),onStale:()=>{for(const form of detail.querySelectorAll('form')){form.dataset.stale='true';for(const control of form.querySelectorAll('button,textarea'))control.disabled=true;}},refresh:()=>open(id),message:text=>status.textContent=text});}else{for(const p of d.proposals){const box=n('article');box.append(n('h3','Proposta #'+p.id+' · versão '+p.capture_version),n('p','Preparador #'+p.prepared_by+' · '+p.created_at),n('p',p.reason),renderEvidence(p.missing));detail.append(box);if(d.permissions.validate&&p.prepared_by!==d.current_actor&&!d.obligations.length)command(id,d.revision,'validate',p.id);}const history=n('section');history.append(n('h2','Histórico'));for(const event of d.events){const row=n('details');row.append(n('summary',event.created_at+' · '+event.kind+' · autor #'+event.actor_id),renderEvidence(event.payload));history.append(row);}detail.append(history);for(const [key,current]of [['proposals',proposalPage],['events',eventPage]]){const meta=d.pagination[key],box=n('section');box.append(n('p',(key==='events'?'Histórico':'Propostas')+' · página '+meta.page+' de '+meta.last_page));for(const [enabled,target,text]of [[current>1,current-1,'Anterior'],[current<meta.last_page,current+1,'Seguinte']])if(enabled){const b=n('button',text);b.onclick=()=>open(id,key==='proposals'?target:proposalPage,key==='events'?target:eventPage);box.append(b);}detail.append(box);}}}catch(e){if(own===turn)status.textContent=e.message;}}
document.querySelector('#filters').onsubmit=e=>{e.preventDefault();const f=e.currentTarget;filter=(f.search.value?'&search='+encodeURIComponent(f.search.value):'')+(f.state.value?'&status='+f.state.value:'');page=1;load();};document.querySelector('#previous').onclick=()=>{if(page>1){page--;load();}};document.querySelector('#next').onclick=()=>{if(page<last){page++;load();}};beginWorkspace({stage:'obligations',root,load:load,open:f=>open(f.invoice),current:()=>turn,error:text=>{status.textContent=text;}});