From f08ab38c2b764169b15b003571768c5b85fc3b8b Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 28 Apr 2026 09:24:11 +0200 Subject: [PATCH] =?UTF-8?q?feat=20:=20relation=20Bovine=20->=20BovineType,?= =?UTF-8?q?=20support=20du=20b=C3=A2timent=20direct=20et=20feed=20=C3=A9te?= =?UTF-8?q?ndu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bovine.breedCode (string) remplacé par bovineType (FK BovineType) - Migration : ajout des races manquantes (Aubrac, Croisé, Blonde d'aquitaine), backfill, drop breed_code - Sync EDNOTIF : auto-création d'un BovineType placeholder pour code inconnu - Bovine.building (FK Building, nullable) en plus de buildingCase - Getter effectiveBuilding (case prime sinon building direct) - Feed XLSX : colonne E optionnelle (code bâtiment), set uniquement si pas de buildingCase - Front : DTO + colonnes en variant inventory/case via composable, race et bâtiment ajustés - Excel export utilise bovineType.label Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 3 + frontend/composables/useBovineColumns.ts | 56 +++++++++++++++---- frontend/pages/infrastructure/case.vue | 20 ++----- frontend/pages/inventory.vue | 8 +-- frontend/services/dto/bovine-data.ts | 10 +++- migrations/Version20260428061801.php | 35 ++++++++++++ migrations/Version20260428065800.php | 50 +++++++++++++++++ src/Command/FeedBovinePricesCommand.php | 31 ++++++++++ src/Command/SeedCommand.php | 3 + src/DataFixtures/ReferenceFixtures.php | 3 + src/Entity/Bovine.php | 53 ++++++++++++++---- src/Entity/BovineType.php | 4 +- .../Bovin/BovineInventoryExportProvider.php | 2 +- .../Bovin/BovineSyncInventoryProcessor.php | 39 ++++++++++++- .../BuildingCaseWeightsReportProvider.php | 2 +- 15 files changed, 270 insertions(+), 49 deletions(-) create mode 100644 migrations/Version20260428061801.php create mode 100644 migrations/Version20260428065800.php diff --git a/README.md b/README.md index 28da722..b594f72 100644 --- a/README.md +++ b/README.md @@ -193,12 +193,14 @@ Pas de ligne d'en-tête, 4 colonnes dans cet ordre : | B | Fournisseur | Texte libre, casse ignorée | `TERRENA` | | C | Poids à l'arrivée (kg) | Entier | `368` | | D | Prix au kilo | Décimal | `5.7` | +| E | Code bâtiment (optionnel) | `B1`, `B2`, `B3`, `ZT` (casse ignorée) | `B2` | ### Comportement - **Numéro national** : le préfixe `FR` (avec ou sans espace) est retiré s'il est présent. Sinon la valeur est utilisée telle quelle. - **Bovin introuvable** en BDD → ligne ignorée, log warning à la fin avec aperçu. - **Fournisseur introuvable** en BDD → le bovin est mis à jour quand même avec `supplier = null`, log warning. +- **Bâtiment** (colonne E) : recherché par `code` (insensible casse). Set uniquement si le bovin n'a pas déjà une `buildingCase` assignée (la case prime sur le bâtiment direct côté affichage). Si code introuvable → log warning, champ non set. - **Cellules `weight` / `price` vides ou non numériques** → champ non modifié. - La commande est **idempotente** : peut être relancée sans effet de bord. @@ -237,3 +239,4 @@ rm /tmp/feed_bovin.xlsx # nettoya - Bovins introuvables (avec aperçu des 10 premiers numéros) - Lignes invalides (numéro national vide) - Fournisseurs introuvables (avec liste et compte par nom) +- Bâtiments introuvables (avec liste des codes inconnus) diff --git a/frontend/composables/useBovineColumns.ts b/frontend/composables/useBovineColumns.ts index bdb228b..a4866cd 100644 --- a/frontend/composables/useBovineColumns.ts +++ b/frontend/composables/useBovineColumns.ts @@ -7,41 +7,77 @@ export interface BovineColumn { width?: string } +export interface UseBovineColumnsOptions { + /** + * 'inventory' (par défaut) : colonnes complètes incluant Bâtiment + Case. + * 'case' : pas de Bâtiment ni Case (déjà dans le titre de la page), + * largeurs élargies pour combler l'espace. + */ + variant?: 'inventory' | 'case' +} + /** * Définition partagée des colonnes des tableaux bovins (inventory + case). - * Deux définitions distinctes admin/user pour pouvoir ajuster les largeurs - * indépendamment selon le contexte. + * Variants distincts pour chaque écran et chaque rôle (admin/user) afin de + * pouvoir ajuster les largeurs indépendamment. */ -export const useBovineColumns = () => { +export const useBovineColumns = (options: UseBovineColumnsOptions = {}) => { const auth = useAuthStore() - const adminColumns: BovineColumn[] = [ + const adminColumnsInventory: BovineColumn[] = [ { key: 'nationalNumber', label: 'N° National', width: '80px' }, { key: 'workNumber', label: 'N° Travail', width: '60px' }, { key: 'sex', label: 'Sexe', width: '70px' }, { key: 'birthDate', label: 'Né le', width: '72px' }, { key: 'age', label: 'Age', width: '110px' }, - { key: 'breedCode', label: 'Race', width: '70px' }, + { key: 'bovineType.label', label: 'Race', width: '90px' }, { key: 'buildingCase.building.label', label: 'Bâtiment', width: '1fr' }, { key: 'buildingCase.caseNumber', label: 'Case', width: '42px' }, { key: 'arrivalDate', label: 'Entrée le', width: '90px' }, { key: 'pricePerKg', label: 'Prix/kg', width: '65px' }, - { key: 'finalPrice', label: 'Prix total', width: '100px' } + { key: 'finalPrice', label: 'Prix total', width: '80px' } ] - const userColumns: BovineColumn[] = [ + const userColumnsInventory: BovineColumn[] = [ { key: 'nationalNumber', label: 'N° National', width: '80px' }, { key: 'workNumber', label: 'N° Travail', width: '60px' }, { key: 'sex', label: 'Sexe', width: '70px' }, { key: 'birthDate', label: 'Né le', width: '72px' }, { key: 'age', label: 'Age', width: '110px' }, - { key: 'breedCode', label: 'Race', width: '70px' }, - { key: 'buildingCase.building.label', label: 'Bâtiment', width: '1fr' }, + { key: 'bovineType.label', label: 'Race', width: '1fr' }, + { key: 'buildingCase.building.label', label: 'Bâtiment', width: '120px' }, { key: 'buildingCase.caseNumber', label: 'Case', width: '42px' }, { key: 'arrivalDate', label: 'Entrée le', width: '90px' } ] - const columns = computed(() => auth.isAdmin ? adminColumns : userColumns) + const adminColumnsCase: BovineColumn[] = [ + { key: 'nationalNumber', label: 'N° National', width: '110px' }, + { key: 'workNumber', label: 'N° Travail', width: '85px' }, + { key: 'sex', label: 'Sexe', width: '90px' }, + { key: 'birthDate', label: 'Né le', width: '100px' }, + { key: 'age', label: 'Age', width: '90px' }, + { key: 'bovineType.label', label: 'Race', width: '1fr' }, + { key: 'arrivalDate', label: 'Entrée le', width: '110px' }, + { key: 'pricePerKg', label: 'Prix/kg', width: '85px' }, + { key: 'finalPrice', label: 'Prix total', width: '105px' } + ] + + const userColumnsCase: BovineColumn[] = [ + { key: 'nationalNumber', label: 'N° National', width: '130px' }, + { key: 'workNumber', label: 'N° Travail', width: '100px' }, + { key: 'sex', label: 'Sexe', width: '110px' }, + { key: 'birthDate', label: 'Né le', width: '140px' }, + { key: 'age', label: 'Age', width: '130px' }, + { key: 'bovineType.label', label: 'Race', width: '1fr' }, + { key: 'arrivalDate', label: 'Entrée le', width: '170px' } + ] + + const columns = computed(() => { + if (options.variant === 'case') { + return auth.isAdmin ? adminColumnsCase : userColumnsCase + } + return auth.isAdmin ? adminColumnsInventory : userColumnsInventory + }) return { columns } } diff --git a/frontend/pages/infrastructure/case.vue b/frontend/pages/infrastructure/case.vue index 9306352..16cb1d1 100644 --- a/frontend/pages/infrastructure/case.vue +++ b/frontend/pages/infrastructure/case.vue @@ -94,19 +94,13 @@ -