import fs from'node:fs';import assert from'node:assert/strict';import crypto from'node:crypto';import{session}from'./sgf-single-http.mjs'; const root=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')).root+'/private/receipt-composition-cc50a276c10c',progressFile=root+'/hr-http-progress-private.json',progress=fs.existsSync(progressFile)?JSON.parse(fs.readFileSync(progressFile)):{keys:{worker:crypto.randomUUID(),rules:crypto.randomUUID(),payroll:crypto.randomUUID()},steps:{}},save=()=>fs.writeFileSync(progressFile,JSON.stringify(progress)),checks=[],ok=(v,n)=>{assert(v,n);checks.push(n);};save(); const prep=await session('hr:hr_prepare','human_resources'),review=await session('hr:hr_review','human_resources'),reader=await session('hr:hr_read','human_resources');for(const [role,s,count]of[['prepare',prep,4],['review',review,4],['read',reader,3]]){ok(JSON.stringify(s.identity.context.modules.map(m=>m.id))==='["human_resources"]','only HR module for '+role);ok(s.identity.context.grants.length===count&&s.identity.context.grants.every(g=>['company.view','unit.view','human_resources.remunerations.view','human_resources.remunerations.'+role].includes(g.permission)),'exact own profile grants '+role);ok(s.identity.context.remunerations_enabled===true,'HR feature available '+role);} const get=async(q,s=reader)=>{const r=await s.get('/api/remunerations?'+new URLSearchParams(q));assert.equal(r.status,200);return r.json();},input=(kind,version,data,action='prepare')=>({kind,record_key:progress.keys[kind],version,action,reason:'Conferência individual de remuneração fictícia, sem efeito financeiro real.',data}); async function run(step,s,body){let p=progress.steps[step];if(p?.result){ok(true,'existing accepted result reused '+step);return p.result;}p??={key:crypto.randomUUID(),body};assert.deepEqual(p.body,body);progress.steps[step]=p;save();const recovered=await s.get('/api/remunerations/receipts/'+p.key);if(recovered.status===200){p.result=(await recovered.json()).result;save();ok(true,'uncertain commit recovered by receipt '+step);return p.result;}assert.equal(recovered.status,404);const preview=await s.command('/api/remunerations/preview',body,p.key);assert.equal(preview.status,200,JSON.stringify(preview.value));ok(preview.value.impact.journals===0&&preview.value.impact.payments===0&&!preview.value.impact.debt_created,'preview no financial effects '+step);p.preview=preview.value.preview;save();const out=await s.command('/api/remunerations/confirm',{preview:p.preview},p.key);assert.equal(out.status,200,JSON.stringify(out.value));p.result=out.value;save();ok(out.value.state===(body.action==='prepare'?'prepared':'approved')&&out.value.version===body.version+1,'individual persisted version '+step);const receipt=await s.get('/api/remunerations/receipts/'+p.key);assert.equal(receipt.status,200);ok((await receipt.json()).result.id===out.value.id,'own command receipt '+step);return out.value;} const worker={operation:'establish',code:'QA-COLABORADOR-01',status:'active'},rules={operation:'establish',label:'Regras artificiais do exercício QA',valid_from:'2026-01-01',valid_to:'2026-12-31',worker_rate:'3',employer_rate:'4',retention_rate:'8',source:'Parâmetros artificiais de ensaio, sem validade legal de INSS ou IRPS.'},payroll={operation:'establish',worker_key:progress.keys.worker,rules_key:progress.keys.rules,period:'2026-11',gross:'10000.00'}; for(const[kind,data]of[['worker',worker],['rules',rules],['payroll',payroll]]){await run(kind+'-prepare',prep,input(kind,0,data));await run(kind+'-review',review,input(kind,1,{decision:'approved'},'review'));} const rows=await get({kind:'payroll',page:1,per_page:25}),row=rows.records.data.find(r=>r.record_key===progress.keys.payroll);assert(row);const c=row.data.calculation;ok(c.net_cents==='890000'&&c.employer_cost_cents==='1040000'&&c.deductions_cents==='110000','actual frozen synthetic amounts');ok(c.rubrics.worker_rate.cents==='30000'&&c.rubrics.employer_rate.cents==='40000'&&c.rubrics.retention_rate.cents==='80000','actual three rubrics exact');ok(!rows.legal_payroll_validated&&rows.synthetic_only&&!c.legal_calculation_validated,'not legal IRPS INSS');ok(rows.page_totals.approved_active_on_page===1&&rows.page_totals.net_cents==='890000'&&rows.totals_basis==='effective_approved_active_payroll_on_this_page_only','known page-only totals');const large=await get({kind:'payroll',page:1,per_page:100});ok(JSON.stringify(large.records.data)===JSON.stringify(rows.records.data),'actual25/100 same results');const hist=await get({kind:'payroll',record_key:progress.keys.payroll,page:1,per_page:25});ok(hist.records.data.length===2&&hist.records.data.map(r=>r.version).join(',')==='2,1','two preserved versions in history');ok((await get({kind:'payroll',period:'2026-11',search:'QA-COLABORADOR-01',state:'approved',page:1,per_page:25})).records.total===1,'period state and worker search combined SQL');ok((await get({kind:'payroll',period:'2026-10',page:1,per_page:25})).records.total===0,'other month empty no fallback');ok((await get({kind:'payroll',page:100000,per_page:25})).records.data.length===0,'far page empty'); let r=await reader.get('/api/remunerations/exports/'+row.id);assert.equal(r.status,200);const bytes=Buffer.from(await r.arrayBuffer()),sha=crypto.createHash('sha256').update(bytes).digest('hex');ok(/text\/csv/.test(r.headers.get('content-type'))&&r.headers.get('x-content-sha256')===row.data.artifact_sha256&&sha===row.data.artifact_sha256,'authenticated artifact MIME header and bytes SHA');ok(bytes.toString().includes('NAO E RECIBO SALARIAL LEGAL')&&bytes.toString().includes('890000'),'artifact actual values and boundary');fs.writeFileSync(root+'/hr-frozen-export-private.csv',bytes); const denied=await reader.command('/api/remunerations/preview',input('worker',2,{...worker,operation:'amend'}));ok(denied.status===403,'HR reader cannot prepare');const own=await prep.command('/api/remunerations/preview',input('payroll',2,{decision:'approved'},'review'));ok(own.status===403,'HR preparer cannot review');r=await review.get('/api/remunerations/receipts/'+progress.steps['payroll-prepare'].key);ok(r.status===404,'other actor cannot recover command');for(const q of[{kind:'payroll',tenant_id:2},{kind:'payroll',per_page:26},{kind:'worker',record_key:crypto.randomUUID()}]){r=await reader.get('/api/remunerations?'+new URLSearchParams(q));ok(r.status===(q.record_key?403:422),'invalid or foreign query refused '+JSON.stringify(q));}r=await reader.get('/api/remunerations/exports/2147483647');ok(r.status===403,'foreign export refused');r=await reader.get('/api/cash');ok([403,409].includes(r.status),'HR cannot read finance');r=await reader.get('/api/general-ledger/trial-balance');ok([403,409].includes(r.status),'HR cannot read accounting');const finance=await session('sgf:finance_prepare','finance');ok(!finance.identity.context.modules.some(m=>m.id==='human_resources'),'financial profile no HR module');r=await finance.get('/api/remunerations?kind=worker');ok([403,409].includes(r.status),'finance cannot read HR');r=await fetch('http://127.0.0.1:8085/api/remunerations?kind=worker',{headers:{Accept:'application/json'}});ok(r.status===401,'unauthenticated read refused');r=await reader.get('/api/remunerations/confirm',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({preview:'invalid'})});ok(r.status===419,'CSRF required'); const entry=await fetch('http://127.0.0.1:8085/sgf'),html=await entry.text();ok(entry.status===200&&html.includes('data-presentation-module="human_resources"')&&html.includes('QA Recursos Humanos'),'entry has own HR dropdown and profiles');const manifest=JSON.parse(fs.readFileSync(root+'/app/public/build/manifest.json'));for(const e of Object.values(manifest).filter(v=>v.isEntry||v.file.includes('RemunerationsWorkspace')||v.file.includes('statement-download'))){r=await fetch('http://127.0.0.1:8085/build/'+e.file);const b=Buffer.from(await r.arrayBuffer());ok(r.status===200&&/javascript/.test(r.headers.get('content-type'))&&crypto.createHash('sha256').update(b).digest('hex')===crypto.createHash('sha256').update(fs.readFileSync(root+'/app/public/build/'+e.file)).digest('hex'),'served JS MIME SHA '+e.file);} const file=root+'/hr-http-20261111.json';fs.writeFileSync(file,JSON.stringify({passed:true,checks,individual_commands:6,real_salary_payments:0,new_journals:0,browser_visual_review:false},null,2));console.log(JSON.stringify({passed:true,checks:checks.length,proof:file}));