@extends('admin.layout') @section('title', __('Tableau de bord')) @php use App\Helpers\CommandeHelper; // Le tunnel renvoie vers l'index des commandes filtre sur l'etat. Sans la // permission de lecture, les barres restent des barres : le compteur a du // sens meme pour qui ne peut pas ouvrir la liste. $peutVoirCommandes = auth()->user()?->can('commandes.view') ?? false; $peutVoirDecoupes = auth()->user()?->can('commande_decoupes.view') ?? false; // Chaque chiffre mene a la liste qu'il compte. Les filtres reprennent la // definition exacte du chiffre (TableauBordService, et CommandeService::VUES // dans l'api) : ce qu'on lit ici est le nombre de lignes apres le clic. // Sans le droit d'ouvrir la liste, le chiffre reste du texte. $lienCmd = fn (array $p) => $peutVoirCommandes ? route('commandes.index', $p) : null; $lienDec = fn (array $p) => $peutVoirDecoupes ? route('commande_decoupes.index', $p) : null; $a = function (?string $url, $texte, string $classe = '') { $texte = e((string) $texte); return $url ? '' . $texte . '' : $texte; }; // Jalons du parcours : la derniere position sert d'echelle a la barre. $totalParcours = collect($parcours)->max('position') ?: 1; $posEntrepot = collect($parcours)->firstWhere('cle', 'entrepot')['position'] ?? 0; $jalonEntrepot = collect($parcours)->firstWhere('cle', 'entrepot'); $jalonPose = collect($parcours)->firstWhere('cle', 'pose'); $jalonVisuel = collect($parcours)->firstWhere('cle', 'visuel'); @endphp @section('content')

@yield('title')

{{-- ================= Tuiles ================= --}}
{{ __('En fabrication') }} {{-- stretched-link : toute la tuile mene a la liste. --}}
{!! $a($lienCmd(['tri' => 'echeance']), number_format($enCours['en_fabrication'], 0, ',', ' '), 'stretched-link') !!}
{{ __('Du devis en attente au chargement') }}
{{ __('Échéance dépassée') }}
{!! $a($lienCmd(['echeance' => 'retard']), number_format($enCours['en_retard'], 0, ',', ' '), 'stretched-link') !!}
@if ($echeances['total'] > 0) {{ round($enCours['en_retard'] / $echeances['total'] * 100) }} % {{ __("de l'en-cours daté") }} @else {{ __('Aucune commande datée en cours') }} @endif
{{ __('Priorité urgent') }}
{!! $a($lienCmd(['priorite' => 3, 'tri' => 'echeance']), number_format($enCours['urgentes'], 0, ',', ' '), 'stretched-link') !!}
{!! $a($lienCmd(['priorite' => 2, 'tri' => 'echeance']), __('+ :n à refaire', ['n' => $enCours['a_refaire']]), 'tdb-sous-lien') !!}
{{ __('Reçues, à poser') }}
{!! $a($lienCmd(['vue' => 'recues_a_poser']), number_format($enCours['a_poser'], 0, ',', ' '), 'stretched-link') !!}
{{ __('sur') }} {!! $a($lienCmd(['vue' => 'recues']), number_format($enCours['recues'], 0, ',', ' '), 'tdb-sous-lien') !!} {{ __('en entrepôt') }}
{{-- ================= Colonne principale ================= --}}
{{-- ---------- Tunnel de fabrication ---------- --}}
{{ __('Le tunnel de fabrication') }} {{ __('— où sont les commandes en cours') }}

{{ __("Les commandes posées sont exclues : elles ne se pilotent plus.") }} @if ($peutVoirCommandes) {{ __("Chaque barre mène à la liste filtrée sur l'état.") }} @endif

@foreach ($tunnel as $etape) @php $balise = $peutVoirCommandes ? 'a' : 'div'; @endphp <{{ $balise }} @if ($peutVoirCommandes) href="{{ route('commandes.index', ['etat' => $etape['code']]) }}" @endif class="tdb-etape {{ $etape['n'] === 0 ? 'is-vide' : '' }}"> {{ $etape['label'] }} {{ number_format($etape['n'], 0, ',', ' ') }} @endforeach
{!! $a($lienCmd(['etat' => 1]), $tunnelPied['standby']) !!} {{ __('en standby') }} {!! $a($lienCmd(['incomplet' => 1, 'tri' => 'echeance']), $tunnelPied['incompletes']) !!} {{ __('marquées incomplètes') }} {!! $a($lienCmd(['vue' => 'poses_a_venir']), $tunnelPied['poses_a_venir']) !!} {{ __('poses programmées à venir') }}
{{-- ---------- Parcours ---------- --}}
{{ __('Où passent les jours') }} {{ __('— jalons médians depuis la réception accueil') }}

{{ __("Médianes et non moyennes : une seule date saisie de travers déplacerait une moyenne.") }} @if ($jalonEntrepot) {{ (int) $jalonEntrepot['position'] }} {{ __('jours') }} {{ __("jusqu'à l'entrepôt, contre les") }} {{ config('commandes.delais_previsionnels.standard') }} {{ __('annoncés.') }} @endif

@foreach ($parcours as $jalon) @endforeach
{{ __('accueil') }} @if ($jalonPose && $jalonEntrepot) {{ __('entrepôt') }} {{ __('pose') }} @endif
@foreach ($parcours as $jalon) {{ $jalon['label'] }} {{ (int) $jalon['duree'] }} {{ __('j') }} @endforeach
@if ($jalonVisuel && $jalonEntrepot && $jalonEntrepot['position'] > 0) @php // Le gros bloc du parcours : du visuel valide au retour // du laquage. On le nomme plutot que de laisser lire la // barre a l'oeil. $retour = collect($parcours)->firstWhere('cle', 'retour'); $bloc = $retour ? (int) ($retour['position'] - $jalonVisuel['position']) : 0; $partBloc = round($bloc / $jalonEntrepot['position'] * 100); @endphp @if ($bloc > 0)

{{ __(':bloc des :total jours tiennent entre le visuel validé et le retour du laquage.', [ 'bloc' => $bloc, 'total' => (int) $jalonEntrepot['position'], ]) }} {{ __("Soit :part % du délai de fabrication dans une seule portion du parcours.", ['part' => $partBloc]) }}

@endif @endif @if ($jalonVisuel)

{{ __('Le jalon « visuel validé » repose sur :n commandes — la colonne n\'existait pas dans le logiciel métier.', [ 'n' => number_format($jalonVisuel['n'], 0, ',', ' '), ]) }}

@endif
{{-- ---------- Commandes passees, mois par mois ---------- --}} {{-- La grille de l'historique des commandes (created_at) plutot qu'une courbe : on y lit chaque mois face au meme mois des annees d'avant. Chaque case mene a ses commandes. --}} @php $lienHistorique = auth()->user()?->can('commandes.view') ? fn (array $p) => route('commandes.historique-commandes', array_filter($p, fn ($v) => $v !== null && $v !== '')) : null; @endphp
{{ __('Commandes passées') }} {{ __('— mois par mois, année par année') }} @if ($lienHistorique) {{ __('Voir l’historique') }} @endif
@include('admin.commandes._grille-periodes', ['grille' => $grille, 'lien' => $lienHistorique])
{{-- ---------- Ce qui se fabrique ---------- --}}
{{ __('Ce qui se fabrique') }} {{ __('— sur les 24 derniers mois') }}
@forelse ($modeles as $modele) @php $url = $lienCmd(['vue' => 'accueil_24m', 'modele' => $modele['valeur']]); @endphp @empty @endforelse
{{ __('Modèle') }} {{ __('Cmd') }}
{!! $a($url, $modele['libelle']) !!} {!! $a($url, number_format($modele['n'], 0, ',', ' ')) !!}
{{ __('Aucune commande sur la période.') }}
@forelse ($couleurs as $couleur) @php $url = $lienCmd(['vue' => 'accueil_24m', 'couleur' => $couleur['valeur']]); @endphp @empty @endforelse
{{ __('Couleur') }} {{ __('Cmd') }}
{!! $a($url, $couleur['libelle']) !!} {!! $a($url, number_format($couleur['n'], 0, ',', ' ')) !!}
{{ __('Aucune commande sur la période.') }}
@if ($partCouleur > 0 && count($couleurs))

{{ __(':couleur pèse :part % des commandes colorées : un stock de poudre calé sur ce seul RAL couvre l\'essentiel du laquage.', [ 'couleur' => $couleurs[0]['libelle'], 'part' => $partCouleur, ]) }}

@endif
{{-- ================= Colonne laterale ================= --}}
{{-- ---------- Echeances ---------- --}}
{{ __('Échéances') }}

{{ __("Date de commande + délai prévisionnel : le degré d'urgence annoncé à l'usine, pas une promesse au client. Les commandes déjà fabriquées en sont exclues — à partir de l'attente de chargement, l'usine n'a plus rien à en faire. Même calcul que les pastilles de la liste.") }}

{{-- Les pastilles reprennent les facettes de l'index, avec ses compteurs cumulatifs : ce qu'elles annoncent doit etre ce qu'on voit apres le clic. Les parts du donut au-dessus sont disjointes, d'ou les libelles differents. --}} @can('commandes.view')
@foreach (config('commandes.echeances', []) as $code => $meta) @php $compte = (int) ($echeances['facettes'][$code] ?? 0); @endphp {{ __($meta['label']) }} {{ $compte }} @endforeach {{-- Pas de pastille « toutes » : l'index n'expose pas de filtre « encore attendue », le lien retirerait toute selection et sortirait les 9 947 lignes. Un compteur qui ne correspond pas a sa liste vaut moins que rien. --}}

{{ __("Ces compteurs sont cumulatifs, comme sur la liste : « sous 15 j » comprend le retard et les 7 jours.") }}

@endcan @if (count($respect['familles']))
{{ __("À l'arrivée en entrepôt") }}

{{ __("Écart à l'échéance, en médiane. Positif, l'usine a livré trop tard ; négatif, trop tôt — et sur une commande avec maçonnerie, trop tôt veut dire avant que le terrain soit prêt.") }}

@foreach ($respect['familles'] as $famille) @php $courant = $respect['en_cours'][$famille['cle']] ?? null; @endphp @php $vueFamille = 'famille_' . $famille['cle']; @endphp @endforeach
{{ __('Famille') }} {{ __('Cmd') }} {{ __('Écart') }} {{ __('En cours') }}
{!! $a($lienCmd(['vue' => 'attendues,' . $vueFamille, 'tri' => 'echeance']), $famille['label']) !!} {!! $a($lienCmd(['vue' => 'arrivees_24m,' . $vueFamille]), number_format($famille['n'], 0, ',', ' ')) !!} {{ $famille['ecart'] > 0 ? '+' : '' }}{{ (int) $famille['ecart'] }} {{ __('j') }} @if ($courant) {!! $a($lienCmd(['echeance' => 'retard', 'vue' => $vueFamille]), $courant['en_retard']) !!}/{!! $a($lienCmd(['vue' => 'attendues,' . $vueFamille, 'tri' => 'echeance']), $courant['n']) !!} @else — @endif
@php $mac = collect($respect['familles'])->firstWhere('cle', 'maconnerie'); $std = collect($respect['familles'])->firstWhere('cle', 'standard'); @endphp {{-- La maconnerie est faite par ACPORTAIL, pas par l'usine : son bareme long dit « ne nous l'envoie pas encore ». Une livraison en avance n'y est donc pas une bonne nouvelle. --}} @if ($mac && $std && $mac['tres_tot'] > 0 && $mac['ecart'] < 0)

{{ __(":tot des :n commandes avec maçonnerie sont arrivées plus d'un mois avant le besoin.", [ 'tot' => number_format($mac['tres_tot'], 0, ',', ' '), 'n' => number_format($mac['n'], 0, ',', ' '), ]) }} {{ __("Le terrain n'était pas prêt : le portail encombre l'entrepôt. Pendant ce temps :retard des :total commandes sans maçonnerie sont arrivées en retard. L'usine fabrique dans l'ordre des commandes, pas dans celui de l'urgence.", [ 'retard' => number_format($std['en_retard'], 0, ',', ' '), 'total' => number_format($std['n'], 0, ',', ' '), ]) }}

@endif @endif
{{-- ---------- Chez le laqueur ---------- --}}
{{ __('Chez le laqueur') }}

{{ __('Parties au laquage, pas encore revenues.') }}

@forelse ($laquage as $ligne) @php $url = $lienCmd(['vue' => 'laquage_ouvert', 'laqueur' => $ligne['code']]); @endphp @empty @endforelse
{{ __('Laqueur') }} {{ __('Cmd') }} {{ __('Moy.') }} {{ __('Plus vieille') }}
{!! $a($url, $ligne['laqueur']) !!} {!! $a($url, $ligne['n']) !!} {{ $ligne['age_moyen'] }} {{ __('j') }} {{ $ligne['age_max'] }} {{ __('j') }}
{{ __('Rien au laquage.') }}
@if (collect($laquage)->max('age_max') > 60)

{{ __("Une commande partie depuis plus de deux mois n'est probablement plus au laquage : c'est une date de retour qu'on a oublié de saisir.") }}

@endif
{{-- ---------- Chez le laseriste ---------- --}}
{{ __('Chez le lasériste') }}

{{ __('Découpes non reçues. :recues sont revenues, :ouvertes restent ouvertes.', [ 'recues' => number_format($decoupes['recues'], 0, ',', ' '), 'ouvertes' => $decoupes['ouvertes'], ]) }}

@forelse ($decoupes['lignes'] as $ligne) {{-- 0 : « non renseigne », comme la pastille de la liste. --}} @php $url = $lienDec(['laseriste' => $ligne['laseriste_code'], 'etat' => $ligne['etat_code']]); @endphp @empty @endforelse
{{ __('Lasériste') }} {{ __('État') }} {{ __('Lig.') }} {{ __('Depuis') }}
{!! $a($url, $ligne['laseriste']) !!} {!! $a($url, $ligne['etat']) !!} {!! $a($url, $ligne['n']) !!} {{ $ligne['depuis']?->format('d/m/y') ?? '—' }}
{{ __('Aucune découpe en attente.') }}
{{-- ---------- Photos des chantiers ---------- --}} {{-- Un chantier passé sans photo envoyée par ACPORTAIL : ses photos sont parties par WhatsApp, ou nulle part (App\Services\Plannings\PhotosDesChantiers). --}} @if ($photosChantiers)
{{ __('Photos des chantiers') }} {{ __('— depuis le :date', ['date' => $photosChantiers['depuis']->translatedFormat('j M')]) }}
@if ($photosChantiers['total'] === 0)

{{ __('Aucun chantier de pose ou de maçonnerie terminé depuis.') }}

@else

{{ trans_choice(':sans chantier passé sur :total sans photo envoyée par ACPORTAIL.|:sans chantiers passés sur :total sans photo envoyée par ACPORTAIL.', $photosChantiers['sans'], [ 'sans' => $photosChantiers['sans'], 'total' => $photosChantiers['total'], ]) }}

@foreach ($photosChantiers['equipes'] as $ligne) @endforeach
{{ __('Équipe') }} {{ __('Chantiers') }} {{ __('Sans photo') }}
{{ $ligne['nom'] }} {{ $ligne['total'] }} {{ $ligne['sans'] }}
@if ($photosChantiers['sans']) @endif @endif
@endif {{-- ---------- Reprises ---------- --}}
{{ __('Reprises') }} {{ __('— 12 mois') }}

{{ __(':total refabrications pour :accueillies commandes accueillies : :taux %.', [ 'total' => $reprises['total'], 'accueillies' => number_format($reprises['accueillies'], 0, ',', ' '), 'taux' => str_replace('.', ',', (string) $reprises['taux']), ]) }}

@forelse ($reprises['lignes'] as $ligne)
{!! $a($lienCmd(['vue' => 'reprise_12m', 'fautif' => $ligne['code']]), $ligne['fautif']) !!} {!! $a($lienCmd(['vue' => 'reprise_12m', 'fautif' => $ligne['code']]), $ligne['n']) !!}
@empty

{{ __('Aucune reprise sur la période.') }}

@endforelse
{{-- ---------- Sante des donnees ---------- --}}
{{ __('Santé des données') }}

{{ __('Ce qui fausse les compteurs ci-dessus.') }}

    {{-- Chaque ligne mene a la liste de ses commandes (ou de ses decoupes), filtree sur le probleme : on corrige sans chercher. Rien a ouvrir quand le compte est a zero, ou sans le droit de voir la liste. --}} @foreach ($sante as $anomalie) @php $cliquable = $anomalie['n'] > 0 && auth()->user()?->can($anomalie['permission']); @endphp
  • <{{ $cliquable ? 'a' : 'div' }} class="tdb-anomalie {{ $cliquable ? 'is-lien' : '' }}" @if ($cliquable) href="{{ $anomalie['lien'] }}" title="{{ __('Voir la liste pour corriger') }}" @endif> {{ number_format($anomalie['n'], 0, ',', ' ') }} {{ $anomalie['libelle'] }} {{ $anomalie['detail'] }} @if ($cliquable) @endif
  • @endforeach
{{-- Les couleurs des pastilles du tunnel viennent de la barre de filtres des commandes : on charge le meme fichier plutot que de dupliquer les valeurs, pour qu'un etat garde la meme couleur sur les deux ecrans. --}} {{-- La grille des commandes passees vient de l'historique. --}} {{-- Les graphes lisent leurs donnees ici plutot que dans une variable globale : la vue reste du Blade, le JS un fichier statique versionne. Le tableau se construit dans un bloc PHP — la directive json ne digere pas un litteral multiligne. --}} @php $donneesGraphes = [ 'echeances' => $echeances, // Les parts du donut, dans l'ordre des series : retard, sous 7 j, // 8 a 15 j, au-dela. Disjointes, comme les parts. 'liens' => $peutVoirCommandes ? [ route('commandes.index', ['echeance' => 'retard']), route('commandes.index', ['vue' => 'echeance_0_7']), route('commandes.index', ['vue' => 'echeance_8_15']), route('commandes.index', ['vue' => 'echeance_au_dela']), ] : [], 'libelles' => [ 'commandes' => __('commandes'), 'retard' => __('En retard'), // Les parts du donut sont disjointes, celles de la liste // cumulatives : on nomme la tranche pour qu'on ne lise pas // « sous 15 j » comme incluant les deux precedentes. 'urgent' => __('Sous :n j', ['n' => config('commandes.seuils_echeance.urgent', 7)]), 'proche' => __(':debut à :fin j', [ 'debut' => (int) config('commandes.seuils_echeance.urgent', 7) + 1, 'fin' => config('commandes.seuils_echeance.proche', 15), ]), 'au_dela' => __('Au-delà'), 'total' => __('encore attendues'), ], ]; @endphp @endsection