Files
SGF/scripts/sgf-workshop-period-proof.php
2026-11-11 09:00:17 +02:00

14 lines
4.6 KiB
PHP

<?php
use Illuminate\Support\Facades\Auth;use Illuminate\Support\Str;use App\Shared\Context\CompanyContext;use App\Modules\Workshop\{WorkshopOrderRead,WorkshopWorkflow};
require __DIR__.'/sgf-ledger-proof-bootstrap.php';
set_exception_handler(function($e){echo json_encode(['passed'=>false,'type'=>get_class($e),'message'=>$e->getMessage()]).PHP_EOL;exit(1);});
$select=function($role)use($profiles,$f,$app){Auth::forgetGuards();Auth::shouldUse('web');$u=App\Models\User::findOrFail($profiles[$role]['user_id']);Auth::guard('web')->setUser($u);$r=Illuminate\Http\Request::create('/api/workshop/orders','GET');$r->attributes->set('sgf_module','workshop');$r->setUserResolver(fn()=>$u);$session=app('session')->driver();$session->start();$session->put('company_context',$f['scope']+['module_id'=>'workshop','token'=>'orders-'.$role]);$r->setLaravelSession($session);$app->instance('request',$r);app(CompanyContext::class)->selectFor($u->id,1,2,2);return $u->id;};
$reject=function($fn,$status,$name)use($ok){$actual=null;try{$fn();}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface$e){$actual=$e->getStatusCode();}catch(Illuminate\Validation\ValidationException){$actual=422;}$ok($actual===$status,$name);};
$tables=['wo_orders','wo_entries','wo_decisions','wo_events','permission_grants'];$before=[];foreach($tables as$t)$before[$t]=$host->table($t)->orderBy('id')->get()->toJson();$host->beginTransaction();try{
$actor=$select('workshop_manage');$reader=app(WorkshopOrderRead::class);$template=(array)$host->table('wo_orders')->first();unset($template['id']);$prefix='QA-OPENING-'.Str::uuid();$dates=['2026-11-09 21:59:59.999999+00','2026-11-09 22:00:00+00','2026-11-10 21:59:59.999999+00','2026-11-10 22:00:00+00',...array_fill(0,26,'2026-11-10 12:00:00+00')];$ids=[];foreach($dates as$i=>$date)$ids[]=$host->table('wo_orders')->insertGetId(array_replace($template,['reference'=>$prefix.'-'.$i,'created_at'=>$date]));
$query=['search'=>$prefix,'from'=>'2026-11-10','to'=>'2026-11-10'];$p1=$reader->listing($actor,$query+['per_page'=>25]);$p2=$reader->listing($actor,$query+['page'=>2,'per_page'=>25]);$p100=$reader->listing($actor,$query+['per_page'=>100]);$a=$p1['orders']->pluck('id')->all();$b=$p2['orders']->pluck('id')->all();$all=$p100['orders']->pluck('id')->all();$ok(count($a)===25&&count($b)===3&&count($all)===28,'SQL date filter before pagination25+3/100');$ok(array_merge($a,$b)===$all,'ordered pages no omission or duplicate');$ok(in_array($ids[1],$all,true)&&in_array($ids[2],$all,true),'local day includes start and instant before end');$ok(!in_array($ids[0],$all,true)&&!in_array($ids[3],$all,true),'local day excludes instant before start and exact next midnight');$ok($p1['opening_period']['start_utc']==='2026-11-09T22:00:00+00:00'&&$p1['opening_period']['end_utc_exclusive']==='2026-11-10T22:00:00+00:00','zone translated to explicit UTC half-open window');$ok($p1['opening_period']['basis']==='order_created_at_current_records','opening period not historical record cutoff');
$broad=$reader->listing($actor,array_replace($query,['from'=>'2026-11-09','to'=>'2026-11-11','per_page'=>100]));$ok($broad['orders']->total()===30,'inclusive multi-day range');$open=$reader->listing($actor,$query+['state'=>'open']);$ok($open['orders']->total()===28,'period combines existing state filter');$outside=$reader->listing($actor,array_replace($query,['from'=>'2026-11-12','to'=>'2026-11-12']));$ok($outside['orders']->total()===0,'empty selected day no fallback');$unfiltered=$reader->listing($actor,['search'=>$prefix]);$ok($unfiltered['orders']->total()===30&&$unfiltered['opening_period']===null,'original unfiltered list preserved');
foreach([['from'=>'2026-11-10'],['to'=>'2026-11-10'],['from'=>'2026-11-11','to'=>'2026-11-10'],['from'=>'2026-02-30','to'=>'2026-03-01']]as$bad)$reject(fn()=>$reader->listing($actor,$bad),422,'invalid period refused '.json_encode($bad));$tech=$select('workshop_record');$ok($reader->listing($tech,$query)['orders']->total()===0,'period does not bypass technician assignment');$actor=$select('workshop_manage');request()->attributes->set('sgf_module','finance');$reject(fn()=>$reader->listing($actor,$query),403,'period cannot cross module');
}finally{while($host->transactionLevel()>0)$host->rollBack();}
foreach($before as$t=>$json)$ok($host->table($t)->orderBy('id')->get()->toJson()===$json,'original '.$t.' unchanged');file_put_contents($work.'/workshop-period-result.json',json_encode(['passed'=>true,'checks'=>$checks,'sql_fixture_orders'=>30,'fixtures_rolled_back'=>true,'business_commands'=>0,'new_rights'=>0],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/workshop-period-result.json']).PHP_EOL;