import React, { useEffect, useRef, useState } from 'react'; const titles = { obligation:'Obrigação de teste', receipt:'Recebimento proposto', observation:'Observação simulada', correction:'Correcção proposta' }; const actions = { reverse_allocation:'Reverter afectação integral', reverse_reconcile:'Desfazer conferência', reverse_receipt:'Reverter recebimento integral', reduction:'Aplicar redução sintética', due:'Corrigir vencimento' }; const effects = { open:'Obrigação aberta', receipt_confirm:'Recebimento confirmado', refused:'Proposta recusada', allocate:'Afectação aplicada', reconcile:'Reconciliado em simulação', divergence:'Divergência preservada', reverse_allocation:'Afectação revertida', reverse_reconcile:'Conferência desfeita', reverse_receipt:'Recebimento revertido', reduction:'Redução sintética aplicada', due:'Vencimento corrigido' }; const money = v => String(v ?? '').replace('.', ','); export default function ServiceReceivablesWorkspace({context, can, api, busy}) { const [data,setData]=useState(null), [error,setError]=useState(''), [working,setWorking]=useState(false), [obId,setObId]=useState(''), [proof,setProof]=useState(null); const attempt=useRef(null); const proposalIntent=useRef(null); const allowed = r => can(`service_receivables.${r}`,context.unit?.id ?? null); const load = async () => { const next=await api('/api/service-receivables'); setData(next); return next; }; useEffect(()=>{load().catch(e=>setError(e.message));},[context.token]); const ob=data?.obligations.find(o=>String(o.id)===String(obId)) ?? data?.obligations[0]; const record=id=>data?.records.find(r=>r.id===Number(id)); const origin=ob && record(ob.id); const receipts=ob ? Object.values(ob.receipts) : []; const disabled=busy||working; async function send(path,input) { setError('');setWorking(true); const canonical=JSON.stringify([path,input]); if(attempt.current?.canonical!==canonical) attempt.current={canonical,key:crypto.randomUUID()}; try { await api(`/api/service-receivables${path}`,'POST',input,context.token,attempt.current.key); await load(); attempt.current=null; proposalIntent.current=null; } catch(e) { setError(e.message); } finally {setWorking(false);} } function propose(kind,values) { const invoice=kind==='obligation'?Number(values.invoice_id):origin.invoice_id; const signature=JSON.stringify([kind,invoice,values]); if(proposalIntent.current?.signature!==signature) proposalIntent.current={signature,id:crypto.randomUUID()}; return send('/proposals',{kind,invoice_id:invoice,currency:'MZN',source:'TEST-MANUAL',external_id:values.external_id || proposalIntent.current.id,revision:kind==='obligation'?0:ob.revision,...(kind==='obligation'?{}:{obligation_id:ob.id}),...values}); } const pending = r => !ob?.effects.some(e=>e.record_id===r.id && e.kind!=='allocate'); return <>

SGF-RECEIVABLES-SIM-v1

Contas a receber — simulação

TESTE — SEM VALIDADE FISCAL

SIMULAÇÃO — SEM MOVIMENTAÇÃO DE DINHEIRO

Liquidar um documento e conferir um recebimento são operações distintas. Sem recibo fiscal, PDF novo, banco ou contabilidade.

{error&&

{error}

}

A vista conserva a revisão apresentada. Alterações entretanto feitas por outro utilizador provocam recusa e exigem actualização; os formulários não substituem valores silenciosamente.

{!data ?

A carregar…

: data.blocked ?

Percurso real indisponível

Este incremento só funciona na instância isolada de testes. As permissões não retiram o bloqueio.

: <> {allowed('prepare')&&

Preparar abertura explícita

propose('obligation',{invoice_id:Number(v.invoice_id),due_date:v.due_date||null})}>
}

Obrigações e confirmação

{data.obligations.length===0&&

Nenhuma obrigação aberta ou proposta. Documentos e rascunhos não criam dívida automaticamente.

}{ob&&<>

Documento #{origin.invoice_id} · Cliente #{origin.client_id} · MZN · Revisão {ob.revision} · Corte {ob.cut_date}

{[['Original',ob.original],['Redução sintética',ob.reduced],['Recebido confirmado',ob.received],['Afectado',ob.allocated],['Por afectar',ob.available],[ob.opened?'Saldo por receber':'Valor proposto — obrigação não aberta',ob.balance]].map(([label,value])=>
{label}

{money(value)} MZN

)}

Vencimento: {ob.due_date||'por definir'} · {({not_open:'Obrigação ainda não aberta',unknown:'Pendência — atraso não determinado',overdue:'Saldo vencido',not_due:'Ainda não vencido',settled:'Liquidada'})[ob.due_state]}

{pending(origin)&&allowed('confirm')&&send(`/${origin.id}/decision`,{version:origin.version,revision:ob.revision,decision,reason:'Decisão explícita sobre proposta sintética.'})}/>}}
{ob&&ob.opened&&<> {allowed('prepare')&&

Registar recebimento proposto

propose('receipt',v)}>