option('timezone') ?: config('app.timezone', 'Europe/Moscow'); $shiftStartTime = $this->option('shift-start') ?: '09:00'; // 1. Валидация и парсинг дат $startDate = Carbon::parse($this->option('start') ?: now($tz)->format('Y-m-d'), $tz)->setTimeFromTimeString($shiftStartTime); $endDate = Carbon::parse($this->option('end') ?: $this->option('start') ?: now($tz)->format('Y-m-d'), $tz)->setTimeFromTimeString($shiftStartTime); if ($endDate->lt($startDate)) { $this->error('Конечная дата не может быть раньше начальной.'); return CommandAlias::FAILURE; } // 2. Разрешение списка отделений $departments = $this->resolveDepartments($this->option('departments')); if ($departments->isEmpty()) { $this->error('Отделения не найдены.'); return CommandAlias::FAILURE; } // 3. Генерация массива смен $shifts = []; $current = $startDate->copy(); while ($current->lte($endDate)) { $shifts[] = [ 'start' => $current->copy(), 'end' => $current->copy()->addDay(), // 09:00 → 09:00 следующего дня ]; $current->addDay(); } // 4. Пользователь CLI $userId = $this->option('user') ? (int) $this->option('user') : null; if (!$userId && !$this->option('dry-run')) { $this->error('Для записи в БД в CLI режиме укажите параметр --user='); return CommandAlias::FAILURE; } // 5. Вывод информации $totalTasks = count($shifts) * $departments->count(); if ($totalTasks === 0) { $this->warn('Нет задач для выполнения.'); return CommandAlias::SUCCESS; } $this->info("Период: {$shifts[0]['start']->format('Y-m-d H:i')} → {$shifts[array_key_last($shifts)]['end']->format('Y-m-d H:i')} ({$tz})"); $this->info("Отделений: {$departments->count()}"); $this->info("Всего смен: {$totalTasks}"); if ($this->option('dry-run')) $this->warn("Режим DRY RUN"); if ($this->option('skip-existing')) $this->warn("Пропуск существующих отчётов включён"); $progressBar = $this->output->createProgressBar($totalTasks); $progressBar->start(); $success = 0; $skipped = 0; $errors = 0; // 6. Основной цикл foreach ($departments as $dept) { foreach ($shifts as $shift) { try { $status = $this->processShift( $shift['start'], $shift['end'], $dept, $userId, $this->reportService, $this->dateRangeService ); if ($status === 'skip' || $status === 'dry_run') { $skipped++; } else { $success++; } } catch (Throwable $e) { $errors++; // Безопасное получение имени отделения $deptName = $dept->name ?? $dept->department_name ?? "Отдел #{$dept->department_id}"; $this->error("\n[{$deptName}] {$shift['start']->format('Y-m-d H:i')}: {$e->getMessage()}"); Log::error('DutyReportShiftGeneration', [ 'department_id' => $dept->department_id, 'shift_start' => $shift['start']->format('Y-m-d H:i:s'), 'shift_end' => $shift['end']->format('Y-m-d H:i:s'), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); } finally { // ✅ ГАРАНТИРУЕМ ровно 1 шаг прогресса на каждую итерацию $progressBar->advance(); } } } $progressBar->finish(); $this->newLine(2); $this->info("Завершено. Успешно: {$success} | Пропущено: {$skipped} | Ошибок: {$errors}"); return $errors > 0 ? CommandAlias::FAILURE : CommandAlias::SUCCESS; } /** * Получение списка отделений */ private function resolveDepartments($input) { if (!$input || strtolower($input) === 'all') { return Department::orderBy('department_id')->get(); } $ids = array_map('trim', explode(',', $input)); return Department::whereIn('department_id', $ids)->get(); } /** * Обработка одной смены * @return string 'success' | 'skip' | 'dry_run' */ private function processShift( Carbon $shiftStart, Carbon $shiftEnd, $dept, int $userId, DutyReportService $reportService, DateRangeService $dateRangeService ): string { $deptId = $dept->department_id; $misDeptId = $dept->rf_mis_department_id; // Пропуск, если отчёт уже существует за ЭТУ ЖЕ СМЕНУ if ($this->option('skip-existing')) { $exists = ReportDuty::where('rf_department_id', $deptId) ->where('period_start', $shiftStart->format('Y-m-d H:i:s')) ->where('period_end', $shiftEnd->format('Y-m-d H:i:s')) ->exists(); if ($exists) { return 'skip'; } } // Тестовый режим if ($this->option('dry-run')) { return 'dry_run'; } // Формируем DateRange через ваш сервис (учёт смен, часовых поясов) $user = User::find($userId); $lpuDoctorId = 0; $dateRange = $dateRangeService->createDateRangeForDate($shiftEnd, $user); // Цепочка из вашего контроллера $report = $reportService->saveReport($dateRange, $userId, $lpuDoctorId, $deptId); $stats = $reportService->saveSnapshot($dateRange, $report, $misDeptId, $userId); $reportService->saveMetrics($stats, $report); return 'success'; } }