mivita/tests/Feature/CustomerAboListViewTest.php
Kevin 685f7a90b9 Kundenabo-Seite: Vorschau taggenau, Liste mit den tatsaechlich gebuchten Punkten
Vorschau und Liste liessen sich nicht gegeneinander nachrechnen — zwei Ursachen,
beide hier behoben.

1. Die Vorschau nahm eine pauschale Monatssumme als Basis. Damit enthielt die
   Kachel zum 5. auch schon die Lieferungen vom 10. Jetzt zaehlt je Termin, was
   bis zu diesem Tag gebucht war, plus die bis dahin offenen Lieferungen.
   Buchungen nach dem letzten Liefertag (Nachzuegler, manuelle Gutschriften)
   haengen sich an die letzte Kachel, damit die den Monatsstand zeigt. Jede
   Kachel weist ihre Aufschluesselung aus ("457 abgerechnet + 131 offen").

   Beraterin 454 im September: 132 / 457 / 588 / 776 statt 0 / 0 / 131 / 319.

2. Die Liste zeigte nur den wiederkehrenden Abo-Inhalt. Einmal-Artikel und
   sonstige Zubuchungen einer Lieferung werden nach der Ausfuehrung aus dem Abo
   entfernt und tauchten deshalb nirgends mehr auf — bei Abo 266 waren das 43
   Punkte, bei Abo 418 rund 39. Neue Spalte "Diesen Monat" mit den je Abo
   tatsaechlich gebuchten Punkten, bei Abweichung mit dem Zusatz "+43 einmalig",
   dazu eine Summenzeile. Ein nicht zuordenbarer Rest (geloeschtes Abo, Buchung
   ohne Ausfuehrungsbezug) wird gesondert ausgewiesen, damit die Addition aufgeht.

   Die Spalte erscheint nur ohne Monatsfilter; fuer einen vergangenen Monat waere
   sie irrefuehrend.

Summe der Monatsspalte = Basis der letzten Kachel; Summe der Abo-Punkte der Abos
mit Termin 20./25. = deren offener Teil.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 08:19:35 +00:00

216 lines
8.1 KiB
PHP

<?php
use App\Models\UserAbo;
use App\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\URL;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
beforeEach(function () {
Carbon::setTestNow(Carbon::parse('2026-09-07'));
URL::resolveMissingNamedRoutesUsing(fn (string $name, $parameters = []) => '/'.$name);
});
afterEach(function () {
Carbon::setTestNow();
});
function calMakeAbo(int $memberId, string $startDate, string $nextDate, int $interval = 5): UserAbo
{
return UserAbo::create([
'user_id' => $memberId,
'member_id' => $memberId,
'shopping_user_id' => 0,
'email' => 'cal-'.uniqid('', true).'@example.com',
'is_for' => 'ot',
'payone_userid' => 0,
'clearingtype' => 'cc',
'amount' => 5000,
'active' => true,
'status' => 2,
'abo_interval' => $interval,
'start_date' => $startDate,
'next_date' => $nextDate,
]);
}
it('rendert die Kundenabo-Liste mit allen gewuenschten Spalten', function (): void {
// Kundenwunsch 2026-09-07: Name, Liefertag, Abo-Punkte, naechste Ausfuehrung,
// Startdatum, Ausfuehrungen — sortierbar statt als Karten.
$user = User::forceCreate([
'email' => 'cal-owner@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_level' => 1,
'payment_account' => now()->addYear(),
]);
$abo = calMakeAbo($user->id, '2026-04-20', '2026-09-20', 20);
$html = view('user.abo._customer_abo_list', [
'listAbos' => collect([$abo]),
'customerAboForecast' => ['base' => 0.0, 'days' => [
['day' => 5, 'date' => '05.09.2026', 'billed' => 0.0, 'upcoming' => 0.0, 'total' => 0.0, 'is_past' => true],
['day' => 10, 'date' => '10.09.2026', 'billed' => 0.0, 'upcoming' => 36.0, 'total' => 36.0, 'is_past' => false],
['day' => 20, 'date' => '20.09.2026', 'billed' => 0.0, 'upcoming' => 81.0, 'total' => 81.0, 'is_past' => false],
['day' => 25, 'date' => '25.09.2026', 'billed' => 0.0, 'upcoming' => 81.0, 'total' => 81.0, 'is_past' => false],
]],
])->render();
expect($html)
->toContain(e(__('tables.abo_delivery_day')))
->toContain(e(__('tables.next_date')))
->toContain(e(__('tables.start_date')))
->toContain(e(__('tables.abo_delivery')))
// Sortier-Partial ist eingebunden und haengt an der richtigen Tabelle
->toContain('customer-abo-table')
->toContain('data-sortable="true"')
// Liefertag-Prognose aus den eigenen Kundenabos
->toContain('81')
->toContain(e(__('abo.customer_abo_forecast_title')));
});
it('rendert die Liste auch ohne Liefertag-Prognose', function (): void {
// Fuer vergangene Monate uebergibt der Controller null — die Tabelle bleibt.
$user = User::forceCreate([
'email' => 'cal-owner2@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_level' => 1,
'payment_account' => now()->addYear(),
]);
$html = view('user.abo._customer_abo_list', [
'listAbos' => collect([calMakeAbo($user->id, '2026-04-20', '2026-09-20')]),
'customerAboForecast' => null,
])->render();
expect($html)->toContain('customer-abo-table')
->not->toContain(e(__('abo.customer_abo_forecast_title')));
});
it('kompiliert die Abo-Seite vollstaendig, ohne Blade-Direktiven zu verschlucken', function (string $view): void {
// Regression 2026-09-07: Wird die Ausdrucksform @php(...) mit einem
// @php ... @endphp-Block in derselben Datei gemischt, matcht Blades
// storePhpBlocks vom ersten @php bis zum ersten @endphp und schluckt
// alles dazwischen — dann fehlen Zuweisungen wie $cardAbos zur Laufzeit.
$source = file_get_contents(resource_path('views/'.str_replace('.', '/', $view).'.blade.php'));
$compiled = app('blade.compiler')->compileString($source);
expect($compiled)->not->toMatch('/@(if|unless|foreach|forelse|php|endif|endforeach|endphp)\b/');
})->with([
'user.abo.index',
'user.abo._customer_abo_list',
'dashboard._statistics',
'user.team.firstlines',
'user.team.points',
'user.team.abos',
'components._sortable_table',
]);
it('gibt unformatierte Sortierwerte aus, unabhaengig von der Sprache (A14)', function (string $locale): void {
// Der Zellentext ist lokalisiert (en: `Y-m-d` und `1,000.00`), der
// Sortierschluessel nicht — sonst sortiert die Tabelle in en falsch.
app()->setLocale($locale);
$user = User::forceCreate([
'email' => 'cal-sort-'.$locale.'@example.com',
'password' => bcrypt('secret'),
'lang' => $locale,
'm_level' => 1,
'payment_account' => now()->addYear(),
]);
$html = view('user.abo._customer_abo_list', [
'listAbos' => collect([calMakeAbo($user->id, '2026-04-05', '2026-09-20', 20)]),
'customerAboForecast' => null,
])->render();
expect($html)
->toContain('data-sort-value="2026-09-20"') // naechste Ausfuehrung
->toContain('data-sort-value="2026-04-05"') // Startdatum
->toContain('data-sort-value="20"'); // Liefertag
})->with(['de', 'en']);
it('zeigt die im Monat abgerechneten Punkte je Abo samt Summenzeile', function (): void {
// Die Liste muss sich gegen die Vorschau darueber nachrechnen lassen: Summe
// der Monatsspalte = Basis der letzten Kachel.
$user = User::forceCreate([
'email' => 'cal-billed@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_level' => 1,
'payment_account' => now()->addYear(),
]);
$geliefert = calMakeAbo($user->id, '2026-04-05', '2026-10-05');
$offen = calMakeAbo($user->id, '2026-05-20', '2026-09-20', 20);
$html = view('user.abo._customer_abo_list', [
'listAbos' => collect([$geliefert, $offen]),
'customerAboForecast' => null,
// 80 gebucht, aber nur 37 wiederkehrend → 43 einmalig.
'customerAboBilled' => collect([$geliefert->id => 80.0]),
'customerAboBilledTotal' => 80.0,
])->render();
expect($html)
->toContain(e(__('abo.customer_abo_points_billed')))
->toContain('data-sort-value="80"')
->toContain(e(__('abo.customer_abo_points_extra')))
->toContain(e(__('abo.customer_abo_list_sum')))
// Kein Rest → weder Rest- noch Monatszeile
->not->toContain(e(__('abo.customer_abo_list_sum_rest')));
});
it('weist einen nicht zuordenbaren Rest in der Summenzeile aus', function (): void {
// Ein im Monat geliefertes, inzwischen geloeschtes Abo steht nicht mehr in der
// Liste. Ohne die Restzeile ginge die Addition nicht auf.
$user = User::forceCreate([
'email' => 'cal-rest@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_level' => 1,
'payment_account' => now()->addYear(),
]);
$abo = calMakeAbo($user->id, '2026-04-05', '2026-10-05');
$html = view('user.abo._customer_abo_list', [
'listAbos' => collect([$abo]),
'customerAboForecast' => null,
'customerAboBilled' => collect([$abo->id => 50.0]),
'customerAboBilledTotal' => 78.0,
])->render();
expect($html)
->toContain(e(__('abo.customer_abo_list_sum_rest')))
->toContain(e(__('abo.customer_abo_list_sum_month')))
->toContain('28,00') // Rest
->toContain('78,00'); // Monat gesamt
});
it('blendet die Monatsspalte aus, wenn auf einen anderen Monat gefiltert wird', function (): void {
$user = User::forceCreate([
'email' => 'cal-nobilled@example.com',
'password' => bcrypt('secret'),
'lang' => 'de',
'm_level' => 1,
'payment_account' => now()->addYear(),
]);
$html = view('user.abo._customer_abo_list', [
'listAbos' => collect([calMakeAbo($user->id, '2026-04-05', '2026-10-05')]),
'customerAboForecast' => null,
'customerAboBilled' => null,
'customerAboBilledTotal' => null,
])->render();
expect($html)
->not->toContain(e(__('abo.customer_abo_points_billed')))
->toContain(e(__('abo.customer_abo_list_sum')));
});