Warenwirtschaft: F-16 — fachliche Vorbedingungen innerhalb der Sperre prüfen
- Statusservice: Vorbedingung (Ausgangsstatus, Zahlungsfreigabe) wird nach lockForUpdate erneut geprüft (Board übergibt sie als Closure). - Wareneingang: Transaktion + Sperre, „offen“ wird innerhalb der Sperre geprüft. - Ausschuss: betroffene Wareneingänge sperren und Restbestand in der Transaktion erneut prüfen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c2a17835ef
commit
731b2461e2
7 changed files with 170 additions and 36 deletions
|
|
@ -15,6 +15,7 @@ use Illuminate\Http\JsonResponse;
|
|||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class StockDisposalController extends Controller
|
||||
|
|
@ -66,6 +67,8 @@ class StockDisposalController extends Controller
|
|||
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
|
||||
'reasons' => $this->reasons(),
|
||||
'prefill' => $prefill,
|
||||
// AP-50/F-17: Köln auch beim Ausschuss vorbelegen.
|
||||
'defaultLocationId' => Location::defaultId(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -74,17 +77,63 @@ class StockDisposalController extends Controller
|
|||
$data = $request->validatedPayload();
|
||||
$data['user_id'] = (int) $request->user()->id;
|
||||
|
||||
DB::transaction(function () use ($data, $productionService): void {
|
||||
foreach ($this->splitAcrossBatches($data, $productionService) as $row) {
|
||||
StockDisposal::query()->create($row);
|
||||
}
|
||||
});
|
||||
try {
|
||||
DB::transaction(function () use ($data, $productionService): void {
|
||||
$this->assertStillAvailable($data, $productionService);
|
||||
foreach ($this->splitAcrossBatches($data, $productionService) as $row) {
|
||||
StockDisposal::query()->create($row);
|
||||
}
|
||||
});
|
||||
} catch (ValidationException $e) {
|
||||
return redirect()->back()->withInput()->withErrors($e->errors());
|
||||
}
|
||||
|
||||
\Session::flash('alert-save', '1');
|
||||
|
||||
return redirect()->route('admin.inventory.stock-disposals.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüfung 08.09.2026 F-16: Bestandsprüfung innerhalb der Transaktion wiederholen,
|
||||
* nachdem die betroffenen Wareneingänge gesperrt wurden — die FormRequest-Prüfung
|
||||
* allein lässt zwei parallele Entnahmen denselben Rest bestehen.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function assertStillAvailable(array $data, ProductionService $productionService): void
|
||||
{
|
||||
if (($data['disposal_type'] ?? null) !== 'ingredient') {
|
||||
return;
|
||||
}
|
||||
|
||||
$ingredientId = (int) $data['ingredient_id'];
|
||||
$quantity = round((float) $data['quantity'], 2);
|
||||
|
||||
if (! empty($data['stock_entry_id'])) {
|
||||
$entry = StockEntry::query()->lockForUpdate()->findOrFail((int) $data['stock_entry_id']);
|
||||
$consumed = $productionService->consumedByStockEntry([$entry->id])[$entry->id] ?? 0.0;
|
||||
$remaining = round((float) $entry->received_quantity - $consumed, 2);
|
||||
} else {
|
||||
// Alle Eingänge des Rohstoffs am Standort sperren, dann Standortrest neu berechnen.
|
||||
StockEntry::query()->lockForUpdate()
|
||||
->where('entry_type', 'ingredient')->where('ingredient_id', $ingredientId)
|
||||
->where('location_id', (int) $data['location_id'])
|
||||
->get();
|
||||
$remaining = round((float) ($this->inventoryService->remainingByLocationForIngredient($ingredientId)[(int) $data['location_id']] ?? 0.0), 2);
|
||||
}
|
||||
|
||||
if ($quantity > $remaining) {
|
||||
throw ValidationException::withMessages([
|
||||
'quantity' => __('Die Menge (:qty g) übersteigt den Restbestand (:rest g) — Bestand hat sich zwischenzeitlich geändert.', [
|
||||
'qty' => number_format($quantity, 2, ',', '.'),
|
||||
'rest' => number_format(max(0, $remaining), 2, ',', '.'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüfung 08.09.2026 F-09: Rohstoff-Ausschuss ohne bestimmte Charge wird — wie der
|
||||
* Produktionsverbrauch (AP-27, FEFO) — auf die am Lagerort verfügbaren Chargen
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue