A1: Jeder Monatstag ist als Liefertag erlaubt (UserAbo::allowedDeliveryDays), Tage 29-31 liefern in kuerzeren Monaten am letzten Tag. Eine Stelle AboHelper::dateForDayInMonth klammert; setNextDate, Erstausfuehrung, Admin-Termin und Pause rechnen darueber, der Folgemonat immer aus dem Liefertag (31.01 -> 28.02 -> 31.03). Liefertag-Aenderung gilt fuer den laufenden Zyklus und nur nach vorn: ist der Tag im Referenzmonat vorbei, wird abgelehnt, der Monat verschiebt sich nie (Rueckfrage R1). Admin-Filter "naechste Ausfuehrung" als Datumsbereich. Prognose-Kacheln bekommen die Kachel Monatsende, Team-Abos rechnen bis Monatsende. A2: Sperrfristen Default 0, Setting 0 ist ausdruecklich 0 (fiel bisher auf den Default zurueck). Bedingung Kalendertage <= Sperre, zentral in AboHelper::isWithinLock: am Ausfuehrungstag ab 0:00 Uhr fixiert, Vortag bis 23:59 Uhr frei, fuer Liefertag, Produkte, Pause und Kuendigung (R2). Texte mit _zero-Varianten ueber AboHelper::lockMessage. Sprachdateien de/en/es/fr, neuer AboDeliveryDayTest, bestehende Tests angepasst, Plan-Status mit Deploy-Schritt (Settings auf 0). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
219 lines
8.4 KiB
PHP
219 lines
8.4 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;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
afterEach(fn () => Carbon::setTestNow());
|
|
|
|
describe('Abo-Sperrfristen als Setting', function () {
|
|
it('nutzt die Defaults, wenn kein Setting gesetzt ist (A2: keine Vorlaufsperre)', function () {
|
|
expect(AboRepository::getLockDaysChange())->toBe(0)
|
|
->and(AboRepository::getLockDaysPauseCancel())->toBe(0);
|
|
});
|
|
|
|
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('nimmt 0 als ausdrueckliche Sperrfrist und faellt nur bei leer auf den Default zurueck', function () {
|
|
Setting::setContentBySlug('abo-lock-days-change', 0, 'int');
|
|
Setting::setContentBySlug('abo-lock-days-pause-cancel', 4, 'int');
|
|
|
|
expect(AboRepository::getLockDaysChange())->toBe(0)
|
|
->and(AboRepository::getLockDaysPauseCancel())->toBe(4);
|
|
|
|
Setting::setContentBySlug('abo-lock-days-pause-cancel', '', 'int');
|
|
|
|
expect(AboRepository::getLockDaysPauseCancel())->toBe(0);
|
|
});
|
|
|
|
it('gibt eine Änderung frei, die bei laengerer Sperrfrist 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 Sperrfrist 10 ist der Wechsel gesperrt (6 <= 10):
|
|
Setting::setContentBySlug('abo-lock-days-change', 10, 'int');
|
|
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'))->toContain('20.03.2026')
|
|
->and(session('alert-error'))->not->toContain('mindestens 10 Tage');
|
|
});
|
|
|
|
it('erlaubt Pausieren im Change-Lock-Fenster, wenn außerhalb der Pause-Sperre', function () {
|
|
Carbon::setTestNow('2026-03-19 00:00:00');
|
|
Setting::setContentBySlug('abo-min-duration', 0, 'int');
|
|
Setting::setContentBySlug('abo-lock-days-change', 10, 'int');
|
|
Setting::setContentBySlug('abo-lock-days-pause-cancel', 3, 'int');
|
|
// 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);
|
|
|
|
$result = $repository->update([
|
|
'action' => 'abo_update_settings',
|
|
'id' => $abo->id,
|
|
'view' => 'me',
|
|
'abo_interval' => 20,
|
|
'abo_pause' => 'true',
|
|
]);
|
|
|
|
// Die Pause verschiebt nur den Liefertermin auf den Liefertag des Folgemonats;
|
|
// das Abo bleibt aktiv.
|
|
expect($result)->toBeTrue()
|
|
->and($abo->refresh()->active)->toBeTrue()
|
|
->and($abo->status)->toBe(2)
|
|
->and($abo->getRawOriginal('next_date'))->toBe('2026-04-20');
|
|
});
|
|
});
|
|
|
|
describe('isAboChangeLocked (UI-Sperre Liefertag)', function () {
|
|
afterEach(fn () => Carbon::setTestNow());
|
|
|
|
it('sperrt ohne Vorlauf erst am Ausfuehrungstag ab 0:00 Uhr, der Vortag ist frei (A2)', function () {
|
|
Carbon::setTestNow('2026-03-19 23:59:00');
|
|
$abo = makeLockTestAbo(['next_date' => '2026-03-20']);
|
|
|
|
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeFalse()
|
|
->and(AboHelper::isPauseCancelLocked($abo, 'me'))->toBeFalse();
|
|
|
|
Carbon::setTestNow('2026-03-20 00:00:00');
|
|
$abo = $abo->fresh();
|
|
|
|
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeTrue()
|
|
->and(AboHelper::isPauseCancelLocked($abo, 'me'))->toBeTrue();
|
|
|
|
// Liegen gebliebener Termin sperrt nicht.
|
|
Carbon::setTestNow('2026-03-21 09:00:00');
|
|
expect(AboHelper::isAboChangeLocked($abo->fresh(), 'me'))->toBeFalse();
|
|
});
|
|
|
|
it('respektiert die konfigurierte Sperrfrist (gesperrt ab 0:00 Uhr des Tages next_date - lock)', function () {
|
|
Carbon::setTestNow('2026-03-19 00:00:00');
|
|
$abo = makeLockTestAbo(['next_date' => '2026-03-24']); // 5 Tage entfernt
|
|
|
|
Setting::setContentBySlug('abo-lock-days-change', 10, 'int');
|
|
expect(AboHelper::isAboChangeLocked($abo, 'me'))->toBeTrue(); // 5 <= 10
|
|
|
|
Setting::setContentBySlug('abo-lock-days-change', 5, 'int');
|
|
expect(AboHelper::isAboChangeLocked($abo->fresh(), 'me'))->toBeTrue(); // 5 <= 5
|
|
|
|
Setting::setContentBySlug('abo-lock-days-change', 3, 'int');
|
|
expect(AboHelper::isAboChangeLocked($abo->fresh(), '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));
|
|
}
|