diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..47b3190 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run:*)" + ] + } +} diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 75b83ee..084e866 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,17 +4,71 @@ - - @@ -798,7 +850,9 @@ - diff --git a/CLAUDE.md b/CLAUDE.md index fbb6785..f1b2667 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,10 +59,18 @@ src/ frontend/ ├── components/ │ ├── ui/ # Composants réutilisables, auto-importés avec préfixe Ui (ex: UiLoadingDots) -│ └── reception/ # Composants métier réception -├── composables/ # useApi, useWeighing, usePdfPrinter, useAppVersion +│ ├── 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) @@ -78,6 +86,7 @@ frontend/ - `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 @@ -89,21 +98,44 @@ frontend/ - 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 `Y-m-d`), `identification_number` (auto `N-BR-####`), `current_step` (défaut 0), `is_valid` (défaut false). -- `Weight` (1-N avec Reception) : `type` (`gross`/`tare`), `dsd`, `weight`, `weighed_at`. +- 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). @@ -111,12 +143,13 @@ frontend/ - Rapport PDF cases : `GET /building_cases/{id}/weights-report` → template Twig, projection depuis `arrivalDate`, gain journalier fixe `1.3 kg/jour`. ### Données de référence -- `ReceptionType`, `MerchandiseType`, `PelletType`, `Building`, `Supplier` (avec `Address` via join table), `Truck`, `Carrier`, `Driver`, `Vehicle`. -- `Address` expose `fullAddress` via getter. +- `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 diff --git a/frontend/assets/css/main.css b/frontend/assets/css/main.css index 1119c1b..8b1addf 100644 --- a/frontend/assets/css/main.css +++ b/frontend/assets/css/main.css @@ -7,3 +7,17 @@ @apply font-sans; } } + +@layer utilities { + .submitted :invalid { + @apply border-red-500 text-red-500; + } + + .submitted :has(:invalid) > label { + @apply text-red-500; + } + + .submitted label:has(:invalid) { + @apply text-red-500; + } +} diff --git a/frontend/components/address.vue b/frontend/components/address.vue index bce2fe9..a6ab4c8 100644 --- a/frontend/components/address.vue +++ b/frontend/components/address.vue @@ -1,5 +1,5 @@