Files
Ferme/docs/superpowers/specs/2026-05-21-pont-bascule-healthcheck-design.md
2026-05-21 13:56:22 +02:00

7.6 KiB

Health-check pont-bascule branché sur l'écran de pesée

Date: 2026-05-21 Ticket: FER-19 Statut: Spec validée

Contexte

Aujourd'hui, à l'arrivée sur l'écran de pesée (réception ou expédition), l'UI affiche un texte hardcodé « Pont-bascule connecté » et un loader. C'est du faux : l'état réel du pont-bascule n'est jamais vérifié.

Le pont-bascule (Raspberry sur le réseau, ex. http://100.122.43.54:8000) expose deux routes :

  • POST /send/dsd — déclenche une pesée et renvoie le poids + le DSD. Déjà utilisée via GET /receptions/weigh et GET /shipments/weigh (state providers → PontBasculeService::fetch()).
  • GET /health — health-check, non branchée aujourd'hui.

Réponse type de /health :

{
  "ok": true,
  "mode": "serial",
  "busy": false,
  "hostname": "liot-rasp-ferme-01",
  "timestamp": 1779357080.6277,
  "port": "/dev/ttyUSB0",
  "baudrate": 9600,
  "port_connected": true,
  "port_error": null
}

Un bypass existe déjà côté backend (PONT_BASCULE_BYPASS=true) : il court-circuite l'appel HTTP et renvoie un payload de test. Il est actif partout aujourd'hui (.env.local, .env.prod).

Objectif

  1. Brancher le vrai health-check du pont-bascule à l'arrivée sur l'écran de pesée.
  2. Afficher le véritable état (connecté / non connecté) à la place du texte hardcodé.
  3. Désactiver le bouton « peser » tant que le pont n'est pas valide.
  4. Conserver le bypass : en bypass, l'état est « sain » pour ne pas casser le dev.
  5. Ne pas dupliquer la configuration d'URL (une seule variable d'env de base).

Décisions de design

  • Endpoint générique GET /pont_bascule/health (ressource API Platform autonome). Le health-check est agnostique de l'entité pesée — un seul endpoint partagé, pas un par entité.
  • Check unique au montage de l'écran de pesée (pas de polling). Si le pont est down à l'arrivée, le bouton reste désactivé jusqu'au rechargement de la page. Conforme à la demande.
  • Critère de validité : healthy = ok === true && port_connected === true && port_error === null && busy === false. (busy bloquant car une pesée déjà en cours met busy à true.)
  • Bypass : PONT_BASCULE_BYPASS=true → health renvoie healthy: true sans appel réseau.
  • Pont injoignable = état normal : le backend renvoie 200 { healthy: false } (jamais 500), pour ne pas déclencher de toast d'erreur côté useApi. /weigh garde son comportement 500 actuel.

Configuration env (suppression de la duplication)

  • Remplacer PONT_BASCULE_URL (URL complète .../send/dsd) par PONT_BASCULE_BASE_URL (URL de base sans chemin, ex. http://100.122.43.54:8000).
  • Le service construit lui-même {base}/send/dsd et {base}/health.
  • Fichiers à mettre à jour : .env (valeur vide), .env.local, .env.prod, et config/services.yaml (argument $baseUrl au lieu de $endpoint).
  • PONT_BASCULE_BYPASS inchangé.

Backend

PontBasculeService (src/Service/PontBasculeService.php)

  • Renommer la dépendance $endpoint$baseUrl.
  • fetch() : POST sur {baseUrl}/send/dsd (comportement inchangé sinon).
  • Nouvelle méthode checkHealth(): PontBasculeHealth :
    • si $bypass → renvoie un PontBasculeHealth sain (healthy = true) sans appel réseau ;
    • sinon → GET {baseUrl}/health, parse le JSON, calcule healthy = ok === true && port_connected === true && port_error === null && busy === false ;
    • si l'appel transport échoue ou le JSON est invalide / incomplet → renvoie PontBasculeHealth avec healthy = false (aucune exception levée).

DTO PontBasculeHealth (src/Dto/PontBasculeHealth.php)

Groupe de sérialisation pont_bascule:health:read. Champs :

  • healthy: bool — le seul champ consommé par le front.
  • Champs informatifs (debug / affichage futur) : ok: bool, busy: bool, portConnected: bool, portError: ?string, hostname: ?string.

Ressource API PontBasculeHealthCheck (src/ApiResource/PontBasculeHealthCheck.php)

  • Classe carrier fine (vide) hébergeant l'opération GET /pont_bascule/health, sans état persistant, output = PontBasculeHealth::class (le DTO), provider = PontBasculeHealthProvider. Même montage que /receptions/weigh (host déclare l'opération, output = DTO, provider renvoie le DTO).
  • Nommée PontBasculeHealthCheck pour éviter la collision de nom court avec le DTO Dto\PontBasculeHealth.
  • Route conservée /pont_bascule/health (pont_bascule invariable).

PontBasculeHealthProvider (src/State/PontBasculeHealthProvider.php)

  • Appelle PontBasculeService::checkHealth() et renvoie le DTO PontBasculeHealth.
  • Toujours 200, même pont down (pas de HttpException).

Frontend

Couche service

  • Nouveau composable usePontBascule (composables/usePontBascule.ts) exposant checkHealth()api.get('pont_bascule/health'), renvoie { healthy: boolean, ... }. Le health-check étant agnostique de l'entité, il vit dans son propre composable (et non dans workflow-service.ts qui est un factory par entité) — une seule implémentation, pas de duplication réception/expédition.

useWeighingStep.ts (composables/steps/)

  • Ajout d'un état réactif pontBasculeStatus: 'checking' | 'connected' | 'disconnected' (initialisé à 'checking').
  • Au onMounted : appel du health-check → connected si healthy === true, sinon disconnected.
  • Exposer pontBasculeStatus au composant.

workflow-weight.vue (components/workflow/)

  • Le texte hardcodé ligne 5 (Pont-bascule connecté) devient dynamique selon pontBasculeStatus :
    • checking → « Vérification du pont-bascule… »
    • connected → « Pont-bascule connecté » (vert, style actuel)
    • disconnected → « Pont-bascule non connecté » (rouge)
  • Le bouton « peser » reçoit disabled tant que pontBasculeStatus !== 'connected' (état grisé). Les boutons « Valider la pesée » et « Générer le bon » restent inchangés.

Textes d'état (codés en dur)

Convention du projet : les textes d'UI dans les composants/pages sont codés en dur en français (le « Pont-bascule connecté » actuel l'est déjà). fr.json n'est consommé que par useApi pour les toasts. On reste cohérent : les trois libellés sont écrits directement dans workflow-weight.vue, pas dans fr.json.

  • checking → « Vérification du pont-bascule… » (couleur primary)
  • connected → « Pont-bascule connecté » (vert)
  • disconnected → « Pont-bascule non connecté » (rouge)

Error handling

  • /weigh : comportement inchangé (500 + PontBasculeException).
  • /pont_bascule/health : ne lève jamais d'erreur réseau vers le front. Pont injoignable ou payload invalide = 200 { healthy: false } → pas de toast d'erreur useApi.

Tests

Backend (PHPUnit, MockHttpClient)

Couvrir PontBasculeService::checkHealth() :

  • bypass actif → healthy = true, aucun appel réseau ;
  • payload sain (ok, port_connected, port_error: null, busy: false) → healthy = true ;
  • port_error non null → healthy = false ;
  • port_connected: falsehealthy = false ;
  • busy: truehealthy = false ;
  • transport KO / JSON invalide → healthy = false (pas d'exception).

Frontend

Pas de test auto existant pour ce flux. Validation manuelle :

  • bypass on → bouton « peser » actif, texte « connecté » ;
  • simuler un fail (healthy: false) → bouton grisé, texte « non connecté ».

Hors périmètre

  • Polling / rafraîchissement automatique de l'état (check unique au montage retenu).
  • Bouton « réessayer » manuel.
  • Traductions autres que fr.