536 行
19 KiB
PHP
536 行
19 KiB
PHP
<?php
|
|
namespace Retail\Controller;
|
|
use Think\Controller;
|
|
|
|
/**
|
|
* 骑手考核管理控制器
|
|
*/
|
|
class RiderAssessmentController extends Controller {
|
|
|
|
/**
|
|
* 考核管理首页
|
|
*/
|
|
public function index() {
|
|
$this->display();
|
|
}
|
|
|
|
/**
|
|
* 获取考核列表
|
|
*/
|
|
public function getAssessmentList() {
|
|
$page = I('page', 1, 'intval');
|
|
$pageSize = I('pageSize', 20, 'intval');
|
|
$keyword = I('keyword', '', 'trim');
|
|
$level = I('level', '', 'trim');
|
|
$month = I('month', '', 'trim');
|
|
|
|
$where = array();
|
|
|
|
// 关键词搜索(骑手姓名、手机号)
|
|
if (!empty($keyword)) {
|
|
$where['_string'] = "(u.nickname LIKE '%{$keyword}%' OR u.phone LIKE '%{$keyword}%')";
|
|
}
|
|
|
|
// 考核等级筛选
|
|
if ($level !== '') {
|
|
$where['a.assessment_level'] = $level;
|
|
}
|
|
|
|
// 月份筛选
|
|
if (!empty($month)) {
|
|
$where['a.assessment_month'] = $month;
|
|
}
|
|
|
|
$model = M('rider_assessment');
|
|
$total = $model->alias('a')
|
|
->join('ls_users u ON a.rider_id = u.id')
|
|
->where($where)
|
|
->count();
|
|
|
|
$list = $model->alias('a')
|
|
->field('a.*, u.nickname, u.phone, u.avatar')
|
|
->join('ls_users u ON a.rider_id = u.id')
|
|
->where($where)
|
|
->order('a.assessment_month desc, a.total_score desc')
|
|
->page($page, $pageSize)
|
|
->select();
|
|
|
|
// 格式化数据
|
|
foreach ($list as &$item) {
|
|
$item['assessment_level_text'] = $this->getAssessmentLevelText($item['assessment_level']);
|
|
$item['create_time_format'] = date('Y-m-d H:i:s', $item['create_time']);
|
|
$item['punctuality_rate_format'] = number_format($item['punctuality_rate'], 2) . '%';
|
|
$item['rating_score_format'] = number_format($item['rating_score'], 1);
|
|
$item['complaint_rate_format'] = number_format($item['complaint_rate'], 2) . '%';
|
|
$item['total_score_format'] = number_format($item['total_score'], 1);
|
|
}
|
|
|
|
$this->ajaxReturn(array(
|
|
'code' => 0,
|
|
'msg' => 'success',
|
|
'data' => array(
|
|
'list' => $list,
|
|
'total' => $total
|
|
)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 获取考核详情
|
|
*/
|
|
public function getAssessmentDetail() {
|
|
$id = I('id', 0, 'intval');
|
|
if (empty($id)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
|
|
}
|
|
|
|
$detail = M('rider_assessment')->alias('a')
|
|
->field('a.*, u.nickname, u.phone, u.avatar')
|
|
->join('ls_users u ON a.rider_id = u.id')
|
|
->where(array('a.id' => $id))
|
|
->find();
|
|
|
|
if (empty($detail)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '考核记录不存在'));
|
|
}
|
|
|
|
// 格式化数据
|
|
$detail['assessment_level_text'] = $this->getAssessmentLevelText($detail['assessment_level']);
|
|
$detail['create_time_format'] = date('Y-m-d H:i:s', $detail['create_time']);
|
|
$detail['punctuality_rate_format'] = number_format($detail['punctuality_rate'], 2) . '%';
|
|
$detail['rating_score_format'] = number_format($detail['rating_score'], 1);
|
|
$detail['complaint_rate_format'] = number_format($detail['complaint_rate'], 2) . '%';
|
|
$detail['total_score_format'] = number_format($detail['total_score'], 1);
|
|
|
|
// 获取详细指标数据
|
|
$detail['metrics'] = array(
|
|
'order_count' => $detail['order_count'],
|
|
'completed_count' => $detail['completed_count'],
|
|
'cancelled_count' => $detail['cancelled_count'],
|
|
'timeout_count' => $detail['timeout_count'],
|
|
'complaint_count' => $detail['complaint_count'],
|
|
'praise_count' => $detail['praise_count'],
|
|
'online_hours' => number_format($detail['online_hours'], 1),
|
|
'avg_delivery_time' => number_format($detail['avg_delivery_time'], 1)
|
|
);
|
|
|
|
$this->ajaxReturn(array(
|
|
'code' => 0,
|
|
'msg' => 'success',
|
|
'data' => $detail
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 生成月度考核
|
|
*/
|
|
public function generateAssessment() {
|
|
$month = I('month', '', 'trim');
|
|
$rider_id = I('rider_id', 0, 'intval');
|
|
|
|
if (empty($month)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '请选择考核月份'));
|
|
}
|
|
|
|
// 验证月份格式
|
|
if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '月份格式错误'));
|
|
}
|
|
|
|
// 获取骑手列表
|
|
$where = array('user_type' => 'rider', 'status' => 1);
|
|
if ($rider_id > 0) {
|
|
$where['id'] = $rider_id;
|
|
}
|
|
|
|
$riders = M('users')->where($where)->select();
|
|
if (empty($riders)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '没有找到符合条件的骑手'));
|
|
}
|
|
|
|
$success_count = 0;
|
|
$start_time = strtotime($month . '-01 00:00:00');
|
|
$end_time = strtotime(date('Y-m-t 23:59:59', $start_time));
|
|
|
|
foreach ($riders as $rider) {
|
|
// 检查是否已生成过该月考核
|
|
$exists = M('rider_assessment')->where(array(
|
|
'rider_id' => $rider['id'],
|
|
'assessment_month' => $month
|
|
))->find();
|
|
|
|
if ($exists) {
|
|
continue; // 跳过已存在的考核记录
|
|
}
|
|
|
|
// 计算考核指标
|
|
$metrics = $this->calculateMetrics($rider['id'], $start_time, $end_time);
|
|
|
|
// 计算总分和等级
|
|
$total_score = $this->calculateTotalScore($metrics);
|
|
$assessment_level = $this->getAssessmentLevel($total_score);
|
|
|
|
// 保存考核记录
|
|
$assessment_data = array_merge($metrics, array(
|
|
'rider_id' => $rider['id'],
|
|
'assessment_month' => $month,
|
|
'total_score' => $total_score,
|
|
'assessment_level' => $assessment_level,
|
|
'create_time' => time(),
|
|
'update_time' => time()
|
|
));
|
|
|
|
$result = M('rider_assessment')->add($assessment_data);
|
|
if ($result) {
|
|
$success_count++;
|
|
}
|
|
}
|
|
|
|
$this->ajaxReturn(array(
|
|
'code' => 0,
|
|
'msg' => "考核生成完成,成功生成{$success_count}条考核记录"
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 更新考核记录
|
|
*/
|
|
public function updateAssessment() {
|
|
$id = I('id', 0, 'intval');
|
|
$manual_score = I('manual_score', 0, 'floatval');
|
|
$remark = I('remark', '', 'trim');
|
|
|
|
if (empty($id)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
|
|
}
|
|
|
|
$assessment = M('rider_assessment')->where(array('id' => $id))->find();
|
|
if (empty($assessment)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '考核记录不存在'));
|
|
}
|
|
|
|
// 重新计算总分
|
|
$new_total_score = $assessment['total_score'] + $manual_score;
|
|
$new_level = $this->getAssessmentLevel($new_total_score);
|
|
|
|
$update_data = array(
|
|
'manual_score' => $manual_score,
|
|
'total_score' => $new_total_score,
|
|
'assessment_level' => $new_level,
|
|
'remark' => $remark,
|
|
'update_time' => time()
|
|
);
|
|
|
|
$result = M('rider_assessment')->where(array('id' => $id))->save($update_data);
|
|
if ($result !== false) {
|
|
$this->ajaxReturn(array('code' => 0, 'msg' => '更新成功'));
|
|
} else {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '更新失败'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除考核记录
|
|
*/
|
|
public function deleteAssessment() {
|
|
$id = I('id', 0, 'intval');
|
|
if (empty($id)) {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
|
|
}
|
|
|
|
$result = M('rider_assessment')->where(array('id' => $id))->delete();
|
|
if ($result) {
|
|
$this->ajaxReturn(array('code' => 0, 'msg' => '删除成功'));
|
|
} else {
|
|
$this->ajaxReturn(array('code' => 1, 'msg' => '删除失败'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 导出考核数据
|
|
*/
|
|
public function exportAssessment() {
|
|
$keyword = I('keyword', '', 'trim');
|
|
$level = I('level', '', 'trim');
|
|
$month = I('month', '', 'trim');
|
|
|
|
$where = array();
|
|
|
|
if (!empty($keyword)) {
|
|
$where['_string'] = "(u.nickname LIKE '%{$keyword}%' OR u.phone LIKE '%{$keyword}%')";
|
|
}
|
|
if ($level !== '') {
|
|
$where['a.assessment_level'] = $level;
|
|
}
|
|
if (!empty($month)) {
|
|
$where['a.assessment_month'] = $month;
|
|
}
|
|
|
|
$list = M('rider_assessment')->alias('a')
|
|
->field('a.*, u.nickname, u.phone')
|
|
->join('ls_users u ON a.rider_id = u.id')
|
|
->where($where)
|
|
->order('a.assessment_month desc, a.total_score desc')
|
|
->select();
|
|
|
|
// 导出Excel
|
|
vendor('PHPExcel.PHPExcel');
|
|
$objPHPExcel = new \PHPExcel();
|
|
$objPHPExcel->setActiveSheetIndex(0);
|
|
$objActSheet = $objPHPExcel->getActiveSheet();
|
|
|
|
// 设置表头
|
|
$objActSheet->setCellValue('A1', '考核ID');
|
|
$objActSheet->setCellValue('B1', '骑手姓名');
|
|
$objActSheet->setCellValue('C1', '手机号');
|
|
$objActSheet->setCellValue('D1', '考核月份');
|
|
$objActSheet->setCellValue('E1', '接单数');
|
|
$objActSheet->setCellValue('F1', '完成数');
|
|
$objActSheet->setCellValue('G1', '准时率');
|
|
$objActSheet->setCellValue('H1', '评分');
|
|
$objActSheet->setCellValue('I1', '投诉率');
|
|
$objActSheet->setCellValue('J1', '总分');
|
|
$objActSheet->setCellValue('K1', '考核等级');
|
|
$objActSheet->setCellValue('L1', '在线时长');
|
|
$objActSheet->setCellValue('M1', '平均配送时间');
|
|
|
|
// 填充数据
|
|
$row = 2;
|
|
foreach ($list as $item) {
|
|
$objActSheet->setCellValue('A' . $row, $item['id']);
|
|
$objActSheet->setCellValue('B' . $row, $item['nickname']);
|
|
$objActSheet->setCellValue('C' . $row, $item['phone']);
|
|
$objActSheet->setCellValue('D' . $row, $item['assessment_month']);
|
|
$objActSheet->setCellValue('E' . $row, $item['order_count']);
|
|
$objActSheet->setCellValue('F' . $row, $item['completed_count']);
|
|
$objActSheet->setCellValue('G' . $row, number_format($item['punctuality_rate'], 2) . '%');
|
|
$objActSheet->setCellValue('H' . $row, number_format($item['rating_score'], 1));
|
|
$objActSheet->setCellValue('I' . $row, number_format($item['complaint_rate'], 2) . '%');
|
|
$objActSheet->setCellValue('J' . $row, number_format($item['total_score'], 1));
|
|
$objActSheet->setCellValue('K' . $row, $this->getAssessmentLevelText($item['assessment_level']));
|
|
$objActSheet->setCellValue('L' . $row, number_format($item['online_hours'], 1) . '小时');
|
|
$objActSheet->setCellValue('M' . $row, number_format($item['avg_delivery_time'], 1) . '分钟');
|
|
$row++;
|
|
}
|
|
|
|
$filename = '骑手考核记录_' . date('YmdHis') . '.xlsx';
|
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
header('Content-Disposition: attachment;filename="' . $filename . '"');
|
|
header('Cache-Control: max-age=0');
|
|
|
|
$objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
|
|
$objWriter->save('php://output');
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* 获取考核统计
|
|
*/
|
|
public function getAssessmentStats() {
|
|
$month = I('month', '', 'trim');
|
|
|
|
$where = array();
|
|
if (!empty($month)) {
|
|
$where['assessment_month'] = $month;
|
|
}
|
|
|
|
$model = M('rider_assessment');
|
|
|
|
$stats = array(
|
|
'total_count' => $model->where($where)->count(),
|
|
'excellent_count' => $model->where(array_merge($where, array('assessment_level' => 'excellent')))->count(),
|
|
'good_count' => $model->where(array_merge($where, array('assessment_level' => 'good')))->count(),
|
|
'average_count' => $model->where(array_merge($where, array('assessment_level' => 'average')))->count(),
|
|
'poor_count' => $model->where(array_merge($where, array('assessment_level' => 'poor')))->count()
|
|
);
|
|
|
|
// 计算平均分
|
|
$avg_score = $model->where($where)->avg('total_score');
|
|
$stats['avg_score'] = number_format($avg_score ?: 0, 1);
|
|
|
|
// 计算平均准时率
|
|
$avg_punctuality = $model->where($where)->avg('punctuality_rate');
|
|
$stats['avg_punctuality'] = number_format($avg_punctuality ?: 0, 2);
|
|
|
|
// 计算平均评分
|
|
$avg_rating = $model->where($where)->avg('rating_score');
|
|
$stats['avg_rating'] = number_format($avg_rating ?: 0, 1);
|
|
|
|
$this->ajaxReturn(array(
|
|
'code' => 0,
|
|
'msg' => 'success',
|
|
'data' => $stats
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 计算骑手考核指标
|
|
*/
|
|
private function calculateMetrics($rider_id, $start_time, $end_time) {
|
|
$order_model = M('order');
|
|
|
|
// 基础订单统计
|
|
$order_where = array(
|
|
'rider_id' => $rider_id,
|
|
'create_time' => array('between', array($start_time, $end_time))
|
|
);
|
|
|
|
$order_count = $order_model->where($order_where)->count();
|
|
$completed_count = $order_model->where(array_merge($order_where, array('status' => 'completed')))->count();
|
|
$cancelled_count = $order_model->where(array_merge($order_where, array('status' => 'cancelled')))->count();
|
|
|
|
// 超时订单统计
|
|
$timeout_count = $order_model->where(array_merge($order_where, array(
|
|
'status' => 'completed',
|
|
'is_timeout' => 1
|
|
)))->count();
|
|
|
|
// 计算准时率
|
|
$punctuality_rate = $completed_count > 0 ? (($completed_count - $timeout_count) / $completed_count) * 100 : 0;
|
|
|
|
// 评分统计
|
|
$rating_score = $order_model->where(array_merge($order_where, array('status' => 'completed')))
|
|
->avg('rider_rating') ?: 0;
|
|
|
|
// 投诉统计
|
|
$complaint_count = M('rider_complaint')->where(array(
|
|
'rider_id' => $rider_id,
|
|
'create_time' => array('between', array($start_time, $end_time))
|
|
))->count();
|
|
|
|
$complaint_rate = $completed_count > 0 ? ($complaint_count / $completed_count) * 100 : 0;
|
|
|
|
// 好评统计
|
|
$praise_count = $order_model->where(array_merge($order_where, array(
|
|
'status' => 'completed',
|
|
'rider_rating' => array('egt', 4.5)
|
|
)))->count();
|
|
|
|
// 在线时长统计(假设有在线记录表)
|
|
$online_hours = M('rider_online_log')->where(array(
|
|
'rider_id' => $rider_id,
|
|
'date' => array('between', array(date('Y-m-d', $start_time), date('Y-m-d', $end_time)))
|
|
))->sum('online_duration') / 3600; // 转换为小时
|
|
|
|
// 平均配送时间
|
|
$avg_delivery_time = $order_model->where(array_merge($order_where, array('status' => 'completed')))
|
|
->avg('delivery_duration') / 60; // 转换为分钟
|
|
|
|
return array(
|
|
'order_count' => $order_count,
|
|
'completed_count' => $completed_count,
|
|
'cancelled_count' => $cancelled_count,
|
|
'timeout_count' => $timeout_count,
|
|
'punctuality_rate' => $punctuality_rate,
|
|
'rating_score' => $rating_score,
|
|
'complaint_count' => $complaint_count,
|
|
'complaint_rate' => $complaint_rate,
|
|
'praise_count' => $praise_count,
|
|
'online_hours' => $online_hours ?: 0,
|
|
'avg_delivery_time' => $avg_delivery_time ?: 0
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 计算总分
|
|
*/
|
|
private function calculateTotalScore($metrics) {
|
|
$score = 0;
|
|
|
|
// 准时率权重 30%
|
|
if ($metrics['punctuality_rate'] >= 95) {
|
|
$score += 30;
|
|
} elseif ($metrics['punctuality_rate'] >= 90) {
|
|
$score += 25;
|
|
} elseif ($metrics['punctuality_rate'] >= 85) {
|
|
$score += 20;
|
|
} elseif ($metrics['punctuality_rate'] >= 80) {
|
|
$score += 15;
|
|
} else {
|
|
$score += 10;
|
|
}
|
|
|
|
// 评分权重 25%
|
|
if ($metrics['rating_score'] >= 4.8) {
|
|
$score += 25;
|
|
} elseif ($metrics['rating_score'] >= 4.5) {
|
|
$score += 20;
|
|
} elseif ($metrics['rating_score'] >= 4.0) {
|
|
$score += 15;
|
|
} elseif ($metrics['rating_score'] >= 3.5) {
|
|
$score += 10;
|
|
} else {
|
|
$score += 5;
|
|
}
|
|
|
|
// 投诉率权重 20%(投诉率越低分数越高)
|
|
if ($metrics['complaint_rate'] <= 1) {
|
|
$score += 20;
|
|
} elseif ($metrics['complaint_rate'] <= 2) {
|
|
$score += 15;
|
|
} elseif ($metrics['complaint_rate'] <= 3) {
|
|
$score += 10;
|
|
} elseif ($metrics['complaint_rate'] <= 5) {
|
|
$score += 5;
|
|
} else {
|
|
$score += 0;
|
|
}
|
|
|
|
// 完成订单数权重 15%
|
|
if ($metrics['completed_count'] >= 200) {
|
|
$score += 15;
|
|
} elseif ($metrics['completed_count'] >= 150) {
|
|
$score += 12;
|
|
} elseif ($metrics['completed_count'] >= 100) {
|
|
$score += 10;
|
|
} elseif ($metrics['completed_count'] >= 50) {
|
|
$score += 8;
|
|
} else {
|
|
$score += 5;
|
|
}
|
|
|
|
// 在线时长权重 10%
|
|
if ($metrics['online_hours'] >= 200) {
|
|
$score += 10;
|
|
} elseif ($metrics['online_hours'] >= 150) {
|
|
$score += 8;
|
|
} elseif ($metrics['online_hours'] >= 100) {
|
|
$score += 6;
|
|
} elseif ($metrics['online_hours'] >= 50) {
|
|
$score += 4;
|
|
} else {
|
|
$score += 2;
|
|
}
|
|
|
|
return $score;
|
|
}
|
|
|
|
/**
|
|
* 根据总分获取考核等级
|
|
*/
|
|
private function getAssessmentLevel($total_score) {
|
|
if ($total_score >= 90) {
|
|
return 'excellent';
|
|
} elseif ($total_score >= 80) {
|
|
return 'good';
|
|
} elseif ($total_score >= 70) {
|
|
return 'average';
|
|
} else {
|
|
return 'poor';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取考核等级文本
|
|
*/
|
|
private function getAssessmentLevelText($level) {
|
|
$levelMap = array(
|
|
'excellent' => '优秀',
|
|
'good' => '良好',
|
|
'average' => '一般',
|
|
'poor' => '较差'
|
|
);
|
|
return isset($levelMap[$level]) ? $levelMap[$level] : '未知';
|
|
}
|
|
}
|
|
?>
|