commit 08-2025
This commit is contained in:
parent
9ae662f63e
commit
480fdc65ed
404 changed files with 65310 additions and 2600431 deletions
|
|
@ -391,6 +391,9 @@ class BusinessUserItem
|
|||
}
|
||||
|
||||
public function __get($property) {
|
||||
if($this->b_user === null){
|
||||
return null;
|
||||
}
|
||||
if (property_exists($this->b_user, $property)) {
|
||||
return $this->b_user->$property;
|
||||
}
|
||||
|
|
|
|||
797
app/Services/BusinessPlan/BusinessUserItemOptimized.php
Normal file
797
app/Services/BusinessPlan/BusinessUserItemOptimized.php
Normal file
|
|
@ -0,0 +1,797 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use stdClass;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserBusiness;
|
||||
use App\Services\TranslationHelper;
|
||||
use App\Models\UserBusinessStructure;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* 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 $user_level_active_pos;
|
||||
private $needsQualificationRecalculation = false;
|
||||
|
||||
public function __construct($date)
|
||||
{
|
||||
$this->date = $date;
|
||||
$this->businessUserItems = []; // Initialize array
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
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("BusinessUserItem: 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("BusinessUserItem: 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 Error-Handling)
|
||||
$account = $user->relationLoaded('account') ? $user->account : null;
|
||||
if (!$account) {
|
||||
\Log::warning("BusinessUserItem: No account found for user {$user->id}");
|
||||
}
|
||||
$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 Fallback
|
||||
'm_account' => $account ? $account->m_account : '',
|
||||
'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,
|
||||
|
||||
// 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 Boundary-Check)
|
||||
$shopVolume = (float) $this->b_user->sales_volume_total_shop;
|
||||
$shopMargin = (float) $this->b_user->margin_shop;
|
||||
$this->b_user->commission_shop_sales = round($shopVolume / 100 * $shopMargin, 2);
|
||||
|
||||
\Log::debug("BusinessUserItem: Created optimized user {$user->id} for {$this->date->month}/{$this->date->year}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = $user->account;
|
||||
|
||||
// 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 : '';
|
||||
|
||||
// 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();
|
||||
|
||||
\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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 potenziellem Caching)
|
||||
*/
|
||||
private function getUserSalesVolumeOptimized(User $user, string $field)
|
||||
{
|
||||
try {
|
||||
// Hier könnte Caching implementiert werden
|
||||
$cacheKey = "sales_volume_{$user->id}_{$this->date->month}_{$this->date->year}_{$field}";
|
||||
|
||||
// Für jetzt: Direkter Aufruf (später durch Cache ersetzen)
|
||||
return $user->getUserSalesVolumeBy($this->date->month, $this->date->year, $field);
|
||||
|
||||
} 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()
|
||||
{
|
||||
TreeCalcBotOptimized::addUserID($this->b_user->user_id);
|
||||
}
|
||||
|
||||
public function getBUser()
|
||||
{
|
||||
return $this->b_user;
|
||||
}
|
||||
|
||||
public function addBusinessLineToUser($line, $obj)
|
||||
{
|
||||
$this->b_user->business_lines[$line] = $obj;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
try {
|
||||
$qualUserLevel = $this->calcuQualLevel();
|
||||
if ($qualUserLevel !== null) {
|
||||
//das erreichte level setzen
|
||||
$this->b_user->qual_user_level = $qualUserLevel->toArray();
|
||||
//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)) {
|
||||
$payline = (int) $this->b_user->qual_user_level['paylines'] + 1;
|
||||
$maxlines = count($this->b_user->business_lines) + 1;
|
||||
$growth_bonus = (float) $this->b_user->qual_user_level['growth_bonus'];
|
||||
|
||||
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 (JSON deserialization inconsistency)
|
||||
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 {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->b_user->commission_pp_total = $commission_pp_total;
|
||||
$this->b_user->commission_growth_total = $commission_growth_total;
|
||||
}
|
||||
|
||||
// ===== WEITERE ORIGINAL-METHODEN (gekürzt, vollständige Implementation in Original) =====
|
||||
//aktuelles level berechnen, max das eigene level, wenn weniger Points dann darunter
|
||||
public function calcuQualLevel()
|
||||
{
|
||||
$qualUserLevels = UserLevel::where('qual_kp', '<=', $this->b_user->sales_volume_points_KP_sum)
|
||||
->where('pos', '<=', $this->user_level_active_pos)
|
||||
->orderBy('qual_pp', 'desc')
|
||||
->get();
|
||||
|
||||
foreach ($qualUserLevels as $qualUserLevel) {
|
||||
$payline_points = $this->getPointsforPayline($qualUserLevel->paylines);
|
||||
$payline_points_qual_kp = $payline_points + $this->getRestQualKP();
|
||||
|
||||
if ($payline_points_qual_kp >= $qualUserLevel->qual_pp) {
|
||||
$this->b_user->payline_points = $payline_points;
|
||||
$this->b_user->payline_points_qual_kp = $payline_points_qual_kp;
|
||||
return $qualUserLevel;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getPointsforPayline($paylines): float
|
||||
{
|
||||
$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) {
|
||||
$this->b_user->qual_user_level_next = $qualUserLevelNext->toArray();
|
||||
}else{
|
||||
$this->b_user->qual_user_level_next = null;
|
||||
}
|
||||
}else{
|
||||
$this->b_user->qual_user_level_next = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function setNextUserLevel($force = false): void
|
||||
{
|
||||
//sucht den nächsten level, der mehr points hat als das aktuelle level
|
||||
$nextQualUserLevel = UserLevel::where('qual_pp', '<=', $this->b_user->payline_points_qual_kp)
|
||||
->where('pos', '>', $this->user_level_active_pos)
|
||||
->orderBy('qual_pp', 'desc')
|
||||
->first();
|
||||
//wenn der nächste level qualifiziert ist und die KP-Qualifikation erfüllt ist, dann setzt es den nächsten level
|
||||
if ($nextQualUserLevel && $this->isQualKP()) {
|
||||
$this->b_user->next_qual_user_level = $nextQualUserLevel->toArray();
|
||||
$this->b_user->next_can_user_level = null;
|
||||
} else {
|
||||
//wenn der nächste level nicht qualifiziert ist, dann sucht es den nächsten level, nach pos
|
||||
$nextCanUserLevel = UserLevel::where('pos', '>', $this->user_level_active_pos)
|
||||
->orderBy('qual_pp', 'asc')
|
||||
->first();
|
||||
if ($nextCanUserLevel) {
|
||||
$this->b_user->next_can_user_level = $nextCanUserLevel->toArray();
|
||||
}
|
||||
$this->b_user->next_qual_user_level = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function setFirstQualLevel(): void
|
||||
{
|
||||
$qualUserLevelNext = UserLevel::where('pos', '=', 1)
|
||||
->orderBy('qual_pp', 'asc')
|
||||
->first();
|
||||
if ($qualUserLevelNext) {
|
||||
$this->b_user->qual_user_level_next = $qualUserLevelNext->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
*/
|
||||
public function readParentsBusinessUsers($forceLiveCalculation = false): void
|
||||
{
|
||||
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)
|
||||
->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) {
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUserFromModel($user, $forceLiveCalculation);
|
||||
$businessUserItem->addUserID();
|
||||
$this->businessUserItems[] = $businessUserItem;
|
||||
}
|
||||
}
|
||||
|
||||
// Rekursiver Aufruf für alle Child-Items
|
||||
foreach ($this->businessUserItems as $businessUserItem) {
|
||||
$businessUserItem->readParentsBusinessUsers($forceLiveCalculation);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error("BusinessUserItem: Error reading parent users for {$this->b_user->user_id}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Parent Business Users aus gespeicherter Struktur (Original-Implementation)
|
||||
*/
|
||||
public function readStoredParentsBusinessUsers($structure): void
|
||||
{
|
||||
try {
|
||||
$parents = $this->findParentsBusinessOnStored($this->b_user->user_id, $structure);
|
||||
|
||||
if ($parents) {
|
||||
foreach ($parents as $obj) {
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUser($obj->user_id);
|
||||
$businessUserItem->addUserID();
|
||||
$this->businessUserItems[] = $businessUserItem;
|
||||
}
|
||||
|
||||
foreach ($this->businessUserItems as $businessUserItem) {
|
||||
$businessUserItem->readStoredParentsBusinessUsers($parents);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("BusinessUserItem: Error reading stored parent users: " . $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;
|
||||
}
|
||||
}
|
||||
198
app/Services/BusinessPlan/BusinessUserRepository.php
Normal file
198
app/Services/BusinessPlan/BusinessUserRepository.php
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use App\Models\UserBusinessStructure;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Repository für effiziente Datenbankabfragen im Business-Kontext
|
||||
* Löst N+1 Probleme durch optimierte Eager Loading Strategien
|
||||
*/
|
||||
class BusinessUserRepository
|
||||
{
|
||||
private $startDate;
|
||||
private $endDate;
|
||||
private $month;
|
||||
private $year;
|
||||
|
||||
public function __construct(int $month, int $year)
|
||||
{
|
||||
$this->month = $month;
|
||||
$this->year = $year;
|
||||
|
||||
$date = Carbon::parse($year.'-'.$month.'-1');
|
||||
$this->startDate = $date->format('Y-m-d H:i:s');
|
||||
$this->endDate = $date->endOfMonth()->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Root-User mit optimiertem Eager Loading und Caching
|
||||
*/
|
||||
public function getRootUsers(): Collection
|
||||
{
|
||||
$cacheKey = "root_users_{$this->month}_{$this->year}";
|
||||
|
||||
return cache()->remember($cacheKey, 3600, function() {
|
||||
\Log::info("BusinessUserRepository: Loading root users from database (cache miss)");
|
||||
|
||||
return User::with([
|
||||
'account',
|
||||
'user_level',
|
||||
'userBusiness' => function($query) {
|
||||
$query->where('month', $this->month)
|
||||
->where('year', $this->year);
|
||||
}
|
||||
])
|
||||
->select('users.*')
|
||||
->where('users.deleted_at', '=', null)
|
||||
->where('users.id', '!=', 1)
|
||||
->where('users.admin', '<', 4)
|
||||
->where('users.m_level', '!=', null)
|
||||
->where('users.m_sponsor', '=', null)
|
||||
->where('users.payment_account', '!=', null)
|
||||
->where('users.active_date', '<=', $this->endDate)
|
||||
->get();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt User ohne Parent-Zuordnung (Lazy Loading für Memory-Effizienz)
|
||||
*/
|
||||
public function getParentlessUsers(array $excludeUserIds = []): LazyCollection
|
||||
{
|
||||
$query = User::with([
|
||||
'account',
|
||||
'user_level',
|
||||
'userBusiness' => function($query) {
|
||||
$query->where('month', $this->month)
|
||||
->where('year', $this->year);
|
||||
}
|
||||
])
|
||||
->select('users.*')
|
||||
->where('users.deleted_at', '=', null)
|
||||
->where('users.id', '!=', 1)
|
||||
->where('users.admin', '<', 4)
|
||||
->where('users.payment_account', '!=', null)
|
||||
->where('users.active_date', '<=', $this->endDate);
|
||||
|
||||
if (!empty($excludeUserIds)) {
|
||||
$query->whereNotIn('users.id', $excludeUserIds);
|
||||
}
|
||||
|
||||
return $query->lazy(100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt einen einzelnen User mit Relations und Caching
|
||||
*/
|
||||
public function getUserWithRelations(int $userId): ?User
|
||||
{
|
||||
$cacheKey = "user_relations_{$userId}_{$this->month}_{$this->year}";
|
||||
|
||||
return cache()->remember($cacheKey, 1800, function() use ($userId) {
|
||||
\Log::debug("BusinessUserRepository: Loading user {$userId} with relations (cache miss)");
|
||||
|
||||
return User::with([
|
||||
'account',
|
||||
'user_level',
|
||||
'userBusiness' => function($query) {
|
||||
$query->where('month', $this->month)
|
||||
->where('year', $this->year);
|
||||
}
|
||||
])->find($userId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Sponsor für einen User
|
||||
*/
|
||||
public function getSponsorForUser(int $userId): ?User
|
||||
{
|
||||
$user = $this->getUserWithRelations($userId);
|
||||
|
||||
if (!$user || !$user->m_sponsor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getUserWithRelations($user->m_sponsor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob gespeicherte Struktur existiert (mit Caching)
|
||||
*/
|
||||
public function getStoredStructure(): ?UserBusinessStructure
|
||||
{
|
||||
$cacheKey = "stored_structure_{$this->month}_{$this->year}";
|
||||
|
||||
return cache()->remember($cacheKey, 7200, function() {
|
||||
\Log::debug("BusinessUserRepository: Loading stored structure (cache miss)");
|
||||
|
||||
$structure = UserBusinessStructure::where('year', $this->year)
|
||||
->where('month', $this->month)
|
||||
->first();
|
||||
|
||||
return ($structure && $structure->completed) ? $structure : null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt User-IDs aus gespeicherter Struktur
|
||||
*/
|
||||
public function getUserIdsFromStoredStructure(UserBusinessStructure $structure): array
|
||||
{
|
||||
$userIds = [];
|
||||
|
||||
if ($structure->structure) {
|
||||
$this->extractUserIdsFromStructure((array) $structure->structure, $userIds);
|
||||
}
|
||||
|
||||
if ($structure->parentless) {
|
||||
foreach ($structure->parentless as $item) {
|
||||
$userIds[] = $item->user_id;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekursive Extraktion von User-IDs aus Struktur
|
||||
*/
|
||||
private function extractUserIdsFromStructure(array $structure, array &$userIds): void
|
||||
{
|
||||
foreach ($structure as $item) {
|
||||
$userIds[] = $item->user_id;
|
||||
|
||||
if (isset($item->parents) && is_array($item->parents)) {
|
||||
$this->extractUserIdsFromStructure($item->parents, $userIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-Loading für User-Kollektionen
|
||||
*/
|
||||
public function loadUsersInBatches(array $userIds, int $batchSize = 100): \Generator
|
||||
{
|
||||
$chunks = array_chunk($userIds, $batchSize);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
yield User::with([
|
||||
'account',
|
||||
'user_level',
|
||||
'userBusiness' => function($query) {
|
||||
$query->where('month', $this->month)
|
||||
->where('year', $this->year);
|
||||
}
|
||||
])
|
||||
->whereIn('id', $chunk)
|
||||
->get()
|
||||
->keyBy('id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ class TreeCalcBot
|
|||
|
||||
}
|
||||
|
||||
public function initStructureAdmin($check = true)
|
||||
public function initStructureAdmin($check = true, $forceLiveCalculation = false)
|
||||
{
|
||||
//check is month is saved.
|
||||
if($check && $UserBusinessStructure = self::isFromStored($this->date->month, $this->date->year)){
|
||||
|
|
@ -46,7 +46,6 @@ class TreeCalcBot
|
|||
$this->readParentsUsers();
|
||||
$this->readParentlessUser();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function initStructureUser($user_id)
|
||||
|
|
|
|||
910
app/Services/BusinessPlan/TreeCalcBotOptimized.php
Normal file
910
app/Services/BusinessPlan/TreeCalcBotOptimized.php
Normal file
|
|
@ -0,0 +1,910 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use stdClass;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\UserBusinessStructure;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Optimierte Version der TreeCalcBot Klasse
|
||||
*
|
||||
* Verbesserungen:
|
||||
* - Trennung von Datenzugriff (Repository Pattern)
|
||||
* - Trennung von HTML-Rendering (Renderer Pattern)
|
||||
* - Optimierte Datenbankabfragen (N+1 Problem gelöst)
|
||||
* - Memory-effiziente Verarbeitung großer Datenmengen
|
||||
* - Robuste Fehlerbehandlung mit Logging
|
||||
* - Dependency Injection für bessere Testbarkeit
|
||||
*/
|
||||
class TreeCalcBotOptimized
|
||||
{
|
||||
private stdClass $date;
|
||||
private string $initFrom;
|
||||
private array $businessUsers = [];
|
||||
private array $parentless = [];
|
||||
private ?BusinessUserItemOptimized $businessUser = null;
|
||||
private ?BusinessUserItemOptimized $sponsor = null;
|
||||
private array $processedUserIds = [];
|
||||
private bool $forceLiveCalculation = false;
|
||||
|
||||
private BusinessUserRepository $repository;
|
||||
private TreeHtmlRenderer $renderer;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(
|
||||
int $month,
|
||||
int $year,
|
||||
string $initFrom = 'member',
|
||||
bool $forceLiveCalculation = false,
|
||||
?BusinessUserRepository $repository = null,
|
||||
?TreeHtmlRenderer $renderer = null,
|
||||
?LoggerInterface $logger = null
|
||||
) {
|
||||
$this->validateInput($month, $year);
|
||||
$this->initializeDate($month, $year);
|
||||
$this->initFrom = $initFrom;
|
||||
$this->forceLiveCalculation = $forceLiveCalculation;
|
||||
|
||||
// Dependency Injection mit Fallback
|
||||
$this->repository = $repository ?? new BusinessUserRepository($month, $year);
|
||||
$this->renderer = $renderer ?? new TreeHtmlRenderer($initFrom, $forceLiveCalculation);
|
||||
$this->logger = $logger ?? app(LoggerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert die Business-Struktur für Admin-Ansicht
|
||||
*
|
||||
* @param bool $check Prüft auf gespeicherte Struktur
|
||||
* @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten
|
||||
*/
|
||||
public function initStructureAdmin(bool $check = true, bool $forceLiveCalculation = false): void
|
||||
{
|
||||
|
||||
try {
|
||||
$this->forceLiveCalculation = $forceLiveCalculation;
|
||||
|
||||
if ($forceLiveCalculation) {
|
||||
$this->logger->info("Building fresh business structure for {$this->date->month}/{$this->date->year} with forced live calculation");
|
||||
$this->buildFreshStructure();
|
||||
return;
|
||||
}
|
||||
|
||||
$storedStructure = null;
|
||||
if ($check) {
|
||||
$storedStructure = $this->repository->getStoredStructure();
|
||||
}
|
||||
if ($storedStructure) {
|
||||
$this->logger->info("Loading stored business structure for {$this->date->month}/{$this->date->year}");
|
||||
$this->loadStoredStructure($storedStructure);
|
||||
} else {
|
||||
$this->logger->info("Building fresh business structure for {$this->date->month}/{$this->date->year}");
|
||||
$this->buildFreshStructure();
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error initializing admin structure: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert die Struktur für einen spezifischen User
|
||||
*
|
||||
* @param int $userId Die User-ID
|
||||
* @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten
|
||||
*/
|
||||
public function initStructureUser(int $userId, bool $forceLiveCalculation = false): void
|
||||
{
|
||||
try {
|
||||
$this->forceLiveCalculation = $forceLiveCalculation;
|
||||
|
||||
if ($forceLiveCalculation) {
|
||||
$this->logger->info("Initializing structure for user: {$userId} with forced live calculation");
|
||||
} else {
|
||||
$this->logger->info("Initializing structure for user: {$userId}");
|
||||
}
|
||||
|
||||
$user = $this->repository->getUserWithRelations($userId);
|
||||
|
||||
if (!$user) {
|
||||
$this->logger->warning("User not found: {$userId}");
|
||||
return;
|
||||
}
|
||||
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUserFromModel($user); // Erst User-Model laden, ohne forceLiveCalculation
|
||||
$this->addUserIdToProcessed($userId);
|
||||
$this->businessUsers[] = $businessUserItem;
|
||||
|
||||
$this->logger->info("Created businessUserItem for user {$userId}, total businessUsers: " . count($this->businessUsers));
|
||||
|
||||
// Prüfe gespeicherte Struktur nur, wenn Live-Berechnung nicht erzwungen wird
|
||||
$storedStructure = null;
|
||||
if (!$forceLiveCalculation) {
|
||||
$storedStructure = $this->repository->getStoredStructure();
|
||||
$this->logger->info("Stored structure " . ($storedStructure ? "found" : "not found"));
|
||||
}
|
||||
|
||||
if ($storedStructure && !$forceLiveCalculation) {
|
||||
$this->loadStoredParentsUsers($storedStructure);
|
||||
if (isset($this->businessUsers[0]) && $this->businessUsers[0]->sponsor) {
|
||||
$this->loadStoredSponsorUser($this->businessUsers[0]->sponsor->user_id);
|
||||
}
|
||||
} else {
|
||||
if ($forceLiveCalculation) {
|
||||
$this->logger->info("Forcing live calculation - skipping stored structure for user {$userId}");
|
||||
}
|
||||
$this->loadParentsUsers();
|
||||
$this->loadSponsorUser($userId);
|
||||
|
||||
$totalSubUsers = 0;
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
$totalSubUsers += count($businessUser->businessUserItems);
|
||||
}
|
||||
$this->logger->info("After loadParentsUsers: {$totalSubUsers} total sub-users loaded across " . count($this->businessUsers) . " business users");
|
||||
|
||||
// WICHTIG: calcQualPP() erst NACH loadParentsUsers() aufrufen, da Points benötigt werden
|
||||
if ($forceLiveCalculation) {
|
||||
$this->logger->info("Calculating qualification levels for all business users");
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
$businessUser->calcQualPP();
|
||||
}
|
||||
//wird nicht benötigt, da hier nur die Points berechnet werden
|
||||
//$this->calculateQualPPForAllUsers(); // Auch für alle Sub-User
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error initializing user structure for {$userId}: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert detaillierte Business-User-Informationen
|
||||
*
|
||||
* @param User $user Das User-Model
|
||||
* @param bool $forceLiveCalculation Erzwingt Live-Berechnung und überspringt gespeicherte Daten
|
||||
*/
|
||||
public function initBusinesslUserDetail(User $user, bool $forceLiveCalculation = false): void
|
||||
{
|
||||
try {
|
||||
$this->logger->info("Initializing business user details for: {$user->id}");
|
||||
|
||||
$this->businessUser = new BusinessUserItemOptimized($this->date);
|
||||
$this->businessUser->makeUserFromModel($user, $forceLiveCalculation); // ✅ Nutzt bereits User-Objekt
|
||||
$this->businessUser->checkSponsor($user);
|
||||
|
||||
// Führe vollständige Berechnung durch, wenn:
|
||||
// 1. Daten nicht gespeichert sind ODER
|
||||
// 2. Live-Berechnung erzwungen wird
|
||||
if (!$this->businessUser->isSave() || $forceLiveCalculation) {
|
||||
if ($forceLiveCalculation) {
|
||||
$this->logger->info("Forcing live calculation for user {$user->id}");
|
||||
}
|
||||
|
||||
// Aufbau der Struktur für den User in die unendliche Tiefe
|
||||
$this->businessUser->readParentsBusinessUsers($forceLiveCalculation);
|
||||
|
||||
// Calculate Points in Lines (optimiert für Memory-Effizienz)
|
||||
if (count($this->businessUser->businessUserItems) > 0) {
|
||||
$this->calculateUserPointsOptimized($this->businessUser->businessUserItems, 1, $this->businessUser);
|
||||
}
|
||||
// Qualifikation nach qual_kp (KundenPoints) und qual_pp (PaylinePoints)
|
||||
$this->businessUser->calcQualPP();
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error initializing business user details for {$user->id}: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Growth Bonus zurück (ab Linie 6)
|
||||
* Erweitert um Array/Object-Kompatibilität für business_lines
|
||||
*/
|
||||
public function getGrowthBonus(): array
|
||||
{
|
||||
if (!$this->businessUser || !$this->businessUser->business_lines) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (count($this->businessUser->business_lines) > 6) {
|
||||
// Handle both array and object types (JSON deserialization inconsistency)
|
||||
if (is_array($this->businessUser->business_lines)) {
|
||||
$bLines = $this->businessUser->business_lines;
|
||||
} else {
|
||||
$bLines = $this->businessUser->business_lines->toArray();
|
||||
}
|
||||
return array_slice($bLines, 6);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Wert für spezifische Linie zurück
|
||||
*/
|
||||
public function getKeybyLine(int $line, string $key)
|
||||
{
|
||||
if (!$this->businessUser || !$this->businessUser->business_lines) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$bLines = $this->businessUser->business_lines;
|
||||
if (!isset($bLines[$line])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lineData = $bLines[$line];
|
||||
|
||||
if ($lineData instanceof stdClass) {
|
||||
return $lineData->{$key} ?? 0;
|
||||
}
|
||||
|
||||
if (is_array($lineData)) {
|
||||
return $lineData[$key] ?? 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML-Rendering Methoden (Delegation an Renderer)
|
||||
*/
|
||||
public function makeHtmlTree(): string
|
||||
{
|
||||
return $this->renderer->renderTree($this->businessUsers);
|
||||
}
|
||||
|
||||
public function makeParentlessHtml(): string
|
||||
{
|
||||
return $this->renderer->renderParentless($this->parentless);
|
||||
}
|
||||
|
||||
public function makeSponsorHtml(): string
|
||||
{
|
||||
return $this->renderer->renderSponsor($this->sponsor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter-Methoden (Rückwärtskompatibilität)
|
||||
*/
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->businessUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zählt die Gesamtanzahl aller User in der Struktur (rekursiv)
|
||||
*/
|
||||
public function getTotalUserCount(): int
|
||||
{
|
||||
$totalCount = 0;
|
||||
|
||||
// Zähle alle Root-User
|
||||
$totalCount += count($this->businessUsers);
|
||||
|
||||
// Zähle alle Unter-User rekursiv
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
$totalCount += $this->countBusinessUserItems($businessUser);
|
||||
}
|
||||
|
||||
// Zähle parentless User
|
||||
$totalCount += count($this->parentless);
|
||||
|
||||
return $totalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zählt BusinessUserItems rekursiv
|
||||
*/
|
||||
private function countBusinessUserItems($businessUserItem): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
if (isset($businessUserItem->businessUserItems) && is_array($businessUserItem->businessUserItems)) {
|
||||
$count += count($businessUserItem->businessUserItems);
|
||||
|
||||
// Rekursiv durch alle Unter-Items zählen
|
||||
foreach ($businessUserItem->businessUserItems as $subItem) {
|
||||
$count += $this->countBusinessUserItems($subItem);
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function isParentless(): bool
|
||||
{
|
||||
return !empty($this->parentless);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static Methoden (Rückwärtskompatibilität)
|
||||
*/
|
||||
public static function isFromStored(int $month, int $year): ?UserBusinessStructure
|
||||
{
|
||||
$structure = UserBusinessStructure::where('year', $year)
|
||||
->where('month', $month)
|
||||
->first();
|
||||
|
||||
return ($structure && $structure->completed) ? $structure : null;
|
||||
}
|
||||
|
||||
public static function addUserID(int $id): void
|
||||
{
|
||||
// Deprecated: Wird durch Instanz-Methode ersetzt
|
||||
// Bleibt für Rückwärtskompatibilität erhalten
|
||||
}
|
||||
|
||||
// ===== Private Methoden =====
|
||||
|
||||
/**
|
||||
* Validiert Eingabeparameter
|
||||
*/
|
||||
private function validateInput(int $month, int $year): void
|
||||
{
|
||||
if ($month < 1 || $month > 12) {
|
||||
throw new \InvalidArgumentException("Invalid month: {$month}");
|
||||
}
|
||||
|
||||
$currentYear = (int) date('Y');
|
||||
if ($year < 2020 || $year > $currentYear + 1) {
|
||||
throw new \InvalidArgumentException("Invalid year: {$year}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert Datums-Objekt
|
||||
*/
|
||||
private function initializeDate(int $month, int $year): void
|
||||
{
|
||||
$this->date = new stdClass();
|
||||
$date = Carbon::parse($year . '-' . $month . '-1');
|
||||
$this->date->month = $month;
|
||||
$this->date->year = $year;
|
||||
$this->date->start_date = $date->format('Y-m-d H:i:s');
|
||||
$this->date->end_date = $date->endOfMonth()->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt gespeicherte Struktur
|
||||
*/
|
||||
private function loadStoredStructure(UserBusinessStructure $structure): void
|
||||
{
|
||||
$this->loadStoredRootUsers($structure);
|
||||
$this->loadStoredParentsUsers($structure);
|
||||
$this->loadStoredParentlessUsers($structure);
|
||||
|
||||
// Prüfe ob gespeicherte Daten vollständig sind, ansonsten berechne neu
|
||||
$this->validateAndRecalculateIfNeeded();
|
||||
$this->validateAndRecalculateParentlessIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut frische Struktur auf
|
||||
*/
|
||||
private function buildFreshStructure(): void
|
||||
{
|
||||
$this->loadRootUsers();
|
||||
$this->loadParentsUsers();
|
||||
$this->loadParentlessUsers();
|
||||
|
||||
// WICHTIG: Berechne Punkte und Qualifikationen für alle Business-Users
|
||||
$this->calculateAllBusinessUsers();
|
||||
$this->calculateAllParentlessUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Root-Users (optimiert mit Memory-Monitoring)
|
||||
*/
|
||||
private function loadRootUsers(): void
|
||||
{
|
||||
$startMemory = memory_get_usage();
|
||||
$users = $this->repository->getRootUsers();
|
||||
|
||||
foreach ($users as $user) {
|
||||
// Memory-Check vor jeder User-Verarbeitung
|
||||
$this->checkMemoryUsage('loadRootUsers', $user->id);
|
||||
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUserFromModel($user, $this->forceLiveCalculation); // ✅ Nutzt bereits geladene Relations mit forceLiveCalculation
|
||||
$this->addUserIdToProcessed($user->id);
|
||||
$this->businessUsers[] = $businessUserItem;
|
||||
}
|
||||
|
||||
$endMemory = memory_get_usage();
|
||||
$memoryUsed = $this->formatBytes($endMemory - $startMemory);
|
||||
|
||||
$this->logger->info("Loaded " . count($users) . " root users with optimized relations. Memory used: {$memoryUsed}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Parent-Users für alle Business-Users
|
||||
*/
|
||||
private function loadParentsUsers(): void
|
||||
{
|
||||
$this->logger->info("Loading parent users for " . count($this->businessUsers) . " business users");
|
||||
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
$businessUser->readParentsBusinessUsers($this->forceLiveCalculation);
|
||||
|
||||
$this->logger->debug("Loaded " . count($businessUser->businessUserItems) . " parent users for user " . ($businessUser->b_user->user_id ?? 'unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt parentlose Users (Memory-optimiert)
|
||||
*/
|
||||
private function loadParentlessUsers(): void
|
||||
{
|
||||
$count = 0;
|
||||
$excludeIds = array_keys($this->processedUserIds);
|
||||
|
||||
foreach ($this->repository->getParentlessUsers($excludeIds) as $user) {
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUserFromModel($user, $this->forceLiveCalculation); // ✅ Nutzt bereits geladene Relations mit forceLiveCalculation
|
||||
$this->parentless[] = $businessUserItem;
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->logger->info("Loaded {$count} parentless users with optimized relations");
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet Punkte und Qualifikationen für alle Business-Users
|
||||
*/
|
||||
private function calculateAllBusinessUsers(): void
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$this->logger->info("Starting calculation for " . count($this->businessUsers) . " business users");
|
||||
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
try {
|
||||
// Berechne Punkte in Linien (wie bei initBusinesslUserDetail)
|
||||
if (count($businessUser->businessUserItems) > 0) {
|
||||
$this->calculateUserPointsOptimized($businessUser->businessUserItems, 1, $businessUser);
|
||||
}
|
||||
|
||||
// Qualifikation nach qual_kp und qual_pp berechnen
|
||||
$businessUser->calcQualPP();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error calculating business user {$businessUser->__get('user_id')}: " . $e->getMessage());
|
||||
// Weiter mit dem nächsten User, nicht abbrechen
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$endTime = microtime(true);
|
||||
$executionTime = round(($endTime - $startTime) * 1000, 2);
|
||||
$this->logger->info("Completed calculations for all business users in {$executionTime}ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet Punkte und Qualifikationen für alle Parentless-Users
|
||||
*/
|
||||
private function calculateAllParentlessUsers(): void
|
||||
{
|
||||
if (empty($this->parentless)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$startTime = microtime(true);
|
||||
$this->logger->info("Starting calculation for " . count($this->parentless) . " parentless users");
|
||||
|
||||
foreach ($this->parentless as $parentlessUser) {
|
||||
try {
|
||||
// Berechne Punkte in Linien
|
||||
if (count($parentlessUser->businessUserItems) > 0) {
|
||||
$this->calculateUserPointsOptimized($parentlessUser->businessUserItems, 1, $parentlessUser);
|
||||
}
|
||||
|
||||
// Qualifikation berechnen
|
||||
$parentlessUser->calcQualPP();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error calculating parentless user {$parentlessUser->__get('user_id')}: " . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$endTime = microtime(true);
|
||||
$executionTime = round(($endTime - $startTime) * 1000, 2);
|
||||
$this->logger->info("Completed calculations for all parentless users in {$executionTime}ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert gespeicherte Daten und berechnet bei Bedarf neu
|
||||
*/
|
||||
private function validateAndRecalculateIfNeeded(): void
|
||||
{
|
||||
$incompleteUsers = 0;
|
||||
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
// Prüfe ob grundlegende Berechnungen vorhanden sind
|
||||
if ($this->isBusinessUserIncomplete($businessUser)) {
|
||||
$incompleteUsers++;
|
||||
|
||||
try {
|
||||
// Führe fehlende Berechnungen durch
|
||||
if (count($businessUser->businessUserItems) > 0) {
|
||||
$this->calculateUserPointsOptimized($businessUser->businessUserItems, 1, $businessUser);
|
||||
}
|
||||
$businessUser->calcQualPP();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error recalculating business user {$businessUser->__get('user_id')}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($incompleteUsers > 0) {
|
||||
$this->logger->info("Recalculated {$incompleteUsers} incomplete business users from stored data");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob ein BusinessUser unvollständige Daten hat
|
||||
* Erweitert um Level-Qualifikationsdaten für Struktur-Ansicht
|
||||
*/
|
||||
private function isBusinessUserIncomplete($businessUser): bool
|
||||
{
|
||||
// Prüfe grundlegende Felder die nach Berechnungen vorhanden sein sollten
|
||||
$salesVolumeSum = $businessUser->__get('sales_volume_points_sum');
|
||||
$qualKp = $businessUser->__get('qual_kp');
|
||||
|
||||
// Prüfe Level-Qualifikationsdaten für Struktur-Ansicht
|
||||
$nextQualUserLevel = $businessUser->__get('next_qual_user_level');
|
||||
$nextCanUserLevel = $businessUser->__get('next_can_user_level');
|
||||
$hasLevelQualificationData = !empty($nextQualUserLevel) || !empty($nextCanUserLevel);
|
||||
|
||||
// User ist unvollständig wenn:
|
||||
// 1. Grundlegende berechnete Werte fehlen ODER
|
||||
// 2. Level-Qualifikationsdaten fehlen (wichtig für Struktur-Ansicht mit grünen Pfeilen)
|
||||
$missingBasicData = ($salesVolumeSum === null || $salesVolumeSum === 0) &&
|
||||
($qualKp === null || $qualKp === 0);
|
||||
|
||||
$missingLevelData = !$hasLevelQualificationData;
|
||||
|
||||
return $missingBasicData || $missingLevelData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert und berechnet parentless Users bei Bedarf neu
|
||||
*/
|
||||
private function validateAndRecalculateParentlessIfNeeded(): void
|
||||
{
|
||||
if (empty($this->parentless)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$incompleteUsers = 0;
|
||||
|
||||
foreach ($this->parentless as $parentlessUser) {
|
||||
if ($this->isBusinessUserIncomplete($parentlessUser)) {
|
||||
$incompleteUsers++;
|
||||
|
||||
try {
|
||||
if (count($parentlessUser->businessUserItems) > 0) {
|
||||
$this->calculateUserPointsOptimized($parentlessUser->businessUserItems, 1, $parentlessUser);
|
||||
}
|
||||
$parentlessUser->calcQualPP();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error recalculating parentless user {$parentlessUser->__get('user_id')}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($incompleteUsers > 0) {
|
||||
$this->logger->info("Recalculated {$incompleteUsers} incomplete parentless users from stored data");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt Sponsor für User
|
||||
*/
|
||||
private function loadSponsorUser(int $userId): void
|
||||
{
|
||||
try {
|
||||
$sponsorUser = $this->repository->getSponsorForUser($userId);
|
||||
|
||||
if ($sponsorUser) {
|
||||
$this->sponsor = new BusinessUserItemOptimized($this->date);
|
||||
$this->sponsor->makeUser($sponsorUser->id);
|
||||
$this->logger->info("Loaded sponsor {$sponsorUser->id} for user {$userId}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning("Could not load sponsor for user {$userId}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gespeicherte Root-Users laden
|
||||
*/
|
||||
private function loadStoredRootUsers(UserBusinessStructure $structure): void
|
||||
{
|
||||
if (!$structure->structure) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($structure->structure as $obj) {
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUser($obj->user_id);
|
||||
$this->addUserIdToProcessed($obj->user_id);
|
||||
$this->businessUsers[] = $businessUserItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gespeicherte Parent-Users laden
|
||||
*/
|
||||
private function loadStoredParentsUsers(UserBusinessStructure $structure): void
|
||||
{
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
$businessUser->readStoredParentsBusinessUsers($structure->structure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gespeicherte parentlose Users laden
|
||||
*/
|
||||
private function loadStoredParentlessUsers(UserBusinessStructure $structure): void
|
||||
{
|
||||
if (!$structure->parentless) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($structure->parentless as $obj) {
|
||||
if (!isset($this->processedUserIds[$obj->user_id])) {
|
||||
$businessUserItem = new BusinessUserItemOptimized($this->date);
|
||||
$businessUserItem->makeUser($obj->user_id);
|
||||
$this->parentless[] = $businessUserItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gespeicherten Sponsor laden
|
||||
*/
|
||||
private function loadStoredSponsorUser(int $userId): void
|
||||
{
|
||||
$this->sponsor = new BusinessUserItemOptimized($this->date);
|
||||
$this->sponsor->makeUser($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimierte Punkte-Berechnung (Stack-basiert mit korrekter Depth-First Reihenfolge)
|
||||
*
|
||||
* KRITISCH: Stack muss gleiche Reihenfolge wie Original-Rekursion produzieren
|
||||
* Original: Depth-First Traversierung (erst tief, dann Punkte addieren)
|
||||
* Stack: Muss umgekehrt arbeiten - erst alle Kinder sammeln, dann von tief zu flach verarbeiten
|
||||
*/
|
||||
private function calculateUserPointsOptimized(array $businessUserItems, int $startLine, BusinessUserItemOptimized $businessUserToUpdate): void
|
||||
{
|
||||
$processingStack = [];
|
||||
$collectionStack = []; // Sammelt Items in korrekter Reihenfolge
|
||||
|
||||
// Phase 1: Sammle alle Items in Depth-First Reihenfolge
|
||||
foreach ($businessUserItems as $item) {
|
||||
$collectionStack[] = ['item' => $item, 'line' => $startLine, 'depth' => 0];
|
||||
}
|
||||
|
||||
// Expandiere alle Kinder (Depth-First)
|
||||
$processedItems = [];
|
||||
while (!empty($collectionStack)) {
|
||||
$current = array_shift($collectionStack); // FIFO für Breadth-First Sammlung
|
||||
$item = $current['item'];
|
||||
$line = $current['line'];
|
||||
$depth = $current['depth'];
|
||||
|
||||
// Markiere für Verarbeitung (mit Tiefe für spätere Sortierung)
|
||||
$processingStack[] = [
|
||||
'item' => $item,
|
||||
'line' => $line,
|
||||
'depth' => $depth,
|
||||
'id' => $item->user_id ?? uniqid()
|
||||
];
|
||||
|
||||
// Füge Kinder hinzu (werden später verarbeitet = Depth-First)
|
||||
if (isset($item->businessUserItems) && count($item->businessUserItems) > 0) {
|
||||
// Kinder in umgekehrter Reihenfolge hinzufügen für korrekte Stack-Verarbeitung
|
||||
$children = array_reverse($item->businessUserItems);
|
||||
foreach ($children as $childItem) {
|
||||
array_unshift($collectionStack, [
|
||||
'item' => $childItem,
|
||||
'line' => $line + 1,
|
||||
'depth' => $depth + 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Sortiere nach Tiefe (tiefste zuerst, wie bei Rekursion)
|
||||
usort($processingStack, function($a, $b) {
|
||||
return $b['depth'] <=> $a['depth']; // Tiefste zuerst
|
||||
});
|
||||
|
||||
// Phase 3: Verarbeite in korrekter Reihenfolge (von tief zu flach)
|
||||
foreach ($processingStack as $current) {
|
||||
$item = $current['item'];
|
||||
$line = $current['line'];
|
||||
|
||||
try {
|
||||
// Business Line initialisieren falls nötig
|
||||
if (!isset($businessUserToUpdate->business_lines[$line])) {
|
||||
$obj = new stdClass();
|
||||
$obj->points = 0;
|
||||
$businessUserToUpdate->addBusinessLineToUser($line, $obj);
|
||||
}
|
||||
|
||||
// Punkte hinzufügen (mit Validierung)
|
||||
$points = (float) ($item->sales_volume_points_TP_sum ?? 0);
|
||||
if ($points > 0) {
|
||||
$businessUserToUpdate->addBusinessLinePoints($line, $points);
|
||||
$businessUserToUpdate->addTotalTP($points);
|
||||
}
|
||||
|
||||
$this->logger->debug("Processed user {$current['id']} at line {$line} with {$points} points");
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error("Error processing user points for {$current['id']}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info("Processed " . count($processingStack) . " business user items in depth-first order");
|
||||
}
|
||||
|
||||
/**
|
||||
* User-ID zu verarbeiteten IDs hinzufügen
|
||||
*/
|
||||
private function addUserIdToProcessed(int $id): void
|
||||
{
|
||||
$this->processedUserIds[$id] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob User bereits verarbeitet wurde
|
||||
*/
|
||||
private function isUserProcessed(int $id): bool
|
||||
{
|
||||
return isset($this->processedUserIds[$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory-Monitoring Methoden
|
||||
*/
|
||||
private function checkMemoryUsage(string $operation, $identifier = null): void
|
||||
{
|
||||
$currentMemory = memory_get_usage();
|
||||
$memoryLimit = $this->parseMemoryLimit(ini_get('memory_limit'));
|
||||
$memoryPercent = ($currentMemory / $memoryLimit) * 100;
|
||||
|
||||
if ($memoryPercent > 80) {
|
||||
$currentFormatted = $this->formatBytes($currentMemory);
|
||||
$limitFormatted = $this->formatBytes($memoryLimit);
|
||||
|
||||
$this->logger->warning("High memory usage detected in {$operation}", [
|
||||
'identifier' => $identifier,
|
||||
'current_memory' => $currentFormatted,
|
||||
'memory_limit' => $limitFormatted,
|
||||
'usage_percent' => round($memoryPercent, 2)
|
||||
]);
|
||||
|
||||
// Garbage Collection bei hohem Memory-Verbrauch
|
||||
if ($memoryPercent > 90) {
|
||||
$this->logger->warning("Critical memory usage - forcing garbage collection");
|
||||
gc_collect_cycles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Public Properties für Rückwärtskompatibilität
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'date':
|
||||
return $this->date;
|
||||
case 'business_user':
|
||||
return $this->businessUser;
|
||||
case 'business_users':
|
||||
return $this->businessUsers;
|
||||
case 'parentless':
|
||||
return $this->parentless;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Property {$name} does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet calcQualPP() für alle BusinessUsers rekursiv
|
||||
* Muss NACH loadParentsUsers() aufgerufen werden, da Points benötigt werden
|
||||
*/
|
||||
private function calculateQualPPForAllUsers(): void
|
||||
{
|
||||
$this->logger->info("Starting recursive calcQualPP for all users");
|
||||
$totalCalculated = 0;
|
||||
|
||||
foreach ($this->businessUsers as $businessUser) {
|
||||
$totalCalculated += $this->calculateQualPPRecursive($businessUser);
|
||||
}
|
||||
|
||||
$this->logger->info("Completed calcQualPP for {$totalCalculated} users");
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekursive Hilfsmethode für calcQualPP
|
||||
*/
|
||||
private function calculateQualPPRecursive($businessUser): int
|
||||
{
|
||||
$calculated = 0;
|
||||
|
||||
if (isset($businessUser->businessUserItems) && is_array($businessUser->businessUserItems)) {
|
||||
foreach ($businessUser->businessUserItems as $subBusinessUser) {
|
||||
if ($subBusinessUser->b_user && $subBusinessUser->b_user->user_id) {
|
||||
try {
|
||||
$subBusinessUser->calcQualPP();
|
||||
$calculated++;
|
||||
$this->logger->debug("Calculated calcQualPP for user " . $subBusinessUser->b_user->user_id);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning("Error calculating calcQualPP for user " . $subBusinessUser->b_user->user_id . ": " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Rekursiver Aufruf
|
||||
$calculated += $this->calculateQualPPRecursive($subBusinessUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $calculated;
|
||||
}
|
||||
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'business_users':
|
||||
$this->businessUsers = $value;
|
||||
break;
|
||||
case 'parentless':
|
||||
$this->parentless = $value;
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Property {$name} cannot be set");
|
||||
}
|
||||
}
|
||||
}
|
||||
183
app/Services/BusinessPlan/TreeHelperOptimized.php
Normal file
183
app/Services/BusinessPlan/TreeHelperOptimized.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use App\Models\UserBusiness;
|
||||
|
||||
|
||||
/**
|
||||
* Klasse für die HTML-Darstellung von Business-Trees
|
||||
* Trennt Präsentationslogik von Geschäftslogik
|
||||
*/
|
||||
class TreeHelperOptimized
|
||||
{
|
||||
/**
|
||||
* Generiert QualKP Badge für UserBusiness
|
||||
*/
|
||||
public static function generateQualKPBadge(UserBusiness $userBusiness): string
|
||||
{
|
||||
if (!$userBusiness->m_level_id) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$qualKP = (int) $userBusiness->qual_kp;
|
||||
$pointsSum = (int) $userBusiness->sales_volume_points_KP_sum;
|
||||
$isQual = $pointsSum >= $qualKP;
|
||||
|
||||
$badgeClass = $isQual ? 'badge-outline-success' : 'badge-outline-danger';
|
||||
|
||||
return '<span class="badge ' . $badgeClass . '"> KU ' . $qualKP . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert QualKP Badge für User
|
||||
*/
|
||||
public static function generateQualKPBadgeForUser(User $user, int $month, int $year): string
|
||||
{
|
||||
if (!$user->user_level) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$qualKP = (int) $user->user_level->qual_kp;
|
||||
$pointsSum = (int) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_points_KP_sum');
|
||||
$isQual = $pointsSum >= $qualKP;
|
||||
|
||||
$badgeClass = $isQual ? 'badge-outline-success' : 'badge-outline-warning-dark';
|
||||
|
||||
return '<span class="badge ' . $badgeClass . '"> KU ' . $qualKP . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Sales Volume Display für UserBusiness
|
||||
*/
|
||||
public static function generateSalesVolumeDisplay(UserBusiness $userBusiness, string $type): string
|
||||
{
|
||||
if ($type === 'points') {
|
||||
$total = (int) $userBusiness->sales_volume_points_KP_sum;
|
||||
$individual = (int) $userBusiness->sales_volume_KP_points;
|
||||
$shop = (int) $userBusiness->sales_volume_points_shop;
|
||||
} else {
|
||||
$total = (float) $userBusiness->sales_volume_total_sum;
|
||||
$individual = (float) $userBusiness->sales_volume_total;
|
||||
$shop = (float) $userBusiness->sales_volume_total_shop;
|
||||
$suffix = ' €';
|
||||
}
|
||||
|
||||
$totalFormatted = $type === 'points' ? $total : formatNumber($total);
|
||||
$individualFormatted = $type === 'points' ? $individual : formatNumber($individual);
|
||||
$shopFormatted = $type === 'points' ? $shop : formatNumber($shop);
|
||||
$suffix = $type === 'points' ? '' : ' €';
|
||||
|
||||
return '<div class="no-line-break">' . $totalFormatted . $suffix . '</div>' .
|
||||
'<span class="small no-line-break">E: ' . $individualFormatted . ' | S: ' . $shopFormatted . $suffix . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Sales Volume Display für User
|
||||
*/
|
||||
public static function generateSalesVolumeDisplayForUser(User $user, string $type, int $month, int $year): string
|
||||
{
|
||||
if ($type === 'points') {
|
||||
$total = (int) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_points_KP_sum');
|
||||
$individual = (int) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_KP_points');
|
||||
$shop = (int) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_points_shop');
|
||||
} else {
|
||||
$total = (float) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_total_sum');
|
||||
$individual = (float) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_total');
|
||||
$shop = (float) $user->getUserSalesVolumeBy($month, $year, 'sales_volume_total_shop');
|
||||
}
|
||||
|
||||
$totalFormatted = $type === 'points' ? $total : formatNumber($total);
|
||||
$individualFormatted = $type === 'points' ? $individual : formatNumber($individual);
|
||||
$shopFormatted = $type === 'points' ? $shop : formatNumber($shop);
|
||||
$suffix = $type === 'points' ? '' : ' €';
|
||||
|
||||
return '<div class="no-line-break">' . $totalFormatted . $suffix . '</div>' .
|
||||
'<span class="small no-line-break">E: ' . $individualFormatted . ' | S: ' . $shopFormatted . $suffix . '</span>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generiert Action Buttons (mit XSS-Schutz)
|
||||
*/
|
||||
public static function generateActionButtons($userId): string
|
||||
{
|
||||
$userId = (int) $userId; // Sicherheit: Nur Integer
|
||||
|
||||
$html = '<button type="button" class="btn icon-btn btn-xs btn-secondary" data-toggle="modal" data-target="#modals-load-content"
|
||||
data-id="' . $userId . '"
|
||||
data-action="business-user-detail"
|
||||
data-back=""
|
||||
data-modal="modal-xl"
|
||||
data-init_from="admin"
|
||||
data-route="' . route('modal_load') . '"><span class="fa fa-calculator"></span></button>';
|
||||
|
||||
if (config('app.debug') === true) {
|
||||
$html .= '<a href="' . route('admin_business_optimized_user_detail', [$userId]) . '" class="btn icon-btn btn-xs btn-primary"><span class="fa fa-calculator"></span></a>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Sponsor Display für UserBusiness
|
||||
*/
|
||||
public static function generateSponsorDisplay(UserBusiness $userBusiness): string
|
||||
{
|
||||
if (!$userBusiness->sponsor || !$userBusiness->sponsor->is_sponsor) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$sponsor = $userBusiness->sponsor;
|
||||
$html = e($sponsor->first_name . ' ' . $sponsor->last_name);
|
||||
|
||||
$html .= ' <button type="button" class="btn icon-btn btn-xs btn-secondary" data-toggle="modal" data-target="#modals-load-content"
|
||||
data-id="' . (int) $sponsor->user_id . '"
|
||||
data-action="business-user-detail"
|
||||
data-back=""
|
||||
data-modal="modal-xl"
|
||||
data-init_from="admin"
|
||||
data-route="' . route('modal_load') . '"><span class="fa fa-calculator"></span></button><br>';
|
||||
|
||||
$html .= '<span class="small no-line-break">' . e($sponsor->email);
|
||||
$html .= ' | ' . e($sponsor->m_account);
|
||||
$html .= '</span>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Sponsor Display für User
|
||||
*/
|
||||
public static function generateSponsorDisplayForUser(User $user): string
|
||||
{
|
||||
if (!$user->user_sponsor) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$sponsor = $user->user_sponsor;
|
||||
$html = '';
|
||||
|
||||
if ($sponsor->account) {
|
||||
$html .= e($sponsor->account->first_name . ' ' . $sponsor->account->last_name);
|
||||
$html .= ' <button type="button" class="btn icon-btn btn-xs btn-secondary" data-toggle="modal" data-target="#modals-load-content"
|
||||
data-id="' . (int) $sponsor->id . '"
|
||||
data-action="business-user-detail"
|
||||
data-back=""
|
||||
data-modal="modal-xl"
|
||||
data-init_from="admin"
|
||||
data-route="' . route('modal_load') . '"><span class="fa fa-calculator"></span></button><br>';
|
||||
}
|
||||
|
||||
$html .= '<span class="small no-line-break">' . e($sponsor->email);
|
||||
if ($sponsor->account) {
|
||||
$html .= ' | ' . e($sponsor->account->m_account);
|
||||
}
|
||||
$html .= '</span>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
391
app/Services/BusinessPlan/TreeHtmlRenderer.php
Normal file
391
app/Services/BusinessPlan/TreeHtmlRenderer.php
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\Services\TranslationHelper;
|
||||
|
||||
/**
|
||||
* Klasse für die HTML-Darstellung von Business-Trees
|
||||
* Trennt Präsentationslogik von Geschäftslogik
|
||||
*/
|
||||
class TreeHtmlRenderer
|
||||
{
|
||||
private string $initFrom;
|
||||
private bool $forceLiveCalculation;
|
||||
|
||||
public function __construct(string $initFrom = 'member', bool $forceLiveCalculation = false)
|
||||
{
|
||||
$this->initFrom = $initFrom;
|
||||
$this->forceLiveCalculation = $forceLiveCalculation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert den kompletten Business-Tree als HTML
|
||||
*/
|
||||
public function renderTree(array $businessUsers): string
|
||||
{
|
||||
if (empty($businessUsers)) {
|
||||
return '<div class="alert alert-info">Keine Business-User gefunden.</div>';
|
||||
}
|
||||
|
||||
$html = '<ol class="dd-list">';
|
||||
foreach ($businessUsers as $businessUser) {
|
||||
$html .= $this->renderUserItem($businessUser, 0);
|
||||
}
|
||||
$html .= '</ol>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert parentlose User als HTML
|
||||
*/
|
||||
public function renderParentless(array $parentless): string
|
||||
{
|
||||
if (empty($parentless)) {
|
||||
return '<div class="alert alert-info">Keine parentlosen User gefunden.</div>';
|
||||
}
|
||||
|
||||
$html = '';
|
||||
foreach ($parentless as $item) {
|
||||
$html .= $this->renderParentlessItem($item);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert Sponsor-Information als HTML
|
||||
*/
|
||||
public function renderSponsor($sponsor): string
|
||||
{
|
||||
if (!$sponsor) {
|
||||
return '<div class="alert alert-warning">' . __('team.no_sponsor_assigned') . '</div>';
|
||||
}
|
||||
|
||||
return '<li class="dd-item dd-nodrag" data-id="">' .
|
||||
'<div class="dd-handle">' .
|
||||
$this->renderUserInfo($sponsor, false, true) .
|
||||
'</div>' .
|
||||
'</li>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert User Team Tree (für UserTeamCalcBot)
|
||||
*/
|
||||
public function renderUserTeamTree(array $teamMembers): string
|
||||
{
|
||||
if (empty($teamMembers)) {
|
||||
return '<div class="alert alert-info">Keine Team-Mitglieder gefunden.</div>';
|
||||
}
|
||||
|
||||
$html = '<ol class="dd-list">';
|
||||
foreach ($teamMembers as $member) {
|
||||
$html .= $this->renderTeamMemberItem($member, 0);
|
||||
}
|
||||
$html .= '</ol>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert User Sponsor (für UserTeamCalcBot)
|
||||
*/
|
||||
public function renderUserSponsor(\App\User $sponsor): string
|
||||
{
|
||||
if (!$sponsor || !$sponsor->account) {
|
||||
return '<div class="alert alert-info">Kein Sponsor gefunden.</div>';
|
||||
}
|
||||
|
||||
$html = '<div class="dd-item">';
|
||||
$html .= '<div class="dd-handle">';
|
||||
$html .= '<div class="row">';
|
||||
|
||||
// Sponsor Info
|
||||
$html .= '<div class="col-md-3">';
|
||||
$html .= '<strong>' . e($sponsor->account->first_name . ' ' . $sponsor->account->last_name) . '</strong><br>';
|
||||
$html .= '<small>' . e($sponsor->email) . '</small>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Account Info
|
||||
$html .= '<div class="col-md-2">';
|
||||
$html .= '<span class="badge badge-secondary">' . e($sponsor->account->m_account ?? '') . '</span>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Level Info
|
||||
$html .= '<div class="col-md-2">';
|
||||
if ($sponsor->user_level) {
|
||||
$html .= '<span class="badge badge-primary">' . e($sponsor->user_level->getLang('name')) . '</span>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
// Status
|
||||
$html .= '<div class="col-md-2">';
|
||||
$html .= get_active_badge($sponsor->isActiveAccount());
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert einzelnes Team-Mitglied
|
||||
*/
|
||||
private function renderTeamMemberItem($member, int $depth): string
|
||||
{
|
||||
$html = '<li class="dd-item" data-id="' . ($member->user_id ?? 0) . '">';
|
||||
$html .= '<div class="dd-handle">';
|
||||
$html .= '<div class="row">';
|
||||
|
||||
// Einrückung basierend auf Tiefe
|
||||
$indent = str_repeat(' ', $depth);
|
||||
|
||||
// Name und Email
|
||||
$html .= '<div class="col-md-3">';
|
||||
$html .= $indent;
|
||||
$html .= '<strong>' . e(($member->first_name ?? '') . ' ' . ($member->last_name ?? '')) . '</strong><br>';
|
||||
$html .= $indent . '<small>' . e($member->email ?? '') . '</small>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Account ID
|
||||
$html .= '<div class="col-md-2">';
|
||||
$html .= '<span class="badge badge-secondary">' . e($member->m_account ?? '') . '</span>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Level
|
||||
$html .= '<div class="col-md-2">';
|
||||
if (!empty($member->user_level_name)) {
|
||||
$html .= '<span class="badge badge-primary">' . e($member->user_level_name) . '</span>';
|
||||
|
||||
if ($member->next_qual_user_level) {
|
||||
$html .= '<span class="badge badge-outline-success ml-2"><i class="fa fa-arrow-up text-success" title="Karriere-Level erreicht!"></i></span>';
|
||||
}
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
// Qualifikation
|
||||
$html .= '<div class="col-md-2">';
|
||||
if (!empty($member->qual_kp)) {
|
||||
$pointsSum = (int) ($member->sales_volume_points_KP_sum ?? 0);
|
||||
$qualKP = (int) $member->qual_kp;
|
||||
$isQual = $pointsSum >= $qualKP;
|
||||
$badgeClass = $isQual ? 'badge-success' : 'badge-warning';
|
||||
$html .= '<span class="badge ' . $badgeClass . '">KU ' . $qualKP . '</span>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
// Status
|
||||
$html .= '<div class="col-md-2">';
|
||||
$html .= get_active_badge($member->active_account ?? 0);
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Kinder rendern
|
||||
if (!empty($member->businessUserItems) && is_array($member->businessUserItems)) {
|
||||
$html .= '<ol class="dd-list">';
|
||||
foreach ($member->businessUserItems as $child) {
|
||||
$html .= $this->renderTeamMemberItem($child, $depth + 1);
|
||||
}
|
||||
$html .= '</ol>';
|
||||
}
|
||||
|
||||
$html .= '</li>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert einen einzelnen User-Item mit Hierarchie
|
||||
*/
|
||||
private function renderUserItem($item, int $deep): string
|
||||
{
|
||||
$childrenHtml = '';
|
||||
if ($item->businessUserItems && count($item->businessUserItems) > 0) {
|
||||
$childrenHtml = '<ol class="dd-list dd-nodrag">';
|
||||
foreach ($item->businessUserItems as $child) {
|
||||
$childrenHtml .= $this->renderUserItem($child, $deep + 1);
|
||||
}
|
||||
$childrenHtml .= '</ol>';
|
||||
}
|
||||
|
||||
return '<li class="dd-item dd-nodrag" data-id="' . $item->user_id . '">' .
|
||||
'<div class="dd-handle">' .
|
||||
$this->renderUserCardWithDepth($item, $deep) .
|
||||
'</div>' .
|
||||
$childrenHtml .
|
||||
'</li>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert parentlosen User-Item
|
||||
*/
|
||||
private function renderParentlessItem($item): string
|
||||
{
|
||||
return '<li class="dd-item dd-nodrag" data-id="' . $item->user_id . '">' .
|
||||
'<div class="dd-handle">' .
|
||||
$this->renderUserInfo($item, true, false) .
|
||||
'</div>' .
|
||||
'</li>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert User-Card mit Tiefe-Anzeige
|
||||
*/
|
||||
private function renderUserCardWithDepth($item, int $deep): string
|
||||
{
|
||||
$depthBadge = '';
|
||||
if ($deep > 0) {
|
||||
$depthBadge = '<div class="d-flex flex-column justify-content-center align-items-center">' .
|
||||
'<div class="text-large font-weight-bolder line-height-1 my-2 text-secondary badge badge-outline-secondary">' . $deep . '</div>' .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
return '<div class="media align-items-center">' .
|
||||
$depthBadge .
|
||||
'<div class="media-body ml-2">' .
|
||||
$this->renderUserInfo($item, false, false) .
|
||||
'</div>' .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert die Basis-User-Informationen
|
||||
*/
|
||||
private function renderUserInfo($item, bool $showSponsor = false, bool $isSponsor = false): string
|
||||
{
|
||||
$statusClass = $item->active_account ? '' : 'text-muted';
|
||||
$iconClass = $item->active_account ? 'text-primary' : 'text-danger';
|
||||
|
||||
\Log::debug("TreeHtmlRenderer: Rendering user info for user {$item->user_id}");
|
||||
|
||||
$html = '<span class="' . $statusClass . '">';
|
||||
|
||||
// User Link
|
||||
$html .= '<a href="#" class="text-black" data-toggle="modal" data-target="#modals-load-content" ' .
|
||||
'data-id="' . $item->user_id . '" data-action="business-user-show" data-back="" ' .
|
||||
'data-modal="modal-md" data-init_from="' . $this->initFrom . '" data-route="' . route('modal_load') . '">' .
|
||||
'<span class="mr-1 ion ion-ios-contact ' . $iconClass . '"></span> ' .
|
||||
'<strong>' . e($item->first_name . ' ' . $item->last_name) . '</strong>' .
|
||||
'</a>';
|
||||
|
||||
// Email
|
||||
$html .= ' <a href="mailto:' . e($item->email) . '">' . e($item->email) . '</a>';
|
||||
|
||||
// Optional: Geburtstag
|
||||
$birthday = $item->user_birthday; // Magic Method __get() verwenden
|
||||
if ($birthday && trim($birthday) !== '') {
|
||||
$html .= ' | <i class="ion ion-ios-gift text-primary"></i> ' . e($birthday);
|
||||
}
|
||||
|
||||
// Optional: Telefon
|
||||
$phone = $item->user_phone; // Magic Method __get() verwenden
|
||||
if ($phone && trim($phone) !== '') {
|
||||
$html .= ' | <i class="ion ion-ios-call text-primary"></i> ' . e($phone);
|
||||
}
|
||||
|
||||
// Level Badge
|
||||
$levelName = $item->user_level_name ? TranslationHelper::transUserLevelName($item->user_level_name) : '';
|
||||
$account = $item->m_account ?: '';
|
||||
$html .= ' <span class="badge badge-outline-default ' . $statusClass . '">' . e($levelName . ' | ' . $account) . '</span>';
|
||||
|
||||
// Karriere-Aufstiegs-Icon für qualifizierte User (nur in Struktur-Ansicht)#
|
||||
|
||||
if ($item->next_qual_user_level) {
|
||||
$html .= '<span class="badge badge-outline-success ml-2"><i class="fa fa-arrow-up text-success" title="Karriere-Level erreicht!"></i></span>';
|
||||
}
|
||||
|
||||
// Details für aktive Accounts
|
||||
if ($item->active_account) {
|
||||
$html .= '<br><span class="small">';
|
||||
if(!$isSponsor){
|
||||
$html .= $this->renderAccountDetails($item);
|
||||
}
|
||||
|
||||
// Action Button (außer für Sponsor-Ansicht)
|
||||
if (!$isSponsor && $this->shouldShowActionButton()) {
|
||||
$html .= $this->renderActionButton($item->user_id);
|
||||
}
|
||||
|
||||
$html .= '</span>';
|
||||
} else {
|
||||
// Inaktive Accounts
|
||||
$paymentDate = $item->payment_account_date ?: '';
|
||||
$html .= '<br><span class="small">' . __('team.account_to') . ': ' . e($paymentDate) . '</span>';
|
||||
}
|
||||
|
||||
// Sponsor für parentlose User
|
||||
if ($showSponsor && $item->m_sponsor_name) {
|
||||
$html .= '<br>' . e($item->m_sponsor_name);
|
||||
}
|
||||
|
||||
$html .= '</span>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert Account-Details (Punkte, Umsatz)
|
||||
*/
|
||||
private function renderAccountDetails($item): string
|
||||
{
|
||||
$totalPoints = $item->sales_volume_points_KP_sum ?: 0;
|
||||
$ePoints = $item->sales_volume_KP_points ?: 0;
|
||||
$sPoints = $item->sales_volume_points_shop ?: 0;
|
||||
|
||||
$totalSum = $item->sales_volume_total_sum ?: 0;
|
||||
$eSum = $item->sales_volume_total ?: 0;
|
||||
$sSum = $item->sales_volume_total_shop ?: 0;
|
||||
|
||||
return '<strong>' . __('team.total_points') . ': ' . $totalPoints . '</strong> | ' .
|
||||
__('team.e') . ': ' . $ePoints . ' | ' .
|
||||
__('team.s') . ': ' . $sPoints . ' <strong> | ' .
|
||||
__('team.net_turnover') . ': ' . formatNumber($totalSum) . ' €</strong> | ' .
|
||||
__('team.e') . ': ' . formatNumber($eSum) . ' € | ' .
|
||||
__('team.s') . ': ' . formatNumber($sSum) . ' €';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert Action-Button für User-Details
|
||||
*/
|
||||
private function renderActionButton(int $userId): string
|
||||
{
|
||||
return ' | <button type="button" class="btn icon-btn btn-xs btn-secondary" ' .
|
||||
'data-toggle="modal" data-target="#modals-load-content" ' .
|
||||
'data-id="' . $userId . '" data-action="business-user-detail" ' .
|
||||
'data-back="" data-modal="modal-xl" ' .
|
||||
'data-init_from="' . $this->initFrom . '" ' .
|
||||
'data-live="' . $this->forceLiveCalculation . '" ' .
|
||||
'data-optimized="1" ' .
|
||||
'data-route="' . route('modal_load') . '">' .
|
||||
'<span class="fa fa-calculator"></span>' .
|
||||
'</button>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob Action-Button angezeigt werden soll
|
||||
*/
|
||||
private function shouldShowActionButton(): bool
|
||||
{
|
||||
try {
|
||||
return ($this->initFrom === 'admin' && \Auth::check() && \Auth::user()->isAdmin()) ||
|
||||
($this->initFrom === 'member');
|
||||
} catch (\Exception $e) {
|
||||
// Fallback for tests or when no user is authenticated
|
||||
return $this->initFrom === 'member';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Kontext für die Darstellung
|
||||
*/
|
||||
public function setInitFrom(string $initFrom): self
|
||||
{
|
||||
$this->initFrom = $initFrom;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
<?php
|
||||
namespace App\Services\BusinessPlan;
|
||||
|
||||
use App\User;
|
||||
use stdClass;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserBusiness;
|
||||
|
||||
|
||||
class TreeUserItem
|
||||
{
|
||||
public $items = [];
|
||||
private $date;
|
||||
public $lines = [];
|
||||
|
||||
public $user_level_active;
|
||||
|
||||
|
||||
|
||||
public function __construct($date)
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function makeUser(User $user){
|
||||
|
||||
$this->user_level_active = $user->user_level ? $user->user_level : null;
|
||||
$this->b_user = new UserBusiness();
|
||||
$fill = [
|
||||
'user_id' => $user->id,
|
||||
'month' => $this->date->month,
|
||||
'year' => $this->date->year,
|
||||
'm_level' => $user->m_level,
|
||||
'm_sponsor' => $user->m_sponsor,
|
||||
'user_level_name' => $user->user_level ? $user->user_level->name : '',
|
||||
'active_account' => $user->payment_account ? Carbon::parse($user->payment_account)->gt(Carbon::parse($this->date->start_date)) : false,
|
||||
'payment_account_date' => $user->payment_account ? $user->getPaymentAccountDateFormat(false) : NULL,
|
||||
'active_date' => $user->active_date ? $user->active_date : NULL,
|
||||
'm_account' => $user->account->m_account,
|
||||
'email' => $user->email,
|
||||
'first_name' => $user->account->first_name,
|
||||
'last_name' => $user->account->last_name,
|
||||
'sales_volume_points' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_points'),
|
||||
'sales_volume_points_shop' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_points_shop'),
|
||||
'sales_volume_points_sum' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_points_sum'),
|
||||
'sales_volume_total' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_total'),
|
||||
'sales_volume_total_shop' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_total_shop'),
|
||||
'sales_volume_total_sum' => $user->getUserSalesVolumeBy($this->date->month, $this->date->year, 'sales_volume_total_sum'),
|
||||
'margin' => $user->user_level_active ? $user->user_level_active->margin : 0,
|
||||
'margin_shop' => $user->user_level_active ? $user->user_level_active->margin_shop : 0,
|
||||
'qual_kp' => $user->user_level_active ? $user->user_level_active->qual_kp : 0,
|
||||
'qual_tp' => $user->user_level_active ? $user->user_level_active->qual_tp : 0,
|
||||
];
|
||||
$this->b_user->fill($fill);
|
||||
}
|
||||
|
||||
public function addUserID(){
|
||||
TreeCalcBot::addUserID($this->user_id);
|
||||
}
|
||||
|
||||
public function isQualKP(){
|
||||
return ($this->sales_volume_points_sum >= $this->qual_kp) ? true : false;
|
||||
}
|
||||
|
||||
public function getRestQualKP(){
|
||||
return $this->sales_volume_points_sum - $this->qual_kp;
|
||||
}
|
||||
|
||||
public function checkSponsor(){
|
||||
if($this->m_sponsor === null){
|
||||
$this->m_sponsor_name = 'Keinen Sponsor zugewiesen';
|
||||
return;
|
||||
}
|
||||
$user = User::find($this->m_sponsor);
|
||||
if($user){
|
||||
if($user->account){
|
||||
$this->m_sponsor_name = substr('Sponsor: '.$user->account->first_name.' '.$user->account->last_name.' | '.$user->email.' | '.$user->account->m_account, 0, 190);
|
||||
}else{
|
||||
$this->m_sponsor_name = 'Sponsor: '.$user->email;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->m_sponsor_name = 'Sponsor wurde gelöscht.';
|
||||
return;
|
||||
}
|
||||
/*
|
||||
|
||||
'total_tp' => ,
|
||||
'total_qual_tp' => ,
|
||||
'commission_total' => ,
|
||||
'lines',
|
||||
'items',
|
||||
'qual_user_level_id'
|
||||
*/
|
||||
public function readParentsUser(){
|
||||
|
||||
$users = User::with('account')->select('users.*')
|
||||
->where('users.deleted_at', '=', null)
|
||||
->where('users.id', '!=', 1)
|
||||
->where('users.admin', "<", 4)
|
||||
->where('users.m_level', "!=", null)
|
||||
->where('users.m_sponsor', "=", $this->user_id) //<- need the id for parents / sponsors
|
||||
->where('users.payment_account', "!=", null)
|
||||
->where('users.active_date', "<=", $this->date->end_date)
|
||||
->get();
|
||||
dd($users);
|
||||
if($users){
|
||||
foreach($users as $user){
|
||||
$TreeUserItem = new TreeUserItem($this->date);
|
||||
$TreeUserItem->makeUser($user);
|
||||
$TreeUserItem->addUserID();
|
||||
$this->items[] = $TreeUserItem;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->items as $item){
|
||||
$item->readParentsUser();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function calcUserTP($line){
|
||||
if(!isset($this->lines[$line])){
|
||||
$this->lines[$line] = new stdClass();
|
||||
$this->lines[$line]->points = 0;
|
||||
}
|
||||
foreach($this->items as $item){
|
||||
if(count($item->items) > 0){
|
||||
$this->calcUserTP($line+1);
|
||||
}
|
||||
$this->lines[$line]->points += $item->sales_volume_points_sum;
|
||||
$this->total_tp += $item->sales_volume_points_sum;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function calcQualTP(){
|
||||
if($this->isQualKP()){
|
||||
$this->total_qual_tp = $this->total_tp + $this->getRestQualKP();
|
||||
$this->qual_user_level = UserLevel::where('qual_tp', '<=', $this->total_qual_tp)->where('pos', '<=', $this->user->user_level->pos)->orderBy('qual_tp', 'desc')->first();
|
||||
$this->commission_total = 0;
|
||||
if($this->qual_user_level){
|
||||
foreach($this->lines as $line => $values){
|
||||
$values->margin = $this->qual_user_level->{'pr_line_'.$line};
|
||||
if($line > 6){
|
||||
//wachstumsbonus
|
||||
$values->margin = $this->qual_user_level->growth_bonus;
|
||||
}
|
||||
$values->commission = round($values->points / 100 * $values->margin, 2);
|
||||
|
||||
$this->commission_total += $values->commission;
|
||||
$this->lines[$line] = $values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function __get($property) {
|
||||
if (property_exists($this->b_user, $property)) {
|
||||
return $this->b_user->$property;
|
||||
}
|
||||
if (isset($this->b_user->{$property})) {
|
||||
return $this->b_user->{$property};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
296
app/Services/DomainService.php
Normal file
296
app/Services/DomainService.php
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\UserShop;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Domain Service - Centralized domain and subdomain management
|
||||
*
|
||||
* This service provides a centralized way to handle domain resolution,
|
||||
* subdomain validation, and domain-specific configuration management.
|
||||
*/
|
||||
class DomainService
|
||||
{
|
||||
private const CACHE_TTL = 3600; // 1 hour
|
||||
private const CACHE_TAG_USER_SHOPS = 'user_shops';
|
||||
private const CACHE_TAG_DOMAIN_PARSING = 'domain_parsing';
|
||||
private const FIXED_SUBDOMAINS = ['my', 'in', 'checkout'];
|
||||
|
||||
private array $domainConfig;
|
||||
|
||||
public function __construct(?array $domainConfig = null)
|
||||
{
|
||||
$this->domainConfig = $domainConfig ?? config('domains');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the type of subdomain
|
||||
*/
|
||||
public function getSubdomainType(string $subdomain): string
|
||||
{
|
||||
// Frühe Validierung: Prüfe reservierte Subdomains aus Konfiguration
|
||||
$reservedSubdomains = $this->domainConfig['reserved_subdomains'] ?? self::FIXED_SUBDOMAINS;
|
||||
|
||||
if (in_array($subdomain, $reservedSubdomains)) {
|
||||
return match($subdomain) {
|
||||
'my' => 'crm',
|
||||
'in' => 'portal',
|
||||
'checkout' => 'checkout',
|
||||
default => 'unknown' // Andere reservierte Subdomains sind ungültig
|
||||
};
|
||||
}
|
||||
|
||||
// Frühe Validierung: Prüfe auf ungültige Zeichen für UserShop-Slugs
|
||||
if (!preg_match('/^[a-z0-9-]+$/', $subdomain) || strlen($subdomain) < 3) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// Check if it's a valid user shop
|
||||
if ($this->isValidUserShop($subdomain)) {
|
||||
return 'user-shop';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a subdomain represents a valid user shop
|
||||
*/
|
||||
public function isValidUserShop(string $slug): bool
|
||||
{
|
||||
$cacheKey = "user_shop_valid_{$slug}";
|
||||
|
||||
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($slug) {
|
||||
// Optimierte Query mit allen Validierungen in einem DB-Call
|
||||
$userShop = UserShop::where('slug', $slug)
|
||||
->where('active', true)
|
||||
->whereHas('user', function ($query) {
|
||||
$query->whereNotNull('payment_shop')
|
||||
->where('payment_shop', '>', now());
|
||||
})
|
||||
->exists();
|
||||
|
||||
return $userShop;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user shop by slug with caching
|
||||
*/
|
||||
public function getUserShop(string $slug): ?UserShop
|
||||
{
|
||||
$cacheKey = "user_shop_{$slug}";
|
||||
|
||||
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($slug) {
|
||||
// Optimierte Query mit allen Validierungen in einem DB-Call
|
||||
return UserShop::where('slug', $slug)
|
||||
->where('active', true)
|
||||
->whereHas('user', function ($query) {
|
||||
$query->whereNotNull('payment_shop')
|
||||
->where('payment_shop', '>', now());
|
||||
})
|
||||
->with('user')
|
||||
->first();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse domain from request and determine context
|
||||
*/
|
||||
public function parseDomain(string $host): array
|
||||
{
|
||||
// Normalisiere den Host (lowercase)
|
||||
$host = strtolower(trim($host));
|
||||
$parts = explode('.', $host);
|
||||
|
||||
// Handle different TLD scenarios
|
||||
if (count($parts) < 2) {
|
||||
\Log::warning('Invalid host format', ['host' => $host]);
|
||||
return [
|
||||
'type' => 'invalid',
|
||||
'domain' => $host,
|
||||
'subdomain' => null,
|
||||
'tld' => null,
|
||||
'host' => $host
|
||||
];
|
||||
}
|
||||
|
||||
// Extract TLD and domain
|
||||
$tld = '.' . end($parts);
|
||||
$domain = $parts[count($parts) - 2];
|
||||
|
||||
// Check for subdomain
|
||||
$subdomain = null;
|
||||
if (count($parts) > 2) {
|
||||
$subdomain = $parts[0];
|
||||
\Log::debug('DomainService: Using extracted subdomain', ['subdomain' => $subdomain, 'host' => $host]);
|
||||
}
|
||||
|
||||
// Determine domain type based on subdomain and host
|
||||
$type = $this->determineDomainType($host, $subdomain);
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'domain' => $domain,
|
||||
'subdomain' => $subdomain,
|
||||
'tld' => $tld,
|
||||
'host' => $host,
|
||||
'default_user_shop' => $this->domainConfig['domains']['shop']['default_user_shop'] ?? null
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine domain type based on full host and subdomain
|
||||
*/
|
||||
private function determineDomainType(string $host, ?string $subdomain): string
|
||||
{
|
||||
|
||||
|
||||
// Check against configured domains
|
||||
foreach ($this->domainConfig['domains'] as $type => $config) {
|
||||
if (isset($config['host'])) {
|
||||
// Handle wildcard user-shop pattern
|
||||
if ($type === 'user-shop') {
|
||||
$pattern = str_replace('{subdomain}', '([a-z0-9-]+)', $config['host']);
|
||||
if (preg_match("/^{$pattern}$/", $host)) {
|
||||
return 'user-shop';
|
||||
}
|
||||
} else {
|
||||
// Exact match for other domains
|
||||
if ($host === $config['host']) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional check for subdomain-based detection
|
||||
if ($subdomain) {
|
||||
$subdomainType = $this->getSubdomainType($subdomain);
|
||||
if ($subdomainType !== 'unknown') {
|
||||
return $subdomainType;
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URL for specific domain type
|
||||
*/
|
||||
public function buildUrl(string $type, ?string $path = null, ?string $slug = null): string
|
||||
{
|
||||
$protocol = $this->domainConfig['protocol'] ?? 'https://';
|
||||
|
||||
$domainConfig = $this->domainConfig['domains'][$type] ?? null;
|
||||
|
||||
if (!$domainConfig) {
|
||||
throw new \InvalidArgumentException("Unknown domain type: {$type}");
|
||||
}
|
||||
|
||||
$host = $domainConfig['host'];
|
||||
|
||||
// Handle user-shop wildcard
|
||||
if ($type === 'user-shop') {
|
||||
if (!$slug) {
|
||||
throw new \InvalidArgumentException('Slug required for user-shop URLs');
|
||||
}
|
||||
$host = str_replace('{subdomain}', $slug, $host);
|
||||
}
|
||||
|
||||
$url = $protocol . $host;
|
||||
|
||||
if ($path) {
|
||||
$url .= '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get domain configuration
|
||||
*/
|
||||
public function getDomainConfiguration(): array
|
||||
{
|
||||
return $this->domainConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear user shop cache
|
||||
*/
|
||||
public function clearUserShopCache(string $slug): void
|
||||
{
|
||||
Cache::forget("user_shop_valid_{$slug}");
|
||||
Cache::forget("user_shop_{$slug}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all user shop caches
|
||||
*/
|
||||
public function clearAllUserShopCaches(): void
|
||||
{
|
||||
// In Laravel mit Cache-Tags würde das eleganter funktionieren
|
||||
// Für jetzt eine einfache Lösung für häufig verwendete Shops
|
||||
$commonSlugs = ['aloevera']; // Füge häufig verwendete Slugs hinzu
|
||||
|
||||
foreach ($commonSlugs as $slug) {
|
||||
$this->clearUserShopCache($slug);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default user shop for main domain (fallback)
|
||||
*/
|
||||
public function getDefaultUserShop(): ?UserShop
|
||||
{
|
||||
$defaultSlug = $this->domainConfig['domains']['shop']['default_user_shop'] ?? 'aloevera';
|
||||
return $this->getUserShop($defaultSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate domain configuration
|
||||
*/
|
||||
public function validateConfiguration(): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Validate main domains
|
||||
$requiredDomains = ['main', 'shop', 'crm', 'portal', 'checkout', 'user-shop'];
|
||||
foreach ($requiredDomains as $domain) {
|
||||
if (empty($this->domainConfig['domains'][$domain]['host'])) {
|
||||
$errors[] = "Domain '{$domain}' not configured";
|
||||
}
|
||||
}
|
||||
|
||||
// Validate protocol
|
||||
if (empty($this->domainConfig['protocol'])) {
|
||||
$errors[] = 'Protocol not configured';
|
||||
}
|
||||
|
||||
// Validate reserved subdomains
|
||||
if (empty($this->domainConfig['reserved_subdomains'])) {
|
||||
$errors[] = 'Reserved subdomains not configured';
|
||||
}
|
||||
|
||||
// Validate shop default
|
||||
$defaultShop = $this->domainConfig['domains']['shop']['default_user_shop'] ?? null;
|
||||
if (!$defaultShop) {
|
||||
$errors[] = 'Default user shop not configured for shop domain';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if domain configuration is valid
|
||||
*/
|
||||
public function isConfigurationValid(): bool
|
||||
{
|
||||
return empty($this->validateConfiguration());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ class HTMLHelper
|
|||
|
||||
|
||||
private static $roles = [
|
||||
0 => 'Kunde',
|
||||
0 => 'Berater',
|
||||
1 => 'VIP',
|
||||
2 => 'Admin',
|
||||
3 => 'SuperAdmin',
|
||||
|
|
@ -314,7 +314,7 @@ class HTMLHelper
|
|||
}
|
||||
|
||||
public static function getSalutation($id){
|
||||
$values = array('mr' => __('MR'), 'ms' => __('MS'));
|
||||
$values = array('mr' => __('MR'), 'ms' => __('MS'), 'di' => __('DIV'));
|
||||
$ret = "";
|
||||
$ret .= '<option value="">'.__('please select').'</option>\n';
|
||||
foreach ($values as $key => $value){
|
||||
|
|
@ -325,7 +325,7 @@ class HTMLHelper
|
|||
}
|
||||
|
||||
public static function getSalutationLang($id){
|
||||
$values = array('mr' => __('MR'), 'ms' => __('MS'));
|
||||
$values = array('mr' => __('MR'), 'ms' => __('MS'), 'di' => __('DIV'));
|
||||
return (!empty($values[$id]) ? $values[$id] : '');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
//use FPDI in myMerge v2
|
||||
//use FPDF;
|
||||
//use FPDI;
|
||||
|
||||
class MyPDFMerger
|
||||
{
|
||||
private $_files; //['form.pdf'] ["1,2,4, 5-19"]
|
||||
private $_fpdi;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
/* if(!class_exists("FPDF")) {
|
||||
require_once(__DIR__.'/fpdf/fpdf.php');
|
||||
}
|
||||
if(!class_exists("FPDI")) {
|
||||
require_once(__DIR__.'/fpdi/fpdi.php');
|
||||
}*/
|
||||
}
|
||||
|
||||
public function addPDF($filepath, $pages = 'all')
|
||||
{
|
||||
if (file_exists($filepath)) {
|
||||
if (strtolower($pages) != 'all') {
|
||||
$pages = $this->_rewritepages($pages);
|
||||
}
|
||||
|
||||
$this->_files[] = array($filepath, $pages);
|
||||
} else {
|
||||
throw new \exception("Could not locate PDF on '$filepath'");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function myMerge($outputmode = 'browser', $outputpath = 'newfile.pdf', $theme = false)
|
||||
{
|
||||
if (!isset($this->_files) || !is_array($this->_files)): throw new \exception("No PDFs to merge."); endif;
|
||||
|
||||
$fpdi = new \setasign\Fpdi\Fpdi();
|
||||
$first = 1;
|
||||
|
||||
//
|
||||
//merger operations
|
||||
foreach ($this->_files as $file) {
|
||||
$filename = $file[0];
|
||||
$filepages = $file[1];
|
||||
|
||||
|
||||
$count = $fpdi->setSourceFile($filename);
|
||||
|
||||
//add the pages
|
||||
if ($filepages == 'all') {
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
$count = $fpdi->setSourceFile($filename);
|
||||
$template = $fpdi->importPage($i);
|
||||
$size = $fpdi->getTemplateSize($template);
|
||||
$orientation = ($size['height'] > $size['width']) ? 'P' : 'L';
|
||||
|
||||
$fpdi->AddPage($orientation, array($size['width'], $size['height']));
|
||||
if($theme){
|
||||
$fpdi->setSourceFile(__DIR__ . '/../../public/pdf/'.$theme.'-'.$first.'.pdf');
|
||||
if($first == 1){
|
||||
$first = 2;
|
||||
}
|
||||
$backId = $fpdi->importPage(1);
|
||||
$fpdi->useTemplate($backId);
|
||||
|
||||
}
|
||||
$fpdi->useTemplate($template);
|
||||
}
|
||||
} else {
|
||||
foreach ($filepages as $page) {
|
||||
$count = $fpdi->setSourceFile($filename);
|
||||
if (!$template = $fpdi->importPage($page)): throw new \exception("Could not load page '$page' in PDF '$filename'. Check that the page exists."); endif;
|
||||
$size = $fpdi->getTemplateSize($template);
|
||||
$orientation = ($size['h'] > $size['w']) ? 'P' : 'L';
|
||||
|
||||
$fpdi->AddPage($orientation, array($size['w'], $size['h']));
|
||||
if($theme){
|
||||
$fpdi->setSourceFile(__DIR__ . '/../../public/pdf/'.$theme.'-'.$first.'.pdf');
|
||||
if($first == 1){
|
||||
$first = 2;
|
||||
}
|
||||
$backId = $fpdi->importPage(1);
|
||||
$fpdi->useTemplate($backId);
|
||||
}
|
||||
|
||||
$fpdi->useTemplate($template);
|
||||
}
|
||||
}
|
||||
//after first file (invoice) on bpaper
|
||||
$slug = false;
|
||||
}
|
||||
|
||||
//output operations
|
||||
$mode = $this->_switchmode($outputmode);
|
||||
|
||||
if ($mode == 'S') {
|
||||
return $fpdi->Output($outputpath, 'S');
|
||||
} else {
|
||||
if ($fpdi->Output($outputpath, $mode) == '') {
|
||||
return true;
|
||||
} else {
|
||||
throw new \exception("Error outputting PDF to '$outputmode'.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* FPDI uses single characters for specifying the output location. Change our more descriptive string into proper format.
|
||||
* @param $mode
|
||||
* @return Character
|
||||
*/
|
||||
private function _switchmode($mode)
|
||||
{
|
||||
switch (strtolower($mode)) {
|
||||
case 'download':
|
||||
return 'D';
|
||||
break;
|
||||
case 'browser':
|
||||
return 'I';
|
||||
break;
|
||||
case 'file':
|
||||
return 'F';
|
||||
break;
|
||||
case 'string':
|
||||
return 'S';
|
||||
break;
|
||||
default:
|
||||
return 'I';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes our provided pages in the form of 1,3,4,16-50 and creates an array of all pages
|
||||
* @param $pages
|
||||
* @return array
|
||||
* @throws exception
|
||||
*/
|
||||
private function _rewritepages($pages)
|
||||
{
|
||||
$pages = str_replace(' ', '', $pages);
|
||||
$part = explode(',', $pages);
|
||||
|
||||
//parse hyphens
|
||||
foreach ($part as $i) {
|
||||
$ind = explode('-', $i);
|
||||
|
||||
if (count($ind) == 2) {
|
||||
$x = $ind[0]; //start page
|
||||
$y = $ind[1]; //end page
|
||||
|
||||
if ($x > $y): throw new \exception("Starting page, '$x' is greater than ending page '$y'.");
|
||||
return false; endif;
|
||||
|
||||
//add middle pages
|
||||
while ($x <= $y): $newpages[] = (int)$x;
|
||||
$x++; endwhile;
|
||||
} else {
|
||||
$newpages[] = (int)$ind[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $newpages;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
$pdf = new PDFMerger;
|
||||
|
||||
$pdf->addPDF('samplepdfs/one.pdf', '1, 3, 4')
|
||||
->addPDF('samplepdfs/two.pdf', '1-2')
|
||||
->addPDF('samplepdfs/three.pdf', 'all')
|
||||
->merge('file', 'samplepdfs/TEST2.pdf');
|
||||
|
||||
//REPLACE 'file' WITH 'browser', 'download', 'string', or 'file' for output options
|
||||
//You do not need to give a file path for browser, string, or download - just the name.
|
||||
*/
|
||||
125
app/Services/NextLevelBadgeHelper.php
Normal file
125
app/Services/NextLevelBadgeHelper.php
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\UserBusiness;
|
||||
|
||||
/**
|
||||
* Helper-Klasse für die optimierte Generierung von Next-Level-Badges
|
||||
*
|
||||
* Diese Klasse nutzt ausschließlich bereits berechnete und gespeicherte Daten
|
||||
* aus der UserBusiness-Tabelle, anstatt für jeden User eine neue TreeCalcBot-Instanz
|
||||
* zu erstellen. Dies führt zu erheblichen Performance-Verbesserungen bei DataTables.
|
||||
*/
|
||||
class NextLevelBadgeHelper
|
||||
{
|
||||
/**
|
||||
* Generiert Badge für nächsten Level Qualifikation basierend auf UserBusiness-Daten
|
||||
*
|
||||
* @param UserBusiness $userBusiness Bereits gespeicherte Business-Daten
|
||||
* @return string HTML Badge
|
||||
*/
|
||||
public static function generateBadgeFromUserBusiness(UserBusiness $userBusiness): string
|
||||
{
|
||||
return self::renderBadge($userBusiness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Badge basierend auf BusinessUser-Objekt (für TreeCalcBot-Kompatibilität)
|
||||
*
|
||||
* @param object $businessUser Business-User-Objekt mit Level-Daten
|
||||
* @return string HTML Badge
|
||||
*/
|
||||
public static function generateBadgeFromBusinessUser($businessUser): string
|
||||
{
|
||||
return self::renderBadge($businessUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zentrale Badge-Rendering-Logik
|
||||
*
|
||||
* @param mixed $source UserBusiness Model oder BusinessUser Objekt
|
||||
* @return string HTML Badge
|
||||
*/
|
||||
private static function renderBadge($source): string
|
||||
{
|
||||
// Prüfe ob User für den nächsten Level qualifiziert ist (grün)
|
||||
if (!empty($source->next_qual_user_level)) {
|
||||
return self::renderQualifiedBadge($source);
|
||||
}
|
||||
|
||||
// Prüfe ob User den Level erreichen könnte, aber noch nicht qualifiziert ist (gelb)
|
||||
if (!empty($source->next_can_user_level)) {
|
||||
return self::renderCanReachBadge($source);
|
||||
}
|
||||
|
||||
// Kein nächster Level verfügbar (rot)
|
||||
return self::renderNoLevelBadge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert Badge für qualifizierte User (grün)
|
||||
*/
|
||||
private static function renderQualifiedBadge($source): string
|
||||
{
|
||||
$level = $source->next_qual_user_level;
|
||||
$ku = formatNumber($source->sales_volume_points_KP_sum ?? 0, 0);
|
||||
$ku_required = formatNumber($level['qual_kp'] ?? 0, 0);
|
||||
$tp = formatNumber($source->payline_points_qual_kp ?? 0, 0);
|
||||
$tp_required = formatNumber($level['qual_pp'] ?? 0, 0);
|
||||
$levelName = TranslationHelper::transUserLevelName($level['name'] ?? 'Unbekannt');
|
||||
|
||||
return '<span class="badge badge-outline-success" title="Qualifiziert für nächsten Level">
|
||||
<i class="fa fa-check"></i> ' . e($levelName) . '<br/>
|
||||
<small>KU: ' . $ku . '/' . $ku_required . ' | TP: ' . $tp . '/' . $tp_required . '</small>
|
||||
</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert Badge für User die den Level erreichen könnten (gelb)
|
||||
*/
|
||||
private static function renderCanReachBadge($source): string
|
||||
{
|
||||
$level = $source->next_can_user_level;
|
||||
$ku = formatNumber($source->sales_volume_points_KP_sum ?? 0, 0);
|
||||
$ku_required = formatNumber($level['qual_kp'] ?? 0, 0);
|
||||
$tp = formatNumber($source->payline_points_qual_kp ?? 0, 0);
|
||||
$tp_required = formatNumber($level['qual_pp'] ?? 0, 0);
|
||||
$levelName = TranslationHelper::transUserLevelName($level['name'] ?? 'Unbekannt');
|
||||
|
||||
return '<span class="badge badge-outline-warning-dark" title="Noch nicht qualifiziert">
|
||||
<i class="fa fa-clock"></i> ' . e($levelName) . '<br/>
|
||||
<small>KU: ' . $ku . '/' . $ku_required . ' | TP: ' . $tp . '/' . $tp_required . '</small>
|
||||
</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert Badge wenn kein nächster Level verfügbar ist (rot)
|
||||
*/
|
||||
private static function renderNoLevelBadge(): string
|
||||
{
|
||||
return '<span class="badge badge-outline-warning" title="Kein nächster Level verfügbar">
|
||||
<i class="fa fa-times"></i>
|
||||
</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback-Badge bei Fehlern oder fehlenden Daten
|
||||
*/
|
||||
public static function renderErrorBadge(string $message = 'Fehler bei der Berechnung'): string
|
||||
{
|
||||
return '<span class="badge badge-outline-danger" title="' . e($message) . '">
|
||||
<i class="fa fa-exclamation"></i> Fehler
|
||||
</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Badge für fehlende Daten
|
||||
*/
|
||||
public static function renderNoDataBadge(): string
|
||||
{
|
||||
return '<span class="badge badge-outline-secondary" title="Keine Daten verfügbar">
|
||||
<i class="fa fa-question"></i> Keine Daten
|
||||
</span>';
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use App\Models\Country;
|
|||
use App\Models\Product;
|
||||
use App\Models\Setting;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingInstance;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Services\dbip\MyDBIP;
|
||||
use App\Services\IPinfo\IPinfo;
|
||||
|
|
@ -46,7 +47,7 @@ class Shop
|
|||
}
|
||||
|
||||
|
||||
public static function getLangChange()
|
||||
public static function getLangChange($instance = 'shopping')
|
||||
{
|
||||
$ret = [];
|
||||
$countries = Country::whereActive(true)->whereSwitch(true)->get();
|
||||
|
|
@ -60,11 +61,11 @@ class Shop
|
|||
$ret[strtolower($country->code)] = $country;
|
||||
}
|
||||
}
|
||||
Shop::getUserShopLang($first_country);
|
||||
Shop::getUserShopLang($first_country, $instance);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function getUserShopLang($country = null)
|
||||
public static function getUserShopLang($country = null, $instance = 'shopping')
|
||||
{
|
||||
if (\Session::has('user_shop_lang')) {
|
||||
if ($user_shop_lang = \Session::get('user_shop_lang')) {
|
||||
|
|
@ -72,21 +73,23 @@ class Shop
|
|||
}
|
||||
}
|
||||
if ($country) {
|
||||
Shop::initUserShopLang($country);
|
||||
Shop::initUserShopLang($country, $instance);
|
||||
return strtolower($country->code);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function initUserShopLang($country)
|
||||
//init User Shop Lang for Webshop
|
||||
public static function initUserShopLang($country, $instance = 'shopping')
|
||||
{
|
||||
Yard::instance('shopping')->destroy();
|
||||
Yard::instance($instance)->destroy();
|
||||
\Session::put('user_shop_lang', strtolower($country->code));
|
||||
//init Yard
|
||||
self::initUserShopYard($country);
|
||||
self::initUserShopYard($country, $instance);
|
||||
}
|
||||
|
||||
public static function initUserShopYard($country)
|
||||
//init Yard for user shop Webshop
|
||||
public static function initUserShopYard($country, $instance = 'shopping')
|
||||
{
|
||||
//Lieferadresse im Drittland?
|
||||
self::$user_tax_free = $country->supply_country ? true : false;
|
||||
|
|
@ -95,8 +98,8 @@ class Shop
|
|||
self::$shipping_country = $ShippingCountry;
|
||||
self::$user_country = $country;
|
||||
|
||||
Yard::instance('shopping')->setShippingCountryWithPrice($ShippingCountry->id);
|
||||
Yard::instance('shopping')->setUserPriceInfos(Shop::getShopYardInfo());
|
||||
Yard::instance($instance)->setShippingCountryWithPrice($ShippingCountry->id);
|
||||
Yard::instance($instance)->setUserPriceInfos(Shop::getShopYardInfo());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -126,6 +129,23 @@ class Shop
|
|||
}
|
||||
return $shopping_user;
|
||||
}
|
||||
|
||||
//prüfe ob checkout bereits gestartet wurde, und wenn ja, dann lösche die Instanz
|
||||
public static function deleteCheckoutInstance(){
|
||||
|
||||
|
||||
if(Yard::instance('checkout')->count() > 0){
|
||||
Yard::instance('checkout')->destroy();
|
||||
}
|
||||
if(\Session::has('user_shop_identifier')){
|
||||
ShoppingInstance::where('identifier', \Session::get('user_shop_identifier'))->delete();
|
||||
\Session::forget('user_shop_identifier');
|
||||
}
|
||||
\Session::forget('user_shop_payment');
|
||||
\Session::forget('auth_user');
|
||||
\Session::forget('back_link');
|
||||
\Session::forget('new_session');
|
||||
}
|
||||
|
||||
public static function checkShoppingCountry($for, $id = null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use Yard;
|
||||
use App\User;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\User;
|
||||
use Illuminate\Support\Str;
|
||||
use Yard;
|
||||
|
||||
class UserService
|
||||
{
|
||||
|
|
@ -12,7 +13,7 @@ class UserService
|
|||
public static $shipping_free = false;
|
||||
public static $user_tax_free = false;
|
||||
public static $user_reverse_charge = false;
|
||||
|
||||
public static $instance = 'shopping';
|
||||
|
||||
public static function getTransChange(){
|
||||
|
||||
|
|
@ -24,7 +25,11 @@ class UserService
|
|||
return $ret;
|
||||
}
|
||||
|
||||
public static function setInstance($instance){
|
||||
self::$instance = $instance;
|
||||
}
|
||||
|
||||
//init Yard for user order Customer
|
||||
public static function initCustomerYard($shopping_user, $for){
|
||||
self::$user_tax_free = false;
|
||||
if($shopping_user->same_as_billing){
|
||||
|
|
@ -40,15 +45,16 @@ class UserService
|
|||
$ShippingCountry = ShippingCountry::whereCountryId(self::$shipping_country->id)->first();
|
||||
self::$shipping_free = $ShippingCountry->shipping ? $ShippingCountry->shipping->free : false;
|
||||
self::$shipping_free = self::$shipping_free !== null ? self::$shipping_free : false;
|
||||
Yard::instance('shopping')->setShippingCountryWithPrice($ShippingCountry->id, $for);
|
||||
Yard::instance('shopping')->setUserPriceInfos(self::getYardInfo());
|
||||
Yard::instance(self::$instance)->setShippingCountryWithPrice($ShippingCountry->id, $for);
|
||||
Yard::instance(self::$instance)->setUserPriceInfos(self::getYardInfo());
|
||||
}
|
||||
|
||||
//init Yard for user order Berater
|
||||
public static function initUserYard(User $user, $shipping_country_id, $for){
|
||||
self::$shipping_free = false;
|
||||
self::checkUserTaxShippingCountry($user, $shipping_country_id,);
|
||||
Yard::instance('shopping')->setShippingCountryWithPrice($shipping_country_id, $for);
|
||||
Yard::instance('shopping')->setUserPriceInfos(self::getYardInfo());
|
||||
Yard::instance(self::$instance)->setShippingCountryWithPrice($shipping_country_id, $for);
|
||||
Yard::instance(self::$instance)->setUserPriceInfos(self::getYardInfo());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -142,7 +148,7 @@ class UserService
|
|||
public static function createConfirmationCode() {
|
||||
$unique = false;
|
||||
do{
|
||||
$confirmation_code = str_random(30);
|
||||
$confirmation_code = Str::random(30);
|
||||
if(User::where('confirmation_code', '=', $confirmation_code)->count() == 0){
|
||||
$unique = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,11 +143,9 @@ class UserUtil
|
|||
$user->save();
|
||||
}
|
||||
|
||||
public static function deleteUser(User $user)
|
||||
public static function deleteUser(User $user, $complete = false)
|
||||
{
|
||||
if($user->account){
|
||||
$user->account->delete();
|
||||
}
|
||||
//shop wird gelöscht
|
||||
if($user->shop){
|
||||
$subdomain_name = $user->shop->slug.'.mivita.care';
|
||||
$user->shop->name = "delete".$user->shop->id;
|
||||
|
|
@ -163,19 +161,32 @@ class UserUtil
|
|||
$kas->action('delete_subdomain', $pra);
|
||||
}
|
||||
}
|
||||
$user->email = "delete".time().mt_rand(1000000, 9999999);
|
||||
|
||||
//user soll nicht komplett gelöscht werden
|
||||
$user->email = "delete-".$user->email;
|
||||
//password wird gelöscht
|
||||
$user->password = "delete".time();
|
||||
$user->confirmed = 0;
|
||||
$user->confirmation_code = "delete".time();
|
||||
$user->confirmation_date = null;
|
||||
$user->confirmation_code_to = null;
|
||||
$user->confirmation_code_remider = 2;
|
||||
$user->agreement = null;
|
||||
// $user->agreement = null;
|
||||
$user->active = 0;
|
||||
$user->remember_token = '';
|
||||
$user->active_date = null;
|
||||
$user->admin = 0;
|
||||
$user->deleted_at = now();
|
||||
$user->pre_deleted_at = now();
|
||||
//user soll komplett gelöscht werden
|
||||
if($complete){
|
||||
$user->email = "delete-".time()."-".rand(1000, 9999);
|
||||
if($user->account){
|
||||
$user->account->delete();
|
||||
}
|
||||
$user->pre_deleted_at = null;
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use Yard;
|
||||
use App\Models\Country;
|
||||
use App\Models\UserHistory;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\UserHistory;
|
||||
use App\Models\UserShop;
|
||||
use Illuminate\Support\Str;
|
||||
use Request;
|
||||
use Yard;
|
||||
|
||||
class Util
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ class Util
|
|||
|
||||
public static function getToken()
|
||||
{
|
||||
return hash_hmac('sha256', str_random(40), config('app.key'));
|
||||
return hash_hmac('sha256', Str::random(40), config('app.key'));
|
||||
}
|
||||
|
||||
public static function uuidToken()
|
||||
|
|
@ -122,10 +123,17 @@ class Util
|
|||
}
|
||||
|
||||
public static function getUserShop(){
|
||||
if(\Session::has('user_shop')){
|
||||
if($user_shop = \Session::get('user_shop')){
|
||||
return $user_shop;
|
||||
}
|
||||
$shop = session('user_shop');
|
||||
if (empty($shop) || !is_object($shop)) {
|
||||
return null;
|
||||
}
|
||||
return $shop;
|
||||
}
|
||||
|
||||
public static function getDefaultUserShop(){
|
||||
$user = \App\User::find(6);
|
||||
if($user && $user->shop){
|
||||
return $user->shop;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -220,17 +228,6 @@ class Util
|
|||
return array_merge($p, $b);
|
||||
}
|
||||
|
||||
public static function isCheckout(){
|
||||
|
||||
if(\Session::has('isCheckout')){
|
||||
if(\Session::get('isCheckout') == true){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static function checkUserLandIsNot($user){
|
||||
|
||||
if(isset($user->account->country_id)){
|
||||
|
|
@ -243,14 +240,35 @@ class Util
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static function getMyMivitaShopUrl($add_url = ""){
|
||||
if(\Session::has('user_shop_domain')){
|
||||
$url = \Session::get('user_shop_domain').$add_url;
|
||||
if (!str_starts_with($url, 'http')) {
|
||||
$url = 'https://' . ltrim($url, '/');
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
//alois sein shop
|
||||
$user = \App\User::find(6);
|
||||
if($user && $user->shop){
|
||||
return config('app.protocol').$user->shop->slug.".".config('app.domain').config('app.tld_care').$add_url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getMyMivitaPortalUrl($protocol = true){
|
||||
$pro = $protocol ? config('app.protocol') : "";
|
||||
return $pro.config('app.pre_url_portal').config('app.domain').config('app.tld_care');
|
||||
}
|
||||
public static function getMyMivitaUrl($protocol = true){
|
||||
$pro = $protocol ? config('app.protocol') : "";
|
||||
return $pro.config('app.pre_url_crm').config('app.domain').config('app.tld_care');
|
||||
}
|
||||
|
||||
public static function getUserPaymentFor(){
|
||||
if(Yard::instance('shopping')->getYardExtra('user_shop_payment')){
|
||||
return Yard::instance('shopping')->getYardExtra('user_shop_payment');
|
||||
public static function getUserPaymentFor($instance = 'shopping'){
|
||||
if(Yard::instance($instance)->getYardExtra('user_shop_payment')){
|
||||
return Yard::instance($instance)->getYardExtra('user_shop_payment');
|
||||
}
|
||||
if(\Session::has('user_shop_payment')){
|
||||
return \Session::get('user_shop_payment');
|
||||
|
|
@ -268,20 +286,20 @@ class Util
|
|||
return config('app.protocol').$user_shop->slug.".".config('app.domain').config('app.tld_care')."/back/to/shop/".$reference;
|
||||
}
|
||||
}
|
||||
return url("/");
|
||||
return config('app.protocol').config('app.domain').config('app.tld_care');
|
||||
}
|
||||
|
||||
public static function getUserCardBackUrl($uri){
|
||||
public static function getUserCardBackUrl($uri, $instance = 'shopping'){
|
||||
|
||||
if(\Session::has('user_shop')){
|
||||
if(\Session::has('user_shop_domain')){
|
||||
if(\Session::has('back_link')){
|
||||
return \Session::get('back_link');
|
||||
}
|
||||
if(self::getUserPaymentFor() === 3){
|
||||
if(self::getUserPaymentFor($instance) === 3){
|
||||
return \Session::get('user_shop_domain')."/user/membership";
|
||||
}
|
||||
if(self::getUserPaymentFor() === 2){
|
||||
if(self::getUserPaymentFor($instance) === 2){
|
||||
return \Session::get('user_shop_domain')."/user/orders";
|
||||
}
|
||||
return \Session::get('user_shop_domain');
|
||||
|
|
@ -290,7 +308,7 @@ class Util
|
|||
return config('app.protocol').$user_shop->slug.".".config('app.domain').config('app.tld_care').$uri;
|
||||
}
|
||||
}
|
||||
return url($uri);
|
||||
return config('app.protocol').config('app.domain').config('app.tld_care');
|
||||
}
|
||||
|
||||
public static function isMivitaShop(){
|
||||
|
|
|
|||
|
|
@ -21,40 +21,49 @@ class Yard extends Cart
|
|||
private $shipping_country_id = 0; //default de
|
||||
private $shipping_is_for;
|
||||
private $num_comp;
|
||||
private $ysession;
|
||||
private $user_tax_free;
|
||||
private $shipping_free;
|
||||
private $user_reverse_charge;
|
||||
private $user_country_id;
|
||||
private $user_country;
|
||||
private $shopping_data = [];
|
||||
private $events;
|
||||
private $initShippingExtras = false;
|
||||
|
||||
|
||||
public function __construct(SessionManager $session, Dispatcher $events)
|
||||
{
|
||||
$this->ysession = $session;
|
||||
$this->yinstance = sprintf('%s.%s', 'cart', 'shipping_extras');
|
||||
parent::__construct($session, $events);
|
||||
|
||||
}
|
||||
|
||||
public function instance($instance = null)
|
||||
{
|
||||
parent::instance($instance);
|
||||
if(!$this->initShippingExtras){
|
||||
$this->initShippingExtras = true; //erst true, sonst wird es immer wieder aufgerufen
|
||||
$this->makeShippingExtras($instance);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function makeShippingExtras($instance){
|
||||
|
||||
if($this->getYardExtra('shipping_price')){
|
||||
$this->shipping_price = (float) ($this->getYardExtra('shipping_price'));
|
||||
}
|
||||
|
||||
if($this->getYardExtra('shipping_price_net')){
|
||||
$this->shipping_price_net = (float) ($this->getYardExtra('shipping_price_net'));
|
||||
}
|
||||
|
||||
if($this->getYardExtra('shipping_tax_rate')){
|
||||
$this->shipping_tax_rate = (float) ($this->getYardExtra('shipping_tax_rate'));
|
||||
}
|
||||
|
||||
if($this->getYardExtra('shipping_tax')){
|
||||
$this->shipping_tax = (float) ($this->getYardExtra('shipping_tax'));
|
||||
}
|
||||
|
||||
if($this->getYardExtra('shipping_country_id')){
|
||||
$this->shipping_country_id = $this->getYardExtra('shipping_country_id');
|
||||
}
|
||||
|
||||
if($this->getYardExtra('shipping_is_for')){
|
||||
$this->shipping_is_for = $this->getYardExtra('shipping_is_for');
|
||||
}
|
||||
|
|
@ -77,33 +86,27 @@ class Yard extends Cart
|
|||
$this->user_country = $this->getYardExtra('user_country');
|
||||
}
|
||||
|
||||
$this->events = $events;
|
||||
|
||||
|
||||
parent::__construct($session, $events);
|
||||
|
||||
if(gettype($this->shipping_country_id) !== 'object' && $this->shipping_country_id == 0){
|
||||
$shippingCountry = ShippingCountry::first();
|
||||
if($shippingCountry){
|
||||
$this->shipping_country_id = $shippingCountry->id;
|
||||
}
|
||||
}
|
||||
|
||||
if($this->shipping_price == 0){
|
||||
self::instance('shopping')->setShippingCountryWithPrice($this->shipping_country_id, $this->shipping_is_for);
|
||||
self::instance($instance)->setShippingCountryWithPrice($this->shipping_country_id, $this->shipping_is_for);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTaxRate()
|
||||
public function getTaxRate()
|
||||
{
|
||||
return config('cart.tax');
|
||||
}
|
||||
|
||||
public function putYardExtra($key, $value){
|
||||
|
||||
$content = $this->getYContent();
|
||||
$content->put($key, $value);
|
||||
$this->ysession->put($this->yinstance, $content);
|
||||
$this->putShippingExtras($content);
|
||||
//$this->ysession->put($this->yinstance, $content);
|
||||
}
|
||||
|
||||
public function getYardExtra($key){
|
||||
|
|
@ -114,6 +117,15 @@ class Yard extends Cart
|
|||
return false;
|
||||
}
|
||||
|
||||
public function getYContent()
|
||||
{
|
||||
return $this->getShippingExtras();
|
||||
/* if (is_null($this->ysession->get($this->yinstance))) {
|
||||
return new Collection([]);
|
||||
}
|
||||
return $this->ysession->get($this->yinstance);*/
|
||||
}
|
||||
|
||||
public function getShippingCountryName(){
|
||||
|
||||
$shippingCountry = ShippingCountry::find($this->shipping_country_id);
|
||||
|
|
@ -143,13 +155,7 @@ class Yard extends Cart
|
|||
}
|
||||
|
||||
|
||||
public function getYContent()
|
||||
{
|
||||
if (is_null($this->ysession->get($this->yinstance))) {
|
||||
return new Collection([]);
|
||||
}
|
||||
return $this->ysession->get($this->yinstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function reCalculateShippingPrice(){
|
||||
|
|
@ -168,22 +174,23 @@ class Yard extends Cart
|
|||
|
||||
}
|
||||
|
||||
public function setUserPriceInfos($setUserPriceInfos = [])
|
||||
public function setUserPriceInfos($user_price_infos = [])
|
||||
{
|
||||
$this->shipping_free = isset($setUserPriceInfos['shipping_free']) ? $setUserPriceInfos['shipping_free'] : false;
|
||||
$this->shipping_free = isset($user_price_infos['shipping_free']) ? $user_price_infos['shipping_free'] : false;
|
||||
$this->putYardExtra('shipping_free', $this->shipping_free);
|
||||
|
||||
$this->user_tax_free = $setUserPriceInfos['user_tax_free'];
|
||||
$this->user_tax_free = $user_price_infos['user_tax_free'];
|
||||
$this->putYardExtra('user_tax_free', $this->user_tax_free);
|
||||
|
||||
$this->user_reverse_charge = $setUserPriceInfos['user_reverse_charge'];
|
||||
$this->user_reverse_charge = $user_price_infos['user_reverse_charge'];
|
||||
$this->putYardExtra('user_reverse_charge', $this->user_reverse_charge);
|
||||
|
||||
$this->user_country_id = $setUserPriceInfos['user_country_id'];
|
||||
$this->user_country_id = $user_price_infos['user_country_id'];
|
||||
$this->putYardExtra('user_country_id', $this->user_country_id);
|
||||
|
||||
$this->user_country = Country::findOrFail($setUserPriceInfos['user_country_id']);
|
||||
$this->user_country = Country::findOrFail($user_price_infos['user_country_id']);
|
||||
$this->putYardExtra('user_country', $this->user_country);
|
||||
|
||||
}
|
||||
|
||||
public function getUserPriceInfos(){
|
||||
|
|
@ -476,9 +483,13 @@ class Yard extends Cart
|
|||
}
|
||||
$price = $product->price;
|
||||
if($set_price === 'with'){
|
||||
$cartItem = $this->getCartItem($product->id, $product->getLang('name'), 1, $price, ['image' => $image, 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'show_on' => $product->show_on]);
|
||||
$price = $product->getPriceWith(false, true, $this->getUserCountry());
|
||||
}
|
||||
$cartItem = $this->getCartItem($product->id, $product->getLang('name'), 1, $price, ['image' => $image, 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points]);
|
||||
if($set_price === 'withTaxFree'){
|
||||
$cartItem = $this->getCartItem($product->id, $product->getLang('name'), 1, $price, ['image' => $image, 'slug' => $product->slug, 'weight' => $product->weight, 'points' => $product->points, 'no_commission' => $product->no_commission, 'no_free_shipping' => $product->no_free_shipping, 'show_on' => $product->show_on]);
|
||||
$price = $product->getPriceWith($this->getUserTaxFree(), false, $this->getUserCountry());
|
||||
}
|
||||
$content = $this->getContent();
|
||||
|
||||
if ($content->has($cartItem->rowId)){
|
||||
|
|
@ -504,7 +515,7 @@ class Yard extends Cart
|
|||
|
||||
public function destroy()
|
||||
{
|
||||
$this->ysession->remove($this->yinstance);
|
||||
// $this->ysession->remove($this->yinstance);
|
||||
parent::destroy();
|
||||
|
||||
}
|
||||
|
|
@ -662,7 +673,7 @@ class Yard extends Cart
|
|||
'cartInstance' => $this->currentInstance(),
|
||||
], $eventOptions);
|
||||
|
||||
$this->events->dispatch('cart.stored', $eventOptions);
|
||||
// $this->events->dispatch('cart.stored', $eventOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
1.0.0.0,1.0.0.254,AU,Australia,OC,Oceania
|
||||
1.0.0.255,1.0.0.255,ID,Indonesia,AS,Asia
|
||||
1.0.1.0,1.0.3.255,CN,China,AS,Asia
|
||||
1.0.4.0,1.0.7.255,AU,Australia,OC,Oceania
|
||||
1.0.8.0,1.0.15.255,CN,China,AS,Asia
|
||||
1.0.16.0,1.0.31.255,JP,Japan,AS,Asia
|
||||
1.0.32.0,1.0.63.255,CN,China,AS,Asia
|
||||
1.0.64.0,1.0.127.255,JP,Japan,AS,Asia
|
||||
1.0.128.0,1.0.218.40,TH,Thailand,AS,Asia
|
||||
1.0.218.41,1.0.218.41,MY,Malaysia,AS,Asia
|
||||
1.0.218.42,1.0.255.255,TH,Thailand,AS,Asia
|
||||
1.1.0.0,1.1.0.255,CN,China,AS,Asia
|
||||
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue