User Statistik
This commit is contained in:
parent
70240d2b6a
commit
53bdba33cd
24 changed files with 2633 additions and 9 deletions
342
tests/Feature/BackofficeStatisticsAccessTest.php
Normal file
342
tests/Feature/BackofficeStatisticsAccessTest.php
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\User\BackofficeStatisticsController;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Services\Backoffice\BackofficeDashboardService;
|
||||
use App\Services\Backoffice\BackofficeDrilldownService;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
function makeBackofficeStatisticsUser(int $admin): User
|
||||
{
|
||||
return (new User)->forceFill([
|
||||
'admin' => $admin,
|
||||
'lang' => 'de',
|
||||
'active' => 1,
|
||||
'blocked' => 0,
|
||||
'wizard' => 100,
|
||||
]);
|
||||
}
|
||||
|
||||
function makeBackofficeStatisticsRequest(User $user): Request
|
||||
{
|
||||
$request = Request::create('/user/backoffice/statistics', 'GET');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
function makeBackofficeStatisticsController(?BackofficeDashboardService $dashboardService = null, ?BackofficeDrilldownService $drilldownService = null): BackofficeStatisticsController
|
||||
{
|
||||
$dashboardService ??= Mockery::mock(BackofficeDashboardService::class);
|
||||
$drilldownService ??= Mockery::mock(BackofficeDrilldownService::class);
|
||||
|
||||
return new BackofficeStatisticsController($dashboardService, $drilldownService);
|
||||
}
|
||||
|
||||
function backofficeStatisticsStreamedContent(StreamedResponse $response): string
|
||||
{
|
||||
ob_start();
|
||||
$response->sendContent();
|
||||
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
it('zeigt die Backoffice-Statistik fuer VIP-User', function () {
|
||||
$vip = makeBackofficeStatisticsUser(1);
|
||||
$dashboardService = Mockery::mock(BackofficeDashboardService::class);
|
||||
$dashboardService
|
||||
->shouldReceive('overview')
|
||||
->once()
|
||||
->andReturn([
|
||||
'month' => now()->month,
|
||||
'year' => now()->year,
|
||||
'metric_labels' => [],
|
||||
'lines' => [],
|
||||
'totals' => [],
|
||||
'_meta' => [
|
||||
'source_label' => 'Live',
|
||||
'calculated_at' => null,
|
||||
],
|
||||
]);
|
||||
|
||||
$controller = makeBackofficeStatisticsController($dashboardService);
|
||||
|
||||
$response = $controller->index(makeBackofficeStatisticsRequest($vip));
|
||||
|
||||
expect($response->getName())->toBe('user.backoffice.statistics.index');
|
||||
expect($response->getData())->toHaveKeys(['selectedMonth', 'selectedYear', 'statistics', 'performance']);
|
||||
expect($response->getData()['performance']['source_label'])->toBe('Live');
|
||||
});
|
||||
|
||||
it('behaelt den zuletzt gewaehlten Statistik-Zeitraum in der Session', function () {
|
||||
$vip = makeBackofficeStatisticsUser(1);
|
||||
$dashboardService = Mockery::mock(BackofficeDashboardService::class);
|
||||
$dashboardService
|
||||
->shouldReceive('overview')
|
||||
->twice()
|
||||
->andReturn([
|
||||
'month' => 4,
|
||||
'year' => 2026,
|
||||
'metric_labels' => [],
|
||||
'lines' => [],
|
||||
'totals' => [],
|
||||
'_meta' => [
|
||||
'source_label' => 'Snapshot',
|
||||
'calculated_at' => '17.05.2026 04:45',
|
||||
],
|
||||
]);
|
||||
|
||||
$controller = makeBackofficeStatisticsController($dashboardService);
|
||||
$firstRequest = makeBackofficeStatisticsRequest($vip);
|
||||
$firstRequest->query->set('month', 4);
|
||||
$firstRequest->query->set('year', 2026);
|
||||
|
||||
$controller->index($firstRequest);
|
||||
$response = $controller->index(makeBackofficeStatisticsRequest($vip));
|
||||
|
||||
expect($response->getData()['selectedMonth'])->toBe(4);
|
||||
expect($response->getData()['selectedYear'])->toBe(2026);
|
||||
expect($response->getData()['performance']['source_label'])->toBe('Snapshot');
|
||||
});
|
||||
|
||||
it('blockiert die Backoffice-Statistik fuer normale aktive User', function () {
|
||||
$user = makeBackofficeStatisticsUser(0);
|
||||
$controller = makeBackofficeStatisticsController();
|
||||
|
||||
expect(fn () => $controller->index(makeBackofficeStatisticsRequest($user)))
|
||||
->toThrow(NotFoundHttpException::class);
|
||||
});
|
||||
|
||||
it('erstellt einen CSV-Export fuer die Statistik-Uebersicht', function () {
|
||||
$vip = makeBackofficeStatisticsUser(1);
|
||||
$dashboardService = Mockery::mock(BackofficeDashboardService::class);
|
||||
$dashboardService
|
||||
->shouldReceive('overview')
|
||||
->once()
|
||||
->andReturn([
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'metric_labels' => [],
|
||||
'lines' => [
|
||||
[
|
||||
'label' => 'Linie 1',
|
||||
'consultants' => 2,
|
||||
'new_partners' => 1,
|
||||
'team_partner_abos' => 1,
|
||||
'team_partner_abos_new' => 1,
|
||||
'team_customer_abos' => 1,
|
||||
'team_customer_abos_new' => 1,
|
||||
'own_points' => 400,
|
||||
'external_points' => 700,
|
||||
'customer_abo_points' => 700,
|
||||
'customer_single_order_points' => 0,
|
||||
'customer_other_points' => 0,
|
||||
'total_points' => 1100,
|
||||
'shop_1000' => 1,
|
||||
'turnover_net' => 300,
|
||||
],
|
||||
],
|
||||
'totals' => [
|
||||
'label' => 'Summe',
|
||||
'consultants' => 2,
|
||||
'new_partners' => 1,
|
||||
'team_partner_abos' => 1,
|
||||
'team_partner_abos_new' => 1,
|
||||
'team_customer_abos' => 1,
|
||||
'team_customer_abos_new' => 1,
|
||||
'own_points' => 400,
|
||||
'external_points' => 700,
|
||||
'customer_abo_points' => 700,
|
||||
'customer_single_order_points' => 0,
|
||||
'customer_other_points' => 0,
|
||||
'total_points' => 1100,
|
||||
'shop_1000' => 1,
|
||||
'turnover_net' => 300,
|
||||
],
|
||||
]);
|
||||
|
||||
$controller = makeBackofficeStatisticsController($dashboardService);
|
||||
$request = makeBackofficeStatisticsRequest($vip);
|
||||
$request->query->set('month', 5);
|
||||
$request->query->set('year', 2026);
|
||||
|
||||
$response = $controller->overviewExport($request);
|
||||
$content = backofficeStatisticsStreamedContent($response);
|
||||
|
||||
expect($response)->toBeInstanceOf(StreamedResponse::class);
|
||||
expect($response->headers->get('content-disposition'))->toContain('backoffice-statistik-uebersicht-05-2026.csv');
|
||||
expect($content)->toContain('Linie;Berater;Neupartner;Teamabos');
|
||||
expect($content)->toContain('Neue Teamabos');
|
||||
expect($content)->toContain('"Linie 1";2;1;1;1;1;1;400;700;700;0;0;1100;1;300');
|
||||
expect($content)->toContain('Summe;2;1;1;1;1;1;400;700;700;0;0;1100;1;300');
|
||||
});
|
||||
|
||||
it('erstellt einen CSV-Export fuer Detaildaten mit Karriere-Level und Summenzeile', function () {
|
||||
$vip = makeBackofficeStatisticsUser(1);
|
||||
$drilldownService = Mockery::mock(BackofficeDrilldownService::class);
|
||||
$drilldownService
|
||||
->shouldReceive('details')
|
||||
->once()
|
||||
->with($vip, 0, 'shop_1000', 5, 2026)
|
||||
->andReturn([
|
||||
'metric' => 'shop_1000',
|
||||
'metric_label' => '1000 Punkte Shop',
|
||||
'line' => 0,
|
||||
'line_label' => 'Alle Linien',
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'rows' => [
|
||||
[
|
||||
'name' => 'Max Mustermann',
|
||||
'email' => 'max@example.test',
|
||||
'career_level' => 'Partner',
|
||||
'own_points' => 400,
|
||||
'external_points' => 700,
|
||||
'customer_abo_points' => 700,
|
||||
'customer_single_order_points' => 0,
|
||||
'customer_other_points' => 0,
|
||||
'total_points' => 1100,
|
||||
],
|
||||
],
|
||||
'summary' => [
|
||||
'count' => 1,
|
||||
'points' => 0,
|
||||
'own_points' => 400,
|
||||
'external_points' => 700,
|
||||
'customer_abo_points' => 700,
|
||||
'customer_single_order_points' => 0,
|
||||
'customer_other_points' => 0,
|
||||
'total_points' => 1100,
|
||||
'deliveries' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
$controller = makeBackofficeStatisticsController(null, $drilldownService);
|
||||
$request = makeBackofficeStatisticsRequest($vip);
|
||||
$request->query->set('line', 0);
|
||||
$request->query->set('metric', 'shop_1000');
|
||||
$request->query->set('month', 5);
|
||||
$request->query->set('year', 2026);
|
||||
|
||||
$response = $controller->export($request);
|
||||
$content = backofficeStatisticsStreamedContent($response);
|
||||
|
||||
expect($response)->toBeInstanceOf(StreamedResponse::class);
|
||||
expect($response->headers->get('content-disposition'))->toContain('backoffice-statistik-shop_1000-linie-alle-05-2026.csv');
|
||||
expect($content)->toContain('Name;E-Mail;Karriere-Level;Eigenpunkte;"Externe Punkte"');
|
||||
expect($content)->toContain('"Max Mustermann";max@example.test;Partner;400;700;700;0;0;1100');
|
||||
expect($content)->toContain('Summe;"1 Eintraege";;400;700;700;0;0;1100');
|
||||
});
|
||||
|
||||
it('nutzt den gespeicherten Zeitraum fuer Detailansicht und Export', function () {
|
||||
$vip = makeBackofficeStatisticsUser(1);
|
||||
$dashboardService = Mockery::mock(BackofficeDashboardService::class);
|
||||
$drilldownService = Mockery::mock(BackofficeDrilldownService::class);
|
||||
|
||||
$dashboardService
|
||||
->shouldReceive('overview')
|
||||
->once()
|
||||
->andReturn([
|
||||
'month' => 4,
|
||||
'year' => 2026,
|
||||
'metric_labels' => [],
|
||||
'lines' => [],
|
||||
'totals' => [],
|
||||
]);
|
||||
|
||||
$drilldownService
|
||||
->shouldReceive('details')
|
||||
->once()
|
||||
->with($vip, 1, 'consultants', 4, 2026)
|
||||
->andReturn([
|
||||
'metric' => 'consultants',
|
||||
'metric_label' => 'Berater',
|
||||
'line' => 1,
|
||||
'line_label' => 'Linie 1',
|
||||
'month' => 4,
|
||||
'year' => 2026,
|
||||
'rows' => [],
|
||||
'summary' => ['count' => 0],
|
||||
]);
|
||||
|
||||
$drilldownService
|
||||
->shouldReceive('details')
|
||||
->once()
|
||||
->with($vip, 1, 'consultants', 4, 2026)
|
||||
->andReturn([
|
||||
'metric' => 'consultants',
|
||||
'metric_label' => 'Berater',
|
||||
'line' => 1,
|
||||
'line_label' => 'Linie 1',
|
||||
'month' => 4,
|
||||
'year' => 2026,
|
||||
'rows' => [],
|
||||
'summary' => ['count' => 0],
|
||||
]);
|
||||
|
||||
$controller = makeBackofficeStatisticsController($dashboardService, $drilldownService);
|
||||
$indexRequest = makeBackofficeStatisticsRequest($vip);
|
||||
$indexRequest->query->set('month', 4);
|
||||
$indexRequest->query->set('year', 2026);
|
||||
$controller->index($indexRequest);
|
||||
|
||||
$detailsRequest = makeBackofficeStatisticsRequest($vip);
|
||||
$detailsRequest->query->set('line', 1);
|
||||
$detailsRequest->query->set('metric', 'consultants');
|
||||
$detailsResponse = $controller->details($detailsRequest);
|
||||
|
||||
$exportRequest = makeBackofficeStatisticsRequest($vip);
|
||||
$exportRequest->query->set('line', 1);
|
||||
$exportRequest->query->set('metric', 'consultants');
|
||||
$exportResponse = $controller->export($exportRequest);
|
||||
|
||||
expect($detailsResponse->getData()['selectedMonth'])->toBe(4);
|
||||
expect($detailsResponse->getData()['selectedYear'])->toBe(2026);
|
||||
expect($exportResponse->headers->get('content-disposition'))->toContain('backoffice-statistik-consultants-linie-1-04-2026.csv');
|
||||
});
|
||||
|
||||
it('rendert eine Suche in der Detailtabelle', function () {
|
||||
$html = file_get_contents(resource_path('views/user/backoffice/statistics/details.blade.php'));
|
||||
|
||||
expect($html)->toContain('backoffice-statistics-detail-search');
|
||||
expect($html)->toContain('backoffice-statistics-detail-table');
|
||||
expect($html)->toContain('data-sortable="true"');
|
||||
expect($html)->toContain('getSortValue');
|
||||
expect($html)->toContain('user_backoffice_statistics_export');
|
||||
expect($html)->toContain('Datenschutz-Hinweis');
|
||||
expect($html)->toContain('Karriere-Level');
|
||||
expect($html)->not->toContain('Qualifikation');
|
||||
|
||||
$indexHtml = file_get_contents(resource_path('views/user/backoffice/statistics/index.blade.php'));
|
||||
|
||||
expect($indexHtml)->toContain('user_backoffice_statistics_overview_export');
|
||||
expect($indexHtml)->toContain('Datenquelle:');
|
||||
expect($indexHtml)->toContain('Berechnet in');
|
||||
});
|
||||
|
||||
it('erfasst die Herkunft bei Kundenbestellungen im Shop-Checkout', function () {
|
||||
$checkoutView = file_get_contents(resource_path('views/web/templates/checkout.blade.php'));
|
||||
$checkoutController = file_get_contents(app_path('Http/Controllers/Web/CheckoutController.php'));
|
||||
$checkoutRepository = file_get_contents(app_path('Repositories/CheckoutRepository.php'));
|
||||
$orderDetail = file_get_contents(resource_path('views/admin/sales/_detail.blade.php'));
|
||||
|
||||
expect($checkoutView)->toContain('customer_order_source');
|
||||
expect($checkoutView)->toContain('Wie bist du auf uns aufmerksam geworden?');
|
||||
expect($checkoutController)->toContain("Request::get('is_from') === 'shopping'");
|
||||
expect($checkoutController)->toContain('customer_order_source');
|
||||
expect($checkoutRepository)->toContain('$shopping_user->is_from === \'shopping\'');
|
||||
expect($checkoutRepository)->toContain('customer_order_source_comment');
|
||||
expect($orderDetail)->toContain('getCustomerOrderSourceLabel');
|
||||
|
||||
$shoppingOrder = (new ShoppingOrder)->forceFill([
|
||||
'customer_order_source' => 'social_media',
|
||||
]);
|
||||
|
||||
expect($shoppingOrder->getCustomerOrderSourceLabel())->toBe('Social Media');
|
||||
});
|
||||
409
tests/Unit/Services/BackofficeDashboardServiceTest.php
Normal file
409
tests/Unit/Services/BackofficeDashboardServiceTest.php
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
<?php
|
||||
|
||||
use App\Models\BackofficeStatisticsSnapshot;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Services\Backoffice\BackofficeDashboardService;
|
||||
use App\Services\Backoffice\BackofficeDrilldownService;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Schema::dropIfExists('user_sales_volumes');
|
||||
Schema::dropIfExists('backoffice_statistics_snapshots');
|
||||
Schema::dropIfExists('user_abo_orders');
|
||||
Schema::dropIfExists('user_abo_items');
|
||||
Schema::dropIfExists('user_abos');
|
||||
Schema::dropIfExists('payment_transactions');
|
||||
Schema::dropIfExists('shopping_payments');
|
||||
Schema::dropIfExists('shopping_orders');
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('user_levels');
|
||||
Schema::dropIfExists('user_accounts');
|
||||
|
||||
Schema::create('user_accounts', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('first_name')->nullable();
|
||||
$table->string('last_name')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('user_levels', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->unsignedInteger('pos')->nullable();
|
||||
$table->boolean('active')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('users', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('email')->unique();
|
||||
$table->string('password');
|
||||
$table->unsignedInteger('account_id')->nullable();
|
||||
$table->unsignedInteger('m_level')->nullable();
|
||||
$table->unsignedInteger('m_sponsor')->nullable();
|
||||
$table->boolean('active')->default(false);
|
||||
$table->timestamp('active_date')->nullable();
|
||||
$table->unsignedTinyInteger('admin')->default(0);
|
||||
$table->unsignedTinyInteger('wizard')->default(0);
|
||||
$table->unsignedTinyInteger('blocked')->default(0);
|
||||
$table->char('lang', 2)->default('de');
|
||||
$table->timestamp('payment_account')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('user_abos', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('user_id')->nullable();
|
||||
$table->unsignedInteger('member_id')->nullable();
|
||||
$table->char('is_for', 2)->nullable();
|
||||
$table->boolean('active')->default(true);
|
||||
$table->unsignedTinyInteger('status')->default(2);
|
||||
$table->date('start_date')->nullable();
|
||||
$table->date('next_date')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('user_abo_items', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('user_abo_id');
|
||||
$table->unsignedInteger('product_id')->nullable();
|
||||
$table->unsignedTinyInteger('comp')->nullable();
|
||||
$table->unsignedInteger('qty')->default(1);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('user_abo_orders', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('user_abo_id');
|
||||
$table->unsignedInteger('shopping_order_id');
|
||||
$table->unsignedTinyInteger('status')->default(2);
|
||||
$table->boolean('paid')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('shopping_orders', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->boolean('is_abo')->default(false);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::create('shopping_payments', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('shopping_order_id');
|
||||
$table->string('clearingtype')->nullable();
|
||||
$table->string('reference')->nullable();
|
||||
$table->integer('amount')->nullable();
|
||||
$table->string('currency')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('payment_transactions', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('shopping_payment_id');
|
||||
$table->string('request')->nullable();
|
||||
$table->unsignedInteger('errorcode')->nullable();
|
||||
$table->string('errormessage')->nullable();
|
||||
$table->string('customermessage')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('user_sales_volumes', function ($table) {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('shopping_order_id')->nullable();
|
||||
$table->unsignedTinyInteger('month')->nullable();
|
||||
$table->unsignedSmallInteger('year')->nullable();
|
||||
$table->decimal('month_KP_points', 13, 2)->nullable();
|
||||
$table->decimal('month_shop_points', 13, 2)->nullable();
|
||||
$table->decimal('month_total_net', 13, 2)->nullable();
|
||||
$table->decimal('month_shop_total_net', 13, 2)->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('backoffice_statistics_snapshots', function ($table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedSmallInteger('year');
|
||||
$table->unsignedTinyInteger('month');
|
||||
$table->json('payload');
|
||||
$table->timestamp('calculated_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
});
|
||||
|
||||
it('aggregiert Linien, Abos und Punkte fuer die Backoffice-Statistik', function () {
|
||||
UserLevel::forceCreate([
|
||||
'id' => 1,
|
||||
'name' => 'Partner',
|
||||
'pos' => 1,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$root = User::forceCreate([
|
||||
'email' => 'root@test.test',
|
||||
'password' => 'secret',
|
||||
'lang' => 'de',
|
||||
'admin' => 1,
|
||||
]);
|
||||
|
||||
$lineOne = User::forceCreate([
|
||||
'email' => 'line-one@test.test',
|
||||
'password' => 'secret',
|
||||
'lang' => 'de',
|
||||
'm_sponsor' => $root->id,
|
||||
'm_level' => 1,
|
||||
'active_date' => '2026-05-03 00:00:00',
|
||||
'payment_account' => '2030-01-01 00:00:00',
|
||||
]);
|
||||
|
||||
$lineTwo = User::forceCreate([
|
||||
'email' => 'line-two@test.test',
|
||||
'password' => 'secret',
|
||||
'lang' => 'de',
|
||||
'm_sponsor' => $lineOne->id,
|
||||
'm_level' => 1,
|
||||
'active_date' => '2026-04-03 00:00:00',
|
||||
'payment_account' => '2030-01-01 00:00:00',
|
||||
]);
|
||||
|
||||
User::forceCreate([
|
||||
'email' => 'expired-line-one@test.test',
|
||||
'password' => 'secret',
|
||||
'lang' => 'de',
|
||||
'm_sponsor' => $root->id,
|
||||
'm_level' => 1,
|
||||
'active_date' => '2026-01-03 00:00:00',
|
||||
'payment_account' => '2020-01-01 00:00:00',
|
||||
]);
|
||||
|
||||
$sponsor = $lineTwo;
|
||||
|
||||
foreach (range(3, 9) as $lineNumber) {
|
||||
$sponsor = User::forceCreate([
|
||||
'email' => 'line-'.$lineNumber.'@test.test',
|
||||
'password' => 'secret',
|
||||
'lang' => 'de',
|
||||
'm_sponsor' => $sponsor->id,
|
||||
'm_level' => 1,
|
||||
'active_date' => '2026-04-03 00:00:00',
|
||||
'payment_account' => '2030-01-01 00:00:00',
|
||||
]);
|
||||
}
|
||||
|
||||
$activePartnerAbo = UserAbo::forceCreate([
|
||||
'user_id' => $lineOne->id,
|
||||
'member_id' => $lineOne->id,
|
||||
'is_for' => 'me',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'start_date' => '2026-05-04',
|
||||
'next_date' => '2026-06-01',
|
||||
]);
|
||||
|
||||
UserAbo::forceCreate([
|
||||
'user_id' => $lineTwo->id,
|
||||
'member_id' => $lineOne->id,
|
||||
'is_for' => 'ot',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'start_date' => '2026-05-05',
|
||||
'next_date' => '2026-06-01',
|
||||
]);
|
||||
|
||||
$pausedPartnerAbo = UserAbo::forceCreate([
|
||||
'user_id' => $lineOne->id,
|
||||
'member_id' => $lineOne->id,
|
||||
'is_for' => 'me',
|
||||
'active' => true,
|
||||
'status' => 3,
|
||||
'start_date' => '2026-04-15',
|
||||
'next_date' => '2026-06-02',
|
||||
]);
|
||||
|
||||
$aboOrderId = DB::table('shopping_orders')->insertGetId([
|
||||
'is_abo' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$singleOrderId = DB::table('shopping_orders')->insertGetId([
|
||||
'is_abo' => false,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$failedAboOrderId = DB::table('shopping_orders')->insertGetId([
|
||||
'is_abo' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$failedAboPaymentId = DB::table('shopping_payments')->insertGetId([
|
||||
'shopping_order_id' => $failedAboOrderId,
|
||||
'clearingtype' => 'cc',
|
||||
'reference' => 'abo-failed',
|
||||
'amount' => 9900,
|
||||
'currency' => 'EUR',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('payment_transactions')->insert([
|
||||
'shopping_payment_id' => $failedAboPaymentId,
|
||||
'request' => 'authorization',
|
||||
'errorcode' => 902,
|
||||
'errormessage' => 'Bank hat abgelehnt',
|
||||
'customermessage' => 'Bitte Zahlungsmittel prüfen',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('user_abo_orders')->insert([
|
||||
'user_abo_id' => $pausedPartnerAbo->id,
|
||||
'shopping_order_id' => $failedAboOrderId,
|
||||
'status' => 3,
|
||||
'paid' => false,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
UserSalesVolume::forceCreate([
|
||||
'user_id' => $lineOne->id,
|
||||
'shopping_order_id' => $aboOrderId,
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'month_KP_points' => 400,
|
||||
'month_shop_points' => 700,
|
||||
'month_total_net' => 100,
|
||||
'month_shop_total_net' => 200,
|
||||
]);
|
||||
|
||||
UserSalesVolume::forceCreate([
|
||||
'user_id' => $lineTwo->id,
|
||||
'shopping_order_id' => $singleOrderId,
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'month_KP_points' => 200,
|
||||
'month_shop_points' => 300,
|
||||
'month_total_net' => 50,
|
||||
'month_shop_total_net' => 60,
|
||||
]);
|
||||
|
||||
$overview = (new BackofficeDashboardService)->overview($root, 5, 2026);
|
||||
|
||||
expect($overview['lines'][1]['consultants'])->toBe(2);
|
||||
expect($overview['lines'][1]['new_partners'])->toBe(1);
|
||||
expect($overview['lines'][1]['team_partner_abos'])->toBe(2);
|
||||
expect($overview['lines'][1]['team_partner_abos_new'])->toBe(1);
|
||||
expect($overview['lines'][1]['team_customer_abos'])->toBe(1);
|
||||
expect($overview['lines'][1]['team_customer_abos_new'])->toBe(1);
|
||||
expect($overview['lines'][1]['own_points'])->toBe(400.0);
|
||||
expect($overview['lines'][1]['external_points'])->toBe(700.0);
|
||||
expect($overview['lines'][1]['customer_abo_points'])->toBe(700.0);
|
||||
expect($overview['lines'][1]['customer_single_order_points'])->toBe(0.0);
|
||||
expect($overview['lines'][1]['customer_other_points'])->toBe(0.0);
|
||||
expect($overview['lines'][1]['total_points'])->toBe(1100.0);
|
||||
expect($overview['lines'][1]['shop_1000'])->toBe(1);
|
||||
expect($overview['lines'][2]['consultants'])->toBe(1);
|
||||
expect($overview['lines'])->toHaveKey(9);
|
||||
expect($overview['lines'])->not->toHaveKey(10);
|
||||
expect($overview['lines'][9]['consultants'])->toBe(1);
|
||||
expect($overview['totals']['total_points'])->toBe(1600.0);
|
||||
|
||||
$details = (new BackofficeDrilldownService(new BackofficeDashboardService))->details($root, 0, 'total_points', 5, 2026);
|
||||
|
||||
expect($details['line_label'])->toBe('Alle Linien');
|
||||
expect($details['rows'])->toHaveCount(2);
|
||||
expect($details['summary']['own_points'])->toBe(600.0);
|
||||
expect($details['summary']['external_points'])->toBe(1000.0);
|
||||
expect($details['summary']['customer_abo_points'])->toBe(700.0);
|
||||
expect($details['summary']['customer_single_order_points'])->toBe(300.0);
|
||||
expect($details['summary']['customer_other_points'])->toBe(0.0);
|
||||
expect($details['summary']['total_points'])->toBe(1600.0);
|
||||
|
||||
$shop1000Details = (new BackofficeDrilldownService(new BackofficeDashboardService))->details($root, 0, 'shop_1000', 5, 2026);
|
||||
|
||||
expect($shop1000Details['rows'])->toHaveCount(1);
|
||||
expect($shop1000Details['rows'][0]['career_level'])->toBe('Partner');
|
||||
expect($shop1000Details['rows'][0]['own_points'])->toBe(400.0);
|
||||
expect($shop1000Details['rows'][0]['customer_abo_points'])->toBe(700.0);
|
||||
|
||||
$consultantDetails = (new BackofficeDrilldownService(new BackofficeDashboardService))->details($root, 1, 'consultants', 5, 2026);
|
||||
|
||||
expect($consultantDetails['rows'])->toHaveCount(2);
|
||||
expect($consultantDetails['summary']['count'])->toBe(2);
|
||||
expect(collect($consultantDetails['rows'])->pluck('is_account_active')->all())->toBe([true, false]);
|
||||
expect(collect($consultantDetails['rows'])->pluck('career_level')->all())->toBe(['Partner', 'Partner']);
|
||||
|
||||
$aboDetails = (new BackofficeDrilldownService(new BackofficeDashboardService))->details($root, 1, 'team_partner_abos', 5, 2026);
|
||||
|
||||
expect($aboDetails['rows'][0]['start_date'])->toBe('04.05.2026');
|
||||
expect($aboDetails['rows'][0]['is_new_this_month'])->toBeTrue();
|
||||
expect($aboDetails['rows'])->toHaveCount(2);
|
||||
expect(collect($aboDetails['rows'])->firstWhere('abo_id', $activePartnerAbo->id)['status_reason'])->toBeNull();
|
||||
expect(collect($aboDetails['rows'])->firstWhere('abo_id', $pausedPartnerAbo->id)['status_reason'])->toBe('[902] Bank hat abgelehnt');
|
||||
expect(collect($aboDetails['rows'])->firstWhere('abo_id', $pausedPartnerAbo->id)['is_new_this_month'])->toBeFalse();
|
||||
});
|
||||
|
||||
it('verwendet gespeicherte Snapshots fuer abgeschlossene Monate', function () {
|
||||
$root = User::forceCreate([
|
||||
'email' => 'snapshot-root@test.test',
|
||||
'password' => 'secret',
|
||||
'lang' => 'de',
|
||||
'admin' => 1,
|
||||
]);
|
||||
|
||||
$payload = [
|
||||
'month' => 4,
|
||||
'year' => 2026,
|
||||
'metric_labels' => [],
|
||||
'lines' => [
|
||||
1 => [
|
||||
'line' => 1,
|
||||
'label' => 'Linie 1',
|
||||
'user_ids' => [],
|
||||
'consultants' => 99,
|
||||
'new_partners' => 0,
|
||||
'team_partner_abos' => 0,
|
||||
'team_partner_abos_new' => 0,
|
||||
'team_customer_abos' => 0,
|
||||
'team_customer_abos_new' => 0,
|
||||
'own_points' => 0,
|
||||
'external_points' => 0,
|
||||
'customer_abo_points' => 0,
|
||||
'customer_single_order_points' => 0,
|
||||
'customer_other_points' => 0,
|
||||
'total_points' => 0,
|
||||
'turnover_net' => 0,
|
||||
'shop_1000' => 0,
|
||||
],
|
||||
],
|
||||
'totals' => [
|
||||
'label' => 'Summe',
|
||||
'consultants' => 99,
|
||||
],
|
||||
];
|
||||
|
||||
BackofficeStatisticsSnapshot::create([
|
||||
'user_id' => $root->id,
|
||||
'year' => 2026,
|
||||
'month' => 4,
|
||||
'payload' => $payload,
|
||||
'calculated_at' => now(),
|
||||
]);
|
||||
|
||||
$overview = (new BackofficeDashboardService)->overview($root, 4, 2026);
|
||||
|
||||
expect($overview['lines'][1]['consultants'])->toBe(99);
|
||||
expect($overview['_meta']['source'])->toBe('snapshot');
|
||||
expect($overview['_meta']['source_label'])->toBe('Snapshot');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue