monthsToStore(); $force = (bool) $this->option('force'); if ($months === []) { $this->info('Keine abgeschlossenen Monate zum Speichern.'); return self::SUCCESS; } $userQuery = User::query() ->where('admin', '>=', 1) ->where('admin', '<', 4) ->whereNull('deleted_at'); if ($userId = $this->option('user')) { $userQuery->where('id', $userId); } $users = $userQuery->get(); $this->info('Berechne Backoffice-Snapshots fuer '.$users->count().' User und '.count($months).' Monate...'); $bar = $this->output->createProgressBar($users->count()); $bar->start(); $stored = 0; $skipped = 0; foreach ($users as $user) { foreach ($months as [$year, $month]) { $exists = BackofficeStatisticsSnapshot::query() ->where('user_id', $user->id) ->where('year', $year) ->where('month', $month) ->exists(); if ($exists && ! $force) { $skipped++; continue; } $dashboardService->storeSnapshot($user, $month, $year); $stored++; } $bar->advance(); gc_collect_cycles(); } $bar->finish(); $this->newLine(); $this->info("Fertig. Gespeichert: {$stored}, uebersprungen: {$skipped}"); return self::SUCCESS; } /** * @return array */ private function monthsToStore(): array { $monthOption = $this->option('month'); $yearOption = $this->option('year'); if ($monthOption && $yearOption) { $month = max(1, min(12, (int) $monthOption)); $year = (int) $yearOption; if (! $this->isClosedMonth($month, $year)) { return []; } return [[$year, $month]]; } $months = []; $cursor = Carbon::create(2026, 1, 1)->startOfMonth(); $lastClosedMonth = now()->startOfMonth()->subMonth(); while ($cursor->lte($lastClosedMonth)) { $months[] = [(int) $cursor->year, (int) $cursor->month]; $cursor->addMonth(); } return $months; } private function isClosedMonth(int $month, int $year): bool { return Carbon::create($year, $month, 1)->endOfMonth()->lt(now()->startOfMonth()); } }