timeStart = microtime(true); $this->year = (int) $this->argument('year'); $monthArg = $this->argument('month'); $this->isDryRun = $this->option('dry-run'); if ($this->isDryRun) { $this->warn('DRY RUN MODE - Keine Änderungen werden gespeichert'); } // Prüfe ob ein Monat angegeben wurde if ($monthArg === null) { $this->processAllMonths = true; $this->info("Starte Update für ALLE MONATE des Jahres: {$this->year}"); $this->info(str_repeat('=', 70)); return $this->processFullYear(); } else { $this->month = (int) $monthArg; $this->info("Starte Update für Monat: {$this->month} | Jahr: {$this->year}"); $this->logMemoryUsage('Command Start'); return $this->processSingleMonth(); } } catch (\Exception $e) { $this->error('Command failed with error: ' . $e->getMessage()); $this->error('Stack trace: ' . $e->getTraceAsString()); $this->logExecutionTime('COMMAND FAILED'); return 1; } } /** * Verarbeite alle 12 Monate eines Jahres */ private function processFullYear(): int { $totalUpdated = 0; $totalSkipped = 0; $totalErrors = 0; $monthsProcessed = 0; $monthsFailed = 0; for ($month = 1; $month <= 12; $month++) { $this->month = $month; $this->newLine(); $this->info("┌─────────────────────────────────────────────────────────────────────┐"); $this->info("│ Verarbeite Monat: " . str_pad($month, 2, '0', STR_PAD_LEFT) . "/" . $this->year . str_repeat(' ', 51) . "│"); $this->info("└─────────────────────────────────────────────────────────────────────┘"); try { $userBusinesses = $this->getUserBusinesses(); if ($userBusinesses->isEmpty()) { $this->warn(" Keine UserBusiness-Einträge für Monat {$month}/{$this->year} gefunden - überspringe"); continue; } $this->info(" Gefunden: {$userBusinesses->count()} UserBusiness-Einträge"); // Aktualisiere die Einträge $stats = $this->updateCalculatedFieldsWithStats($userBusinesses); $totalUpdated += $stats['updated']; $totalSkipped += $stats['skipped']; $totalErrors += $stats['errors']; $monthsProcessed++; $this->info(" ✓ Monat {$month} abgeschlossen: {$stats['updated']} aktualisiert, {$stats['skipped']} übersprungen, {$stats['errors']} Fehler"); $this->logMemoryUsage("Nach Monat {$month}"); } catch (\Exception $e) { $monthsFailed++; $this->error(" ✗ Fehler in Monat {$month}: " . $e->getMessage()); \Log::error("BusinessUpdateCalculatedFields: Error in month {$month}/{$this->year}: " . $e->getMessage()); // Weiter mit nächstem Monat continue; } } // Zusammenfassung für das ganze Jahr $this->newLine(2); $this->info(str_repeat('=', 70)); $this->info("ZUSAMMENFASSUNG FÜR DAS JAHR {$this->year}:"); $this->info(str_repeat('=', 70)); $this->info("Verarbeitete Monate: {$monthsProcessed}/12"); $this->info("Fehlgeschlagene Monate: {$monthsFailed}"); $this->info("Gesamt aktualisiert: {$totalUpdated}"); $this->info("Gesamt übersprungen: {$totalSkipped}"); $this->info("Gesamt Fehler: {$totalErrors}"); $this->info(str_repeat('=', 70)); $this->logExecutionTime('JAHRES-UPDATE ABGESCHLOSSEN'); $this->logMemoryUsage('Command End'); return $monthsFailed > 0 ? 1 : 0; } /** * Verarbeite einen einzelnen Monat */ private function processSingleMonth(): int { // Hole alle UserBusiness-Einträge für den Monat $userBusinesses = $this->getUserBusinesses(); if ($userBusinesses->isEmpty()) { $this->error("Keine UserBusiness-Einträge für Monat {$this->month}/{$this->year} gefunden"); return 1; } $this->info("Gefunden: {$userBusinesses->count()} UserBusiness-Einträge"); // Aktualisiere die Einträge $this->updateCalculatedFields($userBusinesses); $this->logExecutionTime('UPDATE COMPLETED SUCCESSFULLY'); $this->logMemoryUsage('Command End'); return 0; } /** * Hole alle UserBusiness-Einträge für den Monat */ private function getUserBusinesses() { return UserBusiness::where('month', $this->month) ->where('year', $this->year) ->orderBy('id', 'asc') ->get(); } /** * Aktualisiere die berechneten Felder für alle UserBusiness-Einträge */ private function updateCalculatedFields($userBusinesses) { $stats = $this->updateCalculatedFieldsWithStats($userBusinesses); $this->info("Update abgeschlossen:"); $this->info(" - Aktualisiert: {$stats['updated']}"); $this->info(" - Übersprungen: {$stats['skipped']}"); $this->info(" - Fehler: {$stats['errors']}"); } /** * Aktualisiere die berechneten Felder und gebe Statistiken zurück */ private function updateCalculatedFieldsWithStats($userBusinesses): array { $bar = $this->output->createProgressBar($userBusinesses->count()); $bar->start(); $updatedCount = 0; $skippedCount = 0; $errorCount = 0; foreach ($userBusinesses as $userBusiness) { try { $updated = $this->updateSingleUserBusiness($userBusiness); if ($updated) { $updatedCount++; } else { $skippedCount++; } $bar->advance(); // Memory-Check alle 100 Einträge (nur wenn nicht ganzes Jahr) if (!$this->processAllMonths && ($updatedCount + $skippedCount) % 100 === 0) { $this->logMemoryUsage("Nach " . ($updatedCount + $skippedCount) . " Einträgen"); } } catch (\Exception $e) { $errorCount++; $this->newLine(); $this->warn("Fehler bei UserBusiness ID {$userBusiness->id}: " . $e->getMessage()); \Log::error("BusinessUpdateCalculatedFields: Error for UserBusiness {$userBusiness->id}: " . $e->getMessage()); continue; } } $bar->finish(); $this->newLine(); return [ 'updated' => $updatedCount, 'skipped' => $skippedCount, 'errors' => $errorCount, ]; } /** * Aktualisiere einen einzelnen UserBusiness-Eintrag */ private function updateSingleUserBusiness(UserBusiness $userBusiness): bool { $hasChanges = false; // 1. Aktualisiere calc_qual_kp für qual_user_level if (!$userBusiness->calc_qual_kp) { $qualKp = $userBusiness->qual_user_level['qual_kp'] ?? null; if ($qualKp !== null) { $rest_kp = max(0, $userBusiness->sales_volume_points_KP_sum - $qualKp); $calc_qual_kp = $rest_kp > 0 ? $qualKp : $userBusiness->sales_volume_points_KP_sum; if ($userBusiness->calc_qual_kp !== $calc_qual_kp) { $userBusiness->calc_qual_kp = $calc_qual_kp; $hasChanges = true; } } else { $userBusiness->calc_qual_kp = $userBusiness->sales_volume_points_KP_sum; $hasChanges = true; } } // 2. Aktualisiere qual_user_level_next if (!empty($userBusiness->qual_user_level_next) && is_array($userBusiness->qual_user_level_next)) { $levelData = $userBusiness->qual_user_level_next; $updated = $this->addCalculatedFieldsToLevel($levelData, $userBusiness); if ($updated !== false) { $userBusiness->qual_user_level_next = $updated; $hasChanges = true; } } // 3. Aktualisiere next_qual_user_level if (!empty($userBusiness->next_qual_user_level) && is_array($userBusiness->next_qual_user_level)) { $levelData = $userBusiness->next_qual_user_level; $updated = $this->addCalculatedFieldsToLevel($levelData, $userBusiness); if ($updated !== false) { $userBusiness->next_qual_user_level = $updated; $hasChanges = true; } } // 4. Aktualisiere next_can_user_level if (!empty($userBusiness->next_can_user_level) && is_array($userBusiness->next_can_user_level)) { $levelData = $userBusiness->next_can_user_level; $updated = $this->addCalculatedFieldsToLevel($levelData, $userBusiness); if ($updated !== false) { $userBusiness->next_can_user_level = $updated; $hasChanges = true; } } // Speichere nur wenn Änderungen vorhanden sind und nicht im Dry-Run Mode if ($hasChanges && !$this->isDryRun) { $userBusiness->save(); } return $hasChanges; } /** * Füge berechnete Felder zu einem Level-Array hinzu * * @return array|false Array mit neuen Feldern oder false wenn keine Änderungen */ private function addCalculatedFieldsToLevel(array $levelData, UserBusiness $userBusiness) { // Prüfe ob Felder bereits existieren if (isset($levelData['_calculated_qual_kp']) && isset($levelData['_calculated_payline_points_qual_kp'])) { return false; // Keine Änderungen nötig } $qualKp = $levelData['qual_kp'] ?? null; $paylines = $levelData['paylines'] ?? 0; if ($qualKp === null) { return false; } // Berechne die Werte $payline_points = $this->getPointsForPayline($userBusiness, $paylines); $rest_kp = max(0, $userBusiness->sales_volume_points_KP_sum - $qualKp); $payline_points_qual_kp = $payline_points + $rest_kp; $calc_qual_kp = $rest_kp > 0 ? $qualKp : $userBusiness->sales_volume_points_KP_sum; // Füge die berechneten Felder hinzu $levelData['_calculated_qual_kp'] = $calc_qual_kp; $levelData['_calculated_payline_points'] = $payline_points; $levelData['_calculated_payline_points_qual_kp'] = $payline_points_qual_kp; return $levelData; } /** * Berechne Payline-Punkte für eine bestimmte Anzahl von Paylines */ private function getPointsForPayline(UserBusiness $userBusiness, int $paylines): float { $payline_points = 0; $businessLines = $userBusiness->business_lines ?? []; for ($i = 1; $i <= $paylines; $i++) { if (isset($businessLines[$i])) { $line = $businessLines[$i]; // Handle both array and object types if (is_array($line)) { $payline_points += (float) ($line['points'] ?? 0); } elseif (is_object($line)) { $payline_points += (float) ($line->points ?? 0); } } } return $payline_points; } /** * Logge Ausführungszeit */ private function logExecutionTime($message) { $diff = microtime(true) - $this->timeStart; $sec = intval($diff); $micro = $diff - $sec; $this->info($message . ' | Zeit: ' . $sec . 'sec :' . round($micro * 1000, 4) . " ms"); } /** * Logge Memory-Nutzung */ private function logMemoryUsage(string $checkpoint): void { $currentMemory = memory_get_usage(); $peakMemory = memory_get_peak_usage(); $memoryLimit = $this->parseMemoryLimit(ini_get('memory_limit')); $currentFormatted = $this->formatBytes($currentMemory); $peakFormatted = $this->formatBytes($peakMemory); $limitFormatted = $this->formatBytes($memoryLimit); $usagePercent = round(($currentMemory / $memoryLimit) * 100, 2); $this->info("[{$checkpoint}] Memory: {$currentFormatted} / {$limitFormatted} ({$usagePercent}%) | Peak: {$peakFormatted}"); if ($usagePercent > 80) { $this->warn("Hohe Memory-Nutzung bei {$checkpoint}: {$usagePercent}%"); } } /** * Konvertiert Memory-Limit String zu Bytes */ private function parseMemoryLimit(string $limit): int { $limit = trim($limit); $last = strtolower($limit[strlen($limit) - 1]); $number = (int) $limit; switch ($last) { case 'g': $number *= 1024; case 'm': $number *= 1024; case 'k': $number *= 1024; } return $number; } /** * Formatiert Bytes in lesbare Einheiten */ private function formatBytes(int $bytes, int $precision = 2): string { $units = array('B', 'KB', 'MB', 'GB', 'TB'); for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, $precision) . ' ' . $units[$i]; } }