lng,$order->lat]); $worker_ids = array_column($worker_info,'id'); $worker_items_ids = (new WorkerItem()) ->where('item_id', $order->item_id) ->whereIn('worker_id', $worker_ids) ->column('worker_id'); $out_worker = OrderDispatch::where('order_id',$order->id) ->where('status',-30)->column('worker_id'); $out_worker_ids = array_intersect($worker_ids, $worker_items_ids); $out_worker_ids = array_values(array_diff($out_worker_ids, $out_worker)); $worker_doings = (new OrderDispatch()) ->whereIn('worker_id', $out_worker_ids) ->group('worker_id') ->field(['worker_id','count(if(status > 0 & status < 60,1,null)) count']) ->select(); $worker_doing_map = []; foreach ($worker_doings as $item){ $worker_doing_map[$item->worker_id] = $item->toArray(); } // 获取工人信息; $worker_rate = $this->getWorkerRate($out_worker_ids); $worker_scores = []; $worker_rate_map = array_column($worker_rate,null,'worker_id'); foreach ($worker_info as $item){ if (in_array($item['id'],$out_worker_ids)){ $worker_scores [] = [ 'id' => $item['id'], 'distance' => $item['distance'], // 'star' => $item['star'], 'doing' => $worker_doing_map[$item['id']]['count'] ?? 0, 'status' => $worker_rate_map[$item['id']] ?? [ 'arrive_rate' => 0, 'refuse_rate' => 0, 'trans_rate' => 0, 'good_rate' => 0, 'avg_time_diff' => 0, ], ]; } } $worker_score_map = []; foreach ($worker_scores as $worker_score){ $worker_score_map [$worker_score['id']] = $this->scoreWorker($worker_score); } // 根据得分排序(从高到低) arsort($worker_score_map); // 保持键名不变 // 取出第一个 key(就是得分最高的 ID) return array_key_first($worker_score_map); } function scoreWorker($worker, $maxDistance = 10000, $requiredSkills = []) { // 距离得分(越近分数越高) $geoScore = max(0, 1 - ($worker['distance'] / $maxDistance)); // 范围 [0,1] // 技能得分(假设这个人不匹配) $skillScore = count($requiredSkills) ? 0 : 1; // 当前状态得分(未完成订单越少越好) $doing = $worker['doing']; $statusScore = $doing >= 10 ? 0 : (10 - $doing) / 10; // 历史表现得分(各项 0~1) $s = $worker['status']; $historyScore = ( 0.4 * (floatval($s['good_rate']) / 100) + 0.25 * (floatval($s['trans_rate']) / 100) + 0.2 * (floatval($s['arrive_rate']) / 100) + 0.1 * (1 - min(floatval($s['avg_time_diff']) / 10, 1)) + // 联系时间越短越好 0.05 * (1 - floatval($s['refuse_rate']) / 100) // 拒单率越低越好 ); // 综合得分(加权) $totalScore = 0.3 * $geoScore + 0.2 * $skillScore + 0.2 * $statusScore + 0.3 * $historyScore; return round($totalScore, 4); } private function getWorkerRate($worker_ids){ $filter = [ 'start_time' => now()->modify('-31 days')->format('Y-m-d H:i:s'), 'end_timne'=> now()->format('Y-m-d H:i:s') ]; //派单表 $dispatchSubsql = $this->dispatchSubsql($filter); //订单表 $orderSubsql = $this->oderSubsql($filter); //评分表 $viewSubsql = $this->viewSubsql($filter); $list = (new Worker())->alias('fa_worker') ->whereIn('id',$worker_ids) ->field([ 'fa_worker.*', 'IFNULL(a.dispatch_count, 0) AS dispatch_count', 'IFNULL(a.get_count, 0) AS get_count', 'IFNULL(a.refuse_count, 0) AS refuse_count', 'IFNULL(a.arrive_count, 0) AS arrive_count', 'IFNULL(a.avg_time_diff, 0) AS avg_time_diff', 'IFNULL(b.finish_num, 0) AS finish_num', 'IFNULL(b.total, 0) AS total', 'IFNULL(b.performance, 0) AS performance', 'IFNULL(b.refund_total, 0) AS refund_total', 'IFNULL(b.refund_count, 0) AS refund_count', 'IFNULL(b.cost, 0) AS cost', 'IFNULL(c.good_count, 0) AS good_count' ]) ->join([$dispatchSubsql => 'a'], 'fa_worker.id = a.worker_id', 'LEFT') ->join([$orderSubsql => 'b'], 'fa_worker.id = b.worker_id', 'LEFT') ->join([$viewSubsql => 'c'], 'fa_worker.id = c.worker_id', 'LEFT') ->select(); $this->_toList($list); $out = []; foreach ($list as $item){ $out [] = [ 'worker_id' => $item->id, 'arrive_rate' => $item->arrive_rate, 'refuse_rate' => $item->refuse_rate, 'trans_rate' => $item->trans_rate, 'good_rate' => $item->good_rate, 'avg_time_diff' => $item->avg_time_diff, ]; } return $out; } /** * @return bool|Collection|PDOStatement|string * @throws DbException */ public function viewSubsql($filter=[]){ //评分表 $viewSubsql = OrderReview::where('worker_star',5)->field(['worker_id','count(*) as good_count']); if(!empty($filter['start_time'])){ $viewSubsql->where('create_time','>=',$filter['start_time']); } if(!empty($filter['end_time'])){ $viewSubsql->where('create_time','<=',$filter['start_time']); } return $viewSubsql->group('worker_id')->buildSql(); } /** * @return bool|PDOStatement|string|Collection * @throws DbException */ public function dispatchSubsql($filter): Collection|bool|string|PDOStatement { $builder = new OrderDispatch(); $fields = [ 'worker_id', // 使用 IFNULL 确保结果为 null 时返回 0 "IFNULL(COUNT(*), 0) AS dispatch_count", //分配数 "IFNULL(COUNT(CASE WHEN status NOT IN (0, -10) THEN 1 END), 0) AS get_count", //接单数 //"COUNT(CASE WHEN status IN (60) THEN 1 END) AS finish_count", //完成数 "IFNULL(COUNT(CASE WHEN status NOT IN (-10) THEN 1 END), 0) AS refuse_count", //拒绝数 "IFNULL(COUNT(arrive_time), 0) AS arrive_count", //上门数 "IFNULL(AVG(CASE WHEN status = 60 AND arrive_time IS NOT NULL THEN UNIX_TIMESTAMP(arrive_time) - UNIX_TIMESTAMP(create_time) END), 0) AS avg_time_diff", //联系时效 ]; $builder->field($fields); if(!empty($filter['start_time'])){ $builder->where('create_time','>=',$filter['start_time']); } if(!empty($filter['end_time'])){ $builder->where('create_time','<=',$filter['start_time']); } $builder->group('worker_id'); return $builder->buildSql(); } //图表统计 /** * @throws DbException */ public function oderSubsql($filter = []): Collection|bool|string|PDOStatement { $orderValid = implode(',',(new Order())->tabStatus(Order::TAB_VALID)); //"COUNT(CASE WHEN status IN (".$orderValid.") THEN 1 END) AS ing_num", $fields = [ 'worker_id', // 使用 IFNULL 确保结果为 null 时返回 0 "IFNULL(COUNT(CASE WHEN status = 60 THEN 1 END), 0) AS finish_num", //完成数 //"COUNT(CASE WHEN status IN (".$orderValid.") THEN 1 END) AS count_num", //总订单数 (排除取消 和草稿) "IFNULL(SUM(CASE WHEN status = 60 THEN total END), 0) AS total", //成效额 "IFNULL(SUM(CASE WHEN status = 60 THEN performance END), 0) AS performance", //业绩 "IFNULL(SUM(CASE WHEN status = 60 THEN cost END), 0) AS cost", //成效额 // "SUM(CASE WHEN status = 60 THEN (cost + material_cost) END) AS cost_total", //总成本 "IFNULL(SUM(CASE WHEN status = 60 THEN (refund_amount + worker_refund_amount) END), 0) AS refund_total", //退款总数 "IFNULL(COUNT(CASE WHEN refund_amount > 0 OR worker_refund_amount > 0 THEN 1 END), 0) AS refund_count", //退款订单数量 //"AVG(CASE WHEN status > 10 THEN UNIX_TIMESTAMP(dispatch_time) - UNIX_TIMESTAMP(create_time) END) AS avg_time_diff", //派单时效 // "SUM(CASE WHEN status = 60 THEN (field1 + field2) END) AS performance", ]; $builder = (new Order())->field($fields); if(!empty($filter['start_time'])){ $builder->where('create_time','>=',$filter['start_time']); } if(!empty($filter['end_time'])){ $builder->where('create_time','<=',$filter['start_time']); } //->where('dispatch_admin_id','>',0); return $builder->group('worker_id')->buildSql(); } private function _toList($list) { foreach ($list as &$datum){ //利润率 = 总业绩/总成效额 $datum->performance_rate = $this->_calc($datum->performance,$datum->total,4,true); //转化率 = 完单数 / 总接单数 $datum->trans_rate = $this->_calc($datum->finish_num,$datum->get_count,4,true); //变现值 = 总业绩 / 总接单数 $datum->cash_value = $this->_calc($datum->performance,$datum->get_count,2); //拒单率 = 拒绝数 / 派单数 $datum->refuse_rate = $this->_calc($datum->refuse_count,$datum->dispatch_count,4,true); //上门率 = 打卡数 / 接单数 $datum->arrive_rate = $this->_calc($datum->arrive_count,$datum->get_count,4,true); //好评率 $datum->good_rate = $this->_calc($datum->good_count,$datum->finish_num,4,true); $datum->avg_time_diff = $this->_calc($datum->avg_time_diff,3600,2); } } private function _calc($a, $b, int $scale=4, bool $is_percent=false): int|string { $a = $a??0; $b = $b??0; $val = $b > 0 ? bcdiv($a,$b,$scale) : '0.00'; if($is_percent){ return bcmul($val,100,2); } return $val; } }