load(); \support\App::loadAllConfig(['route']); date_default_timezone_set('Asia/Shanghai'); use plugin\saiadmin\app\model\water\WaterModel; use plugin\saiadmin\app\model\water\WaterSku; use plugin\saiadmin\app\model\water\WaterAccount; use plugin\saiadmin\app\model\water\WaterEnterprise; use plugin\saiadmin\app\model\water\WaterDelivery; use plugin\saiadmin\app\model\water\WaterOrder; use plugin\saiadmin\app\model\water\WaterLedger; use plugin\saiadmin\app\service\WaterBusiness; use plugin\saiadmin\app\service\WaterDispatch; $batch = 'TEST-WATER-DISTRICTS-20260911'; $date = '2026-09-11'; $apply = in_array('--apply',$argv,true); $origin = ['origin_address'=>'深圳市南山区南油第四工业区4栋(测试出发点)', 'origin_longitude'=>113.924140,'origin_latitude'=>22.522806]; // 各区内人工模拟选点,仅供地图和调度测试,不代表真实企业或已核实的门牌位置。 $districts = [ ['name'=>'罗湖','code'=>'LH','stops'=>[ ['name'=>'东门商贸','quantity'=>4,'longitude'=>114.117000,'latitude'=>22.547000], ['name'=>'翠竹办公','quantity'=>6,'longitude'=>114.130000,'latitude'=>22.563000], ]], ['name'=>'福田','code'=>'FT','stops'=>[ ['name'=>'中心商务','quantity'=>5,'longitude'=>114.057000,'latitude'=>22.543000], ['name'=>'车公庙研发','quantity'=>7,'longitude'=>114.025000,'latitude'=>22.536000], ]], ['name'=>'宝安','code'=>'BA','stops'=>[ ['name'=>'新安科技','quantity'=>6,'longitude'=>113.883000,'latitude'=>22.554000], ['name'=>'西乡创意','quantity'=>8,'longitude'=>113.863000,'latitude'=>22.578000], ]], ]; $dispatch = new WaterDispatch(); $before = $dispatch->board(['date'=>$date])['summary']; $replayed = false; if ($apply) { $replayed = WaterModel::transaction(function () use ($batch,$date,$origin,$districts,$dispatch) { $sku = WaterSku::query()->where('name','【测试0910】18.9L桶装水')->lock(true)->first()?->toArray(); if (!$sku || (int)$sku['status'] !== 1) throw new RuntimeException('缺少启用的指定测试商品'); if (WaterLedger::query()->where(['kind'=>'demo_seed','reference'=>$batch])->exists()) return true; if (date('Y-m-d') !== $date) throw new RuntimeException('固定日期测试批次仅允许当天首次执行'); $business = new WaterBusiness(); $skuId = (int)$sku['id']; $oldTasks = WaterDelivery::query()->orderBy('id')->get()->toArray(); $oldAccounts = WaterAccount::query()->orderBy('id')->get()->toArray(); $oldOrders = WaterOrder::query()->orderBy('id')->get()->toArray(); $total = array_sum(array_map(fn($d)=>array_sum(array_column($d['stops'],'quantity')),$districts)); $business->stock(['sku_id'=>$skuId,'mode'=>'inbound','quantity'=>$total,'reference'=>$batch.'-STOCK', 'note'=>'【测试数据】三区配送模拟入库36桶,未发生真实入库'],1); $groups = []; $taskIds = []; foreach ($districts as $district) { $region = '深圳市'.$district['name'].'区'; $ids = []; foreach ($district['stops'] as $index=>$stop) { $name = '【测试0911】'.$district['name'].$stop['name']; if (WaterEnterprise::query()->where('name',$name)->exists()) throw new RuntimeException('同名测试企业已存在,停止避免覆盖'); $enterpriseId = $business->save('enterprise',[ 'name'=>$name,'contact'=>'片区测试联系人','phone'=>'TEST-0911-'.$district['code'].'-'.($index+1), 'region'=>$region,'address'=>$region.$stop['name'].'测试点(虚构企业、模拟坐标,请勿实际配送)', 'longitude'=>$stop['longitude'],'latitude'=>$stop['latitude'],'status'=>1, ],1)['id']; WaterAccount::query()->insert(['enterprise_id'=>$enterpriseId,'sku_id'=>$skuId, 'entitlement'=>$stop['quantity'],'threshold_qty'=>2,'updated_at'=>date('Y-m-d H:i:s')]); WaterLedger::query()->insert(['kind'=>'demo_seed','enterprise_id'=>$enterpriseId,'sku_id'=>$skuId, 'quantity'=>$stop['quantity'],'reference'=>$batch.'-RIGHTS-'.$district['code'].'-'.$index, 'note'=>'【测试数据】三区调度模拟权益,无真实收款','created_by'=>1,'created_at'=>date('Y-m-d H:i:s')]); $id = $business->createDelivery(['enterprise_id'=>$enterpriseId,'sku_id'=>$skuId,'quantity'=>$stop['quantity'],'date'=>$date],1)['id']; $dispatch->schedule($origin+['id'=>$id,'date'=>$date,'deadline_at'=>$date.($index===0?' 17:00:00':' 18:00:00'), 'longitude'=>$stop['longitude'],'latitude'=>$stop['latitude']],1); $ids[] = $id; $taskIds[] = $id; } $driverId = $dispatch->saveDriver(['name'=>'【测试0911】'.$district['name'].'配送员', 'phone'=>'TEST-0911-DRIVER-'.$district['code'],'region'=>$region,'capacity'=>30, 'vehicle'=>'测试车辆-'.$district['code']],1)['id']; $dispatch->shift(['driver_id'=>$driverId,'date'=>$date,'state'=>'on'],1); $groups[] = ['driver_id'=>$driverId,'task_ids'=>$ids]; } $result = $dispatch->commit(['date'=>$date,'request_key'=>$batch,'routes'=>$groups],1); if (count($result['route_ids']) !== 3) throw new RuntimeException('路线数量不正确'); $afterSku = WaterSku::query()->find($skuId)->toArray(); if ((int)$afterSku['available'] !== (int)$sku['available'] || (int)$afterSku['locked'] !== (int)$sku['locked']+$total || (int)$afterSku['transit'] !== (int)$sku['transit']) throw new RuntimeException('库存核对失败'); if (WaterOrder::query()->orderBy('id')->get()->toArray() !== $oldOrders) throw new RuntimeException('原收款订单发生变化'); foreach ($oldTasks as $row) { if (WaterDelivery::query()->find($row['id'])->toArray() !== $row) throw new RuntimeException('原任务发生变化'); } foreach ($oldAccounts as $row) { if (WaterAccount::query()->find($row['id'])->toArray() !== $row) throw new RuntimeException('原账户发生变化'); } foreach ($groups as $index=>$group) { $route = $dispatch->route(['id'=>$result['route_ids'][$index]]); if (count($route['tasks']) !== 2) throw new RuntimeException('每条测试路线必须包含两个站点'); foreach ($route['tasks'] as $task) { $account = WaterAccount::query()->where(['enterprise_id'=>$task['enterprise_id'],'sku_id'=>$skuId])->first()->toArray(); if ($task['status'] !== 'assigned' || $task['scheduled_date'] !== $date || $task['region'] !== '深圳市'.$districts[$index]['name'].'区' || (int)$account['frozen'] !== (int)$task['quantity']) throw new RuntimeException('片区、任务状态或冻结权益不一致'); } } WaterLedger::query()->insert(['kind'=>'demo_seed','sku_id'=>$skuId,'quantity'=>0,'reference'=>$batch, 'note'=>'【测试数据】罗湖、福田、宝安各2单,合计36桶及3条两站路线;模拟坐标;任务ID:'.implode(',',$taskIds), 'created_by'=>1,'created_at'=>date('Y-m-d H:i:s')]); return false; }); } $board = $dispatch->board(['date'=>$date]); $filters = []; foreach ($districts as $district) { $region = '深圳市'.$district['name'].'区'; $filtered = $dispatch->board(['date'=>$date,'region'=>$region]); $filters[$region] = ['summary'=>$filtered['summary'],'route_ids'=>array_values(array_unique(array_column($filtered['tasks'],'route_id'))), 'tasks'=>array_map(fn($row)=>array_intersect_key($row,array_flip(['id','enterprise_name','quantity','status','route_id','stop_order'])),$filtered['tasks'])]; } echo json_encode(['mode'=>$apply?'applied':'inspect','batch'=>$batch,'replayed'=>$replayed,'before'=>$before, 'after'=>$board['summary'],'areas'=>$board['areas'],'district_filters'=>$filters],JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT).PHP_EOL;