Warenwirtschaft: AP-52 — Verpackungsvorschau mit Bestand, Differenz und Lagerort (Kundennachtrag 09.09.2026)

Je Verpackungsartikel Bestand (Stück), Differenz zu Bedarf (rot/grün wie bei
Rohstoffen, AP-40) und Lagerplatz; Spalte einheitlich „Lagerort“ für Rohstoffe
(INCI-Tabelle, vorher „Ablage“) und Verpackung.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-09 08:48:30 +00:00
parent 22e218d570
commit 3abb9e644e
5 changed files with 103 additions and 16 deletions

View file

@ -4,6 +4,9 @@ use App\Models\Location;
use App\Models\PackagingItem;
use App\Models\PackagingMaterial;
use App\Models\Product;
use App\Models\StockEntry;
use App\Models\StorageRoom;
use App\Models\StorageShelf;
use App\Models\Supplier;
use App\Services\ProductionService;
use App\User;
@ -72,3 +75,39 @@ test('F-07: produktionsformular liefert die verpackungs-stückliste je ergebnisp
->and($bom[$small->id][0]['per_unit'])->toEqual(2)
->and($html)->toContain('renderPackagingPreview');
});
test('AP-52: verpackungsvorschau liefert bestand und lagerort je verpackungsartikel; spalten heißen einheitlich Lagerort', function () {
$user = fwUser();
$this->actingAs($user, 'user');
$supplier = Supplier::factory()->create();
$material = PackagingMaterial::factory()->create();
$room = StorageRoom::query()->create(['label' => 'Lager 1', 'pos' => 0, 'active' => true]);
$shelf = StorageShelf::query()->create(['label' => 'Regal B', 'pos' => 0, 'active' => true]);
$pk = PackagingItem::factory()->create([
'packaging_material_id' => $material->id, 'supplier_id' => $supplier->id, 'name' => 'AP52-Tiegel', 'category' => 'packaging',
'storage_room_id' => $room->id, 'storage_shelf_id' => $shelf->id,
]);
$location = Location::factory()->create();
// 120 Stück Wareneingang, 20 bereits verbraucht → Bestand 100.
StockEntry::query()->create([
'entry_type' => 'packaging', 'packaging_item_id' => $pk->id, 'supplier_id' => $supplier->id, 'location_id' => $location->id, 'unit' => 'piece',
'ordered_by' => $user->id, 'ordered_at' => '2026-01-01', 'ordered_quantity' => 120, 'price_per_kg' => 1, 'status' => 'received',
'received_by' => $user->id, 'received_at' => '2026-01-15', 'received_quantity' => 120,
]);
$product = Product::query()->create(['name' => 'AP52-Produkt', 'title' => 'x', 'active' => true, 'no_recipe_required' => true]);
$product->packagings()->sync([$pk->id => ['quantity' => 1, 'pos' => 0]]);
app(ProductionService::class)->store(
['product_id' => $product->id, 'location_id' => $location->id, 'produced_at' => '2026-08-01', 'quantity' => 20],
[], $user->id, [['product_id' => $product->id, 'quantity' => 20, 'batch_number' => null]]
);
$html = $this->get(route('admin.inventory.productions.create'))->assertOk()->getContent();
$start = strpos($html, "data-packaging-info='") + strlen("data-packaging-info='");
$info = json_decode(substr($html, $start, strpos($html, "'", $start) - $start), true);
expect($info[$pk->id]['stock'])->toEqual(100)
->and($info[$pk->id]['storage_label'])->toBe('Lager 1 | Regal B')
->and($html)->toContain('packagingRow(')
->and(substr_count($html, '<th>Lagerort</th>'))->toBe(2)
->and($html)->not->toContain('<th>Ablage</th>');
});

View file

@ -563,7 +563,7 @@ test('N-04: bearbeitungsformular einer gebuchten produktion liefert den gespeich
$start = strpos($html, "data-initial-output-totals='") + strlen("data-initial-output-totals='");
$totals = json_decode(substr($html, $start, strpos($html, "'", $start) - $start), true);
expect($snapshot)->toBe([['name' => 'N04-Tiegel', 'pieces' => 1]])
expect($snapshot)->toBe([['id' => $pk->id, 'name' => 'N04-Tiegel', 'pieces' => 1]])
->and($totals)->toBe([(string) $product->id => 1])
->and($html)->toContain('Gespeicherter Verpackungsverbrauch');
});