User::forceCreate([ 'email' => 'rank-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', ]); it('vergibt Raenge zuerst an Qualifizierte, dann nach Punkten', function () use ($makeIncentiveRankingUser) { $incentive = Incentive::factory()->create(['max_winners' => 2]); $notQualifiedTopPoints = IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'is_qualified' => false, 'total_points' => 5000, ]); $qualifiedMidPoints = IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 900, ]); $qualifiedLowPoints = IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 400, ]); $zeroPoints = IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 0, 'rank' => 7, ]); IncentiveTracker::updateRanking($incentive); expect($qualifiedMidPoints->refresh()->rank)->toBe(1) ->and($qualifiedLowPoints->refresh()->rank)->toBe(2) ->and($notQualifiedTopPoints->refresh()->rank)->toBe(3) ->and($zeroPoints->refresh()->rank)->toBeNull(); }); it('macht Qualifizierte mit gespeichertem Rang innerhalb max_winners zu Gewinnern', function () use ($makeIncentiveRankingUser) { $incentive = Incentive::factory()->create(['max_winners' => 2]); $notQualifiedTopPoints = IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'is_qualified' => false, 'total_points' => 5000, ]); $qualifiedA = IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 900, ]); $qualifiedB = IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 400, ]); IncentiveTracker::updateRanking($incentive); expect($qualifiedA->refresh()->isWinner())->toBeTrue() ->and($qualifiedB->refresh()->isWinner())->toBeTrue() ->and($notQualifiedTopPoints->refresh()->isWinner())->toBeFalse(); }); it('vergibt nur bestaetigten Teilnehmern einen Rang (Kundenentscheid 25.09.2026)', function () use ($makeIncentiveRankingUser) { $incentive = Incentive::factory()->create(); $anonymous = IncentiveParticipant::factory()->unconfirmed()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 900, 'rank' => 1, ]); $confirmed = IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'accepted_terms_at' => now(), 'total_points' => 500, ]); IncentiveTracker::updateRanking($incentive); expect($confirmed->refresh()->rank)->toBe(1) ->and($anonymous->refresh()->rank)->toBeNull(); }); it('stimmt mit der Leaderboard-Anzeigereihenfolge ueberein', function () use ($makeIncentiveRankingUser) { $incentive = Incentive::factory()->create(); IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'is_qualified' => false, 'total_points' => 5000, ]); IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 900, ]); IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'total_points' => 400, ]); IncentiveParticipant::factory()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'is_qualified' => false, 'total_points' => 200, ]); IncentiveTracker::updateRanking($incentive); $displayOrder = IncentiveParticipant::query() ->where('incentive_id', $incentive->id) ->orderByIncentiveLeaderboard() ->pluck('rank') ->all(); expect($displayOrder)->toBe([1, 2, 3, 4]); }); describe('Gleichstand: wer den Stand zuerst erreicht hat (Recruiting-Challenge, Punkt 6)', function () use ($makeIncentiveRankingUser) { $challengeWithTie = function () use ($makeIncentiveRankingUser): array { $incentive = Incentive::factory()->create([ 'points_partner_onetime' => 1, 'points_abo_onetime' => 0, 'min_direct_partners' => 0, 'min_customer_abos' => 0, ]); $participant = function (array $registrations) use ($incentive, $makeIncentiveRankingUser): IncentiveParticipant { $participant = IncentiveParticipant::factory()->qualified()->create([ 'incentive_id' => $incentive->id, 'user_id' => $makeIncentiveRankingUser()->id, 'accepted_terms_at' => now(), 'total_points' => count($registrations), ]); foreach ($registrations as $registeredAt) { IncentiveNewPartner::create([ 'participant_id' => $participant->id, 'user_id' => $makeIncentiveRankingUser()->id, 'registered_at' => $registeredAt, ]); } return $participant; }; return [$incentive, $participant]; }; it('setzt bei gleicher Anzahl den vor, dessen letzter Neupartner frueher kam', function () use ($challengeWithTie) { [$incentive, $participant] = $challengeWithTie(); // Beide haben 2 Neupartner; der spaeter angelegte hat die Anzahl frueher erreicht. $reachedLater = $participant(['2026-10-02 10:00:00', '2026-11-20 09:00:00']); $reachedEarlier = $participant(['2026-10-05 10:00:00', '2026-10-30 18:00:00']); IncentiveTracker::updateRanking($incentive); expect($reachedEarlier->refresh()->rank)->toBe(1) ->and($reachedLater->refresh()->rank)->toBe(2); }); it('laesst die Anzahl vor dem Zeitpunkt entscheiden', function () use ($challengeWithTie) { [$incentive, $participant] = $challengeWithTie(); $fewerButEarly = $participant(['2026-10-01 08:00:00']); $moreButLate = $participant(['2026-12-01 08:00:00', '2027-01-30 08:00:00']); IncentiveTracker::updateRanking($incentive); expect($moreButLate->refresh()->rank)->toBe(1) ->and($fewerButEarly->refresh()->rank)->toBe(2); }); it('laesst unbestaetigte Teilnehmer trotz frueherem Zeitpunkt ohne Rang', function () use ($challengeWithTie) { [$incentive, $participant] = $challengeWithTie(); $anonymousEarly = $participant(['2026-10-01 08:00:00']); $anonymousEarly->update(['accepted_terms_at' => null]); $confirmedLate = $participant(['2026-12-01 08:00:00']); IncentiveTracker::updateRanking($incentive); expect($confirmedLate->refresh()->rank)->toBe(1) ->and($anonymousEarly->refresh()->rank)->toBeNull(); }); it('vergibt bei vollstaendigem Gleichstand stabil nach Teilnehmer-ID', function () use ($challengeWithTie) { [$incentive, $participant] = $challengeWithTie(); $first = $participant(['2026-10-10 10:00:00']); $second = $participant(['2026-10-10 10:00:00']); IncentiveTracker::updateRanking($incentive); expect($first->refresh()->rank)->toBe(1) ->and($second->refresh()->rank)->toBe(2); }); });