Das Lösch-Formular für gespeicherte Rezeptur-Inhaltsstoffe war in das Produkt-Formular verschachtelt; Browser verwerfen das innere <form>-Tag, wodurch _method=DELETE ins Speichern-Formular leakte. Jetzt Button, der per JS ein eigenes Formular außerhalb erzeugt und abschickt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
659 lines
29 KiB
PHP
Executable file
659 lines
29 KiB
PHP
Executable file
@extends('layouts.layout-2')
|
||
|
||
@section('content')
|
||
|
||
@if ($errors->any())
|
||
<div class="row">
|
||
<div class="col-sm-12">
|
||
<div class="alert alert-danger">
|
||
<ul>
|
||
@foreach ($errors->all() as $error)
|
||
<li>{{ $error }}</li>
|
||
@endforeach
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<h4 class="font-weight-bold py-2 mb-2">
|
||
{{ __('Create/Edit Produkt') }}
|
||
</h4>
|
||
|
||
{!! Form::open(['action' => route('admin_product_store'), 'class' => 'form-horizontal', 'id'=>'']) !!}
|
||
<input type="hidden" name="id" id="id" value="@if($product->id>0){{$product->id}}@else new @endif">
|
||
|
||
<div class="text-left mt-0 mb-2">
|
||
<button type="submit" class="btn btn-submit">{{ __('save') }}</button>
|
||
<a href="{{ route('admin_product_show') }}" class="btn btn-default">{{ __('back') }}</a>
|
||
</div>
|
||
|
||
@include('admin.product.form')
|
||
|
||
<div class="text-left mt-0 mb-2">
|
||
<button type="submit" class="btn btn-submit">{{ __('save') }}</button>
|
||
<a href="{{ route('admin_product_show') }}" class="btn btn-default">{{ __('back') }}</a>
|
||
</div>
|
||
|
||
{!! Form::close() !!}
|
||
|
||
@include('admin.product.images')
|
||
|
||
@include('admin.product.upload_whitelabel')
|
||
|
||
@endsection
|
||
|
||
@section('scripts')
|
||
@php
|
||
$ingredient_catalog_for_js = $ingredient_catalog->keyBy('id')->map(function ($item) {
|
||
return [
|
||
'id' => $item->id,
|
||
'name' => $item->name,
|
||
'inci' => $item->inci,
|
||
'effect' => $item->effect,
|
||
'default_factor' => $item->default_factor,
|
||
'quality_name' => $item->materialQuality?->name ?? '',
|
||
];
|
||
});
|
||
$packaging_catalog_for_js = $packaging_catalog->keyBy('id')->map(function ($item) {
|
||
return [
|
||
'name' => $item->name,
|
||
'weight_grams' => $item->weight_grams,
|
||
'material_name' => $item->packagingMaterial?->name ?? '',
|
||
];
|
||
});
|
||
$set_product_catalog_for_js = $set_product_catalog->keyBy('id')->map(function ($item) {
|
||
return [
|
||
'name' => $item->name,
|
||
'number' => $item->number,
|
||
'weight' => $item->weight ? $item->weight . ' g' : '—',
|
||
'price' => $item->getFormattedPrice() !== '' ? $item->getFormattedPrice() . ' €' : '—',
|
||
];
|
||
});
|
||
@endphp
|
||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
|
||
<script>
|
||
(function ($) {
|
||
var catalog = @json($ingredient_catalog_for_js);
|
||
var packagingCatalog = @json($packaging_catalog_for_js);
|
||
var setCatalog = @json($set_product_catalog_for_js);
|
||
|
||
function parseDeNumber(val) {
|
||
if (val === undefined || val === null || val === '') return NaN;
|
||
var s = String(val).replace(/\s/g, '');
|
||
// Audit N-6: einzelner Punkt mit 1–2 Nachkommastellen = Dezimaltrenner;
|
||
// sonst deutsche Konvention (Punkt = Tausender) — wie serverseitig.
|
||
if (!/^-?\d+\.\d{1,2}$/.test(s)) {
|
||
s = s.replace(/\./g, '');
|
||
}
|
||
var n = parseFloat(s.replace(',', '.'));
|
||
return isNaN(n) ? NaN : n;
|
||
}
|
||
|
||
function updateEffective($row) {
|
||
var g = parseDeNumber($row.find('.pi-gram').val());
|
||
var f = parseDeNumber($row.find('.pi-factor').val());
|
||
var $out = $row.find('.pi-effective');
|
||
if (!isNaN(g) && !isNaN(f)) {
|
||
var eff = g * f;
|
||
$out.text(eff.toLocaleString('de-DE', { minimumFractionDigits: 6, maximumFractionDigits: 6 }));
|
||
} else {
|
||
$out.text('—');
|
||
}
|
||
}
|
||
|
||
function updateRecipeTotal() {
|
||
var total = 0;
|
||
var hasValue = false;
|
||
$('#ingredient-sortable-rows tr').each(function () {
|
||
var v = parseDeNumber($(this).find('.pi-gram').val());
|
||
if (!isNaN(v)) { total += v; hasValue = true; }
|
||
});
|
||
var $cell = $('#recipe-total-percent');
|
||
if (!hasValue) {
|
||
$cell.text('—').removeClass('text-danger text-success');
|
||
return;
|
||
}
|
||
var formatted = total.toLocaleString('de-DE', { minimumFractionDigits: 6, maximumFractionDigits: 6 }) + ' %';
|
||
$cell.text(formatted);
|
||
var diff = Math.abs(total - 100);
|
||
if (diff < 0.000001) {
|
||
$cell.removeClass('text-danger').addClass('text-success');
|
||
$cell.attr('title', '');
|
||
} else {
|
||
$cell.removeClass('text-success').addClass('text-danger');
|
||
$cell.attr('title', 'Die Gesamtrezeptur ergibt nicht 100 %!');
|
||
}
|
||
}
|
||
|
||
function refreshAllEffectives() {
|
||
$('#ingredient-sortable-rows tr').each(function () { updateEffective($(this)); });
|
||
updateRecipeTotal();
|
||
}
|
||
|
||
$(document).ready(function () {
|
||
var $tbody = document.getElementById('ingredient-sortable-rows');
|
||
if ($tbody && typeof Sortable !== 'undefined') {
|
||
new Sortable($tbody, {
|
||
handle: '.ingredient-drag-handle',
|
||
animation: 150,
|
||
});
|
||
}
|
||
|
||
function appendIngredientRow(id) {
|
||
id = String(id);
|
||
var ing = catalog[id];
|
||
if (!ing) return;
|
||
var factor = ing.default_factor != null ? String(ing.default_factor).replace('.', ',') : '1,10';
|
||
var row = $('<tr data-ingredient-id="' + id + '">' +
|
||
'<td class="text-muted align-middle ingredient-drag-handle" style="cursor:grab">☰</td>' +
|
||
'<td class="align-middle"></td><td class="align-middle small text-muted"></td><td class="align-middle"></td>' +
|
||
'<td><input type="hidden" name="pi_ingredient_id[]" value="' + id + '">' +
|
||
'<input type="text" name="pi_gram[]" class="form-control form-control-sm pi-gram" value="" autocomplete="off" step="0.000001"></td>' +
|
||
'<td><input type="text" name="pi_factor[]" class="form-control form-control-sm pi-factor" value="' + factor + '" autocomplete="off"></td>' +
|
||
'<td class="align-middle pi-effective text-right small text-muted">—</td>' +
|
||
'<td class="align-middle"><a class="text-danger ingredient-row-remove" href="#" title="Entfernen"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||
row.find('td').eq(1).text(ing.name || '');
|
||
row.find('td').eq(2).text(ing.quality_name || '—');
|
||
row.find('td').eq(3).text(ing.inci || '');
|
||
$('#ingredient-sortable-rows').append(row);
|
||
updateEffective(row);
|
||
updateRecipeTotal();
|
||
}
|
||
|
||
function syncIngredientModalRows() {
|
||
var usedIds = {};
|
||
$('#ingredient-sortable-rows tr').each(function () {
|
||
var id = $(this).data('ingredient-id');
|
||
if (id) usedIds[String(id)] = true;
|
||
});
|
||
$('#modal-ingredients-pick .ingredient-modal-row').each(function () {
|
||
var id = String($(this).data('ingredient-id'));
|
||
var inUse = !!usedIds[id];
|
||
var $cb = $(this).find('.js-ingredient-modal-cb');
|
||
$cb.prop('disabled', inUse);
|
||
$cb.attr('title', inUse ? '{{ __('Bereits im Produkt') }}' : '');
|
||
if (inUse) {
|
||
$cb.prop('checked', false);
|
||
}
|
||
$(this).toggleClass('text-muted', inUse);
|
||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||
$(this).find('.js-ing-modal-name').toggleClass('text-muted', inUse);
|
||
$(this).find('.js-ing-modal-inci').toggleClass('text-muted', inUse);
|
||
});
|
||
}
|
||
|
||
function filterIngredientModalRows() {
|
||
var q = $('#ingredient-modal-search').val().trim().toLowerCase();
|
||
var visible = 0;
|
||
$('#ingredient-modal-tbody .ingredient-modal-row').each(function () {
|
||
var hay = $(this).attr('data-ingredient-search') || '';
|
||
var match = !q || hay.indexOf(q) !== -1;
|
||
$(this).toggle(match);
|
||
if (match) {
|
||
visible++;
|
||
}
|
||
});
|
||
$('#ingredient-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||
}
|
||
|
||
$('#ingredient-modal-search').on('input', filterIngredientModalRows);
|
||
|
||
$('#modal-ingredients-pick').on('show.bs.modal', function () {
|
||
$('#ingredient-modal-search').val('');
|
||
$('#ingredient-modal-tbody .ingredient-modal-row').show();
|
||
$('#ingredient-modal-no-results').addClass('d-none');
|
||
syncIngredientModalRows();
|
||
});
|
||
|
||
$('#btn-ingredients-modal-add').on('click', function () {
|
||
var added = 0;
|
||
$('#modal-ingredients-pick .js-ingredient-modal-cb:checked:not(:disabled)').each(function () {
|
||
var id = String($(this).val());
|
||
appendIngredientRow(id);
|
||
$(this).prop('checked', false);
|
||
added++;
|
||
});
|
||
if (added > 0) {
|
||
syncIngredientModalRows();
|
||
$('#modal-ingredients-pick').modal('hide');
|
||
}
|
||
});
|
||
|
||
$(document).on('click', '.ingredient-row-remove', function (ev) {
|
||
ev.preventDefault();
|
||
var $tr = $(this).closest('tr');
|
||
$tr.remove();
|
||
syncIngredientModalRows();
|
||
updateRecipeTotal();
|
||
});
|
||
|
||
// Löschen gespeicherter Inhaltsstoffe: eigenes Formular außerhalb des
|
||
// Produkt-Formulars, damit _method=DELETE nicht ins Speichern leakt.
|
||
$(document).on('click', '.js-ingredient-delete', function () {
|
||
var $btn = $(this);
|
||
if (!confirm($btn.data('confirm'))) return;
|
||
$('<form>', { method: 'post', action: $btn.data('action') })
|
||
.append($('<input>', { type: 'hidden', name: '_token', value: $('meta[name="csrf-token"]').attr('content') }))
|
||
.append($('<input>', { type: 'hidden', name: '_method', value: 'DELETE' }))
|
||
.appendTo('body')[0].submit();
|
||
});
|
||
|
||
$(document).on('input change', '.pi-gram, .pi-factor', function () {
|
||
updateEffective($(this).closest('tr'));
|
||
updateRecipeTotal();
|
||
});
|
||
|
||
refreshAllEffectives();
|
||
|
||
var $packTbody = document.getElementById('packaging-sortable-rows');
|
||
if ($packTbody && typeof Sortable !== 'undefined') {
|
||
new Sortable($packTbody, {
|
||
handle: '.packaging-drag-handle',
|
||
animation: 150,
|
||
});
|
||
}
|
||
|
||
function appendPackagingRow(id) {
|
||
id = String(id);
|
||
var pk = packagingCatalog[id];
|
||
if (!pk) {
|
||
return;
|
||
}
|
||
var wCell = '—';
|
||
if (pk.weight_grams != null && pk.weight_grams !== '') {
|
||
wCell = String(pk.weight_grams).replace('.', ',');
|
||
}
|
||
var row = $('<tr data-packaging-item-id="' + id + '">' +
|
||
'<td class="text-muted align-middle packaging-drag-handle" style="cursor:grab">☰</td>' +
|
||
'<td class="align-middle"></td><td class="align-middle"></td><td class="align-middle text-right"></td>' +
|
||
'<td><input type="hidden" name="pp_packaging_item_id[]" value="' + id + '">' +
|
||
'<input type="text" name="pp_quantity[]" class="form-control form-control-sm pp-quantity" value="1" autocomplete="off"></td>' +
|
||
'<td class="align-middle"><a class="text-danger packaging-row-remove" href="#" title="{{ __('Entfernen') }}"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||
row.find('td').eq(1).text(pk.name || '');
|
||
row.find('td').eq(2).text(pk.material_name || '—');
|
||
row.find('td').eq(3).text(wCell);
|
||
$('#packaging-sortable-rows').append(row);
|
||
}
|
||
|
||
function syncPackagingModalRows() {
|
||
var usedIds = {};
|
||
$('#packaging-sortable-rows tr').each(function () {
|
||
var pid = $(this).data('packaging-item-id');
|
||
if (pid) {
|
||
usedIds[String(pid)] = true;
|
||
}
|
||
});
|
||
$('#modal-packaging-pick .packaging-modal-row').each(function () {
|
||
var pid = String($(this).data('packaging-item-id'));
|
||
var inUse = !!usedIds[pid];
|
||
var $cb = $(this).find('.js-packaging-modal-cb');
|
||
$cb.prop('disabled', inUse);
|
||
$cb.attr('title', inUse ? '{{ __('Bereits im Produkt') }}' : '');
|
||
if (inUse) {
|
||
$cb.prop('checked', false);
|
||
}
|
||
$(this).toggleClass('text-muted', inUse);
|
||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||
$(this).find('.js-pk-modal-name').toggleClass('text-muted', inUse);
|
||
$(this).find('.js-pk-modal-mat').toggleClass('text-muted', inUse);
|
||
});
|
||
}
|
||
|
||
function filterPackagingModalRows() {
|
||
var q = $('#packaging-modal-search').val().trim().toLowerCase();
|
||
var visible = 0;
|
||
$('#packaging-modal-tbody .packaging-modal-row').each(function () {
|
||
var hay = $(this).attr('data-packaging-search') || '';
|
||
var match = !q || hay.indexOf(q) !== -1;
|
||
$(this).toggle(match);
|
||
if (match) {
|
||
visible++;
|
||
}
|
||
});
|
||
$('#packaging-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||
}
|
||
|
||
$('#packaging-modal-search').on('input', filterPackagingModalRows);
|
||
|
||
$('#modal-packaging-pick').on('show.bs.modal', function () {
|
||
$('#packaging-modal-search').val('');
|
||
$('#packaging-modal-tbody .packaging-modal-row').show();
|
||
$('#packaging-modal-no-results').addClass('d-none');
|
||
syncPackagingModalRows();
|
||
});
|
||
|
||
$('#btn-packaging-modal-add').on('click', function () {
|
||
var added = 0;
|
||
$('#modal-packaging-pick .js-packaging-modal-cb:checked:not(:disabled)').each(function () {
|
||
var id = String($(this).val());
|
||
appendPackagingRow(id);
|
||
$(this).prop('checked', false);
|
||
added++;
|
||
});
|
||
if (added > 0) {
|
||
syncPackagingModalRows();
|
||
$('#modal-packaging-pick').modal('hide');
|
||
}
|
||
});
|
||
|
||
$(document).on('click', '.packaging-row-remove', function (ev) {
|
||
ev.preventDefault();
|
||
$(this).closest('tr').remove();
|
||
syncPackagingModalRows();
|
||
});
|
||
|
||
// === Hersteller-Rezeptur (Manufacturer, mit Phasen — AP-23) ===
|
||
var $mfgContainer = document.getElementById('mfg-phases-container');
|
||
var mfgModalTargetTbody = null; // Ziel-Phase für das Rohstoff-Modal
|
||
|
||
function initMfgPhaseRowSortable(tbody) {
|
||
if (typeof Sortable === 'undefined') return;
|
||
new Sortable(tbody, {
|
||
handle: '.mfg-ingredient-drag-handle',
|
||
animation: 150,
|
||
group: 'mfg-phase-rows', // Zeilen zwischen Phasen verschiebbar
|
||
onEnd: syncMfgPhaseIndexes,
|
||
});
|
||
}
|
||
|
||
if ($mfgContainer && typeof Sortable !== 'undefined') {
|
||
new Sortable($mfgContainer, {
|
||
handle: '.mfg-phase-drag-handle',
|
||
animation: 150,
|
||
onEnd: syncMfgPhaseIndexes,
|
||
});
|
||
$('#mfg-phases-container .mfg-phase-rows').each(function () { initMfgPhaseRowSortable(this); });
|
||
}
|
||
|
||
// Hält mfg_row_phase[] (Zeile -> Block-Index) und die Phasen-Platzhalter aktuell.
|
||
function syncMfgPhaseIndexes() {
|
||
$('#mfg-phases-container .mfg-phase-block').each(function (blockIndex) {
|
||
$(this).find('.mfg-phase-name').attr('placeholder', 'Phase ' + String.fromCharCode(65 + blockIndex));
|
||
$(this).find('.mfg-phase-rows tr .mfg-row-phase').val(blockIndex);
|
||
});
|
||
}
|
||
|
||
function updateMfgEffective($row) {
|
||
var g = parseDeNumber($row.find('.mfg-gram').val());
|
||
var f = parseDeNumber($row.find('.mfg-factor').val());
|
||
var $out = $row.find('.mfg-effective');
|
||
if (!isNaN(g) && !isNaN(f)) {
|
||
$out.text((g * f).toLocaleString('de-DE', { minimumFractionDigits: 6, maximumFractionDigits: 6 }));
|
||
} else {
|
||
$out.text('—');
|
||
}
|
||
}
|
||
|
||
function updateMfgRecipeTotal() {
|
||
var total = 0, hasValue = false;
|
||
$('#mfg-phases-container .mfg-phase-rows tr').each(function () {
|
||
var v = parseDeNumber($(this).find('.mfg-gram').val());
|
||
if (!isNaN(v)) { total += v; hasValue = true; }
|
||
});
|
||
var $cell = $('#mfg-recipe-total-percent');
|
||
if (!hasValue) { $cell.text('—').removeClass('text-danger text-success'); return; }
|
||
$cell.text(total.toLocaleString('de-DE', { minimumFractionDigits: 6, maximumFractionDigits: 6 }) + ' %');
|
||
if (Math.abs(total - 100) < 0.000001) {
|
||
$cell.removeClass('text-danger').addClass('text-success').attr('title', '');
|
||
} else {
|
||
$cell.removeClass('text-success').addClass('text-danger').attr('title', 'Die Gesamtrezeptur (über alle Phasen) ergibt nicht 100 %!');
|
||
}
|
||
}
|
||
|
||
function refreshAllMfgEffectives() {
|
||
$('#mfg-phases-container .mfg-phase-rows tr').each(function () { updateMfgEffective($(this)); });
|
||
updateMfgRecipeTotal();
|
||
}
|
||
|
||
function appendMfgIngredientRow(id, targetTbody) {
|
||
id = String(id);
|
||
var ing = catalog[id];
|
||
if (!ing) return;
|
||
var $tbody = targetTbody && targetTbody.length ? targetTbody : $('#mfg-phases-container .mfg-phase-rows').first();
|
||
if (!$tbody.length) return;
|
||
var factor = ing.default_factor != null ? String(ing.default_factor).replace('.', ',') : '1,10';
|
||
var row = $('<tr data-ingredient-id="' + id + '">' +
|
||
'<td class="text-muted align-middle mfg-ingredient-drag-handle" style="cursor:grab">☰</td>' +
|
||
'<td class="align-middle"></td><td class="align-middle small text-muted"></td><td class="align-middle"></td>' +
|
||
'<td><input type="hidden" name="mfg_ingredient_id[]" value="' + id + '">' +
|
||
'<input type="hidden" name="mfg_row_phase[]" class="mfg-row-phase" value="0">' +
|
||
'<input type="text" name="mfg_gram[]" class="form-control form-control-sm mfg-gram" value="" autocomplete="off" step="0.000001"></td>' +
|
||
'<td><input type="text" name="mfg_factor[]" class="form-control form-control-sm mfg-factor" value="' + factor + '" autocomplete="off"></td>' +
|
||
'<td class="align-middle mfg-effective text-right small text-muted">—</td>' +
|
||
'<td class="align-middle"><a class="text-danger mfg-ingredient-row-remove" href="#" title="Entfernen"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||
row.find('td').eq(1).text(ing.name || '');
|
||
row.find('td').eq(2).text(ing.quality_name || '—');
|
||
row.find('td').eq(3).text(ing.inci || '');
|
||
$tbody.append(row);
|
||
syncMfgPhaseIndexes();
|
||
updateMfgEffective(row);
|
||
updateMfgRecipeTotal();
|
||
}
|
||
|
||
$('#btn-mfg-add-phase').on('click', function () {
|
||
var html = $('#mfg-phase-block-template').html();
|
||
var $block = $(html);
|
||
$('#mfg-phases-container').append($block);
|
||
initMfgPhaseRowSortable($block.find('.mfg-phase-rows')[0]);
|
||
syncMfgPhaseIndexes();
|
||
});
|
||
|
||
$(document).on('click', '.mfg-phase-remove', function (ev) {
|
||
ev.preventDefault();
|
||
var $block = $(this).closest('.mfg-phase-block');
|
||
var rowCount = $block.find('.mfg-phase-rows tr').length;
|
||
if ($('#mfg-phases-container .mfg-phase-block').length <= 1) {
|
||
alert('Die letzte Phase kann nicht entfernt werden.');
|
||
return;
|
||
}
|
||
if (rowCount > 0 && !confirm('Diese Phase enthält ' + rowCount + ' Rohstoff-Zeile(n), die mit entfernt werden. Fortfahren?')) {
|
||
return;
|
||
}
|
||
$block.remove();
|
||
syncMfgPhaseIndexes();
|
||
syncMfgIngredientModalRows();
|
||
updateMfgRecipeTotal();
|
||
});
|
||
|
||
$(document).on('click', '.mfg-phase-add-ingredients', function () {
|
||
mfgModalTargetTbody = $(this).closest('.mfg-phase-block').find('.mfg-phase-rows');
|
||
});
|
||
|
||
function syncMfgIngredientModalRows() {
|
||
var usedIds = {};
|
||
$('#mfg-phases-container .mfg-phase-rows tr').each(function () {
|
||
var id = $(this).data('ingredient-id');
|
||
if (id) usedIds[String(id)] = true;
|
||
});
|
||
$('#modal-mfg-ingredients-pick .mfg-ingredient-modal-row').each(function () {
|
||
var id = String($(this).data('ingredient-id'));
|
||
var inUse = !!usedIds[id];
|
||
var $cb = $(this).find('.js-mfg-ingredient-modal-cb');
|
||
$cb.prop('disabled', inUse);
|
||
if (inUse) $cb.prop('checked', false);
|
||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||
});
|
||
}
|
||
|
||
$('#mfg-ingredient-modal-search').on('input', function () {
|
||
var q = $(this).val().trim().toLowerCase();
|
||
var visible = 0;
|
||
$('#mfg-ingredient-modal-tbody .mfg-ingredient-modal-row').each(function () {
|
||
var hay = $(this).attr('data-ingredient-search') || '';
|
||
var match = !q || hay.indexOf(q) !== -1;
|
||
$(this).toggle(match);
|
||
if (match) visible++;
|
||
});
|
||
$('#mfg-ingredient-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||
});
|
||
|
||
$('#modal-mfg-ingredients-pick').on('show.bs.modal', function () {
|
||
$('#mfg-ingredient-modal-search').val('');
|
||
$('#mfg-ingredient-modal-tbody .mfg-ingredient-modal-row').show();
|
||
$('#mfg-ingredient-modal-no-results').addClass('d-none');
|
||
syncMfgIngredientModalRows();
|
||
});
|
||
|
||
$('#btn-mfg-ingredients-modal-add').on('click', function () {
|
||
var added = 0;
|
||
$('#modal-mfg-ingredients-pick .js-mfg-ingredient-modal-cb:checked:not(:disabled)').each(function () {
|
||
appendMfgIngredientRow(String($(this).val()), mfgModalTargetTbody);
|
||
$(this).prop('checked', false);
|
||
added++;
|
||
});
|
||
if (added > 0) { syncMfgIngredientModalRows(); $('#modal-mfg-ingredients-pick').modal('hide'); }
|
||
});
|
||
|
||
$(document).on('click', '.mfg-ingredient-row-remove', function (ev) {
|
||
ev.preventDefault();
|
||
$(this).closest('tr').remove();
|
||
syncMfgIngredientModalRows();
|
||
updateMfgRecipeTotal();
|
||
});
|
||
|
||
$(document).on('input change', '.mfg-gram, .mfg-factor', function () {
|
||
updateMfgEffective($(this).closest('tr'));
|
||
updateMfgRecipeTotal();
|
||
});
|
||
|
||
// Vor dem Absenden Block-Indizes final synchronisieren (Zeile -> Phase)
|
||
$('form').on('submit', function () {
|
||
syncMfgPhaseIndexes();
|
||
});
|
||
|
||
syncMfgPhaseIndexes();
|
||
refreshAllMfgEffectives();
|
||
|
||
function toggleShelfMonths() {
|
||
var v = $('.js-shelf-life-type:checked').val();
|
||
if (v === 'fixed') {
|
||
$('#shelf-life-months-wrap').show();
|
||
} else {
|
||
$('#shelf-life-months-wrap').hide();
|
||
}
|
||
}
|
||
$(document).on('change', '.js-shelf-life-type', toggleShelfMonths);
|
||
toggleShelfMonths();
|
||
|
||
function toggleRecipeFields() {
|
||
var noRecipe = $('#no_recipe_required').is(':checked');
|
||
$('.js-recipe-fields').toggle(!noRecipe);
|
||
}
|
||
$(document).on('change', '#no_recipe_required', toggleRecipeFields);
|
||
toggleRecipeFields();
|
||
|
||
// === Set-Bestandteile ===
|
||
var $setTbody = document.getElementById('set-sortable-rows');
|
||
if ($setTbody && typeof Sortable !== 'undefined') {
|
||
new Sortable($setTbody, {
|
||
handle: '.set-drag-handle',
|
||
animation: 150,
|
||
});
|
||
}
|
||
|
||
function appendSetRow(id) {
|
||
id = String(id);
|
||
var sp = setCatalog[id];
|
||
if (!sp) {
|
||
return;
|
||
}
|
||
var row = $('<tr data-set-product-id="' + id + '">' +
|
||
'<td class="text-muted align-middle set-drag-handle" style="cursor:grab">☰</td>' +
|
||
'<td class="align-middle"></td><td class="align-middle small text-muted"></td>' +
|
||
'<td class="align-middle text-right small text-muted"></td>' +
|
||
'<td class="align-middle text-right small text-muted"></td>' +
|
||
'<td><input type="hidden" name="set_component_id[]" value="' + id + '">' +
|
||
'<input type="number" min="1" step="1" name="set_quantity[]" class="form-control form-control-sm set-quantity" value="1" autocomplete="off"></td>' +
|
||
'<td class="align-middle"><a class="text-danger set-row-remove" href="#" title="{{ __('Entfernen') }}"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||
row.find('td').eq(1).text(sp.name || '');
|
||
row.find('td').eq(2).text(sp.number || '—');
|
||
row.find('td').eq(3).text(sp.weight || '—');
|
||
row.find('td').eq(4).text(sp.price || '—');
|
||
$('#set-sortable-rows').append(row);
|
||
}
|
||
|
||
function syncSetModalRows() {
|
||
var usedIds = {};
|
||
$('#set-sortable-rows tr').each(function () {
|
||
var sid = $(this).data('set-product-id');
|
||
if (sid) {
|
||
usedIds[String(sid)] = true;
|
||
}
|
||
});
|
||
$('#modal-set-pick .set-modal-row').each(function () {
|
||
var sid = String($(this).data('set-product-id'));
|
||
var inUse = !!usedIds[sid];
|
||
var $cb = $(this).find('.js-set-modal-cb');
|
||
$cb.prop('disabled', inUse);
|
||
$cb.attr('title', inUse ? '{{ __('Bereits im Set') }}' : '');
|
||
if (inUse) {
|
||
$cb.prop('checked', false);
|
||
}
|
||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||
$(this).find('.js-set-modal-name').toggleClass('text-muted', inUse);
|
||
$(this).find('.js-set-modal-number').toggleClass('text-muted', inUse);
|
||
});
|
||
}
|
||
|
||
$('#set-modal-search').on('input', function () {
|
||
var q = $(this).val().trim().toLowerCase();
|
||
var visible = 0;
|
||
$('#set-modal-tbody .set-modal-row').each(function () {
|
||
var hay = $(this).attr('data-set-search') || '';
|
||
var match = !q || hay.indexOf(q) !== -1;
|
||
$(this).toggle(match);
|
||
if (match) {
|
||
visible++;
|
||
}
|
||
});
|
||
$('#set-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||
});
|
||
|
||
$('#modal-set-pick').on('show.bs.modal', function () {
|
||
$('#set-modal-search').val('');
|
||
$('#set-modal-tbody .set-modal-row').show();
|
||
$('#set-modal-no-results').addClass('d-none');
|
||
syncSetModalRows();
|
||
});
|
||
|
||
$('#btn-set-modal-add').on('click', function () {
|
||
var added = 0;
|
||
$('#modal-set-pick .js-set-modal-cb:checked:not(:disabled)').each(function () {
|
||
appendSetRow(String($(this).val()));
|
||
$(this).prop('checked', false);
|
||
added++;
|
||
});
|
||
if (added > 0) {
|
||
syncSetModalRows();
|
||
$('#modal-set-pick').modal('hide');
|
||
}
|
||
});
|
||
|
||
$(document).on('click', '.set-row-remove', function (ev) {
|
||
ev.preventDefault();
|
||
$(this).closest('tr').remove();
|
||
syncSetModalRows();
|
||
});
|
||
|
||
function toggleSetMode() {
|
||
var isSet = $('#is_set').is(':checked');
|
||
$('.js-set-fields').toggle(isSet);
|
||
$('#product-section-rezeptur, #product-section-herstellerrezeptur, #product-section-verpackung, #product-section-warenwirtschaft').toggle(!isSet);
|
||
$('.js-nav-recipe').toggleClass('d-none', isSet);
|
||
}
|
||
$(document).on('change', '#is_set', toggleSetMode);
|
||
toggleSetMode();
|
||
|
||
function toggleOutOfStock() {
|
||
var indefinite = $('#out_of_stock_indefinite').is(':checked');
|
||
var active = $('#out_of_stock_active').is(':checked');
|
||
$('#out_of_stock_active').prop('disabled', indefinite);
|
||
$('.js-out-of-stock-date').toggle(active && !indefinite);
|
||
}
|
||
$(document).on('change', '#out_of_stock_active, #out_of_stock_indefinite', toggleOutOfStock);
|
||
toggleOutOfStock();
|
||
});
|
||
})(jQuery);
|
||
</script>
|
||
@endsection
|