previousHttpHost = $_SERVER['HTTP_HOST'] ?? null; $_SERVER['HTTP_HOST'] = config('domains.domains.crm.host'); $this->refreshApplication(); $this->artisan('migrate'); }); afterEach(function () { // Host wieder zurücksetzen, sonst booten nachfolgende Tests mit den CRM-Routen. if ($this->previousHttpHost === null) { unset($_SERVER['HTTP_HOST']); } else { $_SERVER['HTTP_HOST'] = $this->previousHttpHost; } }); function transferConsultant(array $attributes = [], string $firstName = 'Test'): User { $account = UserAccount::create([ 'first_name' => $firstName, 'last_name' => 'Berater', 'm_first_name' => $firstName, 'm_last_name' => 'Berater', 'm_account' => 'M'.random_int(1000, 9999), ]); return User::forceCreate(array_merge([ 'email' => strtolower($firstName).'-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'active' => 1, 'blocked' => 0, 'wizard' => 100, 'payment_account' => now()->addYear(), 'account_id' => $account->id, ], $attributes))->fresh(); } function transferCustomer(User $member, array $attributes = []): ShoppingUser { $country = Country::first() ?? Country::create([ 'code' => 'DE', 'phone' => '49', 'en' => 'Germany', 'de' => 'Deutschland', 'es' => 'Alemania', 'fr' => 'Allemagne', 'it' => 'Germania', 'ru' => 'Германия', ]); return ShoppingUser::create(array_merge([ 'auth_user_id' => 0, 'member_id' => $member->id, 'number' => random_int(1000, 99999), 'billing_firstname' => 'Kunde', 'billing_lastname' => 'Test', 'billing_email' => 'kunde-'.uniqid('', true).'@example.com', 'billing_country_id' => $country->id, 'shipping_country_id' => $country->id, ], $attributes)); } it('hängt die Firstline um, die Teilbäume darunter bleiben erhalten', function () { $source = transferConsultant([], 'Nadine'); $target = transferConsultant([], 'Claudia'); $childA = transferConsultant(['m_sponsor' => $source->id], 'ChildA'); $childB = transferConsultant(['m_sponsor' => $source->id, 'active' => 0], 'ChildB'); $grandChild = transferConsultant(['m_sponsor' => $childA->id], 'Grand'); $unrelated = transferConsultant(['m_sponsor' => $target->id], 'Other'); $summary = app(ConsultantTransferService::class)->transfer($source, $target, 1); expect($summary['firstline'])->toBe(2); expect($childA->fresh()->m_sponsor)->toBe($target->id); expect($childB->fresh()->m_sponsor)->toBe($target->id); expect($grandChild->fresh()->m_sponsor)->toBe($childA->id); expect($unrelated->fresh()->m_sponsor)->toBe($target->id); expect($source->fresh()->m_sponsor)->toBeNull(); expect(UserCleanUpLog::where('inactive_sponsor_id', $source->id)->where('new_sponsor_id', $target->id)->count())->toBe(2); }); it('überträgt Kunden, Kunden-Abos und Portal-Logins, lässt Bestellungen aber beim alten Berater', function () { $source = transferConsultant([], 'Nadine'); $target = transferConsultant([], 'Claudia'); $customer = transferCustomer($source); $deletedCustomer = transferCustomer($source); $deletedCustomer->delete(); $foreignCustomer = transferCustomer($target); $shop = UserShop::create(['user_id' => $source->id, 'name' => 'Shop', 'slug' => 'shop-'.uniqid()]); $order = ShoppingOrder::create([ 'shopping_user_id' => $customer->id, 'member_id' => $source->id, 'auth_user_id' => 0, 'user_shop_id' => $shop->id, 'country_id' => $customer->billing_country_id, 'total' => 10, 'txaction' => 'appointed', 'mode' => 'test', ]); $abo = UserAbo::create([ 'user_id' => null, 'member_id' => $source->id, 'shopping_user_id' => $customer->id, 'is_for' => 'ot', 'email' => $customer->billing_email, 'payone_userid' => 1, 'clearingtype' => 'cc', 'active' => true, 'status' => 2, 'abo_interval' => 1, 'next_date' => now()->addDays(3), ]); $portal = Customer::create([ 'email' => $customer->billing_email, 'shopping_user_id' => $customer->id, 'member_id' => $source->id, 'number' => $customer->number, ]); $summary = app(ConsultantTransferService::class)->transfer($source, $target, 1); expect($summary['customers'])->toBe(2); expect($summary['abos'])->toBe(1); expect($summary['portal_customers'])->toBe(1); expect($customer->fresh()->member_id)->toBe($target->id); expect(ShoppingUser::withTrashed()->find($deletedCustomer->id)->member_id)->toBe($target->id); expect($foreignCustomer->fresh()->member_id)->toBe($target->id); expect($abo->fresh()->member_id)->toBe($target->id); expect($portal->fresh()->member_id)->toBe($target->id); expect($order->fresh()->member_id)->toBe($source->id); expect(ShoppingUserMemberLog::where('pre_member_id', $source->id)->where('new_member_id', $target->id)->count())->toBe(2); }); it('überträgt geparkte Sponsor-Bezüge deaktivierter Berater', function () { $source = transferConsultant([], 'Nadine'); $target = transferConsultant([], 'Claudia'); $parked = transferConsultant(['m_sponsor' => null, 'pre_sponsor' => $source->id, 'active' => 0], 'Parked'); $summary = app(ConsultantTransferService::class)->transfer($source, $target, 1); expect($summary['pre_sponsor'])->toBe(1); expect($parked->fresh()->pre_sponsor)->toBe($target->id); }); it('erlaubt einen Zielberater aus der Firstline, der seinen eigenen Sponsor behält', function () { $source = transferConsultant([], 'Nadine'); $target = transferConsultant(['m_sponsor' => $source->id], 'Claudia'); $sibling = transferConsultant(['m_sponsor' => $source->id], 'Sibling'); app(ConsultantTransferService::class)->transfer($source, $target, 1); expect($target->fresh()->m_sponsor)->toBe($source->id); expect($sibling->fresh()->m_sponsor)->toBe($target->id); }); it('lehnt einen Zielberater unterhalb der Firstline ab, weil das einen Kreis erzeugen würde', function () { $source = transferConsultant([], 'Nadine'); $child = transferConsultant(['m_sponsor' => $source->id], 'Child'); $target = transferConsultant(['m_sponsor' => $child->id], 'Claudia'); expect(fn () => app(ConsultantTransferService::class)->transfer($source, $target, 1)) ->toThrow(ConsultantTransferException::class, __('msg.transfer_error_target_in_downline')); expect($child->fresh()->m_sponsor)->toBe($source->id); }); it('lehnt denselben Berater als Ziel ab', function () { $source = transferConsultant([], 'Nadine'); expect(fn () => app(ConsultantTransferService::class)->transfer($source, $source, 1)) ->toThrow(ConsultantTransferException::class, __('msg.transfer_error_same_user')); }); it('lehnt einen inaktiven Zielberater ab', function (array $attributes) { $source = transferConsultant([], 'Nadine'); $target = transferConsultant($attributes, 'Claudia'); $child = transferConsultant(['m_sponsor' => $source->id], 'Child'); try { app(ConsultantTransferService::class)->transfer($source, $target, 1); $this->fail('Exception erwartet'); } catch (ConsultantTransferException $e) { expect($e->getMessage())->toBe(__('msg.transfer_error_target_inactive')); } expect($child->fresh()->m_sponsor)->toBe($source->id); })->with([ 'deaktiviert' => [['active' => 0]], 'gesperrt' => [['blocked' => 1]], 'Account abgelaufen' => [['payment_account' => now()->subDay()]], ]); it('liefert eine Vorschau mit allen Zählern', function () { $source = transferConsultant([], 'Nadine'); $childA = transferConsultant(['m_sponsor' => $source->id], 'ChildA'); transferConsultant(['m_sponsor' => $childA->id], 'Grand'); transferConsultant(['pre_sponsor' => $source->id, 'active' => 0], 'Parked'); $customer = transferCustomer($source); $deleted = transferCustomer($source); $deleted->delete(); Customer::create(['email' => $customer->billing_email, 'shopping_user_id' => $customer->id, 'member_id' => $source->id]); $preview = app(ConsultantTransferService::class)->preview($source); expect($preview['firstline_count'])->toBe(1) ->and($preview['downline_count'])->toBe(2) ->and($preview['pre_sponsor_count'])->toBe(1) ->and($preview['customers_count'])->toBe(2) ->and($preview['customers_deleted_count'])->toBe(1) ->and($preview['portal_customers_count'])->toBe(1) ->and($preview['firstline']->first()->id)->toBe($childA->id); }); it('ist nur für Superadmins erreichbar', function () { $admin = transferConsultant(['admin' => 2], 'Admin'); $this->actingAs($admin)->get(route('admin_user_transfer'))->assertForbidden(); }); it('zeigt Superadmins die Seite mit Vorschau und führt die Übertragung nach Bestätigung aus', function () { $superadmin = transferConsultant(['admin' => 3], 'Super'); $source = transferConsultant([], 'Nadine'); $target = transferConsultant([], 'Claudia'); $child = transferConsultant(['m_sponsor' => $source->id], 'Child'); $customer = transferCustomer($source); $this->actingAs($superadmin)->get(route('admin_user_transfer')) ->assertOk() ->assertSee(__('navigation.user_transfer')); $this->actingAs($superadmin) ->post(route('admin_user_transfer_preview'), ['source_user_id' => $source->id, 'target_user_id' => $target->id]) ->assertOk() ->assertSee('2. Vorschau und Bestätigung') ->assertSee($child->email); $this->actingAs($superadmin) ->post(route('admin_user_transfer_execute'), ['source_user_id' => $source->id, 'target_user_id' => $target->id]) ->assertRedirect() ->assertSessionHasErrors('confirm_transfer'); expect($child->fresh()->m_sponsor)->toBe($source->id); $this->actingAs($superadmin) ->post(route('admin_user_transfer_execute'), ['source_user_id' => $source->id, 'target_user_id' => $target->id, 'confirm_transfer' => 1]) ->assertRedirect(route('admin_user_transfer')) ->assertSessionHas('alert-success'); expect($child->fresh()->m_sponsor)->toBe($target->id); expect($customer->fresh()->member_id)->toBe($target->id); }); it('zeigt bei unzulässigem Ziel eine Fehlermeldung statt der Vorschau', function () { $superadmin = transferConsultant(['admin' => 3], 'Super'); $source = transferConsultant([], 'Nadine'); $target = transferConsultant(['active' => 0], 'Claudia'); $this->actingAs($superadmin) ->post(route('admin_user_transfer_preview'), ['source_user_id' => $source->id, 'target_user_id' => $target->id]) ->assertRedirect(route('admin_user_transfer')) ->assertSessionHas('alert-error', __('msg.transfer_error_target_inactive')); });