allocatr/application/admin/controller/statistics/Kpidispatcher.php
2025-05-20 17:34:00 +08:00

445 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\admin\controller\statistics;
use app\admin\model\Admin;
use app\admin\model\kpi\Template;
use app\admin\model\Kpiorder;
use app\admin\model\Order;
use app\common\controller\Backend;
use think\Db;
use think\exception\DbException;
use think\response\Json;
use app\admin\model\kpi\Item as KpiItem;
use function Symfony\Component\Clock\now;
/**
* 订单列管理
*
* @icon fa fa-circle-o
*/
class Kpidispatcher extends Backend
{
/**
* Kpiorder模型对象
* @var Order
*/
protected $model = null;
const GROUP_ID = 6; //派单员ID
public function _initialize()
{
parent::_initialize();
$this->model = new Order();
$template = Template::where('group_id',self::GROUP_ID)->with(['kpiitem'])->find()->toArray();
$itemUnits = [];
foreach ($template['kpiitem'] as $item)
{
$itemRate[$item['attr']] = $item['pivot']['rate'];
$itemUnits[$item['attr']] = $item['unit'];
}
$item_title = [];
$item_title['ZHL'] = [
'title' => '转化率'.(!empty($itemRate['ZHL'])?'(权重'.$itemRate['ZHL'].'%)':''),
'isshow' => !empty($itemRate['ZHL']),
'unit' => '%'
];
$item_title['LRL'] = [
'title' => '利润率'.(!empty($itemRate['LRL'])?'(权重'.$itemRate['LRL'].'%)':''),
'isshow' =>!empty($itemRate['LRL']),
];
$item_title['PDSX'] = [
'title' => '派单时效'.(!empty($itemRate['PDSX'])?'(权重'.$itemRate['PDSX'].'%)':''),
'isshow' =>!empty($itemRate['PDSX']),
];
$item_title['PCCGL'] = [
'title' => '派出成功率'.(!empty($itemRate['PCCGL'])?'(权重'.$itemRate['PCCGL'].'%)':''),
'isshow' =>!empty($itemRate['PCCGL']),
];
$item_title['GDJSL'] = [
'title' => '跟单及时率'.(!empty($itemRate['GDJSL'])?'(权重'.$itemRate['GDJSL'].'%)':''),
'isshow' =>!empty($itemRate['GDJSL']),
];
$item_title['LRSFS'] = [
'title' => '录入师傅数'.(!empty($itemRate['LRSFS'])?'(权重'.$itemRate['LRSFS'].'%)':''),
'isshow' =>!empty($itemRate['LRSFS']),
];
/*$item_title['LRL'] = '利润率'.(!empty($itemRate['LRL'])?$itemRate['LRL'].'%':'');
$item_title['PDSX'] = '派单时效'.(!empty($itemRate['PDSX'])?$itemRate['LRL'].'%':'');
$item_title['PCCGL'] = '派出成功率'.(!empty($itemRate['PCCGL'])?$itemRate['LRL'].'%':'');
$item_title['GDJSL'] = '跟单及时率'.(!empty($itemRate['GDJSL'])?$itemRate['LRL'].'%':'');
$item_title['LRSFS'] = '录入师傅数'.(!empty($itemRate['LRSFS'])?$itemRate['LRL'].'%':'');*/
$this->assignconfig('item_titles',$item_title);
}
/**
* 查看
*
* @return string|Json
* @throws \think\Exception
* @throws DbException
*/
public function index()
{
//$this->chart();
//$today = now()->sub('')->format('Y-m-d' );
$month = now()->format('Y-m');
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
$this->assignconfig('month',$month);
return $this->view->fetch();
}
$filter = $this->request->param('filter');
$filter = json_decode($filter,true);
if(empty($filter['monthrange'])){
/*$arr = explode(' - ',$filter['daterange']);
if(trim($arr[0])){
$filter['start_time'] = trim($arr[0]);
}
if(trim($arr[1])){
$filter['end_time'] = trim($arr[1]);
}*/
$filter['monthrange'] = date('Y-m');
}
$this->getMonthTimeRange($filter);
$filter['group_id'] = self::GROUP_ID;
if(!empty($filter['admin_user'])){
$adminIds = Admin::where('username','like','%'.$filter['admin_user'].'%')->column('id');
$filter['admin_user_ids'] = $adminIds;
}
$list = $this->chart($filter,false);
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
public function chartData()
{
$filter = $this->request->post();
if(!empty($filter['daterange'])){
$arr = explode(' - ',$filter['daterange']);
if(trim($arr[0])){
$filter['start_time'] = trim($arr[0]);
}
if(trim($arr[1])){
$filter['end_time'] = trim($arr[1]).' 23:59:59';
}
}
$data = $this->chart($filter,true);
$newData = [
['派单员','总业绩','转化率','利润率','变现值']
];
foreach ($data as $datum){
$newData[] = [
$datum['admin_user'],
$datum['performance'],
$datum['trans_rate'],
$datum['performance_rate'],
$datum['cash_value'],
];
}
return $newData;
}
//图表统计
public function chart($filter,$getAll=false): \think\Collection|\think\Paginator|bool|array|string|\PDOStatement
{
$template = Template::where('group_id',self::GROUP_ID)->with(['kpiitem'])->find();
if(empty($template) || empty($template->kpiitem)){
return [];
}
$kpiItem = [];
foreach ($template->kpiitem as $item){
$kpiItem[$item->attr] = $item->toArray();
}
$orderValid = implode(',',$this->model->tabStatus(Order::TAB_VALID));
//"COUNT(CASE WHEN status IN (".$orderValid.") THEN 1 END) AS ing_num",
$fields = [
'dispatch_admin_id',
'worker_num',
// 使用 IFNULL 确保结果为 null 时返回 0
"IFNULL(COUNT(CASE WHEN status = 60 THEN 1 END), 0) AS finish_num", //完成数
"IFNULL(COUNT(CASE WHEN status IN (".$orderValid.") THEN 1 END), 0) 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 + material_cost) END), 0) 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", //退款订单数量
"IFNULL(AVG(CASE WHEN status > 10 THEN UNIX_TIMESTAMP(dispatch_time) - UNIX_TIMESTAMP(create_time) END), 0) AS avg_time_diff", //派单时效
// "SUM(CASE WHEN status = 60 THEN (field1 + field2) END) AS performance",
];
$builder = $this->model
->where('status',Order::STATUS_FINISHED)
->field($fields);
//->where('dispatch_admin_id','>',0);
if(isset($filter['admin_user_ids'])){
$builder->whereIn('dispatch_admin_id',$filter['admin_user_ids']);
}
if(!empty($filter['start_time']) && !empty($filter['end_time'])){
//$time_by = $filter['time_by'] ??1;
/* if($time_by == 1){ //按派单时间
$time_field = 'dispatch_time';
}else{ //按录单时间
$time_field = 'create_time';
}*/
$time_field = 'audit_time';
$builder->whereBetween($time_field,[$filter['start_time'],$filter['end_time']]);
}
$subsql = $this->_subsql($filter);
$builder->join([$subsql => 'a'], 'a.admin_id = dispatch_admin_id', 'LEFT');
//城市
if(!empty($filter['area_id'])){
$builder->where('area_id',$filter['area_id']);
}
//项目
if(!empty($filter['item_id'])){
$builder->where('item_id',$filter['item_id']);
}
if($getAll){
$data = $builder->group('dispatch_admin_id')->limit(50)->select();
}else{
$data = $builder->group('dispatch_admin_id')->paginate();
}
$newData = [];
$max_score = $template->max_score??100;
if(!empty($data)){
foreach ($data as &$datum){
//利润率 = 总业绩/总成效额
$datum->performance_rate = $this->_calc($datum->performance,$datum->total,4,true);
//转化率 = 完单数 / 总订单数
$datum->trans_rate = $this->_calc($datum->finish_num,$datum->count_num,4,true);
//变现值 = 总业绩 / 总订单数
$datum->cash_value = $this->_calc($datum->performance,$datum->count_num,2);
//客单利润 = 总利润 / 完单数
$datum->performance_avg = $this->_calc($datum->performance,$datum->finish_num,2);
//客单价 = 总成效额 / 完单数
$datum->total_avg = $this->_calc($datum->total,$datum->finish_num,2);
if(!empty($datum->dispatch_admin_id)){
$datum->admin_user = Admin::where('id',$datum->dispatch_admin_id)->value('nickname')??'系统';
}else{
$datum->admin_user = '系统';
}
$datum->avg_time_diff = $this->_calc($datum->avg_time_diff,3600,4);
$datum->id = $datum->dispatch_admin_id;
$kpi_total = 0;
//kpi
//转化率
$datum->zhl_score = 0;
$datum->zhl_target_value = $kpiItem[KpiItem::ATTR_ZHL]['target_value']??0;
$datum->unit = '';
if(!empty($kpiItem[KpiItem::ATTR_ZHL])){
$datum->zhl_score = $this->_kpi_score($datum->trans_rate,$kpiItem[KpiItem::ATTR_ZHL]);
$kpi_total += $datum->zhl_score;
//$datum->zhl_unit = $kpiItem[KpiItem::ATTR_ZHL]['unit'];
}
//利润率
$datum->lrl_score = 0;
$datum->lrl_target_value = $kpiItem[KpiItem::ATTR_LRL]['target_value']??0;
if (!empty($kpiItem[KpiItem::ATTR_LRL])) {
$datum->lrl_score = $this->_kpi_score($datum->performance_rate, $kpiItem[KpiItem::ATTR_LRL]);
$kpi_total += $datum->lrl_score;
//$datum->lrl_unit = $kpiItem[KpiItem::ATTR_LRL]['unit'];
}
//派单时效
$datum->pdsx_score = 0;
$datum->pdsx_target_value = $kpiItem[KpiItem::ATTR_PDSX]['target_value']??0; //秒
if (!empty($kpiItem[KpiItem::ATTR_PDSX])) {
$datum->pdsx_score = $this->_kpi_score($datum->avg_time_diff, $kpiItem[KpiItem::ATTR_PDSX],$fande=true);
$kpi_total += $datum->pdsx_score;
//$datum->pdsx_unit = $kpiItem[KpiItem::ATTR_PDSX]['unit'];
}
//派单成功率
$datum->succ_rate = $this->_calc($datum->finish_num,$datum->count_num,4,true);
$datum->pccgl_score = 0;
$datum->pccgl_target_value = $kpiItem[KpiItem::ATTR_PCCGL]['target_value']??0;
if (!empty($kpiItem[KpiItem::ATTR_PCCGL])) {
$datum->pccgl_score = $this->_kpi_score($datum->succ_rate, $kpiItem[KpiItem::ATTR_PCCGL]);
$kpi_total += $datum->pccgl_score;
//$datum->pccgl_unit = $kpiItem[KpiItem::ATTR_PCCGL]['unit'];
}
//录单师傅数
$datum->lrsfs_score = 0;
$datum->lrsfs_target_value = $kpiItem[KpiItem::ATTR_LRSFS]['target_value']??0;
$datum->worker_num = $datum->worker_num??0;
if (!empty($kpiItem[KpiItem::ATTR_LRSFS])) {
$datum->lrsfs_score = $this->_kpi_score($datum->worker_num, $kpiItem[KpiItem::ATTR_LRSFS]);
$kpi_total += $datum->lrsfs_score;
}
$datum->kpi_total = bcadd($kpi_total,0,2);
$datum->kpi_value = $this->_calc($kpi_total,$template->score ?: 1,2);
if($datum->kpi_value <= 0){
$datum->kpi_money = 0;
}else{
$datum->kpi_money = bcmul($datum->performance,$datum->kpi_value,4);
if($datum->kpi_money <= 0){
$datum->kpi_money = 0;
}else{
$datum->kpi_money = bcdiv($datum->kpi_money,100,2);
}
}
$newData[] = $datum->toArray();
}
}
if($getAll){
return $newData;
}else{
return $data;
}
//dump($newData);exit;
}
/**
* @param $a
* @param $b
* @param int $scale
* @return int|string
*/
private function _calc($a, $b, int $scale=4, $is_percent=false): int|string
{
$a = $a??0;
$b = $b??0;
$val = $b > 0 ? bcdiv($a,$b,$scale) : 0;
if($is_percent){
return bcmul($val,100,2);
}
return $val;
}
private function _kpi_score($num,$item,$fande=false)
{
//完成值/目标值*单个指标分*权重
if($item['target_value'] == 0){
$item['target_value'] = 1;
}
if($fande){
//哦哦 如果定的是10分钟完成的为15分钟则1-(15-10/10
//这个还要区分是超过10还是小于10超过10了就上面的没超过就是1 + 10-完成值)/10
if($num > $item['target_value']){
$rate = bcdiv(($num-$item['target_value']),$item['target_value'],4);
$rate = bcsub(1,$rate,4);
}else{
$rate = bcdiv(($item['target_value']-$num),$item['target_value'],4);
$rate = bcadd(1,$rate,4);
}
}else{
$rate = bcdiv($num,$item['target_value'],4);
}
$score = bcmul($item['score'],$rate,4);
$score = bcmul($score,$item['pivot']['rate']/100,2);
if($score<= 0){
$score = 0;
}
return $score;
}
/**
* 获取指定年月的开始和结束时间戳(到秒)
* @param string $yearMonth 格式为 "YYYY-MM" 的日期字符串
* @return array 包含开始时间戳和结束时间戳的数组
*/
function getMonthTimeRange(&$filter)
{
$yearMonth = $filter['monthrange'];
// 解析输入的年月字符串
list($year, $month) = explode('-', $yearMonth);
// 获取当月第一天的时间戳00:00:00
$startTime = strtotime("{$year}-{$month}-01 00:00:00");
// 获取下个月第一天的时间戳
$nextMonth = strtotime("+1 month", $startTime);
// 当月最后一天的时间戳23:59:59
$endTime = $nextMonth - 1;
$filter['start_time'] = date('Y-m-d H:i:s',$startTime);
$filter['end_time'] = date('Y-m-d H:i:s',$endTime);
return $filter;
}
public function _subsql($filter){
$builder = new \app\admin\model\Worker();
$fields = [
'admin_id',
"count(*) as worker_num", //完成数
];
$builder->field($fields)->whereBetween('create_time',[$filter['start_time'],$filter['end_time']]);
$builder->group('admin_id');
return $builder->buildSql();
}
}