1261 lines
52 KiB
PHP
1261 lines
52 KiB
PHP
<?php
|
|
|
|
namespace App\Services\BusinessPlan;
|
|
|
|
use App\Models\UserAccount;
|
|
use App\Models\UserBusiness;
|
|
use App\Models\UserLevel;
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\Log;
|
|
use stdClass;
|
|
|
|
/**
|
|
* Optimierte Version der BusinessUserItem Klasse
|
|
*
|
|
* Hauptverbesserungen:
|
|
* - makeUserFromModel() für bereits geladene User-Objekte
|
|
* - Bessere Error-Behandlung mit Logging
|
|
* - Optimierte Datenbankzugriffe durch Relations-Nutzung
|
|
* - Input-Validierung und Boundary-Checks
|
|
*/
|
|
class BusinessUserItemOptimized
|
|
{
|
|
public $businessUserItems = [];
|
|
|
|
private $date;
|
|
|
|
private $b_user;
|
|
|
|
private ?TreeCalcBotOptimized $treeCalcBot = null;
|
|
|
|
private $user_level_active_pos;
|
|
|
|
private $needsQualificationRecalculation = false;
|
|
|
|
private $qualificationCalculated = false;
|
|
|
|
public function __construct($date, ?TreeCalcBotOptimized $treeCalcBot = null)
|
|
{
|
|
$this->date = $date;
|
|
$this->treeCalcBot = $treeCalcBot;
|
|
$this->businessUserItems = []; // Initialize array
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isQualificationCalculated(): bool
|
|
{
|
|
return $this->qualificationCalculated;
|
|
}
|
|
|
|
/**
|
|
* Erstellt BusinessUser aus User-ID (Original-Methode für Rückwärtskompatibilität)
|
|
*
|
|
* @param int $user_id Die User-ID
|
|
* @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten
|
|
*/
|
|
public function makeUser($user_id, bool $forceLiveCalculation = false): void
|
|
{
|
|
try {
|
|
// Prüfe nur nach gespeicherten Daten, wenn keine Live-Berechnung erzwungen wird
|
|
if (! $forceLiveCalculation) {
|
|
$this->b_user = UserBusiness::where('user_id', $user_id)
|
|
->where('month', $this->date->month)
|
|
->where('year', $this->date->year)
|
|
->first();
|
|
|
|
if ($this->b_user !== null) {
|
|
\Log::debug("BusinessUserItem: Using stored data for user {$user_id} ({$this->date->month}/{$this->date->year})");
|
|
|
|
// WICHTIG: Auch bei gespeicherten Daten User-Model laden für Grunddaten
|
|
$user = User::with(['account', 'user_level'])->find($user_id);
|
|
if ($user) {
|
|
$this->enrichStoredDataWithUserModel($user);
|
|
|
|
// Prüfe ob Level-Qualifikationsdaten nachberechnet werden müssen
|
|
if ($this->needsQualificationRecalculation) {
|
|
\Log::debug("BusinessUserItem: Triggering qualification recalculation for user {$user_id}");
|
|
$this->calcQualPP(); // Berechne fehlende Level-Qualifikationsdaten
|
|
}
|
|
}
|
|
|
|
return; // Bereits gespeicherte Daten verwenden
|
|
}
|
|
} else {
|
|
\Log::debug("BusinessUserItem: Force live calculation for user {$user_id} ({$this->date->month}/{$this->date->year})");
|
|
}
|
|
|
|
// Lade User mit Relations (weniger effizient als makeUserFromModel)
|
|
$user = User::with(['account', 'user_level'])->find($user_id);
|
|
|
|
if (! $user) {
|
|
\Log::warning("BusinessUserItem: User not found: {$user_id}");
|
|
|
|
return;
|
|
}
|
|
|
|
$this->initializeFromUserModel($user);
|
|
|
|
// WICHTIG: Bei Live-Berechnung auch Level-Qualifikationsdaten berechnen
|
|
// (nicht bei forceLiveCalculation=false, da dort gespeicherte Daten bevorzugt werden)
|
|
if ($forceLiveCalculation) {
|
|
$this->calcQualPP();
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error creating user {$user_id}: ".$e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NEUE OPTIMIERTE METHODE: Erstellt BusinessUser aus bereits geladenem User-Objekt
|
|
* Konsistent zur ursprünglichen makeUser Logik - prüft explizit nach bereits berechneten Daten
|
|
*
|
|
* @param User $user Das User-Model
|
|
* @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten
|
|
*/
|
|
public function makeUserFromModel(User $user, bool $forceLiveCalculation = false): void
|
|
{
|
|
\Log::debug("BusinessUserItemOptimized: makeUserFromModel for user {$user->id} ({$this->date->month}/{$this->date->year})");
|
|
try {
|
|
if (! $user || ! $user->id) {
|
|
throw new \InvalidArgumentException('Invalid user model provided');
|
|
}
|
|
|
|
// Prüfe nur nach gespeicherten Daten, wenn keine Live-Berechnung erzwungen wird
|
|
if (! $forceLiveCalculation) {
|
|
$this->b_user = UserBusiness::where('user_id', $user->id)
|
|
->where('month', $this->date->month)
|
|
->where('year', $this->date->year)
|
|
->first();
|
|
|
|
if ($this->b_user !== null) {
|
|
\Log::debug("BusinessUserItem: Using stored data for user {$user->id} ({$this->date->month}/{$this->date->year})");
|
|
// WICHTIG: Auch bei gespeicherten Daten User-Grunddaten anreichern
|
|
$this->enrichStoredDataWithUserModel($user);
|
|
|
|
// Prüfe ob Level-Qualifikationsdaten nachberechnet werden müssen
|
|
if ($this->needsQualificationRecalculation) {
|
|
\Log::debug("BusinessUserItem: Triggering qualification recalculation for user {$user->id}");
|
|
$this->calcQualPP(); // Berechne fehlende Level-Qualifikationsdaten
|
|
}
|
|
|
|
return; // Bereits berechnete Daten verwenden
|
|
}
|
|
} else {
|
|
\Log::debug("BusinessUserItemOptimized: Force live calculation for user {$user->id} ({$this->date->month}/{$this->date->year})");
|
|
}
|
|
// Erstelle neuen User und führe Live-Berechnung durch
|
|
$this->initializeFromUserModel($user);
|
|
|
|
// WICHTIG: Bei Live-Berechnung auch Level-Qualifikationsdaten berechnen
|
|
// (nicht bei forceLiveCalculation=false, da dort gespeicherte Daten bevorzugt werden)
|
|
if ($forceLiveCalculation) {
|
|
// $this->calcQualPP();
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItemOptimized: Error creating user from model {$user->id}: ".$e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialisiert BusinessUser aus User-Model (gemeinsame Logik)
|
|
*/
|
|
private function initializeFromUserModel(User $user): void
|
|
{
|
|
// Nutze geladene Relations wenn verfügbar
|
|
$user_level_active = null;
|
|
if ($user->relationLoaded('user_level')) {
|
|
$user_level_active = $user->user_level;
|
|
} else {
|
|
$user_level_active = $user->user_level; // Fallback auf Original-Relation
|
|
}
|
|
$this->user_level_active_pos = $user_level_active ? $user_level_active->pos : 0;
|
|
|
|
// Neues UserBusiness Objekt erstellen
|
|
$this->b_user = new UserBusiness;
|
|
|
|
// Account-Daten (mit intelligentem Laden und Error-Handling)
|
|
$account = $this->getAccountForUser($user);
|
|
$fill = [
|
|
'user_id' => $user->id,
|
|
'month' => $this->date->month,
|
|
'year' => $this->date->year,
|
|
'm_level_id' => $user->m_level,
|
|
'user_level_name' => $user_level_active ? $user_level_active->name : '',
|
|
'active_account' => $this->calculateActiveAccount($user),
|
|
'payment_account_date' => $user->payment_account ? $user->getPaymentAccountDateFormat(false) : null,
|
|
'active_date' => $user->active_date,
|
|
|
|
// Account-Daten mit korrekten Fallback-Werten
|
|
'm_account' => $account ? ($account->m_account ?? null) : null,
|
|
'email' => $user->email,
|
|
'first_name' => $account ? ($account->first_name ?? '') : '',
|
|
'last_name' => $account ? ($account->last_name ?? '') : '',
|
|
'user_birthday' => $account ? $account->birthday : null,
|
|
'user_phone' => $account ? ($account->getPhoneNumber() ?? '') : '',
|
|
|
|
// Sales Volume (mit Caching falls möglich)
|
|
'sales_volume_KP_points' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_KP_points'),
|
|
'sales_volume_TP_points' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_TP_points'),
|
|
'sales_volume_points_shop' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_points_shop'),
|
|
'sales_volume_points_KP_sum' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_points_KP_sum'),
|
|
'sales_volume_points_TP_sum' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_points_TP_sum'),
|
|
'sales_volume_total' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_total'),
|
|
'sales_volume_total_shop' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_total_shop'),
|
|
'sales_volume_total_sum' => $this->getUserSalesVolumeOptimized($user, 'sales_volume_total_sum'),
|
|
|
|
// Level-Daten mit Boundary-Checks
|
|
'margin' => $user_level_active ? max(0, $user_level_active->margin) : 0,
|
|
'margin_shop' => $user_level_active ? max(0, $user_level_active->margin_shop) : 0,
|
|
'qual_kp' => $user_level_active ? max(0, $user_level_active->qual_kp) : 0,
|
|
'qual_pp' => $user_level_active ? max(0, $user_level_active->qual_pp) : 0,
|
|
|
|
'active_growth_bonus' => $user_level_active ? (float) $user_level_active->growth_bonus : 0,
|
|
'growth_bonus_details' => null,
|
|
|
|
// Initialisierung
|
|
'payline_points' => 0,
|
|
'commission_pp_total' => 0,
|
|
'commission_shop_sales' => 0,
|
|
'commission_growth_total' => 0,
|
|
'version' => 2,
|
|
];
|
|
|
|
$this->b_user->fill($fill);
|
|
$this->b_user->business_lines = [];
|
|
$this->b_user->user_items = [];
|
|
|
|
// Shop-Provision berechnen (mit verbessertem Logging)
|
|
$shopVolume = (float) $this->b_user->sales_volume_total_shop;
|
|
$shopMargin = (float) $this->b_user->margin_shop;
|
|
$calculatedCommission = round($shopVolume / 100 * $shopMargin, 2);
|
|
$this->b_user->commission_shop_sales = $calculatedCommission;
|
|
|
|
\Log::debug("BusinessUserItem: Created optimized user {$user->id} for {$this->date->month}/{$this->date->year} - Shop commission: {$calculatedCommission} (Volume: {$shopVolume}, Margin: {$shopMargin}%)");
|
|
\Log::debug('BusinessUserItemOptimized: b_user: '.json_encode($this->b_user));
|
|
}
|
|
|
|
/**
|
|
* Ergänzt gespeicherte UserBusiness-Daten mit aktuellen User-Grunddaten
|
|
* Erweitert um Level-Qualifikationsdaten-Validierung für Struktur-Ansicht
|
|
*/
|
|
private function enrichStoredDataWithUserModel(User $user): void
|
|
{
|
|
try {
|
|
$account = $this->getAccountForUser($user);
|
|
|
|
// Ergänze fehlende User-Grunddaten in gespeicherten UserBusiness-Daten
|
|
$this->b_user->user_id = $user->id;
|
|
$this->b_user->email = $user->email;
|
|
$this->b_user->first_name = $account ? ($account->first_name ?? '') : '';
|
|
$this->b_user->last_name = $account ? ($account->last_name ?? '') : '';
|
|
$this->b_user->user_birthday = $account ? $account->birthday : null;
|
|
$this->b_user->user_phone = $account ? ($account->getPhoneNumber() ?? '') : '';
|
|
$this->b_user->m_account = $account ? ($account->m_account ?? null) : null;
|
|
|
|
// Berechne aktiven Account-Status
|
|
$this->b_user->active_account = $this->calculateActiveAccount($user);
|
|
$this->b_user->payment_account_date = $user->payment_account;
|
|
|
|
// User-Level Informationen
|
|
$user_level_active = $user->user_level;
|
|
if ($user_level_active) {
|
|
$this->b_user->user_level_name = $user_level_active->name;
|
|
$this->user_level_active_pos = $user_level_active->pos;
|
|
}
|
|
|
|
// WICHTIG: Validiere Level-Qualifikationsdaten für Struktur-Ansicht
|
|
$this->validateLevelQualificationData();
|
|
|
|
// Prüfe ob Sales Volume Felder aktualisiert werden müssen
|
|
$this->updateSalesVolumeFields($user);
|
|
|
|
\Log::debug("BusinessUserItem: Enriched stored data for user {$user->id} with current user model data");
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error enriching stored data for user {$user->id}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aktualisiert Sales Volume und Commission Felder bei gespeicherten Daten
|
|
*/
|
|
private function updateSalesVolumeFields(User $user): void
|
|
{
|
|
try {
|
|
// Prüfe ob Sales Volume Felder leer sind
|
|
$fieldsToUpdate = [
|
|
'sales_volume_KP_points',
|
|
'sales_volume_TP_points',
|
|
'sales_volume_points_shop',
|
|
'sales_volume_points_KP_sum',
|
|
'sales_volume_points_TP_sum',
|
|
'sales_volume_total',
|
|
'sales_volume_total_shop',
|
|
'sales_volume_total_sum',
|
|
];
|
|
|
|
$needsUpdate = false;
|
|
foreach ($fieldsToUpdate as $field) {
|
|
if (! isset($this->b_user->{$field}) || $this->b_user->{$field} === null || $this->b_user->{$field} === 0) {
|
|
$newValue = $this->getUserSalesVolumeOptimized($user, $field);
|
|
$this->b_user->{$field} = $newValue;
|
|
|
|
if ($newValue > 0) {
|
|
$needsUpdate = true;
|
|
\Log::debug("BusinessUserItem: Updated {$field} for user {$user->id}: {$newValue}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Aktualisiere Shop Commission falls nötig
|
|
if (! isset($this->b_user->commission_shop_sales) || $this->b_user->commission_shop_sales === 0) {
|
|
$shopVolume = (float) $this->b_user->sales_volume_total_shop;
|
|
$shopMargin = (float) $this->b_user->margin_shop;
|
|
|
|
if ($shopVolume > 0 && $shopMargin > 0) {
|
|
$calculatedCommission = round($shopVolume / 100 * $shopMargin, 2);
|
|
$this->b_user->commission_shop_sales = $calculatedCommission;
|
|
$needsUpdate = true;
|
|
\Log::debug("BusinessUserItem: Updated commission_shop_sales for user {$user->id}: {$calculatedCommission}");
|
|
}
|
|
}
|
|
|
|
if ($needsUpdate) {
|
|
\Log::info("BusinessUserItem: Updated sales volume fields for user {$user->id}");
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error updating sales volume fields for user {$user->id}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validiert und aktualisiert Level-Qualifikationsdaten wenn nötig
|
|
* Stellt sicher, dass next_qual_user_level und next_can_user_level für Struktur-Ansicht verfügbar sind
|
|
*/
|
|
private function validateLevelQualificationData(): void
|
|
{
|
|
try {
|
|
// Prüfe ob Level-Qualifikationsdaten vorhanden sind
|
|
$hasNextQual = ! empty($this->b_user->next_qual_user_level);
|
|
$hasNextCan = ! empty($this->b_user->next_can_user_level);
|
|
$hasQualUserLevel = ! empty($this->b_user->qual_user_level);
|
|
// Wenn Level-Qualifikationsdaten fehlen, führe Neuberechnung durch
|
|
if (! $hasNextQual && ! $hasNextCan && ! $hasQualUserLevel) {
|
|
\Log::debug("BusinessUserItem: Level qualification data missing for user {$this->b_user->user_id}, triggering recalculation");
|
|
|
|
// Setze Flag für notwendige Neuberechnung
|
|
$this->needsQualificationRecalculation = true;
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::warning("BusinessUserItem: Error validating level qualification data for user {$this->b_user->user_id}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Berechnet ob Account aktiv ist (mit Error-Handling)
|
|
*/
|
|
private function calculateActiveAccount(User $user): bool
|
|
{
|
|
try {
|
|
if (! $user->payment_account) {
|
|
return false;
|
|
}
|
|
|
|
// Verwende aktuelles Datum, nicht das Berechnungs-Startdatum
|
|
return Carbon::parse($user->payment_account)->gt(Carbon::now());
|
|
} catch (\Exception $e) {
|
|
\Log::warning("BusinessUserItem: Error calculating active account for user {$user->id}: ".$e->getMessage());
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Optimierte Sales Volume Abfrage mit detailliertem Logging
|
|
*/
|
|
private function getUserSalesVolumeOptimized(User $user, string $field)
|
|
{
|
|
try {
|
|
// Direkter Aufruf mit detailliertem Logging
|
|
$value = $user->getUserSalesVolumeBy($this->date->month, $this->date->year, $field);
|
|
|
|
// Log nur bei ersten Aufruf für diesen User (Performance)
|
|
static $loggedUsers = [];
|
|
if (! isset($loggedUsers[$user->id])) {
|
|
$loggedUsers[$user->id] = true;
|
|
|
|
// Prüfe ob UserSalesVolume Daten existieren
|
|
$userSalesVolume = $user->getUserSalesVolume($this->date->month, $this->date->year, 'first');
|
|
if (! $userSalesVolume) {
|
|
\Log::info("BusinessUserItem: No UserSalesVolume found for user {$user->id} in {$this->date->month}/{$this->date->year}");
|
|
|
|
// Prüfe neueste verfügbare Daten
|
|
$latestVolume = \App\Models\UserSalesVolume::where('user_id', $user->id)
|
|
->orderBy('year', 'desc')
|
|
->orderBy('month', 'desc')
|
|
->first();
|
|
|
|
if ($latestVolume) {
|
|
\Log::info("BusinessUserItem: Latest UserSalesVolume for user {$user->id}: {$latestVolume->month}/{$latestVolume->year}");
|
|
} else {
|
|
\Log::warning("BusinessUserItem: No UserSalesVolume records found for user {$user->id} at all");
|
|
}
|
|
} else {
|
|
\Log::debug("BusinessUserItem: UserSalesVolume found for user {$user->id} in {$this->date->month}/{$this->date->year}");
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error getting sales volume {$field} for user {$user->id}: ".$e->getMessage());
|
|
|
|
return 0; // Sicherer Fallback
|
|
}
|
|
}
|
|
|
|
// ===== ORIGINALE METHODEN (unverändert für Kompatibilität) =====
|
|
|
|
public function getSalesVolumeTotalMargin()
|
|
{
|
|
return $this->b_user->getSalesVolumeTotalMargin();
|
|
}
|
|
|
|
public function addUserID()
|
|
{
|
|
if ($this->treeCalcBot) {
|
|
$this->treeCalcBot->addProcessedUserId($this->b_user->user_id);
|
|
} else {
|
|
// Fallback für Rückwärtskompatibilität - sollte in Logs sichtbar sein
|
|
\Log::warning('BusinessUserItemOptimized: TreeCalcBotOptimized Referenz fehlt für User ID: '.$this->b_user->user_id);
|
|
}
|
|
}
|
|
|
|
public function getBUser()
|
|
{
|
|
return $this->b_user;
|
|
}
|
|
|
|
public function addBusinessLineToUser($line, $obj)
|
|
{
|
|
$this->b_user->business_lines[$line] = $obj;
|
|
}
|
|
|
|
/**
|
|
* Initialisiert leere business_lines für diesen User
|
|
*/
|
|
public function initBusinessLines(): void
|
|
{
|
|
if (! isset($this->b_user->business_lines) || ! is_array($this->b_user->business_lines)) {
|
|
$this->b_user->business_lines = [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prüft ob eine business_line existiert
|
|
*/
|
|
public function hasBusinessLine(int $line): bool
|
|
{
|
|
return isset($this->b_user->business_lines[$line]);
|
|
}
|
|
|
|
public function addBusinessLinePoints($line, $points)
|
|
{
|
|
if (! isset($this->b_user->business_lines[$line])) {
|
|
\Log::warning("BusinessUserItem: Trying to add points to non-existent line {$line}");
|
|
|
|
return;
|
|
}
|
|
|
|
$obj = $this->b_user->business_lines[$line];
|
|
|
|
// Handle both array and object types (JSON deserialization inconsistency)
|
|
if (is_array($obj)) {
|
|
$obj['points'] = ($obj['points'] ?? 0) + (float) $points;
|
|
} else {
|
|
// Ensure it's an object
|
|
if (! is_object($obj)) {
|
|
$obj = (object) $obj;
|
|
}
|
|
$obj->points = ($obj->points ?? 0) + (float) $points;
|
|
}
|
|
|
|
$this->b_user->business_lines[$line] = $obj;
|
|
}
|
|
|
|
/**
|
|
* Gibt Details zur Growth Bonus Berechnung zurück (für die View)
|
|
* Nur für Monate ab November 2025 verfügbar (neue Logik)
|
|
*/
|
|
public function getGrowthBonusBreakdown(): array
|
|
{
|
|
// Prüfe ob Legacy-Monat (vor November 2025)
|
|
$isLegacy = ($this->date->year < 2025) || ($this->date->year == 2025 && $this->date->month < 11);
|
|
|
|
if ($isLegacy) {
|
|
return [];
|
|
}
|
|
|
|
if (! $this->isQualLevel() || empty($this->b_user->qual_user_level['growth_bonus'])) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$calculator = new GrowthBonusCalculator;
|
|
// Array zu Object konvertieren für Calculator
|
|
$qualData = (object) $this->b_user->qual_user_level;
|
|
|
|
return $calculator->getCalculationDetails($this, $qualData);
|
|
} catch (\Exception $e) {
|
|
\Log::error('BusinessUserItem: Error getting growth bonus breakdown: '.$e->getMessage());
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gibt Matrix-Details zur Growth Bonus Berechnung zurück (für die View)
|
|
* Nur für Monate ab November 2025 verfügbar (neue Logik)
|
|
*/
|
|
public function getGrowthBonusMatrix(): array
|
|
{
|
|
// Prüfe ob Legacy-Monat (vor November 2025)
|
|
$isLegacy = ($this->date->year < 2025) || ($this->date->year == 2025 && $this->date->month < 11);
|
|
|
|
if ($isLegacy) {
|
|
return [];
|
|
}
|
|
|
|
if (! $this->isQualLevel() || empty($this->b_user->qual_user_level['growth_bonus'])) {
|
|
return [];
|
|
}
|
|
|
|
// Use stored details if available (avoid recalculation)
|
|
if (! empty($this->b_user->growth_bonus_details)) {
|
|
if (is_object($this->b_user->growth_bonus_details) && method_exists($this->b_user->growth_bonus_details, 'toArray')) {
|
|
return $this->b_user->growth_bonus_details->toArray();
|
|
}
|
|
if (is_array($this->b_user->growth_bonus_details)) {
|
|
return $this->b_user->growth_bonus_details;
|
|
}
|
|
// Fallback for standard object
|
|
if (is_object($this->b_user->growth_bonus_details)) {
|
|
return json_decode(json_encode($this->b_user->growth_bonus_details), true);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$calculator = new GrowthBonusCalculator;
|
|
// Array zu Object konvertieren für Calculator
|
|
$qualData = (object) $this->b_user->qual_user_level;
|
|
|
|
return $calculator->getMatrixDetails($this, $qualData);
|
|
} catch (\Exception $e) {
|
|
\Log::error('BusinessUserItem: Error getting growth bonus matrix: '.$e->getMessage());
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public function addTotalTP($points)
|
|
{
|
|
$this->b_user->total_pp += (float) $points; // Type-Safety
|
|
}
|
|
|
|
public function isQualKP(): bool
|
|
{
|
|
return $this->b_user->sales_volume_points_KP_sum >= $this->b_user->qual_kp;
|
|
}
|
|
|
|
public function isQualLevel(): bool
|
|
{
|
|
return ! empty($this->b_user->qual_user_level);
|
|
}
|
|
|
|
/**
|
|
* Methode für Zugriff auf qual_user_level (auch für GrowthBonusCalculator)
|
|
*/
|
|
public function getQualUserLevel()
|
|
{
|
|
return $this->b_user->qual_user_level ?? null;
|
|
}
|
|
|
|
public function getActiveGrowthBonus()
|
|
{
|
|
return $this->active_growth_bonus;
|
|
}
|
|
|
|
/**
|
|
* Gibt das Date-Objekt zurück (für GrowthBonusCalculator)
|
|
*/
|
|
public function getDate()
|
|
{
|
|
return $this->date;
|
|
}
|
|
|
|
/**
|
|
* Gibt den Growth Bonus basierend auf dem ERREICHTEN Qualifikations-Level zurück.
|
|
*
|
|
* WICHTIG: Diese Methode gibt den Growth Bonus nur zurück, wenn der Partner
|
|
* in dem Monat tatsächlich das entsprechende Level qualifiziert hat.
|
|
* Das ist entscheidend für die korrekte Differenz-Berechnung im GrowthBonusCalculator.
|
|
*
|
|
* Die Methode funktioniert sowohl für:
|
|
* - Live-berechnete Daten (qualificationCalculated = true)
|
|
* - Gespeicherte/geladene Daten aus UserBusiness (qual_user_level bereits vorhanden)
|
|
*
|
|
* @return float Der Growth Bonus des erreichten Qualifikations-Levels (0 wenn nicht qualifiziert)
|
|
*/
|
|
public function getQualifiedGrowthBonus(): float
|
|
{
|
|
// Prüfen ob b_user existiert
|
|
if (empty($this->b_user)) {
|
|
return 0.0;
|
|
}
|
|
|
|
// Prüfen ob ein Qualifikations-Level erreicht wurde
|
|
// Dies funktioniert sowohl für live-berechnete als auch für gespeicherte Daten
|
|
if (empty($this->b_user->qual_user_level)) {
|
|
return 0.0;
|
|
}
|
|
|
|
// Handle array und object Zugriff (JSON-Deserialisierung kann beides liefern)
|
|
$qualLevel = $this->b_user->qual_user_level;
|
|
|
|
if (is_array($qualLevel)) {
|
|
return (float) ($qualLevel['growth_bonus'] ?? 0.0);
|
|
}
|
|
|
|
if (is_object($qualLevel)) {
|
|
return (float) ($qualLevel->growth_bonus ?? 0.0);
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
public function isQualEqualLevel(): bool
|
|
{
|
|
if (! $this->b_user->qual_user_level) {
|
|
return false;
|
|
}
|
|
|
|
return $this->b_user->m_level_id == $this->b_user->qual_user_level['id'];
|
|
}
|
|
|
|
public function getQualPaylines(): int
|
|
{
|
|
if (! $this->b_user->qual_user_level) {
|
|
return 0;
|
|
}
|
|
|
|
return (int) $this->b_user->qual_user_level['paylines'];
|
|
}
|
|
|
|
public function getRestQualKP(): float
|
|
{
|
|
$ret = $this->b_user->sales_volume_points_KP_sum - $this->b_user->qual_kp;
|
|
|
|
return max(0, $ret); // Boundary-Check
|
|
}
|
|
|
|
public function getCommissionTotal(): float
|
|
{
|
|
return round(
|
|
$this->b_user->commission_shop_sales +
|
|
$this->b_user->commission_pp_total +
|
|
$this->b_user->commission_growth_total,
|
|
2
|
|
);
|
|
}
|
|
|
|
// ===== PROVISIONSBERECHNUNG (Original-Logik) =====
|
|
|
|
public function calcQualPP($force = false): void
|
|
{
|
|
if ($this->qualificationCalculated && ! $force) {
|
|
return;
|
|
}
|
|
|
|
// Mark as calculated immediately to prevent potential recursion loops
|
|
$this->qualificationCalculated = true;
|
|
|
|
try {
|
|
$qualUserLevel = $this->calcuQualLevel();
|
|
\Log::debug("BusinessUserItemOptimized: calcQualPP for user {$this->b_user->user_id}: ".json_encode($qualUserLevel));
|
|
if ($qualUserLevel !== null) {
|
|
// das erreichte level setzen
|
|
$this->b_user->qual_user_level = $qualUserLevel->toArray();
|
|
// Wichtig: Setze die qual_kp und qual_pp des erreichten Levels im b_user Objekt
|
|
// Diese Werte ändern sich je nach erreichtem Level und müssen hier aktualisiert werden
|
|
$this->b_user->qual_kp = $qualUserLevel->qual_kp;
|
|
$this->b_user->qual_pp = $qualUserLevel->qual_pp;
|
|
|
|
\Log::debug("BusinessUserItemOptimized: Set qual_kp={$qualUserLevel->qual_kp}, qual_pp={$qualUserLevel->qual_pp} for user {$this->b_user->user_id}");
|
|
|
|
// next_qual_user_level nächster qualifizierten level
|
|
$this->setNextUserLevel($force);
|
|
// qual_user_level_next nächste Provisions-Stufe,
|
|
$this->setQualNextLevel($force);
|
|
// provisionen berechnen
|
|
$this->calculateCommissions($qualUserLevel);
|
|
} else {
|
|
$this->setFirstQualLevel();
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error calculating qualifications for user {$this->b_user->user_id}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Berechnet Provisionen mit Error-Handling
|
|
* Erweitert um Array/Object-Kompatibilität für business_lines
|
|
*/
|
|
private function calculateCommissions($qualUserLevel): void
|
|
{
|
|
$commission_pp_total = 0;
|
|
$commission_growth_total = 0;
|
|
|
|
// Payline-Provisionen
|
|
for ($i = 1; $i <= $qualUserLevel->paylines; $i++) {
|
|
if (isset($this->b_user->business_lines[$i])) {
|
|
$object = $this->b_user->business_lines[$i];
|
|
$margin = (float) $this->b_user->qual_user_level['pr_line_'.$i];
|
|
|
|
// Handle both array and object types (JSON deserialization inconsistency)
|
|
if (is_array($object)) {
|
|
$points = (float) ($object['points'] ?? 0);
|
|
$object['margin'] = $margin;
|
|
$object['commission'] = round($points / 100 * $margin, 2);
|
|
$object['payline'] = true;
|
|
$commission_pp_total += $object['commission'];
|
|
} else {
|
|
$points = (float) ($object->points ?? 0);
|
|
$object->margin = $margin;
|
|
$object->commission = round($points / 100 * $margin, 2);
|
|
$object->payline = true;
|
|
$commission_pp_total += $object->commission;
|
|
}
|
|
|
|
$this->b_user->business_lines[$i] = $object;
|
|
}
|
|
}
|
|
|
|
// Growth Bonus
|
|
if (! empty($qualUserLevel->growth_bonus)) {
|
|
// Fallback für alte Monate (vor November 2025)
|
|
// Stichtag: 01.11.2025 - Alles davor nutzt die Legacy-Berechnung
|
|
$isLegacy = ($this->date->year < 2025) || ($this->date->year == 2025 && $this->date->month < 11);
|
|
|
|
if ($isLegacy) {
|
|
$commission_growth_total = $this->calculateLegacyGrowthBonus($qualUserLevel);
|
|
\Log::debug("BusinessUserItem: Used LEGACY growth bonus calculation for user {$this->b_user->user_id} ({$this->date->month}/{$this->date->year})");
|
|
} else {
|
|
// Neue Logik ab Dezember 2025 - delegated to new Calculator service
|
|
try {
|
|
$growthCalculator = new GrowthBonusCalculator;
|
|
$commission_growth_total = $growthCalculator->calculate($this, $qualUserLevel);
|
|
|
|
// Calculate matrix details for storage and total sum
|
|
// This ensures that the stored details match the calculated total exactly
|
|
$matrixDetails = $growthCalculator->getMatrixDetails($this, $qualUserLevel);
|
|
|
|
// Store details in the model so they can be retrieved later without recalculation
|
|
$this->b_user->growth_bonus_details = $matrixDetails;
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error calculating growth bonus for user {$this->b_user->user_id}: ".$e->getMessage());
|
|
// Fallback to 0 if calculation fails
|
|
$commission_growth_total = 0;
|
|
$this->b_user->growth_bonus_details = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->b_user->commission_pp_total = $commission_pp_total;
|
|
$this->b_user->commission_growth_total = $commission_growth_total;
|
|
}
|
|
|
|
/**
|
|
* Alte Berechnungsmethode für Growth Bonus (Kompatibilität für vergangene Monate)
|
|
* Berechnet pauschal ab einer bestimmten Ebene ohne Differenz-Prüfung
|
|
*/
|
|
private function calculateLegacyGrowthBonus($qualUserLevel): float
|
|
{
|
|
$commission_growth_total = 0;
|
|
|
|
// Payline aus Level-Daten + 1 (Start des Bonus)
|
|
$payline = (int) ($this->b_user->qual_user_level['paylines'] ?? 0) + 1;
|
|
$maxlines = count($this->b_user->business_lines ?? []) + 1;
|
|
$growth_bonus = (float) ($this->b_user->qual_user_level['growth_bonus'] ?? 0);
|
|
|
|
for ($i = $payline; $i <= $maxlines; $i++) {
|
|
if (isset($this->b_user->business_lines[$i])) {
|
|
$object = $this->b_user->business_lines[$i];
|
|
|
|
// Handle both array and object types
|
|
if (is_array($object)) {
|
|
$points = (float) ($object['points'] ?? 0);
|
|
$object['margin'] = $growth_bonus;
|
|
$object['commission'] = round($points / 100 * $growth_bonus, 2);
|
|
$object['growth_bonus'] = true;
|
|
$commission_growth_total += $object['commission'];
|
|
} else {
|
|
if (! is_object($object)) {
|
|
$object = (object) $object;
|
|
}
|
|
$points = (float) ($object->points ?? 0);
|
|
$object->margin = $growth_bonus;
|
|
$object->commission = round($points / 100 * $growth_bonus, 2);
|
|
$object->growth_bonus = true;
|
|
$commission_growth_total += $object->commission;
|
|
}
|
|
|
|
$this->b_user->business_lines[$i] = $object;
|
|
}
|
|
}
|
|
|
|
return $commission_growth_total;
|
|
}
|
|
|
|
// ===== WEITERE ORIGINAL-METHODEN (gekürzt, vollständige Implementation in Original) =====
|
|
/**
|
|
* Berechnet das aktuell erreichte Level
|
|
* Durchläuft alle möglichen Levels (max. bis zur eigenen User-Level-Position)
|
|
* und prüft dynamisch die Qualifikation basierend auf den spezifischen qual_kp und qual_pp des jeweiligen Levels
|
|
*/
|
|
public function calcuQualLevel()
|
|
{
|
|
\Log::debug("BusinessUserItemOptimized: calcuQualLevel for user {$this->b_user->user_id} ({$this->date->month}/{$this->date->year})");
|
|
// Hole alle möglichen Levels bis zur eigenen Position, sortiert nach Position absteigend
|
|
// um vom höchsten zum niedrigsten zu prüfen
|
|
$qualUserLevels = UserLevel::where('qual_kp', '<=', $this->b_user->sales_volume_points_KP_sum)
|
|
->where('pos', '<=', $this->user_level_active_pos)
|
|
->orderBy('pos', 'desc') // Sortiere nach Position DESC, um das höchste Level zuerst zu prüfen
|
|
->get();
|
|
foreach ($qualUserLevels as $qualUserLevel) {
|
|
// Berechne die Payline-Punkte für die spezifischen Paylines dieses Levels
|
|
$payline_points = $this->getPointsforPayline($qualUserLevel->paylines);
|
|
\Log::debug('BusinessUserItemOptimized: payline_points: '.$payline_points);
|
|
// WICHTIG: Berechne die Rest-KP basierend auf der qual_kp DES AKTUELL GEPRÜFTEN LEVELS
|
|
// nicht der qual_kp des bereits gesetzten Levels (das war der Fehler!)
|
|
$rest_kp = max(0, $this->b_user->sales_volume_points_KP_sum - $qualUserLevel->qual_kp);
|
|
$payline_points_qual_kp = $payline_points + $rest_kp;
|
|
|
|
// Prüfe ob die Qualifikation für diesen spezifischen Level erfüllt ist
|
|
if ($payline_points_qual_kp >= $qualUserLevel->qual_pp) {
|
|
// Setze die berechneten Werte
|
|
$this->b_user->calc_qual_kp = $rest_kp > 0 ? $qualUserLevel->qual_kp : $this->b_user->sales_volume_points_KP_sum;
|
|
$this->b_user->payline_points = $payline_points;
|
|
$this->b_user->payline_points_qual_kp = $payline_points_qual_kp;
|
|
|
|
$qualUserLevel->_calculated_qual_kp = $rest_kp > 0 ? $qualUserLevel->qual_kp : $this->b_user->sales_volume_points_KP_sum;
|
|
$qualUserLevel->_calculated_payline_points = $payline_points;
|
|
$qualUserLevel->_calculated_payline_points_qual_kp = $payline_points_qual_kp;
|
|
\Log::debug("BusinessUserItemOptimized: User {$this->b_user->user_id} qualifies for level {$qualUserLevel->name} (pos: {$qualUserLevel->pos}) - Payline Points: {$payline_points}, Rest KP: {$rest_kp}, Total: {$payline_points_qual_kp} >= {$qualUserLevel->qual_pp}");
|
|
|
|
return $qualUserLevel;
|
|
}
|
|
}
|
|
|
|
\Log::debug("BusinessUserItemOptimized: User {$this->b_user->user_id} does not qualify for any level");
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getPointsforPayline($paylines): float
|
|
{
|
|
\Log::debug("BusinessUserItemOptimized: getPointsforPayline for user {$this->b_user->user_id} ({$this->date->month}/{$this->date->year}) with paylines: ".$paylines.' and business_lines: '.json_encode($this->b_user->business_lines));
|
|
$payline_points = 0;
|
|
for ($i = 1; $i <= $paylines; $i++) {
|
|
if (isset($this->b_user->business_lines[$i])) {
|
|
$line = $this->b_user->business_lines[$i];
|
|
|
|
// Handle both array and object types (JSON deserialization inconsistency)
|
|
if (is_array($line)) {
|
|
$payline_points += (float) ($line['points'] ?? 0);
|
|
} else {
|
|
$payline_points += (float) ($line->points ?? 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $payline_points;
|
|
}
|
|
|
|
/**
|
|
* Setzt das nächste Provision-Level
|
|
* Wenn das aktuelle Level nicht erreicht ist, dann wird bei aktuelle Provisions-Stufe die erreichte level angezeigt und berechnet
|
|
* Zur Info wird das nächste level angezeigt, der folgt, sonst leer
|
|
*/
|
|
private function setQualNextLevel($force = false): void
|
|
{
|
|
// ist der level nicht das aktuelle level, dann sucht es den nächsten level
|
|
// isQualEqualLevel wenn das erreichte level das akutelle user level ist.
|
|
if (! $this->isQualEqualLevel() && $this->b_user->qual_user_level['next_id'] != null) {
|
|
$qualUserLevelNext = UserLevel::where('id', '=', $this->b_user->qual_user_level['next_id'])
|
|
->orderBy('qual_pp', 'asc')
|
|
->first();
|
|
if ($qualUserLevelNext) {
|
|
// Berechne die spezifischen Werte für diesen Level
|
|
$payline_points = $this->getPointsforPayline($qualUserLevelNext->paylines);
|
|
$rest_kp = max(0, $this->b_user->sales_volume_points_KP_sum - $qualUserLevelNext->qual_kp);
|
|
$payline_points_qual_kp = $payline_points + $rest_kp;
|
|
|
|
// Speichere Level-Daten mit berechneten Werten
|
|
$levelData = $qualUserLevelNext->toArray();
|
|
$levelData['_calculated_qual_kp'] = $rest_kp > 0 ? $qualUserLevelNext->qual_kp : $this->b_user->sales_volume_points_KP_sum;
|
|
$levelData['_calculated_payline_points'] = $payline_points;
|
|
$levelData['_calculated_payline_points_qual_kp'] = $payline_points_qual_kp;
|
|
|
|
$this->b_user->qual_user_level_next = $levelData;
|
|
} else {
|
|
$this->b_user->qual_user_level_next = null;
|
|
}
|
|
} else {
|
|
$this->b_user->qual_user_level_next = null;
|
|
}
|
|
}
|
|
|
|
private function setNextUserLevel($force = false): void
|
|
{
|
|
// Hole nur den direkt nächsten Level (keine Level überspringen!)
|
|
$nextLevel = UserLevel::where('pos', '=', $this->user_level_active_pos + 1)
|
|
->first();
|
|
|
|
// Wenn kein nächster Level existiert, beende
|
|
if (! $nextLevel) {
|
|
$this->b_user->next_qual_user_level = null;
|
|
$this->b_user->next_can_user_level = null;
|
|
\Log::debug("BusinessUserItemOptimized: No next level found for user {$this->b_user->user_id} (already at highest level)");
|
|
|
|
return;
|
|
}
|
|
|
|
// Berechne die Payline-Punkte für die spezifischen Paylines des nächsten Levels
|
|
$payline_points = $this->getPointsforPayline($nextLevel->paylines);
|
|
// Berechne die Rest-KP basierend auf dem nächsten Level
|
|
$rest_kp = max(0, $this->b_user->sales_volume_points_KP_sum - $nextLevel->qual_kp);
|
|
$payline_points_qual_kp = $payline_points + $rest_kp;
|
|
|
|
// Erstelle Level-Daten mit berechneten Werten
|
|
$levelData = $nextLevel->toArray();
|
|
$levelData['_calculated_qual_kp'] = $rest_kp > 0 ? $nextLevel->qual_kp : $this->b_user->sales_volume_points_KP_sum;
|
|
$levelData['_calculated_payline_points'] = $payline_points;
|
|
$levelData['_calculated_payline_points_qual_kp'] = $payline_points_qual_kp;
|
|
|
|
// Prüfe die KP-Qualifikation für den nächsten Level
|
|
if ($this->b_user->sales_volume_points_KP_sum < $nextLevel->qual_kp) {
|
|
// KP-Qualifikation nicht erfüllt - zeige als "next_can_user_level"
|
|
$this->b_user->next_can_user_level = $levelData;
|
|
$this->b_user->next_qual_user_level = null;
|
|
\Log::debug("BusinessUserItemOptimized: User {$this->b_user->user_id} does not meet KP requirement for next level {$nextLevel->name} ({$this->b_user->sales_volume_points_KP_sum} < {$nextLevel->qual_kp})");
|
|
|
|
return;
|
|
}
|
|
|
|
// Prüfe ob die PP-Qualifikation erfüllt ist
|
|
if ($payline_points_qual_kp >= $nextLevel->qual_pp) {
|
|
// Qualifiziert für den nächsten Level
|
|
$this->b_user->next_qual_user_level = $levelData;
|
|
$this->b_user->next_can_user_level = null;
|
|
\Log::debug("BusinessUserItemOptimized: User {$this->b_user->user_id} qualifies for next level {$nextLevel->name} (Payline Points: {$payline_points}, Rest KP: {$rest_kp}, Total: {$payline_points_qual_kp} >= {$nextLevel->qual_pp})");
|
|
} else {
|
|
// PP-Qualifikation nicht erfüllt - zeige als "next_can_user_level"
|
|
$this->b_user->next_can_user_level = $levelData;
|
|
$this->b_user->next_qual_user_level = null;
|
|
\Log::debug("BusinessUserItemOptimized: User {$this->b_user->user_id} does not meet PP requirement for next level {$nextLevel->name} ({$payline_points_qual_kp} < {$nextLevel->qual_pp})");
|
|
}
|
|
}
|
|
|
|
private function setFirstQualLevel(): void
|
|
{
|
|
$qualUserLevelNext = UserLevel::where('pos', '=', 1)
|
|
->orderBy('qual_pp', 'asc')
|
|
->first();
|
|
if ($qualUserLevelNext) {
|
|
$payline_points = $this->getPointsforPayline($qualUserLevelNext->paylines);
|
|
// Berechne die Rest-KP basierend auf dem nächsten Level
|
|
$rest_kp = max(0, $this->b_user->sales_volume_points_KP_sum - $qualUserLevelNext->qual_kp);
|
|
$payline_points_qual_kp = $payline_points + $rest_kp;
|
|
$levelData = $qualUserLevelNext->toArray();
|
|
$levelData['_calculated_qual_kp'] = $rest_kp > 0 ? $qualUserLevelNext->qual_kp : $this->b_user->sales_volume_points_KP_sum;
|
|
$levelData['_calculated_payline_points'] = $payline_points;
|
|
$levelData['_calculated_payline_points_qual_kp'] = $payline_points_qual_kp;
|
|
$this->b_user->qual_user_level_next = $levelData;
|
|
}
|
|
}
|
|
|
|
// Magic Methods für Property-Zugriff (Rückwärtskompatibilität)
|
|
public function __get($name)
|
|
{
|
|
if (isset($this->b_user->$name)) {
|
|
return $this->b_user->$name;
|
|
}
|
|
|
|
// Legacy-Properties
|
|
$legacyMap = [
|
|
'sales_volume_points_KP_sum' => 'sales_volume_points_KP_sum',
|
|
'sales_volume_points_TP_sum' => 'sales_volume_points_TP_sum',
|
|
'business_lines' => 'business_lines',
|
|
'user_id' => 'user_id',
|
|
];
|
|
|
|
if (isset($legacyMap[$name]) && isset($this->b_user->{$legacyMap[$name]})) {
|
|
return $this->b_user->{$legacyMap[$name]};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Prüft und setzt Sponsor-Informationen (Original-Implementation)
|
|
*/
|
|
public function checkSponsor($user): void
|
|
{
|
|
try {
|
|
// Check if already stored
|
|
if ($this->isSave()) {
|
|
return;
|
|
}
|
|
|
|
$sponsor = new stdClass;
|
|
$sponsor->is_sponsor = false;
|
|
$sponsor->user_id = false;
|
|
$sponsor->first_name = '';
|
|
$sponsor->last_name = '';
|
|
$sponsor->email = '';
|
|
$sponsor->m_account = '';
|
|
$sponsor->full_name = 'Keinen Sponsor zugewiesen';
|
|
|
|
if ($user->m_sponsor) {
|
|
if ($user->user_sponsor) {
|
|
$sponsor->is_sponsor = true;
|
|
$sponsor->user_id = $user->user_sponsor->id;
|
|
|
|
if ($user->user_sponsor->account) {
|
|
$sponsor->full_name = substr(
|
|
'Sponsor: '.$user->user_sponsor->account->first_name.' '.
|
|
$user->user_sponsor->account->last_name.' | '.
|
|
$user->user_sponsor->email.' | '.
|
|
$user->user_sponsor->account->m_account,
|
|
0,
|
|
250
|
|
);
|
|
$sponsor->first_name = $user->user_sponsor->account->first_name;
|
|
$sponsor->last_name = $user->user_sponsor->account->last_name;
|
|
$sponsor->m_account = $user->user_sponsor->account->m_account;
|
|
} else {
|
|
$sponsor->full_name = 'Sponsor: '.$user->user_sponsor->email;
|
|
}
|
|
$sponsor->email = $user->user_sponsor->email;
|
|
} else {
|
|
$sponsor->full_name = 'Sponsor wurde gelöscht.';
|
|
}
|
|
}
|
|
|
|
$this->b_user->sponsor = $sponsor;
|
|
} catch (\Exception $e) {
|
|
Log::error("BusinessUserItem: Error checking sponsor for user {$user->id}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lädt Parent Business Users rekursiv (Original-Implementation mit Optimierungen)
|
|
* BUGFIX: Schutz vor unendlicher Rekursion durch zirkuläre Referenzen
|
|
*/
|
|
public function readParentsBusinessUsers($forceLiveCalculation = false, $depth = 0): void
|
|
{
|
|
// Schutz vor zu tiefer Rekursion (maximale Tiefe: 20 Levels)
|
|
$maxDepth = 20;
|
|
if ($depth > $maxDepth) {
|
|
Log::warning("BusinessUserItem: Maximale Rekursionstiefe ({$maxDepth}) erreicht für User {$this->b_user->user_id}");
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Optimiert: Lade mit Relations
|
|
$users = User::with(['account'])
|
|
->select('users.*')
|
|
->where('users.deleted_at', '=', null)
|
|
->where('users.id', '!=', 1)
|
|
->where('users.admin', '<', 4)
|
|
->where('users.m_level', '!=', null)
|
|
->whereColumn('users.id', '!=', 'users.m_sponsor')
|
|
->where('users.m_sponsor', '=', $this->b_user->user_id)
|
|
->where('users.payment_account', '!=', null)
|
|
->where('users.active_date', '<=', $this->date->end_date)
|
|
->get();
|
|
|
|
if ($users->isNotEmpty()) {
|
|
foreach ($users as $user) {
|
|
// KRITISCHER BUGFIX: Prüfe ob User bereits verarbeitet wurde
|
|
if ($this->treeCalcBot && $this->treeCalcBot->isUserProcessed($user->id)) {
|
|
Log::debug("BusinessUserItem: Überspringe bereits verarbeiteten User {$user->id} (zirkuläre Referenz verhindert)");
|
|
|
|
continue;
|
|
}
|
|
|
|
$businessUserItem = new BusinessUserItemOptimized($this->date, $this->treeCalcBot);
|
|
$businessUserItem->makeUserFromModel($user, $forceLiveCalculation);
|
|
$businessUserItem->addUserID();
|
|
$this->businessUserItems[] = $businessUserItem;
|
|
}
|
|
}
|
|
|
|
// Rekursiver Aufruf für alle Child-Items mit Tiefenprüfung
|
|
foreach ($this->businessUserItems as $businessUserItem) {
|
|
$businessUserItem->readParentsBusinessUsers($forceLiveCalculation, $depth + 1);
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::error("BusinessUserItem: Error reading parent users for {$this->b_user->user_id} at depth {$depth}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lädt Parent Business Users aus gespeicherter Struktur (Original-Implementation)
|
|
* BUGFIX: Schutz vor unendlicher Rekursion durch zirkuläre Referenzen
|
|
*/
|
|
public function readStoredParentsBusinessUsers($structure, $depth = 0): void
|
|
{
|
|
// Schutz vor zu tiefer Rekursion (maximale Tiefe: 50 Levels)
|
|
$maxDepth = 50;
|
|
if ($depth > $maxDepth) {
|
|
Log::warning("BusinessUserItem: Maximale Rekursionstiefe ({$maxDepth}) erreicht für gespeicherte User {$this->b_user->user_id}");
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$parents = $this->findParentsBusinessOnStored($this->b_user->user_id, $structure);
|
|
|
|
if ($parents) {
|
|
foreach ($parents as $obj) {
|
|
// KRITISCHER BUGFIX: Prüfe ob User bereits verarbeitet wurde
|
|
if ($this->treeCalcBot && $this->treeCalcBot->isUserProcessed($obj->user_id)) {
|
|
Log::debug("BusinessUserItem: Überspringe bereits verarbeiteten gespeicherten User {$obj->user_id} (zirkuläre Referenz verhindert)");
|
|
|
|
continue;
|
|
}
|
|
|
|
$businessUserItem = new BusinessUserItemOptimized($this->date, $this->treeCalcBot);
|
|
$businessUserItem->makeUser($obj->user_id);
|
|
$businessUserItem->addUserID();
|
|
$this->businessUserItems[] = $businessUserItem;
|
|
}
|
|
|
|
foreach ($this->businessUserItems as $businessUserItem) {
|
|
$businessUserItem->readStoredParentsBusinessUsers($parents, $depth + 1);
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::error("BusinessUserItem: Error reading stored parent users at depth {$depth}: ".$e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Findet Parent Business Items in gespeicherter Struktur (Original-Implementation)
|
|
*/
|
|
private function findParentsBusinessOnStored($user_id, $structures)
|
|
{
|
|
if (! $structures) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($structures as $obj) {
|
|
if ($user_id === $obj->user_id) {
|
|
return $obj->parents ?? null;
|
|
}
|
|
|
|
if (! empty($obj->parents)) {
|
|
$result = $this->findParentsBusinessOnStored($user_id, $obj->parents);
|
|
if ($result) {
|
|
return $result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Prüft ob User bereits gespeichert ist
|
|
* Konsistent zur ursprünglichen BusinessUserItem Implementation
|
|
*/
|
|
public function isSave(): bool
|
|
{
|
|
return $this->b_user && $this->b_user->isSave();
|
|
}
|
|
|
|
/**
|
|
* Gibt die Anzahl der qualifizierten Paylines zurück
|
|
*/
|
|
public function getQualLevelPaylines()
|
|
{
|
|
if ($this->b_user && isset($this->b_user->qual_user_level) && $this->b_user->qual_user_level) {
|
|
return $this->b_user->qual_user_level['paylines'] ?? 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Prüft ob eine Line für Growth-Bonus qualifiziert ist
|
|
*/
|
|
public function isQualLevelGrowth($line)
|
|
{
|
|
if ($this->b_user && isset($this->b_user->business_lines[$line])) {
|
|
$object = $this->b_user->business_lines[$line];
|
|
if (isset($object->growth_bonus)) {
|
|
return $object->growth_bonus > 0;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Intelligentes Laden des UserAccount für einen User
|
|
* Prüft zuerst geladene Relations, lädt nach wenn nötig
|
|
*/
|
|
private function getAccountForUser(User $user): ?UserAccount
|
|
{
|
|
try {
|
|
// Prüfe ob Account-Relation bereits geladen ist
|
|
if ($user->relationLoaded('account')) {
|
|
$account = $user->account;
|
|
if ($account instanceof UserAccount) {
|
|
\Log::debug("BusinessUserItem: Using pre-loaded account for user {$user->id}");
|
|
|
|
return $account;
|
|
}
|
|
}
|
|
|
|
// Wenn User keine account_id hat, gibt es definitiv kein Account
|
|
if (! $user->account_id) {
|
|
\Log::info("BusinessUserItem: User {$user->id} has no account_id - no account available");
|
|
|
|
return null;
|
|
}
|
|
|
|
// Account nachladen falls nötig
|
|
\Log::info("BusinessUserItem: Loading account for user {$user->id} (account_id: {$user->account_id})");
|
|
$account = UserAccount::find($user->account_id);
|
|
|
|
if (! $account) {
|
|
\Log::warning("BusinessUserItem: Account {$user->account_id} not found for user {$user->id}");
|
|
|
|
return null;
|
|
}
|
|
|
|
\Log::debug("BusinessUserItem: Successfully loaded account {$account->id} for user {$user->id}");
|
|
|
|
return $account;
|
|
} catch (\Exception $e) {
|
|
\Log::error("BusinessUserItem: Error loading account for user {$user->id}: ".$e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|