Warenwirtschaft: AP-09 bis AP-13 (Produktbestand, Set-Produkte, Ausschuss, Konzepte)
- AP-09 Produktbestand inkl. Bewegungshistorie (product_stock_movements, ProductStockService) - AP-10 Rohstoffbestand-Ansicht je Lager (RawMaterialStockController) - AP-11 Bestandsschwellen / Out-of-Stock-Handling fuer Produkte und Shop - AP-12 Ausgang/Ausschuss (stock_disposals, StockDisposalController, InventoryService) - Set-Produkte (product_set_items) inkl. Aufloesung - Produktentwicklung & Hinweise-Verwaltung (Notices) - AP-13 Entwicklungskonzept Shop-Bestandsabzug im Plan dokumentiert - Feature-Tests fuer neue Module + aktualisierter Entwicklungsplan Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
78679e0c55
commit
3ee2d756e9
63 changed files with 5968 additions and 901 deletions
125
app/Http/Controllers/Admin/Inventory/StockDisposalController.php
Normal file
125
app/Http/Controllers/Admin/Inventory/StockDisposalController.php
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Inventory;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Inventory\StoreStockDisposalRequest;
|
||||
use App\Models\Ingredient;
|
||||
use App\Models\Location;
|
||||
use App\Models\StockDisposal;
|
||||
use App\Models\StockEntry;
|
||||
use App\Services\InventoryService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class StockDisposalController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected InventoryService $inventoryService,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$query = StockDisposal::query()
|
||||
->with(['ingredient', 'packagingItem', 'location', 'stockEntry', 'user'])
|
||||
->latest('disposed_at')
|
||||
->latest('id');
|
||||
|
||||
if (in_array($request->query('type'), ['ingredient', 'packaging'], true)) {
|
||||
$query->where('disposal_type', $request->query('type'));
|
||||
}
|
||||
|
||||
return view('admin.inventory.stock-disposals.index', [
|
||||
'values' => $query->limit(500)->get(),
|
||||
'typeFilter' => $request->query('type'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request): View|RedirectResponse
|
||||
{
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
return redirect()->route('home');
|
||||
}
|
||||
|
||||
$prefill = [
|
||||
'disposal_type' => 'ingredient',
|
||||
'ingredient_id' => null,
|
||||
'ingredient_label' => null,
|
||||
];
|
||||
|
||||
$ingredientId = (int) $request->query('ingredient_id');
|
||||
if ($ingredientId > 0 && ($ingredient = Ingredient::query()->find($ingredientId))) {
|
||||
$prefill['ingredient_id'] = $ingredient->id;
|
||||
$prefill['ingredient_label'] = $ingredient->inci ? $ingredient->name.' ('.$ingredient->inci.')' : $ingredient->name;
|
||||
}
|
||||
|
||||
return view('admin.inventory.stock-disposals.create', [
|
||||
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
|
||||
'reasons' => $this->reasons(),
|
||||
'prefill' => $prefill,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreStockDisposalRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validatedPayload();
|
||||
$data['user_id'] = (int) $request->user()->id;
|
||||
|
||||
StockDisposal::query()->create($data);
|
||||
|
||||
\Session::flash('alert-save', '1');
|
||||
|
||||
return redirect()->route('admin.inventory.stock-disposals.index');
|
||||
}
|
||||
|
||||
public function ingredientCharges(Ingredient $ingredient): JsonResponse
|
||||
{
|
||||
$remainingByLocation = $this->inventoryService->remainingByLocationForIngredient($ingredient->id);
|
||||
|
||||
$charges = StockEntry::query()
|
||||
->where('status', 'received')
|
||||
->where('entry_type', 'ingredient')
|
||||
->where('ingredient_id', $ingredient->id)
|
||||
->with('location')
|
||||
->orderBy('best_before')
|
||||
->get(['id', 'location_id', 'batch_number', 'best_before', 'received_quantity']);
|
||||
|
||||
$results = $charges->map(function (StockEntry $charge) {
|
||||
$label = $charge->batch_number ? __('Charge').' '.$charge->batch_number : __('Charge #:id', ['id' => $charge->id]);
|
||||
if ($charge->location) {
|
||||
$label .= ' · '.$charge->location->name;
|
||||
}
|
||||
if ($charge->best_before) {
|
||||
$label .= ' · MHD '.$charge->best_before->format('d.m.Y');
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $charge->id,
|
||||
'location_id' => $charge->location_id,
|
||||
'text' => $label,
|
||||
];
|
||||
})->values()->all();
|
||||
|
||||
return response()->json([
|
||||
'charges' => $results,
|
||||
'remaining_by_location' => $remainingByLocation,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
protected function reasons(): array
|
||||
{
|
||||
return [
|
||||
__('Bruch / Beschädigung'),
|
||||
__('Verfall / MHD überschritten'),
|
||||
__('Qualitätsmangel'),
|
||||
__('Schwund / Inventurdifferenz'),
|
||||
__('Muster / Testverbrauch'),
|
||||
__('Sonstiges'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue