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
|
|
@ -0,0 +1,156 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
@include('admin.inventory.partials.table-actions-style')
|
||||
<style>
|
||||
#rms-table tbody tr.rms-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
#rms-table tbody tr.rms-row:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
#rms-table tbody tr.rms-row:hover .fa-edit {
|
||||
color: #26b4ff !important;
|
||||
}
|
||||
</style>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center">
|
||||
<h6 class="mb-0">{{ __('Rohstoffbestand') }}</h6>
|
||||
<label class="form-check d-inline-flex align-items-center mb-0">
|
||||
<input type="checkbox" id="rms-only-critical" class="form-check-input mr-2">
|
||||
<span>{{ __('nur kritische anzeigen') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group row mt-3 mb-0 align-items-center">
|
||||
<label for="rms-search" class="col-sm-1 col-form-label">{{ __('Suchen') }}</label>
|
||||
<div class="col-sm-5">
|
||||
<input type="text" id="rms-search" class="form-control" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-datatable table-responsive">
|
||||
<table class="table table-striped wawi-table mb-0" id="rms-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('Name') }}</th>
|
||||
<th>{{ __('Qualität') }}</th>
|
||||
<th class="text-right">{{ __('Bestand') }}</th>
|
||||
<th class="text-right">{{ __('Offen bestellt') }}</th>
|
||||
<th class="text-right">{{ __('Verbrauch / Tag') }}</th>
|
||||
<th>{{ __('Voraussichtlich auf Null') }}</th>
|
||||
<th class="text-right" style="min-width: 13rem;">
|
||||
<select id="rms-horizon" class="form-control form-control-sm">
|
||||
@foreach($horizonOptions as $days => $label)
|
||||
<option value="{{ $days }}" @selected($days === $defaultHorizon)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($rows as $row)
|
||||
@php
|
||||
$ingredient = $row['ingredient'];
|
||||
$rowClass = $row['status'] === 'critical'
|
||||
? 'table-danger'
|
||||
: (in_array($row['status'], ['warning', 'critical_ordered'], true) ? 'table-warning' : '');
|
||||
@endphp
|
||||
<tr class="rms-row {{ $rowClass }}"
|
||||
data-name="{{ Str::lower($ingredient->name) }} {{ Str::lower($ingredient->inci ?? '') }}"
|
||||
data-status="{{ $row['status'] }}"
|
||||
data-href="{{ route('admin.inventory.raw-material-stock.show', $ingredient) }}">
|
||||
<td>
|
||||
<i class="far fa-edit text-muted mr-2" title="{{ __('Bestellung öffnen') }}"></i>
|
||||
{{ $ingredient->name }}
|
||||
</td>
|
||||
<td class="text-muted">{{ $ingredient->materialQuality?->name ?? '—' }}</td>
|
||||
<td class="text-right {{ in_array($row['status'], ['critical', 'critical_ordered'], true) ? 'text-danger font-weight-bold' : '' }}">
|
||||
{{ \App\Services\Util::formatNumber($row['remaining'], 0) }} g
|
||||
@if($row['status'] === 'critical_ordered')
|
||||
<span class="badge badge-info ml-1">{{ __('bestellt') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@if($row['open_order'] > 0)
|
||||
{{ \App\Services\Util::formatNumber($row['open_order'], 0) }} g
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@if($row['daily'] !== null && $row['daily'] > 0)
|
||||
{{ \App\Services\Util::formatNumber($row['daily'], 0) }} g
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($row['expected_empty'] !== null)
|
||||
{{ $row['expected_empty']->format('d.m.Y') }}
|
||||
<span class="text-muted">({{ $row['days_until_empty'] }} {{ trans_choice('Tag|Tagen', $row['days_until_empty']) }})</span>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-right rms-forecast" data-daily="{{ $row['daily'] !== null ? $row['daily'] : 0 }}">
|
||||
@if($row['daily'] !== null && $row['daily'] > 0)
|
||||
{{ \App\Services\Util::formatNumber($row['daily'] * $defaultHorizon, 0) }} g
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-4">{{ __('Keine aktiven Rohstoffe vorhanden.') }}</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
var $rows = $('#rms-table tbody tr.rms-row');
|
||||
|
||||
function applyFilter() {
|
||||
var term = ($('#rms-search').val() || '').toLowerCase().trim();
|
||||
var onlyCritical = $('#rms-only-critical').is(':checked');
|
||||
|
||||
$rows.each(function () {
|
||||
var $row = $(this);
|
||||
var matchesTerm = term === '' || ($row.data('name') || '').toString().indexOf(term) !== -1;
|
||||
var status = $row.data('status');
|
||||
var matchesCritical = !onlyCritical || (status === 'critical' || status === 'warning');
|
||||
$row.toggle(matchesTerm && matchesCritical);
|
||||
});
|
||||
}
|
||||
|
||||
function applyForecast() {
|
||||
var days = parseInt($('#rms-horizon').val(), 10) || 0;
|
||||
$('.rms-forecast').each(function () {
|
||||
var daily = parseFloat($(this).data('daily')) || 0;
|
||||
if (daily > 0) {
|
||||
var total = Math.round(daily * days);
|
||||
$(this).text(total.toLocaleString('de-DE') + ' g');
|
||||
} else {
|
||||
$(this).html('<span class="text-muted">—</span>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#rms-search').on('keyup', applyFilter);
|
||||
$('#rms-only-critical').on('change', applyFilter);
|
||||
$('#rms-horizon').on('change', applyForecast);
|
||||
|
||||
$rows.on('click', function () {
|
||||
var href = $(this).data('href');
|
||||
if (href) {
|
||||
window.location = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
Loading…
Add table
Add a link
Reference in a new issue