@php $nf0 = fn ($v) => number_format((float) $v, 0, ',', '.'); $pct = fn ($teil, $ganz) => $ganz > 0 ? round($teil / $ganz * 100) : 0; $lieferantName = fn ($id) => match ((int) $id) { 1 => 'Niko Liquids', 2 => 'ExTrade', 3 => 'Riccardo Onlinestore', 4 => 'Niko Liquids GmbH', default => 'Lieferant ' . $id, }; $bereichName = fn ($s) => $s === 'central' ? 'Zentrallager 94' : 'Filialen'; @endphp
← Auswertungen
@if(! $letzterTag)
Noch kein Rechenlauf erfasst. Die Bilanz entsteht am Ende jedes Laufs (replenishment:store / replenishment:central).
@else {{-- ── 1. Trichter ────────────────────────────────────────────────── Die wichtigste Zahl der Seite: von allem, was der Lauf als nötig errechnet hat, wie viel können wir tatsächlich beschaffen. --}} @foreach($trichter as $bereich => $zeilen) @php $gesamtPos = $zeilen->sum('positions'); $gesamtBest = $zeilen->sum('orderable_positions'); $gesamtOffen = $zeilen->sum('unfulfilled_positions'); $gesamtStk = $zeilen->sum('orderable_units'); $gesamtFehlt = $zeilen->sum('unfulfilled_units'); @endphp

{{ $bereichName($bereich) }}

Rechenlauf vom {{ \Illuminate\Support\Carbon::parse($letzterTag)->format('d.m.Y') }}

Deckungsquote {{ $pct($gesamtBest, $gesamtBest + $gesamtOffen) }} % der benötigten Positionen sind beschaffbar

@foreach($zeilen as $z) @php $noetig = $z->orderable_positions + $z->unfulfilled_positions; $deckung = $pct($z->orderable_positions, $noetig); $rest = $z->unfulfilled_positions - $z->reason_empty - $z->reason_triage; @endphp @endforeach
Lieferant gerechnet bestellbar Stück Deckung ohne Deckung Grund durch Pflichtbestand
{{ $z->supplier_name ?: 'Ohne Lieferant' }} {{ $nf0($z->positions) }} {{ $nf0($z->orderable_positions) }} {{ $nf0($z->orderable_units) }} {{ $deckung }} % {{ $nf0($z->unfulfilled_positions) }} @if($z->unfulfilled_units > 0) {{ $nf0($z->unfulfilled_units) }} Stück @endif @if($z->reason_empty > 0){{ $nf0($z->reason_empty) }} Quelle leer@endif @if($z->reason_triage > 0){{ $nf0($z->reason_triage) }} bei Knappheit leer ausgegangen@endif @if($z->capped_positions > 0){{ $nf0($z->capped_positions) }} gekürzt@endif @if($rest > 0){{ $nf0($rest) }} anderer Grund@endif @if($z->unfulfilled_positions === 0)–@endif @if($z->mandatory_positions > 0) {{ $nf0($z->mandatory_positions) }} {{ $pct($z->mandatory_positions, $z->positions) }} % der Zeilen @else – @endif
Summe {{ $nf0($gesamtPos) }} {{ $nf0($gesamtBest) }} {{ $nf0($gesamtStk) }} {{ $nf0($gesamtOffen) }} {{ $nf0($gesamtFehlt) }} Stück
@endforeach {{-- ── 2. Verfügbarkeit beim Lieferanten ──────────────────────────── Der Trichter oben sagt, wie viel durchkommt. Hier steht, woran es liegt: was der Lieferant überhaupt am Lager hat. --}}

Verfügbarkeit beim Lieferanten

Anteil der Artikel mit Bestand, aus unserer täglichen Momentaufnahme. Riccardo fehlt hier bewusst – dessen Bestand kommt aus Lager 94, nicht aus dem Lieferantensatz.

@forelse($verfuegbarkeit as $lid => $reihe) @if($reihe->sum('mit_bestand') > 0) @php $letzte = $reihe->last(); @endphp

{{ $lieferantName($lid) }}

{{ $pct($letzte->mit_bestand, $letzte->artikel) }} % {{ $nf0($letzte->mit_bestand) }} von {{ $nf0($letzte->artikel) }}

{{-- Balkenreihe: ein Balken je Tag, Höhe = Anteil mit Bestand --}}
@foreach($reihe as $tag) @php $anteil = $pct($tag->mit_bestand, $tag->artikel); @endphp
@endforeach
{{ \Illuminate\Support\Carbon::parse($reihe->first()->snapshot_date)->format('d.m.') }} {{ \Illuminate\Support\Carbon::parse($reihe->last()->snapshot_date)->format('d.m.') }}
@endif @empty

Noch keine Momentaufnahmen im gewählten Zeitraum.

@endforelse
{{-- Gegenprobe: unsere Kopie gegen die echte Wawi von Niko --}}

Wie gut trifft unsere Bestandskopie?

Seit dem Lesezugriff auf die Wawi von Niko lässt sich das prüfen. Weicht die Kopie ab, deckelt der Rechenlauf gegen einen falschen Wert – und das fällt sonst erst bei der Lieferung auf.

@if(! $nikoProbe['ok'])

{{ $nikoProbe['meldung'] }}

@else

{{ $pct($nikoProbe['gleich'], $nikoProbe['geprueft']) }} %

stimmen überein
({{ $nf0($nikoProbe['geprueft']) }} geprüft)

{{ $nf0($nikoProbe['kopie_leer']) }}

Kopie sagt leer,
Niko hat Ware

{{ $nf0($nikoProbe['wirklich_leer']) }}

Kopie sagt Ware,
Niko ist leer

Verfügbarkeit auf denselben Artikeln

Unsere Kopie: {{ $pct($nikoProbe['kopie_verfuegbar'], $nikoProbe['geprueft']) }} %

Niko wirklich: {{ $pct($nikoProbe['echt_verfuegbar'], $nikoProbe['geprueft']) }} %

Der Balken links zeigt für Niko einen deutlich niedrigeren Wert – er zählt auch die Artikel mit, für die keine Lieferantennummer gepflegt ist und die wir deshalb ohnehin nicht bestellen können.

Die beiden rechten Zahlen sind die teuren Fälle: Kopie leer, Ware da heißt, wir hätten nicht bestellt, obwohl geliefert worden wäre – Kopie voll, Niko leer heißt Bestellung ins Leere. Stehen beide auf 0, taugt die Kopie für die Frage ob lieferbar. Beim wieviel weicht sie ab, und genau darauf deckelt der Rechenlauf die Menge. Deshalb zieht der Nachschub bei Niko seit dem 08.09.2026 den echten Bestand heran und nicht mehr die Kopie.

@endif
{{-- ── 3. Regallücken ─────────────────────────────────────────────── Die Kehrseite: nicht was wir bestellen wollen, sondern was in der Filiale bereits fehlt. --}} @if($luecken->isNotEmpty())

Leere Plätze je Filiale

Artikel, die sich in dieser Filiale verkaufen, am {{ \Illuminate\Support\Carbon::parse($lueckenTag)->format('d.m.Y') }} aber keinen Bestand hatten. Insgesamt {{ $nf0($luecken->sum('leer')) }} Lücken.

@foreach($luecken as $l) @endforeach
Filiale leere Artikel
{{ $l->name }} {{ $nf0($l->leer) }}

Fehlt in den meisten Filialen

Wenn derselbe Artikel überall fehlt, liegt es selten an der Filiale – dann fehlt er in der Beschaffung.

@foreach($lueckenArtikel as $a) @endforeach

{{ $a->name ?: $a->sku }}

{{ $a->sku }}@if($a->manufacturer) · {{ $a->manufacturer }}@endif

{{ $a->filialen }} Filialen
@endif {{-- ── 4. Ausgelöste Bestellungen ───────────────────────────────── --}}

Ausgelöste Bestellungen

Letzte {{ $tage }} Tage. Der Rechenlauf schlägt vor – hier steht, was tatsächlich rausging.

@if($bestellungen->isEmpty())

In diesem Zeitraum wurde noch nichts ausgelöst.

@else
@foreach($bestellungen as $b) @endforeach
Bereich Lieferant Status Belege Positionen Stück
{{ $bereichName($b->scope) }} {{ $lieferantName($b->supplier_id) }} {{ $b->status }} {{ $nf0($b->n) }} {{ $nf0($b->pos) }} {{ $nf0($b->stueck) }}
@endif
{{-- ── 5. Verlauf, sobald es einen gibt ─────────────────────────── --}} @php $verlaufTage = $verlauf->flatten(1)->pluck('run_date')->unique()->count(); @endphp

Entwicklung

@if($verlaufTage < 2)

Für einen Verlauf fehlt noch die Historie: es liegt {{ $verlaufTage }} Tag vor.

Das hat einen Grund und ist kein Fehler: jeder Rechenlauf entfernt die nicht bestellten Vorschläge der Vortage, weil deren Mengen auf alten Beständen beruhen. Seit dem 08.09.2026 wird am Ende jedes Laufs eine Tagesbilanz festgeschrieben – wenige Zahlen statt tausender Zeilen. Ab dem zweiten Lauf entsteht hier die Kurve: Deckungsquote, ausgelöste Mengen und der Anteil, den der Pflichtbestand verursacht.

@else
@foreach($verlauf as $bereich => $reihe) @foreach($reihe->sortByDesc('run_date') as $t) @endforeach @endforeach
Tag Bereich bestellbar Stück ohne Deckung Deckung durch Pflichtbestand
{{ \Illuminate\Support\Carbon::parse($t->run_date)->format('d.m.Y') }} {{ $bereichName($bereich) }} {{ $nf0($t->bestellbar) }} {{ $nf0($t->stueck) }} {{ $nf0($t->offen) }} {{ $pct($t->bestellbar, $t->bestellbar + $t->offen) }} % {{ $nf0($t->pflicht) }}
@endif
@endif