15 lines
5.5 KiB
React
15 lines
5.5 KiB
React
import React,{useEffect,useState} from 'react';
|
|
const labels={invoice:'Factura de teste',invoice_receipt:'Factura-recibo',receipt:'Recibo',credit_note:'Nota de crédito',quote:'Orçamento',draft:'Rascunho'};
|
|
const states={issued:'Emitido em teste',cancelled:'Anulado tecnicamente',draft:'Rascunho',approved:'Aprovado',rejected:'Recusado',refused:'Recusado',customer_accepted:'Aceite pelo cliente',customer_declined:'Recusado pelo cliente',converted:'Convertido',confirmed_internal:'Confirmado internamente',superseded:'Histórico / substituído'};
|
|
export default function BillingRegisterWorkspace({api,context,navigate,section}){
|
|
const initial={kind:section==='service_invoices'?'invoice':'',search:'',currency:'',series:'',author:'',state:'',from:'',to:'',per_page:'25'};
|
|
const [form,setForm]=useState(initial),[query,setQuery]=useState({...initial,page:1}),[result,setResult]=useState(null),[error,setError]=useState(''),[loading,setLoading]=useState(false);
|
|
useEffect(()=>{setForm(initial);setQuery({...initial,page:1});setResult(null);},[context.token,section]);
|
|
useEffect(()=>{let current=true;setLoading(true);setError('');setResult(null);const p=new URLSearchParams(Object.entries(query).filter(([,v])=>v!==''));api('/api/service-documents/register?'+p).then(v=>{if(current)setResult(v);}).catch(e=>{if(current)setError(e.message);}).finally(()=>{if(current)setLoading(false);});return()=>{current=false;};},[context.token,query]);
|
|
const input=(key,value)=>setForm(v=>({...v,[key]:value}));const rows=result?.documents?.data??[],p=result?.documents;
|
|
return <section className="panel"><p className="eyebrow">COMERCIAL</p><h1>{section==='service_invoices'?'Facturas':'Todos os documentos'}</h1><p>Consulta unificada por empresa, unidade e módulo. Factura-recibo representa a sua factura de origem uma única vez nesta lista. Rascunhos e orçamentos não são facturas.</p>
|
|
<form onSubmit={e=>{e.preventDefault();setQuery({...form,page:1});}}><div className="billing-filters"><label>Tipo<select value={form.kind} onChange={e=>input('kind',e.target.value)}><option value="">Todos os autorizados</option>{Object.entries(labels).map(([v,t])=><option key={v} value={v}>{t}</option>)}</select></label><label>Número, cliente ou NUIT<input value={form.search} maxLength={100} onChange={e=>input('search',e.target.value)}/></label><label>Moeda<select value={form.currency} onChange={e=>input('currency',e.target.value)}><option value="">Todas</option><option value="MZN">MZN</option></select></label><label>Série exacta<input value={form.series} maxLength={100} onChange={e=>input('series',e.target.value)}/></label><label>Autor registado<input type="number" min="1" value={form.author} onChange={e=>input('author',e.target.value)}/></label><label>Estado<select value={form.state} onChange={e=>input('state',e.target.value)}><option value="">Todos</option>{Object.entries(states).map(([v,t])=><option value={v} key={v}>{t}</option>)}</select></label><label>Emissão desde<input type="date" value={form.from} onChange={e=>input('from',e.target.value)}/></label><label>Emissão até<input type="date" value={form.to} onChange={e=>input('to',e.target.value)}/></label><label>Por página<select value={form.per_page} onChange={e=>input('per_page',e.target.value)}><option value="25">25</option><option value="100">100</option></select></label></div><button disabled={loading}>Aplicar filtros</button><button type="button" disabled={loading} onClick={()=>{setForm(initial);setQuery({...initial,page:1});}}>Limpar filtros</button></form>
|
|
{error&&<p role="alert">{error}</p>}{loading&&<p role="status">A consultar…</p>}{p&&<><p role="status">{p.total} registo(s); página {p.current_page} de {p.last_page}.</p><div className="table-scroll"><table><thead><tr><th>Referência / tipo</th><th>Cliente</th><th>Emissão</th><th>Montante documental</th><th>Estado</th><th>Saldo da factura de origem</th><th>Consultar</th></tr></thead><tbody>{rows.map(r=><tr key={r.kind+':'+r.source_id}><td>{r.reference}<small>{labels[r.kind]} · versão {r.version}</small></td><td>{r.client??'Por determinar'}{r.nuit&&<small>NUIT {r.nuit}</small>}</td><td>{r.document_on??'Não emitido'}</td><td>{r.amount!==null?`${r.amount} ${r.currency}`:r.base!==null?`Base ${r.base} ${r.currency}; imposto por conferir`:'Não determinado'}</td><td>{states[r.document_state]??r.document_state}</td><td>{r.balance_available?`${r.origin_invoice_balance} MZN`:r.invoice_id?'Sem consulta financeira autorizada ou sem reconhecimento':'Não aplicável'}</td><td>{r.pdf_url&&<a href={r.pdf_url} target="_blank" rel="noopener">PDF de teste</a>}{r.print_url&&<a href={r.print_url} target="_blank" rel="noopener">Orçamento desta versão</a>}{r.kind==='draft'&&<button onClick={()=>navigate('billing_drafts',r.source_id)}>Abrir rascunho</button>}{['receipt','credit_note','invoice_receipt'].includes(r.kind)&&<a href={'/commercial/documents?kind='+r.kind}>Histórico e decisões</a>}</td></tr>)}</tbody></table></div>{rows.length===0&&<p>Nenhum documento corresponde aos filtros autorizados.</p>}<button disabled={loading||p.current_page<=1} onClick={()=>setQuery(q=>({...q,page:q.page-1}))}>Anterior</button><button disabled={loading||p.current_page>=p.last_page} onClick={()=>setQuery(q=>({...q,page:q.page+1}))}>Seguinte</button></>}
|
|
<p className="help">Sem validade fiscal. Os montantes de facturas, recibos, créditos e bases não são somados. O saldo mostrado é o da factura de origem, podendo repetir-se em documentos ligados; não representa dinheiro recebido nem saldo próprio do recibo. Datas de emissão excluem rascunhos e orçamentos não emitidos.</p></section>;
|
|
}
|