doGenerate($data['month']); }); } catch (\Throwable $e) { return [false, $e->getMessage()]; } return [true, '']; } protected function doGenerate(string $month) { $monthStart = Carbon::createFromFormat('Y-m', $month)->startOfMonth()->timestamp; $monthEnd = Carbon::createFromFormat('Y-m', $month)->endOfMonth()->timestamp; // 日历 $calendar = DB::table('calendar') ->where('time', $monthStart) ->first(); if (!$calendar) { throw new \Exception('当月日历未配置'); } // 工作日 $workDays = DB::table('calendar_details') ->where('calendar_id', $calendar->id) ->where('is_work', 1) ->orderBy('time') ->pluck('time') ->toArray(); if (empty($workDays)) { throw new \Exception('当月无工作日'); } // 启用项目 $items = DB::table('item') ->where('is_use', 1) ->where('start_time', '<=', $monthEnd) ->where('end_time', '>=', $monthStart) ->get(); // 删除当月所有项目的老数据 $itemIds = $items->pluck('id')->toArray(); if (!empty($itemIds)) { $rds = DB::table('rd') ->whereIn('item_id', $itemIds) ->whereBetween('order_time', [$monthStart, $monthEnd]) ->pluck('id') ->toArray(); if (!empty($rds)) { DB::table('rd_details')->whereIn('rd_id', $rds)->delete(); DB::table('rd')->whereIn('id', $rds)->delete(); } } // 获取所有员工id $employeeIds = DB::table('item_details') ->whereIn('item_id', $itemIds) ->where('type', 1) ->pluck('data_id') ->unique() ->toArray(); // 按员工生成当月工时单 foreach ($employeeIds as $employeeId) { $this->generateEmployeeMonth($employeeId, $items, $workDays, $calendar); } } /** * 按员工生成当月工时单,严格保证总工时匹配 */ protected function generateEmployeeMonth(int $employeeId, $items, array $workDays, $calendar) { $employeeMonth = DB::table('employee_details') ->where('employee_id', $employeeId) ->where('time', $calendar->time) ->first(); if (!$employeeMonth || $employeeMonth->total_hours_2 <= 0) { return; } $totalMinutes = (int)round($employeeMonth->total_hours_2 * 60); // 员工总分钟数 $dayMaxMinutes = 510; // 每天最大分钟数 (含午休) // 员工参与的项目 $employeeItems = []; foreach ($items as $item) { $exists = DB::table('item_details') ->where('item_id', $item->id) ->where('type', 1) ->where('data_id', $employeeId) ->exists(); if ($exists) { $employeeItems[] = $item; } } if (empty($employeeItems)) return; // 生成所有槽位:项目 × 工作日(有效周期内) $slots = []; foreach ($employeeItems as $item) { foreach ($workDays as $day) { if ($day >= $item->start_time && $day <= $item->end_time) { $slots[] = [ 'item_id' => $item->id, 'day' => $day ]; } } } if (empty($slots)) return; // 均分总分钟数 $numSlots = count($slots); $baseMinutes = floor($totalMinutes / $numSlots); $remainder = $totalMinutes - $baseMinutes * $numSlots; foreach ($slots as $i => $slot) { $minutes = $baseMinutes; if ($i === $numSlots - 1) { // 最后 slot 补齐余数 $minutes += $remainder; } // 确保单日不超过最大分钟数 $minutes = min($minutes, $dayMaxMinutes - 30); // 扣掉午休 $this->createRd($slot['item_id'], $slot['day'], $minutes, $employeeId); } } /** * 创建单条研发工时单 */ protected function createRd(int $itemId, int $dayTime, int $minutes, int $employeeId) { $dayStart = Carbon::createFromTimestamp($dayTime)->setTime(8, 0); $lunchStart = Carbon::createFromTimestamp($dayTime)->setTime(11, 30); $lunchEnd = Carbon::createFromTimestamp($dayTime)->setTime(12, 0); $dayEnd = Carbon::createFromTimestamp($dayTime)->setTime(16, 30); // 上午可用分钟 $morningMinutes = $lunchStart->diffInMinutes($dayStart); // 下午可用分钟 $afternoonMinutes = $dayEnd->diffInMinutes($lunchEnd); // 确保总工时不会超过全天可用分钟 $minutes = min($minutes, $morningMinutes + $afternoonMinutes); // 决定工时分配到上午和下午 if ($minutes <= $morningMinutes) { $start = $dayStart->copy()->addMinutes(mt_rand(0, $morningMinutes - $minutes)); $end = $start->copy()->addMinutes($minutes); } elseif ($minutes <= $afternoonMinutes) { $start = $lunchEnd->copy()->addMinutes(mt_rand(0, $afternoonMinutes - $minutes)); $end = $start->copy()->addMinutes($minutes); } else { // 分两段:上午尽量满,剩余在下午 $start = $dayStart->copy(); $end = $lunchEnd->copy()->addMinutes($minutes - $morningMinutes); } // 调整到整5分钟 $start->minute = ceil($start->minute / 5) * 5; $end->minute = ceil($end->minute / 5) * 5; // crt_time 16:30 ~ 17:00 $crtTime = Carbon::createFromTimestamp($dayTime) ->setTime(16, 30) ->addSeconds(mt_rand(0, 30*60)) ->timestamp; $dayPrefix = date('Ymd', $dayTime); $orderNumber = 'RD' . $dayPrefix . mt_rand(1000, 9999) . substr(uniqid(), -3); $rdId = DB::table('rd')->insertGetId([ 'crt_time' => $crtTime, 'order_time' => $dayTime, 'item_id' => $itemId, 'start_time_hour' => $start->hour, 'start_time_min' => $start->minute, 'end_time_hour' => $end->hour, 'end_time_min' => $end->minute, 'total_hours' => $minutes, 'order_number' => $orderNumber, 'type' => 1 ]); DB::table('rd_details')->insert([ 'rd_id' => $rdId, 'data_id' => $employeeId, 'type' => 1, 'crt_time' => $crtTime, 'upd_time' => $crtTime, ]); } }