Warenwirtschaft: AP-50 — Lagerort löschbar (Buchungen übernehmen) + Köln als Default
Kundenentscheid 26.08.: „Waldbröl" gibt es nicht mehr, alles wird Köln; die Standort-Funktion bleibt für künftige Standorte. - LocationController::destroy überschrieben: ein belegter Lagerort lässt sich löschen, indem seine Buchungen (Wareneingänge, Produktionen, Ausschuss) transaktional auf einen Ziel-Lagerort übernommen werden. Ohne Ziel-Lagerort bleibt die Sperre bestehen; unbelegte Orte werden direkt gelöscht. - Lagerort-Liste: Ziel-Auswahl („→ Köln" vorbelegt) im Lösch-Formular. - Wareneingang: Köln als Standard-Lagerort vorbelegt (wie in der Produktion). Außerdem AP-49-Folgeanpassung: Test-Helper hängt die Kategorie „Kosmetikrohstoffe" an, damit Lieferanten im gefilterten INCI-Formular erscheinen; Lagerort-Sperr-Test prüft die neue Meldung. Suite 322 grün. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4eb36486b0
commit
e08c8dec40
6 changed files with 142 additions and 4 deletions
|
|
@ -6,6 +6,7 @@ use App\Models\StorageRoom;
|
|||
use App\Models\StorageShelf;
|
||||
use App\Models\StorageSlot;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\SupplierCategory;
|
||||
use App\Models\TaxRate;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
|
@ -32,10 +33,15 @@ function ingredientOrderUser(int $adminLevel): User
|
|||
|
||||
function ingredientOrderSupplier(string $name): Supplier
|
||||
{
|
||||
return Supplier::factory()->create([
|
||||
$supplier = Supplier::factory()->create([
|
||||
'name' => $name,
|
||||
'active' => true,
|
||||
]);
|
||||
// AP-49: Das INCI-Formular zeigt nur Rohstoff-Lieferanten (Kategorie „Kosmetikrohstoffe").
|
||||
$category = SupplierCategory::query()->firstOrCreate(['name' => 'Kosmetikrohstoffe'], ['pos' => 0]);
|
||||
$supplier->supplierCategories()->attach($category->id);
|
||||
|
||||
return $supplier;
|
||||
}
|
||||
|
||||
test('ingredient form shows suppliers, tax rates and delivery time templates', function () {
|
||||
|
|
|
|||
|
|
@ -121,10 +121,11 @@ test('lagerort mit wareneingang kann nicht geloescht werden (kein 500)', functio
|
|||
$supplier = Supplier::factory()->create();
|
||||
delStock($ing, $location, $supplier, $user);
|
||||
|
||||
// AP-50: belegter Lagerort ohne Ziel-Lagerort bleibt gesperrt (Übernahme-Ziel nötig).
|
||||
$this->delete(route('admin.inventory.locations.destroy', $location))
|
||||
->assertRedirect();
|
||||
|
||||
expect(session('alert-error'))->toContain('Löschen nicht möglich');
|
||||
expect(session('alert-error'))->toContain('Ziel-Lagerort');
|
||||
expect(Location::query()->find($location->id))->not->toBeNull();
|
||||
});
|
||||
|
||||
|
|
|
|||
67
tests/Feature/LocationDeletionTest.php
Normal file
67
tests/Feature/LocationDeletionTest.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Ingredient;
|
||||
use App\Models\Location;
|
||||
use App\Models\StockEntry;
|
||||
use App\Models\Supplier;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function locDelAdmin(): User
|
||||
{
|
||||
$user = User::query()->create(['email' => uniqid('loc_', true).'@test.example', 'password' => bcrypt('password')]);
|
||||
$user->forceFill(['admin' => 8, 'confirmed' => true, 'active' => true, 'wizard' => 100, 'blocked' => false])->save();
|
||||
|
||||
return $user->fresh();
|
||||
}
|
||||
|
||||
function locDelStock(Location $location): StockEntry
|
||||
{
|
||||
$ing = Ingredient::query()->create(['name' => 'INCI-'.uniqid('', true), 'active' => true, 'pos' => 0]);
|
||||
$supplier = Supplier::factory()->create();
|
||||
|
||||
return StockEntry::query()->create([
|
||||
'entry_type' => 'ingredient', 'ingredient_id' => $ing->id, 'supplier_id' => $supplier->id,
|
||||
'location_id' => $location->id, 'unit' => 'gram', 'ordered_by' => 1, 'ordered_at' => '2026-01-01',
|
||||
'ordered_quantity' => 500, 'status' => 'received', 'received_at' => '2026-01-10',
|
||||
'received_quantity' => 500, 'batch_number' => 'LOC-1', 'best_before' => '2030-12-31',
|
||||
]);
|
||||
}
|
||||
|
||||
test('AP-50: belegter lagerort wird gelöscht und buchungen auf ziel-lagerort übernommen', function () {
|
||||
$this->actingAs(locDelAdmin(), 'user');
|
||||
$waldbroel = Location::query()->create(['name' => 'Waldbröl', 'active' => true]);
|
||||
$koeln = Location::query()->create(['name' => 'Köln', 'active' => true]);
|
||||
$entry = locDelStock($waldbroel);
|
||||
|
||||
$this->delete(route('admin.inventory.locations.destroy', $waldbroel), ['reassign_to' => $koeln->id])
|
||||
->assertRedirect(route('admin.inventory.locations.index'));
|
||||
|
||||
expect(Location::query()->find($waldbroel->id))->toBeNull()
|
||||
->and($entry->fresh()->location_id)->toBe($koeln->id);
|
||||
});
|
||||
|
||||
test('AP-50: belegter lagerort ohne ziel-lagerort bleibt gesperrt', function () {
|
||||
$this->actingAs(locDelAdmin(), 'user');
|
||||
$waldbroel = Location::query()->create(['name' => 'Waldbröl', 'active' => true]);
|
||||
Location::query()->create(['name' => 'Köln', 'active' => true]);
|
||||
locDelStock($waldbroel);
|
||||
|
||||
$this->delete(route('admin.inventory.locations.destroy', $waldbroel))
|
||||
->assertRedirect(route('admin.inventory.locations.index'));
|
||||
|
||||
expect(Location::query()->find($waldbroel->id))->not->toBeNull();
|
||||
});
|
||||
|
||||
test('AP-50: unbelegter lagerort wird direkt gelöscht', function () {
|
||||
$this->actingAs(locDelAdmin(), 'user');
|
||||
$leer = Location::query()->create(['name' => 'Leerstand', 'active' => true]);
|
||||
|
||||
$this->delete(route('admin.inventory.locations.destroy', $leer))
|
||||
->assertRedirect(route('admin.inventory.locations.index'));
|
||||
|
||||
expect(Location::query()->find($leer->id))->toBeNull();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue