Warenwirtschaft: Anforderungsrunde 12.06. — Plan V5.0 + AP-26/AP-25/AP-22

Neue Anforderungen (docs/) interpretiert und als Entwicklungsplan V5.0
(AP-20 bis AP-28) aufgenommen; erste drei Pakete umgesetzt:

AP-26 Ausschuss-Gründe konfigurierbar:
- Stammdaten-Tabelle disposal_reasons + CRUD unter Einstellungen → Allgemein
- StockDisposalController liest aktive DB-Gründe statt hartkodierter Liste
- Seeder übernimmt die bisherigen 6 Gründe idempotent

AP-25 Lieferbestand — Datum statt Tage:
- "Nicht vorrätig" wird über Datepicker "Wieder lieferbar ab" gepflegt;
  Resttage-Hinweis zählt täglich automatisch herunter
- Interne Bestellliste wieder kaufbar: Hinweis erscheint zusätzlich zu
  den Mengen-Buttons (VP entscheidet selbst)

AP-22 Produktbestand-Erweiterungen:
- Default-Sortierung nach Dringlichkeit, Status-Kopf toggelt
- Alle vier Status-Kacheln als Filter klickbar
- Neue Spalte "Verbrauch/Monat" (Ø Abgänge der letzten 6 Monate)
- Produkt-Flag "Im Produktbestand anzeigen" (products.show_in_product_stock)

Tests: 77 grün (DisposalReasonSettings 8, ProductOutOfStock 8,
ProductStock 13 + Regression). Hinweise-Doku + Plan-Protokoll fortgeschrieben;
nächster Schritt laut Plan: AP-21 (INCI-Erweiterungen).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-12 16:28:45 +00:00
parent a8f6fef38e
commit e53201f229
32 changed files with 1377 additions and 94 deletions

View file

@ -1,5 +1,6 @@
<?php
use App\Http\Middleware\ActiveAccount;
use App\Models\Product;
use App\Repositories\ProductRepository;
use App\User;
@ -34,8 +35,8 @@ function oosMakeProduct(string $name): Product
]);
}
test('repository berechnet out_of_stock_until aus tagesangabe', function () {
$product = oosMakeProduct('OOS-Tage');
test('repository speichert out_of_stock_until aus datumsangabe', function () {
$product = oosMakeProduct('OOS-Datum');
$repo = new ProductRepository($product);
$repo->update([
@ -44,7 +45,7 @@ test('repository berechnet out_of_stock_until aus tagesangabe', function () {
'title' => $product->title,
'active' => '1',
'out_of_stock_active' => '1',
'out_of_stock_days' => '14',
'out_of_stock_date' => now()->addDays(14)->format('d.m.Y'),
]);
$product->refresh();
@ -65,7 +66,7 @@ test('unbestimmt hat vorrang und nullt das datum', function () {
'title' => $product->title,
'active' => '1',
'out_of_stock_active' => '1',
'out_of_stock_days' => '14',
'out_of_stock_date' => now()->addDays(14)->format('d.m.Y'),
'out_of_stock_indefinite' => '1',
]);
@ -96,6 +97,25 @@ test('ohne aktivierung werden die felder geleert', function () {
expect($product->isOutOfStock())->toBeFalse();
});
test('ungueltiges datum leert das feld statt zu crashen', function () {
$product = oosMakeProduct('OOS-Invalid');
$repo = new ProductRepository($product);
$repo->update([
'id' => (string) $product->id,
'name' => $product->name,
'title' => $product->title,
'active' => '1',
'out_of_stock_active' => '1',
'out_of_stock_date' => 'kein-datum',
]);
$product->refresh();
expect($product->out_of_stock_until)->toBeNull();
expect($product->isOutOfStock())->toBeFalse();
});
test('vergangenes datum gilt nicht als nicht vorrätig', function () {
$product = oosMakeProduct('OOS-Vergangen');
$product->update(['out_of_stock_until' => now()->subDays(2)]);
@ -106,7 +126,7 @@ test('vergangenes datum gilt nicht als nicht vorrätig', function () {
expect($product->outOfStockNotice())->toBeNull();
});
test('hinweis zeigt verbleibende tage', function () {
test('hinweis zeigt verbleibende tage und zaehlt taeglich herunter', function () {
$product = oosMakeProduct('OOS-Hinweis');
$product->update(['out_of_stock_until' => now()->addDays(5)]);
$product->refresh();
@ -114,16 +134,20 @@ test('hinweis zeigt verbleibende tage', function () {
expect($product->isOutOfStock())->toBeTrue();
expect($product->outOfStockRemainingDays())->toBe(5);
expect($product->outOfStockNotice())->toBe('In ca. 5 Tagen wieder da!');
$this->travel(2)->days();
expect($product->outOfStockRemainingDays())->toBe(3);
$this->travelBack();
});
test('http-store speichert nicht-vorrätig mit tagen', function () {
test('http-store speichert nicht-vorrätig mit datum', function () {
$this->actingAs(oosMakeUser(1), 'user');
$response = $this->post(route('admin_product_store'), [
'id' => 'new',
'name' => 'OOS-HTTP',
'out_of_stock_active' => '1',
'out_of_stock_days' => '7',
'out_of_stock_date' => now()->addDays(7)->format('d.m.Y'),
]);
$response->assertRedirect();
@ -133,3 +157,25 @@ test('http-store speichert nicht-vorrätig mit tagen', function () {
expect($product->out_of_stock_until->isSameDay(now()->addDays(7)))->toBeTrue();
expect($product->isOutOfStock())->toBeTrue();
});
test('interne bestellliste zeigt mengen-buttons trotz nicht-vorrätig', function () {
// active.account prüft `payment_account` (Spalte existiert nur im Live-Schema, nicht in den Migrationen)
$this->withoutMiddleware(ActiveAccount::class);
$this->actingAs(oosMakeUser(1), 'user');
$product = oosMakeProduct('OOS-Bestellliste');
$product->update([
'out_of_stock_until' => now()->addDays(12),
'show_on' => ['2'],
]);
$response = $this->getJson(route('user_order_my_datatable', ['shipping_is_for' => 'me', 'draw' => 1, 'start' => 0, 'length' => 50]));
$response->assertSuccessful();
$row = collect($response->json('data'))->first(fn ($r) => str_contains($r['product'] ?? '', 'OOS-Bestellliste'));
expect($row)->not->toBeNull();
expect($row['product'])->toContain('product-stock-hint');
expect($row['product'])->toContain('wieder da');
expect($row['product'])->toContain('add-product-basket');
});