gruene-seele/app/Http/Controllers/Admin/Inventory/PackagingItemController.php
Kevin Adametz 30d4e28696 Warenwirtschaft: AP-51 — Lagerplatz-Felder für Produkte & Verpackung
Lagerplatz-Zuordnung (Raum/Regal/Fach) gab es bisher nur bei Rohstoffen. Jetzt
dieselbe Storage-Hierarchie (StorageRoom/Shelf/Slot) auch für Produkte und
Verpackungsartikel, inkl. Anzeige an den passenden Stellen.

- Migration: storage_room_id/shelf/slot an products + packaging_items
  (nullable, FK constrained, nullOnDelete).
- Models Product + PackagingItem: fillable + storageRoom/Shelf/Slot-Relationen +
  storageLabel() („Raum 1 | Regal 3 | A").
- Produktformular (Inventar-Abschnitt) + Verpackungsformular: Auswahl-Zeile
  Raum/Regal/Fach; Optionen kommen aktiv/sortiert aus den zentral pflegbaren
  Bausteinen (Einstellungen → Allgemein).
- Speichern: ProductRepository normalisiert leere Auswahl -> null (FK);
  PackagingItem-Requests + Repository um die drei Felder erweitert.
- Anzeige: Produktbestand (Sub-Zeile mit Marker), Produktverpackung-Übersicht
  (neue Spalte „Lagerplatz").
- DeletionGuard: Lagerbaustein ist nicht löschbar, solange Produkte/Verpackung
  darauf verweisen (analog Rohstoffe).

Tests: 4 neue (Produkt speichern + leer->null, Verpackung speichern + Anzeige,
Löschsperre, freier Baustein löschbar). Migration auf Dev-/Live-DB gelaufen.
Suite 331 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 09:46:40 +00:00

134 lines
4.7 KiB
PHP

<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StorePackagingItemRequest;
use App\Http\Requests\Inventory\UpdatePackagingItemRequest;
use App\Models\PackagingItem;
use App\Models\PackagingMaterial;
use App\Models\StorageRoom;
use App\Models\StorageShelf;
use App\Models\StorageSlot;
use App\Models\Supplier;
use App\Repositories\PackagingItemRepository;
use App\Services\DeletionGuard;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
class PackagingItemController extends Controller
{
public function __construct(
protected PackagingItemRepository $packagingItemRepository
) {}
public function index(Request $request)
{
$category = $request->get('category', 'packaging');
$isShipping = $category === 'shipping';
$query = PackagingItem::query()
->with(['packagingMaterial', 'supplier', 'storageRoom', 'storageShelf', 'storageSlot'])
->orderBy('name');
if ($isShipping) {
$query->where('category', 'shipping');
} else {
$query->where('category', 'packaging');
}
return view('admin.inventory.packaging-items.index', [
'values' => $query->get(),
'category' => $category,
'pageTitle' => $isShipping ? __('Versandverpackung') : __('Produktverpackung'),
]);
}
public function create(Request $request)
{
$category = $request->query('category', 'packaging');
if (! in_array($category, ['packaging', 'shipping'], true)) {
$category = 'packaging';
}
return view('admin.inventory.packaging-items.form', [
'model' => new PackagingItem(['active' => true, 'category' => $category === 'shipping' ? 'shipping' : 'packaging', 'weight_grams' => 0]),
'packagingMaterials' => PackagingMaterial::query()->orderBy('pos')->orderBy('name')->get(),
'suppliers' => Supplier::query()->orderBy('name')->get(),
'category' => $category,
...$this->storageOptions(),
]);
}
/**
* AP-51: Lagerplatz-Bausteine (Raum/Regal/Fach) für das Formular.
*
* @return array<string, Collection<int, mixed>>
*/
private function storageOptions(): array
{
return [
'storageRooms' => StorageRoom::query()->active()->orderBy('pos')->orderBy('label')->get(),
'storageShelves' => StorageShelf::query()->active()->orderBy('pos')->orderBy('label')->get(),
'storageSlots' => StorageSlot::query()->active()->orderBy('pos')->orderBy('label')->get(),
];
}
public function store(StorePackagingItemRequest $request)
{
$validated = $request->validated();
$item = $this->packagingItemRepository->create($validated);
\Session::flash('alert-save', '1');
$category = $item->category === 'shipping' ? 'shipping' : 'packaging';
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
public function edit(PackagingItem $packagingItem)
{
$category = $packagingItem->category === 'shipping' ? 'shipping' : 'packaging';
return view('admin.inventory.packaging-items.form', [
'model' => $packagingItem,
'packagingMaterials' => PackagingMaterial::query()->orderBy('pos')->orderBy('name')->get(),
'suppliers' => Supplier::query()->orderBy('name')->get(),
'category' => $category,
...$this->storageOptions(),
]);
}
public function update(UpdatePackagingItemRequest $request, PackagingItem $packagingItem)
{
$validated = $request->validated();
$this->packagingItemRepository->update($packagingItem, $validated);
\Session::flash('alert-save', '1');
$category = $packagingItem->category === 'shipping' ? 'shipping' : 'packaging';
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
public function destroy(PackagingItem $packagingItem)
{
$category = $packagingItem->category === 'shipping' ? 'shipping' : 'packaging';
$guard = app(DeletionGuard::class);
$blockers = $guard->blockersFor($packagingItem);
if ($blockers !== []) {
\Session::flash('alert-error', $guard->message($blockers));
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
$packagingItem->delete();
\Session::flash('alert-success', __('Eintrag gelöscht'));
return redirect()->route('admin.inventory.packaging-items.index', ['category' => $category]);
}
}