load(); \support\App::loadAllConfig(['route']); date_default_timezone_set('Asia/Shanghai'); use plugin\saiadmin\app\model\water\{WaterModel,WaterSku,WaterEnterprise,WaterAccount,WaterDelivery,WaterDriver,WaterDriverShift,WaterRoute,WaterLedger,WaterDeliveryEvent}; use plugin\saiadmin\app\service\{WaterBusiness,WaterDispatch}; use Tinywan\Jwt\JwtToken; $tag='HTTP-FULFILL-'.bin2hex(random_bytes(5)); $date=date('Y-m-d'); $checks=0; $skuId=null; $enterpriseId=null; $driverId=null; $routeIds=[]; $taskIds=[]; // A distinct short-lived client avoids replacing any user's single-device browser session. $token=JwtToken::generateToken(['id'=>1,'username'=>'admin','plat'=>'saiadmin','type'=>'web','client'=>$tag,'access_exp'=>120,'refresh_exp'=>120])['access_token']; function api(string $path,?array $data=null): array { global $token,$checks; $ch=curl_init('http://127.0.0.1:8787/core/water/'.$path); $options=[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>15,CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$token]]; if ($data!==null) $options += [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode($data)]; curl_setopt_array($ch,$options); $raw=curl_exec($ch); $status=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch); $result=json_decode($raw,true); if ($status!==200 || ($result['code']??null)!==200) throw new RuntimeException('HTTP '.$status.' '.$path.': '.($result['message']??'invalid response')); $checks++; return $result['data']; } function verify(bool $value,string $message): void { global $checks; if (!$value) throw new RuntimeException($message); $checks++; } try { WaterModel::transaction(function () use ($tag,$date,&$skuId,&$enterpriseId,&$driverId,&$taskIds,&$routeIds) { $b=new WaterBusiness(); $d=new WaterDispatch(); $skuId=$b->save('sku',['name'=>$tag,'liters'=>18.9],1)['id']; $b->stock(['sku_id'=>$skuId,'mode'=>'inbound','quantity'=>10,'reference'=>$tag,'note'=>'temporary HTTP fixture'],1); $enterpriseId=$b->save('enterprise',['name'=>$tag,'contact'=>'test','phone'=>'TEST','address'=>'TEST DO NOT DELIVER','region'=>$tag],1)['id']; WaterAccount::query()->insert(['enterprise_id'=>$enterpriseId,'sku_id'=>$skuId,'entitlement'=>10,'empty_qty'=>2,'updated_at'=>date('Y-m-d H:i:s')]); $taskIds[]=$b->createDelivery(['enterprise_id'=>$enterpriseId,'sku_id'=>$skuId,'quantity'=>4,'date'=>$date],1)['id']; $driverId=$d->saveDriver(['name'=>$tag,'phone'=>$tag,'region'=>$tag,'capacity'=>10],1)['id']; $d->shift(['driver_id'=>$driverId,'date'=>$date,'state'=>'on'],1); $routeIds=$d->commit(['date'=>$date,'request_key'=>$tag,'routes'=>[['driver_id'=>$driverId,'task_ids'=>$taskIds]]],1)['route_ids']; }); api('dispatch',['action'=>'depart','id'=>$taskIds[0]]); $receipt=['action'=>'receive','id'=>$taskIds[0],'accepted_qty'=>2,'empty_returned'=>2,'receiver'=>'HTTP test receiver','reason'=>'test partial receipt']; verify(api('dispatch',$receipt)['status']==='returning','partial status'); verify(api('dispatch',$receipt)['replayed']===true,'HTTP receipt replay'); $board=api('dispatch/board?keyword='.rawurlencode($tag)); verify($board['summary']['returning']===1,'HTTP exception filter visible'); api('dispatch',['action'=>'return_receive','id'=>$taskIds[0],'good_qty'=>1,'damaged_qty'=>1,'note'=>'test warehouse check']); $route=api('dispatch/route?id='.$routeIds[0]); verify($route['fulfillment']['can_settle']===true && $route['fulfillment']['empty_items'][0]['expected']===2,'HTTP route custody'); api('dispatch',['action'=>'route_settle','route_id'=>$routeIds[0],'items'=>[['sku_id'=>$skuId,'actual'=>2,'lost'=>0]],'note'=>'test warehouse handover']); $new=api('dispatch',['action'=>'redeliver','id'=>$taskIds[0],'date'=>$date,'note'=>'test reschedule']); $taskIds[]=$new['id']; verify((int)WaterDelivery::query()->find($new['id'])->redelivery_of===$taskIds[0],'HTTP linked retry'); api('dispatch',['action'=>'cancel','id'=>$new['id']]); verify(count(api('dispatch/history?id='.$taskIds[0]))>=3,'HTTP audit history'); $s=WaterSku::query()->find($skuId); $a=WaterAccount::query()->where('enterprise_id',$enterpriseId)->first(); verify((int)$s->available===7 && (int)$s->transit===0 && (int)$s->locked===0 && (int)$s->damaged_qty===1 && (int)$s->empty_qty===2,'HTTP full and empty stock reconciled'); verify((int)$a->entitlement===8 && (int)$a->frozen===0 && (int)$a->full_qty===2,'HTTP rights reconciled'); } finally { WaterModel::transaction(function () use ($tag,$skuId,$enterpriseId,$driverId) { if (!$skuId) return; $sku=WaterSku::query()->find($skuId); if (!$sku) return; if ($sku->name!==$tag) throw new RuntimeException('Fixture ownership changed; cleanup stopped'); $tasks=WaterDelivery::query()->where('sku_id',$skuId)->get(); if ($tasks->contains(fn($task)=>(int)$task->enterprise_id!==$enterpriseId)) throw new RuntimeException('Unexpected task attached to test SKU'); $ids=$tasks->pluck('id')->all(); $routes=$tasks->pluck('route_id')->filter()->unique()->all(); WaterDeliveryEvent::query()->whereIn('delivery_id',$ids)->orWhereIn('route_id',$routes)->delete(); WaterLedger::query()->where('sku_id',$skuId)->delete(); $refs=array_merge(array_map(fn($id)=>'route:'.$id,$routes),array_map(fn($id)=>'delivery:'.$id,$ids),['driver:'.$driverId]); WaterLedger::query()->where('kind','dispatch_config')->whereIn('reference',$refs)->delete(); WaterDelivery::query()->whereIn('id',$ids)->delete(); WaterRoute::query()->whereIn('id',$routes)->delete(); WaterAccount::query()->where('sku_id',$skuId)->delete(); if ($enterpriseId) WaterEnterprise::query()->where('id',$enterpriseId)->where('name',$tag)->delete(); if ($driverId) { WaterDriverShift::query()->where('driver_id',$driverId)->delete(); WaterDriver::query()->where('id',$driverId)->where('name',$tag)->delete(); } WaterSku::query()->where('id',$skuId)->where('name',$tag)->delete(); }); } verify(!WaterSku::query()->where('name',$tag)->exists() && !WaterEnterprise::query()->where('name',$tag)->exists(),'temporary fixtures removed'); echo "PASS: $checks local HTTP fulfillment checks; temporary fixtures removed; normal operation audit retained.\n";