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>
115 lines
3.8 KiB
PHP
115 lines
3.8 KiB
PHP
<?php
|
||
|
||
use App\Mail\MailAboExecutionReminder;
|
||
use App\Models\ShoppingUser;
|
||
use App\Models\UserAbo;
|
||
use App\Services\Util;
|
||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
use Illuminate\Support\Facades\Mail;
|
||
|
||
uses(Tests\TestCase::class, RefreshDatabase::class);
|
||
|
||
/**
|
||
* Phase 6 – Reminder-E-Mail vor der Abo-Ausführung (Einmalprodukte-Fenster).
|
||
* Default-Fenster = 4 Tage. Berater (is_for=me) → Sales Center, Endkunde (ot) → Portal.
|
||
*/
|
||
beforeEach(function () {
|
||
makeShopEnv();
|
||
Mail::fake();
|
||
});
|
||
|
||
function makeOtAbo(?\Carbon\Carbon $nextDate = null): UserAbo
|
||
{
|
||
$shoppingUser = ShoppingUser::forceCreate([
|
||
'auth_user_id' => 1,
|
||
'billing_firstname' => 'Erika',
|
||
'billing_email' => 'kunde-'.uniqid('', true).'@example.com',
|
||
'billing_country_id' => 1,
|
||
'shipping_country_id' => 1,
|
||
'wp_order_number' => 0,
|
||
'language' => 'de',
|
||
]);
|
||
|
||
return UserAbo::create([
|
||
'shopping_user_id' => $shoppingUser->id,
|
||
'is_for' => 'ot',
|
||
'email' => $shoppingUser->billing_email,
|
||
'payone_userid' => 900200,
|
||
'clearingtype' => 'cc',
|
||
'active' => true,
|
||
'status' => 2,
|
||
'abo_interval' => 5,
|
||
'next_date' => $nextDate ?? now()->addDays(4),
|
||
]);
|
||
}
|
||
|
||
describe('AboSendExecutionReminder Selektion', function () {
|
||
it('versendet an ein Abo, dessen Ausführung im Fenster (heute + 4 Tage) liegt', function () {
|
||
$abo = makeMeAbo();
|
||
$abo->update(['next_date' => now()->addDays(4)]);
|
||
|
||
$this->artisan('abo:send-execution-reminder')->assertSuccessful();
|
||
|
||
Mail::assertSent(MailAboExecutionReminder::class, 1);
|
||
});
|
||
|
||
it('versendet NICHT außerhalb des Fensters', function () {
|
||
$abo = makeMeAbo();
|
||
$abo->update(['next_date' => now()->addDays(10)]);
|
||
|
||
$this->artisan('abo:send-execution-reminder')->assertSuccessful();
|
||
|
||
Mail::assertNothingSent();
|
||
});
|
||
|
||
it('versendet NICHT bei inaktivem oder falschem Status', function () {
|
||
makeMeAbo()->update(['next_date' => now()->addDays(4), 'active' => false]);
|
||
makeMeAbo()->update(['next_date' => now()->addDays(4), 'status' => 3]);
|
||
|
||
$this->artisan('abo:send-execution-reminder')->assertSuccessful();
|
||
|
||
Mail::assertNothingSent();
|
||
});
|
||
});
|
||
|
||
describe('AboSendExecutionReminder Link-Wahl', function () {
|
||
it('Berater-Abo verlinkt ins Sales Center', function () {
|
||
$abo = makeMeAbo();
|
||
$abo->update(['next_date' => now()->addDays(4)]);
|
||
|
||
$html = (new MailAboExecutionReminder($abo->fresh()))->render();
|
||
|
||
expect($html)->toContain(rtrim(Util::getMyMivitaUrl(), '/').'/user/abos/detail/me/'.$abo->id);
|
||
});
|
||
|
||
it('Endkunden-Abo verlinkt ins Kundenportal', function () {
|
||
$abo = makeOtAbo();
|
||
|
||
$html = (new MailAboExecutionReminder($abo->fresh()))->render();
|
||
|
||
expect($html)->toContain(rtrim(Util::getMyMivitaPortalUrl(), '/').'/portal/my-subscriptions');
|
||
});
|
||
|
||
it('formatiert das Ausführungsdatum korrekt (kein doppeltes ICU-Format)', function () {
|
||
$abo = makeMeAbo();
|
||
$abo->update(['next_date' => '2026-07-10']);
|
||
|
||
$html = (new MailAboExecutionReminder($abo->fresh()))->render();
|
||
|
||
expect($html)->toContain('10.07.2026')
|
||
->and($html)->not->toContain('1010.0707');
|
||
});
|
||
});
|
||
|
||
describe('AboSendExecutionReminder Duplikat-Schutz', function () {
|
||
it('versendet pro Zyklus nur einmal', function () {
|
||
$abo = makeMeAbo();
|
||
$abo->update(['next_date' => now()->addDays(4)]);
|
||
|
||
$this->artisan('abo:send-execution-reminder')->assertSuccessful();
|
||
$this->artisan('abo:send-execution-reminder')->assertSuccessful();
|
||
|
||
Mail::assertSent(MailAboExecutionReminder::class, 1);
|
||
expect($abo->fresh()->onetime_reminder_sent_for)->not->toBeNull();
|
||
});
|
||
});
|