cqp 1 сар өмнө
parent
commit
2ae7ebcb4b

+ 69 - 159
app/Service/StatisticService.php

@@ -97,82 +97,6 @@ class StatisticService extends StatisticCommonService
         return [true, $month_employee_list];
     }
 
-    public function employeeMonthSalaryStatistic3($data, $user)
-    {
-        list($status, $month_start, $month_end) = $this->commonRule($data);
-        if (!$status) return [false, $month_start];
-
-        // 1. 获取基础工时数据 (人+项目+月)
-        $month_employee_list = DailyPwOrderDetails::Clear($user, $data)
-            ->where("order_time", ">=", $month_start)->where("order_time", "<", $month_end)->where('del_time', 0)
-            ->select("item_id", "employee_id", 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();
-
-        // 2. 获取工资及配置
-        $ps_query = MonthlyPsOrder::Clear($user, $data)->where('del_time', 0)->where("month", ">=", $month_start)->where("month", "<", $month_end);
-        $monthly_ps_order_ids = $ps_query->pluck('id')->toArray();
-        $month_map = $ps_query->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))->pluck('month_str', 'id')->toArray();
-
-        // 3. 构建人员月工资 Map (单位:分)
-        $salary_details = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
-            ->select("employee_id", "main_id", DB::raw("(base_salary + performance_salary + bonus + other) as s_orig"))->get();
-
-        $salary_map = [];
-        foreach ($salary_details as $val) {
-            $m = $month_map[$val['main_id']] ?? '';
-            if ($m) {
-                $key = $val['employee_id'] . '_' . $m;
-                $salary_map[$key] = ($salary_map[$key] ?? 0) + (int)round($val['s_orig'] * 100);
-            }
-        }
-
-        // 4. 计算每人每月总工时及项目数(平账用)
-        $emp_total_min = [];
-        $emp_item_count = [];
-        foreach ($month_employee_list as $row) {
-            $k = $row->employee_id . '_' . $row->order_month;
-            $emp_total_min[$k] = ($emp_total_min[$k] ?? 0) + $row->total_work;
-            $emp_item_count[$k] = ($emp_item_count[$k] ?? 0) + 1;
-        }
-
-        // 5. 按“人+项目”分摊并按“项目”汇总
-        $rem_salary_pool = $salary_map;
-        $project_agg = [];
-        foreach ($month_employee_list as $item) {
-            $k = $item->employee_id . '_' . $item->order_month;
-            $total_s = $salary_map[$k] ?? 0;
-            $total_m = $emp_total_min[$k] ?? 0;
-
-            if ($total_m > 0 && --$emp_item_count[$k] > 0) {
-                $allocated = (int)floor(($item->total_work / $total_m) * $total_s);
-                $rem_salary_pool[$k] -= $allocated;
-            } else {
-                $allocated = $rem_salary_pool[$k] ?? 0;
-            }
-
-            $aggKey = $item->order_month . '_' . $item->item_id;
-            if (!isset($project_agg[$aggKey])) {
-                $project_agg[$aggKey] = ['month' => $item->order_month, 'item_id' => $item->item_id, 's_cents' => 0, 'mins' => 0];
-            }
-            $project_agg[$aggKey]['s_cents'] += $allocated;
-            $project_agg[$aggKey]['mins'] += $item->total_work;
-        }
-
-        // 6. 填充项目信息
-        $item_ids = collect($project_agg)->pluck('item_id')->unique()->all();
-        $item_info = Item::TopClear($user, $data)->whereIn('id', $item_ids)->get()->keyBy('id');
-
-        $result = collect($project_agg)->sortBy('month')->transform(function ($v) use ($item_info) {
-            $v['item_title'] = $item_info[$v['item_id']]->title ?? "未知({$v['item_id']})";
-            $v['item_code'] = $item_info[$v['item_id']]->code ?? "-";
-            $v['allocated_salary'] = round($v['s_cents'] / 100, 2);
-            $v['days'] = (int)round($v['mins'] / 60 / 8);
-            return $v;
-        })->values()->all();
-
-        return [true, $result];
-    }
-
     public function itemDaySalaryStatistic($data, $user)
     {
         list($status, $month_start, $month_end) = $this->commonRule($data);
@@ -254,11 +178,11 @@ class StatisticService extends StatisticCommonService
 
     public function employeeMonthSalaryStatistic($data, $user)
     {
-        // 1. 基础规则验证与时间范围获取
+        // 1. 基础规则验证
         list($status, $month_start, $month_end) = $this->commonRule($data);
         if (!$status) return [false, $month_start];
 
-        // 2. 查询员工工时明细 (按月、项目、员工聚合)
+        // 2. 获取所有工时明细(按月、项目、人聚合)
         $month_employee_list = DailyPwOrderDetails::Clear($user, $data)
             ->where("order_time", ">=", $month_start)
             ->where("order_time", "<", $month_end)
@@ -270,111 +194,97 @@ class StatisticService extends StatisticCommonService
                 DB::raw("SUM(total_work_min) as total_work")
             )
             ->groupBy("order_month", "item_id", "employee_id")
-            ->get()->toArray();
+            ->get();
 
-        // 3. 查询工资主表 (为了获取月份字符串映射)
-        $monthly_ps_order_query = MonthlyPsOrder::Clear($user, $data)
+        if ($month_employee_list->isEmpty()) {
+            return [true, []];
+        }
+
+        // 3. 获取工资数据
+        $ps_query = MonthlyPsOrder::Clear($user, $data)
             ->where('del_time', 0)
             ->where("month", ">=", $month_start)
             ->where("month", "<", $month_end);
 
-        $monthly_ps_order_ids = $monthly_ps_order_query->pluck('id')->toArray();
-        $monthly_ps_order_key_list = $monthly_ps_order_query
-            ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
-            ->pluck('month_str', 'id')
-            ->toArray();
-
-        // 4. 查询工资详情 (计算每人在每个月的分摊基数)
-        $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();
+        $monthly_ps_order_ids = $ps_query->pluck('id')->toArray();
+        $month_map = $ps_query->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
+            ->pluck('month_str', 'id')->toArray();
 
-        // 汇总:员工工资映射表 及 每月总工资池
-        $salary_map = []; // [employee_id_month => salary]
-        $all_salary_pool = []; // [month => total_salary_of_month]
-        foreach ($month_employee_salary as $val) {
-            $month_str = $monthly_ps_order_key_list[$val['main_id']] ?? '';
-            if ($month_str) {
-                $s_key = $val['employee_id'] . '_' . $month_str;
-                $salary_map[$s_key] = $val['salary'];
+        $salary_details = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
+            ->select("employee_id", "main_id", DB::raw("(base_salary + performance_salary + bonus + other) as s_orig"))
+            ->get();
 
-                if (!isset($all_salary_pool[$month_str])) $all_salary_pool[$month_str] = 0;
-                $all_salary_pool[$month_str] = round($all_salary_pool[$month_str] + $val['salary'], 2);
+        // 4. 建立工资映射(统一使用分单位,避免浮点误差)
+        $salary_map = [];
+        foreach ($salary_details as $val) {
+            $m = $month_map[$val['main_id']] ?? '';
+            if ($m) {
+                $k = $val['employee_id'] . '_' . $m;
+                // 采用与 itemDaySalaryStatistic 一致的整数处理
+                $salary_map[$k] = ($salary_map[$k] ?? 0) + (int)round($val['s_orig'] * 100);
             }
         }
 
-        // 5. 计算员工全月总工时 (用于算比例)
-        $employee_monthly_total_min = [];
+        // 5. 统计每人每月总工时和参与的项目数(用于精准分摊尾差)
+        $emp_total_min = [];
+        $emp_item_count = [];
         foreach ($month_employee_list as $row) {
-            $key = $row['employee_id'] . '_' . $row['order_month'];
-            if (!isset($employee_monthly_total_min[$key])) $employee_monthly_total_min[$key] = 0;
-            $employee_monthly_total_min[$key] += $row['total_work'];
+            $k = $row->employee_id . '_' . $row->order_month;
+            $emp_total_min[$k] = ($emp_total_min[$k] ?? 0) + $row->total_work;
+            $emp_item_count[$k] = ($emp_item_count[$k] ?? 0) + 1;
         }
 
-        // 6. 按项目分摊 (初步计算,不四舍五入)
-        $item_month_list = [];
+        // 6. 执行分摊逻辑并按项目汇总
+        $item_summary = []; // 存放项目维度的汇总结果
+        $rem_salary_pool = $salary_map; // 余额池,按人+月控制
+
         foreach ($month_employee_list as $item) {
-            $e_key = $item['employee_id'] . '_' . $item['order_month'];
-            $item_key = $item['order_month'] . '_' . $item['item_id'];
+            $k = $item->employee_id . '_' . $item->order_month;
+            $total_s = $salary_map[$k] ?? 0;
+            $total_m = $emp_total_min[$k] ?? 0;
 
-            if (!isset($item_month_list[$item_key])) {
-                $item_month_list[$item_key] = [
-                    "month"          => $item['order_month'],
-                    "item_id"        => $item['item_id'],
-                    "allocated_salary" => 0,
-                    "work_minutes"   => 0,
-                ];
+            // 计算当前员工在该项目上的分摊金额(单位:分)
+            if ($total_m > 0 && --$emp_item_count[$k] > 0) {
+                // 非该员工的最后一个项目:计算分摊
+                $allocated = (int)floor(($item->total_work / $total_m) * $total_s);
+                $rem_salary_pool[$k] -= $allocated;
+            } else {
+                // 该员工的最后一个项目:取余额清零
+                $allocated = $rem_salary_pool[$k] ?? 0;
             }
 
-            $total_salary = $salary_map[$e_key] ?? 0;
-            $total_min    = $employee_monthly_total_min[$e_key] ?? 0;
-
-            if ($total_min > 0) {
-                // 这里是关键:(项目工时 / 员工月总工时) * 员工月工资
-                // 累加时保持高精度
-                $ratio = $item['total_work'] / $total_min;
-                $item_month_list[$item_key]['allocated_salary'] += ($ratio * $total_salary);
+            // 按项目+月份进行累加
+            $item_key = $item->order_month . '_' . $item->item_id;
+            if (!isset($item_summary[$item_key])) {
+                $item_summary[$item_key] = [
+                    'month'            => $item->order_month,
+                    'item_id'          => $item->item_id,
+                    'allocated_salary' => 0,
+                    'work_minutes'     => 0,
+                ];
             }
-            $item_month_list[$item_key]['work_minutes'] += $item['total_work'];
+            $item_summary[$item_key]['allocated_salary'] += $allocated;
+            $item_summary[$item_key]['work_minutes']     += $item['total_work'];
         }
 
-        // 7. 准备项目基础信息
-        $item_month_list = collect($item_month_list)->sortBy('month')->values();
-        $item_ids = $item_month_list->pluck('item_id')->unique()->toArray();
-        $item_info = Item::TopClear($user, $data)->whereIn('id', $item_ids)->get()->keyBy('id');
-
-        // 8. 最终格式化与尾差修正
-        $month_group_count = $item_month_list->groupBy('month')->map->count()->toArray();
-        $month_day_pool = $item_month_list->groupBy('month')->map(fn($g) => round($g->sum('work_minutes') / 480))->toArray();
-
-        $final_list = $item_month_list->transform(function ($item) use (
-            $item_info,
-            &$all_salary_pool,
-            &$month_day_pool,
-            &$month_group_count
-        ) {
-            $month = $item['month'];
-            $item['item_title'] = $item_info[$item['item_id']]->title ?? "未知项目";
-            $item['item_code']  = $item_info[$item['item_id']]->code ?? "N/A";
+        // 7. 获取项目信息并进行最终格式化
+        $i_ids = collect($item_summary)->pluck('item_id')->unique()->all();
+        $i_info = Item::TopClear($user, $data)->whereIn('id', $i_ids)->get()->keyBy('id');
 
-            if ($month_group_count[$month] > 1) {
-                // 非本月最后一个项目:四舍五入并扣除余额
-                $item['days'] = round($item['work_minutes'] / 480);
-                $item['allocated_salary'] = round($item['allocated_salary'], 2);
+        $result = collect($item_summary)->sortBy('month')->transform(function ($v) use ($i_info) {
+            $v['item_title'] = $i_info[$v['item_id']]->title ?? "未知项目({$v['item_id']})";
+            $v['item_code']  = $i_info[$v['item_id']]->code ?? "-";
 
-                $month_day_pool[$month] -= $item['days'];
-                $all_salary_pool[$month] = round($all_salary_pool[$month] - $item['allocated_salary'], 2);
-                $month_group_count[$month]--;
-            } else {
-                // 本月最后一个项目:直接拿走剩下的所有钱和天数,解决精度问题
-                $item['days'] = $month_day_pool[$month];
-                $item['allocated_salary'] = $all_salary_pool[$month];
-            }
+            // 将“分”转回“元”
+            $v['allocated_salary'] = round($v['allocated_salary'] / 100, 2);
+            // 计算天数(按 8 小时/天)
+            $v['days'] = round($v['work_minutes'] / 480);
 
-            return $item;
-        });
+            unset($v['work_minutes']); // 移除中间计算字段
+            return $v;
+        })->values()->all();
 
-        return [true, $final_list->all()];
+        return [true, $result];
     }