Обновлен стартовый экран
Переписаны запросы для статистики, отчетов Добавлена интеграция отчета сестры
This commit is contained in:
@@ -104,10 +104,10 @@ class FillReportsFromDate extends Command
|
||||
->whereHas('departments', function ($departmentQuery) use ($department) {
|
||||
$departmentQuery->where('rf_department_id', $department->department_id);
|
||||
})
|
||||
->whereHas('roles', function ($roleQuery) {
|
||||
$roleQuery->where('slug', 'doctor');
|
||||
->whereHas('appRoles', function ($roleQuery) {
|
||||
$roleQuery->where('slug', 'dej');
|
||||
})
|
||||
->with(['roles', 'departments']);
|
||||
->with(['appRoles', 'departments']);
|
||||
|
||||
if ($userId) {
|
||||
return $query->where('id', $userId)->first();
|
||||
@@ -116,7 +116,7 @@ class FillReportsFromDate extends Command
|
||||
return $query->get()
|
||||
->sortBy(function (User $user) use ($department) {
|
||||
$departmentLink = $user->departments->firstWhere('rf_department_id', $department->department_id);
|
||||
$isDoctor = $user->roles->contains('slug', 'doctor');
|
||||
$isDoctor = $user->appRoles->contains('slug', 'dej');
|
||||
$isFavorite = (bool) optional($departmentLink)->is_favorite;
|
||||
$order = optional($departmentLink)->order ?? PHP_INT_MAX;
|
||||
|
||||
|
||||
191
app/Console/Commands/GenerateDutyReport.php
Normal file
191
app/Console/Commands/GenerateDutyReport.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\ReportDuty;
|
||||
use App\Models\User;
|
||||
use App\Services\DateRange;
|
||||
use App\Services\DateRangeService;
|
||||
use App\Services\DutyReportService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Console\Command\Command as CommandAlias;
|
||||
use Throwable;
|
||||
|
||||
class GenerateDutyReport extends Command
|
||||
{
|
||||
protected $signature = 'duty:generate
|
||||
{--start= : Начальная дата смены (YYYY-MM-DD). По умолчанию сегодня}
|
||||
{--end= : Конечная дата смены (YYYY-MM-DD). По умолчанию равно --start}
|
||||
{--departments= : ID отделений через запятую или "all"}
|
||||
{--user= : ID пользователя для аудита (обязательно для CLI)}
|
||||
{--shift-start=09:00 : Время начала смены (HH:MM)}
|
||||
{--timezone= : Часовой пояс смены (по умолчанию config(\'app.timezone\'))}
|
||||
{--dry-run : Тестовый режим без записи в БД}
|
||||
{--skip-existing : Пропускать смены, где отчёт уже существует}';
|
||||
|
||||
protected $description = 'Пакетная генерация суточных отчётов за период по сменам (09:00–09:00)';
|
||||
|
||||
public function __construct(
|
||||
protected DutyReportService $reportService,
|
||||
protected DateRangeService $dateRangeService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$tz = $this->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=<ID>');
|
||||
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 = $user->rf_lpudoctor_id ?? 1;
|
||||
$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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user