Files
SGF/scripts/sgf-hr-personnel-ui.mjs
2026-11-11 15:04:01 +02:00

13 lines
8.8 KiB
JavaScript

import fs from'node:fs';import assert from'node:assert/strict';import{pathToFileURL}from'node:url';import{transformWithOxc}from'vite';const root=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')).root+'/private/receipt-composition-cc50a276c10c',checks=[],ok=(v,n)=>{assert(v,n);checks.push(n);};let slots=[],cursor=0,effects=[],tree,calls=[],rootComponent,props;const same=(a,b)=>a&&b&&a.length===b.length&&a.every((v,i)=>v===b[i]);const R={createElement:(type,props,...children)=>({type,props:props??{},children}),Fragment:Symbol('fragment'),Suspense:Symbol('suspense'),lazy:()=>Symbol('lazy'),useRef(initial){const i=cursor++;slots[i]??={value:{current:initial}};return slots[i].value;},useState(initial){const i=cursor++;slots[i]??={value:initial};return[slots[i].value,v=>{slots[i].value=typeof v==='function'?v(slots[i].value):v;}];},useMemo(fn,deps){const i=cursor++;if(!same(slots[i]?.deps,deps))slots[i]={value:fn(),deps};return slots[i].value;},useEffect(fn,deps){const i=cursor++;if(!same(slots[i]?.deps,deps)){slots[i]?.cleanup?.();slots[i]={deps};effects.push(()=>slots[i].cleanup=fn());}}};globalThis.__hrHooks=R;const stub='const r=globalThis.__hrHooks;export default r;export const useState=r.useState,useMemo=r.useMemo,useEffect=r.useEffect,useRef=r.useRef;',reactUrl='data:text/javascript;base64,'+Buffer.from(stub).toString('base64'),compile=async(file,imports)=>{let s=fs.readFileSync('resources/js/'+file,'utf8').replace(/from\s*['"]react['"]/, 'from '+JSON.stringify(reactUrl));for(const f of imports)s=s.replace(new RegExp('from\\s*[\'"]\\./'+f.replaceAll('.','\\.')+'[\'"]'),'from '+JSON.stringify(pathToFileURL(process.cwd()+'/resources/js/'+f).href));const c=await transformWithOxc(s,file,{jsx:{runtime:'classic'}});return import('data:text/javascript;base64,'+Buffer.from(c.code).toString('base64'));};
const{default:Workspace,PersonnelSourcePicker,PersonnelResult,checkedPersonnel}=await compile('PersonnelWorkspace.jsx',['workshop-stock-operation.js','statement-download.js']);const{navigationGroups}=await compile('WorkspaceNavigation.jsx',['billing-menu.js','workshop-menu.js','stock-menu.js','accounting-menu.js']);const{moduleGroups}=await compile('ModuleNavigation.jsx',[]);
const wk='00000000-0000-4000-8000-000000000002',worker={record_key:wk,version:2,data:{status:'active',input:{code:'QA-SECOND-EXPLICIT'}}},row={id:99,kind:'contract',record_key:'00000000-0000-4000-8000-000000000009',version:1,state:'prepared',actor_id:999,prepared_by:999,created_at:'QA',reason:'Fixture UI sem operação de negócio',data:{input:{operation:'establish',worker_key:wk,reference:'QA-CONTRACT-UI',mode:'fixed',valid_from:'2026-11-01',valid_to:'2026-12-31',gross:'10000.00',evidence:'Fixture sem contrato real'},status:'active',result:{dependent_records:0},dependencies:{worker:{code:'QA-SECOND-EXPLICIT'}}},effective:null,impact:{before:{version:0,state:null},after:{version:1,state:'prepared'}}};
const response=q=>({policy:'SGF-HR-PERSONNEL-QA-v1',kind:q.kind,synthetic_only:true,salary_effect:false,worker:{record_key:q.worker_key},records:{data:q.kind==='contract'?[row]:[],current_page:Number(q.page),last_page:2,total:26},capabilities:{prepare:true,review:false}});let fail=true,commands=[];
const api=async(path,method,body,unused,key)=>{calls.push({path,method,body,key});if(method==='POST'&&path.endsWith('/preview'))return{preview:'opaque-fixture',impact:{before:{version:0,state:null},after:{version:1,state:'prepared',proposal:row.data},journals:0,payments:0}};if(method==='POST'){commands.push({path,body:structuredClone(body),key});if(fail){const e=Error('Resultado incerto');e.status=503;throw e;}return{id:100,version:1,state:'prepared',replayed:true};}const q=Object.fromEntries(new URL('http://qa'+path).searchParams);return response(q);};
const render=()=>{cursor=0;tree=rootComponent(props);const jobs=effects;effects=[];jobs.forEach(f=>f());return tree;},tick=async()=>{await new Promise(r=>setTimeout(r,0));render();},descend=n=>Array.isArray(n)?n.flatMap(descend):n&&typeof n==='object'?[n,...descend(n.children)]:[],nodes=(n=tree)=>descend(n),text=n=>Array.isArray(n)?n.map(text).join(''):n&&typeof n==='object'?text(n.children):String(n??''),find=(tag,label)=>nodes().find(n=>n.type===tag&&text(n).includes(label));
rootComponent=Workspace;props={api,context:{token:'hp-context',module_id:'human_resources',unit:{id:2}}};render();await tick();ok(calls.length===0,'no implicit first employee or personnel query');let picker=nodes().find(n=>n.type===PersonnelSourcePicker);picker.props.onChange(wk,worker);render();await tick();ok(calls[0].path.includes('worker_key='+wk),'chosen second worker exact scope in query');ok(text(tree).includes('QA-SECOND-EXPLICIT'),'selected worker identity visible');find('button','Seguinte').props.onClick();render();await tick();ok(calls.at(-1).path.includes('page=2')&&calls.at(-1).path.includes('worker_key='+wk),'pagination preserves exact worker');find('button','Escolher este registo').props.onClick();render();const resultNode=nodes().find(n=>n.type===PersonnelResult);ok(resultNode.props.data.input.reference==='QA-CONTRACT-UI','explicit chosen contract shown');find('button','Novo registo').props.onClick();render();ok(find('label','Referência interna QA').children.find(n=>n?.type==='input').props.value==='QA-','new form does not inherit chosen contract');ok(find('button','Planos e assiduidade').props.disabled,'form locks module function changes');const field=(label,value)=>find('label',label).children.find(n=>['input','textarea'].includes(n?.type)).props.onChange({target:{value}});field('Referência interna QA','QA-NEW-EXPLICIT');field('Início','2026-11-01');field('Fim explícito','2026-12-31');field('Bruto mensal','10000.00');field('Referência da evidência','Evidência fictícia individual');field('Fundamento individual','Fundamento QA sem contrato real');render();const gross=find('label','Bruto mensal').children.find(n=>n?.type==='input');ok(new RegExp('^'+gross.props.pattern+'$').test('10000.00'),'documentary gross browser pattern valid');await nodes().find(n=>n.type==='form'&&text(n).includes('Conferir impacto')).props.onSubmit({preventDefault(){}});render();ok(calls.at(-1).body.data.worker_key===wk&&calls.at(-1).body.data.reference==='QA-NEW-EXPLICIT','preview exact employee and form input');ok(Boolean(find('h2','Impacto antes da confirmação')),'impact visible before confirmation');await find('button','Confirmar esta decisão individual').props.onClick();render();ok(Boolean(find('button','Recuperar a mesma confirmação'))&&find('button','Pedidos e gozo').props.disabled,'uncertain503 keeps decision and blocks navigation');fail=false;await find('button','Recuperar a mesma confirmação').props.onClick();render();await tick();ok(commands.length===2&&JSON.stringify(commands[0])===JSON.stringify(commands[1]),'recovery uses same body path and key');ok(!find('button','Recuperar a mesma confirmação'),'successful receipt clears uncertainty');find('button','Pedidos e gozo').props.onClick();render();await tick();find('button','Novo registo').props.onClick();render();const sources=nodes().filter(n=>n.type===PersonnelSourcePicker&&n.props.kind!=='worker');ok(sources.length===3&&sources.every(n=>n.props.value===''),'contract entitlement and day explicitly unselected');
const q={kind:'contract',worker_key:wk,page:1,per_page:25},sample=response(q);assert.equal(checkedPersonnel(sample,q),sample);assert.throws(()=>checkedPersonnel({...sample,kind:'leave'},q));assert.throws(()=>checkedPersonnel({...sample,records:{...sample.records,data:[{...row,data:{...row.data,input:{...row.data.input,worker_key:'other'}}}]}},q));ok(true,'late wrong kind or worker response refused');const groups=navigationGroups(r=>r.startsWith('human_resources.'),2,{remunerations:true,personnel:true}),owned=moduleGroups(groups,'human_resources');ok(owned.length===1&&owned[0].items.some(i=>i.id==='hr_personnel'),'own personnel dropdown visible with rights');ok(moduleGroups(groups,'finance').length===0,'HR rights confer no finance menu');const denied=navigationGroups(r=>r==='human_resources.remunerations.view',2,{remunerations:true,personnel:true});ok(!denied.flatMap(g=>g.items).some(i=>i.id==='hr_personnel'),'salary read alone cannot open personnel area');
const report={input:{period:'2026-11',evidence:'Fixture'},dependencies:{worker:{code:'QA'}},status:'active',result:{known_days:2,known_conferred_days:2,other_calendar_days_unknown:28,days:[],leave_balances:[],payroll:[{record_key:'x',version:2,contract_link_proven:false}]}};const rt=PersonnelResult({kind:'report',data:report});ok(text(rt).includes('Mês completo: não demonstrado')&&text(rt).includes('Sem ligação contratual histórica comprovada'),'report explicit partial coverage and historical salary boundary');
const file=root+'/hp-ui-20261111.json';fs.writeFileSync(file,JSON.stringify({passed:true,checks,api_mocked:true,browser_visual_review:false},null,2));console.log(JSON.stringify({passed:true,checks:checks.length,proof:file}));