Incentive (E1): Recruiting-Challenge mit Geldpraemie, Auszahlungs-Export und parallelen Incentives
Neue Felder amount_per_partner, bonus_rank_1..3 und include_sales_points an incentives. Mit ausgeschalteten Umsatzpunkten zaehlt die Wertung nur die Einmalpunkte je Neupartner, die Rangliste entspricht damit der Anzahl Neupartner mit Starterpaket. IncentivePayoutService rechnet Neupartner mal Betrag plus Rang-Bonus fuer qualifizierte Gewinner; Admin-Detailseite bekommt Praemienspalte, Summen und XLSX-Export fuer die Auszahlung. Sidenav und Dashboard zeigen alle aktiven Incentives, damit Montenegro und die Challenge parallel laufen koennen. Teaser, Rangliste und Details zeigen bei Geldpraemie die Praemien-Kachel und "Deine Praemie bisher" und blenden Abo-Bloecke aus, wenn Abos keine Rolle spielen. Montenegro bleibt unveraendert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
4899e3a890
commit
b28f51bf62
27 changed files with 1418 additions and 129 deletions
147
tests/Feature/Incentive/IncentiveAdminPayoutTest.php
Normal file
147
tests/Feature/Incentive/IncentiveAdminPayoutTest.php
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\UserAccount;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* E1: Admin legt die Recruiting-Challenge mit Praemienfeldern an und laedt die
|
||||
* Auszahlungsliste als XLSX. Admin-Routen sind host-abhaengig (CRM-Host).
|
||||
*/
|
||||
beforeEach(function () {
|
||||
$this->previousHttpHost = $_SERVER['HTTP_HOST'] ?? null;
|
||||
$_SERVER['HTTP_HOST'] = config('domains.domains.crm.host');
|
||||
$this->refreshApplication();
|
||||
$this->artisan('migrate');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if ($this->previousHttpHost === null) {
|
||||
unset($_SERVER['HTTP_HOST']);
|
||||
} else {
|
||||
$_SERVER['HTTP_HOST'] = $this->previousHttpHost;
|
||||
}
|
||||
});
|
||||
|
||||
function payoutAdmin(): User
|
||||
{
|
||||
$account = UserAccount::create(['first_name' => 'Ad', 'last_name' => 'Min']);
|
||||
|
||||
return User::forceCreate([
|
||||
'email' => 'admin-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'admin' => 2,
|
||||
'account_id' => $account->id,
|
||||
'payment_account' => now()->addYear(),
|
||||
]);
|
||||
}
|
||||
|
||||
function challengePayload(array $overrides = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'name' => 'Recruiting-Challenge Okt-Jan',
|
||||
'qualification_start' => '01.10.2026',
|
||||
'qualification_end' => '31.01.2027',
|
||||
'calculation_end' => '28.02.2027',
|
||||
'points_partner_onetime' => 1,
|
||||
'points_abo_onetime' => 0,
|
||||
'min_direct_partners' => 0,
|
||||
'min_customer_abos' => 0,
|
||||
'max_winners' => 3,
|
||||
'amount_per_partner' => '50',
|
||||
'bonus_rank_1' => '300',
|
||||
'bonus_rank_2' => '200',
|
||||
'bonus_rank_3' => '',
|
||||
'include_sales_points' => '0',
|
||||
'status' => 1,
|
||||
], $overrides);
|
||||
}
|
||||
|
||||
it('speichert Praemienfelder und den Umsatzpunkte-Schalter beim Anlegen', function () {
|
||||
$this->actingAs(payoutAdmin());
|
||||
|
||||
$this->post(route('admin_incentive_store'), challengePayload())->assertRedirect(route('admin_incentives'));
|
||||
|
||||
$incentive = Incentive::where('name', 'Recruiting-Challenge Okt-Jan')->firstOrFail();
|
||||
|
||||
expect((float) $incentive->amount_per_partner)->toBe(50.0)
|
||||
->and($incentive->rankBonuses())->toBe([1 => 300.0, 2 => 200.0])
|
||||
->and($incentive->bonus_rank_3)->toBeNull()
|
||||
->and($incentive->includesSalesPoints())->toBeFalse()
|
||||
->and($incentive->hasCashPayout())->toBeTrue();
|
||||
});
|
||||
|
||||
it('setzt leere Betragsfelder beim Bearbeiten auf NULL und den Schalter wieder an', function () {
|
||||
$this->actingAs(payoutAdmin());
|
||||
$incentive = Incentive::factory()->create([
|
||||
'amount_per_partner' => 50, 'bonus_rank_1' => 300, 'include_sales_points' => false,
|
||||
]);
|
||||
|
||||
$this->post(route('admin_incentive_update', [$incentive->id]), challengePayload([
|
||||
'amount_per_partner' => '', 'bonus_rank_1' => '', 'bonus_rank_2' => '', 'include_sales_points' => '1',
|
||||
]))->assertRedirect(route('admin_incentive_show', [$incentive->id]));
|
||||
|
||||
$incentive->refresh();
|
||||
|
||||
expect($incentive->amount_per_partner)->toBeNull()
|
||||
->and($incentive->hasCashPayout())->toBeFalse()
|
||||
->and($incentive->includesSalesPoints())->toBeTrue();
|
||||
});
|
||||
|
||||
it('lehnt negative Betraege ab', function () {
|
||||
$this->actingAs(payoutAdmin());
|
||||
|
||||
$this->from(route('admin_incentive_create'))
|
||||
->post(route('admin_incentive_store'), challengePayload(['amount_per_partner' => '-5']))
|
||||
->assertSessionHasErrors('amount_per_partner');
|
||||
});
|
||||
|
||||
it('laedt die Auszahlungsliste als XLSX herunter', function () {
|
||||
Excel::fake();
|
||||
$this->actingAs(payoutAdmin());
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'slug' => 'recruiting-challenge',
|
||||
'amount_per_partner' => 50, 'bonus_rank_1' => 300, 'max_winners' => 3, 'include_sales_points' => false,
|
||||
]);
|
||||
$account = UserAccount::create(['first_name' => 'Anna', 'last_name' => 'Berater']);
|
||||
$winner = User::forceCreate([
|
||||
'email' => 'winner-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'account_id' => $account->id,
|
||||
]);
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id, 'user_id' => $winner->id,
|
||||
'qualified_partners' => 3, 'total_points' => 3, 'is_qualified' => true, 'rank' => 1,
|
||||
]);
|
||||
|
||||
$this->get(route('admin_incentive_payout_export', [$incentive->id]))->assertOk();
|
||||
|
||||
Excel::assertDownloaded('incentive-recruiting-challenge-auszahlung-'.now()->format('Y-m-d').'.xlsx', function ($export) {
|
||||
$rows = $export->collection();
|
||||
|
||||
return $rows->count() === 1
|
||||
&& $rows[0][2] === 'Anna Berater'
|
||||
&& $rows[0][9] === 450.0;
|
||||
});
|
||||
});
|
||||
|
||||
it('zeigt auf der Admin-Detailseite Export-Button und Praemienspalte nur bei Geldpraemie', function () {
|
||||
$this->actingAs(payoutAdmin());
|
||||
|
||||
$challenge = Incentive::factory()->create(['amount_per_partner' => 50, 'include_sales_points' => false]);
|
||||
$montenegro = Incentive::factory()->create();
|
||||
|
||||
$this->get(route('admin_incentive_show', [$challenge->id]))
|
||||
->assertOk()
|
||||
->assertSee('id="incentive-payout-export"', false)
|
||||
->assertSee(__('incentive.payout_total'));
|
||||
|
||||
$this->get(route('admin_incentive_show', [$montenegro->id]))
|
||||
->assertOk()
|
||||
->assertDontSee('id="incentive-payout-export"', false);
|
||||
});
|
||||
207
tests/Feature/Incentive/IncentivePayoutTest.php
Normal file
207
tests/Feature/Incentive/IncentivePayoutTest.php
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\UserAccount;
|
||||
use App\Services\Incentive\IncentivePayoutService;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* Recruiting-Challenge (Entwicklungsplan September 2026, E1): Geldpraemie je Neupartner
|
||||
* plus Rang-Bonus fuer die Gewinnerplaetze, mehrere parallel aktive Incentives.
|
||||
*/
|
||||
function payoutUser(string $firstName = 'Anna'): User
|
||||
{
|
||||
$account = UserAccount::create(['first_name' => $firstName, 'last_name' => 'Berater']);
|
||||
|
||||
return User::forceCreate([
|
||||
'email' => 'payout-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'account_id' => $account->id,
|
||||
'payment_account' => now()->addYear(),
|
||||
]);
|
||||
}
|
||||
|
||||
function recruitingChallenge(array $overrides = []): Incentive
|
||||
{
|
||||
return Incentive::factory()->create(array_merge([
|
||||
'name' => 'Recruiting-Challenge',
|
||||
'qualification_start' => '2026-10-01',
|
||||
'qualification_end' => '2027-01-31',
|
||||
'calculation_end' => '2027-02-28',
|
||||
'points_partner_onetime' => 1,
|
||||
'points_abo_onetime' => 0,
|
||||
'min_direct_partners' => 0,
|
||||
'min_customer_abos' => 0,
|
||||
'max_winners' => 3,
|
||||
'amount_per_partner' => 50,
|
||||
'bonus_rank_1' => 300,
|
||||
'bonus_rank_2' => 200,
|
||||
'bonus_rank_3' => 100,
|
||||
'include_sales_points' => false,
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
describe('Incentive-Modell: Praemienfelder', function () {
|
||||
it('erkennt eine Geldpraemie und die Challenge-Wertung nach Neupartnern', function () {
|
||||
$challenge = recruitingChallenge();
|
||||
$montenegro = Incentive::factory()->create();
|
||||
|
||||
expect($challenge->hasCashPayout())->toBeTrue()
|
||||
->and($challenge->rankBonuses())->toBe([1 => 300.0, 2 => 200.0, 3 => 100.0])
|
||||
->and($challenge->includesSalesPoints())->toBeFalse()
|
||||
->and($challenge->hasMinimumRequirements())->toBeFalse()
|
||||
->and($challenge->usesAbos())->toBeFalse()
|
||||
->and($challenge->ranksByPartnerCount())->toBeTrue()
|
||||
->and($montenegro->hasCashPayout())->toBeFalse()
|
||||
->and($montenegro->includesSalesPoints())->toBeTrue()
|
||||
->and($montenegro->ranksByPartnerCount())->toBeFalse();
|
||||
});
|
||||
|
||||
it('gibt den Rang-Bonus nur innerhalb der Gewinnerplaetze', function () {
|
||||
$challenge = recruitingChallenge(['max_winners' => 2]);
|
||||
|
||||
expect($challenge->rankBonus(1))->toBe(300.0)
|
||||
->and($challenge->rankBonus(2))->toBe(200.0)
|
||||
->and($challenge->rankBonus(3))->toBe(0.0)
|
||||
->and($challenge->rankBonus(null))->toBe(0.0);
|
||||
});
|
||||
|
||||
it('liefert aktive Incentives fuer die Navigation in Reihenfolge des Starts', function () {
|
||||
$later = recruitingChallenge();
|
||||
$earlier = Incentive::factory()->create(['qualification_start' => '2026-04-01']);
|
||||
Incentive::factory()->closed()->create();
|
||||
Incentive::factory()->draft()->create();
|
||||
|
||||
expect(Incentive::activeForNavigation()->pluck('id')->all())->toBe([$earlier->id, $later->id]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IncentivePayoutService', function () {
|
||||
it('rechnet Neupartner mal Betrag plus Rang-Bonus fuer qualifizierte Gewinner', function () {
|
||||
$challenge = recruitingChallenge();
|
||||
$service = new IncentivePayoutService;
|
||||
|
||||
$first = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser()->id,
|
||||
'qualified_partners' => 4, 'total_points' => 4, 'is_qualified' => true, 'rank' => 1,
|
||||
]);
|
||||
$fourth = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser()->id,
|
||||
'qualified_partners' => 1, 'total_points' => 1, 'is_qualified' => true, 'rank' => 4,
|
||||
]);
|
||||
$notQualified = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser()->id,
|
||||
'qualified_partners' => 2, 'total_points' => 2, 'is_qualified' => false, 'rank' => 2,
|
||||
]);
|
||||
|
||||
expect($service->forParticipant($challenge, $first))->toBe([
|
||||
'partners' => 4, 'per_partner' => 50.0, 'partner_amount' => 200.0, 'rank_bonus' => 300.0, 'total' => 500.0,
|
||||
])->and($service->forParticipant($challenge, $fourth)['total'])->toBe(50.0)
|
||||
->and($service->forParticipant($challenge, $notQualified))->toMatchArray(['rank_bonus' => 0.0, 'total' => 100.0]);
|
||||
});
|
||||
|
||||
it('listet nur Teilnehmer mit Praemie, nach Rang, und summiert', function () {
|
||||
$challenge = recruitingChallenge();
|
||||
$service = new IncentivePayoutService;
|
||||
|
||||
$second = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser('Bea')->id,
|
||||
'qualified_partners' => 2, 'total_points' => 2, 'is_qualified' => true, 'rank' => 2,
|
||||
]);
|
||||
$first = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser('Anna')->id,
|
||||
'qualified_partners' => 3, 'total_points' => 3, 'is_qualified' => true, 'rank' => 1,
|
||||
]);
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser('Null')->id,
|
||||
'qualified_partners' => 0, 'total_points' => 0, 'is_qualified' => true, 'rank' => null,
|
||||
]);
|
||||
|
||||
$rows = $service->rows($challenge);
|
||||
|
||||
expect($rows->pluck('participant.id')->all())->toBe([$first->id, $second->id])
|
||||
->and($service->totals($rows))->toBe([
|
||||
'recipients' => 2, 'partners' => 5, 'partner_amount' => 250.0, 'rank_bonus' => 500.0, 'total' => 750.0,
|
||||
]);
|
||||
|
||||
$export = $service->exportRows($challenge);
|
||||
expect($export)->toHaveCount(2)
|
||||
->and($export[0])->toHaveCount(count($service->exportHeaders()))
|
||||
->and($export[0][0])->toBe(1)
|
||||
->and($export[0][1])->toBe($first->user_id)
|
||||
->and($export[0][2])->toBe('Anna Berater')
|
||||
->and($export[0][5])->toBe(3)
|
||||
->and($export[0][6])->toBe(50.0)
|
||||
->and($export[0][7])->toBe(150.0)
|
||||
->and($export[0][8])->toBe(300.0)
|
||||
->and($export[0][9])->toBe(450.0);
|
||||
});
|
||||
|
||||
it('zahlt ohne Betrag je Neupartner nur den Rang-Bonus', function () {
|
||||
$challenge = recruitingChallenge(['amount_per_partner' => null]);
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => payoutUser()->id,
|
||||
'qualified_partners' => 5, 'total_points' => 5, 'is_qualified' => true, 'rank' => 3,
|
||||
]);
|
||||
|
||||
expect($challenge->hasCashPayout())->toBeTrue()
|
||||
->and((new IncentivePayoutService)->forParticipant($challenge, $participant))->toMatchArray([
|
||||
'partner_amount' => 0.0, 'rank_bonus' => 100.0, 'total' => 100.0,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ansichten', function () {
|
||||
beforeEach(function () {
|
||||
URL::resolveMissingNamedRoutesUsing(fn (string $name, array $parameters) => '/'.$name.'/'.implode('/', array_map('strval', $parameters)));
|
||||
});
|
||||
|
||||
it('zeigt die Praemien-Kachel mit Betrag und Boni', function () {
|
||||
$html = view('user.incentive._payout_tile', ['incentive' => recruitingChallenge()])->render();
|
||||
|
||||
expect($html)->toContain('id="incentive-payout-tile"')
|
||||
->toContain('50,00 €')
|
||||
->toContain(__('incentive.payout_rank_bonus_label', ['n' => 1]))
|
||||
->toContain('300,00 €');
|
||||
});
|
||||
|
||||
it('rendert im Dashboard je aktivem Incentive eine Karte mit eigenen Modal-IDs', function () {
|
||||
$montenegro = Incentive::factory()->create(['name' => 'Montenegro', 'qualification_start' => '2026-04-01']);
|
||||
$challenge = recruitingChallenge();
|
||||
$user = payoutUser();
|
||||
// Noch unbestaetigt: dann erscheint auch fuer die Challenge das Teilnahme-Modal.
|
||||
$participant = IncentiveParticipant::factory()->unconfirmed()->create([
|
||||
'incentive_id' => $challenge->id, 'user_id' => $user->id,
|
||||
'qualified_partners' => 2, 'total_points' => 2, 'is_qualified' => true, 'rank' => 1,
|
||||
]);
|
||||
|
||||
$html = view('dashboard._incentive', [
|
||||
'activeIncentives' => Incentive::activeForNavigation(),
|
||||
'incentiveParticipants' => collect([$challenge->id => $participant]),
|
||||
])->render();
|
||||
|
||||
expect($html)->toContain('Montenegro')
|
||||
->toContain('Recruiting-Challenge')
|
||||
->toContain('id="incParticipateModal-'.$montenegro->id.'"')
|
||||
->toContain('id="incParticipateModal-'.$challenge->id.'"')
|
||||
->toContain(__('incentive.badge_challenge'))
|
||||
->toContain(__('incentive.payout_so_far'))
|
||||
->toContain('400,00 €')
|
||||
->and(substr_count($html, '<style>'))->toBe(1);
|
||||
});
|
||||
|
||||
it('rendert nichts, wenn kein Incentive aktiv ist', function () {
|
||||
$html = view('dashboard._incentive', [
|
||||
'activeIncentives' => collect(),
|
||||
'incentiveParticipants' => collect(),
|
||||
])->render();
|
||||
|
||||
expect(trim($html))->toBe('');
|
||||
});
|
||||
});
|
||||
135
tests/Feature/Incentive/IncentiveSalesPointsToggleTest.php
Normal file
135
tests/Feature/Incentive/IncentiveSalesPointsToggleTest.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewPartner;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Services\Incentive\IncentivePointsLogRepairService;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* E1: Bei `include_sales_points = 0` (Recruiting-Challenge) zaehlen nur die Einmalpunkte
|
||||
* je Neupartner; Umsaetze der Neupartner erzeugen keine Punkte.
|
||||
*/
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-11-15 12:00:00'));
|
||||
});
|
||||
|
||||
afterEach(fn () => Carbon::setTestNow());
|
||||
|
||||
function toggleUser(array $overrides = []): User
|
||||
{
|
||||
return User::forceCreate(array_merge([
|
||||
'email' => 'toggle-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: Incentive, 1: IncentiveParticipant, 2: User}
|
||||
*/
|
||||
function trackedNewPartner(bool $includeSalesPoints): array
|
||||
{
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-10-01',
|
||||
'qualification_end' => '2027-01-31',
|
||||
'calculation_end' => '2027-02-28',
|
||||
'points_partner_onetime' => 1,
|
||||
'points_abo_onetime' => 0,
|
||||
'min_direct_partners' => 0,
|
||||
'min_customer_abos' => 0,
|
||||
'max_winners' => 3,
|
||||
'include_sales_points' => $includeSalesPoints,
|
||||
]);
|
||||
$sponsor = toggleUser();
|
||||
$partner = toggleUser(['m_sponsor' => $sponsor->id]);
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $sponsor->id,
|
||||
]);
|
||||
$tracking = IncentiveNewPartner::create([
|
||||
'participant_id' => $participant->id,
|
||||
'user_id' => $partner->id,
|
||||
'registered_at' => '2026-10-20 10:00:00',
|
||||
]);
|
||||
IncentivePointsLog::create([
|
||||
'participant_id' => $participant->id,
|
||||
'type' => 'partner',
|
||||
'source_type' => User::class,
|
||||
'source_id' => $partner->id,
|
||||
'source_label' => 'Neupartner',
|
||||
'month' => 10,
|
||||
'year' => 2026,
|
||||
'points_onetime' => 1,
|
||||
'points_accumulated' => 0,
|
||||
'incentive_new_partner_id' => $tracking->id,
|
||||
]);
|
||||
$participant->recalculateFromTrackingTables()->save();
|
||||
|
||||
return [$incentive, $participant, $partner];
|
||||
}
|
||||
|
||||
function partnerSalesVolume(User $partner, int $points = 120): UserSalesVolume
|
||||
{
|
||||
return UserSalesVolume::create([
|
||||
'user_id' => $partner->id,
|
||||
'month' => 11,
|
||||
'year' => 2026,
|
||||
'date' => '15.11.2026',
|
||||
'points' => $points,
|
||||
'total_net' => 150,
|
||||
'status_points' => 1,
|
||||
'status' => 1,
|
||||
'message' => 'Eigenbestellung Neupartner',
|
||||
]);
|
||||
}
|
||||
|
||||
it('schreibt ohne Umsatzpunkte keinen Log fuer Umsaetze des Neupartners', function () {
|
||||
[$incentive, $participant, $partner] = trackedNewPartner(includeSalesPoints: false);
|
||||
|
||||
IncentiveTracker::trackSalesVolume(partnerSalesVolume($partner));
|
||||
|
||||
expect(IncentivePointsLog::where('participant_id', $participant->id)->whereNotNull('user_sales_volume_id')->count())->toBe(0)
|
||||
->and($participant->fresh()->total_points)->toBe(1)
|
||||
->and($participant->fresh()->qualified_partners)->toBe(1);
|
||||
});
|
||||
|
||||
it('schreibt mit Umsatzpunkten weiterhin den Log (Montenegro-Verhalten)', function () {
|
||||
[$incentive, $participant, $partner] = trackedNewPartner(includeSalesPoints: true);
|
||||
|
||||
IncentiveTracker::trackSalesVolume(partnerSalesVolume($partner));
|
||||
|
||||
expect(IncentivePointsLog::where('participant_id', $participant->id)->whereNotNull('user_sales_volume_id')->count())->toBe(1)
|
||||
->and($participant->fresh()->total_points)->toBe(121);
|
||||
});
|
||||
|
||||
it('zieht ohne Umsatzpunkte auch bei der Reparatur keine Umsatz-Logs nach', function () {
|
||||
[$incentive, $participant, $partner] = trackedNewPartner(includeSalesPoints: false);
|
||||
partnerSalesVolume($partner);
|
||||
|
||||
$synced = (new IncentivePointsLogRepairService)->syncMissingSalesVolumeLogs($participant->fresh());
|
||||
|
||||
expect($synced)->toBe(0)
|
||||
->and(IncentivePointsLog::where('participant_id', $participant->id)->whereNotNull('user_sales_volume_id')->count())->toBe(0);
|
||||
});
|
||||
|
||||
it('haelt die Umsatzpunkte auch beim Umschalten heraus, wenn die Wertung neu berechnet wird', function () {
|
||||
[$incentive, $participant, $partner] = trackedNewPartner(includeSalesPoints: true);
|
||||
IncentiveTracker::trackSalesVolume(partnerSalesVolume($partner));
|
||||
expect($participant->fresh()->total_points)->toBe(121);
|
||||
|
||||
// Schalter nachtraeglich aus: die bestehenden Umsatz-Logs bleiben, bis --force neu aufbaut;
|
||||
// ein Neuaufbau ohne Umsatzpunkte endet nach A/B.
|
||||
$incentive->update(['include_sales_points' => false]);
|
||||
$rebuilt = $participant->fresh()->load('incentive')->rebuildFromSourceTables();
|
||||
|
||||
expect(IncentivePointsLog::where('participant_id', $participant->id)->whereNotNull('user_sales_volume_id')->count())->toBe(0)
|
||||
->and($rebuilt->total_points)->toBe(0);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue