Warenwirtschaft: AP-48 — Produktbestand-Buchungsgründe pflegbar, getrennt Ein/Aus
Kundenentscheid 26.08. (D-5): Eingang und Ausgang haben eigene, pflegbare Gründe-Listen statt einer festen Liste im Controller. - Neues Modell/Tabelle stock_movement_reasons (label, direction in/out, pos, active, SoftDeletes) inkl. Seed der bisherigen Gründe sinnvoll auf Ein-/Ausgang aufgeteilt. - CRUD wie DisposalReason (SettingCrudController + FormRequest + Formular mit Richtungs-Auswahl); zwei Listen (Eingangs-/Ausgangs-Gründe) auf der Seite Einstellungen → Allgemeines, „Neu anlegen" mit vorbelegter Richtung. - Produktbestand-Maske: das Grund-Dropdown wird jetzt je nach Eingang/Ausgang per JS aus den passenden, gepflegten Gründen befüllt (manualReasons entfernt). - DeletionGuard: StockMovementReason immer löschbar. Migration create_stock_movement_reasons auf Dev-/Live-DB. 4 Tests, Suite 326 grün. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e08c8dec40
commit
190713c21b
12 changed files with 381 additions and 22 deletions
|
|
@ -173,6 +173,59 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- AP-48: Buchungsgründe Produktbestand — getrennte Listen für Eingang und Ausgang --}}
|
||||
@foreach ([['in', __('Eingangs-Gründe (Produktbestand)'), $stockReasonsIn], ['out', __('Ausgangs-Gründe (Produktbestand)'), $stockReasonsOut]] as [$dir, $title, $reasons])
|
||||
<div class="wawi-card">
|
||||
<div class="wawi-card__header">
|
||||
<span>{{ $title }}</span>
|
||||
<a href="{{ route('admin.inventory.stock-movement-reasons.create', ['direction' => $dir]) }}" class="btn btn-sm btn-primary">{{ __('Neu anlegen') }}</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table wawi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="max-width: 60px;"> </th>
|
||||
<th>{{ __('Bezeichnung') }}</th>
|
||||
<th>{{ __('Status') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($reasons as $reason)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ route('admin.inventory.stock-movement-reasons.edit', $reason) }}" class="btn icon-btn btn-sm btn-primary">
|
||||
<span class="far fa-edit"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ $reason->label }}</td>
|
||||
<td>
|
||||
@if ($reason->active)
|
||||
<span class="wawi-pill wawi-pill--ok">{{ __('Aktiv') }}</span>
|
||||
@else
|
||||
<span class="wawi-pill wawi-pill--danger">{{ __('Inaktiv') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<form action="{{ route('admin.inventory.stock-movement-reasons.destroy', $reason) }}" method="post" class="d-inline"
|
||||
onsubmit="return confirm('{{ __('Really delete entry?') }}');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-link text-danger p-0" title="{{ __('Delete') }}"><i class="far fa-trash-alt"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4"><div class="wawi-empty">{{ __('Noch keine Gründe angelegt.') }}</div></td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@include('admin.inventory.partials.setting-list-card', [
|
||||
'title' => __('Lagerorte: Räume'),
|
||||
'routePrefix' => 'admin.inventory.storage-rooms',
|
||||
|
|
|
|||
|
|
@ -176,11 +176,8 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ps-move-reason">{{ __('Grund') }} <span class="text-danger">*</span></label>
|
||||
<select name="reason" id="ps-move-reason" class="form-control" required>
|
||||
@foreach ($reasons as $reason)
|
||||
<option value="{{ $reason }}">{{ $reason }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
{{-- AP-48: Optionen werden je Richtung (Eingang/Ausgang) per JS gefüllt --}}
|
||||
<select name="reason" id="ps-move-reason" class="form-control" required></select>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<label for="ps-move-note">{{ __('Hinweis') }}</label>
|
||||
|
|
@ -247,6 +244,14 @@
|
|||
|
||||
var moveTemplate = "{{ route('admin.inventory.product-stock.movement', ['product' => '__ID__']) }}";
|
||||
var moveStock = 0;
|
||||
// AP-48: getrennte, pflegbare Buchungsgründe je Richtung.
|
||||
var psReasons = {in: @json($reasonsIn), out: @json($reasonsOut)};
|
||||
function psPopulateReasons(direction) {
|
||||
var $sel = $('#ps-move-reason').empty();
|
||||
(psReasons[direction] || []).forEach(function (r) {
|
||||
$sel.append($('<option></option>').val(r).text(r));
|
||||
});
|
||||
}
|
||||
$('.js-ps-move').on('click', function () {
|
||||
var id = $(this).data('product');
|
||||
var name = $(this).data('name');
|
||||
|
|
@ -254,6 +259,7 @@
|
|||
moveStock = parseInt($(this).data('stock'), 10) || 0;
|
||||
$('#ps-move-form').attr('action', moveTemplate.replace('__ID__', id));
|
||||
$('#ps-move-direction').val(direction);
|
||||
psPopulateReasons(direction);
|
||||
$('#ps-move-product').text(name);
|
||||
$('#ps-move-stock').text(moveStock);
|
||||
$('#ps-move-title').text(direction === 'in' ? '{{ __('Eingang buchen') }}' : '{{ __('Ausgang buchen') }}');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<h6 class="card-header">{{ $model->exists ? __('Buchungsgrund bearbeiten') : __('Buchungsgrund anlegen') }}</h6>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{{ $model->exists ? route('admin.inventory.stock-movement-reasons.update', $model) : route('admin.inventory.stock-movement-reasons.store') }}">
|
||||
@csrf
|
||||
@if($model->exists)
|
||||
@method('PUT')
|
||||
@endif
|
||||
|
||||
<div class="form-group">
|
||||
<label for="direction">{{ __('Richtung') }}</label>
|
||||
<select name="direction" id="direction" class="form-control @error('direction') is-invalid @enderror">
|
||||
<option value="in" @selected(old('direction', $model->direction) === 'in')>{{ __('Eingang') }}</option>
|
||||
<option value="out" @selected(old('direction', $model->direction) === 'out')>{{ __('Ausgang') }}</option>
|
||||
</select>
|
||||
@error('direction')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="label">{{ __('Bezeichnung') }}</label>
|
||||
<input type="text" name="label" id="label" maxlength="100"
|
||||
class="form-control @error('label') is-invalid @enderror"
|
||||
value="{{ old('label', $model->label) }}" placeholder="{{ __('z. B. Retoure') }}" required>
|
||||
@error('label')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pos">{{ __('Sortierung') }}</label>
|
||||
<input type="number" name="pos" id="pos" min="0" max="255"
|
||||
class="form-control @error('pos') is-invalid @enderror"
|
||||
value="{{ old('pos', $model->pos) }}">
|
||||
@error('pos')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="custom-control custom-checkbox">
|
||||
<input type="checkbox" name="active" value="1" class="custom-control-input"
|
||||
@checked(old('active', $model->active))>
|
||||
<span class="custom-control-label">{{ __('Aktiv') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">{{ __('Speichern') }}</button>
|
||||
<a href="{{ route('admin.inventory.general') }}" class="btn btn-outline-secondary">{{ __('Zurück') }}</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Loading…
Add table
Add a link
Reference in a new issue