commit melhorias sgf
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {selectedReceivable} from './receivable-position-view.js';
|
||||
|
||||
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);
|
||||
export default function ServiceReceivablesWorkspace({context, can, api, busy, initialObligationId=null}) {
|
||||
const [data,setData]=useState(null), [error,setError]=useState(''), [working,setWorking]=useState(false), [obId,setObId]=useState(initialObligationId===null?'':String(initialObligationId)), [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 ob=selectedReceivable(data?.obligations,obId);
|
||||
const record=id=>data?.records.find(r=>r.id===Number(id));
|
||||
const origin=ob && record(ob.id);
|
||||
const receipts=ob ? Object.values(ob.receipts) : [];
|
||||
@@ -32,10 +33,10 @@ export default function ServiceReceivablesWorkspace({context, can, api, busy}) {
|
||||
}
|
||||
const pending = r => !ob?.effects.some(e=>e.record_id===r.id && e.kind!=='allocate');
|
||||
return <>
|
||||
<section className="panel"><p className="eyebrow">SGF-RECEIVABLES-SIM-v1</p><h1>Contas a receber — simulação</h1><div className="alert error"><strong>TESTE — SEM VALIDADE FISCAL</strong><p>SIMULAÇÃO — SEM MOVIMENTAÇÃO DE DINHEIRO</p></div><p>Liquidar um documento e conferir um recebimento são operações distintas. Sem recibo fiscal, PDF novo, banco ou contabilidade.</p>{error&&<p role="alert" className="alert error">{error}</p>}<button disabled={disabled} onClick={()=>load().catch(e=>setError(e.message))}>Actualizar dados explicitamente</button><p className="help">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.</p></section>
|
||||
<section className="panel"><p className="eyebrow">FINANCEIRO</p><h1>Recebimentos, afectações e comprovativos</h1><div className="alert error"><strong>TESTE — SEM VALIDADE FISCAL</strong><p>SIMULAÇÃO — SEM MOVIMENTAÇÃO DE DINHEIRO</p></div><p>Liquidar um documento e conferir um recebimento são operações distintas. A emissão do recibo documental e a conferência contabilística têm percursos próprios; esta página não executa transferências bancárias nem lança automaticamente.</p>{error&&<p role="alert" className="alert error">{error}</p>}<button disabled={disabled} onClick={()=>load().catch(e=>setError(e.message))}>Actualizar dados explicitamente</button><p className="help">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.</p></section>
|
||||
{!data ? <p>A carregar…</p> : data.blocked ? <section className="panel"><h2>Percurso real indisponível</h2><p>Este incremento só funciona na instância isolada de testes. As permissões não retiram o bloqueio.</p></section> : <>
|
||||
{allowed('prepare')&&<section className="panel"><h2>Preparar abertura explícita</h2><SmallForm submit="Propor obrigação" disabled={disabled} onSubmit={v=>propose('obligation',{invoice_id:Number(v.invoice_id),due_date:v.due_date||null})}><label>Documento exacto<select name="invoice_id" required>{data.documents.map(d=><option key={d.id} value={d.id}>#{d.id} · {d.series_code}/{d.number}</option>)}</select></label><label>Vencimento conhecido (opcional)<input type="date" name="due_date"/></label></SmallForm></section>}
|
||||
<section className="panel"><h2>Obrigações e confirmação</h2>{data.obligations.length===0&&<p>Nenhuma obrigação aberta ou proposta. Documentos e rascunhos não criam dívida automaticamente.</p>}<label>Obrigação<select value={ob?.id||''} onChange={e=>{setObId(e.target.value);setProof(null);}}>{data.obligations.map(o=><option key={o.id} value={o.id}>#{o.id} · {record(o.id)?.external_id} · {o.state==='proposed'?'Aguarda confirmação':o.state==='refused'?'Recusada':o.state==='settled'?'Liquidada':'Em aberto'}</option>)}</select></label>{ob&&<><p>Documento #{origin.invoice_id} · Cliente #{origin.client_id} · MZN · Revisão {ob.revision} · Corte {ob.cut_date}</p><div className="summary-grid">{[['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])=><section key={label}><span>{label}</span><h2>{money(value)} MZN</h2></section>)}</div><p>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]}</p>{pending(origin)&&allowed('confirm')&&<Decision record={origin} disabled={disabled} decide={decision=>send(`/${origin.id}/decision`,{version:origin.version,revision:ob.revision,decision,reason:'Decisão explícita sobre proposta sintética.'})}/>}</>}</section>
|
||||
<section className="panel"><h2>Obrigações e confirmação</h2>{data.obligations.length===0&&<p>Nenhuma obrigação aberta ou proposta. Documentos e rascunhos não criam dívida automaticamente.</p>}<label>Obrigação<select value={ob?.id||''} onChange={e=>{setObId(e.target.value);setProof(null);}}><option value="">Escolha explicitamente uma posição</option>{data.obligations.map(o=><option key={o.id} value={o.id}>#{o.id} · {record(o.id)?.external_id} · {o.state==='proposed'?'Aguarda confirmação':o.state==='refused'?'Recusada':o.state==='settled'?'Liquidada':'Em aberto'}</option>)}</select></label>{data&&obId&&!ob&&<p role="alert">A posição escolhida não está disponível nesta consulta. Não foi seleccionada outra posição.</p>}{ob&&<><p>Documento #{origin.invoice_id} · Cliente #{origin.client_id} · MZN · Revisão {ob.revision} · Corte {ob.cut_date}</p><div className="summary-grid">{[['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])=><section key={label}><span>{label}</span><h2>{money(value)} MZN</h2></section>)}</div><p>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]}</p>{pending(origin)&&allowed('confirm')&&<Decision record={origin} disabled={disabled} decide={decision=>send(`/${origin.id}/decision`,{version:origin.version,revision:ob.revision,decision,reason:'Decisão explícita sobre proposta sintética.'})}/>}</>}</section>
|
||||
{ob&&ob.opened&&<>
|
||||
{allowed('prepare')&&<section className="panel"><h2>Registar recebimento proposto</h2><SmallForm submit="Propor recebimento" disabled={disabled} onSubmit={v=>propose('receipt',v)}><label>Valor recebido (MZN; ponto decimal)<input name="amount" inputMode="decimal" placeholder="400.00" required/></label><label>Data declarada<input name="date" type="date" required/></label><label>Identificador do acontecimento sintético<input name="external_id" required/></label><label>Evidência sintética<textarea name="evidence" required/></label></SmallForm></section>}
|
||||
<section className="panel"><h2>Recebimentos e afectações</h2>{receipts.map(rc=>{const r=record(rc.id); return <article className="panel" key={rc.id}><h3>Recebimento #{rc.id} · {r.external_id}</h3><p>{({proposed:'Aguarda confirmação',confirmed:'Confirmado em teste',refused:'Recusado',reversed:'Revertido'})[rc.state]} · Recebido {money(rc.amount)} · Afectado {money(rc.allocated)} · Por afectar {money(rc.available)} MZN</p><p>{rc.reconciled?'Reconciliado em simulação':'Sem reconciliação simulada confirmada'}</p><button onClick={()=>setProof(rc.id)}>Consultar comprovativo interno</button>{pending(r)&&allowed('confirm')&&<Decision record={r} disabled={disabled} decide={decision=>send(`/${r.id}/decision`,{version:r.version,revision:ob.revision,decision,reason:'Confirmação de evidência exclusivamente sintética.'})}/ >}{rc.state==='confirmed'&&allowed('allocate')&&<SmallForm submit="Afectar explicitamente" disabled={disabled} onSubmit={v=>send(`/${rc.id}/allocate`,{amount:v.amount,version:r.version,revision:ob.revision})}><label>Montante a afectar a este documento<input name="amount" inputMode="decimal" required/></label></SmallForm>}</article>;})}</section>
|
||||
|
||||
Reference in New Issue
Block a user