# CLAUDE.md ## Stack - **Backend:** Symfony 8 + API Platform 4 (PHP 8.4) - **Frontend:** Nuxt 4 (Vue 3, Pinia, Tailwind, Zod) in `frontend/` - **Infra:** Docker (PHP-FPM + Nginx), Apache vhost serves API sous `/api` et frontend depuis `frontend/dist` ## Commands ```bash # Docker make start # Démarrer les containers make stop # Arrêter les containers make restart # Redémarrer les containers make shell # Shell dans le container PHP # Install complet make install # composer install + migrations + build frontend # Backend make composer-install # Installer dépendances PHP make migration-migrate # Lancer les migrations make fixtures # Charger les fixtures make cache-clear # Vider le cache Symfony make test # Lancer les tests PHPUnit make test FILES=tests/path/to/TestFile.php # Test spécifique make php-cs-fixer-allow-risky FILES=src/... # Fixer le style # Frontend make build-nuxtJS # npm install + build:dist (dans le container) make dev-nuxt # Serveur dev Nuxt (dans le container) # Ou directement dans frontend/ : cd frontend && npm run dev # Dev server (port 3000) cd frontend && npm run build:dist # Build production # Base de données make db-reset # ⚠️ Supprime et recrée la BDD + migrations + fixtures ``` ## Architecture backend ``` src/ ├── ApiResource/ # Ressources API Platform custom ├── Command/ # Commandes Symfony (dont app:seed) ├── DataFixtures/ # Fixtures Doctrine ├── Dto/ # DTOs (ex: PontBasculeReading) ├── Entity/ # Entités Doctrine (= ressources API Platform) ├── Exception/ # Exceptions custom (PontBasculeException) ├── Kernel.php ├── Service/ # Services métier (PontBasculePayloadDecoder…) └── State/ # State providers/processors API Platform ``` ## Architecture frontend ``` frontend/ ├── components/ │ ├── ui/ # Composants réutilisables, auto-importés avec préfixe Ui (ex: UiLoadingDots) │ ├── reception/ # Composants métier réception │ ├── shipment/ # Composants métier expédition │ ├── workflow/ # Composants partagés réception/expédition (workflow-weight, workflow-waiting-list, workflow-liot-fields) │ └── commun/ # Composants communs (update-weight) ├── composables/ # useApi, useWeighing, usePdfPrinter, useAppVersion, useLiotHandling, useFormDataLoading, useAddressSync, useWorkflowSteps │ └── steps/ # useWeighingStep (logique étape pesée) ├── config/ # reception.config.ts, shipment.config.ts (WorkflowConfig) ├── types/ # workflow.ts (interfaces partagées WorkflowEntity, WorkflowConfig, StepDefinition) ├── services/ # Couche service avec DTOs typés dans services/dto/ │ └── workflow-service.ts # Factory service API (createWorkflowService) ├── stores/ # Pinia stores (reception, shipment, auth) │ └── workflow-store.ts # Factory store (useWorkflowStoreLogic) ├── pages/ # Pages Nuxt (file-based routing) ├── layouts/ # Layout default : max-width 1050px ├── i18n/locales/ # Traductions (défaut: fr) ├── utils/ # Constants, zod-errors helpers └── assets/css/ # Tailwind config, main.css (font Helvetica) ``` ## Conventions backend - Code en anglais ; "pont-bascule" est un terme métier conservé tel quel. - Les opérations API Platform sont définies directement sur les entités Doctrine. - Pas de classes Repository custom : utiliser `EntityManagerInterface` avec les repos par défaut. - `config/reference.php` est auto-généré — ne pas modifier à la main. - Endpoints toujours au pluriel (convention API Platform). - Ne jamais créer de GET qui crée des ressources : utiliser POST + PATCH. - Les noms de `Supplier`, `Customer` et `Carrier` sont automatiquement mis en majuscule via `mb_strtoupper` dans `setName()`. ## Conventions frontend - SSR désactivé. Tailwind avec palette custom `primary` (ex: `bg-primary-500`). - `useApi` (`composables/useApi.ts`) : méthodes `get/post/put/patch/delete` avec content-types par défaut. - Toasts personnalisables via `toastErrorMessage`/`toastSuccessMessage` ou clés i18n `toastErrorKey`/`toastSuccessKey`. - Utilise `useNuxtApp().$i18n` (pas `useI18n`) pour fonctionner hors setup. - Validation formulaires avec Zod ; helpers dans `utils/zod-errors.ts`. - Nav active : `NuxtLink` avec slot `custom`. - PDFs : `usePdfPrinter` (receipt réception, rapport poids cases). ### Validation required & erreurs visuelles - Les champs `required` utilisent l'attribut HTML natif forwardé via `v-bind="attrs"` dans les composants UI. - La bordure rouge n'apparaît qu'après soumission grâce à la classe CSS `submitted` ajoutée sur le `
` au clic sur le bouton Valider (`@click="submitted = true"`). - Règles CSS globales dans `main.css` : `.submitted :invalid` (bordure + texte rouge), `.submitted :has(:invalid) > label` et `.submitted label:has(:invalid)` (labels rouges). - Pour les validations manuelles (checkboxes, radio groups), les messages d'erreur utilisent `invisible` (pas `hidden`) pour garder l'espace réservé et éviter les décalages de layout. - Les dates de l'API sont renvoyées au format `Y-m-d H:i` ; les formulaires utilisent `.slice(0, 10)` pour extraire la date seule (compatible ``). ### Workflow réception/expédition (mutualisé) - Factory service `createWorkflowService` et factory store `useWorkflowStoreLogic` pour éviter la duplication. - Composables partagés : `useLiotHandling` (logique LIOT), `useFormDataLoading` (users, trucks, carriers), `useAddressSync` (sync adresse fournisseur/client). - `useWeighing` : une seule fonction paramétrée pour réception et expédition (remplace `useWeighing` + `useWeighingShipment`). - Configs workflow dans `config/reception.config.ts` et `config/shipment.config.ts` (étapes, labels pesée, filename PDF). - `WorkflowWeight` composant partagé pour les étapes de pesée (remplace `reception-weight.vue` et `shipment-weight.vue`). - `WorkflowWaitingList` composant partagé pour les listes en attente, avec support colonnes dynamiques, slot `actions`, et prop `showActions`. ## Domaine métier clé ### Réception (pesée pont-bascule) - Entité principale `Reception` : `date_reception` (DateTimeImmutable, format lecture `Y-m-d H:i`, écriture `Y-m-d`), `identification_number` (auto `N-BR-####`), `current_step` (défaut 0), `is_valid` (défaut false). - `Weight` (1-N avec Reception, cascade remove + orphanRemoval) : `type` (`gross`/`tare`), `dsd`, `weight`, `weighed_at`. - Endpoint pesée : `/receptions/weigh` → `PontBasculeReading` (dsd, weight, weighedAt). - Endpoint suppression : `DELETE /receptions/{id}` — supprime en cascade weights, pelletBuildings, bovines. - Parsing payload pont-bascule : `Service/PontBasculePayloadDecoder.php`. - Exception : `PontBasculeException` (messages en français, mappée 500). - Store Pinia `reception.ts` = source de vérité pour la réception en cours. - UI multi-étapes dans `pages/reception/[[id]].vue` basée sur `currentStep`. - `PrePersist` : injecte l'heure courante sur `receptionDate` à la création ; `setReceptionDate` préserve l'heure existante au PATCH. ### Expédition - Entité `Shipment` : même pattern que Reception, `shipment_date` (format lecture `Y-m-d H:i`, écriture `Y-m-d`). - Endpoint suppression : `DELETE /shipments/{id}`. - `PrePersist` : injecte l'heure courante sur `shipmentDate` ; `setShipmentDate` préserve l'heure au PATCH. ### LIOT (transport) - Si carrier code = `LIOT` : afficher sélecteurs driver + vehicle, masquer saisie plaque manuelle. - Liste véhicules filtrée par type de camion et transporteur. - Le véhicule sélectionné alimente `license_plate`. - Logique mutualisée dans `composables/useLiotHandling.ts`. ### Bovins & infrastructure - `Bovine` : `nationalNumber` (unique), `receivedWeight`, `arrivalDate`, `buildingCase` (ManyToOne). - `BuildingCase` a `bovines` (OneToMany). - Rapport PDF cases : `GET /building_cases/{id}/weights-report` → template Twig, projection depuis `arrivalDate`, gain journalier fixe `1.3 kg/jour`. ### Scanner boucles auriculaires - Page dédiée `/scan` : scan de codes-barres Code 39/128 (boucles auriculaires bovines) depuis un téléphone Android via Chrome. - Utilise l'API native `BarcodeDetector` (Shape Detection API, Chrome Android 83+) — pas de lib JS, décodage hardware quasi-instantané. - **Non supporté sur iOS** (tous les navigateurs iOS utilisent WebKit, qui n'implémente pas `BarcodeDetector`). - Les 4 premiers caractères du code-barres sont retirés avant enregistrement (`rawValue.slice(4)`). - Composable `useBarcodeScanner` : caméra arrière, anti-doublon 2s, vibration au scan. - Le bovin est créé via `POST /bovines` avec `Content-Type: application/ld+json` (nécessaire pour la résolution d'IRI de `buildingCase`). - Sélection bâtiment → case (filtrées dynamiquement) avant de scanner. ### Données de référence - `ReceptionType`, `MerchandiseType`, `PelletType`, `Building`, `Supplier` (avec `Address` via join table, `createdBy` → User), `Customer` (avec `Address` via join table, `createdBy` → User), `Truck`, `Carrier`, `Driver`, `Vehicle`. - `Address` : champ `label` nullable (déprécié, retiré du front et du `address:write`), expose `fullAddress` via getter. `countryCode` par défaut `FR` côté front. ### Seed & fixtures - Commande `app:seed` : seed infrastructure (statut, building_layout, building_case, building_case_position) puis bovins. - Utilise des flush intermédiaires pour que les queries find fonctionnent sur les records fraîchement créés. - `upsertAddress` cherche par `street|postalCode` (plus par `label`). - Fixtures : `BuildingInfrastructureFixtures` + `BovineFixtures` (via dépendances `AppFixtures`). ## Environnement - API base dev : `http://localhost:8080/api` (via `NUXT_PUBLIC_API_BASE` dans `frontend/.env`) - CORS : Nelmio, configurable via `CORS_ALLOW_ORIGIN` dans `.env` - Locale par défaut : `fr` — traductions dans `frontend/i18n/locales/fr.json` - Docker env : `docker/.env.docker` (défaut) avec override possible via `docker/.env.docker.local`