From c3ef078c36cd7222a974efd385e7900d0ff1f4f5 Mon Sep 17 00:00:00 2001 From: xman <1946321327@qq.com> Date: Mon, 19 May 2025 21:36:57 +0800 Subject: [PATCH] sth --- .../controller/statistics/Kpidispatcher.php | 431 ++++++++++++++++++ .../lang/zh-cn/statistics/kpidispatcher.php | 71 +++ application/admin/model/Kpiorder.php | 63 +++ application/admin/validate/Kpiorder.php | 27 ++ .../view/statistics/kpidispatcher/add.html | 319 +++++++++++++ .../view/statistics/kpidispatcher/edit.html | 319 +++++++++++++ .../view/statistics/kpidispatcher/index.html | 21 + .../js/backend/statistics/kpidispatcher.js | 184 ++++++++ 8 files changed, 1435 insertions(+) create mode 100644 application/admin/controller/statistics/Kpidispatcher.php create mode 100644 application/admin/lang/zh-cn/statistics/kpidispatcher.php create mode 100644 application/admin/model/Kpiorder.php create mode 100644 application/admin/validate/Kpiorder.php create mode 100644 application/admin/view/statistics/kpidispatcher/add.html create mode 100644 application/admin/view/statistics/kpidispatcher/edit.html create mode 100644 application/admin/view/statistics/kpidispatcher/index.html create mode 100644 public/assets/js/backend/statistics/kpidispatcher.js diff --git a/application/admin/controller/statistics/Kpidispatcher.php b/application/admin/controller/statistics/Kpidispatcher.php new file mode 100644 index 0000000..fc6a2df --- /dev/null +++ b/application/admin/controller/statistics/Kpidispatcher.php @@ -0,0 +1,431 @@ +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,2); + + $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]); + $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) + { + //完成值/目标值*单个指标分*权重 + + if($item['target_value'] == 0){ + $item['target_value'] = 1; + } + $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(); + } + +} diff --git a/application/admin/lang/zh-cn/statistics/kpidispatcher.php b/application/admin/lang/zh-cn/statistics/kpidispatcher.php new file mode 100644 index 0000000..5033350 --- /dev/null +++ b/application/admin/lang/zh-cn/statistics/kpidispatcher.php @@ -0,0 +1,71 @@ + 'ID', + 'Order_no' => '订单号', + 'Customer' => '客户姓名', + 'Tel' => '客户电话', + 'Status' => '订单状态', + 'Status 0' => '草稿', + 'Set status to 0' => '设为草稿', + 'Status 10' => '未派单', + 'Set status to 10' => '设为未派单', + 'Status 20' => '已派单', + 'Set status to 20' => '设为已派单', + 'Status 30' => '进行中', + 'Set status to 30' => '设为进行中', + 'Status 40' => '待验收', + 'Set status to 40' => '设为待验收', + 'Status 50' => '待财务审核', + 'Set status to 50' => '设为待财务审核', + 'Status 60' => '已完成', + 'Set status to 60' => '设为已完成', + 'Status -10' => '取消', + 'Set status to -10' => '设为取消', + 'Area_id' => '地域', + 'Address' => '详细地址', + 'Lng' => '经度', + 'Lat' => '纬度', + 'Work_tel_id' => '工作机', + 'Source_shop' => '来源店铺', + 'Source' => '订单来源', + 'Source_uid' => '来源UID', + 'Item_id' => '服务ID', + 'Item_title' => '服务名称', + 'Detail' => '订单详情', + 'Remark' => '订单备注', + 'Images' => '图片', + 'Plan_time' => '客户预约时间', + 'Admin_id' => '录单员ID', + 'Coupon_id' => '优惠码id', + 'Total' => '总收款', + 'Online_amount' => '线上收款', + 'Offline_amount' => '线下尾款', + 'Online_amount_last' => '线上尾款', + 'Offline_amount_type' => '线下尾款类型', + 'Offline_amount_type 1' => '师傅收', + 'Offline_amount_type 2' => '公司收', + 'Discount_amount' => '优惠抵扣', + 'Refund_amount' => '公司款额', + 'Worker_refund_amount' => '师傅退款', + 'Real_amount' => '实际收款', + 'Cost' => '师傅成本', + 'Material_cost' => '材料成本', + 'Performance' => '预计利润', + 'Cancel_reason_id' => '取消原因', + 'Cancel_detail' => '取消详情', + 'Audit_remark' => '审核备注', + 'Audit_admin_id' => '审核员', + 'Create_time' => '录单时间', + 'Update_time' => '更新时间', + 'Delete_time' => '删除时间', + 'Dispatch_type' => '1 手动派单 2自动排单', + 'Receive_type' => '1 定金 2全款', + 'Revisit_id' => '回访ID', + 'Dispatch_admin_id' => '派单员', + 'Dispatch_admin_user' => '派单员', + 'Dispatch_time' => '派单时间', + 'Aftersale_id' => '售后ID', + 'Amount_images' => '收款凭据', + 'Worker_id' => '师傅ID' +]; diff --git a/application/admin/model/Kpiorder.php b/application/admin/model/Kpiorder.php new file mode 100644 index 0000000..161e519 --- /dev/null +++ b/application/admin/model/Kpiorder.php @@ -0,0 +1,63 @@ + __('Status 0'), '10' => __('Status 10'), '20' => __('Status 20'), '30' => __('Status 30'), '40' => __('Status 40'), '50' => __('Status 50'), '60' => __('Status 60'), '-10' => __('Status -10')]; + } + + public function getOfflineAmountTypeList() + { + return ['1' => __('Offline_amount_type 1'), '2' => __('Offline_amount_type 2')]; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + public function getOfflineAmountTypeTextAttr($value, $data) + { + $value = $value ?: ($data['offline_amount_type'] ?? ''); + $list = $this->getOfflineAmountTypeList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/application/admin/validate/Kpiorder.php b/application/admin/validate/Kpiorder.php new file mode 100644 index 0000000..0e38406 --- /dev/null +++ b/application/admin/validate/Kpiorder.php @@ -0,0 +1,27 @@ + [], + 'edit' => [], + ]; + +} diff --git a/application/admin/view/statistics/kpidispatcher/add.html b/application/admin/view/statistics/kpidispatcher/add.html new file mode 100644 index 0000000..df38d11 --- /dev/null +++ b/application/admin/view/statistics/kpidispatcher/add.html @@ -0,0 +1,319 @@ +
diff --git a/application/admin/view/statistics/kpidispatcher/edit.html b/application/admin/view/statistics/kpidispatcher/edit.html new file mode 100644 index 0000000..02743c3 --- /dev/null +++ b/application/admin/view/statistics/kpidispatcher/edit.html @@ -0,0 +1,319 @@ + diff --git a/application/admin/view/statistics/kpidispatcher/index.html b/application/admin/view/statistics/kpidispatcher/index.html new file mode 100644 index 0000000..cd9e92f --- /dev/null +++ b/application/admin/view/statistics/kpidispatcher/index.html @@ -0,0 +1,21 @@ +