Warenwirtschaft: AP-42 — Rohstoff-Dichte (g/ml) für ml-Erfassung im Wareneingang
Kundenentscheid 26.08.: Rohstoffe werden in Gramm geführt, dürfen aber in ml erfasst werden; über die Dichte ergeben sich die Gramm (100 ml Propolistinktur × 0,79 = 79 g). - Neues Feld ingredients.density (g/ml, optional) inkl. Feld im INCI-Formular. - Wareneingang: Einheiten-Umschalter g/ml am Mengenfeld (nur wenn Dichte hinterlegt; Live-Hinweis mit Gramm-Umrechnung, Dichte kommt aus der Rohstoff-Suche mit). Erfassung in ml wird über die Dichte in Gramm umgerechnet und kanonisch als Gramm gespeichert — InventoryService und alle Aggregationen bleiben unverändert. purchase_unit + density_used (Snapshot) protokollieren die ml-Erfassung für Anzeige/Bearbeitung; Detailseite zeigt „790 g (= 1.000 ml)". Wareneingang-Buchung rechnet gelieferte ml ebenfalls um. - Serverseitige Validierung: ml nur mit hinterlegter Dichte. Migrationen add_density_* auf Dev-/Live-DB. 4 Tests, Suite 318 grün. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d51860af39
commit
fa852340ee
13 changed files with 332 additions and 11 deletions
|
|
@ -182,6 +182,12 @@
|
|||
<label class="form-label" for="min_stock_alert">{{ __('Meldebestand (g)') }}</label>
|
||||
{{ Form::text('min_stock_alert', $model->min_stock_alert !== null ? formatNumber($model->min_stock_alert) : '', array('placeholder'=>__('optional'), 'class'=>'form-control', 'id'=>'min_stock_alert')) }}
|
||||
</div>
|
||||
<div class="form-group col-sm-3">
|
||||
{{-- AP-42: Dichte g/ml — erlaubt die Erfassung des Wareneingangs in ml (100 ml × 0,79 = 79 g). --}}
|
||||
<label class="form-label" for="density">{{ __('Dichte (g/ml)') }}</label>
|
||||
{{ Form::text('density', $model->density !== null ? formatNumber($model->density) : '', array('placeholder'=>__('z. B. 0,79 – optional'), 'class'=>'form-control', 'id'=>'density')) }}
|
||||
<small class="form-text text-muted">{{ __('Leer = Erfassung in Gramm.') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<select name="ingredient_id" id="ingredient_id" class="w-100"
|
||||
data-search-url="{{ route('admin.inventory.api.ingredients.search') }}">
|
||||
@if ($model->ingredient_id && $model->ingredient)
|
||||
<option value="{{ $model->ingredient_id }}" selected>{{ $model->ingredient->name }}@if ($model->ingredient->inci)
|
||||
<option value="{{ $model->ingredient_id }}" data-density="{{ $model->ingredient->density }}" selected>{{ $model->ingredient->name }}@if ($model->ingredient->inci)
|
||||
({{ $model->ingredient->inci }})
|
||||
@endif
|
||||
</option>
|
||||
|
|
@ -103,15 +103,31 @@
|
|||
@enderror
|
||||
</div>
|
||||
|
||||
@php
|
||||
// AP-42: in ml erfasste Rohstoffe im Formular auch in ml anzeigen (Rückrechnung
|
||||
// über die gespeicherte Dichte); Bestand bleibt intern in Gramm.
|
||||
$orderedIsMl = $model->purchase_unit === 'ml' && $model->density_used;
|
||||
$orderedDisplay = $model->ordered_quantity !== null
|
||||
? \App\Services\Util::formatNumber($orderedIsMl ? $model->ordered_quantity / $model->density_used : $model->ordered_quantity)
|
||||
: '';
|
||||
@endphp
|
||||
<div class="form-group">
|
||||
<label for="ordered_quantity">{{ __('Bestellte Menge') }} <span class="text-danger">*</span></label>
|
||||
<input type="text" name="ordered_quantity" id="ordered_quantity"
|
||||
class="form-control @error('ordered_quantity') is-invalid @enderror"
|
||||
value="{{ old('ordered_quantity', $model->ordered_quantity !== null ? \App\Services\Util::formatNumber($model->ordered_quantity) : '') }}"
|
||||
required>
|
||||
<small class="text-muted">{{ __('Bei Rohstoff in Gramm, bei Verpackung in Stück.') }}</small>
|
||||
<div class="input-group">
|
||||
<input type="text" name="ordered_quantity" id="ordered_quantity"
|
||||
class="form-control @error('ordered_quantity') is-invalid @enderror"
|
||||
value="{{ old('ordered_quantity', $orderedDisplay) }}"
|
||||
required>
|
||||
<div class="input-group-append" id="ordered-unit-wrap" style="display:none;">
|
||||
<select name="ordered_unit" id="ordered_unit" class="form-control">
|
||||
<option value="gram" @selected(old('ordered_unit', $orderedIsMl ? 'ml' : 'gram') === 'gram')>g</option>
|
||||
<option value="ml" @selected(old('ordered_unit', $orderedIsMl ? 'ml' : 'gram') === 'ml')>ml</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted" id="ordered-quantity-hint">{{ __('Bei Rohstoff in Gramm, bei Verpackung in Stück.') }}</small>
|
||||
@error('ordered_quantity')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,44 @@
|
|||
});
|
||||
}
|
||||
|
||||
// AP-42: aktuelle Dichte des gewählten Rohstoffs (aus dem AJAX-Ergebnis oder
|
||||
// — bei vorbelegtem Rohstoff — aus der data-density-Option).
|
||||
function currentDensity() {
|
||||
var sel = $('#ingredient_id').select2('data');
|
||||
if (sel && sel[0] && sel[0].density != null && sel[0].density !== '') {
|
||||
return parseFloat(sel[0].density);
|
||||
}
|
||||
var d = $('#ingredient_id option:selected').data('density');
|
||||
return (d != null && d !== '') ? parseFloat(d) : null;
|
||||
}
|
||||
|
||||
function applyOrderedUnit() {
|
||||
var isIng = $('#entry_type').val() === 'ingredient';
|
||||
$('#ordered-unit-wrap').toggle(isIng);
|
||||
|
||||
var density = isIng ? currentDensity() : null;
|
||||
var hasDensity = density != null && density > 0;
|
||||
$('#ordered_unit option[value="ml"]').prop('disabled', !hasDensity);
|
||||
if (!hasDensity && $('#ordered_unit').val() === 'ml') {
|
||||
$('#ordered_unit').val('gram');
|
||||
}
|
||||
|
||||
var $hint = $('#ordered-quantity-hint');
|
||||
if (!isIng) {
|
||||
$hint.text('{{ __('Bei Verpackung in Stück.') }}');
|
||||
return;
|
||||
}
|
||||
if ($('#ordered_unit').val() === 'ml' && hasDensity) {
|
||||
var ml = parseNumber($('#ordered_quantity').val());
|
||||
var g = ml != null ? formatNumber(ml * density) : '?';
|
||||
$hint.text('{{ __('Erfassung in ml — entspricht ca.') }} ' + g + ' {{ __('g (Dichte') }} ' + formatNumber(density) + ' g/ml).');
|
||||
} else if (hasDensity) {
|
||||
$hint.text('{{ __('In Gramm — oder Einheit auf ml stellen (Dichte hinterlegt).') }}');
|
||||
} else {
|
||||
$hint.text('{{ __('In Gramm. Für ml-Erfassung zuerst die Dichte am Rohstoff pflegen.') }}');
|
||||
}
|
||||
}
|
||||
|
||||
function parseNumber(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
|
|
@ -106,12 +144,17 @@
|
|||
toggleBlocks();
|
||||
initIngredientSelect2();
|
||||
initPackagingSelect2();
|
||||
applyOrderedUnit();
|
||||
|
||||
$('#entry_type').on('change', function () {
|
||||
toggleBlocks();
|
||||
applyOrderedUnit();
|
||||
$('#packaging_item_id').val(null).trigger('change');
|
||||
initPackagingSelect2();
|
||||
});
|
||||
$('#ingredient_id').on('change', applyOrderedUnit);
|
||||
$('#ordered_unit').on('change', applyOrderedUnit);
|
||||
$('#ordered_quantity').on('input', applyOrderedUnit);
|
||||
|
||||
$('#price_per_kg').on('input', recalcFromNet);
|
||||
$('#price_per_kg_gross').on('input', recalcFromGross);
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@
|
|||
<span class="wawi-deflist__value">
|
||||
@if($model->unit === 'gram')
|
||||
{{ \App\Services\Util::formatNumber($model->ordered_quantity) }} g
|
||||
@if($model->purchase_unit === 'ml' && $model->density_used)
|
||||
<span class="text-muted">(= {{ \App\Services\Util::formatNumber($model->ordered_quantity / $model->density_used) }} ml)</span>
|
||||
@endif
|
||||
@else
|
||||
{{ \App\Services\Util::formatNumber($model->ordered_quantity, 0) }} {{ __('Stk.') }}
|
||||
@endif
|
||||
|
|
@ -134,6 +137,9 @@
|
|||
<span class="wawi-deflist__value">
|
||||
@if($model->unit === 'gram')
|
||||
{{ \App\Services\Util::formatNumber($model->received_quantity) }} g
|
||||
@if($model->purchase_unit === 'ml' && $model->density_used && $model->received_quantity !== null)
|
||||
<span class="text-muted">(= {{ \App\Services\Util::formatNumber($model->received_quantity / $model->density_used) }} ml)</span>
|
||||
@endif
|
||||
@else
|
||||
{{ \App\Services\Util::formatNumber($model->received_quantity, 0) }} {{ __('Stk.') }}
|
||||
@endif
|
||||
|
|
@ -184,13 +190,23 @@
|
|||
@enderror
|
||||
</div>
|
||||
|
||||
@php
|
||||
// AP-42: Wurde in ml eingekauft, wird auch der Eingang in ml erfasst
|
||||
// und über die gespeicherte Dichte in Gramm umgerechnet.
|
||||
$receiveIsMl = $model->purchase_unit === 'ml' && $model->density_used;
|
||||
$receiveDefault = $receiveIsMl
|
||||
? \App\Services\Util::formatNumber($model->ordered_quantity / $model->density_used)
|
||||
: \App\Services\Util::formatNumber($model->ordered_quantity);
|
||||
@endphp
|
||||
<div class="form-group">
|
||||
<label for="received_quantity">{{ __('Eingegangene Menge') }} <span class="text-danger">*</span></label>
|
||||
<input type="text" name="received_quantity" id="received_quantity" required
|
||||
class="form-control @error('received_quantity') is-invalid @enderror"
|
||||
value="{{ old('received_quantity', \App\Services\Util::formatNumber($model->ordered_quantity)) }}">
|
||||
value="{{ old('received_quantity', $receiveDefault) }}">
|
||||
<small class="text-muted">
|
||||
@if($model->unit === 'gram')
|
||||
@if($receiveIsMl)
|
||||
{{ __('Angabe in ml — Umrechnung in Gramm über die Dichte') }} ({{ \App\Services\Util::formatNumber($model->density_used) }} g/ml)
|
||||
@elseif($model->unit === 'gram')
|
||||
{{ __('Angabe in Gramm') }}
|
||||
@else
|
||||
{{ __('Angabe in Stück') }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue