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>
181 lines
5.8 KiB
PHP
181 lines
5.8 KiB
PHP
<?php
|
|
|
|
use App\Http\Middleware\ActiveAccount;
|
|
use App\Models\Product;
|
|
use App\Repositories\ProductRepository;
|
|
use App\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
function oosMakeUser(int $adminLevel = 1): User
|
|
{
|
|
$user = User::query()->create([
|
|
'email' => uniqid('oos_', true).'@test.example',
|
|
'password' => bcrypt('password'),
|
|
]);
|
|
$user->forceFill([
|
|
'admin' => $adminLevel,
|
|
'confirmed' => true,
|
|
'active' => true,
|
|
'wizard' => 100,
|
|
'blocked' => false,
|
|
])->save();
|
|
|
|
return $user->fresh();
|
|
}
|
|
|
|
function oosMakeProduct(string $name): Product
|
|
{
|
|
return Product::query()->create([
|
|
'name' => $name,
|
|
'title' => $name,
|
|
'active' => true,
|
|
]);
|
|
}
|
|
|
|
test('repository speichert out_of_stock_until aus datumsangabe', function () {
|
|
$product = oosMakeProduct('OOS-Datum');
|
|
|
|
$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' => now()->addDays(14)->format('d.m.Y'),
|
|
]);
|
|
|
|
$product->refresh();
|
|
|
|
expect($product->out_of_stock_indefinite)->toBeFalse();
|
|
expect($product->out_of_stock_until)->not->toBeNull();
|
|
expect($product->out_of_stock_until->isSameDay(now()->addDays(14)))->toBeTrue();
|
|
expect($product->isOutOfStock())->toBeTrue();
|
|
});
|
|
|
|
test('unbestimmt hat vorrang und nullt das datum', function () {
|
|
$product = oosMakeProduct('OOS-Unbestimmt');
|
|
|
|
$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' => now()->addDays(14)->format('d.m.Y'),
|
|
'out_of_stock_indefinite' => '1',
|
|
]);
|
|
|
|
$product->refresh();
|
|
|
|
expect($product->out_of_stock_indefinite)->toBeTrue();
|
|
expect($product->out_of_stock_until)->toBeNull();
|
|
expect($product->isOutOfStock())->toBeTrue();
|
|
expect($product->outOfStockNotice())->toBe('Zur Zeit nicht vorrätig');
|
|
});
|
|
|
|
test('ohne aktivierung werden die felder geleert', function () {
|
|
$product = oosMakeProduct('OOS-Aus');
|
|
$product->update(['out_of_stock_until' => now()->addDays(5), 'out_of_stock_indefinite' => false]);
|
|
|
|
$repo = new ProductRepository($product);
|
|
$repo->update([
|
|
'id' => (string) $product->id,
|
|
'name' => $product->name,
|
|
'title' => $product->title,
|
|
'active' => '1',
|
|
]);
|
|
|
|
$product->refresh();
|
|
|
|
expect($product->out_of_stock_until)->toBeNull();
|
|
expect($product->out_of_stock_indefinite)->toBeFalse();
|
|
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)]);
|
|
$product->refresh();
|
|
|
|
expect($product->isOutOfStock())->toBeFalse();
|
|
expect($product->outOfStockRemainingDays())->toBeNull();
|
|
expect($product->outOfStockNotice())->toBeNull();
|
|
});
|
|
|
|
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();
|
|
|
|
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 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_date' => now()->addDays(7)->format('d.m.Y'),
|
|
]);
|
|
|
|
$response->assertRedirect();
|
|
|
|
$product = Product::query()->where('name', 'OOS-HTTP')->firstOrFail();
|
|
expect($product->out_of_stock_until)->not->toBeNull();
|
|
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');
|
|
});
|