mivita/app/Http/Controllers/HomeController.php
Kevin 3341c929eb Incentive (E1): "Nur fuer VIP sichtbar" und Ranglisten-Seite mit Teilnahme-Block oben
Haken vip_only am Incentive: aktives Incentive erscheint dann nur fuer
VIP-Konten in Sidenav, Dashboard, Teaser, Rangliste und Details, andere
Berater bekommen 404; das Tracking laeuft fuer alle weiter. Seeder setzt den
Haken fuer die Abstimmung der Recruiting Challenge.

Ranglisten-Seite: solange die Teilnahme nicht bestaetigt ist, steht der
Teilnahme-Block mit Praemien-Chips, Checkbox, Button und aufklappbaren
Bedingungen ganz oben, darunter Trennlinie, Titel, Untertitel, Bild,
Beschreibung und die Info-Kacheln. Ueberschriften vergroessert, Abo-Bloecke
nur wenn Abos zaehlen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:11:33 +00:00

437 lines
14 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\Services\Abo\PaylineForecastService;
use App\Services\Abo\TeamAboOverviewService;
use App\Services\AboHelper;
use App\Services\Backoffice\BackofficeDashboardService;
use App\Services\BusinessPlan\LiveCommissionService;
use App\Services\Team\TopResultsService;
use App\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
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(): View
{
return view('dashboard.news_archive', [
'currentNews' => DashboardNews::getActiveNews(),
'archiveNews' => DashboardNews::getArchiveNews(),
]);
}
// login / Dashboard
public function show()
{
if (! Auth::check()) {
return redirect('login');
}
$user = Auth::user();
// Mehrere Incentives koennen parallel aktiv sein (E1): je eine Dashboard-Karte.
$activeIncentives = collect();
$incentiveParticipants = collect();
if ($user->isActiveAccount()) {
$activeIncentives = Incentive::activeForNavigation($user);
if ($activeIncentives->isNotEmpty()) {
$incentiveParticipants = IncentiveParticipant::query()
->whereIn('incentive_id', $activeIncentives->pluck('id'))
->where('user_id', $user->id)
->get()
->keyBy('incentive_id');
}
}
$activeIncentive = $activeIncentives->first();
$incentiveParticipant = $activeIncentive ? $incentiveParticipants->get($activeIncentive->id) : null;
// 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,
'activeIncentives' => $activeIncentives,
'incentiveParticipants' => $incentiveParticipants,
'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 = max(2020, min((int) now()->year + 1, (int) Request::get('stats_year', now()->year)));
$salesVolume = $user->getUserSalesVolume($month, $year, 'first');
[$paylinePoints, $paylineIsLive] = $this->teamPaylinePoints($user, $month, $year);
$customerPoints = $salesVolume ? (float) $salesVolume->getPointsKPSum() : 0.0;
// Payline-Prognose (B10) nur für den laufenden Monat — Basis ist der heutige
// Stand aus Kundenumsatz + Payline-Punkten.
$isCurrentMonth = $month === (int) now()->month && $year === (int) now()->year;
$paylineForecast = $isCurrentMonth
? app(PaylineForecastService::class)->forecastDays($user, $customerPoints + (float) $paylinePoints)
: null;
$levelForecast = $isCurrentMonth
? app(PaylineForecastService::class)->levelAndCommissionForecast($user, $month, $year)
: null;
$dashboardService = app(BackofficeDashboardService::class);
$teamOverview = app(TeamAboOverviewService::class);
$paylineService = app(PaylineForecastService::class);
// Firstlines: die Neupartner-Zählung braucht alle direkten Partner (der
// Scope filtert selbst), die Kachel zeigt aber nur aktive Berater —
// sonst zählt sie anders als "Teampartner gesamt" daneben (F1).
$firstlines = User::firstlinesOf($user->id)->get();
$firstlineIds = $firstlines->pluck('id')->map(fn ($id) => (int) $id)->all();
$activeFirstlineCount = $firstlines
->filter(fn (User $member) => $dashboardService->isActiveConsultant($member))
->count();
// Anzeige-Zählung konsistent zu "Teampartner gesamt" (F1: nur aktive
// Berater) — die Payline-Mitgliedermenge selbst enthält auch inaktive,
// deren Abos weiterhin in die Abo-/Punkte-Summen der Payline zählen.
$activeTeamUserIds = $dashboardService->activeTeamUserIds($user->id);
$paylineMemberIds = $paylineService->paylineContext($user, $month, $year)['member_ids'];
$activePaylineMemberCount = count(array_intersect($activeTeamUserIds, $paylineMemberIds));
// Abo-Zähler der Payline monatsbezogen wie die übrigen Abo-Kacheln (N2) —
// sonst widersprechen sich die Kacheln beim Monatswechsel.
$paylineAboCount = $teamOverview->aboStockCount($paylineMemberIds, 'user_id', 'me', $month, $year)
+ $teamOverview->aboStockCount($paylineMemberIds, 'member_id', 'ot', $month, $year);
return array_merge(
[
'month' => $month,
'year' => $year,
'customer_points' => $customerPoints,
'team_payline_points' => $paylinePoints,
'payline_is_live' => $paylineIsLive,
'payline_forecast' => $paylineForecast,
'commission' => app(LiveCommissionService::class)->commission($user, $month, $year),
'direct_new_partners' => $dashboardService->newPartnersCount($firstlineIds, $month, $year),
'firstline_count' => $activeFirstlineCount,
'team_partner_count' => count($activeTeamUserIds),
'payline_member_count' => $activePaylineMemberCount,
'payline_abo_count' => $paylineAboCount,
'payline_abos_new' => $paylineService->newPaylineAboCount($user, $month, $year),
'level' => app(LiveCommissionService::class)->levelProgress($user, $month, $year),
'level_forecast' => $levelForecast,
'top_results' => app(TopResultsService::class)->monthlyTop3($user, $month, $year),
],
$teamOverview->teamKpis($user, $month, $year),
$teamOverview->personalAboKpis($user, $month, $year)
);
}
/**
* Team-Payline-Punkte: aus dem gemeinsamen Live-Lauf ({@see LiveCommissionService}),
* der auch die Provision und die Payline-Tiefe liefert — ein Baum-Lauf für alles.
*
* @return array{0: float, 1: bool} [Punkte, istLive]
*/
private function teamPaylinePoints(User $user, int $month, int $year): array
{
$snapshot = app(LiveCommissionService::class)->snapshot($user, $month, $year);
return [$snapshot['payline_points'], $snapshot['is_live']];
}
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('/'));
}
}
}
}