@extends('admin.layout') @section('title', __('Commande') . ' #' . ($commande['id'] ?? $modele->id)) @section('content') @include('admin._retour', ['vers' => route('commandes.index')]) @php // Les passages chez le laqueur, du premier au dernier. Le 1 est le // laquage normal ; les suivants sont des relaquages, avec fautif et // raison. Un passage sans date de retour est en cours. // // Calcules avant le titre : ses boutons en dependent. $laquages = $modele->laquages; $laquageOuvert = $laquages->firstWhere('date_retour', null); @endphp {{-- Toute la fiche est une zone (data-zone) : apres un geste, js/sur-place.js la remplace sans recharger la page. En-tete, bandeaux, suivi, onglets et fenetres dependent tous de l'etat de la commande ; pas de zones plus fines, les blocs sont des enfants directs de la section et le css compte dessus (.fiche-commande > .card). --}}
{{-- `data-messages` : apres un geste fait sur place, ces bandeaux flottent en haut a droite au lieu de rester ici, hors de vue. --}}
@if (session('success'))
{{ session('success') }}
@endif @if (session('error'))
{{ session('error') }}
@endif {{-- Une saisie refusee dans une fenetre (fautif oublie au relaquage) : la fenetre s'est refermee, l'erreur se lit ici. --}} @if ($errors->any())
{{ $errors->first() }}
@endif
@if ($commande === null)
{{ __('Le service commandes est momentanément indisponible : le client et le devis Gestan ne peuvent pas être affichés. Seules les données locales sont visibles ci-dessous.') }}
@endif @php // Repli sur le modèle local quand l'api n'a pas répondu. $c = $commande ?? [ 'id' => $modele->id, 'client_id' => $modele->client_id, 'devis_id' => $modele->devis_id, 'couleur' => $modele->couleur, 'statut' => $modele->statut, 'etat' => $modele->etat, 'priorite' => $modele->priorite, 'finition' => $modele->finition, 'platine' => $modele->platine, 'destination' => $modele->destination, 'url' => $modele->url, 'longueur' => $modele->longueur, 'hauteur' => $modele->hauteur, 'quantite' => $modele->quantite, 'prix_usine' => $modele->prix_usine, 'maconnerie' => $modele->maconnerie, 'cintrage' => $modele->cintrage, 'negoce' => $modele->negoce, 'created_at' => $modele->created_at, 'libelle_modele' => $modele->modele, 'type' => $modele->type ? ['nom' => $modele->type->nom] : null, ]; $client = $c['client'] ?? null; $devis = $c['devis'] ?? null; // Les mises a jour du plan, la plus recente d'abord : la derniere // nourrit le bandeau tant que l'usine ne l'a pas vue, et toutes // restent comptees sous le lien du plan une fois vues. Une note // reprise de l'ancien logiciel n'a pas de date : elle passe apres. $notesPlan = $modele->observations ->filter(fn ($observation) => (int) $observation->type === \App\Models\Observation::TYPE_PLAN) ->sortByDesc(fn ($observation) => $observation->created_at?->timestamp ?? 0) ->values(); // « nom prenom — 12/09/2026 ». L'auteur peut manquer : note creee // hors requete web, ou utilisateur supprime — `user_id` n'a pas de // contrainte, la relation rend alors null. On n'affiche alors que // la date, plutot qu'un tiret qui ferait croire a une erreur. $signature = function ($observation) { $auteur = $observation->user ? trim($observation->user->nom . ' ' . $observation->user->prenom) : ''; $date = $observation->created_at?->format('d/m/Y'); return trim($auteur . ($auteur && $date ? ' — ' : '') . $date); }; $dates = [ __('Réception accueil') => $c['date_accueil'] ?? null, __('Bureau d’étude') => $c['date_etude'] ?? null, __('Côtes validées') => $c['date_cotes_ok'] ?? null, __('Visuel validé') => $c['date_visuel_ok'] ?? null, __('Envoi laquage') => $c['date_laq_envoi'] ?? null, __('Réception laquage') => $c['date_laq_recep'] ?? null, __('Entrepôt') => $c['date_entrepot'] ?? null, __('Pose') => $c['date_pose'] ?? null, ]; @endphp {{-- ---------------- En-tete ---------------- Ce qu'on vient lire d'un coup d'œil (quoi, ou ça en est) et ce qu'on vient faire, avant tout le reste. Les deux signalements, plus rares, sont ranges derriere « … » : l'en-tete tient sur une ligne. Refabriquer ouvre la creation pre-remplie avec cette commande (CommandeController::refabriquer). --}} @php $typeNom = \App\Helpers\CommandeHelper::nomTraduit($c['type']['nom'] ?? null, ''); $peutSignaler = auth()->user()->canAny(['commandes.edit', 'commandes.laquage']) && !$modele->incomplet; $peutPlan = auth()->user()->can('commandes.edit'); @endphp

{{ __('Commande') }} #{{ $c['id'] ?? $modele->id }} @if ($typeNom !== '' || filled($c['libelle_modele'] ?? null)) · {{ trim($typeNom . ' ' . ($c['libelle_modele'] ?? '')) }} @endif

@if (filled($c['etat'] ?? null)) {{ \App\Helpers\CommandeHelper::etat($c['etat']) }} @endif @if (filled($c['statut'] ?? null)) {{ \App\Helpers\CommandeHelper::statut($c['statut']) }} @endif @if (filled($c['priorite'] ?? null)) {{ \App\Helpers\CommandeHelper::priorite($c['priorite']) }} @endif @if (!empty($c['maconnerie'])) {{ __('Maçonnerie') }} @endif @if (!empty($c['cintrage'])) {{ __('Cintrage') }} @endif @if (!empty($c['negoce'])) {{ __('Négoce') }} @endif
@if (!empty($c['created_at']))
{{ __('Commandée le :date', ['date' => \Carbon\Carbon::parse($c['created_at'])->format('d/m/Y')]) }} {{-- Sortie du standby : l'echeance part d'ici, pas de la commande (CommandeHelper::depart). A cote de la date de commande plutot que dans la frise, qui tient ses huit etapes sur une ligne. --}} @if (!empty($c['lancee_le'])) · {{ __('lancée le :date', ['date' => \Carbon\Carbon::parse($c['lancee_le'])->format('d/m/Y')]) }} @endif
@endif
@can('commandes.view') {{ __('Fiche') }} @endcan @can('commandes.edit') {{ __('Modifier') }} @endcan @can('commandes.create') {{ __('Refabriquer') }} @endcan {{-- Laquage : un seul des deux boutons a la fois, selon qu'un passage est ouvert. Un relaquage, c'est la meme commande qui repart : aucune commande fille (contrairement a Refabriquer). --}} @can('commandes.laquage') @if ($laquageOuvert) @else @endif @endcan {{-- Plan mis a jour : toujours proposable, meme deja signale et pas encore vu (une note de plus, une notification de plus). Incomplete : le signalement part d'ici ; une fois signalee, les actions passent dans le bandeau, sous ce qui manque. --}} @if ($peutPlan || $peutSignaler) @endif
{{-- Incomplete : en tete, c'est ce qu'on doit voir en ouvrant la fiche. Ce qui manque est dans la derniere note INCOMPLET (code 3 de `observation_types`) ; elle reste aussi listee avec les autres. --}} @if ($modele->incomplet) @php $noteIncomplet = $modele->observations ->filter(fn ($observation) => (int) $observation->type === 3) ->sortByDesc('created_at') ->first(); @endphp
{{ __('Commande incomplète') }} @if ($noteIncomplet)
{{ $noteIncomplet->observation }} — {{ $signature($noteIncomplet) }}
@endif
@canany(['commandes.edit', 'commandes.laquage'])
@csrf
@endcanany
@endif {{-- Plan mis a jour, pas encore vu par l'usine : ce qui change est dans la derniere note PLAN. Passee par __() : la note d'un lien modifie est un texte fixe, traduit pour l'usine ; une note libre, elle, ressort telle quelle. --}} @if ($modele->maj_plan) @php $notePlan = $notesPlan->first(); @endphp
{{ __('Plan mis à jour') }} @if (!empty($c['url'])) · {{ __('Ouvrir le PDF') }} @endif @if ($notePlan)
{{ __($notePlan->observation) }} @if ($signature($notePlan) !== '') — {{ $signature($notePlan) }} @endif
@endif
@if (auth()->user()->estPortugal())
@csrf
@else {{ __('En attente de l’usine') }} @endif
@endif @php $refab = $modele->refabrication; $refaite = $modele->refabricationsEnfants; @endphp {{-- ---------------- Refabrication ---------------- Pourquoi cette commande existe, ou ce qu'elle a donne : une ligne sous l'en-tete, pas un onglet, on doit le savoir en ouvrant. La table `refabrications` est locale : l'api ne la sert pas, c'est le seul endroit ou elle s'affiche. --}} @if ($refab || $refaite->isNotEmpty())
@if ($refab)
{{ __('Cette commande refait la commande') }} #{{ $refab->commande_parente_id }} @if ($refab->commandeParente?->type) ({{ \App\Helpers\CommandeHelper::nomTraduit($refab->commandeParente->type->nom) }}) @endif @if ($refab->created_at) — {{ \Carbon\Carbon::parse($refab->created_at)->format('d/m/Y') }} @endif · {{ __('Fautif') }} @if (filled($refab->fautif)) {{ \App\Helpers\CommandeHelper::fautif($refab->fautif) }} @else {{ __('Non renseigné') }} @endif {{-- 60 lignes sur 66 n'en portent pas : le dire plutot que de laisser un vide. --}} · {{ __('Raison') }} {{ filled($refab->raison) ? $refab->raison : __('Non renseignée') }}
@endif {{-- L'autre sens : cette commande a du etre refaite. --}} @if ($refaite->isNotEmpty())
{{ __('Cette commande a été refaite par') }} @foreach ($refaite as $enfant) #{{ $enfant->commande_id }}@if (!$loop->last), @endif @endforeach
@endif
@endif {{-- ---------------- Suivi ---------------- Les huit dates du deroule, en frise : une etape datee est faite. Un laquage parti et pas revenu le dit a la place de sa date. --}} @php $validateur = \App\Helpers\CommandeHelper::validateurVisuel($modele->validateur_visuel, ''); $laquageEnCours = !empty($c['date_laq_envoi']) && empty($c['date_laq_recep']); @endphp
@foreach ($dates as $libelle => $valeur)
{{ $libelle }}
@if ($valeur) {{ \Carbon\Carbon::parse($valeur)->format('d/m/Y') }} @elseif ($laquageEnCours && $libelle === __('Réception laquage')) {{ __('en cours') }} @else — @endif
@endforeach
{{-- Lu sur $modele et non sur $c : le select de l'api ne porte pas cette colonne, le modele local si, et il est toujours charge. --}} @if ($validateur !== '')
{{ __('Visuel validé par') }} {{ $validateur }}
@endif
{{-- ---------------- Client / devis Gestan ---------------- A droite sur PC, en premier sur telephone. --}}
{{ __('Client & devis (Gestan)') }}
{{ $client['libelle'] ?? '—' }} · {{ __('n°') }} {{ $c['client_id'] ?? '-' }}
@if (filled(trim(($client['adresse1'] ?? '') . ($client['cp'] ?? '') . ($client['ville'] ?? ''))))
{{ trim(($client['adresse1'] ?? '') . ' ' . ($client['adresse2'] ?? '')) }}, {{ $client['cp'] ?? '' }} {{ $client['ville'] ?? '' }}
@endif @if (filled($client['tel'] ?? null))
{{ $client['tel'] }}
@endif @if (filled($client['email'] ?? null)) @endif
{{ __('Devis') }} {{ __('n°') }} {{ $c['devis_id'] ?? '-' }} @if (filled($devis['nature'] ?? null)) · {{ $devis['nature'] }} @endif
{{-- Le chantier : l'adresse saisie a la commande pour ce devis (adresses_chantier), sinon celle du devis — la meme regle que le planning (PlanningService::lieu). --}} @php $adresseChantier = \App\Models\AdresseChantier::pourCommande($c['devis_id'] ?? null, $modele->id); @endphp @if ($adresseChantier?->complete())
{{ __('Chantier') }} : {{ $adresseChantier->complete() }} ({{ __('saisie à la commande') }})
@elseif (filled(trim(($devis['adresse_1'] ?? '') . ($devis['code_postal'] ?? '') . ($devis['ville'] ?? ''))))
{{ __('Chantier') }} : {{ trim(($devis['adresse_1'] ?? '') . ' ' . ($devis['adresse_2'] ?? '')) }}, {{ $devis['code_postal'] ?? '' }} {{ $devis['ville'] ?? '' }}
@endif @if (filled($devis['reference'] ?? null))
{{ __('V/REF') }} : {{ $devis['reference'] }}
@endif @if (filled($devis['responsable'] ?? null))
{{ __('Responsable') }} : {{ $devis['responsable'] }}
@endif
{{-- ---------------- Produit ---------------- Statut, etat, priorite, type, modele et options sont dans l'en-tete. Une valeur vide ne s'affiche pas. --}}
{{ __('Produit') }}
@php $produit = array_filter([ __('Dimensions') => filled($c['longueur'] ?? null) || filled($c['hauteur'] ?? null) ? ($c['longueur'] ?? '?') . ' × ' . ($c['hauteur'] ?? '?') . ' mm' : null, __('Couleur') => \App\Helpers\CommandeHelper::couleur($c['couleur'] ?? null), __('Finition') => \App\Helpers\CommandeHelper::finition($c['finition'] ?? null), __('Platine') => \App\Helpers\CommandeHelper::platine($c['platine'] ?? null), __('Quantité') => $c['quantite'] ?? null, __('Destination') => \App\Helpers\CommandeHelper::destination($c['destination'] ?? null), __('Prix usine') => $c['prix_usine'] ?? null, ], fn ($v) => filled($v)); @endphp
@foreach ($produit as $libelle => $valeur)
{{ $libelle }}
{{ $valeur }}
@endforeach
@if (!empty($c['url'])) {{ __('Ouvrir le PDF') }} {{-- Faute de plan, les poteaux 150 ont une fiche de fabrication generee pour l'atelier. Le test porte sur la valeur brute : le cast remonte le modele en majuscules. --}} @elseif ($modele->getRawOriginal('modele') === 'poteau 150') {{ __('Générer la fiche de fabrication') }} @else {{ __('Pas de plan') }} @endif {{-- Que le plan a change, une fois l'usine au courant : le bandeau s'en va avec « Plan vu », pas l'information. Le detail de chaque mise a jour est dans les observations. --}} @if ($notesPlan->isNotEmpty() && !$modele->maj_plan) @php $derniere = $notesPlan->first()->created_at; $fois = trans_choice('{1} Mis à jour une fois|[2,*] Mis à jour :n fois', $notesPlan->count(), ['n' => $notesPlan->count()]); @endphp · {{ $derniere ? __(':fois, dernière le :date', ['fois' => $fois, 'date' => $derniere->format('d/m/Y')]) : __(':fois, dans l’ancien logiciel', ['fois' => $fois]) }} @endif
{{-- ---------------- Contexte, en onglets ---------------- Ce qu'on consulte, pas ce qu'on vient lire en ouvrant. L'onglet ouvert est le premier qui a quelque chose a montrer. --}} @php $decoupes = $c['decoupes'] ?? []; $onglets = [ 'observations' => [__('Observations'), $fil->count()], 'decoupes' => [__('Découpes'), count($decoupes)], 'laquages' => [__('Laquages'), $laquages->count()], 'historique' => [__('Historique'), count($historique)], ]; $ouvert = collect($onglets)->search(fn ($o) => $o[1] > 0) ?: 'observations'; @endphp
{{-- Le fil des observations : commande et decoupes, ajout en tete, l'auteur corrige ou retire les siennes. --}}
@include('admin.commandes._observations-fil', [ 'fil' => $fil, 'decoupes' => $decoupes, 'commandeId' => $modele->id, ])
@include('admin.commandes._decoupes-fiche', [ 'decoupes' => $decoupes, 'documents' => $documents, 'commandeId' => $modele->id, ])
{{-- Un aller-retour chez le laqueur par ligne. `commandes` ne garde que le dernier passage (colonnes date_laq_*), l'historique complet vit dans la table `laquages`. --}}
@forelse ($laquages as $laquage)
{{ __('Passage') }} {{ $laquage->passage }} @if (!$laquage->estRelaquage()) {{ __('initial') }} @endif · {{ \App\Helpers\CommandeHelper::laqueur($laquage->laqueur, '—') }} · {{ $laquage->date_envoi?->format('d/m/Y') ?? '—' }} @if ($laquage->date_retour) {{ $laquage->date_retour->format('d/m/Y') }} @else {{ __('en cours') }} @endif @if (filled($laquage->fautif)) · {{ \App\Helpers\CommandeHelper::fautif($laquage->fautif) }} @endif @if (filled($laquage->raison))
{{ $laquage->raison }}
@endif
@empty

{{ __('Aucun passage au laquage.') }}

@endforelse
@include('admin.commandes._historique', ['historique' => $historique, 'commandeId' => $modele->id])
{{-- Saisie du depart et du retour. Deux petites fenetres plutot qu'un ecran : c'est une ligne a poser, depuis la fiche ou on la lit. --}} @can('commandes.laquage') @unless ($laquageOuvert) @php $relaquage = $laquages->isNotEmpty(); @endphp @else @endunless @endcan {{-- Les decoupes oubliees a la saisie. Le meme formulaire que sous une ligne de la liste ; ce que la commande porte deja se lit dans l'onglet, juste derriere. --}} @can('commandes.decoupes') @include('admin.commandes._decoupes_catalogue', ['decoupes' => $catalogueDecoupes]) @endcan {{-- Le suivi et les pieces jointes d'une ligne de decoupe (onglet Decoupes) : les fenetres de l'ecran des decoupes. Deja dans la zone de la fiche, d'ou `zone` a faux. --}} @include('admin.commande_decoupes._modale-suivi', ['catalogue' => $catalogueDecoupes ?? collect(), 'zone' => false]) @include('admin.commande_decoupes._modale-joindre') {{-- Ce qui change sur le plan. L'usine est prevenue a l'enregistrement. --}} @can('commandes.edit') @endcan {{-- Ce qui manque. Rappelee sur une commande deja incomplete, elle ajoute une note : la plus recente s'affiche dans le bandeau, les autres restent dans les observations. --}} @canany(['commandes.edit', 'commandes.laquage']) @endcanany
{{-- La structure de la fiche (en-tete, blocs, onglets) est celle des fiches Gestan ; les etiquettes (etat, statut, fautif) sont celles des listes. --}} @endsection