import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import React from 'react'; import {renderToStaticMarkup} from 'react-dom/server'; import {createServer} from 'vite'; const vite=await createServer({server:{middlewareMode:true},appType:'custom'}); try{ console.log('Checking billing modules and rendering…'); const model=await vite.ssrLoadModule('/resources/js/billing-menu.js'); const {navigationGroups}=await vite.ssrLoadModule('/resources/js/WorkspaceNavigation.jsx'); const {BillingDocumentList,default:Workspace}=await vite.ssrLoadModule('/resources/js/BillingMenuWorkspace.jsx'); const workshop=await vite.ssrLoadModule('/resources/js/workshop-menu.js'); const {default:WorkshopMenu}=await vite.ssrLoadModule('/resources/js/WorkshopMenuWorkspace.jsx'); const context={token:'synthetic',company:{name:'Empresa sintética'},unit:{id:7,name:'Unidade sintética'}}; const grants=[['service_invoices.view',7],['billing_drafts.view',7],['billing_drafts.create',7],['service_receivables.view',7],['issuer_documents.view',null]]; const can=(p,u=null)=>grants.some(g=>g[0]===p&&g[1]===u); assert.deepEqual(navigationGroups(can,7).find(g=>g.id==='billing').items.map(i=>i.label),['Todos os documentos','Facturas','Facturas-recibo','Recibos','Notas de crédito','Orçamentos','Rascunhos']); assert(!navigationGroups(()=>false,7).some(g=>g.id==='billing')); assert(!navigationGroups(can,8).flatMap(g=>g.items).some(i=>['service_invoices','billing_drafts'].includes(i.id))); assert(navigationGroups(can,7).flatMap(g=>g.items).some(i=>i.id==='service_invoice_setup')); const techCan=(p,u=null)=>u===7&&['workshop.view','workshop.record'].includes(p); const workshopCan=(p,u=null)=>techCan(p,u)||can(p,u)||(['clients.view','company.view','services.view'].includes(p)&&u===null); assert.equal(workshop.visibleWorkshopSections(workshopCan,7).length,12); assert(!workshop.visibleWorkshopSections(techCan,7).some(s=>['workshop_finance','workshop_clients'].includes(s.id))); assert.deepEqual(workshop.visibleWorkshopSections(techCan,8),[]); const orders=[{id:1,reference:'OS-SIM-1',client_id:5,state:'open',assigned_to:12,equipment:{id:4,identifier:'BOMBA-SIM',description:'Bomba sintética',serial:'SER-SIM'}},{id:2,reference:'OS-SIM-2',client_id:5,state:'records_verified',assigned_to:12,equipment:{id:4,identifier:'BOMBA-SIM',description:'Bomba sintética',serial:'SER-SIM'}}]; assert.deepEqual(workshop.filterWorkshopOrders(orders,'sintetica','open').map(o=>o.id),[1]); assert.equal(workshop.filterWorkshopOrders(orders,'SER-SIM').length,2); assert.deepEqual(workshop.visibleWorkshopEquipment(orders).map(e=>e.orders),[[1,2]]); for(const section of workshop.workshopSections.filter(s=>s.pending)){ const pending=renderToStaticMarkup(React.createElement(WorkshopMenu,{section:section.id,context,can:workshopCan,api:()=>{},navigate:()=>{}})); assert(pending.includes('Capacidade ainda não disponível'));assert(!pending.includes('Saldo inicial')); } const invoice=model.invoiceRow({id:1,series_code:'TEST-A',number:4,issued_at:'2026-10-29T10:00:00Z'},{snapshot:{issued_local:'2026-10-29 12:00:00',document:{client:{name:'João sintético',data:{nuit:'SIM-001'}},data:{currency:'MZN'}},calculation:{total_document_proposed:'11600.00'}},document_lines:[],corrections:[]}); const draft=model.draftRow({id:2,name:'Proposta sintética',version:3,data:{client_name:'Cliente sintético',currency:'USD'},base_amount:'10.000000000001',actor_id:9}); assert.equal(invoice.total,'11600.00');assert.equal(invoice.balance,null);assert.equal(invoice.dueDate,null);assert.equal(draft.total,null);assert.equal(draft.base,'10.000000000001'); const rows=[invoice,draft]; for(const [filters,expected] of [[{search:'joao'},['invoice:1']],[{search:'SIM-001'},['invoice:1']],[{currency:'USD'},['draft:2']],[{from:'2026-10-01'},['invoice:1']],[{to:'2026-10-01'},[]],[{author:'9'},['draft:2']],[{series:'TEST-A',type:'draft'},[]]])assert.deepEqual(model.filterBillingRows(rows,filters).map(r=>r.key),expected); const calls=[]; const loaded=await model.loadBillingRows(async path=>{calls.push(path);if(path==='/api/a/billing_drafts')return {records:[]};throw new Error('fixture outage');},can,7); assert.equal(loaded.sources.find(s=>s.label==='Facturas').error,'fixture outage');assert.equal(loaded.sources.find(s=>s.label==='Rascunhos').error,undefined); assert.deepEqual(await model.loadBillingRows(()=>{throw Error('Unauthorised fetch');},()=>false,7),{rows:[],sources:[]}); const html=renderToStaticMarkup(React.createElement(BillingDocumentList,{rows,filters:{search:'',type:'',currency:'',series:'',state:'',author:'',from:'',to:''},onFilter:()=>{},onClear:()=>{},onOpen:()=>{}})); for(const text of ['11600.00','10.000000000001','Consultar percurso financeiro','Por determinar','SIM-001','MZN','USD','Limpar filtros'])assert(html.includes(text)); for(const section of model.billingSections.filter(s=>s.pending)){ const pending=renderToStaticMarkup(React.createElement(Workspace,{section:section.id,context,can,api:()=>{},navigate:()=>{}})); assert(pending.includes('Capacidade ainda não disponível'));assert(!pending.includes('Confirmar emissão')); } // Actual existing isolated SGF host: use the established evaluation entry, no credentials. console.log('Checking existing isolated HTTP sources…'); const cookies=new Map(); async function request(path,method='GET',body){ const r=await fetch('http://127.0.0.1:8002'+path,{signal:AbortSignal.timeout(15000),method,redirect:'manual',headers:{Connection:'close',Cookie:[...cookies].map(([k,v])=>`${k}=${v}`).join('; '),...(body?{'Content-Type':'application/x-www-form-urlencoded'}:{})},...(body?{body:new URLSearchParams(body)}:{})}); for(const c of r.headers.getSetCookie()){const pair=c.split(';',1)[0],i=pair.indexOf('=');cookies.set(pair.slice(0,i),pair.slice(i+1));} const text=await r.text();return {status:r.status,text}; } const landing=await request('/avaliar');assert.equal(landing.status,200);const csrf=landing.text.match(/name="_token" value="([^"]+)"/)?.[1];assert(csrf); assert.equal((await request('/avaliar/entrar','POST',{role:'preparador',_token:csrf})).status,302); const session=JSON.parse((await request('/api/session')).text),unit=session.context.unit.id; const actualCan=(p,u=null)=>session.context.grants.some(g=>g.permission===p&&g.unit_id===u); const actual=await model.loadBillingRows(async path=>{const r=await request(path);assert.equal(r.status,200,path);return JSON.parse(r.text);},actualCan,unit); assert(actual.sources.every(s=>!s.error));assert(actual.rows.length>0); for(const row of actual.rows.filter(r=>r.type==='invoice'))assert.equal(row.total,row.detail.snapshot.calculation.total_document_proposed); const actualWorkshop=JSON.parse((await request('/api/workshop')).text);assert.equal(actualWorkshop.blocked,false); assert(actualWorkshop.orders.every(o=>o.equipment&&o.reference&&o.state)); const logout=await fetch('http://127.0.0.1:8002/api/logout',{method:'POST',headers:{Connection:'close',Accept:'application/json',Cookie:[...cookies].map(([k,v])=>`${k}=${v}`).join('; '),'X-CSRF-TOKEN':session.csrf}});assert.equal(logout.status,200); await fs.writeFile('docs/proofs/billing-menu.json',JSON.stringify({checked_at:new Date().toISOString(),navigation_sections:7,workshop_sections:12,permission_and_unit_checks:true,technician_financial_menu_hidden:true,equipment_identity_preserved:true,filters_checked:true,source_outage_distinguished:true,exact_document_values_preserved:true,actual_host:'http://127.0.0.1:8002',source_counts:actual.sources.map(s=>({source:s.label,count:s.rows.length})),visible_workshop_orders:actualWorkshop.orders.length,business_commands:0,visual_review:false,publication:false},null,2)+'\n'); console.log('Billing menu passed: seven sections, scoped navigation, exact values, combined filters, unknown balances/dates, unavailable sources and actual isolated host reads. Not a browser visual review.'); }finally{await vite.close();}