Files
SGF/scripts/sgf-human-resources-personnel-time-proof.php
2026-11-11 15:04:01 +02:00

7 lines
3.7 KiB
PHP

<?php
require __DIR__.'/sgf-single-bootstrap.php';$checks=[];$ok=function($v,$n)use(&$checks){if(!$v)throw new LogicException($n);$checks[]=$n;};$reject=function($fn,$status,$n)use($ok){try{$fn();throw new LogicException('Expected refusal');}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface$e){$ok($e->getStatusCode()===$status,$n);}};
use App\Modules\HumanResources\PersonnelTime as T;
$full=[['from'=>480,'to'=>720],['from'=>780,'to'=>1020]];$d=T::day('2026-11-16',$full,$full,[]);$ok($d['planned_minutes']===480&&$d['declared_minutes']===480&&$d['unaccounted_minutes']===0,'explicit split day480 complete declared');$ok($d['planned_utc'][0]['from']==='2026-11-16T06:00:00+00:00'&&$d['zone']===T::ZONE,'local plan UTC bounds explicit');$ok(!$d['salary_effect']&&!$d['overtime_authorized']&&$d['legal_classification']===null,'no financial or legal assumption');$d=T::day('2026-11-17',$full,[],$full);$ok($d['leave_minutes']===480&&$d['unaccounted_minutes']===0,'explicit no work and approved leave cover planned day');$ok(T::day('2026-11-17',$full,null,[])['unaccounted_minutes']===null,'missing declaration never absence zero');$ok(T::day('2026-11-17',null,[],[])['planned_minutes']===null,'missing plan never zero');$ok(T::day('2026-11-17',$full,[],null)['unaccounted_minutes']===null,'unknown leave conference remains unknown');$ok(T::day('2026-11-17',$full,[],[])['unaccounted_minutes']===480,'explicit absence of work and leave remains unaccounted no legal status');$ok(T::day('2026-11-17',$full,[['from'=>480,'to'=>1080]],[])['outside_plan_minutes']===120,'outside plan counted without approved overtime');$reject(fn()=>T::day('2026-11-17',$full,$full,$full),409,'work and leave conflict refused');$reject(fn()=>T::day('2026-11-17',$full,[],[['from'=>420,'to'=>480]]),409,'leave outside explicit plan refused');$reject(fn()=>T::day('2026-02-30',$full,[],[]),422,'invalid day refused');$reject(fn()=>T::intervals([['from'=>480,'to'=>800],['from'=>700,'to'=>900]]),409,'overlapping intervals refused');$reject(fn()=>T::intervals([['from'=>480,'to'=>480]]),422,'zero duration refused');$reject(fn()=>T::intervals([['from'=>480,'to'=>1600]]),422,'overnight requires explicit next day record');$ok(T::intervals([['from'=>600,'to'=>720],['from'=>480,'to'=>600]])===[['from'=>480,'to'=>720]],'adjacent ranges normalized deterministically');$b=T::leaveBalance(960,[['key'=>'QA-LEAVE-1','minutes'=>480,'state'=>'reserved']]);$ok($b['available_minutes']===480&&$b['reserved_minutes']===480&&$b['taken_minutes']===0,'approved request reserves480 of explicit960');$b=T::leaveBalance(960,[['key'=>'QA-LEAVE-1','minutes'=>480,'state'=>'taken']]);$ok($b['available_minutes']===480&&$b['reserved_minutes']===0&&$b['taken_minutes']===480,'enjoyment moves reservation to used without double count');$ok(T::leaveBalance(960,[['key'=>'QA-LEAVE-1','minutes'=>480,'state'=>'cancelled']])['available_minutes']===960,'cancellation releases balance');$ok(T::leaveBalance(null,[])['available_minutes']===null,'unknown entitlement never zero');$reject(fn()=>T::leaveBalance(100,[['key'=>'A','minutes'=>480,'state'=>'reserved']]),409,'insufficient entitlement refused');$reject(fn()=>T::leaveBalance(960,[['key'=>'A','minutes'=>480,'state'=>'reserved'],['key'=>'A','minutes'=>480,'state'=>'taken']]),409,'same request cannot count twice');
$ok(T::day('2026-11-09',[['to'=>720,'from'=>480]],[],[])['planned_minutes']===240,'JSON field order has no semantic effect');
file_put_contents($work.'/hr-personnel-time-core.json',json_encode(['passed'=>true,'checks'=>$checks,'pure_arithmetic_only'=>true,'business_writes'=>0,'new_grants'=>0,'installed_8085'=>false],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/hr-personnel-time-core.json']).PHP_EOL;