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>
165 lines
6 KiB
PHP
165 lines
6 KiB
PHP
<?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);
|
|
});
|
|
|
|
it('setzt Abo-Punkte und Mindestabos auf 0, wenn Abos nicht beruecksichtigt werden', function () {
|
|
$this->actingAs(payoutAdmin());
|
|
|
|
$this->post(route('admin_incentive_store'), challengePayload([
|
|
'include_abos' => '0', 'points_abo_onetime' => 400, 'min_customer_abos' => 6,
|
|
]))->assertRedirect(route('admin_incentives'));
|
|
|
|
$incentive = Incentive::where('name', 'Recruiting-Challenge Okt-Jan')->firstOrFail();
|
|
|
|
expect($incentive->includesAbos())->toBeFalse()
|
|
->and($incentive->points_abo_onetime)->toBe(0)
|
|
->and($incentive->min_customer_abos)->toBe(0);
|
|
|
|
$this->get(route('admin_incentive_show', [$incentive->id]))
|
|
->assertOk()
|
|
->assertDontSee('<th>'.__('incentive.abos').'</th>', false);
|
|
});
|