// Navigation and read models only. Financial values are copied from preserved sources. export const billingSections = [ {id:'billing_all',label:'Todos os documentos'}, {id:'service_invoices',label:'Facturas'}, {id:'billing_invoice_receipts',label:'Facturas-recibo',pending:'A emissão de facturas-recibo ainda não está implementada. Uma factura e um recebimento não serão reunidos automaticamente neste tipo de documento.'}, {id:'billing_receipts',label:'Recibos',pending:'O documento fiscal Recibo ainda não está implementado. O percurso existente de recebimentos e afectações de teste conserva a sua identidade própria.'}, {id:'billing_credit_notes',label:'Notas de crédito',pending:'A emissão de notas de crédito ainda não está implementada. Pedidos de correcção existentes não são notas de crédito e não alteram automaticamente a dívida, o stock ou o dinheiro.'}, {id:'billing_quotes',label:'Orçamentos',pending:'A proposta comercial ao cliente e a sua conversão em rascunho ainda não estão implementadas. Este orçamento comercial é distinto do controlo orçamental de Compras e do orçamento de custos industriais.'}, {id:'billing_drafts',label:'Rascunhos'}, ]; export function billingAccess(can,unit) { return (unit!==null && (can('service_invoices.view',unit)||can('billing_drafts.view',unit)||can('service_receivables.view',unit))) || can('issuer_documents.view')||can('client_documents.view')||(unit!==null&&can('draft_documents.view',unit)); } export function visibleBillingSections(can,unit) { if(!billingAccess(can,unit))return []; return billingSections.filter(s=>s.id==='service_invoices'?unit!==null&&can('service_invoices.view',unit) :s.id==='billing_drafts'?unit!==null&&can('billing_drafts.view',unit):true); } export function invoiceRow(record,detail) { const snapshot=detail.snapshot,doc=snapshot.document; return {key:`invoice:${record.id}`,id:record.id,type:'invoice',typeLabel:'Factura de teste',number:`${record.series_code} / ${record.number}`, client:doc.client?.name??null,nuit:doc.client?.data?.nuit??null,issuedDate:(snapshot.issued_local??record.issued_at)?.slice(0,10)??null, dueDate:null,total:snapshot.calculation?.total_document_proposed??null,currency:doc.data?.currency??null, series:record.series_code,author:null,state:'Emitido apenas em teste',balance:null,payment:'Indeterminado',detail}; } export function draftRow(record) { return {key:`draft:${record.id}`,id:record.id,type:'draft',typeLabel:'Rascunho',number:`Interno #${record.id}`,name:record.name, client:record.data?.client_name??null,nuit:null,issuedDate:null,dueDate:null,total:null,base:record.base_amount??null,currency:record.data?.currency??null, series:null,author:record.actor_id??null,state:record.workshop_commercial_state==='superseded'?'Histórico / substituído':record.workshop_commercial_state==='cancelled'?'Cancelado':record.internal_confirmation?'Confirmado internamente':'Rascunho', balance:null,payment:'Não aplicável',version:record.version,updatedAt:record.updated_at}; } export function filterBillingRows(rows,filters) { const normalise=v=>String(v??'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLocaleLowerCase('pt'); const query=normalise(filters.search).trim(); return rows.filter(r=>(!query||[r.number,r.name,r.client,r.nuit].some(v=>normalise(v).includes(query))) &&(!filters.type||r.type===filters.type)&&(!filters.currency||r.currency===filters.currency) &&(!filters.series||r.series===filters.series)&&(!filters.state||r.state===filters.state) &&(!filters.author||String(r.author??'')===filters.author) &&(!filters.from||(r.issuedDate&&r.issuedDate>=filters.from))&&(!filters.to||(r.issuedDate&&r.issuedDate<=filters.to))); } export async function loadBillingRows(api,can,unit) { const jobs=[]; if(unit!==null&&can('service_invoices.view',unit))jobs.push(['Facturas',async()=>{ const list=await api('/api/service-invoices'); if(!list.test_enabled)return {rows:[],notice:list.notice??'Documentos de teste indisponíveis neste ambiente.'}; const rows=[]; // Bound simultaneous requests while reusing the existing scoped immutable document endpoint. for(let start=0;startinvoiceRow(r,await api(`/api/service-invoices/${r.id}`))))); } return {rows}; }]); if(unit!==null&&can('billing_drafts.view',unit))jobs.push(['Rascunhos',async()=>({rows:(await api('/api/a/billing_drafts')).records.map(draftRow)})]); const sources=await Promise.all(jobs.map(async([label,job])=>{ try{return {label,...await job()};}catch(e){return {label,error:e.message,rows:[]};} })); return {rows:sources.flatMap(s=>s.rows),sources}; }