312 lines
11 KiB
PHP
312 lines
11 KiB
PHP
<?php
|
||
|
||
namespace app\admin\controller\statistics;
|
||
|
||
use app\admin\model\Admin;
|
||
use app\admin\model\Aftersale;
|
||
use app\admin\model\Order;
|
||
use app\admin\model\OrderDispatch;
|
||
use app\admin\model\OrderReview;
|
||
use app\common\controller\Backend;
|
||
use PDOStatement;
|
||
use think\Collection;
|
||
use think\Db;
|
||
use think\Exception;
|
||
use think\exception\DbException;
|
||
use think\Loader;
|
||
use think\Model;
|
||
use think\response\Json;
|
||
use function Symfony\Component\Clock\now;
|
||
|
||
/**
|
||
* 服务项目统计
|
||
*
|
||
* @icon fa fa-circle-o
|
||
*/
|
||
class Item extends Backend
|
||
{
|
||
protected $itemsformattedTree = null;
|
||
|
||
protected $noNeedRight = ['list','chartData'];
|
||
|
||
protected $relationSearch = true;
|
||
|
||
public function _initialize()
|
||
{
|
||
parent::_initialize();
|
||
|
||
$sources = Db::name('source')
|
||
->where('status', 1)
|
||
->field(['id', 'title', 'key_word', 'pid'])
|
||
->order('pid', 'asc')
|
||
->order('sort', 'desc')
|
||
->select();
|
||
$filtered = array_filter($sources, function ($item) {
|
||
return $item['pid'] == 0;
|
||
});
|
||
|
||
$pid_map = array_column($filtered, null, 'id');
|
||
$res = [];
|
||
foreach ($sources as $item) {
|
||
if ($item['pid'] != 0 && isset($pid_map[$item['pid']])) {
|
||
$res [] = [
|
||
...$item, 'ptitle' => $pid_map[$item['pid']]['title']
|
||
];
|
||
}
|
||
}
|
||
|
||
/* $items = Db::name('item')
|
||
->where('status', 1)
|
||
->field(['id', 'title', 'key_word', 'pid'])
|
||
->order('pid', 'asc')
|
||
->order('sort', 'desc')
|
||
->select();
|
||
$tree = $this->buildTree($items);
|
||
$formattedTree = $this->formatTree($tree);
|
||
|
||
$this->items = $items;
|
||
$this->itemsformattedTree = $formattedTree;*/
|
||
|
||
$items = \app\admin\model\Item::where('level',1)->field('id,title')->order('sort','desc')->select();
|
||
$this->view->assign("sources", $res);
|
||
$this->view->assign("items", $items);
|
||
|
||
}
|
||
|
||
public function index()
|
||
{
|
||
$start = now()->modify('-7 days')->format('Y-m-d');
|
||
$end_at = now()->format('Y-m-d');
|
||
$default_daterange = $start . ' - ' . $end_at;
|
||
return $this->fetch('index',[
|
||
'default_daterange' => $default_daterange
|
||
]);
|
||
}
|
||
|
||
public function list()
|
||
{
|
||
$build = new Order();
|
||
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
|
||
|
||
|
||
$start = now()->modify('-7 days')->format('Y-m-d');
|
||
$end_at = now()->format('Y-m-d 23:29:59');
|
||
|
||
$filter = request()->get('range','');
|
||
if (!empty($filter)){
|
||
$arr = explode(' - ', $filter);
|
||
if (trim($arr[0])) {
|
||
$start = trim($arr[0]);
|
||
}
|
||
if (trim($arr[1])) {
|
||
$end_at = trim($arr[1]) . ' 23:29:59';
|
||
}
|
||
}
|
||
$area_id = request()->get('area_id');
|
||
|
||
if ($area_id) {
|
||
$area_id = $this->trimSpecialZeros($area_id);
|
||
$build->where('area_id', 'like', $area_id . '%');
|
||
}
|
||
|
||
//来源
|
||
if(!empty(request()->get('source',null))){
|
||
$build->where('source',request()->get('source'));
|
||
}
|
||
//城市
|
||
/* if(!empty(request()->post('area_id',null))){
|
||
$build->where('area_id','LIKE',request()->post('source').'%');
|
||
}*/
|
||
|
||
if(!empty(request()->get('item_id',null))){
|
||
$item_id =request()->get('item_id');
|
||
$item_ids = $this->getItemsById($item_id);
|
||
$item_ids [] = $item_id;
|
||
$build->whereIn('item_id', $item_ids);
|
||
}
|
||
|
||
$build->whereBetween('create_time', [$start, $end_at])
|
||
->field([
|
||
'item_title name', // 类型
|
||
'sum(total) total', // 营业额
|
||
'count(id) count_num', // 单量
|
||
'count(if(status=60,1,null)) finish_num', // 单量
|
||
'sum(performance) performance', // 收益
|
||
'sum(cost) cost', // 收益
|
||
'sum(material_cost) material_cost', // 收益
|
||
'sum(refund_amount) refund_amount', // 公司退款
|
||
'sum(worker_refund_amount) worker_refund_amount', // 工人退款
|
||
"IFNULL(AVG(CASE WHEN status > 10 THEN UNIX_TIMESTAMP(dispatch_time) - UNIX_TIMESTAMP(create_time) END), 0) AS avg_time_diff", //派单时效
|
||
|
||
])->group('item_title')
|
||
->order('count_num', 'desc');
|
||
// dd($total);
|
||
$res = $build->paginate($limit);
|
||
$total = $res->total();
|
||
$ress = $res->items();
|
||
$data = [];
|
||
// dd($res);
|
||
foreach ($ress as $re) {
|
||
|
||
$re = $re->toArray();
|
||
// dd($re);
|
||
$cost_total = $re['cost'] + $re['material_cost'];
|
||
$name = explode('/', $re['name']);
|
||
$re['name'] = str_replace(' ', '', array_pop($name));
|
||
$re['performance_rate'] = $this->mydiv($re['performance'],$re['total']);
|
||
$re['trans_rate'] = $this->mydiv($re['finish_num'],$re['count_num']);
|
||
$re['cash_value'] = $this->mydiv($re['performance'],$re['count_num'],2,false);
|
||
$re['total_avg'] = $this->mydiv($re['total'],$re['count_num'],2,false);
|
||
$re['performance_avg'] = $this->mydiv($re['performance'],$re['finish_num'],2,false);
|
||
$re['avg_time_diff'] = $this->mydiv($re['avg_time_diff'],3600,2,false);
|
||
$re['cost_total'] = number_format($cost_total,2,'.','');
|
||
$data [] = $re;
|
||
}
|
||
|
||
return [
|
||
'rows' => $data,
|
||
'total' => $total
|
||
];
|
||
|
||
}
|
||
|
||
function trimSpecialZeros($str) {
|
||
if (strlen($str) !== 6) {
|
||
return $str; // 非6位字符串直接返回
|
||
}
|
||
|
||
if (substr($str, -4) === "0000") {
|
||
return substr($str, 0, 2); // 去掉后4位
|
||
} elseif (substr($str, -2) === "00") {
|
||
return substr($str, 0, 4); // 去掉后2位
|
||
}
|
||
|
||
return $str; // 不符合条件则原样返回
|
||
}
|
||
|
||
|
||
public function chartData()
|
||
{
|
||
$build = new Order();
|
||
|
||
$start = now()->modify('-14 days')->format('Y-m-d');
|
||
$end_at = now()->format('Y-m-d 23:29:59');
|
||
|
||
$filter ['daterange'] = request()->post('daterange');
|
||
|
||
|
||
|
||
if (!empty($filter['daterange'])) {
|
||
$arr = explode(' - ', $filter['daterange']);
|
||
if (trim($arr[0])) {
|
||
$start = trim($arr[0]);
|
||
}
|
||
if (trim($arr[1])) {
|
||
$end_at = trim($arr[1]) . ' 23:29:59';
|
||
}
|
||
}
|
||
|
||
$build = $build
|
||
->whereBetween('create_time', [$start, $end_at]);
|
||
|
||
//来源
|
||
if(!empty(request()->post('source',null))){
|
||
$build->where('source',request()->post('source'));
|
||
}
|
||
|
||
//城市
|
||
if(!empty(request()->post('area_id',null))){
|
||
$build->where('area_id','LIKE',request()->post('source').'%');
|
||
}
|
||
|
||
if(!empty(request()->post('item_id',null))){
|
||
$item_id =request()->post('item_id');
|
||
$item_ids = $this->getItemsById($item_id);
|
||
$item_ids [] = $item_id;
|
||
$build->whereIn('item_id', $item_ids);
|
||
}
|
||
|
||
|
||
$res = $build->field([
|
||
'item_title name', // 类型
|
||
'sum(total) total', // 营业额
|
||
'count(id) count', // 单量
|
||
'count(if(status=60,1,null)) finish_num', // 单量
|
||
'sum(performance) performance', // 收益
|
||
'sum(refund_amount) refund_amount', // 公司退款
|
||
'sum(worker_refund_amount) worker_refund_amount', // 工人退款
|
||
])->group('item_title')
|
||
->order('count', 'desc')->select();
|
||
$data = [];
|
||
foreach ($res as $re) {
|
||
$re = $re->getData();
|
||
$name = explode('/', $re['name']);
|
||
$re['name'] = str_replace(' ', '', array_pop($name));
|
||
$data [] = $re;
|
||
}
|
||
// dd($data);
|
||
|
||
$xAxis = [];
|
||
$totalPerformance = [];
|
||
$conversionRate = []; // 假设转化率 = performance / total(或自定义逻辑)
|
||
$profitRate = []; // 假设利润率 = (performance - worker_refund_amount) / performance
|
||
$refundRate = []; // 假设退款率 = refund_amount / total
|
||
$monetizedValue = []; // 假设变现值 = performance - refund_amount
|
||
|
||
foreach ($data as $item) {
|
||
$name = $item['name'];
|
||
$total = (float)$item['total'];
|
||
$finish_num = (int)$item['finish_num'];
|
||
$performance = (float)$item['performance'];
|
||
$refund = (float)($item['refund_amount'] + $item['worker_refund_amount']);
|
||
$workerRefund = (float)$item['worker_refund_amount'];
|
||
$count = max((int)$item['count'], 1); // 避免除以0
|
||
|
||
$xAxis[] = $name;
|
||
$totalPerformance[] = $total;
|
||
$conversionRate[] = $total > 0 ? round($finish_num / $count * 100, 2) : 0;
|
||
$profitRate[] = $total > 0 ? round(($performance) / $total * 100, 2) : 0;
|
||
$refundRate[] = $total > 0 ? round($refund / $total * 100, 2) : 0;
|
||
$monetizedValue[] = round($total / $count, 2) ;
|
||
}
|
||
|
||
if ($totalPerformance){
|
||
$result = [
|
||
'xAxis' => $xAxis,
|
||
'series' => [
|
||
['name' => '总业绩(¥)', 'type' => 'bar', 'yAxisIndex' => 0, 'data' => $totalPerformance],
|
||
['name' => '转化率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $conversionRate],
|
||
['name' => '利润率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $profitRate],
|
||
['name' => '退款率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $refundRate],
|
||
['name' => '变现值(¥)', 'type' => 'line', 'yAxisIndex' => 0, 'data' => $monetizedValue],
|
||
]
|
||
];
|
||
}else{
|
||
$result = [
|
||
'xAxis' => ['无'],
|
||
'series' => [
|
||
['name' => '总业绩(¥)', 'type' => 'bar', 'yAxisIndex' => 0, 'data' => [0]],
|
||
['name' => '转化率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => [0]],
|
||
['name' => '利润率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => [0]],
|
||
['name' => '退款率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => [0]],
|
||
['name' => '变现值(¥)', 'type' => 'line', 'yAxisIndex' => 0, 'data' => [0]],
|
||
]
|
||
];
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
private function mydiv($a, $b, int $scale = 4, $is_percent = true): int|string
|
||
{
|
||
$val = $b > 0 ? bcdiv($a, $b, $scale) : 0;
|
||
|
||
if ($is_percent) {
|
||
|
||
return bcmul($val, 100, 2);
|
||
}
|
||
return $val;
|
||
}
|
||
|
||
}
|