| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- <?php
- /** Inventory read-model regression: every fixture is rolled back. Optional --http only reads live data. */
- require dirname(__DIR__).'/vendor/autoload.php';
- \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
- \support\App::loadAllConfig(['route']);
- date_default_timezone_set('Asia/Shanghai');
- use plugin\saiadmin\app\model\water\{WaterSku,WaterAccount,WaterDelivery,WaterLedger};
- use plugin\saiadmin\app\service\{WaterBusiness,WaterInventory};
- use plugin\saiadmin\app\controller\water\WaterController;
- 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); }
- function workbook(string $content): array {
- $path=tempnam(sys_get_temp_dir(),'inventory-test-'); $reader=new \OpenSpout\Reader\XLSX\Reader();
- try { file_put_contents($path,$content); $reader->open($path); $rows=[]; foreach ($reader->getSheetIterator() as $sheet) foreach ($sheet->getRowIterator() as $row) $rows[]=$row->toArray(); return $rows; }
- finally { $reader->close(); unlink($path); }
- }
- $db=(new WaterSku())->getConnection(); $service=new WaterInventory(); $business=new WaterBusiness();
- $tag='INV-'.bin2hex(random_bytes(5)); $db->beginTransaction();
- try {
- $before=$service->dashboard([]); $ids=[];
- foreach (['healthy','alert','disabled'] as $i=>$state) {
- $id=$business->save('sku',['name'=>($i===0?'=':'').$tag.'-'.$state,'liters'=>18.9,'safety_stock'=>5,'status'=>$i===2?2:1],1)['id'];
- WaterSku::query()->where('id',$id)->update(['available'=>$i===0?20:5,'locked'=>2,'transit'=>3,'empty_qty'=>4,'damaged_qty'=>1,'empty_lost_qty'=>2]); $ids[]=$id;
- }
- $enterprise=$business->save('enterprise',['name'=>$tag,'contact'=>'test','phone'=>'TEST','address'=>'TEST'],1)['id'];
- WaterAccount::query()->insert(['enterprise_id'=>$enterprise,'sku_id'=>$ids[0],'empty_qty'=>7,'entitlement'=>20,'updated_at'=>date('Y-m-d H:i:s')]);
- WaterDelivery::query()->insert(['enterprise_id'=>$enterprise,'sku_id'=>$ids[0],'quantity'=>1,'address'=>'TEST','created_by'=>1,'empty_in_vehicle'=>6,'status'=>'completed','scheduled_date'=>date('Y-m-d'),'created_at'=>date('Y-m-d H:i:s'),'updated_at'=>date('Y-m-d H:i:s')]);
- $data=$service->dashboard(['keyword'=>' '.$tag.' ','page'=>1,'limit'=>2]); $next=$service->dashboard(['keyword'=>$tag,'page'=>2,'limit'=>2]);
- check($data['list']['total']===3 && count($data['list']['data'])===2 && count($next['list']['data'])===1,'search and pagination');
- check(array_column(array_merge($data['list']['data'],$next['list']['data']),'id')===array_reverse($ids),'stable page order');
- check(array_column(array_merge($data['list']['data'],$next['list']['data']),'stock_status')===['disabled','alert','healthy'],'disabled and safety equality status');
- check($data['summary']===$next['summary'],'global summary independent of page');
- check($data['summary']===$service->dashboard(['keyword'=>$tag.'-missing'])['summary'],'global summary independent of search');
- foreach (['specs'=>3,'available'=>30,'locked'=>6,'transit'=>9,'warehouse_empty'=>12,'damaged'=>3,'empty_lost'=>6,'alerts'=>1,'site_empty'=>7,'vehicle_empty'=>6,'empty_assets'=>25,'total'=>45] as $key=>$delta) check($data['summary'][$key]-$before['summary'][$key]===$delta,'aggregate delta '.$key);
- check(count($data['trend'])===7 && $data['trend'][0]['day']===date('Y-m-d',strtotime('-6 days')) && $data['trend'][6]['day']===date('Y-m-d'),'seven calendar days');
- $start=$data['trend'][0]['day']; $today=date('Y-m-d');
- foreach ([['inbound',11,$start.' 00:00:00'],['delivery_return',3,$today.' 23:59:59'],['depart',8,$today.' 12:00:00'],['warehouse_count',100,$today.' 12:00:00'],['empty_handover',200,$today.' 12:00:00'],['inbound',99,date('Y-m-d',strtotime('-7 days')).' 23:59:59'],['depart',99,date('Y-m-d',strtotime('+1 day')).' 00:00:00']] as [$kind,$qty,$time]) {
- WaterLedger::query()->insert(['sku_id'=>$ids[0],'enterprise_id'=>$enterprise,'kind'=>$kind,'quantity'=>$qty,'reference'=>$tag.'-'.$kind.'-'.$time,'created_by'=>1,'created_at'=>$time]);
- }
- $trend=$service->dashboard([])['trend'];
- for ($i=0;$i<7;$i++) {
- check($trend[$i]['inbound']-$data['trend'][$i]['inbound']===($i===0?11:($i===6?3:0)),'inbound calendar boundary '.$i);
- check($trend[$i]['outbound']-$data['trend'][$i]['outbound']===($i===6?8:0),'outbound excludes counts and outside dates '.$i);
- }
- $stock=['sku_id'=>$ids[0],'mode'=>'inbound','quantity'=>4,'reference'=>$tag.'-operation','note'=>'transactional test'];
- $business->stock($stock,1);
- check($service->dashboard([])['summary']['available']===$data['summary']['available']+4,'inbound reflected in dashboard');
- rejects(fn()=>$business->stock($stock,1),'duplicate inbound rejected');
- $business->stock(array_replace($stock,['mode'=>'warehouse_count','quantity'=>0]),1);
- check((int)WaterSku::query()->find($ids[0])->available===0,'zero warehouse count supported');
- $business->stock(array_replace($stock,['mode'=>'site_count','enterprise_id'=>$enterprise,'quantity'=>2]),1);
- $business->stock(array_replace($stock,['mode'=>'exchange','enterprise_id'=>$enterprise,'quantity'=>1]),1);
- $account=WaterAccount::query()->where('enterprise_id',$enterprise)->first();
- check((int)$account->full_qty===1 && (int)$account->empty_qty===8 && (int)$account->entitlement===20,'site count and exchange preserve rights');
- $xlsx=$service->export(['keyword'=>$tag,'page'=>2,'limit'=>1]); $book=workbook($xlsx);
- check(substr($xlsx,0,2)==='PK' && count($book)===4,'XLSX exports entire filtered result');
- check($book[3][0]==="'=".$tag.'-healthy','spreadsheet formula prefix escaped');
- check(count($book[0])===10 && in_array('破损隔离满桶',$book[0],true),'export includes custody fields');
- check(count(workbook($service->export(['keyword'=>$tag.'-missing'])))===1,'empty export has headers');
- rejects(fn()=>$service->dashboard(['keyword'=>str_repeat('字',121)]),'long dashboard keyword rejected');
- rejects(fn()=>$service->export(['keyword'=>str_repeat('字',121)]),'long export keyword rejected');
- $reflection=new ReflectionClass(WaterController::class); $controller=$reflection->newInstanceWithoutConstructor();
- $reflection->getProperty('adminId')->setValue($controller,99999999);
- $request=new \support\Request("GET /core/water/inventory/dashboard HTTP/1.1\r\nHost: localhost\r\n\r\n");
- rejects(fn()=>$controller->inventoryDashboard($request),'dashboard authorization');
- rejects(fn()=>$controller->inventoryExport($request),'export authorization');
- $reflection->getProperty('adminId')->setValue($controller,1);
- $body=json_encode(['keyword'=>$tag]);
- $request=new \support\Request("POST /core/water/inventory/export HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nContent-Length: ".strlen($body)."\r\n\r\n".$body);
- check(count(workbook($controller->inventoryExport($request)->rawBody()))===4,'JSON export controller');
- } finally { $db->rollBack(); }
- check(WaterSku::query()->where('name','like','%'.$tag.'%')->count()===0,'fixtures rolled back');
- if (in_array('--http',$argv,true)) {
- $token=\Tinywan\Jwt\JwtToken::generateToken(['id'=>1,'username'=>'admin','plat'=>'saiadmin','type'=>'web','client'=>$tag,'access_exp'=>120,'refresh_exp'=>120])['access_token'];
- foreach (['inventory/dashboard?limit=1','inventory/export'] as $path) {
- $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 ($path==='inventory/export') $options += [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>'{}'];
- curl_setopt_array($ch,$options); $raw=curl_exec($ch); $status=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
- check($status===200,'HTTP status '.$path);
- if ($path==='inventory/export') check(substr($raw,0,2)==='PK' && count(workbook($raw))>1,'HTTP XLSX body');
- else { $json=json_decode($raw,true); check(($json['code']??0)===200 && count($json['data']['trend']??[])===7 && count($json['data']['list']['data']??[])<=1,'HTTP dashboard contract'); }
- }
- unset($token);
- }
- echo "PASS: $checks inventory checks; fixtures rolled back.\n";
|