mivita/app/Http/Controllers/User/IncentiveController.php
Kevin 11afdb11d1 Incentive: Schalter "Abos beruecksichtigen", Recruiting Challenge ohne Abos
Die Recruiting Challenge erfasste Eigen- und Kundenabos mit 0 Punkten
(Live-Kopie: 175 Eigenabos). Sie tauchten in Details und Admin auf und
brachten Berater ohne Neupartner in die Rangliste.

- incentives.include_abos (Standard an); Migration schaltet Incentives
  ohne jede Abo-Wertung aus (0 Abo-Punkte, 0 Mindestabos, ohne Umsatzpunkte)
- aus: kein Abo-Tracking in Tracker, Neuaufbau und Reparatur;
  incentive:calculate raeumt vorhandene Abo-Zeilen ab
- Admin: Checkbox im Formular, Abo-Punkte/Mindestabos werden dann auf 0
  gesetzt; Abo-Spalte und -Bereiche in Admin, Details und Teilnahme-Hinweis
  entfallen
- Seeder der Recruiting Challenge setzt den Schalter aus

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 11:37:32 +00:00

194 lines
6.5 KiB
PHP

<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Admin\IncentiveController as AdminIncentiveController;
use App\Http\Controllers\Controller;
use App\Models\Incentive;
use App\Models\IncentiveParticipant;
use App\Models\UserAbo;
use App\Services\Incentive\IncentivePayoutService;
use App\Services\Incentive\IncentivePointsLogRepairService;
use App\Services\Incentive\IncentiveTracker;
use App\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Request;
class IncentiveController extends Controller
{
/**
* Anzahl Plaetze in der User-Live-Rangliste (Gewinner-Highlight bleibt ueber max_winners, typ. Top 20).
*/
public const USER_RANKING_DISPLAY_LIMIT = 30;
public function __construct()
{
$this->middleware('active.account');
}
/**
* Die eine Incentive-Seite: Teilnahme oder Kopf mit Stand, Titelbild, Text,
* Kacheln, "So funktioniert es", Galerie und Live-Rangliste.
*/
public function teaser($slug)
{
$incentive = Incentive::where('slug', $slug)
->where('status', '!=', 0)
->firstOrFail();
$user = Auth::user();
abort_unless($incentive->isVisibleFor($user), 404);
$participant = IncentiveParticipant::where('incentive_id', $incentive->id)
->where('user_id', $user->id)
->first();
$ranking = IncentiveParticipant::where('incentive_id', $incentive->id)
->withRankingActivity()
->with('user', 'user.account')
->orderByIncentiveLeaderboard()
->paginate(100);
$participateHasTrackableAbos = false;
if (! $participant?->accepted_terms_at && $incentive->includesAbos()) {
$participateHasTrackableAbos = $this->userHasTrackableAbosForIncentive($user, $incentive);
}
$payoutService = new IncentivePayoutService;
$participantPayout = $incentive->hasCashPayout() && $participant
? $payoutService->forParticipant($incentive, $participant)
: null;
$rankingPayouts = $incentive->hasCashPayout()
? $ranking->getCollection()->mapWithKeys(fn (IncentiveParticipant $row) => [
$row->id => $payoutService->forParticipant($incentive, $row),
])
: collect();
return view('user.incentive.teaser', [
'incentive' => $incentive,
'participant' => $participant,
'hasConfirmedParticipation' => $participant && $participant->accepted_terms_at !== null,
'galleryImages' => $this->collectGalleryImages($incentive),
'ranking' => $ranking,
'participateHasTrackableAbos' => $participateHasTrackableAbos,
'isVipView' => $user->isVIP(),
'participantPayout' => $participantPayout,
'rankingPayouts' => $rankingPayouts,
]);
}
/**
* Alte Ranglisten-URL: leitet auf die zusammengefasste Seite mit Sprungmarke zur Rangliste.
*/
public function show($slug)
{
return redirect(route('user_incentive_teaser', [$slug]).'#ranking');
}
public function participate($slug)
{
$incentive = Incentive::where('slug', $slug)
->active()
->firstOrFail();
$user = Auth::user();
abort_unless($incentive->isVisibleFor($user), 404);
if (! Request::has('accept_terms')) {
\Session()->flash('alert-error', __('incentive.terms_required'));
return redirect(route('user_incentive_teaser', [$slug]));
}
$participant = IncentiveParticipant::firstOrCreate(
[
'incentive_id' => $incentive->id,
'user_id' => $user->id,
],
[
'accepted_terms_at' => null,
]
);
if ($participant->accepted_terms_at !== null) {
\Session()->flash('alert-info', __('incentive.already_participating'));
return redirect(route('user_incentive_teaser', [$slug]));
}
$participant->accepted_terms_at = Carbon::now();
$participant->save();
$repair = app(IncentivePointsLogRepairService::class);
$repair->syncMissingTrackingAbos($participant);
$repair->syncMissingSalesVolumeLogs($participant);
$participant->refresh()->recalculateFromTrackingTables()->save();
IncentiveTracker::updateRanking($incentive);
\Session()->flash('alert-success', __('incentive.participation_confirmed'));
return redirect(route('user_incentive_teaser', [$slug]));
}
public function details($slug)
{
$incentive = Incentive::where('slug', $slug)
->where('status', '!=', 0)
->firstOrFail();
$user = Auth::user();
abort_unless($incentive->isVisibleFor($user), 404);
$participant = IncentiveParticipant::with('incentive', 'user', 'user.account')
->where('incentive_id', $incentive->id)
->where('user_id', $user->id)
->firstOrFail();
if ($participant->accepted_terms_at === null) {
\Session()->flash('alert-info', __('incentive.details_requires_confirmation'));
return redirect(route('user_incentive_teaser', [$slug]));
}
$data = AdminIncentiveController::buildParticipantDetailData($participant);
return view('user.incentive.details', $data);
}
/**
* Galerie-Bilder des Incentives aus seinem eigenen Ordner ({@see Incentive::galleryImages()}).
*
* @return list<string>
*/
private function collectGalleryImages(Incentive $incentive): array
{
return $incentive->galleryImages();
}
/**
* Hinweis auf der Teilnehmen-Karte: Es gibt bereits ein Eigenabo oder ein Kundenabo im Qualifikationszeitraum.
*/
private function userHasTrackableAbosForIncentive(User $user, Incentive $incentive): bool
{
$qualEnd = $incentive->qualification_end->copy()->endOfDay();
$hasOwnActiveAbo = UserAbo::query()
->where('user_id', $user->id)
->where('is_for', 'me')
->where('status', 2)
->exists();
$hasCustomerAboInQualification = UserAbo::query()
->where('member_id', $user->id)
->where('is_for', 'ot')
->where('status', 2)
->whereBetween('created_at', [
$incentive->qualification_start,
$qualEnd,
])
->exists();
return $hasOwnActiveAbo || $hasCustomerAboInQualification;
}
}