17 lines
7.0 KiB
React
17 lines
7.0 KiB
React
import React,{useEffect,useState} from 'react';
|
|
import PayableDueWorkspace from './PayableDueWorkspace.jsx';
|
|
import {minorAmount} from './ledger-report-view.js';
|
|
import {documentStates,payableStates,payablePageSummary,payableAgeSummary,ageLabels,checkedPayablePage,payablePageCsv} from './payable-position-view.js';
|
|
const amount=v=>v===null?'Indeterminado':minorAmount(v);
|
|
export function PayablePositionRows({data,onDue}){return <div className="table-scroll"><table><thead><tr><th>Documento e fornecedor</th><th>Conferência / reconhecimento</th><th>Principal</th><th>Histórico confirmado / afectações</th><th>Saldo actual</th><th>Vencimento / pendências</th><th>Consultar</th></tr></thead><tbody>{data.positions.map(r=><tr key={r.document_id}><td>{r.reference}<small>{r.supplier}</small><small>{r.currency}</small></td><td>{documentStates[r.document_state]??r.document_state}<small>{payableStates[r.payment_state]??r.payment_state}</small></td><td>{amount(r.principal_minor)}</td><td>{amount(r.confirmed_payments_minor)} / {amount(r.allocated_payments_minor)}</td><td>{amount(r.balance_minor)}</td><td>Original: {r.due_on??'Vencimento desconhecido'}{r.reviewed_terms&&<small>Condição administrativa revista v{r.reviewed_terms.version}: {r.reviewed_terms.due_on??'Desconhecida'}. Consulte a referência de atraso e o histórico.</small>}<small>{r.days_overdue===null?'Atraso indeterminado pelo livro':r.days_overdue+' dia(s) segundo o livro'}</small>{r.reviewed_age_bucket&&<small>{ageLabels[r.reviewed_age_bucket]}{r.reviewed_age_days===null?'':' · '+r.reviewed_age_days+' dia(s) à referência '+data.aging_reference_on}</small>}{r.source_conflict&&<strong>Origem/histórico divergente: saldo não presumido.</strong>}<ul>{r.pending.map((p,i)=><li key={i}>{p}</li>)}</ul></td><td>{r.obligation_id?<><a href={'/finance/payables?document_id='+r.document_id}>Abrir esta obrigação e comprovativos</a>{onDue&&<button onClick={()=>onDue(r)}>Condições de vencimento e histórico</button>}</>:<span>Reconhecimento ainda pendente</span>}</td></tr>)}</tbody></table>{!data.positions.length&&<p>Nenhum documento corresponde aos filtros autorizados nesta página.</p>}</div>;}
|
|
export default function PayablePositionsWorkspace({context,api,aging=false}){
|
|
const[selected,setSelected]=useState(null);
|
|
const initial={search:'',document_state:'',per_page:'25',reference_on:''};const [form,setForm]=useState(initial),[query,setQuery]=useState({...initial,page:1}),[data,setData]=useState(null),[loading,setLoading]=useState(false),[error,setError]=useState('');
|
|
useEffect(()=>{let current=true;setData(null);setError('');if(aging&&!query.reference_on){setLoading(false);return()=>{current=false;};}setLoading(true);api('/api/finance/payable-positions?'+new URLSearchParams(Object.entries(query).filter(([,v])=>v!==''))).then(v=>{const result=checkedPayablePage(v,query);if(current)setData(result);}).catch(e=>{if(current)setError(e.message);}).finally(()=>{if(current)setLoading(false);});return()=>{current=false;};},[context.token,query,aging]);
|
|
const summary=data?payablePageSummary(data):null;const exportPage=()=>{const url=URL.createObjectURL(new Blob(['\uFEFF'+payablePageCsv(data,query)],{type:'text/csv;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download='sgf-fornecedores-pagina-'+data.pagination.page+'.csv';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);};
|
|
if(selected)return <PayableDueWorkspace row={selected} api={api} context={context} onClose={()=>setSelected(null)}/>;
|
|
return <section className="panel"><p className="eyebrow">FINANCEIRO</p><h1>{aging?'Mapa de antiguidade de fornecedores':'Posições de fornecedores'}</h1>{aging&&<p>Classificação do saldo actual segundo a condição administrativa revista e uma referência escolhida. Totais apenas da página, por moeda; não é saldo histórico, prazo legal ou recomendação de pagamento.</p>}<p>Saldo, pagamentos históricos e afectações provêm do mesmo livro de obrigações. Uma factura pendente ou sem reconhecimento não é dívida zero. Não são criados pagamentos ou lançamentos por esta consulta.</p><form data-sgf-read-filters="true" onSubmit={e=>{e.preventDefault();setQuery({...form,page:1});}}><div className="form-grid"><label>Referência para faixas administrativas<input type="date" required={aging} value={form.reference_on} onChange={e=>setForm({...form,reference_on:e.target.value})}/></label><label>Fornecedor ou número do documento<input type="search" maxLength={100} value={form.search} onChange={e=>setForm({...form,search:e.target.value})}/></label><label>Estado do documento<select value={form.document_state} onChange={e=>setForm({...form,document_state:e.target.value})}><option value="">Todos</option>{Object.entries(documentStates).map(([v,t])=><option key={v} value={v}>{t}</option>)}</select></label><label>Por página<select value={form.per_page} onChange={e=>setForm({...form,per_page:e.target.value})}><option value="25">25</option><option value="100">100</option></select></label></div><button disabled={loading}>Pesquisar</button><button type="button" disabled={loading} onClick={()=>setQuery(q=>({...q,page:1}))}>Actualizar consulta</button></form>
|
|
{loading&&<p role="status">A consultar a origem e o livro financeiro…</p>}{error&&<p role="alert">{error}. A indisponibilidade não significa saldo zero.</p>}{data&&<><p>{data.pagination.total} documento(s) · página {data.pagination.page} de {data.pagination.last_page} · leitura {data.observed_at}.</p><dl className="overview-definitions">{summary.totals.map(t=><div key={t.currency}><dt>Saldo conhecido apenas desta página</dt><dd>{t.value} {t.currency}</dd></div>)}<div><dt>Sem reconhecimento nesta página</dt><dd>{summary.unrecognized}</dd></div><div><dt>Saldo indeterminado nesta página</dt><dd>{summary.unknown}</dd></div></dl><>{query.reference_on&&<section><h2>Faixas apenas desta página · referência{data.aging_reference_on}</h2><dl className="overview-definitions">{payableAgeSummary(data).map(t=><div key={t.currency+'|'+t.bucket}><dt>{ageLabels[t.bucket]}</dt><dd>{t.value} {t.currency}</dd></div>)}</dl><p>Saldos indeterminados e documentos sem reconhecimento ficam fora das faixas monetárias; saldos liquidados não representam comprovativo bancário.</p></section>}</><PayablePositionRows data={data} onDue={setSelected}/><div className="actions"><button disabled={loading||data.pagination.page<=1} onClick={()=>setQuery(q=>({...q,page:q.page-1}))}>Anterior</button><button disabled={loading||data.pagination.page>=data.pagination.last_page} onClick={()=>setQuery(q=>({...q,page:q.page+1}))}>Seguinte</button><button onClick={exportPage}>Exportar apenas esta página CSV</button></div></>}
|
|
<p className="help">O número de documentos refere-se ao filtro completo; os saldos resumidos e o CSV referem-se apenas à página. Não se somam moedas diferentes. Cada página é uma leitura actual, sem fotografia global entre páginas ou corte histórico. O vencimento e o atraso permanecem indeterminados quando o livro não os sustenta. Um pagamento preparado não comprova execução bancária.</p></section>;
|
|
}
|