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

7 lines
5.4 KiB
PHP

<?php
$book='8085';require __DIR__.'/sgf-single-bootstrap.php';$pointer=json_decode(file_get_contents($s['root'].'/hr-personnel-backup-pointer.json'),true);$before=json_decode(file_get_contents($pointer['root'].'/preservation-private.json'),true);$checks=[];$same=[];$append=[];$ok=function($v,$n)use(&$checks){if(!$v)throw new LogicException($n);$checks[]=$n;};$p=json_decode(file_get_contents($repo.'/docs/integration/sgf-human-resources-personnel-access-proposal.json'),true);$profiles=$host->table('hr_qa_profiles')->get()->keyBy('profile_key');$order=fn($t)=>in_array($t,['sr_outbox','sr_deliveries'],true)?'event_id':(in_array($t,['sc_catalogue_meta','pd_due_meta','fo_meta','fs_meta','rm_meta'],true)?'policy':(in_array($t,['hr_qa_profiles','sgf_presentation_profiles'],true)?'profile_key':(in_array($t,['fs_results','fs_result_lines'],true)?'snapshot_key':($t==='company_memberships'?'user_id':'id'))));
foreach(['host','native']as$name){$db=$name==='host'?$host:$native;foreach($before[$name]as$table=>$json){$rows=$db->table($table)->orderBy($order($table))->when($table==='fs_result_lines',fn($q)=>$q->orderBy('account_id'))->get();if($name==='host'&&$table==='permission_grants'){$old=collect(json_decode($json,true))->keyBy('id');$ok($rows->filter(fn($r)=>$old->has($r->id))->values()->toJson()===$json,'all old permissions identical');$new=$rows->reject(fn($r)=>$old->has($r->id))->values();$expected=[];foreach($p['grants_per_book']as$g)$expected[]=['user_id'=>$profiles[$g['profile_key']]->user_id,'tenant_id'=>1,'company_id'=>2,'unit_id'=>2,'permission'=>$g['permission']];$actual=$new->map(fn($r)=>['user_id'=>$r->user_id,'tenant_id'=>$r->tenant_id,'company_id'=>$r->company_id,'unit_id'=>$r->unit_id,'permission'=>$r->permission])->all();$sort=fn($a,$b)=>strcmp($a['user_id'].'/'.$a['permission'],$b['user_id'].'/'.$b['permission']);usort($expected,$sort);usort($actual,$sort);$ok($actual===$expected&&count($actual)===15,'only15 exact approved grants to existing HR profiles');$append[]='host.permission_grants';}else{$ok($rows->toJson()===$json,'original identical '.$name.'.'.$table);$same[]=$name.'.'.$table;}}}
$older=json_decode(file_get_contents(json_decode(file_get_contents($s['root'].'/remunerations-backup-pointer.json'),true)['root'].'/preservation-private.json'),true);foreach(['binding'=>'binding.json','single_binding'=>'single-bindings.json','stock_binding'=>'stock-binding.json']as$key=>$f)$ok(hash_file('sha256',$s['root'].'/'.$f)===$older[$key],$f.' unchanged from accepted pre-HR baseline');$originalSalary=json_decode($host->table('rm_versions')->where(['kind'=>'payroll','state'=>'approved'])->first()->data,true);$ok($originalSalary['artifact_csv']===file_get_contents($s['root'].'/hr-frozen-export-private.csv'),'old salary artifact remains byte-identical');
$versions=$host->table('hp_versions')->orderBy('id')->get();$ok($versions->count()===16&&$host->table('hp_commands')->count()===16,'exactly16 individual versions and receipts');$ok($versions->groupBy('kind')->map->count()->all()===['contract'=>2,'day'=>6,'entitlement'=>2,'leave'=>4,'report'=>2],'six sources with expected histories');$prepare=$profiles['hr_prepare']->user_id;$review=$profiles['hr_review']->user_id;$ok($versions->pluck('actor_id')->all()===array_merge(...array_fill(0,8,[$prepare,$review])),'separate preparer reviewer for all8 decisions');$report=json_decode($versions->where('kind','report')->last()->data,true);$ok($report['result']['known_days']===2&&!$report['result']['complete_month_proven'],'only two explicit days no complete month claim');$bal=$report['result']['leave_balances'][0];$ok($bal['available_minutes']===480&&$bal['taken_minutes']===480&&$bal['reserved_minutes']===0,'actual480 available480 taken0 reserved');$ok(file_get_contents($s['root'].'/hp-frozen-period-private.csv')===$report['artifact_csv']&&hash('sha256',$report['artifact_csv'])===$report['artifact_sha256'],'frozen period download equals conserved bytes');$ok($host->table('rm_versions')->count()===6&&$host->table('rm_commands')->count()===6,'original six salary versions and receipts unchanged');$ok($native->table('gl_journals')->count()===27&&$native->table('gl_accounts')->where(['tenant_id'=>1,'company_id'=>2,'unit_id'=>2])->count()===19,'27journals19accounts unchanged');$public=$host->select("SELECT c.relname FROM pg_class c CROSS JOIN LATERAL aclexplode(coalesce(c.relacl,CASE WHEN c.relkind='S' THEN acldefault('S',c.relowner) ELSE acldefault('r',c.relowner) END)) a WHERE c.relnamespace='public'::regnamespace AND c.relname IN('hp_meta','hp_versions','hp_commands','hp_versions_id_seq','hp_commands_id_seq') AND a.grantee=0");$ok($public===[],'no PUBLIC table or sequence grants');$pubfun=$host->select("SELECT a.* FROM pg_proc p CROSS JOIN LATERAL aclexplode(coalesce(p.proacl,acldefault('f',p.proowner))) a WHERE p.pronamespace='public'::regnamespace AND p.proname='hp_append_only' AND a.grantee=0");$ok($pubfun===[],'no PUBLIC append-only function access');
file_put_contents($work.'/hp-preservation.json',json_encode(['passed'=>true,'checks'=>$checks,'identical_original_tables'=>$same,'original_rows_preserved_with_authorized_append'=>$append,'new_grants'=>15,'new_profiles'=>0,'new_personnel_versions'=>16,'new_salary_versions'=>0,'new_journals'=>0,'cache_cleared'=>false,'protected_six_targeted'=>false],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'identical_original_tables'=>count($same),'proof'=>$work.'/hp-preservation.json']).PHP_EOL;