Warenwirtschaft: Einkäufe auch nach Buchung korrigierbar, mit Änderungsprotokoll

Bisher ließen sich nur offene Bestellungen bearbeiten; ein Tippfehler in
einem gebuchten Wareneingang war nicht korrigierbar (Anforderung 14.09.2026).

- Bearbeiten/Korrigieren für gebuchte Eingänge (Admin): Bestell- und
  Eingangsdaten (Menge, Datum, Charge, MHD, Qualität, Preise, Lieferant,
  Lagerort). Menge wirkt sofort auf den Bestand; ml-Eingänge werden in ml
  korrigiert und über die gespeicherte Dichte in Gramm gebucht.
- Schutz: Menge nicht unter den bereits verbrauchten Anteil (Rohstoff je
  Eingang, Verpackung je Artikel); bei Verbrauch sind Art und Rohstoff/
  Artikel gesperrt (Formular + Server).
- Änderungsprotokoll (stock_entry_changes): wer/wann/Grund + geänderte
  Felder alt → neu; Anzeige auf der Detailseite. Grund bei gebuchten
  Eingängen Pflicht, bei offenen optional. Speichern ohne Änderung
  erzeugt keine Zeile.
- Tests (6 neue), In-App-Doku (Hinweise + Leitfaden 3.2) nachgezogen.

Migration: 2026_09_14_110000_create_stock_entry_changes_table.
Suite: 396 grün.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-14 14:51:27 +00:00
parent be1d8fdc39
commit 736cb087ae
14 changed files with 783 additions and 37 deletions

View file

@ -92,6 +92,7 @@ class StockEntryController extends Controller
'quality',
'orderedByUser.account',
'receivedByUser.account',
'changes.user',
]);
return view('admin.inventory.stock-entries.show', array_merge($this->formSharedData(), [
@ -105,16 +106,13 @@ class StockEntryController extends Controller
if (! auth()->user()->isAdmin()) {
return redirect()->route('home');
}
if (! $stockEntry->isPending()) {
\Session::flash('alert-error', __('Nur offene Bestellungen können bearbeitet werden.'));
return redirect()->route('admin.inventory.stock-entries.show', $stockEntry);
}
// Anforderung 14.09.2026: auch gebuchte Eingänge sind korrigierbar (mit Protokoll).
$stockEntry->load(['ingredient', 'packagingItem']);
return view('admin.inventory.stock-entries.edit', array_merge($this->formSharedData(), [
'model' => $stockEntry,
'correctionBlockers' => $stockEntry->isReceived() ? app(DeletionGuard::class)->blockersFor($stockEntry) : [],
]));
}
@ -123,17 +121,18 @@ class StockEntryController extends Controller
if (! $request->user()->isAdmin()) {
return redirect()->route('home');
}
if (! $stockEntry->isPending()) {
\Session::flash('alert-error', __('Nur offene Bestellungen können bearbeitet werden.'));
return redirect()->route('admin.inventory.stock-entries.show', $stockEntry);
try {
$this->stockEntryRepository->update($stockEntry, $request->validatedPayload(), $request->input('change_reason'));
} catch (ValidationException $e) {
return redirect()->route('admin.inventory.stock-entries.edit', $stockEntry)->withErrors($e->errors())->withInput();
}
$this->stockEntryRepository->update($stockEntry, $request->validatedPayload());
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.stock-entries.index');
return $stockEntry->isReceived()
? redirect()->route('admin.inventory.stock-entries.show', $stockEntry)
: redirect()->route('admin.inventory.stock-entries.index');
}
public function copy(StockEntry $stockEntry): RedirectResponse