| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- <?php
- require dirname(__DIR__).'/vendor/autoload.php';
- \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
- \support\App::loadAllConfig(['route']);
- date_default_timezone_set('Asia/Shanghai');
- use support\Db;
- use plugin\saiadmin\app\model\water\WaterModel;
- use plugin\saiadmin\app\service\WaterBusiness;
- use plugin\saiadmin\app\service\WaterDispatch;
- use plugin\saiadmin\exception\ApiException;
- $checks=0;
- function check(bool $ok,string $message): void { global $checks; $checks++; if (!$ok) throw new RuntimeException($message); }
- function rejects(callable $fn,string $message): void { try { $fn(); } catch (ApiException $e) { check(true,$message); return; } throw new RuntimeException($message); }
- $service=new WaterDispatch(); $business=new WaterBusiness(); $tag='DISPATCH-'.bin2hex(random_bytes(5)); $date=date('Y-m-d');
- $before=\plugin\saiadmin\app\model\water\WaterDelivery::query()->count();
- Db::beginTransaction();
- try {
- $sku=$business->save('sku',['name'=>$tag,'liters'=>18.9],1)['id'];
- $business->stock(['sku_id'=>$sku,'mode'=>'inbound','quantity'=>100,'reference'=>$tag,'note'=>'test'],1);
- $tasks=[]; $enterpriseIds=[];
- foreach ([4,6,8] as $i=>$qty) {
- $e=$business->save('enterprise',['name'=>$tag.'-'.$i,'contact'=>'测试','phone'=>'TEST','address'=>'测试地址','region'=>$tag,'longitude'=>121.5+$i*0.01,'latitude'=>31.2],1)['id'];
- $enterpriseIds[]=$e;
- \plugin\saiadmin\app\model\water\WaterAccount::query()->insert(['enterprise_id'=>$e,'sku_id'=>$sku,'entitlement'=>20,'threshold_qty'=>2,'updated_at'=>date('Y-m-d H:i:s')]);
- $tasks[]=$business->createDelivery(['enterprise_id'=>$e,'sku_id'=>$sku,'quantity'=>$qty],1)['id'];
- }
- $driver=$service->saveDriver(['name'=>$tag,'phone'=>$tag,'region'=>$tag,'capacity'=>10],1)['id'];
- $driver2=$service->saveDriver(['name'=>$tag.'b','phone'=>$tag.'b','region'=>$tag,'capacity'=>6],1)['id'];
- check($service->board(['date'=>$date,'keyword'=>$tag])['summary']['tasks']===3,'board tasks from existing delivery');
- check($service->board(['date'=>$date,'keyword'=>$tag])['summary']['buckets']===18,'board quantities from whole filtered scope');
- check($service->board(['date'=>$date,'keyword'=>$tag.'-1'])['summary']['tasks']===1,'enterprise search');
- check($service->board(['date'=>$date,'region'=>$tag])['summary']['tasks']===3,'region search');
- check($service->board(['date'=>$date,'keyword'=>$tag])['tasks'][0]['longitude']!==null,'enterprise location snapshotted');
- $service->shift(['driver_id'=>$driver,'date'=>$date,'state'=>'on'],1);
- $service->shift(['driver_id'=>$driver2,'date'=>$date,'state'=>'rest'],1);
- $service->schedule(['id'=>$tasks[1],'date'=>$date,'deadline_at'=>$date.' 09:00:00','longitude'=>121.51,'latitude'=>31.2],1);
- $plan=$service->plan(['date'=>$date,'keyword'=>$tag]);
- check(count($plan['routes'])===1 && $plan['routes'][0]['buckets']===10,'capacity bounded regional merge');
- check($plan['routes'][0]['task_ids']===[$tasks[1],$tasks[0]],'deadline prioritized');
- check(count($plan['unplanned'])===1 && (int)$plan['unplanned'][0]['id']===$tasks[2],'oversized remainder explained');
- $request=['date'=>$date,'request_key'=>$tag,'routes'=>[['driver_id'=>$driver,'task_ids'=>$plan['routes'][0]['task_ids']]]];
- rejects(fn()=>$service->commit(['date'=>$date,'request_key'=>$tag.'too','routes'=>[['driver_id'=>$driver,'task_ids'=>$tasks]]],1),'overcapacity rejected');
- rejects(fn()=>$service->commit(['date'=>$date,'request_key'=>$tag.'rest','routes'=>[['driver_id'=>$driver2,'task_ids'=>[$tasks[0]]]]],1),'resting driver rejected');
- $result=$service->commit($request,1); $routeId=$result['route_ids'][0];
- check(count($service->board(['date'=>$date,'keyword'=>$tag.'-1'])['tasks'])===1 && count($service->route(['id'=>$routeId])['tasks'])===2,'route detail preserves stations excluded by keyword');
- check(count($service->route(['id'=>$routeId])['tasks'])===2,'complete route detail');
- check($service->commit($request,1)['replayed']===true,'same request idempotent');
- rejects(fn()=>$service->commit(['date'=>$date,'request_key'=>$tag,'routes'=>[['driver_id'=>$driver,'task_ids'=>[$tasks[2]]]]],1),'request key cannot reuse different tasks');
- rejects(fn()=>$service->commit(['date'=>$date,'request_key'=>$tag.'stale','routes'=>$request['routes']],1),'stale proposal rejected');
- $skuRow=\plugin\saiadmin\app\model\water\WaterSku::query()->where('id',$sku)->first()?->toArray();
- check((int)$skuRow['locked']===18 && (int)$skuRow['available']===82,'merge does not freeze stock twice');
- check(\plugin\saiadmin\app\model\water\WaterDelivery::query()->where('route_id',$routeId)->where('status','assigned')->count()===2,'merge assigns tasks');
- $drivers=array_column($service->drivers($date),null,'id');
- check($drivers[$driver]['state']==='busy' && $drivers[$driver]['buckets']===10,'driver load linked to tasks');
- rejects(fn()=>$service->shift(['driver_id'=>$driver,'date'=>$date,'state'=>'rest'],1),'cannot rest while delivering');
- rejects(fn()=>$service->saveDriver(['id'=>$driver,'name'=>$tag,'phone'=>$tag,'region'=>$tag,'capacity'=>9],1),'cannot change capacity while delivering');
- rejects(fn()=>$service->schedule(['id'=>$tasks[0],'date'=>$date],1),'assigned schedule immutable');
- $business->transition(['id'=>$tasks[1],'action'=>'depart'],1);
- check((int)\plugin\saiadmin\app\model\water\WaterSku::query()->where('id',$sku)->value('transit')===6,'depart updates warehouse transit');
- $business->transition(['id'=>$tasks[1],'action'=>'complete','empty_returned'=>0],1);
- $account=\plugin\saiadmin\app\model\water\WaterAccount::query()->where('enterprise_id',$enterpriseIds[1])->first()?->toArray();
- check((int)$account['entitlement']===14 && (int)$account['full_qty']===6 && (int)$account['frozen']===0,'completion synchronizes entitlement and site stock');
- check($business->listing('alerts',['keyword'=>$tag.'-1'])['total']===0,'completion resolves quantity alert');
- check($service->board(['date'=>$date,'keyword'=>$tag])['summary']['completed']===1,'board completion summary synchronized');
- $business->transition(['id'=>$tasks[0],'action'=>'cancel'],1);
- check((int)\plugin\saiadmin\app\model\water\WaterSku::query()->where('id',$sku)->value('available')===86,'cancel releases warehouse stock');
- $drivers=array_column($service->drivers($date),null,'id');
- check($drivers[$driver]['state']==='busy','driver remains responsible until warehouse settlement');
- (new \plugin\saiadmin\app\service\WaterFulfillment())->settleRoute(['route_id'=>$routeId,'items'=>[['sku_id'=>$sku,'actual'=>0,'lost'=>0]],'note'=>'test return handover'],1);
- $drivers=array_column($service->drivers($date),null,'id');
- check($drivers[$driver]['state']==='available','driver available after route settlement');
- rejects(fn()=>$service->commit(['date'=>$date,'request_key'=>$tag.'duplicate','routes'=>[['driver_id'=>$driver,'task_ids'=>[$tasks[2],$tasks[2]]]]],1),'duplicate tasks rejected');
- $service->shift(['driver_id'=>$driver2,'date'=>$date,'state'=>'on'],1);
- $extra=$business->createDelivery(['enterprise_id'=>$enterpriseIds[0],'sku_id'=>$sku,'quantity'=>2],1)['id'];
- $routesBefore=\plugin\saiadmin\app\model\water\WaterRoute::query()->count();
- rejects(fn()=>$service->commit(['date'=>$date,'request_key'=>$tag.'atomic','routes'=>[['driver_id'=>$driver,'task_ids'=>[$extra]],['driver_id'=>$driver2,'task_ids'=>[$tasks[2]]]]],1),'later group overcapacity aborts all routes');
- check(\plugin\saiadmin\app\model\water\WaterDelivery::query()->where('id',$extra)->value('status')==='pending' && \plugin\saiadmin\app\model\water\WaterRoute::query()->count()===$routesBefore,'failed multi-route commit rolls back first assignment and route');
- $business->transition(['id'=>$extra,'action'=>'cancel'],1);
- check($service->commit($request,1)['replayed']===true,'completed request replay no duplicate assignment');
- $future=date('Y-m-d',strtotime('+1 day'));
- $service->schedule(['id'=>$tasks[2],'date'=>$future],1);
- check($service->board(['date'=>$date,'keyword'=>$tag])['summary']['tasks']===0,'future task excluded from today');
- $service->shift(['driver_id'=>$driver,'date'=>$future,'state'=>'on'],1);
- $service->commit(['date'=>$future,'request_key'=>$tag.'future','routes'=>[['driver_id'=>$driver,'task_ids'=>[$tasks[2]]]]],1);
- rejects(fn()=>$business->transition(['id'=>$tasks[2],'action'=>'depart'],1),'future route cannot depart early');
- foreach ([['date'=>'2026-02-30'],['date'=>'bad']] as $invalid) rejects(fn()=>$service->board($invalid),'invalid date rejected');
- rejects(fn()=>WaterDispatch::coordinates(['longitude'=>181,'latitude'=>20]),'invalid coordinates rejected');
- rejects(fn()=>WaterDispatch::coordinates(['longitude'=>121]),'unpaired coordinates rejected');
- $reflection=new ReflectionClass(\plugin\saiadmin\app\controller\water\WaterController::class);
- $controller=$reflection->newInstanceWithoutConstructor(); $reflection->getProperty('adminId')->setValue($controller,0);
- $http=new \support\Request("GET /core/water/dispatch/board HTTP/1.1\r\nHost: localhost\r\n\r\n");
- foreach (['dispatchBoard','dispatchPlan','dispatchRoute'] as $method) rejects(fn()=>$controller->$method($http),'dispatch endpoint requires authorization');
- } finally { Db::rollback(); }
- check(\plugin\saiadmin\app\model\water\WaterDelivery::query()->count()===$before,'fixtures rolled back');
- echo "PASS: $checks dispatch checks; all fixtures rolled back.\n";
|