- 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>
256 lines
13 KiB
PHP
256 lines
13 KiB
PHP
@extends('layouts.layout-2')
|
|
|
|
@section('content')
|
|
@include('admin.inventory.partials.table-actions-style')
|
|
|
|
@php
|
|
$statusBadge = match ($status) {
|
|
'critical' => '<span class="badge badge-danger">' . __('Kritisch') . '</span>',
|
|
'critical_ordered' => '<span class="badge badge-warning">' . __('Kritisch · bereits bestellt') . '</span>',
|
|
'warning' => '<span class="badge badge-warning">' . __('Bald nachbestellen') . '</span>',
|
|
default => '<span class="badge badge-success">' . __('Ausreichend') . '</span>',
|
|
};
|
|
@endphp
|
|
|
|
<div class="card mb-3">
|
|
<h6 class="card-header d-flex justify-content-between align-items-center">
|
|
<span>{{ __('Rohstoffbestellung') }}: {{ $ingredient->name }} {!! $statusBadge !!}</span>
|
|
<a href="{{ route('admin.inventory.raw-material-stock.index') }}" class="btn btn-sm btn-outline-secondary">{{ __('Zurück') }}</a>
|
|
</h6>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3 col-6 mb-2">
|
|
<div class="text-muted small">{{ __('Qualität') }}</div>
|
|
<div>{{ $ingredient->materialQuality?->name ?? '—' }}</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 mb-2">
|
|
<div class="text-muted small">{{ __('Gesamtbestand') }}</div>
|
|
<div class="{{ in_array($status, ['critical', 'critical_ordered'], true) ? 'text-danger font-weight-bold' : 'font-weight-bold' }}">
|
|
{{ \App\Services\Util::formatNumber($remaining, 0) }} g
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 mb-2">
|
|
<div class="text-muted small">{{ __('Offen bestellt') }}</div>
|
|
<div>
|
|
@if($openTotal > 0)
|
|
{{ \App\Services\Util::formatNumber($openTotal, 0) }} g
|
|
@else
|
|
—
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 mb-2">
|
|
<div class="text-muted small">{{ __('Verbrauch / Tag') }}</div>
|
|
<div>
|
|
@if($daily !== null && $daily > 0)
|
|
{{ \App\Services\Util::formatNumber($daily, 0) }} g
|
|
@else
|
|
—
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 mb-2">
|
|
<div class="text-muted small">{{ __('Voraussichtlich auf Null') }}</div>
|
|
<div>
|
|
@if($expectedEmpty !== null)
|
|
{{ $expectedEmpty->format('d.m.Y') }}
|
|
<span class="text-muted">({{ $daysUntilEmpty }} {{ trans_choice('Tag|Tagen', $daysUntilEmpty) }})</span>
|
|
@else
|
|
—
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@if($ingredient->min_stock_alert !== null)
|
|
<div class="text-muted small mt-2">
|
|
{{ __('Meldebestand') }}: {{ \App\Services\Util::formatNumber($ingredient->min_stock_alert, 0) }} g
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-3">
|
|
<h6 class="card-header">{{ __('Enthalten in') }}</h6>
|
|
<div class="card-datatable table-responsive">
|
|
<table class="table table-striped wawi-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ __('Produkt') }}</th>
|
|
<th class="text-right">{{ __('Produktbestand') }}</th>
|
|
<th class="text-right">{{ __('Rezeptur-Anteil (g/Stück)') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($ingredient->products as $product)
|
|
<tr>
|
|
<td>{{ $product->name }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($productStock[$product->id] ?? 0, 0) }} {{ __('Stück') }}</td>
|
|
<td class="text-right">
|
|
@if($product->pivot->gram !== null && $product->pivot->gram !== '')
|
|
{{ \App\Services\Util::formatNumber($product->pivot->gram, 2) }} g
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="3" class="text-center text-muted py-3">{{ __('Dieser Rohstoff wird in keiner aktiven Rezeptur verwendet.') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-3">
|
|
<h6 class="card-header">{{ __('Lieferanten & Bestellung') }}</h6>
|
|
<div class="card-datatable table-responsive">
|
|
<table class="table table-striped wawi-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ __('Lieferant') }}</th>
|
|
<th>{{ __('Lieferzeit') }}</th>
|
|
<th class="text-right">{{ __('Letzter Einkauf (Netto/kg)') }}</th>
|
|
<th class="text-right" style="width: 11rem;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($ingredient->suppliers as $supplier)
|
|
@php
|
|
$deliveryTime = $ingredient->delivery_time ?: $supplier->delivery_time;
|
|
$lastPrice = $lastPriceBySupplier[$supplier->id] ?? null;
|
|
$orderUrl = $supplier->order_url ?: $supplier->url;
|
|
$orderEmail = $supplier->order_email ?: $supplier->email;
|
|
@endphp
|
|
<tr>
|
|
<td>
|
|
{{ $supplier->name }}
|
|
@if($supplier->pivot->preferred)
|
|
<span class="badge badge-success ml-1">{{ __('bevorzugt') }}</span>
|
|
@endif
|
|
@if($supplier->pivot->supplier_sku)
|
|
<div class="text-muted small">{{ __('Art.-Nr.') }}: {{ $supplier->pivot->supplier_sku }}</div>
|
|
@endif
|
|
</td>
|
|
<td>{{ $deliveryTime ?: '—' }}</td>
|
|
<td class="text-right">
|
|
@if($lastPrice !== null)
|
|
{{ \App\Services\Util::formatNumber($lastPrice, 2) }} €
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
<td class="text-right">
|
|
@if($supplier->order_method === 'online_shop' && $orderUrl)
|
|
<a href="{{ $orderUrl }}" target="_blank" rel="noopener" class="btn btn-sm btn-dark">{{ __('Zum Shop') }}</a>
|
|
@elseif($orderEmail)
|
|
<a href="mailto:{{ $orderEmail }}?subject={{ rawurlencode(__('Bestellung') . ': ' . $ingredient->name) }}" class="btn btn-sm btn-dark">{{ __('Per Mail') }}</a>
|
|
@elseif($orderUrl)
|
|
<a href="{{ $orderUrl }}" target="_blank" rel="noopener" class="btn btn-sm btn-dark">{{ __('Zum Shop') }}</a>
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="4" class="text-center text-muted py-3">{{ __('Diesem Rohstoff ist kein Lieferant zugeordnet.') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@if(Auth::user()->isAdmin())
|
|
<div class="card-footer">
|
|
<a href="{{ route('admin.inventory.stock-entries.create', ['ingredient_id' => $ingredient->id]) }}" class="btn btn-sm btn-primary">{{ __('Einkauf erfassen') }}</a>
|
|
<a href="{{ route('admin.inventory.stock-disposals.create', ['ingredient_id' => $ingredient->id]) }}" class="btn btn-sm btn-outline-danger">{{ __('Ausschuss erfassen') }}</a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="card mb-3">
|
|
<h6 class="card-header">{{ __('Offene Bestellungen / unterwegs') }}</h6>
|
|
<div class="card-datatable table-responsive">
|
|
<table class="table table-striped wawi-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ __('Bestellt am') }}</th>
|
|
<th>{{ __('Lieferant') }}</th>
|
|
<th>{{ __('Lagerort') }}</th>
|
|
<th class="text-right">{{ __('Bestellte Menge') }}</th>
|
|
<th class="text-right" style="width: 8rem;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($openOrders as $order)
|
|
<tr>
|
|
<td>{{ $order->ordered_at?->format('d.m.Y') ?? '—' }}</td>
|
|
<td>{{ $order->supplier?->name ?? '—' }}</td>
|
|
<td>{{ $order->location?->name ?? '—' }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($order->ordered_quantity, 0) }} g</td>
|
|
<td class="text-right">
|
|
<a href="{{ route('admin.inventory.stock-entries.show', $order) }}" class="btn icon-btn btn-sm btn-primary" title="{{ __('Wareneingang buchen') }}">
|
|
<span class="far fa-eye"></span>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="text-center text-muted py-3">{{ __('Keine offenen Bestellungen.') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@if($openOrders->isNotEmpty())
|
|
<div class="card-footer text-muted small">
|
|
{{ __('Offene Bestellungen zählen erst nach gebuchtem Wareneingang zum Bestand.') }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="card mb-3">
|
|
<h6 class="card-header">{{ __('Verfügbare Chargen') }}</h6>
|
|
<div class="card-datatable table-responsive">
|
|
<table class="table table-striped wawi-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ __('Charge') }}</th>
|
|
<th>{{ __('Lieferant') }}</th>
|
|
<th>{{ __('Lagerort') }}</th>
|
|
<th>{{ __('MHD') }}</th>
|
|
<th class="text-right">{{ __('Restbestand') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($charges as $charge)
|
|
<tr>
|
|
<td>{{ $charge->batch_number ?: '#' . $charge->id }}</td>
|
|
<td>{{ $charge->supplier?->name ?? '—' }}</td>
|
|
<td>{{ $charge->location?->name ?? '—' }}</td>
|
|
<td>{{ $charge->best_before?->format('d.m.Y') ?? '—' }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($charge->getAttribute('remaining_quantity'), 0) }} g</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="text-center text-muted py-3">{{ __('Kein Restbestand vorhanden.') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
@if($charges->isNotEmpty() && count($remainingByLocation) > 0)
|
|
<tfoot>
|
|
@foreach($locations as $location)
|
|
@if(isset($remainingByLocation[$location->id]) && $remainingByLocation[$location->id] > 0)
|
|
<tr>
|
|
<td colspan="4" class="text-right text-muted">{{ __('Bestand') }} {{ $location->name }}</td>
|
|
<td class="text-right">{{ \App\Services\Util::formatNumber($remainingByLocation[$location->id], 0) }} g</td>
|
|
</tr>
|
|
@endif
|
|
@endforeach
|
|
</tfoot>
|
|
@endif
|
|
</table>
|
|
</div>
|
|
</div>
|
|
@endsection
|