فهرست منبع

Merge remote-tracking branch 'origin/master'

gogs 1 ماه پیش
والد
کامیت
83a3cd68ae
1فایلهای تغییر یافته به همراه143 افزوده شده و 1 حذف شده
  1. 143 1
      app/Service/PersonWorkService.php

+ 143 - 1
app/Service/PersonWorkService.php

@@ -1117,7 +1117,7 @@ class PersonWorkService extends Service
      * @param array $user 用户信息
      * @return array
      */
-    private function calculateDailyAllocation($monthStart, $topDepartId, $user)
+    private function calculateDailyAllocation1($monthStart, $topDepartId, $user)
     {
         $monthEnd = strtotime('+1 month', $monthStart) - 1;
         $now = time();
@@ -1277,6 +1277,148 @@ class PersonWorkService extends Service
         return ['status' => true, 'data' => $previewList];
     }
 
+    private function calculateDailyAllocation($monthStart, $topDepartId, $user)
+    {
+        $monthEnd = strtotime('+1 month', $monthStart) - 1;
+        $step = 5; // 5分钟硬对齐
+
+        // --- 1. 基础数据加载 (保持原版结构) ---
+        $monthlyOrder = DB::table('monthly_pw_order_details as d')
+            ->join('monthly_pw_order as m', 'm.id', '=', 'd.main_id')
+            ->leftJoin('employee as e', 'e.id', '=', 'd.employee_id')
+            ->where('m.month', $monthStart)->where('m.top_depart_id', $topDepartId)
+            ->where('m.del_time', 0)->where('d.del_time', 0)
+            ->select('d.*', 'e.title as employee_title')->get();
+
+        if ($monthlyOrder->isEmpty()) return ['status' => false, 'msg' => '未找到明细'];
+
+        $empNameMap = $monthlyOrder->pluck('employee_title', 'employee_id')->toArray();
+        $empIds = array_keys($empNameMap);
+        $itemIds = DB::table('rule_set_details as rd')->join('rule_set as r', 'r.id', '=', 'rd.main_id')
+            ->where('r.month', $monthStart)->where('r.top_depart_id', $topDepartId)->pluck('rd.item_id')->unique()->toArray();
+        $itemMap = DB::table('item')->whereIn('id', $itemIds)->pluck('title', 'id')->toArray();
+        $ruleSet = DB::table('rule_set_details as rd')->join('rule_set as r', 'r.id', '=', 'rd.main_id')
+            ->where('r.month', $monthStart)->where('rd.type', 1)->where('r.del_time', 0)->where('rd.del_time', 0)
+            ->select('rd.*')->get()->groupBy('data_id');
+
+        $empWorkRanges = DB::table('employee_work_range')->whereIn('employee_id', $empIds)->where('top_depart_id', $topDepartId)->get()->groupBy('employee_id');
+        $standardWorkRanges = DB::table('work_range_details')->where('top_depart_id', $topDepartId)->where('del_time', 0)->get();
+        $allDays = DB::table('calendar_details')->where('top_depart_id', $topDepartId)->where('month', $monthStart)->where('del_time', 0)->orderBy('time', 'asc')->get();
+        $leaveOverData = DB::table('p_leave_over_order_details as d')->join('p_leave_over_order as m', 'd.main_id', '=', 'm.id')
+            ->whereBetween('m.order_time', [$monthStart, $monthEnd])->where('d.top_depart_id', $topDepartId)->where('m.del_time', 0)
+            ->select('d.*', 'm.order_time', 'm.type as main_type')->get()->groupBy(['employee_id', 'order_time']);
+
+        // --- 2. 阶段一:计算预分配 (加入动态余数抵消) ---
+        $finalAlloc = [];
+        $empBalance = []; // 记录每个人的“工时余额” [emp_id => minutes]
+
+        foreach ($monthlyOrder as $mDetail) {
+            $empId = $mDetail->employee_id;
+            $empRules = $ruleSet->get($empId);
+            if (!$empRules) continue;
+
+            $empRemainingMin = (int)round((float)$mDetail->rd_total_hours * 60);
+
+            foreach ($allDays as $dayInfo) {
+                if ($empRemainingMin <= 0) break;
+                $dayTs = $dayInfo->time;
+                $tempPool = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
+                $dayCapacity = 0;
+                foreach ($tempPool as $p) { $dayCapacity += (int)($p['e'] - $p['s']); }
+                if ($dayCapacity <= 0) continue;
+
+                // 核心变动:计算今天理想分配量,并尝试 5 分钟取整
+                $theoryAlloc = min($empRemainingMin, $dayCapacity);
+
+                // 如果还没到最后一天,强行把当天的分配量凑成 5 的倍数
+                if ($theoryAlloc < $empRemainingMin) {
+                    // 结合之前的余额进行取整
+                    $currentWithBalance = $theoryAlloc + ($empBalance[$empId] ?? 0);
+                    $roundedAlloc = (int)round($currentWithBalance / $step) * $step;
+
+                    // 不能超过当天池子上限
+                    $canAllocToday = min($roundedAlloc, $dayCapacity);
+                    $empBalance[$empId] = $currentWithBalance - $canAllocToday; // 剩下的留给明天
+                } else {
+                    // 最后一天,全部吃掉(包含之前剩下的所有余额)
+                    $canAllocToday = $empRemainingMin;
+                }
+
+                $allocatedInDay = 0;
+                $ruleCount = count($empRules);
+                foreach ($empRules as $index => $rule) {
+                    $rate = (float)$rule->rate / 100;
+                    $projectMin = ($index === $ruleCount - 1) ? ($canAllocToday - $allocatedInDay) : (int)round($canAllocToday * $rate);
+
+                    // 内部项目分配也尽量 5 分钟取整
+                    if ($index < $ruleCount - 1 && $projectMin > $step) {
+                        $projectMin = (int)round($projectMin / $step) * $step;
+                    }
+
+                    if ($projectMin > 0) {
+                        $finalAlloc[$dayTs][$rule->item_id][$empId] = ($finalAlloc[$dayTs][$rule->item_id][$empId] ?? 0) + $projectMin;
+                        $allocatedInDay += $projectMin;
+                    }
+                }
+                $empRemainingMin -= $canAllocToday;
+            }
+        }
+
+        // --- 3. 阶段二:打散到具体点 ---
+        $previewList = [];
+        $dailyEmpTimePools = [];
+        $tempMainIdCounter = 1;
+
+        foreach ($finalAlloc as $dayTs => $projects) {
+            foreach ($projects as $itemId => $employees) {
+                $itemTitle = $itemMap[$itemId] ?? '未知项目';
+                foreach ($employees as $empId => $toAllocMin) {
+                    $currentTempMainId = $tempMainIdCounter++;
+
+                    if (!isset($dailyEmpTimePools[$dayTs][$empId])) {
+                        $dailyEmpTimePools[$dayTs][$empId] = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
+                    }
+
+                    $tempRem = (int)$toAllocMin;
+                    foreach ($dailyEmpTimePools[$dayTs][$empId] as &$p) {
+                        if ($tempRem <= 0) break;
+
+                        // 起点必须对齐 5 分钟
+                        $realStart = (int)ceil($p['s'] / $step) * $step;
+                        if ($realStart >= $p['e']) continue;
+
+                        $pMax = (int)($p['e'] - $realStart);
+                        $take = min($tempRem, $pMax);
+
+                        $realEnd = $realStart + $take;
+
+                        $previewList[] = [
+                            'temp_main_id'    => $currentTempMainId,
+                            'order_time'      => date('Y-m-d', $dayTs),
+                            'order_timestamp' => $dayTs,
+                            'item_id'         => $itemId,
+                            'item_title'      => $itemTitle,
+                            'employee_id'     => $empId,
+                            'employee_title'  => $empNameMap[$empId] ?? '未知人员',
+                            'start_time'      => sprintf('%02d:%02d', floor($realStart / 60), $realStart % 60),
+                            'end_time'        => sprintf('%02d:%02d', floor($realEnd / 60), $realEnd % 60),
+                            'start_hour'      => (int)floor($realStart / 60),
+                            'start_min'       => (int)($realStart % 60),
+                            'end_hour'        => (int)floor($realEnd / 60),
+                            'end_min'         => (int)($realEnd % 60),
+                            'total_work_min'  => $take,
+                        ];
+
+                        $tempRem -= $take;
+                        $p['s'] = $realEnd;
+                    }
+                }
+            }
+        }
+
+        return ['status' => true, 'data' => $previewList];
+    }
+
     public function dailyPwOrderSave($data, $user)
     {
         $list = $data['list'] ?? [];