430 lines
15 KiB
PHP
430 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Carbon\Carbon;
|
|
use App\Models\UserBusiness;
|
|
use App\Models\UserLevel;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class BusinessUpdateCalculatedFields extends Command
|
|
{
|
|
/**
|
|
* php artisan business:update-calculated-fields {year} {month?}
|
|
*
|
|
* Aktualisiert bereits gespeicherte UserBusiness-Einträge mit den neuen berechneten Feldern:
|
|
* - calc_qual_kp
|
|
* - _calculated_qual_kp in Level-Arrays
|
|
* - _calculated_payline_points_qual_kp in Level-Arrays
|
|
*
|
|
* Wenn kein Monat angegeben wird, werden alle 12 Monate des Jahres aktualisiert.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'business:update-calculated-fields {year} {month? : Optional - Wenn nicht angegeben, werden alle Monate des Jahres aktualisiert} {--dry-run : Zeige nur was gemacht werden würde, ohne zu speichern}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Aktualisiert gespeicherte UserBusiness-Einträge mit neuen berechneten Feldern für Level-Qualifikationen';
|
|
|
|
private $timeStart;
|
|
private $month;
|
|
private $year;
|
|
private $isDryRun = false;
|
|
private $processAllMonths = false;
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function handle()
|
|
{
|
|
try {
|
|
$this->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];
|
|
}
|
|
}
|