cqp пре 2 месеци
родитељ
комит
bc842b771a
1 измењених фајлова са 165 додато и 1 уклоњено
  1. 165 1
      app/Service/PersonWorkService.php

+ 165 - 1
app/Service/PersonWorkService.php

@@ -1089,7 +1089,7 @@ class PersonWorkService extends Service
      * @param array $user 用户信息
      * @param array $user 用户信息
      * @return array
      * @return array
      */
      */
-    private function calculateDailyAllocation($monthStart, $topDepartId, $user)
+    private function calculateDailyAllocation1($monthStart, $topDepartId, $user)
     {
     {
         $monthEnd = strtotime('+1 month', $monthStart) - 1;
         $monthEnd = strtotime('+1 month', $monthStart) - 1;
         $now = time();
         $now = time();
@@ -1249,6 +1249,170 @@ class PersonWorkService extends Service
         return ['status' => true, 'data' => $previewList];
         return ['status' => true, 'data' => $previewList];
     }
     }
 
 
+    private function calculateDailyAllocation($monthStart, $topDepartId, $user)
+    {
+        $monthEnd = strtotime('+1 month', $monthStart) - 1;
+        $now = time();
+
+        // --- 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 = [];
+        foreach ($monthlyOrder as $mDetail) {
+            $empId = $mDetail->employee_id;
+            $empRules = $ruleSet->get($empId);
+            if (!$empRules) continue;
+
+            $empRemainingMin = (int)round((float)$mDetail->rd_total_hours * 60);
+            if ($empRemainingMin <= 0) continue;
+
+            foreach ($allDays as $dayInfo) {
+                if ($empRemainingMin <= 0) break;
+                $dayTs = $dayInfo->time;
+                $tempPool = $this->buildAvailablePool($empId, $dayTs, $allDays, $empWorkRanges, $standardWorkRanges, $leaveOverData);
+                $dayAvailableMin = 0;
+                foreach ($tempPool as $p) { $dayAvailableMin += (int)($p['e'] - $p['s']); }
+                if ($dayAvailableMin <= 0) continue;
+
+                $canAllocToday = min($empRemainingMin, $dayAvailableMin);
+                $allocatedInDay = 0;
+                $ruleCount = count($empRules);
+
+                foreach ($empRules as $index => $rule) {
+                    $rate = (float)$rule->rate / 100;
+                    if ($index === $ruleCount - 1) {
+                        $projectMin = $canAllocToday - $allocatedInDay;
+                    } else {
+                        $projectMin = (int)round($canAllocToday * $rate);
+                    }
+                    if ($projectMin > 0) {
+                        $finalAlloc[$dayTs][$rule->item_id][$empId] = $projectMin;
+                        $allocatedInDay += $projectMin;
+                    }
+                }
+                $empRemainingMin -= $canAllocToday;
+            }
+        }
+
+        // --- 3. 阶段二:打散到具体时间点并对齐 30 分钟步长 ---
+        $previewList = [];
+        $dailyEmpTimePools = [];
+        $tempMainIdCounter = 1;
+        $step = 30; // 设定步长为 30 分钟
+
+        foreach ($finalAlloc as $dayTs => $projects) {
+            foreach ($projects as $itemId => $employees) {
+                $currentTempMainId = $tempMainIdCounter++;
+                $itemTitle = $itemMap[$itemId] ?? '未知项目';
+
+                foreach ($employees as $empId => $toAllocMin) {
+                    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;
+
+                        // 1. 起始点对齐:如果当前开始时间不是 30 的倍数,向上取整
+                        // 例如:08:15 (495min) -> 08:30 (510min)
+                        if ($p['s'] % $step != 0) {
+                            $p['s'] = ceil($p['s'] / $step) * $step;
+                        }
+
+                        $pMax = (int)($p['e'] - $p['s']);
+                        if ($pMax <= 0) continue;
+
+                        // 2. 确定本次扣除的分钟数
+                        // 如果剩余量大于步长,取步长的倍数;如果小于步长,则一次性取完
+                        if ($tempRem >= $step) {
+                            $take = min(floor($tempRem / $step) * $step, floor($pMax / $step) * $step);
+                            // 如果计算出的 take 为 0(说明当前池子不够 30min),则跳过该池子
+                            if ($take <= 0) continue;
+                        } else {
+                            $take = min($tempRem, $pMax);
+                        }
+
+                        $realStart = (int)$p['s'];
+                        $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)
     public function dailyPwOrderSave($data, $user)
     {
     {
         $list = $data['list'] ?? [];
         $list = $data['list'] ?? [];