gogs пре 2 месеци
родитељ
комит
486edfa7ff
1 измењених фајлова са 411 додато и 0 уклоњено
  1. 411 0
      app/Service/Statistic/StatisticCommonService.php

+ 411 - 0
app/Service/Statistic/StatisticCommonService.php

@@ -0,0 +1,411 @@
+<?php
+
+namespace App\Service\Statistic;
+
+
+use App\Model\DailyDwOrderDetails;
+use App\Model\DailyPwOrderDetails;
+use App\Model\MonthlyDdOrder;
+use App\Model\MonthlyDdOrderDetails;
+use App\Model\MonthlyPsOrder;
+use App\Model\MonthlyPsOrderDetails;
+use App\Service\Service;
+use Illuminate\Support\Facades\DB;
+
+class StatisticCommonService extends Service
+{
+    /**
+     * 传参相关、时间数据自动拼接
+     * @param $data
+     * @return array
+     */
+    public function commonRule($data)
+    {
+        if (!empty($data['year'])) {
+            $return = $this->getYearRangeInfo($data['year']);
+            if (is_null($return)) return [false, '年度格式错误'];
+            list($data['month_start'], $data['month_end']) = $return;
+        } else {
+            if (isset($data['time']) && !empty($data['time'])) {
+                $start = $this->changeDateToDate($data['time'][0]);
+                $end = $this->changeDateToDate($data['time'][1], true);
+                $data['month_start'] = date("Y-m-d", $start);
+                $data['month_end'] = date("Y-m-d", $end);
+            }
+        }
+
+        if (!isset($data['month_start'])) $month_start = date('Y-01-01');
+        else $month_start = date('Y-m-01', strtotime($data['month_start']));
+        if (!isset($data['month_end'])) $month_end = date('Y-01-01', strtotime('+1 year', strtotime($month_start)));
+        else {
+            $start_year = date('Y', strtotime($month_start));
+            $end_year = date('Y', strtotime($data['month_end']));
+            if ($start_year != $end_year) return [false, "查询不得跨年!", ""];
+            $month_end = date('Y-m-01', strtotime($data['month_end'] . ' +1 month'));
+        }
+
+        return [true, strtotime($month_start), strtotime($month_end)];
+    }
+
+    /**
+     * 根据前端 ISO 时间字符串获取该年份的起止日期和时间戳
+     * 适配:2019-12-31T16:00:00.000Z 这种带时区的数据
+     *
+     * @param string $isoStr 前端传来的时间字符串
+     * @return array|null
+     */
+    public function getYearRangeInfo($isoStr)
+    {
+        if (empty($isoStr)) return null;
+
+        try {
+            // 1. 解析 ISO 8601 字符串
+            $date = new \DateTime($isoStr);
+
+            // 2. 强制转为中国时区(PRC),处理 16:00:00Z 这种 UTC 偏移
+            $date->setTimezone(new \DateTimeZone('PRC'));
+
+            // 3. 提取年份
+            $year = $date->format('Y');
+
+            // 4. 构造日期字符串
+            $startDate = $year . "-01-01";
+            $endDate = $year . "-12-31";
+
+            return [
+                $startDate,
+                $endDate,
+            ];
+        } catch (\Exception $e) {
+//            var_dump($e->getMessage());die;
+            return null;
+        }
+    }
+
+
+    /**
+     * 用于拉取对应项目人员日维度的对应工时信息
+     * @param $user
+     * @param $data
+     * @param $month_start
+     * @param $month_end
+     * @return array
+     */
+    public function getItemEmployeeDayWorkList($user, $data, $month_start, $month_end)
+    {
+        $month_employee = DailyPwOrderDetails::Clear($user, $data);
+        return $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
+            ->where('del_time', 0)
+            ->select(
+                "item_id",
+                "employee_id",
+                // 将时间戳转为 Y-m-d 格式并起别名
+                DB::raw("FROM_UNIXTIME(order_time, '%Y-%m-%d') as order_date"),
+                // 聚合求和
+                DB::raw("SUM(total_work_min) as total_work")
+            )
+            ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m-%d')"), "item_id", "employee_id")
+            ->orderby("order_date", "asc")->get()->toArray();
+    }
+
+    /**
+     * 用于拉取对应项目设备日维度的对应工时信息
+     * @param $user
+     * @param $data
+     * @param $month_start
+     * @param $month_end
+     * @return array
+     */
+    public function getItemDeviceMonthWorkList($user, $data, $month_start, $month_end)
+    {
+        $month_device = DailyDwOrderDetails::Clear($user, $data);
+        return $month_device->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
+            ->where('del_time', 0)
+            ->select(
+                "item_id",
+                "device_id",
+                // 将时间戳转为 Y-m-d 格式并起别名
+                DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
+                // 聚合求和
+                DB::raw("SUM(total_work_min) as total_work")
+            )
+            ->groupBy(DB::raw("FROM_UNIXTIME(order_time, '%Y-%m')"), "item_id", "device_id")->get()->toArray();
+    }
+
+    /**
+     * 用于拉取对应项目人员月维度的对应工时信息
+     * @param $user
+     * @param $data
+     * @param $month_start
+     * @param $month_end
+     * @return array
+     */
+    public function getItemEmployeeMonthWorkList($user, $data, $month_start, $month_end)
+    {
+        $month_employee = DailyPwOrderDetails::Clear($user, $data);
+        return $month_employee->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)
+            ->where('del_time', 0)
+            ->select(
+                "item_id",
+                "employee_id",
+                // 将时间戳转为 Y-m-d 格式并起别名
+                DB::raw("FROM_UNIXTIME(order_time, '%Y-%m') as order_month"),
+                // 聚合求和
+                DB::raw("SUM(total_work_min) as total_work")
+            )
+            ->groupBy("order_month", "item_id", "employee_id")->get()->toArray();
+    }
+
+    /**
+     * 统计对应条件的人员月度工资
+     * @param $user
+     * @param $data
+     * @param $month_start
+     * @param $month_end
+     * @return array
+     */
+    public function getEmployeeSalary($user, $data, $month_start, $month_end){
+
+        $monthly_ps_order_ids = MonthlyPsOrder::Clear($user, $data)
+            ->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
+            ->pluck('id')->toArray();
+        $monthly_ps_order_key_list = MonthlyPsOrder::Clear($user, $data)
+            ->where('del_time', 0)
+            ->where("month", ">=", $month_start)
+            ->where("month", "<", $month_end)
+            ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
+            ->pluck('month_str', 'id')
+            ->toArray();
+        $month_employee_salary = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
+            ->select("employee_id",DB::raw("(base_salary + performance_salary + bonus + other) as salary"), "main_id")
+            ->get()->toArray();
+        return collect($month_employee_salary)->mapWithKeys(function ($val) use ($monthly_ps_order_key_list) {
+            $month = $monthly_ps_order_key_list[$val['main_id']] ?? null;
+            // 如果没有找到月份,返回空数组,mapWithKeys 会自动忽略它
+            if (!$month) {
+                return [];
+            }
+            return [$val['employee_id'] . '_' . $month => (int)round($val['salary'] * 100)];
+        })->toArray();
+    }
+
+    /**
+     * 统计对应条件的设备月度费用
+     * @param $user
+     * @param $data
+     * @param $month_start
+     * @param $month_end
+     * @return array
+     */
+    public function getDeviceAmount($user, $data, $month_start, $month_end){
+
+        $monthly_dd_order_ids = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
+            ->pluck('id')->toArray();
+        $monthly_dd_order_key_list = MonthlyDdOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end)
+            ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
+            ->pluck("month_str", 'id')->toArray();
+        $month_device_salary = MonthlyDdOrderDetails::wherein('main_id', $monthly_dd_order_ids)
+            ->select("device_id", "depreciation_amount", "main_id")
+            ->get()->toArray();
+        return collect($month_device_salary)->mapWithKeys(function ($val) use ($monthly_dd_order_key_list) {
+            $month = $monthly_dd_order_key_list[$val['main_id']] ?? null;
+            // 如果没有找到月份,返回空数组,mapWithKeys 会自动忽略它
+            if (!$month) {
+                return [];
+            }
+            return [$val['device_id'] . '_' . $month => (int)round($val['depreciation_amount'] * 100)];
+        })->toArray();
+    }
+
+    /**
+     * 基于key用于统计信息的总数量
+     * @param $list
+     * @param $key
+     * @return array
+     */
+    public function calculateCount($list, $key)
+    {
+        //统计人员相关的合计信息,用于补全计算差额
+        $collect = collect($list);
+
+        return  $collect->groupBy(function ($item) use ($key) {
+            return collect($key)->map(function ($k) use ($item) {
+                return $item[$k] ?? '';
+            })->implode('_');
+        })->map(function ($group) {
+            return $group->count();
+        })->toArray();
+    }
+    /**
+     * 基于用于统计信息的汇总信息(时)
+     * @param $list
+     * @param $key
+     * @param $sum
+     * @return array
+     */
+    public function calculateSumForHour($list, $key, $sum)
+    {
+        //统计人员相关的合计信息,用于补全计算差额
+        $collect = collect($list);
+        return $collect->groupBy(function ($item) use ($key) {
+            // 动态拼接分组 Key:例如 "101_2023-10-01"
+            return collect($key)->map(fn($k) => $item[$k] ?? '')->implode('_');
+        })->map(function ($group) use ($sum) {
+            // 1. 动态对指定字段求和,算出小时并保留两位小数
+            $hours = round($group->sum($sum) / 60, 2);
+
+            // 2. 乘以 100 并转为整数,存储为分位(解决浮点数精度问题)
+            return (int)round($hours * 100);
+        })->toArray();
+
+    }
+    /**
+     * 基于用于统计信息的汇总信息
+     * @param $list
+     * @param $key
+     * @param $sum
+     * @return array
+     */
+    public function calculateSum($list, $key, $sum)
+    {
+        //统计人员相关的合计信息,用于补全计算差额
+        $collect = collect($list);
+        return $collect->groupBy(function ($item) use ($key) {
+            // 动态拼接分组 Key:例如 "101_2023-10-01"
+            return collect($key)->map(fn($k) => $item[$k] ?? '')->implode('_');
+        })->map(function ($group) use ($sum) {
+            return $group->sum($sum);
+        })->toArray();
+
+    }
+
+    /**
+     * 计算key的比例和统计分种维度的总计和平均工资
+     * @param $month_employee_list
+     * @return array
+     */
+    public function calculateRatioForMonth($month_employee_list,$employee_monthly_total_min,$salary_map,$key1,$key2){
+        $item_month_list = [];
+        foreach ($month_employee_list as $item) {
+            $key = collect($key1)->map(fn($k) => $item[$k] ?? '')->implode('_');
+            $item_key = collect($key2)->map(fn($k) => $item[$k] ?? '')->implode('_');
+            if (!isset($item_month_list[$item_key])) {
+                $item_month_list[$item_key] = [
+                    "month" => $item['order_month'],
+                    "total_work" => $item['total_work'],
+                    "work_minutes" => 0,
+                    "ratio" => 0,
+                    "total_min" => 0,
+                    "total_salary" => 0,
+                    "item_id" => $item['item_id'],
+                    "employee_id" => $item['employee_id'],
+                    "allocated_salary" => 0,
+                ];
+            }
+            $total_min = $employee_monthly_total_min[$key] ?? 0;
+            $total_salary = $salary_map[$key] ?? 0;
+            $item_month_list[$item_key]['total_min'] += $total_salary;
+            // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
+            if ($total_min > 0) {
+                $ratio = $item['total_work'] / $total_min;
+                $allocated_salary = round($ratio * $total_salary, 2);
+            } else {
+                $allocated_salary = 0;
+                $ratio = 0;
+            }
+            $item_month_list[$item_key]['ratio'] += round($ratio,2);
+            $item_month_list[$item_key]['allocated_salary'] += $allocated_salary;
+            $item_month_list[$item_key]['work_minutes'] += $item['total_work'];
+        }
+        return $item_month_list;
+    }
+
+
+    /**
+     * 计算设备的的比例和统计分种维度的总计和平均工资
+     * 并且返回总计金额和总计工时
+     * @param $month_employee_list
+     * @return array
+     */
+    public function calculateDeviceRatioForMonth($month_device_list,$device_monthly_total_min,$salary_map,$key1,$key2){
+        $item_month_list = [];
+        $device_total_depreciation = [];
+        foreach ($month_device_list as $item) {
+            $key = collect($key1)->map(fn($k) => $item[$k] ?? '')->implode('_');
+            $item_key = collect($key2)->map(fn($k) => $item[$k] ?? '')->implode('_');
+            $total_depreciation = $salary_map[$key] ?? 0;
+            $total_min = $device_monthly_total_min[$key] ?? 0;
+            if (!isset($item_month_list[$item_key])) {
+                $item_month_list[$item_key] = [
+                    "month" => $item['order_month'],
+                    "allocated_depreciation" => 0,
+                    "work_minutes" => 0,
+                    "total_min" => $total_min,
+                    "item_id" => $item['item_id'],
+                    "device_id" => $item['device_id'],
+                    "total_depreciation" => $total_depreciation,
+                ];
+                $device_total_depreciation['total_hours'][$key] =  round($total_min/60,2)*100;
+                $device_total_depreciation['total_depreciation'][$key] =  $total_depreciation*100;
+            }
+
+
+            // B. 计算工资分摊:(项目工时 / 月总工时) * 月工资
+            if ($total_min > 0) {
+                $ratio = round($item['total_work'] / $total_min, 3);
+                $allocated_salary = round($ratio * $total_depreciation, 2);
+            } else {
+                $ratio = 0;
+                $allocated_salary = 0;
+            }
+            $item_month_list[$item_key]['allocated_depreciation'] += $allocated_salary;
+            $item_month_list[$item_key]['work_minutes'] += $item['total_work'];
+            $item_month_list[$item_key]['ratio'] = $ratio;
+
+        }
+        return [$item_month_list,$device_total_depreciation];
+    }
+
+    /**
+     * 闭包中调用计算余数方法
+     * @param $key
+     * @param $item
+     * @param $count
+     * @param $sum
+     * @param $word_keys
+     * @return void
+     */
+    public function calculateClosure($key, &$item, &$count, &$sum, $word_keys)
+    {
+//        数据格式
+//        $word_keys = [
+//            "employee_work_count" =>
+//                [
+//                    "key" => "total_work",
+//                    "value" => "total_work_hours",
+//                    "type" => "hour",
+//                ]
+//        ];
+        foreach ($word_keys as $k => $v) {
+
+
+
+            if($v['type'] == 'ratio'&&!isset($sum[$k][$key])) $sum[$k][$key] = 100;
+            if( !isset($sum[$k][$key]))  $sum[$k][$key] = 0;
+            if (--$count[$key] > 0) {
+                if($v['type'] == "hour") $current_hours = round($item[$v['key']] / 60, 2);
+                elseif($v['type'] == "money") $current_hours = round($item[$v['key']] / 100, 2);
+                else $current_hours = $item[$v['key']];
+                $item[$v['value']] = $current_hours;
+                $sum[$k][$key] -= $current_hours * 100;
+            } else {
+
+                $item[$v['value']] = round($sum[$k][$key] / 100, 2);
+            }
+
+        }
+
+    }
+
+
+}