@php $d = $proposal->algorithm_details ?? []; $t = $d['trace'] ?? []; $hasTrace = ! empty($t); // ── Formatierer (deutsche Schreibweise) ───────────────────────────── $nf0 = fn ($v) => number_format((float) $v, 0, ',', '.'); $nf1 = fn ($v) => rtrim(rtrim(number_format((float) $v, 1, ',', '.'), '0'), ','); $nf2 = fn ($v) => number_format((float) $v, 2, ',', '.'); // ── Kernwerte (Trace bevorzugt, sonst aus gespeicherten Feldern) ──── $mu = (float) $proposal->avg_daily_demand; $sigma = (float) $proposal->demand_std_dev; $ss = (float) $proposal->safety_stock; $rop = (float) $proposal->reorder_point; $ip = (float) $proposal->expected_stock; $oq = (float) $proposal->order_quantity; $cur = (float) $proposal->current_stock; $leadDays = (int) $proposal->lead_time_days; $sl = (int) $proposal->service_level; $z = (float) ($d['z_factor'] ?? 0); $muTau = (float) ($t['mu_tau'] ?? max(0, $rop - $ss)); $ssStat = array_key_exists('safety_stock_stat', $t) ? (float) $t['safety_stock_stat'] : null; $nTau = (int) ($t['selling_days_tau'] ?? 0); $tauDays = (int) ($t['protection_days'] ?? $leadDays); $orderUpTo= (float) ($t['order_up_to'] ?? ($ip + $oq)); $cycle = max(0, $orderUpTo - $rop); $pack = max(1.0, (float) ($t['pack_size'] ?? 1)); $liefBestand = $proposal->supplier_stock !== null ? (float) $proposal->supplier_stock : null; $corrected = (int) ($d['corrected_days'] ?? 0); $targetDays = $t['target_coverage_days'] ?? $proposal->target_coverage_days; $arrival = $d['arrival_date'] ?? null; $arrivalFmt = $arrival ? \Illuminate\Support\Carbon::parse($arrival)->format('d.m.Y') : null; // ── Mengenherleitung (Rohbedarf → Gebinde → Deckelung) ────────────── $roundUpFrom = (float) ($t['pack_round_up_from'] ?? 0); $rawNeed = (float) ($t['raw_need'] ?? max(0, $orderUpTo - $ip)); $packRounded = array_key_exists('pack_rounded', $t) ? (float) $t['pack_rounded'] : $rawNeed; $caps = ['Gebinde-Rohbedarf' => $packRounded]; if ($liefBestand !== null) $caps['Lieferantenbestand (Tagesmeldung)'] = max(0, $liefBestand); $binding = array_keys($caps, min($caps))[0] ?? 'Gebinde-Rohbedarf'; $oosDays = $proposal->stockout_date ? (int) today()->diffInDays($proposal->stockout_date) : null; @endphp
{{-- Header --}}

Zentrallager 94 · Autostore / Onlineshop

{{ $proposal->article_name }}

SKU: {{ $proposal->article_sku }} · Berechnet: {{ $proposal->proposal_date->format('d.m.Y') }}

Lieferant: {{ $proposal->supplier_name }} @if($liefBestand !== null) · gedeckelt auf Lieferantenbestand (Tagesmeldung: {{ $nf0($liefBestand) }} Stk) @else · Bestand unbekannt – ungedeckelt (kein Filial-Mapping für die Tagesmeldung) @endif

@if($proposal->failure_reason) {{ $proposal->failure_reason }} @elseif($proposal->status === 'pending') Ausstehend @else {{ $proposal->status }} @endif

Bestand Lager 94

{{ $nf0($cur) }}

Bestandslage

{{ $cur <= 0 ? 'Ausverkauft' : 'Unter Meldebestand' }}

@if(!is_null($proposal->days_of_stock) && $proposal->days_of_stock > 0)

reicht ~{{ $nf0($proposal->days_of_stock) }} Tage

@endif

Bestellmenge

{{ $nf0($oq) }}

Lieferzeit

{{ $leadDays }} Tage

{{-- Klartext-Fazit --}}

{{ $oq > 0 ? 'Warum diese Menge?' : 'Warum wird nicht bestellt?' }}

Aus Lager 94 fließen im Schnitt {{ $nf2($mu) }} Stück pro Tag ab (Onlinekunden und Filialbestellungen zusammen). Bis frische Ware @if($arrivalFmt) am {{ $arrivalFmt }} @endif eintrifft (Schutzzeitraum {{ $tauDays }} Tage), werden voraussichtlich {{ $nf1($muTau) }} Stück gebraucht. Damit trotz Schwankungen nichts ausgeht, kommt ein Sicherheitsbestand von {{ $nf1($ss) }} Stück dazu – zusammen der Meldebestand {{ $nf1($rop) }}. Verfügbar sind {{ $nf1($ip) }} Stück, also unter dem Meldebestand → nachbestellen. Aufgefüllt wird bis zum Zielbestand {{ $nf1($orderUpTo) }} ({{ (int) $targetDays }} Tage Zielreichweite); nach Rundung @if($binding !== 'Gebinde-Rohbedarf') und Begrenzung durch „{{ $binding }}" @endif ergibt das {{ $nf0($oq) }} Stück.

@if($proposal->failure_reason)

{{ $proposal->failure_reason }}: Rechnerisch fehlen {{ $nf0($rawNeed) }} Stück, aber die ExTrade-Tagesmeldung weist {{ $nf0($liefBestand ?? 0) }} Stück aus – es kann nichts geliefert werden. Die Position bleibt als Markierung stehen und verschwindet, sobald ExTrade wieder Bestand meldet.

@elseif($liefBestand === null)

Bestand unbekannt: Für diesen Artikel gibt es kein Filial-Mapping, die ExTrade-Tagesmeldung ist nicht anwendbar – der Vorschlag ist ungedeckelt. Das löst sich, sobald der ExTrade-Bestandsimport in die Online-Wawi läuft.

@endif
{{-- Zusammensetzung & Entscheidung --}}

So kommt Meldebestand & Ziel zustande

Der Meldebestand s ist der Bedarf bis zur Lieferung plus Sicherheitsbestand. Unterschreitet die Position ihn, wird bis zum Zielbestand S aufgefüllt.

@php $S = max((float) $orderUpTo, 0.0001); $segMu = max(0.0, (float) $muTau); $segSS = max(0.0, (float) $ss); $segCyc = max(0.0, $S - $segMu - $segSS); $PAD_L = 8; $PAD_R = 692; $sx = fn ($v) => $PAD_L + (max(0.0, min($S, (float) $v)) / $S) * ($PAD_R - $PAD_L); $xMu = $sx($segMu); $xS_s = $sx($segMu + $segSS); $xEnd = $sx($S); $xIP = $sx($ip); $wMu = $xMu - $PAD_L; $wSS = $xS_s - $xMu; $wCyc = $xEnd - $xS_s; $wGap = $xEnd - $xIP; @endphp

Zielbestand S

{{ $nf0($S) }} Stk

Zusammensetzung des Zielbestands @if($wMu > 46){{ $nf0($segMu) }}@endif @if($wSS > 46){{ $nf0($segSS) }}@endif @if($wCyc > 46){{ $nf0($segCyc) }}@endif s = {{ $nf0($rop) }}
Bedarf bis Lieferung (μτ) {{ $nf0($segMu) }} Sicherheitsbestand (SS) {{ $nf0($segSS) }} Zyklusbedarf {{ $nf0($segCyc) }}

Verfügbar vs. Ziel

Position {{ $nf0($ip) }} · Ziel {{ $nf0($S) }}

Bestandsposition gegenüber Zielbestand @if($wGap > 60)auffüllen {{ $nf0($rawNeed) }}@endif Position {{ $nf0($ip) }} Meldebestand s = {{ $nf0($rop) }}
@if($rawNeed > 0) Position {{ $nf0($ip) }} liegt unter dem Meldebestand {{ $nf0($rop) }} → auffüllen bis Ziel {{ $nf0($S) }}. Rechnerisch fehlen {{ $nf0($rawNeed) }} Stk, nach Rundung/Deckelung ergibt das {{ $nf0($oq) }} Stk. @else Position {{ $nf0($ip) }} hat den Zielbestand {{ $nf0($S) }} bereits erreicht – rechnerisch besteht kein Auffüllbedarf. @endif
{{-- Schritt-für-Schritt-Herleitung --}} @php $steps = []; $steps[] = [ 'title' => 'Tagesbedarf bestimmen', 'plain' => 'Durchschnittlicher Tagesabfluss aus Lager 94 – geglättet (Trend berücksichtigt). Quelle: Online-Verkaufshistorie (Onlinekunden + Filialbestellungen).', 'formula' => 'μ/Tag = geglätteter Ø-Abfluss (Holt-Verfahren)', 'calc' => 'μ/Tag = ' . $nf2($mu) . ' Stk' . ($corrected ? ' · aus ' . $corrected . ' bereinigten Tagen' : '') . ' · Streuung σ = ' . $nf2($sigma), ]; $steps[] = [ 'title' => 'Schutzzeitraum bis zur Lieferung', 'plain' => 'So lange muss der Bestand reichen: von heute bis die nächste ExTrade-Lieferung eintrifft.', 'formula' => 'τ = heute bis Warenankunft', 'calc' => 'τ = ' . $tauDays . ' Kalendertage' . ($nTau ? ' (' . $nTau . ' Verkaufstage)' : '') . ($arrivalFmt ? ' · Ankunft ' . $arrivalFmt : ''), ]; $steps[] = [ 'title' => 'Bedarf im Schutzzeitraum', 'plain' => 'Wie viel fließt bis zur Lieferung voraussichtlich ab?', 'formula' => 'μτ = Σ Tagesprognosen ≈ μ/Tag × Verkaufstage', 'calc' => ($nTau ? 'μτ ≈ ' . $nf2($mu) . ' × ' . $nTau . ' = ' . $nf1($mu * $nTau) . ' → ' : '') . 'μτ = ' . $nf1($muTau) . ' Stk', ]; $steps[] = [ 'title' => 'Sicherheitsbestand', 'plain' => 'Puffer gegen Nachfrageschwankungen. Je höher der gewünschte Servicegrad (' . $sl . ' %), desto größer der Sicherheitsfaktor z' . ($z ? ' = ' . $nf2($z) : '') . '.', 'formula' => 'SS = z × √(Verkaufstage × σ²)', 'calc' => $ssStat !== null && $nTau ? 'SS = ' . $nf2($z) . ' × √(' . $nTau . ' × ' . $nf2($sigma) . '²) = ' . $nf1($ssStat) . ' → effektiv = ' . $nf1($ss) : 'SS = ' . $nf1($ss) . ' Stk', ]; $steps[] = [ 'title' => 'Meldebestand s', 'plain' => 'Ab diesem Bestand muss nachbestellt werden.', 'formula' => 's = μτ + SS', 'calc' => 's = ' . $nf1($muTau) . ' + ' . $nf1($ss) . ' = ' . $nf1($rop) . ' Stk', ]; $steps[] = [ 'title' => 'Zielbestand S', 'plain' => 'Bis hierhin wird aufgefüllt – Meldebestand plus Bedarf für den Wochenzyklus (' . (int) $targetDays . ' Tage Zielreichweite, weil nur einmal pro Woche bestellt wird).', 'formula' => 'S = s + Zyklusbedarf', 'calc' => 'S = ' . $nf1($rop) . ' + ' . $nf1($cycle) . ' = ' . $nf1($orderUpTo) . ' Stk', ]; $steps[] = [ 'title' => 'Auslösung prüfen', 'plain' => 'Bestandsposition des Zentrallagers (verfügbarer Bestand Lager 94; Zulauf ist derzeit nicht angebunden).', 'formula' => 'Position = Bestand Lager 94 → bestellen, wenn Position ≤ s', 'calc' => 'Position = ' . $nf1($ip) . ' ≤ s ' . $nf1($rop) . ' → ja', ]; $steps[] = [ 'title' => 'Bestellmenge', 'plain' => 'Von der Position bis zum Ziel auffüllen' . ($roundUpFrom > 0 ? ', gerundet (aufgerundet erst ab ' . $nf1($roundUpFrom * 100) . ' % eines Gebindes)' : '') . ' und auf den gemeldeten ExTrade-Bestand begrenzen.', 'formula' => 'Menge = min( runden(S − Position), Lieferantenbestand )', 'calc' => 'S − Position = ' . $nf1($orderUpTo) . ' − ' . $nf1($ip) . ' = ' . $nf1($rawNeed) . ' → gerundet: ' . $nf1($packRounded) . ($liefBestand !== null ? ' → Deckel ' . $nf0($liefBestand) : '') . ' → Bestellmenge = ' . $nf0($oq) . ' Stk', ]; @endphp

Wie die Menge zustande kommt

    @foreach($steps as $i => $s)
  1. {{ $i + 1 }}

    {{ $s['title'] }}

    {{ $s['plain'] }}

    {{ $s['formula'] }}

    {{ $s['calc'] }}

  2. @endforeach
Ergebnis Bestellmenge = {{ $nf0($oq) }} Stk{{ $proposal->failure_reason ? ' – ' . $proposal->failure_reason : '' }}
{{-- Begrenzungen --}} @if($hasTrace && count($caps) > 1)

Begrenzungen

Die kleinste Grenze bestimmt die Bestellmenge. Bindend war: {{ $binding }}.

@foreach($caps as $label => $capVal)
{{ $label }} {{ $nf0($capVal) }} Stk
@endforeach
@endif {{-- Projizierte Reichweite --}}

Projizierte Reichweite

Bestandsabbau bei Ø {{ $nf2($mu) }} Stk/Tag. @if($oosDays !== null) Voraussichtlicher Ausverkauf in {{ $oosDays }} Tagen ({{ $proposal->stockout_date->format('d.m.Y') }}). @endif

Bestandsabbau Meldebestand Sicherheitsbestand
{{-- Rohwerte (einklappbar) --}} @if(!empty($d))
Rohwerte (Algorithmus)
@foreach($d as $key => $val) @continue($key === 'trace')
{{ $key }}
{{ is_scalar($val) ? $val : json_encode($val) }}
@endforeach
@endif