import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';import {spawnSync} from 'node:child_process'; import {Element,walk,parsePage,installDom,byName,setLabel} from './purchasing-workspace-dom.mjs'; const run=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')),root=path.join(run.root,'private/receipt-composition-cc50a276c10c'),s=JSON.parse(fs.readFileSync(path.join(root,'state.json'))),native=JSON.parse(fs.readFileSync(path.join(root,'native-fixture.json'))),base='http://127.0.0.1:8085'; assert(s.singleInstallation&&s.tag==='cc50a276c10c'&&s.env.DB_PORT==='55439');const progress=path.join(root,'completion-flow-progress.json'),out=path.join(root,'completion-flow-result.json');assert(!fs.existsSync(out)); const state=fs.existsSync(progress)?JSON.parse(fs.readFileSync(progress)):{steps:{},checks:[]},save=()=>fs.writeFileSync(progress,JSON.stringify(state,null,2));const ok=(v,name)=>{assert(v,name);if(!state.checks.includes(name))state.checks.push(name);save();}; const backup=path.join(root,'completion-backup');if(!fs.existsSync(backup)){fs.mkdirSync(backup);for(const [name,c]of [['host',{...s.env,database:s.env.DB_DATABASE,username:s.env.DB_USERNAME,password:s.env.DB_PASSWORD}],['native',s.nativeConnection]]){const r=spawnSync('C:/Program Files/PostgreSQL/17/bin/pg_dump.exe',['-h','127.0.0.1','-p','55439','-U',c.username,'-d',c.database,'-Fc','--no-owner','--no-acl','-f',path.join(backup,name+'.dump')],{env:{...process.env,PGPASSWORD:c.password},windowsHide:true,encoding:'utf8'});if(r.status!==0)throw Error('Private pre-flow backup failed');}} const originalFetch=globalThis.fetch,cookies=new Map,calls=[];let pending=0,document,epoch=0,loseReceipt=false; async function raw(url,options={}){const headers=new Headers(options.headers);headers.set('Cookie',[...cookies].map(([k,v])=>k+'='+v).join('; '));const r=await originalFetch(base+url,{...options,headers,redirect:'manual'});for(const cookie of r.headers.getSetCookie()){const pair=cookie.split(';')[0],at=pair.indexOf('=');cookies.set(pair.slice(0,at),pair.slice(at+1));}return r;} globalThis.fetch=async(url,options={})=>{assert(typeof url==='string'&&url.startsWith('/api/'),'Only existing local API calls');pending++;try{const r=await raw(url,options),data=await r.clone().json().catch(()=>({message:'Non-JSON response'})),call={url,method:options.method??'GET',status:r.status,data,input:options.body?JSON.parse(options.body):null};calls.push(call);if(loseReceipt&&r.status===201&&url.includes('/purchasing-receipts/')&&url.endsWith('/confirm')){loseReceipt=false;state.steps.receipt_committed=data;save();throw Error('Resposta interrompida após a confirmação.');}return r;}finally{pending--;}}; const delay=ms=>new Promise(resolve=>setTimeout(resolve,ms));async function until(test,label){const stop=Date.now()+45000;while(Date.now()pending===0,'previous requests');if(document)for(const e of walk(document.root))e.isConnected=false; const entry=await raw('/compras'),entryHtml=await entry.text();assert.equal(entry.status,200);const token=/name="_token" value="([^"]+)"/.exec(entryHtml)?.[1];assert(token,'Entry form CSRF token'); const target='/purchasing/'+stage+query;const r=await raw('/compras/entrar',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({_token:token,role,stage,...(query?{return_to:target}:{})})});assert.equal(r.status,302,'Explicit role selection failed');assert.equal(r.headers.get('location'),base+target); const screen=await raw(target),html=await screen.text();assert.equal(screen.status,200);document=parsePage(html);installDom(document);globalThis.location={search:query};const [file,id]=files[stage];assert(document.querySelector('#'+id));assert(html.includes('Entrada de Compras / trocar função')); const before=calls.length;await import('../public/'+file+'.mjs?completion='+Date.now()+'-'+epoch++);await until(()=>pending===0&&calls.length>before,'workspace initial read');await delay(30); if(calls.slice(before).some(c=>c.status>=400))throw Error('Workspace initial read refused: '+JSON.stringify(calls.slice(before).map(c=>({url:c.url,status:c.status,error:c.data.message})))); return document; } const detail=()=>document.querySelector('#detail')??document.querySelector('#process-detail')??document.querySelector('#award-detail'); const form=title=>walk(detail()).find(e=>e.tag==='form'&&e.textContent.includes(title));const button=(root,text)=>walk(root).find(e=>e.tag==='button'&&e._text.includes(text)); async function submit(f,key){assert(f,'Expected usable form '+key);f.dispatchEvent(new Event('input',{bubbles:true}));const before=calls.length;await f.onsubmit({preventDefault(){},currentTarget:f});const preview=calls.slice(before).find(c=>c.method==='POST'&&c.url.endsWith('/preview'));assert.equal(preview?.status,200,'Preview '+key+': '+JSON.stringify(preview?.data));const confirm=button(f,'Confirmar');assert(confirm&&!confirm.disabled,'Explicit confirmation enabled '+key);await confirm.onclick();const result=calls.slice(before).find(c=>c.method==='POST'&&c.url.endsWith('/confirm'));assert([200,201].includes(result?.status),'Confirmation '+key+': '+JSON.stringify(result?.data));await until(()=>pending===0,'refresh '+key);await delay(30);state.steps[key]=result.data;save();return result.data;} try{ if(!state.steps.supplier){await page('actor','suppliers');document.querySelector('#create').onclick();const f=form('Preparar fornecedor');for(const [name,value]of Object.entries({name:'Fornecedor Percurso Completo QA',nuit:'900110401',contact:'Contacto sintético',email:'percurso-completo@example.test',phone:'+258840000401',address:'Endereço sintético para conferência',category:'Material',beneficiaries:'Participação sintética declarada',document_references:'DOCUMENTO-QA-COMPRAS-20261104',diligence_notes:'Conferência individual do fornecedor para o percurso completo.',next_review:'2027-11-04'})){const input=byName(f,name);assert(input,name);input.value=value;}await submit(f,'supplier');ok(true,'C02 fornecedor novo criado pelo formulário real');} const supplier=state.steps.supplier.supplier_id; if(!state.steps.supplier_review){await page('issuer','suppliers','?supplier='+supplier);await until(()=>form('Decisão independente'),'supplier review');const f=form('Decisão independente');walk(f).find(e=>e.tag==='select').value='active';setLabel(f,'Fundamento','Fornecedor sintético conferido por identidade distinta para esta compra.');await submit(f,'supplier_review');ok(true,'C02 revisão distinta activa o fornecedor na mesma instalação');} if(!state.steps.request){await page('requester','requests');document.querySelector('#create').onclick();const f=form('Preparar requisição');setLabel(f,'Centro de custo',native.centre);setLabel(f,'Prioridade','normal');setLabel(f,'Necessário em','2026-12-04');setLabel(f,'Fundamento da necessidade','Material sintético para comprovar o percurso completo de Compras.');for(const [label,value]of [['Descrição','Material do percurso completo'],['Categoria','Material'],['Unidade','UN'],['Quantidade','1.000'],['Preço estimado','100.00'],['Taxa documental','0'],['Tipo de aquisição','consumable']])setLabel(f,label,value);await submit(f,'request');ok(true,'C03 necessidade criada pelo formulário sem preparar outra demonstração');} const request=state.steps.request.request_id; if(!state.steps.request_submit){await page('requester','requests','?request='+request);await until(()=>form('Submeter para aprovação'),'request submission');await submit(form('Submeter para aprovação'),'request_submit');} if(!state.steps.request_approval){await page('approver','requests','?request='+request);await until(()=>form('Decisão independente'),'request approval');const f=form('Decisão independente');setLabel(f,'Decisão','approved');setLabel(f,'Fundamento','Necessidade conferida e aprovada pela chefia sintética distinta.');await submit(f,'request_approval');ok(true,'C03 submissão e aprovação pela chefia usam a mesma necessidade');} for(let index=0;index<3;index++)if(!state.steps['quote'+index]){ await page('actor','sourcing','?request='+request);await until(()=>form('Registar proposta de fornecedor'),'quotation form');const f=form('Registar proposta de fornecedor');await until(()=>byName(f,'supplier_id')?.children.length>1&&pending===0,'supplier catalogue');const select=byName(f,'supplier_id'),supplierId=[supplier,...native.supplier_ids][index];assert(select.children.some(o=>String(o.value)===String(supplierId)),'Eligible reviewed supplier in actual picker');select.value=supplierId;select.onchange();for(const [name,value]of Object.entries({reference:'COMPRA-COMPLETA-P'+(index+1),delivery_days:'5',valid_until:'2026-12-04',payment_terms:'Condições sintéticas de pagamento após conferência',unit_price:String(100+index*20),discount_type:'none',discount_value:'0'})){const input=byName(f,name);assert(input,name);input.value=value;}byName(f,'no_conflict').checked=true;await submit(f,'quote'+index); }ok(true,'C04 três propostas incluem o fornecedor criado e revisto nesta instalação'); for(const role of ['actor','issuer'])for(let index=0;index<3;index++)if(!state.steps['evaluation-'+role+index]){ await page(role,'sourcing','?request='+request);await until(()=>form('Avaliar '),'evaluation forms');const id=state.steps['quote'+index].quotation_id,last=calls.filter(c=>c.url==='/api/purchasing-sourcing/requests/'+request&&c.status===200).at(-1),quote=last.data.quotes.find(q=>q.id===id);assert(quote);const f=form('Avaliar '+quote.supplier.name);assert(f,'Distinct evaluation available');for(const name of ['quality','history','payment'])byName(f,name).value=name==='history'?'5':'8';byName(f,'comment').value='Avaliação individual fundamentada da proposta para o percurso completo.';byName(f,'no_conflict').checked=true;await submit(f,'evaluation-'+role+index); }ok(true,'C04 seis avaliações individuais por duas identidades no mesmo processo'); if(!state.steps.award){await page('actor','sourcing','?request='+request);await until(()=>form('Propor adjudicação'),'selection available');const f=form('Propor adjudicação');for(const [name,value]of Object.entries({quotation_id:state.steps.quote0.quotation_id,justification:'Escolha expressa após comparação completa e avaliações distintas.',delivery_date:'2026-11-20',delivery_location:'Armazém sintético da unidade'}))byName(f,name).value=value;await submit(f,'award');} const award=state.steps.award.award_id; if(!state.steps.award_approval){await page('approver','sourcing','?request='+request);await until(()=>form('Decisão independente da adjudicação'),'award approval');const f=form('Decisão independente da adjudicação');byName(f,'decision').value='approved';byName(f,'comment').value='Adjudicação conferida distintamente pela chefia no processo completo.';await submit(f,'award_approval');ok(true,'C05 adjudicação fundamentada recebe aprovação distinta');} if(!state.steps.order){await page('actor','orders','?award='+award);await until(()=>button(detail(),'Conferir impacto da emissão'),'order issue');await button(detail(),'Conferir impacto da emissão').onclick();assert(!button(detail(),'Emitir esta ordem').disabled);const before=calls.length;await button(detail(),'Emitir esta ordem').onclick();const issued=calls.slice(before).find(c=>c.url.endsWith('/issue'));assert.equal(issued?.status,201,JSON.stringify(issued?.data));state.steps.order=issued.data;save();ok(true,'C05 ordem emitida pelo botão real a partir da adjudicação aprovada');} const order=state.steps.order.order.id; if(!state.steps.receipt){await page('validator','receipts','?order='+order);await until(()=>byName(detail(),'chosen'),'receipt form');const f=walk(detail()).find(e=>e.tag==='form'),chosen=byName(f,'chosen');chosen.checked=true;chosen.onchange();await until(()=>byName(f,'stock_item_id')?.children.some(o=>String(o.value)===String(native.stock))&&pending===0,'article catalogue');const select=byName(f,'stock_item_id');select.value=native.stock;select.onchange();for(const [name,value]of Object.entries({delivery_note:'GUIA-COMPRA-COMPLETA-20261104',received_on:'2026-11-04',warehouse_id:native.warehouse,received_quantity:'1.000',accepted_quantity:'1.000',quality_status:'compliant'}))byName(f,name).value=value; f.dispatchEvent(new Event('input',{bubbles:true}));await f.onsubmit({preventDefault(){}});const confirm=button(f,'Confirmar esta recepção');assert(!confirm.disabled);loseReceipt=true;await confirm.onclick();assert(state.steps.receipt_committed&&!confirm.disabled,'Uncertain result remains recoverable');await confirm.onclick();const results=calls.filter(c=>c.url==='/api/purchasing-receipts/orders/'+order+'/confirm');assert.deepEqual(results.map(c=>c.status),[201,200]);state.steps.receipt=results.at(-1).data;save();ok(true,'C06 recepção pelo formulário cria um movimento e recupera resposta perdida sem duplicar');} if(!state.steps.invoice){await page('issuer','invoices','?order='+order);await until(()=>form('Registar documento recebido'),'invoice registration');const f=form('Registar documento recebido');for(const [label,value]of [['Número da factura','FACT-COMPRA-COMPLETA-20261104'],['Data de emissão','2026-11-04'],['Base documental','100.00'],['Imposto documental','0.00'],['Total documental','100.00']])setLabel(f,label,value);const chosen=walk(f).find(e=>e.type==='checkbox');chosen.checked=true;chosen.onchange();setLabel(f,'Quantidade facturada','1.000');setLabel(f,'Fundamento desta conferência','Factura recebida confrontada com a ordem e a recepção do processo completo.');await submit(f,'invoice');} const invoice=state.steps.invoice.invoice_id; if(!state.steps.invoice_validation){await page('reviewer','invoices','?order='+order+'&invoice='+invoice);await until(()=>form('Validar conferência de'),'invoice validation');const f=form('Validar conferência de');setLabel(f,'Fundamento desta conferência','Conferência documental independente das quantidades e valores desta factura.');await submit(f,'invoice_validation');ok(true,'C07 documento recebido registado e validado por outra identidade');} if(!state.steps.obligation_prepare){await page('issuer','obligations','?invoice='+invoice);await until(()=>form('Conferir preparação'),'obligation preparation');const f=form('Conferir preparação');setLabel(f,'Fundamento individual','Fotografia da obrigação conferida com a factura validada deste processo.');await submit(f,'obligation_prepare');} if(!state.steps.obligation){await page('reviewer','obligations','?invoice='+invoice);const proposal=state.steps.obligation_prepare.proposal_id;await until(()=>button(detail(),'Conferir proposta #'+proposal),'proposal selection');button(detail(),'Conferir proposta #'+proposal).onclick();const f=form('Conferir reconhecimento');assert(f);setLabel(f,'Fundamento individual','Reconhecimento independente da obrigação da factura e da evidência de origem.');await submit(f,'obligation');ok(true,'C08 obrigação reconhecida por revisão distinta na mesma instalação');} await page('issuer','obligations','?invoice='+invoice);await until(()=>detail().textContent.includes('Responsabilidade única reconhecida'),'final recognised state');ok(!form('Conferir preparação'),'C08 documento reconhecido deixa de oferecer nova preparação');ok(detail().textContent.includes('Vencimento desconhecido'),'C08 vencimento ausente fica explícito sem data inventada'); state.phase='complete';state.source_ids={supplier,request,award,order,receipt:state.steps.receipt.receipt.id,movement:state.steps.receipt.movements[0].id,invoice,proposal:state.steps.obligation_prepare.proposal_id,obligation:state.steps.obligation.obligation_id};save(); fs.writeFileSync(out,JSON.stringify({passed:true,installation:'cc50a276c10c',port:8085,checks:state.checks,source_ids:state.source_ids,actual_workspace_modules:true,real_http:true,dom_simulation:true,new_database:false,new_server:false,new_demonstration:false,payments_created:0,visual_review:false},null,2));console.log(JSON.stringify({phase:state.phase,checks:state.checks.length,source_ids:state.source_ids})); }catch(error){fs.writeFileSync(path.join(root,'completion-flow-diagnostic.json'),JSON.stringify({error:error.message,phase:state.phase,steps:Object.keys(state.steps),status:document?.querySelector('#status')?.textContent,message:document?.querySelector('#message')?.textContent,calls:calls.slice(-12)},null,2));throw error;}finally{globalThis.fetch=originalFetch;}