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>
75 lines
2.5 KiB
PHP
75 lines
2.5 KiB
PHP
<?php
|
|
|
|
use App\Models\ShoppingOrder;
|
|
use App\Models\ShoppingUser;
|
|
use App\Models\UserInvoice;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(Tests\TestCase::class, RefreshDatabase::class);
|
|
|
|
beforeEach(fn () => makeShopEnv());
|
|
|
|
function makePreviewOrder(): ShoppingOrder
|
|
{
|
|
$shoppingUser = ShoppingUser::forceCreate([
|
|
'auth_user_id' => 0,
|
|
'billing_firstname' => 'Max',
|
|
'billing_lastname' => 'Mustermann',
|
|
'billing_email' => 'preview@example.com',
|
|
'billing_country_id' => 1,
|
|
'shipping_country_id' => 1,
|
|
'wp_order_number' => 0,
|
|
'language' => 'de',
|
|
]);
|
|
|
|
$order = ShoppingOrder::create([
|
|
'shopping_user_id' => $shoppingUser->id,
|
|
'auth_user_id' => 0,
|
|
'country_id' => 1,
|
|
'user_shop_id' => 0,
|
|
'payment_for' => 6,
|
|
'shipping' => 0,
|
|
'shipping_net' => 0,
|
|
'subtotal_ws' => 80,
|
|
'subtotal' => 80,
|
|
'tax' => 0,
|
|
'total_shipping' => 80,
|
|
'points' => 0,
|
|
'is_abo' => true,
|
|
]);
|
|
|
|
$aboProduct = makeProduct(['12'], 50, 0);
|
|
$order->shopping_order_items()->create([
|
|
'product_id' => $aboProduct->id, 'is_abo_addon' => false, 'qty' => 1,
|
|
'price' => 50, 'price_net' => 50, 'tax_rate' => 0, 'tax' => 0, 'price_vk_net' => 50, 'discount' => 0, 'points' => 0,
|
|
]);
|
|
$addonProduct = makeProduct(['2'], 30, 0);
|
|
$order->shopping_order_items()->create([
|
|
'product_id' => $addonProduct->id, 'is_abo_addon' => true, 'qty' => 1,
|
|
'price' => 30, 'price_net' => 30, 'tax_rate' => 0, 'tax' => 0, 'price_vk_net' => 30, 'discount' => 0, 'points' => 0,
|
|
]);
|
|
|
|
return $order->fresh('shopping_order_items.product');
|
|
}
|
|
|
|
it('lehnt eine unbekannte Bestellung ab', function () {
|
|
$this->artisan('sales:preview-invoice', ['order' => 999999])
|
|
->expectsOutputToContain('nicht gefunden')
|
|
->assertExitCode(1);
|
|
});
|
|
|
|
it('erzeugt Vorschau-PDFs ohne offizielle Rechnung oder Nummernverbrauch', function () {
|
|
Storage::fake('public');
|
|
$order = makePreviewOrder();
|
|
|
|
$this->artisan('sales:preview-invoice', ['order' => $order->id])
|
|
->assertExitCode(0);
|
|
|
|
// Keine offizielle Rechnung angelegt, keine Rechnungsnummer verbraucht.
|
|
expect(UserInvoice::count())->toBe(0);
|
|
|
|
// Vorschau-PDFs liegen im Preview-Ordner.
|
|
Storage::disk('public')->assertExists('invoice/preview/VORSCHAU-'.$order->id.'-MIVITA-Rechnung.pdf');
|
|
Storage::disk('public')->assertExists('delivery/preview/VORSCHAU-'.$order->id.'-MIVITA-Lieferschein.pdf');
|
|
});
|