docs(fer-19) : spec health-check pont-bascule sur écran de pesée
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# 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` :
|
||||
|
||||
```json
|
||||
{
|
||||
"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 `PontBasculeHealth` (`src/ApiResource/PontBasculeHealth.php`)
|
||||
|
||||
- Opération `GET /pont_bascule/health`, sans état persistant, `output` = DTO,
|
||||
`provider` = `PontBasculeHealthProvider`.
|
||||
- Endpoint au pluriel : `pont_bascule` est déjà invariable, conserver `/pont_bascule/health`.
|
||||
|
||||
### `PontBasculeHealthProvider` (`src/State/`)
|
||||
|
||||
- Appelle `PontBasculeService::checkHealth()` et renvoie le DTO.
|
||||
- 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.
|
||||
|
||||
### i18n (`frontend/i18n/locales/fr.json`)
|
||||
|
||||
Nouvelles clés :
|
||||
|
||||
- `pontBascule.checking` → « Vérification du pont-bascule… »
|
||||
- `pontBascule.connected` → « Pont-bascule connecté »
|
||||
- `pontBascule.disconnected` → « Pont-bascule non connecté »
|
||||
|
||||
## 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: false` → `healthy = false` ;
|
||||
- `busy: true` → `healthy = 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`.
|
||||
Reference in New Issue
Block a user