mivita/tests/Feature/AboLockDaysSettingTest.php
Kevin 65b867f8ee Abo Einmalprodukte: Phasen 5-7, konfigurierbare Sperrfristen, Lokalisierung & Dashboard
Rechnung/Lieferschein:
- PDF-Gruppierung Abo- vs. Einmal-Artikel (is_abo_addon), rueckwaertskompatibel
- Vorschau-Command sales:preview-invoice (umgeht Test-System-Gate, ohne Rechnungsnummer)

Reminder-Mail (vorbereitet, Cron inaktiv):
- MailAboExecutionReminder + Template, abo:send-execution-reminder, Spalte onetime_reminder_sent_for
- Berater -> Sales Center, Kunde -> Portal; Vorschau abo:preview-reminder
- Datum-Fix: formatDateDB() statt formatDate() (ICU-Format)

Sperrfristen konfigurierbar (Admin-Settings):
- abo-lock-days-change (Liefertag + Abo-Produkte), abo-lock-days-pause-cancel
- Meldungen mit :lock-Platzhalter; Pausieren/Kuendigen vom Change-Lock entkoppelt
- Abo-Produkt-Aenderungen im Lock-Fenster server- + clientseitig eingefroren
- Selbstbedienung: Add-only/Lock nach $view (kein Admin-Bypass), Stale-Cache-Fix beim Mengen-Update

Dashboard:
- Einmalprodukte-Card + Team-Abo-KPI-Card, News-Card responsiv (col-xl-6)

Lokalisierung:
- Sprachdateien en/es/fr vollstaendig mit de synchronisiert (inkl. marketingplan/portal)
- LocalizationSyncTest sichert Key-Gleichheit ab

Tests: AboOneTimeInvoiceGrouping, AboExecutionReminder, AboLockDaysSetting,
AboOnetimeDashboardAndMail, PreviewInvoiceCommand, TeamAboOverview + erweiterte View-Tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:38:11 +00:00

192 lines
6.9 KiB
PHP

<?php
use App\Models\Country;
use App\Models\Setting;
use App\Models\ShoppingUser;
use App\Models\UserAbo;
use App\Repositories\AboRepository;
use App\Services\AboHelper;
use App\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(Tests\TestCase::class, RefreshDatabase::class);
afterEach(fn () => Carbon::setTestNow());
describe('Abo-Sperrfristen als Setting', function () {
it('nutzt die Defaults, wenn kein Setting gesetzt ist', function () {
expect(AboRepository::getLockDaysChange())->toBe(10)
->and(AboRepository::getLockDaysPauseCancel())->toBe(3);
});
it('liest die konfigurierten Sperrfristen aus den Settings', function () {
Setting::setContentBySlug('abo-lock-days-change', 5, 'int');
Setting::setContentBySlug('abo-lock-days-pause-cancel', 7, 'int');
expect(AboRepository::getLockDaysChange())->toBe(5)
->and(AboRepository::getLockDaysPauseCancel())->toBe(7);
});
it('fällt bei 0 / leer auf den Default zurück', function () {
Setting::setContentBySlug('abo-lock-days-change', 0, 'int');
expect(AboRepository::getLockDaysChange())->toBe(10);
});
it('gibt eine Änderung frei, die bei Default gesperrt wäre, wenn die Sperrfrist reduziert wird', function () {
Carbon::setTestNow('2026-03-19 00:00:00');
// Liefertag 20, next_date in 6 Tagen → Liefertag-WECHSEL auf 25 testen.
$abo = makeLockTestAbo([
'abo_interval' => 20,
'next_date' => '2026-03-25',
]);
$this->actingAs($abo->user);
$repository = new AboRepository;
$repository->setModel($abo);
// Mit Default (10) ist der Wechsel gesperrt (6 < 10):
expect($repository->update([
'action' => 'abo_update_settings',
'id' => $abo->id,
'view' => 'me',
'abo_interval' => 25,
'abo_is_active' => 'true',
]))->toBeFalse();
// Sperrfrist auf 3 Tage reduzieren → Wechsel jetzt erlaubt (6 ≥ 3, neues Datum 2026-03-25):
Setting::setContentBySlug('abo-lock-days-change', 3, 'int');
expect($repository->update([
'action' => 'abo_update_settings',
'id' => $abo->id,
'view' => 'me',
'abo_interval' => 25,
'abo_is_active' => 'true',
]))->toBeTrue()
->and($abo->refresh()->abo_interval)->toBe(25);
});
it('zeigt die konfigurierte Sperrfrist in der Fehlermeldung (nicht den Default 10)', function () {
Carbon::setTestNow('2026-03-19 00:00:00');
app()->setLocale('de');
Setting::setContentBySlug('abo-lock-days-change', 3, 'int');
// Liefertag-WECHSEL (20 → 25), next_date morgen (1 Tag) → bei Sperrfrist 3 weiterhin gesperrt.
$abo = makeLockTestAbo(['abo_interval' => 20, 'next_date' => '2026-03-20']);
$this->actingAs($abo->user);
$repository = new AboRepository;
$repository->setModel($abo);
expect($repository->update([
'action' => 'abo_update_settings',
'id' => $abo->id,
'view' => 'me',
'abo_interval' => 25,
'abo_is_active' => 'true',
]))->toBeFalse()
->and(session('alert-error'))->toContain('mindestens 3 Tage')
->and(session('alert-error'))->not->toContain('mindestens 10 Tage');
});
it('erlaubt Pausieren im Change-Lock-Fenster, wenn außerhalb der Pause-Sperre (Intervall unverändert)', function () {
Carbon::setTestNow('2026-03-19 00:00:00');
// 5 Tage entfernt: innerhalb Change-Lock (10), außerhalb Pause-Lock (3).
$abo = makeLockTestAbo(['abo_interval' => 20, 'next_date' => '2026-03-24', 'active' => true, 'status' => 2]);
$this->actingAs($abo->user);
$repository = new AboRepository;
$repository->setModel($abo);
// Intervall unverändert (20), kein abo_is_active => Pausieren.
$result = $repository->update([
'action' => 'abo_update_settings',
'id' => $abo->id,
'view' => 'me',
'abo_interval' => 20,
]);
expect($result)->toBeTrue()
->and($abo->refresh()->active)->toBeFalse()
->and($abo->status)->toBe(6);
});
});
describe('isAboChangeLocked (UI-Sperre Liefertag)', function () {
afterEach(fn () => Carbon::setTestNow());
it('ist gesperrt im Fenster, frei außerhalb (User-Ansicht)', function () {
Carbon::setTestNow('2026-03-19 00:00:00');
$abo = makeLockTestAbo(['next_date' => '2026-03-20']); // 1 Tag → < Default 10
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeTrue();
$abo->next_date = '2026-04-19'; // weit entfernt
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeFalse();
});
it('respektiert die konfigurierte Sperrfrist', function () {
Carbon::setTestNow('2026-03-19 00:00:00');
$abo = makeLockTestAbo(['next_date' => '2026-03-24']); // 5 Tage entfernt
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeTrue(); // 5 < 10 (Default)
Setting::setContentBySlug('abo-lock-days-change', 3, 'int');
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeFalse(); // 5 >= 3
});
it('gilt nicht für Admin und nicht ohne next_date', function () {
Carbon::setTestNow('2026-03-19 00:00:00');
$abo = makeLockTestAbo(['next_date' => '2026-03-20']);
expect(AboHelper::isAboChangeLocked($abo, 'admin'))->toBeFalse();
$abo->next_date = null;
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeFalse();
});
});
/**
* @param array<string, mixed> $overrides
*/
function makeLockTestAbo(array $overrides = []): UserAbo
{
$country = Country::create([
'code' => 'DE', 'phone' => '49', 'en' => 'Germany', 'de' => 'Deutschland',
'es' => 'Alemania', 'fr' => 'Allemagne', 'it' => 'Germania', 'ru' => 'Deutschland',
]);
$user = User::forceCreate([
'email' => 'abo-lock-'.uniqid('', true).'@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'admin' => 0,
]);
$shoppingUser = ShoppingUser::create([
'auth_user_id' => $user->id,
'member_id' => $user->id,
'billing_country_id' => $country->id,
'shipping_country_id' => $country->id,
'billing_email' => $user->email,
'is_for' => 'me',
'is_from' => 'user_order',
]);
return UserAbo::create(array_merge([
'user_id' => $user->id,
'member_id' => $user->id,
'shopping_user_id' => $shoppingUser->id,
'is_for' => 'me',
'email' => $user->email,
'payone_userid' => random_int(100000, 999999),
'clearingtype' => 'cc',
'active' => true,
'status' => 2,
'abo_interval' => 5,
'start_date' => '2026-01-05',
'last_date' => '2026-02-05',
'next_date' => '2026-03-05',
], $overrides));
}