@extends('admin.layout') @section('title', __('Gestion des commandes')) @section('content')

@yield('title')

{{-- Les listes d'arrivages sont des ecrans du bureau : leur propre permission, que l'usine portugaise n'a pas. --}} @can('commandes.arrivages') {{ __('Liste des arrivages') }} {{ __('Arrivage Nathalie') }} @endcan @can('commandes.create') {{ __('Commande') }} @endcan
{{-- Les zones (data-zone) : ce que js/sur-place.js remplace apres un geste, sans recharger la page — messages, pastilles, compte, lignes, pagination. Le reste ne bouge pas. --}}
@if (session('success'))
{{ session('success') }}
@endif @if (session('error'))
{{ session('error') }}
@endif {{-- Les lots (etat, laquage) partent d'une barre ou d'une modale qui n'ont pas de champ ou afficher l'erreur : elle vient ici. --}} @if ($errors->any())
{{ $errors->first() }}
@endif
@if ($unavailable || $stale)
@if ($unavailable) {{ __('Le service commandes est momentanément indisponible : aucune donnée à afficher.') }} @else {{ __("L'api ne répond pas : ces commandes sont la dernière copie connue, elles peuvent être périmées.") }} @endif @if ($apiMessage)
{{ __('Détail (APP_DEBUG) :') }} {{ $apiMessage }} @endif
@endif {{-- Recherche côté api : elle porte sur l'ensemble des commandes, pas seulement sur la page affichée. Les filtres actifs sont conservés en champs cachés pour ne pas les perdre en cherchant. --}}
@foreach ($filtres as $champ => $valeur) @endforeach
@if ($q !== '') {{ __('Réinitialiser') }} @endif
@include('admin.commandes._filtres')
{{ __(':n commande(s)', ['n' => $commandes->total()]) }} @if ($commandes->total() > 0) — {{ __('page :courante / :total', ['courante' => $commandes->currentPage(), 'total' => $commandes->lastPage()]) }} @endif
{{-- Une ligne par commande (cmd-table-compacte) : les libelles longs sont coupes, leur texte entier reste au survol. Avant, le modele et sa couleur sur trois lignes, la ville sur deux et quatre gros boutons faisaient 85 a 105 px par ligne ; ~34 px maintenant. --}} @include('admin.commandes._icones-options') {{-- Coche de page : elle ne parle que des lignes affichees, jamais de la selection entiere. Sans la selection, pas de colonne du tout. --}} @can('commandes.selection') @endcan {{-- Statut et priorite n'ont plus leur colonne : ORIGINAL et NORMALE couvrent presque toutes les lignes. Ils ne se montrent qu'en exception, a cote de l'id. --}} {{-- Options : colonne etroite, icones seules. Fondues dans la cellule du modele, elles ne se voyaient plus ; leur colonne ne coute que la largeur de quelques icones. --}} {{-- Plan du bureau d'etude (commandes.url) : une colonne a lui, icone PDF, comme dans l'ancien logiciel. Le nom du modele n'est plus un lien : on confondait le plan avec la fiche de la commande, qui prend l'icone QR code dans les actions. --}} {{-- Échéance estimée par ACPORTAIL : un sablier et pas une date, pour ne pas la confondre avec la date de réception réelle juste à côté. Le texte masqué sert de libellé aux cartes sur téléphone (responsive-tables.js). --}} {{-- Réception à l'entrepôt (PROCESS.D_RéceptionEntrepot) --}} @php // Évalué une fois pour toute la page plutôt qu'à chaque ligne. $peutVoirClient = auth()->user()?->can('clients.view'); $peutVoirDevis = auth()->user()?->can('devis.view'); // Laissees de cote par le dernier changement d'etat en lot // (retour du laquage manquant) : elles clignotent. $lignesBloquees = array_map('intval', (array) session('lignes_bloquees', [])); @endphp @forelse ($commandes as $commande) @php $client = $commande['client'] ?? null; $devis = $commande['devis'] ?? null; // Le nom vient du contact Gestan ; si le contact n'a pas // pu être résolu, le devis porte souvent la même raison // sociale et évite une ligne vide. $nomClient = $client['libelle'] ?? ($devis['raison_sociale'] ?? null); // Gestan n'a pas résolu le contact : plutôt qu'un tiret // sur lequel cliquer, on remet l'identifiant, qui reste // la seule prise pour retrouver la fiche. if (!$nomClient && !empty($commande['client_id'])) { $nomClient = __('Client') . ' ' . $commande['client_id']; } // La ville du chantier : celle de l'adresse saisie à // la commande s'il y en a une (adresses_chantier), // sinon celle du devis. Sans devis — 647 commandes, // dont 131 qui portaient un 0 avant la normalisation — // on se rabat sur la ville du contact Gestan. // // ⚠️ Ce n'est pas la même chose : CONTACT.VILLEP est la // ville du client, pas celle du chantier. Pour un pro qui // commande pour plusieurs sites, la colonne affichera son // siège. C'est assumé : mieux vaut une ville approchée // qu'une colonne vide sur 6,5 % des lignes. // La saisie garde sa casse (« saint sever ») : en // majuscules, comme les villes venues de Gestan. $ville = mb_strtoupper(trim($commande['ville_chantier'] ?? ''), 'UTF-8') ?: trim($devis['ville'] ?? '') ?: trim($client['ville'] ?? ''); @endphp {{-- data-ligne : la ligne qu'un autre poste a changee se surligne quand la liste se relit (js/sur-place.js). --}} in_array((int) $commande['id'], $lignesBloquees, true)])> {{-- Encore chez le laqueur : la modale de laquage reprend son laqueur et sa date d'envoi (`data-laqueur`, `data-envoi`), ce qui a deja ete saisi. Un relaquage ne passe que par la fiche. --}} @php $dejaLaquee = filled($commande['date_laq_envoi'] ?? null); $chezLeLaqueur = $dejaLaquee && blank($commande['date_laq_recep'] ?? null); @endphp @can('commandes.selection') @endcan {{-- L'id, et en exception seulement le statut (hors ORIGINAL) et la priorite (hors NORMALE) : les montrer sur chaque ligne ne signalait plus rien. --}} @php $statut = $commande['statut'] ?? null; $statutLibelle = (int) $statut !== 1 ? \App\Helpers\CommandeHelper::statut($statut, '') : ''; $priorite = $commande['priorite'] ?? null; $prioriteLibelle = (int) $priorite !== 1 ? \App\Helpers\CommandeHelper::priorite($priorite, '') : ''; @endphp {{-- Client : lien vers la fiche Gestan. Sans identifiant ou sans le droit de la consulter, on garde le nom en texte simple plutôt qu'un lien mort. --}} {{-- Ville du chantier, portée par le devis --}} @php /* Le plan du bureau d'etude prime toujours. A defaut, les poteaux 150 ont une fiche generee : G la distingue d'un vrai plan, le temps des tests. */ $lienPlan = $commande['url'] ?? null; $ficheGeneree = empty($lienPlan) && mb_strtoupper((string) ($commande['modele'] ?? '')) === 'POTEAU 150'; if ($ficheGeneree) { $lienPlan = route('commandes.fiche-poteau', $commande['id']); } @endphp {{-- Date prévisionnelle : calculée à partir de la date de commande, allongée par la maçonnerie et le cintrage. Elle annonce une urgence à l'usine : une fois le portail fabriqué — en attente de chargement, chargé ou arrivé — l'estimation n'a plus d'objet. Tant qu'il reste à produire, une pastille dit ce qui presse. --}} {{-- État : étiquette colorée, mêmes couleurs que les pastilles de la barre de filtres. --}} {{-- Petits boutons, discrets tant qu'on ne survole pas la ligne (pleins sur un ecran tactile, qui ne survole pas). --}} {{-- `data-resume` : ce que la fenetre de laquage affiche pour qu'on sache quelle commande part (client, modele, couleur). Sur la cellule et non sur le bouton laquage : une commande deja laquee n'a plus ce bouton, mais peut etre cochee. --}} {{-- Ligne allegee pour qui coche et modifie (bureau, administrateurs) : l'etiquette et le laquage d'une seule commande passent par sa case et la barre, qui font le meme geste. Les autres gardent ces boutons : sans la selection, ils n'auraient plus d'autre chemin ; l'usine, qui n'a pas l'edition, laque d'abord ligne par ligne. --}} @php $ligneAllegee ??= auth()->user()?->can('commandes.selection') && auth()->user()?->can('commandes.edit'); @endphp @if ($decoupesCommande->isNotEmpty() || auth()->user()->can('commandes.decoupes')) @include('admin.commandes._decoupes-detail', [ 'commandeId' => $commande['id'], 'decoupes' => $decoupesCommande, 'documents' => $documentsDecoupes, ]) @endif @empty @endforelse
{{ __('ID') }} {{ __('Client') }} {{ __('Ville') }}{{ __('Options') }}{{ __('Plan') }} {{ __('Modèle') }} {{ __('Commande') }} {{ __('Échéance') }} {{ __('Réception') }} {{ __('État') }}
{{-- Le numero mene a la fiche, comme le client a la sienne. --}} @can('commandes.view') {{ $commande['id'] }} @else {{ $commande['id'] }} @endcan @if ($prioriteLibelle !== '') {{ ['URGENT' => 'URG'][$prioriteLibelle] ?? $prioriteLibelle }} @endif @if ($statutLibelle !== '') {{-- Abrege : la colonne de l'id se regle sur sa ligne la plus large. Le libelle entier reste au survol. --}} {{ ['REFABRICATION' => 'REFAB', 'RELAQUAGE' => 'RELAQ', 'MODIFICATION' => 'MODIF'][$statutLibelle] ?? $statutLibelle }} @endif @if (!empty($commande['incomplet'])) {{-- Ce qui manque est ecrit dans la note INCOMPLET de la fiche : l'api ne la sert pas a la liste. --}} INC @endif @if (!empty($commande['maj_plan'])) {{-- Plan mis a jour que l'usine n'a pas encore vu. --}} PLAN @endif @if (!empty($commande['client_id']) && $peutVoirClient) {{ $nomClient ?: '—' }} @else {{ $nomClient ?: '—' }} @endif @if (!empty($commande['devis_id']) && $peutVoirDevis) {{ $ville ?: '—' }} @else {{ $ville ?: '—' }} @endif {{-- Icones seules, le nom au survol --}} {{-- Pictos dessines dans _icones-options. --}} @if (!empty($commande['maconnerie'])) @endif @if (!empty($commande['cintrage'])) @endif {{-- Le nombre plutot que la liste : une commande peut en porter des dizaines, qui allongeaient la ligne d'autant. Les noms restent au survol. Seules les decoupes du catalogue en ont un. --}} @php $decoupesCommande = collect($commande['decoupes'] ?? []); $nomsDecoupes = $decoupesCommande ->pluck('nom') ->filter(fn ($nom) => filled($nom)); @endphp {{-- Avec des lignes de decoupe, le picto deroule leur detail sous la commande (_decoupes-detail). Sans, la case est cochee mais il n'y a rien a montrer. --}} @if ($decoupesCommande->isNotEmpty()) @elseif (auth()->user()->can('commandes.decoupes')) {{-- Aucune ligne : le meme accordeon, reduit au champ d'ajout. Le picto est en pointilles tant que la case Decoupe n'est pas cochee, plein sinon. --}} @elseif (!empty($commande['decoupe'])) @endif @if (!empty($commande['accessoire'])) @endif @if (!empty($lienPlan)) @if ($ficheGeneree)G@endif @endif {{-- Le modele, puis en gris la platine (quand elle sort de l'ordinaire : NettoyerPlatineSeeder vide les autres, et « SANS PLATINE », code 1, n'en est pas une) et la couleur, sur une seule ligne. Le type et la finition ne sont pas affiches : le type se devine du modele, la finition se lit sur le plan. La couleur est reduite a son code RAL ; son nom complet reste dans l'infobulle. --}} @php $libelleModele = mb_strtoupper($commande['libelle_modele'] ?? '—', 'UTF-8'); $platineModele = !empty($commande['platine']) && (int) $commande['platine'] !== 1 ? \App\Helpers\CommandeHelper::platine($commande['platine']) : null; $couleurModele = $commande['couleur'] ?? null; $detailsModele = array_filter([ $platineModele, !empty($couleurModele) ? \App\Helpers\CommandeHelper::couleurCourte($couleurModele, '') : null, ]); $titreModele = implode(' · ', array_filter([ $libelleModele, $platineModele, !empty($couleurModele) ? \App\Helpers\CommandeHelper::couleur($couleurModele, '') : null, ])); @endphp {{ $libelleModele }} @if ($detailsModele) · {{ implode(' · ', $detailsModele) }} @endif @if (!empty($commande['created_at'])) {{ \Carbon\Carbon::parse($commande['created_at'])->format('d/m/y') }} @endif @php // Un relaquage en cours fait repartir le compteur de // son depart chez le laqueur (CommandeHelper::datePrevue) : // l'api sert la date dans `relaquage_envoi`. // Elle part de la sortie du standby s'il y en a une. $previsionnelle = \App\Helpers\CommandeHelper::datePrevue( \App\Helpers\CommandeHelper::depart($commande['created_at'] ?? null, $commande['lancee_le'] ?? null), $commande['maconnerie'] ?? null, $commande['cintrage'] ?? null, $commande['etat'] ?? null, $commande['date_entrepot'] ?? null, $commande['relaquage_envoi'] ?? null, ); $echeance = \App\Helpers\CommandeHelper::echeance($previsionnelle); @endphp @if ($echeance) {{-- Le compteur seul, sans la date : elle se lisait comme une date de réception. Elle reste au survol. Sablier qui s'écoule tant qu'il reste des jours, vide en retard. --}} {{ $echeance['libelle'] }} @else — @endif @if (!empty($commande['date_entrepot'])) {{ \Carbon\Carbon::parse($commande['date_entrepot'])->format('d/m/y') }} @else — @endif @php $etat = $commande['etat'] ?? null; $etatLibelle = \App\Helpers\CommandeHelper::etat($etat, ''); @endphp @if ($etatLibelle !== '') {{ $etatLibelle }} @endif @can('commandes.view') @endcan @if (!$ligneAllegee) @can('commandes.view') {{-- Fiche de la commande, generee a la demande : icone QR code, pour ne pas la confondre avec le PDF de la colonne Plan, qui est le dessin du bureau d'etude. Nouvel onglet : on ne perd pas sa page de resultats pour un PDF. --}} @endcan {{-- Laquage : le premier depart, ou le retour si le portail est encore chez le laqueur. La meme modale que la barre de selection, pour cette seule ligne. Pas de bouton sur une commande deja laquee et revenue : la renvoyer serait un relaquage, qui se decide sur la fiche, ou l'on voit ses passages avant de dire qui est fautif et pourquoi. --}} @can('commandes.laquage') @if (!$dejaLaquee || $chezLeLaqueur) @endif @endcan @endif @can('commandes.edit') @endcan @can('commandes.delete')
@csrf @method('DELETE')
@endcan
{{ __('Aucune commande ne correspond à ces filtres.') }}
{{-- Barre fixe en bas : on coche en parcourant la liste, un bouton pose ici reste sous la main au lieu qu'il faille remonter le chercher. Elle n'apparait qu'une fois quelque chose de coche. La selection ne survit pas au changement de page, de filtre ou de recherche (js/commandes/selection.js) : ce qu'on applique est toujours ce qu'on a sous les yeux. Sans `commandes.selection`, la barre reste dans la page sans ses boutons : selection.js y tient aussi l'icone de laquage d'une ligne. --}} @php $selectionPermise = auth()->user()?->can('commandes.selection'); @endphp
@if ($selectionPermise) {{-- Nouvel onglet : le pdf s'ouvre a cote, l'index garde sa page, ses filtres et sa selection. --}}
@csrf
{{-- Le meme geste que l'ecran de scan, mais depuis la liste : le scan sert quand on a les etiquettes en main, celui-ci quand aucun papier ne circule. Meme droit que lui : l'usine n'a pas l'edition. --}} @canany(['commandes.edit', 'scans.appliquer'])
@csrf
{{-- REÇU et POSÉ datent une etape (date_entrepot, date_pose) : la date se saisit, jamais d'office. Depuis la liste on rattrape souvent des arrivees d'il y a plusieurs jours, et la date du jour posee sans qu'on la voie s'enregistrait fausse. Vide a chaque ouverture, comme le retour du laquage. --}}
@endcanany @can('commandes.laquage') {{-- Cache par selection.js des que la pile contient une commande passee AU LAQUAGE (ATTENTE CHARG. et au-dela) : elle est revenue du laqueur, la renvoyer serait un relaquage, qui se decide sur sa fiche. --}} @endcan {{-- Le suivi des decoupes de ces commandes : l'ecran des decoupes, filtre sur elles, toutes leurs lignes deja cochees. Un vrai depart (GET) : la selection d'ici se vide, comme a tout changement de page. --}} @can('commande_decoupes.view')
@endcan @endif
{{-- Depart ou retour du laquage, pour une ligne (icone de la ligne) ou pour la selection (barre du bas). Les identifiants sont poses a l'ouverture par selection.js. Un lot n'est jamais refuse en bloc : les commandes qui ne peuvent pas passer sont nommees dans le message, les autres passent. --}} @can('commandes.laquage') @endcan
{{ $commandes->links() }}
@if ($catalogueDecoupes) @include('admin.commandes._decoupes_catalogue', ['decoupes' => $catalogueDecoupes]) @endif {{-- Le suivi et les pieces jointes d'une ligne de decoupe, depuis le detail sous la commande : les fenetres de l'ecran des decoupes. --}} @include('admin.commande_decoupes._modale-suivi', ['catalogue' => $catalogueDecoupes ?? collect()]) @include('admin.commande_decoupes._modale-joindre') {{-- Deroule ou replie le detail des decoupes d'une commande. --}} @endsection