cqp 1 месяц назад
Родитель
Сommit
f954de33fc
1 измененных файлов с 114 добавлено и 86 удалено
  1. 114 86
      app/Service/StatisticService.php

+ 114 - 86
app/Service/StatisticService.php

@@ -1429,115 +1429,143 @@ class StatisticService extends StatisticCommonService
 
     public function itemEmployeeSalaryStatistic($data, $user)
     {
-        // 1. 基础校验
         list($status, $month_start, $month_end) = $this->commonRule($data);
         if (!$status) return [false, $month_start];
 
-        // 2. 获取工时记录 (人+项目+月)
+        // 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();
+            ->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()->toArray();
+
+        // 2. 获取工资及主表月份
+        $ps_query = MonthlyPsOrder::Clear($user, $data)
+            ->where('del_time', 0)
+            ->where("month", ">=", $month_start)
+            ->where("month", "<", $month_end);
 
-        // 3. 获取工资主表与月份映射
-        $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();
+        $monthly_ps_order_key_list = $ps_query
+            ->select('id', DB::raw("FROM_UNIXTIME(month, '%Y-%m') as month_str"))
+            ->pluck('month_str', 'id')->toArray();
 
-        // 4. 获取详细工资并按人+月汇总 (单位:分)
-        $salary_details = MonthlyPsOrderDetails::whereIn('main_id', $monthly_ps_order_ids)
-            ->select("employee_id", "main_id", "social_insurance", "public_housing_fund",
-                DB::raw("(base_salary + performance_salary + bonus + other) as salary_orig"))->get();
+        $month_employee_salary_raw = MonthlyPsOrderDetails::wherein('main_id', $monthly_ps_order_ids)
+            ->select("employee_id", DB::raw("(base_salary + performance_salary + bonus + other) as salary"), "main_id", "social_insurance", "public_housing_fund")
+            ->get()->toArray();
 
-        $salary_pool = []; // 格式:[key][field] => cents
-        foreach ($salary_details as $val) {
-            $m = $month_map[$val['main_id']] ?? '';
-            if ($m) {
-                $key = $val['employee_id'] . '_' . $m;
-                if (!isset($salary_pool[$key])) {
-                    $salary_pool[$key] = ['s' => 0, 'si' => 0, 'phf' => 0];
-                }
-                $salary_pool[$key]['s'] += (int)round($val['salary_orig'] * 100);
-                $salary_pool[$key]['si'] += (int)round($val['social_insurance'] * 100);
-                $salary_pool[$key]['phf'] += (int)round($val['public_housing_fund'] * 100);
+        // 3. 构建高精度原始数据池 (放大100倍处理)
+        $salary_map = [];
+        $all_salary_cents = []; // 余额平账池
+        foreach ($month_employee_salary_raw as $val) {
+            $month = $monthly_ps_order_key_list[$val['main_id']] ?? '';
+            if ($month) {
+                $key = $val['employee_id'] . '_' . $month;
+                $salary_map[$key] = $val;
+                $all_salary_cents[$key] = [
+                    "salary" => (int)round($val['salary'] * 100),
+                    "social_insurance" => (int)round($val['social_insurance'] * 100),
+                    "public_housing_fund" => (int)round($val['public_housing_fund'] * 100),
+                ];
             }
         }
 
-        // 5. 统计总工时与项目计数
-        $emp_total_min = [];
-        $emp_item_count = [];
+        // 4. 统计月总工时及工时余额池
+        $employee_monthly_total_min = [];
+        $all_min_cents = [];
         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;
+            $key = $row['employee_id'] . '_' . $row['order_month'];
+            $employee_monthly_total_min[$key] = ($employee_monthly_total_min[$key] ?? 0) + $row['total_work'];
+            // 工时也按放大100倍处理平账
+            if (!isset($all_min_cents[$key])) {
+                $all_min_cents[$key] = (int)round(($employee_monthly_total_min[$key] / 60), 2) * 100;
+            }
         }
 
-        // 6. 分摊计算
-        $rem_pool = $salary_pool; // 剩余金额池
-        $rem_work_pool = [];      // 剩余工时池 (小时*100)
-        foreach ($emp_total_min as $k => $min) {
-            $rem_work_pool[$k] = (int)round(($min / 60) * 100);
+        // 5. 准备基础资料 (项目和人员)
+        $item_month_list = [];
+        foreach ($month_employee_list as $item) {
+            $key = $item['employee_id'] . '_' . $item['order_month'];
+            $item_key = $item['order_month'] . '_' . $item['item_id'] . '_' . $item['employee_id'];
+
+            $item_month_list[$item_key] = [
+                "month" => $item['order_month'],
+                "employee_id" => $item['employee_id'],
+                "item_id" => $item['item_id'],
+                "work_minutes_raw" => $item['total_work'], // 原始分钟
+                "total_min_raw" => $employee_monthly_total_min[$key] ?? 0,
+                // 归集总额字段保留(原始展示用)
+                "total_salary" => ($salary_map[$key]['salary'] ?? 0),
+                "total_social_insurance" => ($salary_map[$key]['social_insurance'] ?? 0),
+                "total_public_housing_fund" => ($salary_map[$key]['public_housing_fund'] ?? 0),
+            ];
         }
 
-        $final_list = [];
-        foreach ($month_employee_list as $item) {
-            $key = $item->employee_id . '_' . $item->order_month;
-            $total_m = $emp_total_min[$key] ?? 0;
+        $employee_ids = collect($item_month_list)->pluck('employee_id')->unique()->values()->all();
+        $employee_key_list = Employee::TopClear($user, $data)->wherein('id', $employee_ids)
+            ->select("major", "title", "id")->get()->keyBy('id')->toArray();
 
-            $current_hours_cents = (int)round(($item->total_work / 60) * 100);
+        $items_ids = collect($item_month_list)->pluck('item_id')->unique()->values()->all();
+        $item_info = Item::TopClear($user, $data)->wherein('id', $items_ids)
+            ->select("title", "code", "id")->get()->keyBy('id')->toArray();
 
-            if ($total_m > 0 && --$emp_item_count[$key] > 0) {
-                // 非最后一项:按高精度比例计算
-                $ratio = $item->total_work / $total_m;
+        // 6. 确定每个人的项目计数(用于平账判断)
+        $employee_count = collect($item_month_list)->groupBy(function ($item) {
+            return $item['employee_id'] . '_' . $item['month'];
+        })->map(fn($group) => $group->count())->toArray();
 
-                $work_s = (int)floor($ratio * $salary_pool[$key]['s']);
-                $work_si = (int)floor($ratio * $salary_pool[$key]['si']);
-                $work_phf = (int)floor($ratio * $salary_pool[$key]['phf']);
+        // 7. 循环处理精度和分摊
+        $result = collect($item_month_list)->sortBy('month')->values()->transform(function ($item) use ($item_info, $employee_key_list, &$employee_count, &$all_salary_cents, &$all_min_cents) {
+            $key = $item['employee_id'] . '_' . $item['month'];
 
-                $rem_pool[$key]['s'] -= $work_s;
-                $rem_pool[$key]['si'] -= $work_si;
-                $rem_pool[$key]['phf'] -= $work_phf;
-                $rem_work_pool[$key] -= $current_hours_cents;
-            } else {
-                // 最后一项:平账
-                $work_s = $rem_pool[$key]['s'] ?? 0;
-                $work_si = $rem_pool[$key]['si'] ?? 0;
-                $work_phf = $rem_pool[$key]['phf'] ?? 0;
-                $current_hours_cents = $rem_work_pool[$key] ?? 0;
-            }
+            // 基础信息填充
+            $item['item_title'] = $item_info[$item['item_id']]['title'] ?? "未知项目";
+            $item['item_code'] = $item_info[$item['item_id']]['code'] ?? "-";
+            $item['employee_title'] = $employee_key_list[$item['employee_id']]['title'] ?? "未知人员";
+            $item['major'] = $employee_key_list[$item['employee_id']]['major'] ?? "-";
 
-            $final_list[] = [
-                'month' => $item->order_month,
-                'item_id' => $item->item_id,
-                'employee_id' => $item->employee_id,
-                'total_hours' => round($total_m / 60, 2),
-                'work_hours' => round($current_hours_cents / 100, 2),
-                'ratio' => $total_m > 0 ? round($item->total_work / $total_m, 4) : 0,
-                // 归集金额
-                'total_salary' => ($salary_pool[$key]['s'] ?? 0) / 100,
-                'total_si' => ($salary_pool[$key]['si'] ?? 0) / 100,
-                'total_phf' => ($salary_pool[$key]['phf'] ?? 0) / 100,
-                // 研发分摊金额
-                'work_salary' => $work_s / 100,
-                'work_si' => $work_si / 100,
-                'work_phf' => $work_phf / 100,
-            ];
-        }
+            // 总工时展示(小时)
+            $item['total_hours'] = round($item['total_min_raw'] / 60, 2);
 
-        // 7. 补充基础信息 (Item, Employee)
-        $i_ids = collect($final_list)->pluck('item_id')->unique()->all();
-        $e_ids = collect($final_list)->pluck('employee_id')->unique()->all();
-        $i_info = Item::TopClear($user, $data)->whereIn('id', $i_ids)->get()->keyBy('id');
-        $e_info = Employee::TopClear($user, $data)->whereIn('id', $e_ids)->get()->keyBy('id');
+            // 比例计算 (不四舍五入,用于中间乘法)
+            $ratio = $item['total_min_raw'] > 0 ? ($item['work_minutes_raw'] / $item['total_min_raw']) : 0;
+            $item['work_ratio'] = round($ratio, 4); // 仅展示用
 
-        // 8. 格式化输出
-        $result = collect($final_list)->sortBy('month')->transform(function ($v) use ($i_info, $e_info) {
-            $v['item_title'] = $i_info[$v['item_id']]->title ?? "未知({$v['item_id']})";
-            $v['item_code'] = $i_info[$v['item_id']]->code ?? "-";
-            $v['employee_title'] = $e_info[$v['employee_id']]->title ?? "未知({$v['employee_id']})";
-            $v['major'] = $e_info[$v['employee_id']]->major ?? "-";
-            return $v;
-        })->values()->all();
+            if (--$employee_count[$key] > 0) {
+                // 非最后一项
+                $work_hours = round($item['work_minutes_raw'] / 60, 2);
+                $item['work_hours'] = $work_hours;
+                $all_min_cents[$key] -= (int)round($work_hours * 100);
+
+                $w_salary = round($item['total_salary'] * $ratio, 2);
+                $item['work_salary'] = $w_salary;
+                $all_salary_cents[$key]['salary'] -= (int)round($w_salary * 100);
+
+                $w_social = round($item['total_social_insurance'] * $ratio, 2);
+                $item['work_social_insurance'] = $w_social;
+                $all_salary_cents[$key]['social_insurance'] -= (int)round($w_social * 100);
+
+                $w_fund = round($item['total_public_housing_fund'] * $ratio, 2);
+                $item['work_public_housing_fund'] = $w_fund;
+                $all_salary_cents[$key]['public_housing_fund'] -= (int)round($fund * 100);
+            } else {
+                // 最后一项:直接拿余额平账,确保总额一致
+                $item['work_salary'] = round($all_salary_cents[$key]['salary'] / 100, 2);
+                $item['work_social_insurance'] = round($all_salary_cents[$key]['social_insurance'] / 100, 2);
+                $item['work_public_housing_fund'] = round($all_salary_cents[$key]['public_housing_fund'] / 100, 2);
+                $item['work_hours'] = round($all_min_cents[$key] / 100, 2);
+            }
+
+            // 清理不需要输出的原始辅助字段
+            unset($item['work_minutes_raw'], $item['total_min_raw']);
+            return $item;
+        })->all();
 
         return [true, $result];
     }