- Statistik-Uebersicht: Tabelle auf Linie/Berater/Umsatz(Punkte)/Teamabos/ Teamkundenabos reduziert; Neupartner als grünes "+N"-Badge in der Berater-Spalte; 1000-Punkte-Shop & Neupartner als klickbare KPI-Cards (Augen-Icon/Liste anzeigen) - Teamkundenabos-Drilldown pro Teampartner gruppiert (Anzahl + Volumenpunkte gesamt, aufklappbar je Abo), CSV-Export angepasst - Home: fehlerhafte Monatsstatistik-Card (inkonsistente aktiv-Definition, kein Monatsbezug, schwere Live-Logik im Blade) mit der Abo-KPI-Card zusammengefuehrt; Logik nach HomeController::buildMonthlyStats() ausgelagert, konsistent ueber TeamAboOverviewService/BackofficeDashboardService - Team-Payline fuer laufenden Monat live berechnet und 6h gecacht (Snapshot) - Kacheln in Cluster (Umsatz/Neupartner/Abos) gruppiert - Services: teamKpis() um *_new ergaenzt, personalAboKpis() & newPartnersCount() neu - i18n (de/en/es/fr), Tests ergaenzt/aktualisiert Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
421 lines
12 KiB
PHP
421 lines
12 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Models\DashboardNews;
|
||
use App\Models\Incentive;
|
||
use App\Models\IncentiveParticipant;
|
||
use App\Models\ShoppingPayment;
|
||
use App\Models\UserAbo;
|
||
use App\Models\UserBusiness;
|
||
use App\Services\Abo\TeamAboOverviewService;
|
||
use App\Services\AboHelper;
|
||
use App\Services\Backoffice\BackofficeDashboardService;
|
||
use App\Services\BusinessPlan\TreeCalcBot;
|
||
use App\User;
|
||
use Carbon\Carbon;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Request;
|
||
use Util;
|
||
|
||
class HomeController extends Controller
|
||
{
|
||
/**
|
||
* Create a new controller instance.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function __construct() {}
|
||
|
||
public function index()
|
||
{
|
||
|
||
if (! Auth::check()) {
|
||
return redirect('login');
|
||
}
|
||
|
||
return redirect('home');
|
||
}
|
||
|
||
public function newsArchive(): \Illuminate\View\View
|
||
{
|
||
return view('dashboard.news_archive', [
|
||
'currentNews' => \App\Models\DashboardNews::getActiveNews(),
|
||
'archiveNews' => \App\Models\DashboardNews::getArchiveNews(),
|
||
]);
|
||
}
|
||
|
||
// login / Dashboard
|
||
public function show()
|
||
{
|
||
|
||
if (! Auth::check()) {
|
||
return redirect('login');
|
||
}
|
||
|
||
$user = Auth::user();
|
||
|
||
$activeIncentive = null;
|
||
$incentiveParticipant = null;
|
||
|
||
if ($user->isActiveAccount()) {
|
||
$activeIncentive = Incentive::active()->first();
|
||
if ($activeIncentive) {
|
||
$incentiveParticipant = IncentiveParticipant::where('incentive_id', $activeIncentive->id)
|
||
->where('user_id', $user->id)
|
||
->first();
|
||
}
|
||
}
|
||
|
||
// Eigenes Abo des Beraters für die Einmalprodukte-Dashboard-Card (nur wenn
|
||
// das Zeitfenster offen ist und das Feature für den User sichtbar ist).
|
||
$oneTimeAbo = null;
|
||
$aboOneTimeId = UserAbo::where('user_id', $user->id)
|
||
->where('is_for', 'me')
|
||
->where('active', true)
|
||
->where('status', 2)
|
||
->value('id');
|
||
|
||
if ($aboOneTimeId) {
|
||
$abo = UserAbo::find($aboOneTimeId);
|
||
if ($abo && AboHelper::isOneTimeWindowOpen($abo) && AboHelper::isOneTimeFeatureVisible($abo)) {
|
||
$oneTimeAbo = $abo;
|
||
}
|
||
}
|
||
|
||
$monthlyStats = null;
|
||
if ($user->isActiveAccount()) {
|
||
$monthlyStats = $this->buildMonthlyStats($user);
|
||
}
|
||
|
||
$data = [
|
||
'user' => $user,
|
||
'now' => Carbon::now(),
|
||
'dashboardNews' => DashboardNews::getActiveNews(),
|
||
'activeIncentive' => $activeIncentive,
|
||
'incentiveParticipant' => $incentiveParticipant,
|
||
'oneTimeAbo' => $oneTimeAbo,
|
||
'monthlyStats' => $monthlyStats,
|
||
];
|
||
|
||
return view('home', $data);
|
||
}
|
||
|
||
/**
|
||
* Kennzahlen der Monatsstatistik-Card. Bündelt persönliche Punkte/Abos, Team-Abo-KPIs
|
||
* (konsistent über {@see TeamAboOverviewService}) und die Team-Payline-Punkte.
|
||
*
|
||
* @return array<string, mixed>
|
||
*/
|
||
private function buildMonthlyStats(User $user): array
|
||
{
|
||
$month = max(1, min(12, (int) Request::get('stats_month', now()->month)));
|
||
$year = (int) Request::get('stats_year', now()->year);
|
||
|
||
$salesVolume = $user->getUserSalesVolume($month, $year, 'first');
|
||
[$paylinePoints, $paylineIsLive] = $this->teamPaylinePoints($user, $month, $year);
|
||
|
||
$firstlineIds = User::where('m_sponsor', $user->id)
|
||
->whereColumn('id', '!=', 'm_sponsor')
|
||
->whereNull('deleted_at')
|
||
->pluck('id')
|
||
->map(fn ($id) => (int) $id)
|
||
->all();
|
||
|
||
$dashboardService = app(BackofficeDashboardService::class);
|
||
$teamOverview = app(TeamAboOverviewService::class);
|
||
|
||
return array_merge(
|
||
[
|
||
'month' => $month,
|
||
'year' => $year,
|
||
'customer_points' => $salesVolume ? (float) $salesVolume->getPointsKPSum() : 0.0,
|
||
'team_payline_points' => $paylinePoints,
|
||
'payline_is_live' => $paylineIsLive,
|
||
'direct_new_partners' => $dashboardService->newPartnersCount($firstlineIds, $month, $year),
|
||
],
|
||
$teamOverview->teamKpis($user, $month, $year),
|
||
$teamOverview->personalAboKpis($user, $month, $year)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Team-Payline-Punkte: aus dem UserBusiness-Snapshot (abgeschlossene Monate) oder
|
||
* – für den laufenden Monat – einmalig live berechnet und für Folge-Aufrufe gecacht.
|
||
*
|
||
* @return array{0: float, 1: bool} [Punkte, istLive]
|
||
*/
|
||
private function teamPaylinePoints(User $user, int $month, int $year): array
|
||
{
|
||
$userBusiness = UserBusiness::where('user_id', $user->id)
|
||
->where('month', $month)
|
||
->where('year', $year)
|
||
->first();
|
||
|
||
if ($userBusiness) {
|
||
return [(float) ($userBusiness->payline_points ?? 0), false];
|
||
}
|
||
|
||
$points = Cache::remember(
|
||
"home_payline_{$user->id}_{$month}_{$year}",
|
||
now()->addHours(6),
|
||
fn (): float => $this->calculatePaylineLive($user, $month, $year)
|
||
);
|
||
|
||
return [(float) $points, true];
|
||
}
|
||
|
||
private function calculatePaylineLive(User $user, int $month, int $year): float
|
||
{
|
||
try {
|
||
$treeCalcBot = new TreeCalcBot($month, $year, 'member');
|
||
$treeCalcBot->initBusinesslUserDetail($user);
|
||
|
||
if ($treeCalcBot->business_user) {
|
||
return (float) ($treeCalcBot->business_user->payline_points ?? 0);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
\Log::warning("Live-Berechnung Payline-Punkte fehlgeschlagen für User {$user->id}: ".$e->getMessage());
|
||
}
|
||
|
||
return 0.0;
|
||
}
|
||
|
||
public function loadingModal()
|
||
{
|
||
|
||
$data = Request::get('data');
|
||
$target = Request::get('target');
|
||
$response = '';
|
||
if ($data === 'data_protection') {
|
||
$data = [
|
||
'modal' => true,
|
||
'user_shop' => true,
|
||
'isMivitaShop' => false,
|
||
];
|
||
$response = view('legal.data_protect_de', $data)->render();
|
||
}
|
||
if ($data === 'imprint') {
|
||
$data = [
|
||
'modal' => true,
|
||
'user_shop' => Util::getUserShop(),
|
||
];
|
||
$response = view('legal.imprint_de', $data)->render();
|
||
}
|
||
if ($data === 'shop_term_of_use') {
|
||
$data = [
|
||
'modal' => true,
|
||
'user_shop' => Util::getUserShop(),
|
||
];
|
||
$response = view('legal.shop_term_of_use_de', $data)->render();
|
||
}
|
||
if ($data === 'agb') {
|
||
$data = [
|
||
'modal' => true,
|
||
'user_shop' => Util::getUserShop(),
|
||
];
|
||
$response = view('legal.agb_de', $data)->render();
|
||
}
|
||
if (Request::ajax()) {
|
||
return response()->json(['response' => $response, 'target' => $target]);
|
||
}
|
||
abort(404);
|
||
}
|
||
|
||
/* public function checkLogin($identify, $token)
|
||
{
|
||
if($identify){
|
||
//user find by $identify
|
||
$user = User::where('identify', '=', $identify)->first();
|
||
if(!$user){
|
||
return abort(404);
|
||
}
|
||
//user - check für from $sf_guard_user - old system
|
||
$sf_guard_user = SfGuardUser::where('identify', '=', $identify)->first();
|
||
if(!$sf_guard_user){
|
||
return abort(404);
|
||
}
|
||
if($user->id != $sf_guard_user->user_id){
|
||
return abort(404);
|
||
|
||
}
|
||
if($sf_guard_user->token != $token){
|
||
return abort(404);
|
||
}
|
||
$time = Carbon::parse($sf_guard_user->token_at);
|
||
$now = Carbon::now();
|
||
$duration = $time->diffInSeconds($now);
|
||
|
||
if($duration > 3){
|
||
return abort(404);
|
||
}
|
||
$sf_guard_user->token = null;
|
||
$sf_guard_user->token_at = null;
|
||
$sf_guard_user->save();
|
||
if(!Auth::check()){
|
||
$user->last_login = now();
|
||
$user->save();
|
||
Auth::login($user);
|
||
}
|
||
if(Auth::check()){
|
||
return redirect('/templates');
|
||
}
|
||
}
|
||
return abort(404);
|
||
}
|
||
*/
|
||
public function zahlungsarten()
|
||
{
|
||
return view('web.templates.zahlungsarten', [
|
||
'user_shop' => Util::getUserShop(),
|
||
'isMivitaShop' => Util::isMivitaShop(),
|
||
'yard_instance' => 'webshop',
|
||
]);
|
||
}
|
||
|
||
public function versandkosten()
|
||
{
|
||
return view('web.templates.versandkosten', [
|
||
'user_shop' => Util::getUserShop(),
|
||
'isMivitaShop' => Util::isMivitaShop(),
|
||
'yard_instance' => 'webshop',
|
||
]);
|
||
}
|
||
|
||
public function legalDataProtected()
|
||
{
|
||
$data = [
|
||
'modal' => false,
|
||
'user_shop' => Util::getUserShop(),
|
||
'isMivitaShop' => Util::isMivitaShop(),
|
||
'yard_instance' => 'webshop',
|
||
];
|
||
|
||
return view('legal.data_protected', $data);
|
||
}
|
||
|
||
public function legalAGB()
|
||
{
|
||
$data = [
|
||
'modal' => false,
|
||
'user_shop' => Util::getUserShop(),
|
||
'yard_instance' => 'webshop',
|
||
'yard_instance' => 'webshop',
|
||
|
||
];
|
||
|
||
return view('legal.agb', $data);
|
||
}
|
||
|
||
public function legalImprint()
|
||
{
|
||
|
||
$data = [
|
||
'modal' => false,
|
||
'user_shop' => Util::getUserShop(),
|
||
'yard_instance' => 'webshop',
|
||
];
|
||
|
||
return view('legal.imprint', $data);
|
||
}
|
||
|
||
public function verify($confirmation_code)
|
||
{
|
||
if (! $confirmation_code) {
|
||
return redirect('/status/error');
|
||
}
|
||
|
||
$user = User::whereConfirmationCode($confirmation_code)->first();
|
||
if (! $user) {
|
||
return redirect('/status/not/found');
|
||
}
|
||
$user_auto_login = false;
|
||
if ($user->confirmed === 0) {
|
||
$user->confirmed = 1;
|
||
$user->confirmation_date = now();
|
||
$user_auto_login = true;
|
||
// nur bei der ersten Verifizierung den user auto login
|
||
}
|
||
// wird nun in WizardController::releaseAccount() auf null gesetzt
|
||
// $user->confirmation_code = null;
|
||
// $user->confirmation_code_to = null;
|
||
// $user->confirmation_code_remider = 0;
|
||
$user->save();
|
||
|
||
// Login!
|
||
if ($user_auto_login) {
|
||
Auth::login($user);
|
||
}
|
||
$url = Util::getMyMivitaUrl();
|
||
|
||
return redirect($url);
|
||
}
|
||
|
||
public function statusRegister()
|
||
{
|
||
return view('status.status_register');
|
||
}
|
||
|
||
public function statusVerify()
|
||
{
|
||
return view('status.status_verify');
|
||
}
|
||
|
||
public function statusError()
|
||
{
|
||
return view('status.status_error');
|
||
}
|
||
|
||
public function notFound()
|
||
{
|
||
return view('status.not_found');
|
||
}
|
||
|
||
/**
|
||
* @return string
|
||
*/
|
||
public function checkMail()
|
||
{
|
||
|
||
$data = Request::all();
|
||
if ($data['user_id'] === 'new') {
|
||
if (User::where('email', $data['email'])->count()) {
|
||
return json_encode(false);
|
||
}
|
||
} else {
|
||
if (User::where('email', $data['email'])->where('id', '!=', $data['user_id'])->count()) {
|
||
return json_encode(false);
|
||
}
|
||
}
|
||
|
||
return json_encode(true);
|
||
}
|
||
|
||
public function blocked()
|
||
{
|
||
return view('status.user_blocked');
|
||
}
|
||
|
||
public function backToShop($reference = '')
|
||
{
|
||
|
||
if ($reference) {
|
||
$ShoppingPayment = ShoppingPayment::where('reference', $reference)->first();
|
||
if ($ShoppingPayment && $ShoppingPayment->status === 'success') {
|
||
$user = Auth::user();
|
||
// is form wizard create payment
|
||
if ($user && ($user->wizard == 13 || $user->wizard == 20)) {
|
||
$user->wizard = 15; // realese Payments
|
||
$user->save();
|
||
|
||
return redirect(route('wizard_create', [15]));
|
||
}
|
||
} else {
|
||
\Session()->flash('alert-error', __('msg.error_occurred_with_order'));
|
||
|
||
return redirect(url('/'));
|
||
}
|
||
}
|
||
}
|
||
}
|