Warenwirtschaft: AP-23 Rezeptur-Phasen (Hersteller-Rezeptur)
- Tabelle recipe_phases + product_ingredients.recipe_phase_id (nullable, rueckwaertskompatibel: Bestandsrezepturen bleiben phasenlos) - Produktformular: Hersteller-Rezeptur in Phasen-Bloecke gegliedert (Name, Notiz, Rohstoffe je Phase, Drag&Drop auch zwischen Phasen, "+ Neue Phase", 100%-Summe global ueber alle Phasen) - Repository: Phasen-Persistierung (Auto-Name Phase A/B..., einzelner unbenannter Block bleibt phasenlos), copy() uebernimmt Phasenstruktur - Produktions-Sollmengen rechnen unveraendert ueber alle Zeilen - Blade-Fix: inline @php() + @php-Block im selben Template paarten sich falsch (Raw-Block verschluckte halbes Formular) - auf Block-Form vereinheitlicht - Hinweise-Doku fortgeschrieben Tests: RecipePhaseTest (6 gruen), volle Suite 178 gruen, 649 Assertions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ea36067831
commit
97d420c98a
10 changed files with 592 additions and 100 deletions
|
|
@ -103,7 +103,9 @@
|
|||
<div class="card mb-2" id="product-section-verfuegbarkeit">
|
||||
<h5 class="card-header">{{ __('Verfügbarkeit') }}</h5>
|
||||
<div class="card-body">
|
||||
@php($outOfStockActive = $product->out_of_stock_until !== null && $product->out_of_stock_until->endOfDay()->isFuture())
|
||||
@php
|
||||
$outOfStockActive = $product->out_of_stock_until !== null && $product->out_of_stock_until->endOfDay()->isFuture();
|
||||
@endphp
|
||||
<div class="form-group">
|
||||
<label class="custom-control custom-checkbox">
|
||||
{!! Form::checkbox('out_of_stock_active', 1, old('out_of_stock_active', $outOfStockActive), ['class' => 'custom-control-input', 'id' => 'out_of_stock_active']) !!}
|
||||
|
|
@ -748,63 +750,93 @@
|
|||
<div class="card-body">
|
||||
<input type="hidden" name="manufacturer_inci_sync_sent" value="1">
|
||||
<div class="js-recipe-fields">
|
||||
<p class="text-muted small mb-2">{{ __('Eigene Hersteller-Rezeptur (separate INCI-Liste). Reihenfolge per Drag & Drop ändern, danach speichern.') }}</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered mb-3" id="recipe-table-manufacturer">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:2rem"></th>
|
||||
<th>{{ __('Name') }}</th>
|
||||
<th>{{ __('Qualität') }}</th>
|
||||
<th>{{ __('INCI') }}</th>
|
||||
<th style="width:8rem">{{ __('Anteil (%)') }}</th>
|
||||
<th style="width:6rem">{{ __('Faktor') }}</th>
|
||||
<th style="width:7rem">{{ __('Effektiv') }}</th>
|
||||
<th style="width:3rem"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mfg-ingredient-sortable-rows">
|
||||
@foreach ($product->manufacturer_ingredients as $ingredient)
|
||||
<tr data-ingredient-id="{{ $ingredient->id }}">
|
||||
<td class="text-muted align-middle mfg-ingredient-drag-handle" style="cursor:grab"
|
||||
title="{{ __('Verschieben') }}">☰</td>
|
||||
<td class="align-middle">{{ $ingredient->name }}</td>
|
||||
<td class="align-middle small text-muted">{{ $ingredient->materialQuality?->name ?? '—' }}</td>
|
||||
<td class="align-middle">{{ $ingredient->inci }}</td>
|
||||
<td>
|
||||
<input type="hidden" name="mfg_ingredient_id[]" value="{{ $ingredient->id }}">
|
||||
<input type="text" name="mfg_gram[]" class="form-control form-control-sm mfg-gram"
|
||||
value="{{ $ingredient->pivot->gram !== null ? formatNumber($ingredient->pivot->gram, 6) : '' }}"
|
||||
autocomplete="off" step="0.000001">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="mfg_factor[]"
|
||||
class="form-control form-control-sm mfg-factor"
|
||||
value="{{ formatNumber($ingredient->pivot->factor ?? ($ingredient->default_factor ?? 1.1)) }}"
|
||||
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>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="font-weight-bold" id="mfg-recipe-total-row">
|
||||
<td colspan="4" class="text-right">{{ __('Gesamt:') }}</td>
|
||||
<td class="text-right" id="mfg-recipe-total-percent">—</td>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p class="text-muted small mb-2">{{ __('Eigene Hersteller-Rezeptur (separate INCI-Liste), gegliedert in Phasen (Phase A, B …). Rohstoffe und Phasen per Drag & Drop sortieren, danach speichern. Die Gesamtsumme über alle Phasen muss 100 % ergeben.') }}</p>
|
||||
|
||||
@php
|
||||
$mfgPhaseList = $product->exists ? $product->recipePhases()->get() : collect();
|
||||
$mfgRowsByPhase = $product->manufacturer_ingredients->groupBy(fn ($i) => (int) ($i->pivot->recipe_phase_id ?? 0));
|
||||
$mfgBlocks = [];
|
||||
if (($mfgRowsByPhase[0] ?? collect())->isNotEmpty() || $mfgPhaseList->isEmpty()) {
|
||||
// Altbestand ohne Phase bzw. leere Rezeptur => impliziter erster Block
|
||||
$mfgBlocks[] = ['name' => '', 'note' => '', 'rows' => $mfgRowsByPhase[0] ?? collect()];
|
||||
}
|
||||
foreach ($mfgPhaseList as $mfgPhase) {
|
||||
$mfgBlocks[] = ['name' => $mfgPhase->name, 'note' => $mfgPhase->note, 'rows' => $mfgRowsByPhase[$mfgPhase->id] ?? collect()];
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div id="mfg-phases-container">
|
||||
@foreach ($mfgBlocks as $blockIndex => $block)
|
||||
<div class="card mb-3 mfg-phase-block">
|
||||
<div class="card-header d-flex align-items-center py-2">
|
||||
<span class="text-muted mfg-phase-drag-handle mr-2" style="cursor:grab" title="{{ __('Phase verschieben') }}">☰</span>
|
||||
<input type="text" name="mfg_phase_name[]" class="form-control form-control-sm mfg-phase-name"
|
||||
style="max-width: 16rem" placeholder="{{ __('Phase') }} {{ chr(65 + $blockIndex) }}"
|
||||
value="{{ $block['name'] }}" autocomplete="off">
|
||||
<a class="text-danger ml-auto mfg-phase-remove" href="#" title="{{ __('Phase entfernen') }}"><i class="far fa-trash-alt"></i></a>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered mb-2 mfg-phase-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:2rem"></th>
|
||||
<th>{{ __('Name') }}</th>
|
||||
<th>{{ __('Qualität') }}</th>
|
||||
<th>{{ __('INCI') }}</th>
|
||||
<th style="width:8rem">{{ __('Anteil (%)') }}</th>
|
||||
<th style="width:6rem">{{ __('Faktor') }}</th>
|
||||
<th style="width:7rem">{{ __('Effektiv') }}</th>
|
||||
<th style="width:3rem"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mfg-phase-rows">
|
||||
@foreach ($block['rows'] as $ingredient)
|
||||
<tr data-ingredient-id="{{ $ingredient->id }}">
|
||||
<td class="text-muted align-middle mfg-ingredient-drag-handle" style="cursor:grab"
|
||||
title="{{ __('Verschieben') }}">☰</td>
|
||||
<td class="align-middle">{{ $ingredient->name }}</td>
|
||||
<td class="align-middle small text-muted">{{ $ingredient->materialQuality?->name ?? '—' }}</td>
|
||||
<td class="align-middle">{{ $ingredient->inci }}</td>
|
||||
<td>
|
||||
<input type="hidden" name="mfg_ingredient_id[]" value="{{ $ingredient->id }}">
|
||||
<input type="hidden" name="mfg_row_phase[]" class="mfg-row-phase" value="{{ $blockIndex }}">
|
||||
<input type="text" name="mfg_gram[]" class="form-control form-control-sm mfg-gram"
|
||||
value="{{ $ingredient->pivot->gram !== null ? formatNumber($ingredient->pivot->gram, 6) : '' }}"
|
||||
autocomplete="off" step="0.000001">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="mfg_factor[]"
|
||||
class="form-control form-control-sm mfg-factor"
|
||||
value="{{ formatNumber($ingredient->pivot->factor ?? ($ingredient->default_factor ?? 1.1)) }}"
|
||||
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>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-default mfg-phase-add-ingredients" data-toggle="modal"
|
||||
data-target="#modal-mfg-ingredients-pick">
|
||||
+ {{ __('Rohstoffe hinzufügen') }}
|
||||
</button>
|
||||
<textarea name="mfg_phase_note[]" class="form-control mt-2 mfg-phase-note" rows="2"
|
||||
placeholder="{{ __('Notiz zur Phase (optional) …') }}">{{ $block['note'] }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal"
|
||||
data-target="#modal-mfg-ingredients-pick">
|
||||
{{ __('Inhaltsstoffe hinzufügen') }}
|
||||
</button>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<button type="button" class="btn btn-sm btn-primary" id="btn-mfg-add-phase">+ {{ __('Neue Phase') }}</button>
|
||||
<div class="ml-auto font-weight-bold">
|
||||
{{ __('Gesamt:') }} <span id="mfg-recipe-total-percent">—</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal-mfg-ingredients-pick" tabindex="-1" role="dialog"
|
||||
|
|
@ -863,6 +895,42 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="mfg-phase-block-template">
|
||||
<div class="card mb-3 mfg-phase-block">
|
||||
<div class="card-header d-flex align-items-center py-2">
|
||||
<span class="text-muted mfg-phase-drag-handle mr-2" style="cursor:grab" title="{{ __('Phase verschieben') }}">☰</span>
|
||||
<input type="text" name="mfg_phase_name[]" class="form-control form-control-sm mfg-phase-name"
|
||||
style="max-width: 16rem" placeholder="{{ __('Phase') }}" value="" autocomplete="off">
|
||||
<a class="text-danger ml-auto mfg-phase-remove" href="#" title="{{ __('Phase entfernen') }}"><i class="far fa-trash-alt"></i></a>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered mb-2 mfg-phase-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:2rem"></th>
|
||||
<th>{{ __('Name') }}</th>
|
||||
<th>{{ __('Qualität') }}</th>
|
||||
<th>{{ __('INCI') }}</th>
|
||||
<th style="width:8rem">{{ __('Anteil (%)') }}</th>
|
||||
<th style="width:6rem">{{ __('Faktor') }}</th>
|
||||
<th style="width:7rem">{{ __('Effektiv') }}</th>
|
||||
<th style="width:3rem"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mfg-phase-rows"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-default mfg-phase-add-ingredients" data-toggle="modal"
|
||||
data-target="#modal-mfg-ingredients-pick">
|
||||
+ {{ __('Rohstoffe hinzufügen') }}
|
||||
</button>
|
||||
<textarea name="mfg_phase_note[]" class="form-control mt-2 mfg-phase-note" rows="2"
|
||||
placeholder="{{ __('Notiz zur Phase (optional) …') }}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</div>{{-- /.js-recipe-fields --}}
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue