mivita/app/Services/LevelReportService.php
2026-01-23 17:35:23 +01:00

342 lines
14 KiB
PHP

<?php
namespace App\Services;
use App\Models\UserBusiness;
use App\Models\UserLevel;
use App\User;
use Illuminate\Support\Collection;
use App\Services\BusinessPlan\TreeCalcBotOptimized;
class LevelReportService
{
public function getLevelPromotions(array $filters = []): Collection
{
$month = $filters['month'] ?? null;
$year = $filters['year'] ?? null;
$userId = $filters['user_id'] ?? null;
$onlyNotUpdated = $filters['only_not_updated'] ?? false;
// Lade UserLevels für Referenz
$userLevels = UserLevel::where('active', 1)->orderBy('pos')->get()->keyBy('id');
// Query UserBusiness Einträge mit Level-Aufstiegen
$query = UserBusiness::whereNotNull('next_qual_user_level')
->whereRaw("JSON_LENGTH(next_qual_user_level) > 0")
->orderBy('year', 'desc')
->orderBy('month', 'desc')
->orderBy('user_id');
// Filter anwenden
if ($month) {
$query->where('month', $month);
}
if ($year) {
$query->where('year', $year);
}
if ($userId) {
$query->where('user_id', $userId);
}
$userBusinesses = $query->get();
return $this->processLevelPromotions($userBusinesses, $userLevels, $onlyNotUpdated);
}
public function processLevelPromotions($userBusinesses, $userLevels, $onlyNotUpdated = false): Collection
{
$promotions = [];
// Lade User-Daten für alle Level-Vergleiche
$userIds = $userBusinesses->pluck('user_id')->unique();
$users = User::whereIn('id', $userIds)->get(['id', 'm_level']);
$currentUserLevels = $users->keyBy('id');
foreach ($userBusinesses as $userBusiness) {
$nextQualUserLevel = $userBusiness->next_qual_user_level;
if (is_array($nextQualUserLevel) && !empty($nextQualUserLevel)) {
// next_qual_user_level kann sowohl ein einzelnes Level-Objekt als auch ein Array von Level-Objekten sein
$levelArray = isset($nextQualUserLevel['id']) ? [$nextQualUserLevel] : $nextQualUserLevel;
foreach ($levelArray as $newLevelData) {
// Überprüfe ob es ein vollständiges Level-Objekt ist
if (is_array($newLevelData) && isset($newLevelData['id'])) {
$currentLevel = $userLevels->get($userBusiness->m_level_id);
$newLevelId = $newLevelData['id'];
// Lade aktuellen User Level
$currentUser = $currentUserLevels->get($userBusiness->user_id);
$currentUserLevelName = 'Unbekannt';
$currentUserLevel = null;
if ($currentUser && $currentUser->m_level) {
$currentUserLevel = $userLevels->get($currentUser->m_level);
$currentUserLevelName = $currentUserLevel ? $currentUserLevel->name : 'Level ID: ' . $currentUser->m_level;
}
// Lade neues Level für POS-Vergleich (wird für Filter und level_updated benötigt)
$newLevel = $userLevels->get($newLevelId);
$newLevelPos = $newLevelData['pos'] ?? ($newLevel ? $newLevel->pos : 0);
// Filter: Nur User die noch nicht auf das neue Level umgestellt wurden
if ($onlyNotUpdated) {
// Skip wenn:
// 1. User existiert nicht oder hat kein Level
// 2. User ist bereits auf dem neuen Level (gleiche ID)
// 3. User hat bereits ein höheres oder gleichwertiges Level (POS >= neue Level POS)
if (
!$currentUser ||
$currentUser->m_level == $newLevelId ||
($currentUserLevel && $currentUserLevel->pos >= $newLevelPos)
) {
continue; // Skip - User ist bereits auf das neue Level umgestellt oder hat bereits ein höheres Level
}
}
$promotions[] = [
'user_id' => $userBusiness->user_id,
'email' => $userBusiness->email,
'first_name' => $userBusiness->first_name,
'last_name' => $userBusiness->last_name,
'month' => $userBusiness->month,
'year' => $userBusiness->year,
'date' => sprintf('%04d-%02d', $userBusiness->year, $userBusiness->month),
'from_level_id' => $userBusiness->m_level_id,
'from_level_name' => $currentLevel ? $currentLevel->name : 'Unbekannt',
'to_level_id' => $newLevelId,
'to_level_name' => $newLevelData['name'] ?? 'Unbekannt',
'to_level_margin' => $newLevelData['margin'] ?? 0,
'to_level_margin_shop' => $newLevelData['margin_shop'] ?? 0,
'to_level_qual_kp' => $newLevelData['qual_kp'] ?? 0,
'to_level_qual_pp' => $newLevelData['qual_pp'] ?? 0,
'to_level_growth_bonus' => $newLevelData['growth_bonus'] ?? 0,
'to_level_pos' => $newLevelData['pos'] ?? 0,
'current_user_level_id' => $currentUser ? $currentUser->m_level : null,
'current_user_level_name' => $currentUserLevelName,
'level_updated' => $onlyNotUpdated ? 'Nein' : ($currentUser && ($currentUser->m_level == $newLevelId || ($currentUserLevel && $currentUserLevel->pos >= $newLevelPos)) ? 'Ja' : 'Nein'),
'total_pp' => $userBusiness->total_pp ?? 0,
'total_qual_pp' => $userBusiness->total_qual_pp ?? 0,
'payline_points_qual_kp' => $userBusiness->payline_points_qual_kp ?? 0,
'sales_volume_points_sum' => $userBusiness->sales_volume_points_KP_sum ?? 0,
'active_account' => $userBusiness->active_account ? 'Ja' : 'Nein',
];
}
}
}
}
// Sortiere nach Datum (neueste zuerst) und dann nach User ID
usort($promotions, function ($a, $b) {
if ($a['year'] !== $b['year']) {
return $b['year'] - $a['year'];
}
if ($a['month'] !== $b['month']) {
return $b['month'] - $a['month'];
}
return $a['user_id'] - $b['user_id'];
});
return collect($promotions);
}
public function getStatistics(Collection $promotions): array
{
$stats = [
'total_count' => $promotions->count(),
'level_stats' => [],
'period_stats' => []
];
// Statistik nach Level
foreach ($promotions as $promotion) {
$levelName = $promotion['to_level_name'];
if (!isset($stats['level_stats'][$levelName])) {
$stats['level_stats'][$levelName] = 0;
}
$stats['level_stats'][$levelName]++;
}
// Statistik nach Monat/Jahr
foreach ($promotions as $promotion) {
$period = $promotion['date'];
if (!isset($stats['period_stats'][$period])) {
$stats['period_stats'][$period] = 0;
}
$stats['period_stats'][$period]++;
}
return $stats;
}
public function exportToCsv(Collection $promotions, ?string $filename = null): string
{
if (!$filename) {
$filename = 'level_promotions_' . date('Y-m-d_H-i-s') . '.csv';
}
$filepath = storage_path('app/reports/' . $filename);
// Erstelle Verzeichnis falls nicht vorhanden
if (!file_exists(dirname($filepath))) {
mkdir(dirname($filepath), 0755, true);
}
$file = fopen($filepath, 'w');
// UTF-8 BOM für korrekte Darstellung in Excel
fwrite($file, "\xEF\xBB\xBF");
// CSV Headers
$headers = [
'Datum',
'User ID',
'Vorname',
'Nachname',
'E-Mail',
'Von Level ID',
'Von Level Name',
'Zu Level ID',
'Zu Level Name',
'Zu Level KP Anforderung',
'Zu Level PP Anforderung',
'User PP Total',
'User PP Qualifiziert',
'Aktueller User Level ID',
'Aktueller User Level Name',
'Level bereits geupdatet',
'Aktiver Account',
'Monat',
'Jahr'
];
fputcsv($file, $headers, ';');
// Daten schreiben
foreach ($promotions as $promotion) {
$row = [
$promotion['date'],
$promotion['user_id'],
$promotion['first_name'],
$promotion['last_name'],
$promotion['email'],
$promotion['from_level_id'],
$promotion['from_level_name'],
$promotion['to_level_id'],
$promotion['to_level_name'],
$promotion['to_level_qual_kp'],
$promotion['to_level_qual_pp'],
$promotion['sales_volume_points_sum'],
$promotion['payline_points_qual_kp'],
$promotion['current_user_level_id'] ?? 'N/A',
$promotion['current_user_level_name'],
$promotion['level_updated'],
$promotion['active_account'],
$promotion['month'],
$promotion['year']
];
fputcsv($file, $row, ';');
}
fclose($file);
return $filepath;
}
/**
* Holt Level-Aufstiege für ein Team basierend auf TreeCalcBotOptimized
* Nur für einen spezifischen Monat/Jahr und nur für Team-Mitglieder
*/
public function getTeamLevelPromotions(TreeCalcBotOptimized $treeCalcBot, int $month, int $year, array $filters = []): Collection
{
$onlyNotUpdated = $filters['only_not_updated'] ?? false;
// Lade UserLevels für Referenz
$userLevels = UserLevel::where('active', 1)->orderBy('pos')->get()->keyBy('id');
// Extrahiere alle User-IDs aus dem Team
$teamUserIds = $this->extractTeamUserIds($treeCalcBot);
if (empty($teamUserIds)) {
return collect([]);
}
// Lade UserBusiness Einträge für Team-Mitglieder mit Level-Aufstiegen
$userBusinesses = UserBusiness::whereIn('user_id', $teamUserIds)
->where('month', $month)
->where('year', $year)
->whereNotNull('next_qual_user_level')
->whereRaw("JSON_LENGTH(next_qual_user_level) > 0")
->orderBy('user_id')
->get();
return $this->processLevelPromotions($userBusinesses, $userLevels, $onlyNotUpdated);
}
/**
* Extrahiert alle User-IDs aus TreeCalcBotOptimized-Struktur
* Ähnlich wie getTeamUsersFromStructure im TeamController, aber nur IDs
*/
private function extractTeamUserIds(TreeCalcBotOptimized $treeCalcBot): array
{
$userIds = [];
$processedIds = [];
// Sammle User-IDs aus Root-Items
$businessUsers = $treeCalcBot->getItems();
foreach ($businessUsers as $businessUser) {
$userId = $businessUser->user_id ?? null;
if ($userId && !isset($processedIds[$userId])) {
$processedIds[$userId] = true;
$userIds[] = $userId;
$this->collectUserIdsRecursive($businessUser->businessUserItems ?? [], $userIds, $processedIds);
}
}
// Sammle parentless User-IDs
if ($treeCalcBot->isParentless()) {
$parentless = $treeCalcBot->__get('parentless');
if (is_array($parentless)) {
foreach ($parentless as $businessUser) {
if ($businessUser) {
$userId = $businessUser->user_id ?? null;
if ($userId && !isset($processedIds[$userId])) {
$processedIds[$userId] = true;
$userIds[] = $userId;
$this->collectUserIdsRecursive($businessUser->businessUserItems ?? [], $userIds, $processedIds);
}
}
}
}
}
return $userIds;
}
/**
* Sammelt rekursiv User-IDs aus businessUserItems
*/
private function collectUserIdsRecursive(array $businessUserItems, array &$userIds, array &$processedIds, int $depth = 0): void
{
$maxDepth = 20;
if ($depth > $maxDepth) {
return;
}
foreach ($businessUserItems as $businessUserItem) {
if ($businessUserItem) {
$userId = $businessUserItem->user_id ?? null;
if ($userId && !isset($processedIds[$userId])) {
$processedIds[$userId] = true;
$userIds[] = $userId;
// Rekursiv für verschachtelte Items
if (isset($businessUserItem->businessUserItems) && is_array($businessUserItem->businessUserItems)) {
$this->collectUserIdsRecursive($businessUserItem->businessUserItems, $userIds, $processedIds, $depth + 1);
}
}
}
}
}
}