diff --git a/CARNET_DE_BORD.md b/CARNET_DE_BORD.md deleted file mode 100644 index d23778c..0000000 --- a/CARNET_DE_BORD.md +++ /dev/null @@ -1,425 +0,0 @@ -# 📔 Carnet de Bord - Migration Inventory → Symfony - -**Projet** : Migration backend NestJS/Prisma → Symfony/API Platform -**DĂ©but** : 2026-01-10 -**Objectif** : Migrer vers Symfony + JWT + API Platform propre et maintenable - ---- - -## 🔗 Convention de liaison des commits (INV) - -- **Format** : `[INV-YYYYMMDD-XX]` -- **Usage** : mĂȘme code dans les commits du backend **et** du frontend + ajout ici pour retrouver le duo rapidement. - -## đŸ§Ÿ Journal des liaisons INV - -- INV-20260111-01 : ajout du lien submodule `Inventory_frontend` (commit backend : `987aa5c`, commit frontend : `936a73f`) -- INV-20260111-02 : alignement front API Platform + sessions (commit backend : `f7fc1bd`, commit frontend : `e99f053`) - -## 🎯 Contexte - -- **Situation initiale** : - - `Inventory_backend/` : NestJS + Prisma (fonctionnel, ~11k lignes) - - `Inventory_frontend/` : Nuxt 3 (fonctionnel, 105 fichiers) - - Base de donnĂ©es PostgreSQL avec donnĂ©es en production - -- **Objectif** : - - Backend Symfony 8 + API Platform + JWT - - Garder les donnĂ©es existantes (migration Prisma → Doctrine) - - Frontend Nuxt connectĂ© au nouveau backend - - Docker : 2 backends en parallĂšle pendant transition - ---- - -## ✅ Phase 1 : PrĂ©paration (TERMINÉE - 10/01/2026) - -### Ce qui a Ă©tĂ© fait - -#### 1. Docker & Infrastructure ✅ -- **pgAdmin ajoutĂ©** au docker-compose.yml - - Port : 5050 - - Login : admin@admin.com / admin - - Container : `pgadmin-inventory` - - Volume persistant : `pgadmin_data` - - **Serveur PostgreSQL prĂ©-configurĂ©** : - - Fichier `docker/pgadmin/servers.json` montĂ© automatiquement - - Fichier `docker/pgadmin/pgpass` pour authentification sans mot de passe - - Connexion automatique Ă  `db:5432/inventory` au dĂ©marrage - - Nom du serveur : "Inventory PostgreSQL" - -#### 2. Bundles Symfony installĂ©s ✅ -```bash -# Versions installĂ©es -- lexik/jwt-authentication-bundle: v3.2.0 -- vich/uploader-bundle: v2.9.1 -- symfony/uid: 8.0.* -``` - -#### 3. JWT Configuration ✅ -- **ClĂ©s RSA gĂ©nĂ©rĂ©es** : `config/jwt/private.pem` + `public.pem` -- **security.yaml configurĂ©** : - - Firewall `login` : pattern `^/api/login_check` avec `json_login` - - Firewall `api` : pattern `^/api` avec `jwt` authenticator - - Provider : `app_user_provider` (entitĂ© Profile via email) - - Password hasher : bcrypt auto - -#### 4. EntitĂ© Profile créée ✅ -**Fichier** : `src/Entity/Profile.php` - -**CaractĂ©ristiques** : -- ImplĂ©mente `UserInterface` + `PasswordAuthenticatedUserInterface` -- Champs : - - `id` : string (30 chars, CUID-compatible pour Prisma) - - `email` : string unique (username pour JWT) - - `password` : string (hashed) - - `roles` : array JSON (ROLE_USER par dĂ©faut) - - `firstName`, `lastName` : string - - `isActive` : boolean - - `createdAt`, `updatedAt` : DateTimeImmutable -- Repository : `ProfileRepository` avec `PasswordUpgraderInterface` -- API Platform : endpoints CRUD auto-gĂ©nĂ©rĂ©s - -#### 5. Base de DonnĂ©es ✅ -- **Migration créée** : `Version20260110175413` -- **Table** : `profiles` créée avec succĂšs -- **Utilisateur test créé** : - ``` - Email: admin@admin.com - Password: admin123 - Roles: ['ROLE_USER', 'ROLE_ADMIN'] - ``` - -#### 6. API Platform ✅ -- **Endpoint racine** : http://localhost:8081/api/ -- **RĂ©ponse** : - ```json - { - "@context": "/api/contexts/Entrypoint", - "@id": "/api/", - "@type": "Entrypoint", - "profile": "/api/profiles" - } - ``` -- **OpenAPI Docs** : ConfigurĂ©es (Ă  tester) - -#### 7. Configuration Apache ✅ -- **VirtualHost** : `docker/php/config/vhost.conf` -- **DocumentRoot** : `/var/www/html/public` -- **AllowOverride** : All (pour `.htaccess`) -- **Port** : 8081 (Apache) → accessible depuis l'hĂŽte - -#### 8. Routing Symfony ✅ -- **Routes dĂ©finies** : - - `/api/login_check` : Login JWT - - `/api/test` : Test endpoint (TestController) - - `/api/*` : API Platform auto-routes -- **VĂ©rification** : - ```bash - php bin/console debug:router api_test - php bin/console router:match /api/test --method=GET - # ✅ Route found and matches - ``` - -#### 9. .htaccess créé ✅ -**Fichier** : `public/.htaccess` - -**Contenu** : Symfony standard avec mod_rewrite - ---- - -### ⚠ ProblĂšmes identifiĂ©s - -#### 1. Routes inaccessibles via Apache (404) - -**SymptĂŽme** : -```bash -curl http://localhost:8081/api/test -# → 404 Not Found -``` - -**Tests effectuĂ©s** : -- ✅ Route existe : `php bin/console debug:router api_test` -- ✅ Route match : `php bin/console router:match /api/test` -- ✅ Symfony fonctionne : `curl http://localhost:8081/api/` → JSON OK -- ✅ PHP built-in server OK : - ```bash - php -S localhost:9000 - curl http://localhost:9000/api/test - # → {"status":"ok","message":"Test endpoint works!"} - ``` -- ❌ Apache 404 : Depuis l'hĂŽte via port 8081 - -**Diagnostic** : -- Le problĂšme est **Apache-spĂ©cifique** -- Symfony/PHP fonctionnent correctement -- Le `.htaccess` n'est probablement **PAS lu par Apache** -- HypothĂšses : - 1. `AllowOverride All` non pris en compte - 2. `mod_rewrite` mal configurĂ© - 3. Ordre des directives Apache incorrect - 4. ProblĂšme de permissions sur `.htaccess` - -**Actions Ă  faire** : -- [ ] VĂ©rifier permissions `.htaccess` dans container -- [ ] Tester `apache2ctl configtest` -- [ ] Activer logs de rewrite : `LogLevel alert rewrite:trace3` -- [ ] Tester FallbackResource dans vhost au lieu de `.htaccess` - -#### 2. JWT Login non testĂ© - -**Raison** : BloquĂ© par problĂšme #1 (routes inaccessibles) - -**Actions Ă  faire** : -- [ ] RĂ©soudre problĂšme Apache -- [ ] Tester `POST /api/login_check` avec credentials -- [ ] VĂ©rifier gĂ©nĂ©ration du token JWT -- [ ] Tester route protĂ©gĂ©e avec token - ---- - -## 📝 Configuration Actuelle - -### Docker Compose - -```yaml -services: - web: - ports: - - "8081:80" # Symfony API - - "3001:3000" # (prĂ©vu pour Nuxt) - - db: - ports: - - "5433:5432" # PostgreSQL - - pgadmin: - ports: - - "5050:80" # pgAdmin -``` - -### Variables d'Environnement - -**Fichier** : `docker/.env.docker.local` - -```env -# PostgreSQL -POSTGRES_DB=inventory -POSTGRES_USER=root -POSTGRES_PASSWORD=root -POSTGRES_PORT=5433 - ---- - -## ✅ Phase 2 : Migration DB + Frontend (TERMINÉE - 10/01/2026) - -### Ce qui a Ă©tĂ© fait - -#### 1. EntitĂ©s Doctrine alignĂ©es Prisma ✅ -- **Toutes les entitĂ©s manquantes** créées (Machine, ModelType, Composant, Piece, Product, Links, Requirements, CustomField, Document, etc.) -- **IDs en string(36)** pour compatibilitĂ© CUID/UUID -- **Colonnes Prisma en camelCase** conservĂ©es via `name="..."` (ex: `machineId`, `createdAt`, `supplierPrice`) -- **Corrections** : - - `Document.path` passĂ© en `TEXT` - - `CustomField.options` nullable - - `TypeMachineComponentRequirement.required` corrigĂ© - -#### 2. Migration DB inventory-data → inventory ✅ -- **Dump data-only + normalisation** (conversion des identifiants quoted vers lowercase) -- **Mapping table Prisma** : `"ModelType"` → `model_types` -- **Exclusions** : `profiles`, `_prisma_migrations` -- **Import validĂ©** : `Counts match for all tables.` - -Scripts utiles : -```bash -scripts/normalize-dump.py -scripts/validate-migration.php -``` - -#### 3. Frontend basculĂ© sur Inventory_frontend ✅ -- `make dev-nuxt` pointe vers `Inventory_frontend/` -- `README.md` mis Ă  jour -- **Base API** ajustĂ©e : `http://localhost:8081/api` - -Fichiers modifiĂ©s : -``` -makefile -README.md -Inventory_frontend/.env -Inventory_frontend/nuxt.config.ts -Inventory_frontend/app/services/modelTypes.ts -``` - ---- - -# pgAdmin -PGADMIN_EMAIL=admin@admin.com -PGADMIN_PASSWORD=admin -PGADMIN_PORT=5050 - -# Symfony -APP_ENV=dev -APP_SECRET=changeme_super_secret_key_123456789 -JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem -JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem -JWT_PASSPHRASE=your_jwt_passphrase_change_me - -# NestJS (pour futur parallĂšle) -NESTJS_PORT=3000 -SESSION_SECRET=changeme_session_secret -CORS_ORIGIN=http://localhost:3001 -``` - ---- - -## 🚧 Phase 2 : Debugging & Tests (EN COURS) - -### Objectifs -- [x] RĂ©soudre problĂšme Apache `.htaccess` -- [ ] Tester authentification JWT complĂšte -- [ ] CrĂ©er endpoint de test public fonctionnel -- [ ] Documenter la solution Apache - -### Prochaines Ă©tapes -1. **Fix Apache** : Logs de debug + test FallbackResource -2. **Test JWT** : Login + gĂ©nĂ©ration token + route protĂ©gĂ©e -3. **Documentation** : Documenter la config Apache qui fonctionne - ---- - -## 📊 MĂ©triques - -### Temps passĂ© -- **Phase 1** : ~3h (exploration + setup + debugging) -- **ProblĂšme Apache** : ~1h30 (en cours) - -### Fichiers créés/modifiĂ©s - -**Nouveaux fichiers** : -- `src/Entity/Profile.php` -- `src/Repository/ProfileRepository.php` -- `src/Controller/TestController.php` -- `public/.htaccess` -- `config/routes/routing.controllers.yaml` -- `create_test_user.php` (script utilitaire) -- `migrations/Version20260110175413.php` -- `docker/pgadmin/servers.json` (config serveur PostgreSQL) -- `docker/pgadmin/pgpass` (credentials PostgreSQL) -- `CARNET_DE_BORD.md` (ce fichier) - -**Fichiers modifiĂ©s** : -- `docker-compose.yml` (+ pgAdmin) -- `docker/.env.docker.local` (+ variables Symfony/JWT/pgAdmin) -- `docker/php/config/vhost.conf` (DocumentRoot → public/) -- `config/packages/security.yaml` (JWT firewalls) -- `config/routes.yaml` (+ api_login_check) -- `composer.json` (+ lexik JWT, vich uploader) - ---- - -## 🎓 Leçons Apprises - -### 1. Symfony 8 + API Platform -- **Attributs PHP 8** : `use Symfony\Component\Routing\Attribute\Route;` (pas `Annotation`) -- **Routes controllers** : NĂ©cessite `config/routes/routing.controllers.yaml` avec `type: attribute` -- **API Platform** : Auto-gĂ©nĂšre les endpoints CRUD avec `#[ApiResource]` - -### 2. JWT Authentication -- **3 composants** : - 1. Firewall `login` : `json_login` intercepte `/api/login_check` - 2. Firewall `api` : `jwt` vĂ©rifie le token sur `/api/*` - 3. Access control : `PUBLIC_ACCESS` vs `IS_AUTHENTICATED_FULLY` -- **username_path** : Permet de mapper `email` au lieu de `username` -- **Provider** : Doit ĂȘtre dĂ©fini dans le firewall `login` - -### 3. Doctrine Migrations -- **ID Prisma CUID** : Garder en `string(30)` pour compatibilitĂ© -- **Lifecycle callbacks** : `#[ORM\PrePersist]` pour `createdAt`/`updatedAt` -- **UserInterface** : NĂ©cessite `getUserIdentifier()`, `getRoles()`, `eraseCredentials()` - -### 4. Docker & Apache -- **`.htaccess` vs VirtualHost** : Le vhost peut override le `.htaccess` -- **AllowOverride All** : Indispensable pour que `.htaccess` fonctionne -- **FallbackResource** : Alternative au mod_rewrite dans `.htaccess` -- **Debugging** : Tester avec PHP built-in server pour isoler le problĂšme - ---- - -## 📚 Ressources Utiles - -### AccĂšs aux Services - -``` -🌐 pgAdmin: http://localhost:5050 - └─ Login: admin@admin.com / admin - └─ Serveur: "Inventory PostgreSQL" (prĂ©-configurĂ©) - └─ Database: inventory - └─ Note: Le serveur PostgreSQL est automatiquement connectĂ© au dĂ©marrage - -🌐 API Platform: http://localhost:8081/api/ - └─ Docs: http://localhost:8081/api/docs (Ă  venir) - -đŸ—„ïž PostgreSQL: localhost:5433 - └─ User: root / root - └─ Database: inventory -``` - -### Commandes frĂ©quentes - -```bash -# Symfony -make shell # Entrer dans le container -php bin/console cache:clear # Clear cache -make cache-clear-full # Clear cache + purge var/cache -php bin/console debug:router # Lister routes -php bin/console debug:firewall # Lister firewalls -php bin/console doctrine:migrations:migrate # ExĂ©cuter migrations - -# Docker -make start # DĂ©marrer containers -make stop # ArrĂȘter containers -docker logs -f php-inventory-apache # Logs Apache -docker logs -f pgadmin-inventory # Logs pgAdmin -docker exec php-inventory-apache bash # Shell root - -# Tests API -curl http://localhost:8081/api/ # Test API Platform -curl -X POST http://localhost:8081/api/login_check \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@admin.com","password":"admin123"}' -``` - -### Documentation -- [Lexik JWT Bundle](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst) -- [API Platform Security](https://api-platform.com/docs/core/security/) -- [Symfony Security](https://symfony.com/doc/current/security.html) - ---- - -## 🔄 Historique des Changements - -### 2026-01-15 - Session 3 -- ✅ Filtre API Platform `category` sur `ModelType` -- ✅ Normalisation des structures `ModelType` (structure ↔ skeleton) -- ✅ Migration `custom_fields.options` en JSON -- ✅ Ajout commande `make cache-clear-full` -- ✅ Correctifs frontend: headers API Platform, pagination par catĂ©gorie, persistance tri - -### 2026-01-10 - Session 2 (20h30) -- ✅ ProblĂšme Apache rĂ©solu (routes fonctionnelles) -- ✅ Phase 2 complĂšte (JWT 100% opĂ©rationnel) -- ✅ Authentification testĂ©e avec succĂšs -- ✅ RĂ©organisation projet (frontend/ + _archives/) -- ✅ État des lieux dans MIGRATION_PLAN.md -- ✅ 5 commits conventionnels créés -- 📊 Base inventory-data analysĂ©e (673 lignes) - -### 2026-01-10 - Session 1 (19h00) -- ✅ CrĂ©ation projet migration -- ✅ Phase 1 complĂšte (pgAdmin, JWT, Profile, migrations) -- ⚠ ProblĂšme Apache identifiĂ© (routes 404) -- 📝 Carnet de bord créé - ---- - -**DerniĂšre mise Ă  jour** : 2026-01-15 13:45 -**Statut** : Phase 3 EN COURS ⚠ - Migrations et intĂ©gration frontend diff --git a/Inventory_frontend b/Inventory_frontend index 41f5319..c06c852 160000 --- a/Inventory_frontend +++ b/Inventory_frontend @@ -1 +1 @@ -Subproject commit 41f5319b670149a494c6a1102a63c092bc13fb58 +Subproject commit c06c8524939491c21304853a2515a7674525e69f diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md deleted file mode 100644 index b31cddc..0000000 --- a/MIGRATION_PLAN.md +++ /dev/null @@ -1,1419 +0,0 @@ -# Plan de Migration : NestJS/Prisma → Symfony/API Platform - -**Date de crĂ©ation**: 2026-01-10 -**DerniĂšre mise Ă  jour**: 2026-01-11 -**Objectif**: Migrer le backend Inventory (NestJS + Prisma) vers Symfony 8 + API Platform avec JWT auth, tout en conservant les donnĂ©es existantes et en minimisant les disruptions. - ---- - -## 📊 État des Lieux - 2026-01-11 - -### ✅ Phase 1 : PrĂ©paration (TERMINÉE, ajustĂ©e) -- ✅ Bundles Symfony installĂ©s (JWT, VichUploader, Uid) -- ✅ EntitĂ© Profile créée (UserInterface + session ready) -- ✅ pgAdmin configurĂ© et connectĂ© Ă  PostgreSQL -- ✅ Docker fonctionnel (web:8081, db:5433, pgadmin:5050) -- ✅ API Platform configurĂ©e avec prĂ©fixe `/api` -- ⚠ JWT dĂ©sactivĂ© temporairement (session cookie pour compatibilitĂ© front) - -### ✅ Phase 2 : Tests & Validation (TERMINÉE, version session) -- ✅ API Platform retourne JSON-LD correctement -- ✅ Routes session `/api/session/profile` + `/api/session/profiles` opĂ©rationnelles -- ✅ CORS prĂ©vu via Nelmio (mais variable d'env Ă  injecter dans container) - -### 📩 DonnĂ©es Ă  Migrer (Base: `inventory-data`) - -**Total: 673 lignes rĂ©parties sur 22 tables (migrĂ©es)** - -| Table | Lignes | PrioritĂ© | Commentaire | -|-------|--------|----------|-------------| -| `sites` | 3 | ⚠ Critique | Point d'entrĂ©e, relations cascades | -| `machines` | 3 | ⚠ Critique | EntitĂ© centrale | -| `type_machines` | 3 | ⚠ Critique | Configuration machines | -| `ModelType` | 71 | 🔮 Haute | Templates composants/piĂšces/produits | -| `composants` | 23 | 🔮 Haute | - | -| `pieces` | 82 | 🔮 Haute | Plus grande table mĂ©tier | -| `products` | 2 | 🟡 Moyenne | - | -| `constructeurs` | 20 | 🟡 Moyenne | - | -| `custom_fields` | 95 | 🔮 Haute | Configuration champs custom | -| `custom_field_values` | 203 | 🔮 Haute | Plus grande table (valeurs) | -| `documents` | 14 | 🟡 Moyenne | Fichiers uploadĂ©s | -| `machine_component_links` | 25 | 🔮 Haute | Relations machines ↔ composants | -| `machine_piece_links` | 99 | 🔮 Haute | Relations machines ↔ piĂšces | -| `machine_product_links` | 0 | 🟱 Basse | Table vide | -| `type_machine_*_requirements` (×3) | 22 | 🟡 Moyenne | Config requirements types | -| `_*Constructeurs` (×4) | 14 | 🟡 Moyenne | Tables de liaison ManyToMany | -| `profiles` | 2 | 🟱 Basse | Utilisateurs session (migrĂ©s) | - -**ComplexitĂ©s identifiĂ©es (rĂ©solues ou contournĂ©es)** : -- ✅ IDs CUID Prisma → conservĂ©s en `string(36)` cĂŽtĂ© Doctrine (compatibles) -- ✅ Champs JSON complexes importĂ©s (components, criticalParts, machinePieces, specifications) -- ✅ Relations polymorphiques Document + CustomFieldValue migrĂ©es -- ✅ Tables de liaison ManyToMany (`_*Constructeurs`) migrĂ©es -- ✅ Custom fields dynamiques migrĂ©s (95 dĂ©finitions, 203 valeurs) - -### đŸ—ïž Structure Actuelle des Projets - -``` -/home/r-dev/Inventory/ -├── Inventory_backend/ # NestJS + Prisma (11k lignes) -│ ├── src/ # 18 modules NestJS -│ ├── prisma/schema.prisma # SchĂ©ma DB source -│ └── package.json -├── Inventory_frontend/ # Nuxt 3 (front principal) -│ ├── app/ -│ │ ├── composables/ -│ │ └── pages/ -│ └── package.json -├── src/ # ⚡ Symfony backend -│ ├── Entity/ # Toutes entitĂ©s créées -│ ├── Controller/ # Session + custom fields + documents + skeleton -│ └── Repository/ -├── config/ # Configuration Symfony -├── migrations/ # Migrations Doctrine -└── docker-compose.yml # Web + DB + pgAdmin -``` - -### 🎯 Prochaines Étapes (Phase 3 - Mise en production fonctionnelle) - -#### Étape 3.1 : RĂ©organisation du Projet (optionnel) -- [ ] DĂ©placer `Inventory_frontend/` vers `frontend/` (si besoin) -- [ ] Archiver `Inventory_backend/` vers `_archives/backend_nestjs/` -- [ ] Mettre Ă  jour .gitignore - -#### Étape 3.2 : EntitĂ©s & API Platform (TERMINÉE) -- [x] Site, TypeMachine, Machine -- [x] ModelType, Composant, Piece, Product -- [x] Constructeur, Document -- [x] CustomField, CustomFieldValue -- [x] MachineComponentLink, MachinePieceLink, MachineProductLink -- [x] TypeMachine*Requirement (×3) - -#### Étape 3.3 : Migration des DonnĂ©es (TERMINÉE) -- [x] Backup `inventory-data` -- [x] SchĂ©ma Doctrine alignĂ© -- [x] Import `inventory-data` → `inventory` -- [x] Validation des comptes (script `scripts/validate-migration.php`) - -#### Étape 3.4 : Front Nuxt (EN COURS) -- [x] Base URL API -> `http://localhost:8081/api` -- [x] Parsing JSON-LD dans composables -- [x] Normalisation des relations vers IRI -- [x] Endpoints session profile compatibles -- [ ] Corriger CORS (injecter `CORS_ALLOW_ORIGIN` dans container + clear cache) -- [ ] Valider le squelette machine (endpoint `/api/machines/{id}/skeleton`) -- [ ] Valider documents catalogue (composant/piece/produit) - -#### Étape 3.5 : SĂ©curitĂ© (À FAIRE) -- [ ] RĂ©activer JWT (aprĂšs stabilisation) -- [ ] Ajouter refresh token si nĂ©cessaire - ---- - -## Table des MatiĂšres -1. [Vue d'ensemble](#vue-densemble) -2. [Architecture Cible](#architecture-cible) -3. [Mapping Prisma → Doctrine](#mapping-prisma--doctrine) -4. [StratĂ©gie de Migration des DonnĂ©es](#stratĂ©gie-de-migration-des-donnĂ©es) -5. [Configuration Docker](#configuration-docker) -6. [Plan d'ExĂ©cution Phase par Phase](#plan-dexĂ©cution-phase-par-phase) -7. [Gestion des Risques](#gestion-des-risques) -8. [Checklist de Migration](#checklist-de-migration) - ---- - -## 1. Vue d'ensemble - -### Situation Actuelle -- **Backend NestJS**: Port 3000, Prisma ORM, PostgreSQL, Sessions cookie-based -- **Frontend Nuxt 3**: Port 3001, API calls vers localhost:3000 -- **Base de donnĂ©es**: PostgreSQL 16 sur port 5433, 28 modĂšles Prisma, donnĂ©es en production -- **Docker**: Container unique avec PHP 8.4 + Node 24.12, Apache, XDebug - -### Objectif Final -- **Backend Symfony 8**: Port 8081, Doctrine ORM, API Platform 4.2, JWT auth -- **Frontend Nuxt 3**: Port 3001, API calls vers localhost:8081 -- **Base de donnĂ©es**: MĂȘme PostgreSQL, migration Prisma → Doctrine -- **Docker**: 2 backends en parallĂšle pendant la transition (NestJS:3000 + Symfony:8081) - -### Pourquoi cette migration ? -- Code gĂ©nĂ©rĂ© par IA (ChatGPT) Ă  nettoyer et reprendre en main -- Symfony + API Platform = stack PHP standard, mature, bien documentĂ©e -- JWT auth plus adaptĂ© pour API REST moderne -- Meilleure intĂ©gration avec l'Ă©cosystĂšme PHP existant - ---- - -## 2. Architecture Cible - -### Stack Technique - -#### Backend Symfony -``` -Symfony 8.0 -├── API Platform 4.2 # REST API auto-documentĂ©e (OpenAPI) -├── Doctrine ORM 3.6 # ORM pour PostgreSQL -├── Lexik JWT Bundle # JWT authentication -├── Nelmio CORS Bundle # CORS pour frontend -├── VichUploader Bundle # Upload de documents -├── Symfony Validator # Validation des DTOs -└── Doctrine Migrations # Gestion des migrations DB -``` - -#### Structure de Projet -``` -src/ -├── Entity/ # 16 entitĂ©s Doctrine -│ ├── Site.php -│ ├── Machine.php -│ ├── Composant.php -│ ├── Piece.php -│ ├── Product.php -│ ├── TypeMachine.php -│ ├── ModelType.php -│ ├── Constructeur.php -│ ├── Profile.php -│ ├── Document.php -│ ├── CustomField.php -│ ├── CustomFieldValue.php -│ ├── MachineComponentLink.php -│ ├── MachinePieceLink.php -│ ├── MachineProductLink.php -│ └── TypeMachine*Requirement.php (×3) -├── Repository/ # Repositories Doctrine -├── Controller/ # Controllers custom si besoin -├── State/ # State providers/processors API Platform -├── Validator/ # Contraintes de validation custom -└── Service/ # Services mĂ©tier (Ă©quivalents NestJS services) -``` - -#### Configuration JWT - -**Flow d'authentification**: -``` -1. POST /api/login_check - Body: {"username": "...", "password": "..."} - Response: {"token": "eyJ0eXAiOiJKV1QiLCJhbGc..."} - -2. RequĂȘtes authentifiĂ©es - Header: Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc... - -3. Token refresh (optionnel) - POST /api/token/refresh - Body: {"refresh_token": "..."} -``` - -**Configuration sĂ©curitĂ©** (`config/packages/security.yaml`): -```yaml -security: - password_hashers: - App\Entity\Profile: - algorithm: auto - - providers: - app_user_provider: - entity: - class: App\Entity\Profile - property: email # À ajouter Ă  Profile - - firewalls: - login: - pattern: ^/api/login - stateless: true - json_login: - check_path: /api/login_check - success_handler: lexik_jwt_authentication.handler.authentication_success - failure_handler: lexik_jwt_authentication.handler.authentication_failure - - api: - pattern: ^/api - stateless: true - jwt: ~ - - access_control: - - { path: ^/api/login, roles: PUBLIC_ACCESS } - - { path: ^/api/docs, roles: PUBLIC_ACCESS } - - { path: ^/api, roles: IS_AUTHENTICATED_FULLY } -``` - ---- - -## 3. Mapping Prisma → Doctrine - -### 3.1 Types de DonnĂ©es - -| Prisma | Doctrine | Notes | -|--------|----------|-------| -| `String` | `string` | `@ORM\Column(type="string")` | -| `String @id @default(cuid())` | `Ulid` | `@ORM\Id` + `@ORM\GeneratedValue(strategy="CUSTOM")` | -| `Int` | `integer` | `@ORM\Column(type="integer")` | -| `Decimal @db.Decimal(10,2)` | `string` | `@ORM\Column(type="decimal", precision=10, scale=2)` | -| `Boolean` | `bool` | `@ORM\Column(type="boolean")` | -| `DateTime` | `\DateTimeImmutable` | `@ORM\Column(type="datetime_immutable")` | -| `Json` | `array` | `@ORM\Column(type="json")` | -| `String[]` | `array` | `@ORM\Column(type="json")` | -| `String?` | `?string` | `@ORM\Column(nullable=true)` | - -### 3.2 Relations - -| Prisma | Doctrine | Exemple | -|--------|----------|---------| -| `@relation(onDelete: Cascade)` | `@ORM\JoinColumn(onDelete="CASCADE")` | Machine → Site | -| `@relation(onDelete: SetNull)` | `@ORM\JoinColumn(nullable=true, onDelete="SET NULL")` | Composant → Product | -| `Model[]` | `@ORM\OneToMany` + `Collection` | Site → machines | -| `Model` | `@ORM\ManyToOne` | Machine → site | -| Pas de `@relation` | `@ORM\ManyToMany` | Machine ↔ Constructeurs | - -### 3.3 Contraintes et Index - -| Prisma | Doctrine | -|--------|----------| -| `@unique` | `@ORM\Column(unique=true)` | -| `@@unique([field1, field2])` | `@ORM\UniqueConstraint(columns=["field1", "field2"])` | -| `@@map("table_name")` | `@ORM\Table(name="table_name")` | -| `@db.VarChar(120)` | `@ORM\Column(type="string", length=120)` | -| `@default("")` | `#[ORM\Column(options: ['default' => ''])]` | - -### 3.4 Mapping DĂ©taillĂ© des 16 EntitĂ©s - -#### 1. Site -```php -#[ORM\Entity(repositoryClass: SiteRepository::class)] -#[ORM\Table(name: 'sites')] -#[ApiResource( - operations: [ - new Get(), - new GetCollection(), - new Post(), - new Put(), - new Delete() - ] -)] -class Site -{ - #[ORM\Id] - #[ORM\Column(type: 'ulid', unique: true)] - #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\CustomIdGenerator(class: UlidGenerator::class)] - private ?Ulid $id = null; - - #[ORM\Column(type: 'string', length: 255)] - #[Assert\NotBlank] - private string $name; - - #[ORM\Column(type: 'string', length: 255, options: ['default' => ''])] - private string $contactName = ''; - - #[ORM\Column(type: 'string', length: 20, options: ['default' => ''])] - private string $contactPhone = ''; - - #[ORM\Column(type: 'string', length: 500, options: ['default' => ''])] - private string $contactAddress = ''; - - #[ORM\Column(type: 'string', length: 10, options: ['default' => ''])] - private string $contactPostalCode = ''; - - #[ORM\Column(type: 'string', length: 100, options: ['default' => ''])] - private string $contactCity = ''; - - #[ORM\Column(type: 'datetime_immutable')] - private \DateTimeImmutable $createdAt; - - #[ORM\Column(type: 'datetime_immutable')] - private \DateTimeImmutable $updatedAt; - - #[ORM\OneToMany(mappedBy: 'site', targetEntity: Machine::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - private Collection $machines; - - #[ORM\OneToMany(mappedBy: 'site', targetEntity: Document::class, cascade: ['remove'], orphanRemoval: true)] - private Collection $documents; - - #[ORM\PrePersist] - public function setCreatedAtValue(): void - { - $this->createdAt = new \DateTimeImmutable(); - $this->updatedAt = new \DateTimeImmutable(); - } - - #[ORM\PreUpdate] - public function setUpdatedAtValue(): void - { - $this->updatedAt = new \DateTimeImmutable(); - } -} -``` - -#### 2. Machine -```php -#[ORM\Entity] -#[ORM\Table(name: 'machines')] -#[ApiResource] -class Machine -{ - #[ORM\Id] - #[ORM\Column(type: 'ulid')] - private ?Ulid $id = null; - - #[ORM\Column(type: 'string', length: 255, unique: true)] - private string $name; - - #[ORM\Column(type: 'string', length: 255, nullable: true)] - private ?string $reference = null; - - #[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)] - private ?string $prix = null; - - #[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'machines')] - #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] - private Site $site; - - #[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'machines')] - #[ORM\JoinColumn(nullable: true)] - private ?TypeMachine $typeMachine = null; - - #[ORM\ManyToMany(targetEntity: Constructeur::class, inversedBy: 'machines')] - #[ORM\JoinTable(name: 'machine_constructeurs')] - private Collection $constructeurs; - - #[ORM\OneToMany(mappedBy: 'machine', targetEntity: MachineComponentLink::class, cascade: ['persist', 'remove'])] - private Collection $componentLinks; - - #[ORM\OneToMany(mappedBy: 'machine', targetEntity: MachinePieceLink::class, cascade: ['persist', 'remove'])] - private Collection $pieceLinks; - - #[ORM\OneToMany(mappedBy: 'machine', targetEntity: MachineProductLink::class, cascade: ['persist', 'remove'])] - private Collection $productLinks; - - #[ORM\OneToMany(mappedBy: 'machine', targetEntity: Document::class, cascade: ['remove'])] - private Collection $documents; - - #[ORM\OneToMany(mappedBy: 'machine', targetEntity: CustomFieldValue::class, cascade: ['persist', 'remove'])] - private Collection $customFieldValues; - - #[ORM\Column(type: 'datetime_immutable')] - private \DateTimeImmutable $createdAt; - - #[ORM\Column(type: 'datetime_immutable')] - private \DateTimeImmutable $updatedAt; -} -``` - -#### 3. TypeMachine (avec champs JSON) -```php -#[ORM\Entity] -#[ORM\Table(name: 'type_machines')] -#[ApiResource] -class TypeMachine -{ - #[ORM\Id] - #[ORM\Column(type: 'ulid')] - private ?Ulid $id = null; - - #[ORM\Column(type: 'string', length: 255, unique: true)] - private string $name; - - #[ORM\Column(type: 'text', nullable: true)] - private ?string $description = null; - - #[ORM\Column(type: 'string', length: 100, nullable: true)] - private ?string $category = null; - - #[ORM\Column(type: 'string', length: 100, nullable: true)] - private ?string $maintenanceFrequency = null; - - // Champs JSON (structure hiĂ©rarchique, specs techniques) - #[ORM\Column(type: 'json', nullable: true)] - private ?array $components = null; - - #[ORM\Column(type: 'json', nullable: true)] - private ?array $criticalParts = null; - - #[ORM\Column(type: 'json', nullable: true)] - private ?array $machinePieces = null; - - #[ORM\Column(type: 'json', nullable: true)] - private ?array $specifications = null; - - #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: Machine::class)] - private Collection $machines; - - #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: CustomField::class, cascade: ['remove'])] - private Collection $customFields; - - #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: TypeMachineComponentRequirement::class, cascade: ['persist', 'remove'])] - private Collection $componentRequirements; - - #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: TypeMachinePieceRequirement::class, cascade: ['persist', 'remove'])] - private Collection $pieceRequirements; - - #[ORM\OneToMany(mappedBy: 'typeMachine', targetEntity: TypeMachineProductRequirement::class, cascade: ['persist', 'remove'])] - private Collection $productRequirements; -} -``` - -#### 4. ModelType (avec Enum) -```php -enum ModelCategory: string -{ - case COMPONENT = 'COMPONENT'; - case PIECE = 'PIECE'; - case PRODUCT = 'PRODUCT'; -} - -#[ORM\Entity] -#[ORM\Table(name: 'model_types')] -#[ORM\UniqueConstraint(name: 'unique_category_name', columns: ['category', 'name'])] -#[ApiResource] -class ModelType -{ - #[ORM\Id] - #[ORM\Column(type: 'ulid')] - private ?Ulid $id = null; - - #[ORM\Column(type: 'string', length: 120)] - private string $name; - - #[ORM\Column(type: 'string', length: 60, unique: true)] - private string $code; - - #[ORM\Column(type: 'string', enumType: ModelCategory::class)] - private ModelCategory $category; - - #[ORM\Column(type: 'text', nullable: true)] - private ?string $notes = null; - - #[ORM\Column(type: 'text', nullable: true)] - private ?string $description = null; - - #[ORM\Column(type: 'json', nullable: true)] - private ?array $componentSkeleton = null; - - #[ORM\Column(type: 'json', nullable: true)] - private ?array $pieceSkeleton = null; - - #[ORM\Column(type: 'json', nullable: true)] - private ?array $productSkeleton = null; - - #[ORM\OneToMany(mappedBy: 'typeComposant', targetEntity: Composant::class)] - private Collection $composants; - - #[ORM\OneToMany(mappedBy: 'typePiece', targetEntity: Piece::class)] - private Collection $pieces; - - #[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: Product::class)] - private Collection $products; - - // Relations avec les requirements (×3) - // Relations avec les custom fields (×3) -} -``` - -#### 5. Document (Relations Polymorphiques) -```php -#[ORM\Entity] -#[ORM\Table(name: 'documents')] -#[ApiResource( - normalizationContext: ['groups' => ['document:read']], - denormalizationContext: ['groups' => ['document:write']] -)] -class Document -{ - #[ORM\Id] - #[ORM\Column(type: 'ulid')] - private ?Ulid $id = null; - - #[ORM\Column(type: 'string', length: 255)] - private string $name; - - #[ORM\Column(type: 'string', length: 255)] - private string $filename; - - #[ORM\Column(type: 'string', length: 500)] - private string $path; - - #[ORM\Column(type: 'string', length: 100)] - private string $mimeType; - - #[ORM\Column(type: 'integer')] - private int $size; - - // Relations polymorphiques (nullable) - #[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'documents')] - #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] - private ?Machine $machine = null; - - #[ORM\ManyToOne(targetEntity: Composant::class, inversedBy: 'documents')] - #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] - private ?Composant $composant = null; - - #[ORM\ManyToOne(targetEntity: Piece::class, inversedBy: 'documents')] - #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] - private ?Piece $piece = null; - - #[ORM\ManyToOne(targetEntity: Product::class, inversedBy: 'documents')] - #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] - private ?Product $product = null; - - #[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'documents')] - #[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')] - private ?Site $site = null; -} -``` - -#### 6-16. Autres EntitĂ©s -Les autres entitĂ©s suivent le mĂȘme pattern : -- **Composant, Piece, Product** : Similaires Ă  Machine avec relations vers ModelType -- **Constructeur** : ManyToMany avec Machine/Composant/Piece/Product -- **Profile** : EntitĂ© utilisateur (Ă  enrichir avec email/password pour JWT) -- **CustomField, CustomFieldValue** : SystĂšme de champs dynamiques -- **MachineComponentLink, MachinePieceLink, MachineProductLink** : Tables de liaison avec overrides -- **TypeMachine*Requirement (×3)** : DĂ©finition des requirements (minCount, maxCount, label, etc.) - ---- - -## 4. StratĂ©gie de Migration des DonnĂ©es - -### 4.1 ProblĂšme : CUID vs ULID - -**Prisma utilise CUID** (`@default(cuid())`) : -- Format: `clabcd123xyz...` (25 caractĂšres) -- Exemple: `cldu8kzj30000vh8q9k0y8h7i` - -**Doctrine recommande ULID** : -- Format: `01ARZ3NDEKTSV4RRFFQ69G5FAV` (26 caractĂšres) -- Type Symfony : `Symfony\Component\Uid\Ulid` - -**Solutions possibles** : - -#### Option A : Garder les CUID comme chaĂźnes (RECOMMANDÉE) -```php -#[ORM\Id] -#[ORM\Column(type: 'string', length: 30)] -private string $id; -``` - -**Avantages** : -- ✅ Aucune modification des IDs existants -- ✅ Migration transparente, pas de mapping d'IDs -- ✅ Relations FK prĂ©servĂ©es -- ✅ Frontend continue Ă  utiliser les mĂȘmes IDs - -**InconvĂ©nients** : -- ❌ Pas de type Ulid natif Symfony -- ❌ Moins "moderne" - -#### Option B : Migrer CUID → ULID avec mapping -```php -#[ORM\Id] -#[ORM\Column(type: 'ulid')] -private Ulid $id; -``` - -**NĂ©cessite** : -1. CrĂ©er une table de mapping `cuid_to_ulid` -2. GĂ©nĂ©rer de nouveaux ULIDs pour chaque ligne -3. Mettre Ă  jour toutes les FK -4. Adapter le frontend - -**Risque** : ComplexitĂ© Ă©levĂ©e, risque de perte de donnĂ©es. - -### 4.2 Approche RecommandĂ©e : Migration en 3 Étapes - -#### Étape 1 : CrĂ©er le schĂ©ma Doctrine en parallĂšle (SAFE) - -1. GĂ©nĂ©rer les migrations Doctrine sans exĂ©cution : - ```bash - php bin/console doctrine:migrations:diff - ``` - -2. **NE PAS exĂ©cuter** la migration immĂ©diatement - -3. Analyser le SQL gĂ©nĂ©rĂ© pour dĂ©tecter les diffĂ©rences : - - Noms de colonnes (camelCase → snake_case) - - Types de donnĂ©es (Decimal en Prisma → string en Doctrine) - - Contraintes (vĂ©rifier les FK, indexes) - -#### Étape 2 : Script de Validation (Dry-run) - -CrĂ©er un script PHP qui : -1. Lit toutes les tables Prisma actuelles -2. Compare avec le schĂ©ma Doctrine gĂ©nĂ©rĂ© -3. GĂ©nĂšre un rapport de diffĂ©rences -4. Valide que toutes les donnĂ©es peuvent ĂȘtre mappĂ©es - -```php -// scripts/validate-migration.php -use Doctrine\ORM\EntityManagerInterface; - -class MigrationValidator -{ - public function validate(): array - { - $issues = []; - - // VĂ©rifier chaque table - foreach ($this->getTables() as $table) { - // Comparer schĂ©ma Prisma vs Doctrine - $schemaIssues = $this->compareSchemas($table); - - // VĂ©rifier les donnĂ©es peuvent ĂȘtre migrĂ©es - $dataIssues = $this->validateData($table); - - $issues = array_merge($issues, $schemaIssues, $dataIssues); - } - - return $issues; - } -} -``` - -#### Étape 3 : Migration rĂ©elle (avec Backup) - -```bash -# 1. BACKUP de la DB -pg_dump -U postgres -h localhost -p 5433 inventory_db > backup_$(date +%Y%m%d_%H%M%S).sql - -# 2. ExĂ©cuter les migrations Doctrine -php bin/console doctrine:migrations:migrate --no-interaction - -# 3. VĂ©rifier l'intĂ©gritĂ© -php bin/console doctrine:schema:validate - -# 4. Si erreur : ROLLBACK -psql -U postgres -h localhost -p 5433 inventory_db < backup_YYYYMMDD_HHMMSS.sql -``` - -### 4.3 Mapping des Noms de Colonnes - -Prisma → Doctrine naming strategy : - -| Prisma | Doctrine | Action | -|--------|----------|--------| -| `siteId` | `site_id` | Annotation `#[ORM\JoinColumn(name="siteId")]` ou laisser Doctrine snake_case | -| `createdAt` | `created_at` | Idem | -| `contactName` | `contact_name` | Idem | - -**Recommandation** : Utiliser `#[ORM\Column(name="xxx")]` pour garder les noms Prisma existants et Ă©viter les ALTER TABLE. - -### 4.4 Checklist de Migration DB - -- [ ] CrĂ©er backup complet de la DB -- [ ] GĂ©nĂ©rer migration Doctrine (`doctrine:migrations:diff`) -- [ ] Analyser SQL gĂ©nĂ©rĂ© manuellement -- [ ] Tester migration sur copie locale de la DB -- [ ] Valider que les donnĂ©es sont intactes aprĂšs migration -- [ ] CrĂ©er script de rollback -- [ ] Documenter les diffĂ©rences schĂ©ma Prisma vs Doctrine -- [ ] Tester toutes les requĂȘtes via Doctrine -- [ ] VĂ©rifier les performances (indexes, FK) - ---- - -## 5. Configuration Docker - -### 5.1 Setup : 2 Backends en ParallĂšle - -**Objectif** : Faire tourner NestJS (port 3000) ET Symfony (port 8081) simultanĂ©ment pendant la transition. - -#### Modification `docker-compose.yml` - -```yaml -services: - web: - container_name: php-${DOCKER_APP_NAME}-apache - build: - context: ./docker/php - dockerfile: Dockerfile - args: - DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION} - DOCKER_NODE_VERSION: ${DOCKER_NODE_VERSION} - CURRENT_UID: ${CURRENT_UID} - CURRENT_GID: ${CURRENT_GID} - environment: - PHP_IDE_CONFIG: serverName=${DOCKER_APP_NAME}-docker - XDEBUG_CLIENT_HOST: ${XDEBUG_CLIENT_HOST:-host.docker.internal} - XDEBUG_CONFIG: client_host=${XDEBUG_CLIENT_HOST:-host.docker.internal} client_port=9003 - DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=16&charset=utf8" - # Variables pour NestJS - NESTJS_DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public" - NESTJS_PORT: 3000 - NESTJS_CORS_ORIGIN: "http://localhost:3001" - # Variables pour Symfony - APP_ENV: dev - APP_SECRET: ${APP_SECRET} - JWT_SECRET_KEY: ${JWT_SECRET_KEY} - JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY} - JWT_PASSPHRASE: ${JWT_PASSPHRASE} - volumes: - - ./:/var/www/html - - ~/.cache:/var/www/.cache - - ~/.config:/var/www/.config - - ~/.composer:/var/www/.composer - - ./docker/php/config/php.ini:/usr/local/etc/php/php.ini - - ./docker/php/config/vhost.conf:/etc/apache2/sites-available/000-default.conf - - ./docker/php/config/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - - ./LOG:/var/www/html/LOG - - ./LOG/logs_apache:/var/log/apache2/ - extra_hosts: - - "host.docker.internal:host-gateway" - depends_on: - - db - ports: - - "8081:80" # Apache/Symfony - - "3000:3000" # NestJS backend (nouveau mapping) - - "3001:3001" # Nuxt frontend - restart: unless-stopped - - db: - image: postgres:16-alpine - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - pg_data:/var/lib/postgresql/data - ports: - - "${POSTGRES_PORT:-5433}:5432" - restart: unless-stopped - -volumes: - pg_data: -``` - -#### Configuration Apache VirtualHost - -```apache -# /docker/php/config/vhost.conf - - - ServerName localhost - DocumentRoot /var/www/html/public - - - AllowOverride All - Require all granted - FallbackResource /index.php - - - # Logs - ErrorLog /var/log/apache2/error.log - CustomLog /var/log/apache2/access.log combined - -``` - -#### Script de DĂ©marrage des 2 Backends - -CrĂ©er `/docker/start-services.sh` : - -```bash -#!/bin/bash - -# DĂ©marrer Apache (Symfony) -apache2-foreground & - -# Installer dĂ©pendances NestJS si besoin -cd /var/www/html/Inventory_backend -if [ ! -d "node_modules" ]; then - npm install -fi - -# DĂ©marrer NestJS -npm run start:dev & - -# Installer dĂ©pendances Nuxt si besoin -cd /var/www/html/Inventory_frontend -if [ ! -d "node_modules" ]; then - npm install -fi - -# DĂ©marrer Nuxt -npm run dev & - -# Garder le container actif -wait -n -``` - -Modifier `Dockerfile` pour utiliser ce script : - -```dockerfile -# À la fin du Dockerfile -COPY start-services.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/start-services.sh - -CMD ["start-services.sh"] -``` - -### 5.2 Variables d'Environnement - -CrĂ©er `/docker/.env.docker.local` (dĂ©jĂ  existant, Ă  complĂ©ter) : - -```env -# Existant -DOCKER_APP_NAME=inventory -DOCKER_PHP_VERSION=8.4.6 -DOCKER_NODE_VERSION=24.12.0 -CURRENT_UID=1000 -CURRENT_GID=1000 -POSTGRES_DB=inventory_db -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_PORT=5433 - -# Nouveau : Symfony -APP_ENV=dev -APP_SECRET=changeme_super_secret_key_123456789 -JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem -JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem -JWT_PASSPHRASE=your_jwt_passphrase - -# Nouveau : NestJS -NESTJS_PORT=3000 -SESSION_SECRET=changeme_session_secret -CORS_ORIGIN=http://localhost:3001 -``` - -### 5.3 GĂ©nĂ©ration des ClĂ©s JWT - -Ajouter au script de dĂ©marrage : - -```bash -# GĂ©nĂ©rer clĂ©s JWT si inexistantes -mkdir -p /var/www/html/config/jwt -if [ ! -f /var/www/html/config/jwt/private.pem ]; then - openssl genpkey -out /var/www/html/config/jwt/private.pem -aes256 -algorithm rsa -pkeyopt rsa_keybits:4096 -pass pass:${JWT_PASSPHRASE} - openssl pkey -in /var/www/html/config/jwt/private.pem -passin pass:${JWT_PASSPHRASE} -out /var/www/html/config/jwt/public.pem -pubout - chmod 600 /var/www/html/config/jwt/*.pem -fi -``` - ---- - -## 6. Plan d'ExĂ©cution Phase par Phase - -### Phase 1 : PrĂ©paration (Semaine 1) - -#### TĂąches -1. **Installer bundles Symfony** : - ```bash - composer require lexik/jwt-authentication-bundle - composer require vich/uploader-bundle - composer require symfony/uid - ``` - -2. **Configurer JWT** : - - GĂ©nĂ©rer clĂ©s RSA - - Configurer `lexik_jwt_authentication.yaml` - - Configurer `security.yaml` - -3. **CrĂ©er entitĂ© Profile enrichie** : - ```php - class Profile implements UserInterface - { - // Ajouter email, password, roles - } - ``` - -4. **Analyser schĂ©ma Prisma** : - - Documenter toutes les relations - - Identifier les champs JSON complexes - - Lister les contraintes de validation - -5. **Setup Docker** : - - Modifier `docker-compose.yml` - - CrĂ©er script de dĂ©marrage multi-services - - Tester les 2 backends en parallĂšle - -#### Livrables -- [ ] Symfony configurĂ© avec tous les bundles -- [ ] JWT fonctionnel avec endpoint `/api/login_check` -- [ ] Docker lance NestJS (3000) + Symfony (8081) + Nuxt (3001) -- [ ] Documentation schĂ©ma Prisma complĂšte - ---- - -### Phase 2 : CrĂ©ation des EntitĂ©s (Semaine 2-3) - -#### TĂąches -1. **CrĂ©er toutes les entitĂ©s Doctrine** (16 entitĂ©s) : - - Utiliser les attributs PHP 8 (`#[ORM\Entity]`) - - Mapper exactement le schĂ©ma Prisma - - Ajouter annotations API Platform - - ImplĂ©menter les lifecycle callbacks (`#[ORM\PrePersist]`, etc.) - -2. **GĂ©nĂ©rer repositories** : - ```bash - php bin/console make:entity --regenerate - ``` - -3. **CrĂ©er migration Doctrine** : - ```bash - php bin/console doctrine:migrations:diff - ``` - -4. **Analyser SQL gĂ©nĂ©rĂ©** : - - Comparer avec schĂ©ma Prisma actuel - - Identifier les diffĂ©rences - - PrĂ©parer script d'ajustement si besoin - -5. **Tester sur DB vide** : - ```bash - # CrĂ©er DB test - createdb inventory_test - # Appliquer migrations - php bin/console doctrine:migrations:migrate --env=test - # Valider schĂ©ma - php bin/console doctrine:schema:validate - ``` - -#### Livrables -- [ ] 16 entitĂ©s Doctrine complĂštes avec relations -- [ ] Migration Doctrine gĂ©nĂ©rĂ©e et validĂ©e -- [ ] Tests sur DB vide rĂ©ussis -- [ ] Documentation des diffĂ©rences Prisma/Doctrine - ---- - -### Phase 3 : Migration de la Base de DonnĂ©es (Semaine 4) - -#### TĂąches -1. **Backup complet** : - ```bash - pg_dump -U postgres -h localhost -p 5433 inventory_db > backup_avant_migration.sql - ``` - -2. **CrĂ©er script de validation** : - ```php - // scripts/validate-schema.php - // Comparer schĂ©ma Prisma vs Doctrine - ``` - -3. **ExĂ©cuter migration Doctrine** : - ```bash - php bin/console doctrine:migrations:migrate --no-interaction - ``` - -4. **Valider donnĂ©es migrĂ©es** : - - Compter les lignes par table (Prisma count vs Doctrine count) - - VĂ©rifier intĂ©gritĂ© rĂ©fĂ©rentielle - - Tester quelques requĂȘtes - -5. **Tests de rĂ©gression** : - - Lire toutes les entitĂ©s via Doctrine - - VĂ©rifier les relations - - Tester les champs JSON - -#### Livrables -- [ ] Base de donnĂ©es migrĂ©e avec succĂšs -- [ ] Backup de sĂ©curitĂ© créé -- [ ] Script de validation OK (0 diffĂ©rences) -- [ ] Tests de lecture/Ă©criture via Doctrine OK - ---- - -### Phase 4 : Logique MĂ©tier (Semaine 5-6) - -#### TĂąches -1. **Migrer services NestJS → Symfony** : - -**Mapping des services** : - -| NestJS Service | Symfony Service | ResponsabilitĂ©s | -|----------------|-----------------|-----------------| -| `SitesService` | `SiteManager` | CRUD sites | -| `MachinesService` | `MachineManager` | CRUD machines, reconfiguration | -| `ComposantsService` | `ComposantManager` | CRUD composants | -| `PiecesService` | `PieceManager` | CRUD pieces | -| `ProductsService` | `ProductManager` | CRUD products | -| `TypesService` | `TypeMachineManager` | Gestion types machines | -| `ModelTypeService` | `ModelTypeManager` | Gestion model types | -| `DocumentsService` | `DocumentManager` | Upload/download documents | -| `CustomFieldsService` | `CustomFieldManager` | Gestion custom fields | -| `ConstructeursService` | `ConstructeurManager` | Gestion constructeurs | -| `ProfilesService` | `ProfileManager` | Gestion profiles | -| `SessionService` | SupprimĂ© (JWT) | - | - -2. **ImplĂ©menter upload de documents** : - - Configurer VichUploaderBundle - - CrĂ©er endpoint `POST /api/documents` - - CrĂ©er endpoint `GET /api/documents/{id}/download` - -3. **ImplĂ©menter custom fields** : - - Service de gestion des dĂ©finitions - - Service de gestion des valeurs - - Validation dynamique - -4. **CrĂ©er State Processors API Platform** : - - Pour logique custom (ex: reconfiguration machine) - - Pour validation complexe - -5. **Tests unitaires** : - - Tester chaque service - - Mocker les repositories - - VĂ©rifier logique mĂ©tier - -#### Livrables -- [ ] Tous les services mĂ©tier migrĂ©s -- [ ] Upload/download documents fonctionnel -- [ ] Custom fields opĂ©rationnels -- [ ] Tests unitaires couvrant 80%+ du code - ---- - -### Phase 5 : API Platform (Semaine 7) - -#### TĂąches -1. **Configurer les endpoints API Platform** : - - VĂ©rifier routes auto-gĂ©nĂ©rĂ©es - - Customiser opĂ©rations si besoin - - Ajouter filtres de recherche - - Configurer pagination - -2. **SĂ©rialisation** : - - CrĂ©er groupes de normalisation - - GĂ©rer relations circulaires - - Optimiser avec `@Groups` - -3. **Validation** : - - Ajouter contraintes de validation - - CrĂ©er validators custom - - GĂ©rer messages d'erreur - -4. **Documentation API** : - - GĂ©nĂ©rer OpenAPI spec - - Tester via Swagger UI - - Documenter endpoints custom - -5. **Tests d'intĂ©gration** : - - Tester tous les endpoints CRUD - - VĂ©rifier codes de rĂ©ponse HTTP - - Valider structure JSON - -#### Livrables -- [ ] Tous les endpoints API fonctionnels -- [ ] Documentation OpenAPI complĂšte -- [ ] Tests d'intĂ©gration OK -- [ ] Swagger UI accessible sur `/api/docs` - ---- - -### Phase 6 : Adaptation Frontend (Semaine 8) - -#### TĂąches -1. **Modifier `useApi.js`** : - ```js - // Ancien - const baseURL = 'http://localhost:3000'; - - // Nouveau - const baseURL = import.meta.env.VITE_API_URL || 'http://localhost:8081/api'; - ``` - -2. **ImplĂ©menter gestion JWT** : - ```js - // composables/useAuth.js - export const useAuth = () => { - const token = ref(localStorage.getItem('jwt_token')); - - const login = async (username, password) => { - const response = await fetch('/api/login_check', { - method: 'POST', - body: JSON.stringify({ username, password }) - }); - const { token } = await response.json(); - localStorage.setItem('jwt_token', token); - token.value = token; - }; - - const logout = () => { - localStorage.removeItem('jwt_token'); - token.value = null; - }; - - return { token, login, logout }; - }; - ``` - -3. **Adapter les composables** : - - `useSites.js` → vĂ©rifier format rĂ©ponse - - `useMachines.js` → idem - - Tous les autres composables - -4. **Gestion des changements d'API** : - - Ajuster parsing des rĂ©ponses JSON:API vs JSON simple - - GĂ©rer les nouveaux formats d'erreur - - Adapter les IDs si changement - -5. **Tests fonctionnels** : - - Tester chaque page du frontend - - VĂ©rifier tous les flows utilisateur - - Tester upload/download documents - -#### Livrables -- [ ] Frontend connectĂ© au backend Symfony -- [ ] Authentification JWT fonctionnelle -- [ ] Tous les composables adaptĂ©s -- [ ] Tests fonctionnels rĂ©ussis - ---- - -### Phase 7 : Tests & DĂ©commissionnement NestJS (Semaine 9) - -#### TĂąches -1. **Tests de charge** : - - Comparer performances NestJS vs Symfony - - VĂ©rifier temps de rĂ©ponse - - Tester avec donnĂ©es rĂ©elles - -2. **Tests de rĂ©gression complĂšte** : - - Rejouer tous les scĂ©narios utilisateur - - Comparer rĂ©sultats NestJS vs Symfony - - Fixer les bugs - -3. **Documentation** : - - Documenter nouvelle architecture - - CrĂ©er guide migration pour Ă©quipe - - Documenter diffĂ©rences API - -4. **DĂ©commissionner NestJS** : - - ArrĂȘter le service NestJS - - Supprimer du docker-compose - - Archiver le code (ne pas supprimer) - -5. **Cleanup** : - - Supprimer dĂ©pendances NestJS inutilisĂ©es - - Nettoyer fichiers de config - - Optimiser Docker - -#### Livrables -- [ ] Tests de charge validĂ©s -- [ ] 0 bugs de rĂ©gression -- [ ] Documentation complĂšte -- [ ] NestJS arrĂȘtĂ©, Symfony en production -- [ ] Code NestJS archivĂ© - ---- - -## 7. Gestion des Risques - -### 7.1 Risques Techniques - -| Risque | ProbabilitĂ© | Impact | Mitigation | -|--------|-------------|--------|------------| -| **Perte de donnĂ©es pendant migration DB** | Faible | Critique | Backup complet avant migration, script de rollback, validation post-migration | -| **IncompatibilitĂ© schĂ©ma Prisma/Doctrine** | Moyenne | ÉlevĂ© | Validation schema dry-run, tests sur DB copie, garder noms de colonnes Prisma | -| **Performances Symfony < NestJS** | Faible | Moyen | Benchmarks, optimisation requĂȘtes Doctrine, cache API Platform | -| **Bugs lors migration logique mĂ©tier** | Moyenne | ÉlevĂ© | Tests unitaires + intĂ©gration, comparison NestJS/Symfony side-by-side | -| **JWT incompatible avec frontend** | Faible | Moyen | Tests d'auth dĂšs Phase 1, documentation claire | -| **Upload documents ne fonctionne pas** | Faible | Moyen | Tester VichUploader tĂŽt, fallback manuel si besoin | -| **Relations Doctrine mal configurĂ©es** | Moyenne | ÉlevĂ© | Tests avec donnĂ©es rĂ©elles, validation via `doctrine:schema:validate` | - -### 7.2 Risques Projet - -| Risque | ProbabilitĂ© | Impact | Mitigation | -|--------|-------------|--------|------------| -| **Timeline trop optimiste** | ÉlevĂ©e | Moyen | Buffer de 2 semaines, dĂ©coupage en phases indĂ©pendantes | -| **Manque de tests** | Moyenne | ÉlevĂ© | Écrire tests dĂšs Phase 2, coverage > 80% | -| **Documentation insuffisante** | Moyenne | Moyen | Documenter au fur et Ă  mesure, pas Ă  la fin | -| **RĂ©version nĂ©cessaire** | Faible | Critique | Garder NestJS actif en parallĂšle, backup DB, script de rollback | - -### 7.3 Plan de Rollback - -Si problĂšme critique : - -1. **ArrĂȘter Symfony** : - ```bash - docker-compose stop web - ``` - -2. **Restaurer DB depuis backup** : - ```bash - psql -U postgres -h localhost -p 5433 inventory_db < backup_avant_migration.sql - ``` - -3. **RedĂ©marrer NestJS** : - ```bash - cd Inventory_backend && npm run start:dev - ``` - -4. **Reconfigurer frontend** : - ```js - // Revert API URL - const baseURL = 'http://localhost:3000'; - ``` - -5. **Analyser l'erreur** : - - Lire logs Symfony (`var/log/dev.log`) - - Lire logs PostgreSQL - - Identifier root cause - -6. **Fix et retry** : - - Corriger le problĂšme - - Retester en local - - Relancer migration - ---- - -## 8. Checklist de Migration - -### Avant de Commencer -- [ ] Lire entiĂšrement ce document -- [ ] Comprendre schĂ©ma Prisma actuel -- [ ] Valider que Docker fonctionne -- [ ] CrĂ©er backup DB initial -- [ ] Setup environnement de dev local - -### Phase 1 : PrĂ©paration -- [ ] Installer bundles Symfony (JWT, VichUploader, Uid) -- [ ] Configurer JWT avec clĂ©s RSA -- [ ] Enrichir entitĂ© Profile (email, password, roles) -- [ ] Modifier docker-compose.yml pour 2 backends -- [ ] Tester dĂ©marrage des 2 backends en parallĂšle -- [ ] Documenter schĂ©ma Prisma - -### Phase 2 : EntitĂ©s -- [ ] CrĂ©er 16 entitĂ©s Doctrine -- [ ] Ajouter annotations API Platform -- [ ] GĂ©nĂ©rer migration Doctrine -- [ ] Analyser SQL gĂ©nĂ©rĂ© -- [ ] Tester migration sur DB vide -- [ ] Valider schĂ©ma Doctrine - -### Phase 3 : Migration DB -- [ ] CrĂ©er backup DB production -- [ ] CrĂ©er script de validation -- [ ] ExĂ©cuter migration Doctrine -- [ ] Appliquer migrations post-migration (ajouts colonnes, types JSON) -- [ ] Valider count de lignes par table -- [ ] Tester requĂȘtes Doctrine -- [ ] VĂ©rifier intĂ©gritĂ© rĂ©fĂ©rentielle - -### Phase 4 : Services -- [ ] Migrer les 11 services mĂ©tier -- [ ] ImplĂ©menter upload documents -- [ ] ImplĂ©menter custom fields -- [ ] Écrire tests unitaires -- [ ] Valider logique mĂ©tier - -### Phase 5 : API -- [ ] Configurer endpoints API Platform -- [ ] ImplĂ©menter sĂ©rialisation -- [ ] Ajouter validation -- [ ] GĂ©nĂ©rer doc OpenAPI -- [ ] Tests d'intĂ©gration - -### Phase 6 : Frontend -- [ ] Modifier useApi.js (nouveau baseURL) -- [ ] ImplĂ©menter gestion JWT -- [ ] Adapter tous les composables -- [ ] Tester toutes les pages -- [ ] Valider flows utilisateur - -### Phase 7 : Finalisation -- [ ] Tests de charge -- [ ] Tests de rĂ©gression complĂšte -- [ ] Documenter nouvelle architecture -- [ ] ArrĂȘter NestJS -- [ ] Archiver code NestJS -- [ ] Cleanup Docker - ---- - -## Annexes - -### A. Commandes Utiles - -```bash -# Symfony -php bin/console doctrine:schema:validate -php bin/console doctrine:migrations:diff -php bin/console doctrine:migrations:migrate -php bin/console doctrine:query:sql "SELECT COUNT(*) FROM sites" -php bin/console debug:router -php bin/console cache:clear - -# Docker -docker-compose up -d --build -docker-compose logs -f web -docker-compose exec web bash -docker-compose down - -# Base de donnĂ©es -pg_dump -U postgres -h localhost -p 5433 inventory_db > backup.sql -psql -U postgres -h localhost -p 5433 inventory_db < backup.sql -psql -U postgres -h localhost -p 5433 inventory_db -c "SELECT COUNT(*) FROM sites" - -# Tests -php bin/phpunit -php vendor/bin/phpstan analyse src -``` - -### B. Structure Finale du Projet - -``` -/home/r-dev/Inventory/ -├── Inventory_backend/ # À archiver aprĂšs migration -├── Inventory_frontend/ # Frontend Nuxt (Ă  garder) -│ ├── app/ -│ │ ├── composables/ -│ │ │ ├── useApi.js # Modifier baseURL -│ │ │ ├── useAuth.js # Nouveau : JWT -│ │ │ └── ... -│ │ └── pages/ -├── src/ # Backend Symfony (nouveau) -│ ├── Entity/ -│ │ ├── Site.php -│ │ ├── Machine.php -│ │ └── ... (16 entitĂ©s) -│ ├── Repository/ -│ ├── Service/ -│ │ ├── SiteManager.php -│ │ ├── MachineManager.php -│ │ └── ... (11 services) -│ ├── Controller/ # Si besoin de custom controllers -│ └── State/ # State providers API Platform -├── config/ -│ ├── packages/ -│ │ ├── doctrine.yaml -│ │ ├── api_platform.yaml -│ │ ├── lexik_jwt_authentication.yaml -│ │ ├── security.yaml -│ │ └── nelmio_cors.yaml -│ └── jwt/ -│ ├── private.pem -│ └── public.pem -├── migrations/ # Migrations Doctrine -├── docker/ -│ ├── php/ -│ │ ├── Dockerfile -│ │ └── config/ -│ ├── start-services.sh # Nouveau : lance Symfony + Nuxt -│ ├── .env.docker -│ └── .env.docker.local -├── docker-compose.yml # ModifiĂ© : 2 backends -├── composer.json # Symfony dependencies -└── MIGRATION_PLAN.md # Ce document -``` - -### C. Ressources - -- [API Platform Docs](https://api-platform.com/docs/) -- [Doctrine ORM Docs](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/) -- [Lexik JWT Bundle](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst) -- [Symfony Best Practices](https://symfony.com/doc/current/best_practices.html) -- [Prisma to Doctrine Migration Guide](https://symfony.com/doc/current/doctrine.html#creating-an-entity-class) - ---- - -## Conclusion - -Ce plan de migration dĂ©taille la stratĂ©gie pour passer de NestJS/Prisma Ă  Symfony/API Platform de maniĂšre progressive et sĂ©curisĂ©e. Les points clĂ©s : - -1. **Migration incrĂ©mentale** : Garder NestJS actif en parallĂšle -2. **SĂ©curitĂ© des donnĂ©es** : Backups, validation, rollback -3. **Modernisation** : JWT auth, API Platform, documentation OpenAPI -4. **Minimiser les risques** : Tests Ă  chaque phase, validation continue - -**DurĂ©e estimĂ©e** : 9 semaines (avec buffer) -**Effort requis** : 1 dĂ©veloppeur full-time - -**PrĂȘt Ă  dĂ©marrer ?** Validation de ce plan avant de commencer la Phase 1. diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md deleted file mode 100644 index 5004116..0000000 --- a/REFACTORING_PLAN.md +++ /dev/null @@ -1,786 +0,0 @@ -# Plan de Refactoring - Inventory v1.2.0 - -> **Date de creation :** 2026-02-03 -> **Branche de travail :** `refacto/v1.3.0` -> **Base :** `develop` (commit `8d83076`) - ---- - -## Legende des statuts - -| Statut | Signification | -| ------ | ---------------------- | -| `[ ]` | A faire | -| `[~]` | En cours | -| `[x]` | Termine | -| `[!]` | Bloque / besoin d'info | - ---- - -## Phase 1 - Securite (CRITIQUE) - -> **Priorite :** MAXIMALE - A traiter en premier - -### 1.1 Corriger la configuration de securite - -- **Statut :** `[ ]` -- **Fichier :** `config/packages/security.yaml` -- **Probleme :** `PUBLIC_ACCESS` applique a toutes les routes `/api` avant la regle `IS_AUTHENTICATED_FULLY`. Le pattern matching "first match wins" rend potentiellement tout public. -- **Action :** Reordonner les regles `access_control` pour que les routes protegees soient listees AVANT les routes publiques. -- **Agent :** - -- **Notes :** - - -### 1.2 Ajouter les controles d'autorisation sur les controllers - -- **Statut :** `[ ]` -- **Fichiers :** - - `src/Controller/MachineSkeletonController.php` - - `src/Controller/CustomFieldValueController.php` - - `src/Controller/DocumentQueryController.php` - - `src/Controller/SessionProfileController.php` - - `src/Controller/SessionProfilesController.php` - - Tous les `*HistoryController.php` -- **Probleme :** Aucun attribut `#[IsGranted]` sur les controllers custom. Pas de RBAC. -- **Action :** Ajouter `#[IsGranted('IS_AUTHENTICATED_FULLY')]` sur chaque controller (ou route). Definir des roles si necessaire. -- **Agent :** - -- **Notes :** - - -### 1.3 Securiser les secrets - -- **Statut :** `[ ]` -- **Fichiers :** - - `.env` (JWT_PASSPHRASE en dur, APP_SECRET vide) - - `docker/.env.docker` (credentials `root:root`) -- **Action :** - 1. Deplacer `JWT_PASSPHRASE` dans `.env.local` (git-ignore) - 2. Generer un `APP_SECRET` valide - 3. Ajouter `.env.local` dans `.gitignore` si pas deja fait - 4. Documenter la configuration des secrets pour les devs -- **Agent :** - -- **Notes :** - - ---- - -## Phase 2 - Elimination de la duplication de code - -> **Priorite :** HAUTE - Impact direct sur la maintenabilite - -### 2.1 Refactorer les 3 Audit Subscribers en un seul generique - -- **Statut :** `[ ]` -- **Fichiers concernes :** - - `src/EventSubscriber/ProductAuditSubscriber.php` (298 LOC) - - `src/EventSubscriber/PieceAuditSubscriber.php` (300 LOC) - - `src/EventSubscriber/ComposantAuditSubscriber.php` (300 LOC) -- **Probleme :** ~900 LOC dupliquees a ~95%. Les methodes `onFlush()`, `buildDiffFromChangeSet()`, `resolveActorProfileId()`, `mergeDiffs()`, `normalizeCollection()` sont identiques. Seules les methodes `snapshot*()` different legerement. -- **Action :** - 1. Creer un `AbstractAuditSubscriber` ou un `GenericAuditSubscriber` parametrable - 2. Extraire la logique commune (onFlush, buildDiff, resolveActor, mergeDiffs, normalizeCollection) - 3. Utiliser un systeme de configuration par entite (map `entityClass => entityType + snapshotMethod`) - 4. Supprimer les 3 fichiers redondants - 5. Verifier que l'audit fonctionne toujours sur Product, Piece et Composant -- **Agent :** - -- **Notes :** Tester manuellement les logs d'audit apres refacto. - -### 2.2 Extraire un CuidGenerator utilitaire - -- **Statut :** `[ ]` -- **Fichiers concernes :** 18 entites contenant `generateCuid()` en prive -- **Probleme :** Methode `generateCuid()` dupliquee dans chaque entite. De plus, `AuditLog.php` utilise une variante differente (base_convert). -- **Action :** - 1. Creer `src/Util/CuidGenerator.php` avec une methode statique `generate(): string` - 2. Uniformiser l'implementation (choisir une seule methode) - 3. Remplacer tous les appels dans les 18 entites - 4. Supprimer les methodes privees devenues inutiles -- **Agent :** - -- **Notes :** Attention a l'inconsistance entre AuditLog et les autres entites. - -### 2.3 Factoriser la logique de liaison dans MachineSkeletonController - -- **Statut :** `[ ]` -- **Fichier :** `src/Controller/MachineSkeletonController.php` (756 LOC) -- **Probleme :** Les methodes `applyComponentLinks()`, `applyPieceLinks()`, `applyProductLinks()` sont quasi identiques (~90 LOC chacune). -- **Action :** - 1. Extraire une methode generique `applyLinks(Machine $machine, array $links, string $type)` - 2. Parametrer par le type d'entite liee (Composant, Piece, Product) - 3. Reduire le controller a ~400 LOC max -- **Agent :** - -- **Notes :** - - ---- - -## Phase 3 - Restructuration des controllers - -> **Priorite :** MOYENNE - Amelioration de la lisibilite et maintenabilite - -### 3.1 Decouper MachineSkeletonController - -- **Statut :** `[ ]` -- **Fichier :** `src/Controller/MachineSkeletonController.php` (756 LOC) -- **Action :** - 1. Extraire la logique metier dans un `MachineSkeletonService` - 2. Le controller ne doit gerer que la requete/reponse HTTP - 3. Le service gere la logique de skeleton (get, update, applyLinks) - 4. Extraire les helpers (`resolveIdentifier`, `indexLinksById`, `applyOverrides`, `normalizeMachineSkeletonResponse`) dans le service -- **Agent :** - -- **Notes :** Depend de la phase 2.3 (factorisation des liens). - -### 3.2 Ajouter un try-catch et du logging dans les controllers - -- **Statut :** `[ ]` -- **Fichiers :** Tous les controllers dans `src/Controller/` -- **Probleme :** Aucun try-catch autour des `flush()` et `persist()`. Pas de logging d'erreurs. -- **Action :** - 1. Ajouter `try-catch` autour des operations Doctrine dans chaque controller - 2. Logger les erreurs avec le `LoggerInterface` de Symfony (Monolog) - 3. Retourner des reponses JSON coherentes en cas d'erreur serveur (500) -- **Agent :** - -- **Notes :** - - -### 3.3 Renforcer la validation des entrees - -- **Statut :** `[ ]` -- **Fichiers :** - - `src/Controller/CustomFieldValueController.php` - - `src/Controller/MachineSkeletonController.php` -- **Probleme :** Pas de validation de longueur max, pas de regex sur les IDs, pas de controle de profondeur JSON. -- **Action :** - 1. Valider le format des IDs (regex CUID : `/^cl[a-f0-9]{24}$/`) - 2. Ajouter des limites de longueur sur les champs string - 3. Utiliser le composant Validator de Symfony pour les DTOs si pertinent -- **Agent :** - -- **Notes :** - - ---- - -## Phase 4 - Amelioration du stockage - -> **Priorite :** MOYENNE - Performance et scalabilite - -### 4.1 Migrer le stockage PDF de base64 vers le filesystem - -- **Statut :** `[ ]` -- **Fichiers :** - - `src/Entity/Document.php` - - `src/Command/CompressPdfCommand.php` - - `src/Service/PdfCompressorService.php` -- **Probleme :** Les PDFs sont stockes en base64 dans la colonne `path` (TEXT) de la BDD. Risque de DoS et mauvaise perf sur des gros fichiers. -- **Action :** - 1. Utiliser `vich/uploader-bundle` (deja installe) pour le stockage fichier - 2. Configurer un repertoire de stockage (`var/uploads/documents/`) - 3. Migrer les documents existants (script de migration) - 4. Adapter `PdfCompressorService` pour lire/ecrire sur le filesystem - 5. Mettre a jour l'entite Document -- **Agent :** - -- **Notes :** Prevoir une migration de donnees pour les documents existants. - -### 4.2 Corriger les types de prix (string -> decimal) - -- **Statut :** `[ ]` -- **Fichiers :** - - `src/Entity/Machine.php` (`$prix`) - - `src/Entity/Product.php` (`$supplierPrice`) -- **Probleme :** Les prix sont types `?string` en PHP alors que la colonne est `DECIMAL(10,2)` en BDD. -- **Action :** - 1. Changer le type PHP en `?float` ou utiliser `brick/money` - 2. Adapter les getters/setters - 3. Verifier la serialisation API Platform -- **Agent :** - -- **Notes :** Impact potentiel sur le frontend (format des nombres). - ---- - -## Phase 5 - Utilisation du Process Component - -> **Priorite :** BASSE - Bonne pratique - -### 5.1 Remplacer exec() par Symfony Process - -- **Statut :** `[ ]` -- **Fichiers :** - - `src/Command/CompressPdfCommand.php` (lignes 42, 98-101) - - `src/Service/PdfCompressorService.php` (lignes 37-41) -- **Probleme :** Utilisation de `exec()` directe pour appeler `qpdf`. -- **Action :** - 1. Remplacer par `Symfony\Component\Process\Process` - 2. Gerer le timeout et les erreurs proprement - 3. Tester que la compression fonctionne toujours -- **Agent :** - -- **Notes :** `escapeshellarg()` est deja utilise, donc pas de faille de securite immediate. - ---- - -## Phase 6 - Tests - -> **Priorite :** HAUTE - Indispensable avant toute refacto majeure - -### 6.1 Mettre en place les tests unitaires - -- **Statut :** `[ ]` -- **Fichiers a creer :** - - `tests/Unit/Util/CuidGeneratorTest.php` - - `tests/Unit/Entity/MachineTest.php` - - `tests/Unit/Entity/ProductTest.php` - - `tests/Unit/Service/PdfCompressorServiceTest.php` -- **Action :** - 1. Tester le CuidGenerator (format, unicite) - 2. Tester les entites (validation, lifecycle callbacks) - 3. Tester le PdfCompressorService -- **Agent :** - -- **Notes :** - - -### 6.2 Mettre en place les tests fonctionnels (API) - -- **Statut :** `[ ]` -- **Fichiers a creer :** - - `tests/Functional/Api/MachineTest.php` - - `tests/Functional/Api/ProductTest.php` - - `tests/Functional/Api/AuthenticationTest.php` - - `tests/Functional/Api/MachineSkeletonTest.php` -- **Action :** - 1. Configurer une base de test (SQLite ou PostgreSQL de test) - 2. Creer des fixtures de test - 3. Tester les endpoints CRUD - 4. Tester l'authentification JWT - 5. Tester les endpoints custom (skeleton, custom fields) -- **Agent :** - -- **Notes :** Utiliser `ApiTestCase` de API Platform. - -### 6.3 Tests des Audit Subscribers - -- **Statut :** `[ ]` -- **Fichiers a creer :** - - `tests/Unit/EventSubscriber/AuditSubscriberTest.php` -- **Action :** - 1. Tester la creation de logs sur insert/update/delete - 2. Tester le format des diffs et snapshots - 3. Tester la resolution de l'acteur -- **Agent :** - -- **Notes :** A faire APRES la phase 2.1 (refacto des subscribers). - ---- - -## Phase 7 - Nett oyage et conventions - -> **Priorite :** BASSE - Polish final - -### 7.1 Supprimer les fichiers inutiles - -- **Statut :** `[ ]` -- **Fichiers a verifier :** - - `frontend/` (dossier legacy ? vs `Inventory_frontend/`) - - `src/ApiResource/` (repertoire vide) - - Fichiers SQL a la racine (`backup_v1.0.0.sql`, `data_norm.sql`, `fullasse.sql`, `fulldata.sql`) -- **Action :** Confirmer avec l'equipe quels fichiers sont obsoletes et les supprimer. -- **Agent :** - -- **Notes :** Ne pas supprimer sans validation. - -### 7.2 Uniformiser la gestion des null - -- **Statut :** `[ ]` -- **Fichiers :** Toutes les entites dans `src/Entity/` -- **Action :** S'assurer que les types nullable sont coherents entre PHP et la BDD (colonnes NOT NULL vs nullable). -- **Agent :** - -- **Notes :** - - ---- - ---- - -# FRONTEND (`Inventory_frontend/`) - ---- - -## Phase F1 - Decoupage des mega-composants (CRITIQUE) - -> **Priorite :** MAXIMALE - Les fichiers actuels sont inmaintenables - -### F1.1 Decouper `machine/[id].vue` (2989 LOC → 219 LOC) - -- **Statut :** `[x]` -- **Fichier :** `Inventory_frontend/app/pages/machine/[id].vue` -- **Resultat :** Page decomposee en 2 composables + 7 composants. Orchestrateur = 219 LOC. -- **Fichiers crees :** - - `composables/useMachineDetailData.ts` (1404 LOC) — state + logique metier - - `composables/useMachineSkeletonEditor.ts` (843 LOC) — logique skeleton - - `components/machine/MachineDetailHeader.vue` (76 LOC) - - `components/machine/MachineInfoCard.vue` (185 LOC) - - `components/machine/MachineDocumentsCard.vue` (116 LOC) - - `components/machine/MachineProductsCard.vue` (62 LOC) - - `components/machine/MachineComponentsCard.vue` (53 LOC) - - `components/machine/MachinePiecesCard.vue` (34 LOC) - - `components/machine/MachineSkeletonSummary.vue` (199 LOC) -- **Pattern :** Props + Events (pas de provide/inject). Composables avec injection de dependances (interface Deps). -- **Notes :** Typecheck 0 erreurs. Lint OK. - -### F1.2 Decouper `machines/new.vue` (1231 LOC → 196 LOC) - -- **Statut :** `[x]` -- **Fichier :** `Inventory_frontend/app/pages/machines/new.vue` -- **Resultat :** Page decomposee en 1 composable + 5 composants. Orchestrateur = 196 LOC. -- **Fichiers crees :** - - `composables/useMachineCreatePage.ts` (460 LOC) — state, entity lookups, options, creation - - `components/machine/create/RequirementComponentSelector.vue` (126 LOC) - - `components/machine/create/RequirementPieceSelector.vue` (130 LOC) - - `components/machine/create/RequirementProductSelector.vue` (142 LOC) - - `components/machine/create/MachineCreatePreview.vue` (205 LOC) - - `components/machine/create/PreviewRequirementGroup.vue` (59 LOC) -- **Pattern :** Props + Events. Composable consolide entity lookups, options, label helpers, creation. -- **Notes :** Typecheck 0 erreurs. Lint OK. Corrige aussi un bug F1.1 (defineProps dans mauvais script block de MachineSkeletonSummary.vue). - -### F1.3 Decouper les pages de creation/edition (Piece, Component, Product) - -- **Statut :** `[x]` -- **Fichiers :** - - `pages/component/create.vue` (1282 LOC) - - `pages/component/[id]/edit.vue` (1629 LOC) - - `pages/pieces/create.vue` (817 LOC) - - `pages/pieces/[id]/edit.vue` (1327 LOC) - - `pages/product/[id]/edit.vue` (936 LOC) -- **Probleme :** Formulaires monolithiques avec sections multiples (infos generales, fournisseurs, documents, custom fields, etc.). -- **Action :** - 1. Identifier les sections communes entre create/edit (factoriser) - 2. Extraire chaque section en composant reutilisable : - - `EntityFormGeneral.vue` (nom, reference, description) - - `EntityFormSuppliers.vue` (constructeurs) - - `EntityFormDocuments.vue` (documents) - - `EntityFormCustomFields.vue` (champs personnalises) - 3. Objectif par page : <400 LOC -- **Agent :** - -- **Notes :** Les formulaires create et edit partagent beaucoup de code. Factoriser. -- **Sous-taches :** - - [x] F1.3a Extraire `customFieldFormUtils.ts` (duplique dans 5 fichiers) - - [x] F1.3b Extraire `documentDisplayUtils.ts` (duplique dans 3 pages edit) - - [x] F1.3c Extraire `historyDisplayUtils.ts` (duplique dans 3 pages edit) - - [x] F1.3d Rewire les 5 pages create/edit sur les modules extraits - - [x] F1.3e Typecheck + commit F1.3 (erreurs F1.3 corrigees, 120 erreurs preexistantes documentees) - -### F1.4 Reduire PieceItem.vue (1588 LOC) et ComponentItem.vue (1336 LOC) - -- **Statut :** `[x]` -- **Fichiers :** - - `Inventory_frontend/app/components/PieceItem.vue` (1588 → 740 LOC) - - `Inventory_frontend/app/components/ComponentItem.vue` (1336 → 585 LOC) -- **Probleme :** ~700 LOC de logique dupliquee entre les deux composants (champs personnalises, documents, affichage produit). -- **Action realisee :** - 1. Extraction de la logique pure custom fields dans `shared/utils/entityCustomFieldLogic.ts` (~350 LOC) - 2. Creation de `composables/useEntityCustomFields.ts` (composable reactif, ~180 LOC) - 3. Creation de `composables/useEntityDocuments.ts` (CRUD documents + preview, ~120 LOC) - 4. Creation de `composables/useEntityProductDisplay.ts` (affichage produit, ~100 LOC) - 5. Import des helpers document depuis `shared/utils/documentDisplayUtils.ts` (existant) - 6. Rewrite des deux composants pour utiliser les modules partages - 7. Typecheck 0 erreurs, lint 0 erreurs -- **Sous-taches :** - - [x] F1.4a Extraire `entityCustomFieldLogic.ts` (fonctions pures) - - [x] F1.4b Creer `useEntityCustomFields.ts` (composable reactif) - - [x] F1.4c Creer `useEntityDocuments.ts` (composable documents) - - [x] F1.4d Creer `useEntityProductDisplay.ts` (composable produit) - - [x] F1.4e Rewrite ComponentItem.vue (1336 → 585 LOC, script 900 → 150 LOC) - - [x] F1.4f Rewrite PieceItem.vue (1588 → 740 LOC, script 1100 → 255 LOC) - - [x] F1.4g Typecheck + lint (0 erreurs) -- **Notes :** Les templates restent volumineux (~430-480 LOC) car le contenu UI est dense. Une extraction en sous-composants (DocumentList, ProductDisplay, CustomFieldForm) serait une etape future optionnelle. - ---- - -## Phase F2 - Elimination de la duplication frontend - -> **Priorite :** HAUTE - DRY - -### F2.1 Extraire `extractCollection()` dans un utilitaire partage - -- **Statut :** `[x]` -- **Fichiers concernes :** - - `composables/useSites.ts` - - `composables/useProducts.ts` - - `composables/usePieces.ts` - - `composables/useComposants.ts` - - `composables/useMachineTypesApi.js` - - `composables/useConstructeurs.ts` - - `composables/useDocuments.ts` - - `composables/useMachineCreateSelections.ts` - - `components/ComponentStructureAssignmentNode.vue` - - `components/model-types/ManagementView.vue` -- **Probleme :** La fonction `extractCollection()` (parsing `hydra:member` / `member` / `items` / `data` / array) etait dupliquee dans 10 fichiers. -- **Action :** - 1. [x] Creer `shared/utils/apiHelpers.ts` avec `extractCollection()` generique - 2. [x] Remplacer les 10 implementations locales par un import -- **Agent :** - -- **Notes :** Gere aussi `items` (utilise par ManagementView.vue). `extractRelationId()` et `normalizeRelationIds()` restent dans `shared/apiRelations.ts` (deja partages). - -### F2.2 Fusionner les 3 composables d'historique - -- **Statut :** `[x]` -- **Fichiers concernes :** - - `composables/useComponentHistory.ts` (67 → 13 LOC, thin wrapper) - - `composables/usePieceHistory.ts` (67 → 13 LOC, thin wrapper) - - `composables/useProductHistory.ts` (67 → 13 LOC, thin wrapper) - - `composables/useEntityHistory.ts` (NEW, 65 LOC, logique generique) -- **Probleme :** 3 fichiers quasi identiques (seul le endpoint differait). -- **Action :** - 1. [x] Creer `composables/useEntityHistory.ts` parametrable par type d'entite - 2. [x] Reecrire les 3 fichiers specifiques en wrappers backward-compatible -- **Agent :** - -- **Notes :** Les wrappers preservent l'API existante (types + fonction), aucun consommateur a modifier. - -### F2.3 Factoriser les composables de types (Component/Piece/Product) - -- **Statut :** `[x]` -- **Fichiers concernes :** - - `composables/useComponentTypes.ts` (165 → 30 LOC, thin wrapper) - - `composables/usePieceTypes.ts` (165 → 30 LOC, thin wrapper) - - `composables/useProductTypes.ts` (160 → 28 LOC, thin wrapper) - - `composables/useEntityTypes.ts` (NEW, 172 LOC, logique generique) -- **Probleme :** 3 composables tres similaires pour gerer les categories/types. -- **Action :** - 1. [x] Creer `composables/useEntityTypes.ts` generique (CRUD + singleton state par categorie) - 2. [x] Reecrire les 3 fichiers specifiques en wrappers avec renommage des champs -- **Agent :** - -- **Notes :** Les wrappers renomment `types` → `componentTypes`/`pieceTypes`/`productTypes`, preservent `getXxxTypes()` et `isXxxTypeLoading()`. Etat partage via `stateByCategory` map module-level. - ---- - -## Phase F3 - Migration TypeScript - -> **Priorite :** HAUTE - Securite du typage - -### F3.1 Definir les types pour les reponses API - -- **Statut :** `[x]` (partiellement — types definis dans chaque composable + `ApiResponse` dans useApi.ts) -- **Fichiers :** - - `composables/useApi.ts` — `ApiResponse` generique (success/data/error/status) - - `composables/useMachines.ts` — `Machine` interface - - `composables/useMachineTypesApi.ts` — `MachineType`, `MachineTypeRequirement` interfaces - - `composables/useToast.ts` — `Toast`, `ToastType` types - - `composables/useProfiles.ts` — `Profile` interface - - `composables/useCustomFields.ts` — `CustomFieldValue` interface -- **Notes :** Les types sont definis dans chaque composable (colocation). Types entite existants : `Product`, `Piece`, `Composant`, `Constructeur`, `Site`, `Document` dans leurs composables respectifs (.ts). `shared/types/inventory.ts` contient les types de structure de modele. - -### F3.2 Convertir les composables JS en TS - -- **Statut :** `[x]` -- **Fichiers convertis (7 fichiers JS → TS) :** - - [x] `useToast.js` → `useToast.ts` (72 LOC, types: `Toast`, `ToastType`) - - [x] `useProfiles.js` → `useProfiles.ts` (68 LOC, type: `Profile`) - - [x] `useProfileSession.js` → `useProfileSession.ts` (85 LOC, importe `Profile`) - - [x] `useApi.js` → `useApi.ts` (106 LOC → 120 LOC, types: `ApiResponse`, `ApiCallOptions`, ajout `put()`) - - [x] `useCustomFields.js` → `useCustomFields.ts` (105 LOC, type: `CustomFieldValue`) - - [x] `useMachineTypesApi.js` → `useMachineTypesApi.ts` (173 → 188 LOC, types: `MachineType`, `MachineTypeRequirement`) - - [x] `useMachines.js` → `useMachines.ts` (267 LOC, type: `Machine`, utilise `extractCollection`) -- **Fichiers deja TS :** `useProducts.ts`, `usePieces.ts`, `useComposants.ts`, `useConstructeurs.ts`, `useSites.ts`, `useDocuments.ts` -- **Fichiers JS restants (deprecated) :** `useComponentModels.js`, `usePieceModels.js` (stubs deprecated, a supprimer) -- **Notes :** `ApiResponse` par defaut `any` pour backward-compat. Les callers existants fonctionnent sans changement ; le nouveau code peut opt-in strict via `get()`. - -### F3.3 Eliminer les `any` restants - -- **Statut :** `[x]` -- **Fichiers concernes :** - - `components/ProductSelect.vue` — 1 `any` restant (slot template, incompressible) - - `components/model-types/ManagementView.vue` — remplace `data?: any` → `Record`, `error: any` → `error: unknown`, `item: any` → `item: unknown` - - `components/ComponentStructureAssignmentNode.vue` — 12 casts `(definition as any).typePiece/typeProduct` elimines grace a l'extension des types - - `components/ComponentModelStructureEditor.vue` — `Promise` → `Promise` - - `components/model-types/ModelTypeForm.vue` — `(incoming as any).description` → cast `Record` - - `shared/types/inventory.ts` — `ComponentModelPiece.typePiece?` et `ComponentModelProduct.typeProduct?` ajoutes, 3 casts `(value as any)` supprimes -- **Probleme :** 20+ usages de `any` type identifies. -- **Action :** Etendre les interfaces de types pour supporter les formes alternatives de l'API. Remplacer les `any` par `unknown` ou `Record` la ou possible. -- **Agent :** Claude -- **Notes :** ~15 casts `any` elimines. Les `Record` restants dans ComponentModelStructureEditor sont justifies (manipulation dynamique interne de custom fields). Typecheck 0 erreurs. - ---- - -## Phase F4 - Qualite du code frontend - -> **Priorite :** MOYENNE - -### F4.1 Activer les regles ESLint critiques - -- **Statut :** `[x]` DONE -- **Fichier :** `Inventory_frontend/eslint.config.mjs` -- **Probleme :** Presque toutes les regles etaient desactivees (`no-console: off`, `no-unused-vars: off`, `no-explicit-any: off`). -- **Action realisee :** - 1. [x] Active `@typescript-eslint/no-explicit-any: warn` (526 warnings — amelioration progressive) - 2. [x] Active `no-console: warn` avec `allow: ['error']` — 0 violations (deja nettoye en F4.2) - 3. [x] Active `@typescript-eslint/no-unused-vars: warn` avec ignore `^_` — 0 violations (26 corrigees) - 4. [x] Corrige les 26 violations `no-unused-vars` : imports inutilises supprimes, variables prefixees `_`, destructurations nettoyees -- **Agent :** Claude -- **Notes :** 16 fichiers modifies. Regles organisees par categorie (vue, console, typescript, formatting). 0 erreurs, 526 warnings `no-explicit-any` restants (warn, pas bloquant). - -### F4.2 Nettoyer les console.log/console.error - -- **Statut :** `[x]` (console.log supprime, console.error conserve) -- **Fichiers modifies :** 8 fichiers (useMachineTypesApi.ts, useSites.ts, type/[id].vue, type/edit/[id].vue, TypeEditPieceRequirementsSection.vue, SearchSelect.vue, app.vue) -- **Probleme :** 19 appels `console.log` de debug laisses dans le code de production. -- **Action :** - 1. [x] Supprimer les 19 `console.log` de debug (normalizeRequirementList, page loading, route params, etc.) - 2. [ ] Les 72 `console.error` restants sont conserves (gestion d'erreur legitime). Migration vers un logger centralise a faire en F4.3. -- **Agent :** Claude -- **Notes :** 0 `console.log/warn/debug/info` restants dans le frontend. - -### F4.3 Centraliser la gestion d'erreurs API - -- **Statut :** `[ ]` -- **Fichier :** `Inventory_frontend/app/composables/useApi.js` (105 LOC) -- **Probleme :** Gestion d'erreur basique (juste un toast). Pas de retry, pas d'intercepteur, erreurs silencieuses dans certains composables. -- **Action :** - 1. Ajouter un systeme de retry configurable (1-3 tentatives) - 2. Centraliser la gestion des erreurs HTTP (401 -> redirect login, 500 -> message explicite) - 3. Ajouter des intercepteurs request/response - 4. Uniformiser le pattern dans tous les composables -- **Agent :** - -- **Notes :** - - ---- - -## Phase F5 - Reduire le fichier modelUtils.ts (1017 LOC) - -> **Priorite :** MOYENNE - -### F5.1 Decouper `shared/modelUtils.ts` - -- **Statut :** `[x]` -- **Fichier :** `Inventory_frontend/app/shared/modelUtils.ts` (1017 LOC → 37 LOC barrel) -- **Probleme :** Fichier utilitaire monolithique de 1017 lignes regroupant toute la logique de manipulation de modeles. -- **Action :** - 1. Identifier les groupes de fonctions (structure, custom fields, requirements, serialization) - 2. Decouper en 3 modules thematiques : - - `shared/model/componentStructure.ts` (~590 LOC) — helpers, sanitize, hydrate, normalize, extract, format pour composants - - `shared/model/pieceProductStructure.ts` (~155 LOC) — structure piece/produit (clone, sanitize, hydrate, format) - - `shared/model/definitionOverrides.ts` (~50 LOC) — sanitization des overrides de definition - 3. Re-exporter depuis `shared/modelUtils.ts` (barrel) pour ne pas casser les imports -- **Agent :** Claude -- **Notes :** 11 fichiers consommateurs inchanges (barrel preserve la retro-compat). Typecheck 0 erreurs. - ---- - -## Phase F6 - Tests frontend - -> **Priorite :** HAUTE - Aucun test actuellement - -### F6.1 Configurer Vitest - -- **Statut :** `[x]` DONE -- **Fichiers crees :** - - `vitest.config.ts` — config Vitest avec happy-dom, alias `~` et `#imports` - - `tests/__mocks__/imports.ts` — mock des auto-imports Nuxt (useRuntimeConfig, useRoute, etc.) - - `tests/shared/inventory-types.test.ts` — 9 tests smoke (validator, empty structures) -- **Action realisee :** - 1. [x] Installe `vitest`, `@vue/test-utils`, `happy-dom` - 2. [x] Configure Vitest avec environment happy-dom et resolution d'alias - 3. [x] Ajoute scripts `test` et `test:watch` dans `package.json` - 4. [x] Premier test suite : `componentModelStructureValidator` (9 tests, 100% pass) -- **Agent :** Claude -- **Notes :** `npm test` → 9 tests, 0 failures, <1s. Alias `#imports` pointe vers un mock minimal extensible. - -### F6.2 Tests unitaires des composables - -- **Statut :** `[x]` DONE (base) -- **Fichiers crees :** - - `tests/shared/apiHelpers.test.ts` — 10 tests (extractCollection, tous formats API) - - `tests/shared/modelUtils.test.ts` — 18 tests (isPlainObject, clone, stats, format, piece/product) - - `tests/shared/inventory-types.test.ts` — 9 tests (validator, empty structures) - - `tests/composables/useToast.test.ts` — 9 tests (add, types, max limit, clear, singleton) - - `tests/composables/useConfirm.test.ts` — 8 tests (open, confirm, cancel, options, singleton) -- **Action realisee :** - 1. [x] Teste `extractCollection()` : array, hydra:member, member, items, data, null, undefined - 2. [x] Teste `useToast` : ajout, types, max 3 toasts, clearAll, removeToast, singleton - 3. [x] Teste `useConfirm` : open/close, resolve true/false, custom options, singleton state - 4. [x] Teste `modelUtils` : clone, stats, preview, isPlainObject, piece/product variants - 5. [x] Teste `componentModelStructureValidator` : valid/invalid, custom fields, subcomponents -- **Agent :** Claude -- **Notes :** 54 tests, 5 fichiers, 100% pass, <2s. Tests `useApi` et CRUD composables necessitent mock fetch (phase ulterieure). - -### F6.3 Tests de composants - -- **Statut :** `[ ]` -- **Fichiers a creer :** - - `tests/components/Pagination.test.ts` - - `tests/components/SearchSelect.test.ts` - - `tests/components/MachineHeader.test.ts` (apres F1.1) -- **Action :** - 1. Tester les composants communs (Pagination, SearchSelect) - 2. Tester le rendu conditionnel et les events -- **Agent :** - -- **Notes :** - - ---- - -## Phase F7 - Ameliorations UX/DX - -> **Priorite :** BASSE - Polish - -### F7.1 Reduire le props drilling - -- **Statut :** `[ ]` -- **Probleme :** Props passees sur 3+ niveaux (ex: machine data dans les sous-composants). -- **Action :** - 1. Identifier les cas de props drilling >2 niveaux - 2. Utiliser `provide/inject` ou des composables partages - 3. Documenter le pattern choisi -- **Agent :** - -- **Notes :** A traiter apres F1 (decoupage des composants). - -### F7.2 Remplacer `confirm()` natif par des modales DaisyUI - -- **Statut :** `[x]` DONE -- **Probleme :** Les confirmations de suppression utilisaient `window.confirm()` (UI native, non-stylee). -- **Action realisee :** - 1. [x] Cree `composables/useConfirm.ts` — composable promise-based avec etat reactif partage - 2. [x] Cree `components/common/ConfirmModal.vue` — modale DaisyUI teleportee (backdrop blur, btn-error) - 3. [x] Monte `ConfirmModal` globalement dans `app.vue` - 4. [x] Remplace les 10 `confirm()` natifs dans 10 fichiers : - - `constructeurs.vue`, `profiles/manage.vue`, `ManagementView.vue` - - `product-catalog.vue`, `index.vue`, `machines/index.vue` - - `machine-skeleton/index.vue`, `pieces-catalog.vue`, `component-catalog.vue` - - `useSiteManagement.ts` (composable — import explicite) -- **Agent :** Claude -- **Notes :** API : `const { confirm } = useConfirm(); const ok = await confirm({ message: '...' })`. Auto-import Nuxt pour les SFC, import explicite pour les composables. - -### F7.3 Nettoyer `app.vue` (861 LOC) - -- **Statut :** `[x]` DONE -- **Fichier :** `Inventory_frontend/app/app.vue` (861 → 49 LOC) -- **Probleme :** Le fichier racine contenait le layout principal, la navbar (~676 LOC dupliquee mobile/desktop), et du state management. -- **Action realisee :** - 1. Cree `composables/useNavDropdown.ts` (~65 LOC) — gestion etat dropdowns navbar - 2. Cree `components/layout/AppNavbar.vue` (~310 LOC) — navbar data-driven avec `v-for` eliminant duplication mobile/desktop - 3. `app.vue` reecrit en orchestrateur minimal (49 LOC) + converti en TypeScript - 4. Supprime 4 imports d'icones inutilises -- **Agent :** Claude -- **Notes :** Approche data-driven : liens et groupes definis comme tableaux types (`NavLink[]`, `NavGroup[]`), rendus par `v-for` pour mobile et desktop - ---- - -## Ordre d'execution recommande - -``` -=== BACKEND === === FRONTEND === - -Phase 6.1 (Tests unitaires) Phase F6.1 (Config Vitest) - | | - v v -Phase 1 (Securite) Phase F1 (Decoupage mega-composants) - | | - v v -Phase 2 (Duplication backend) Phase F2 (Duplication frontend) - | | - v v -Phase 3 (Controllers) Phase F3 (Migration TypeScript) - | | - v v -Phase 6.2 (Tests API) Phase F4 (Qualite code) + Phase F5 (modelUtils) - | | - v v -Phase 4 (Stockage) Phase F6.2-F6.3 (Tests frontend) - | | - v v -Phase 5 + Phase 7 (Nettoyage) Phase F7 (UX/DX polish) - | - v -Phase 6.3 (Tests audit) -``` - -> Les colonnes backend et frontend peuvent etre executees **en parallele** par des agents differents. - ---- - -## Journal des modifications - -| Date | Phase | Tache | Agent | Statut | Notes | -| ---------- | ----- | ------------------------- | --------------- | ------- | ---------------------------------------------- | -| 2026-02-03 | - | Creation du plan backend | Claude Opus 4.5 | Termine | Analyse initiale backend (7 phases, 17 taches) | -| 2026-02-03 | - | Creation du plan frontend | Claude Opus 4.5 | Termine | Analyse frontend (7 phases, 22 taches) | -| | | | | | | - ---- - -## Commandes de verification - -> **Contexte :** Le backend tourne dans Docker (`docker compose`), le frontend est en local. -> Les commandes ci-dessous sont executees **depuis la racine du projet** (`/home/matthieu/dev_malio/Inventory/`). - -### Frontend (Nuxt 3 / Vue 3 / TypeScript) - -| Commande | Description | Quand l'utiliser | -| -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| `npx nuxi typecheck` | Verification des types TypeScript via `vue-tsc` | Apres chaque modification de fichier `.vue` ou `.ts`. C'est la commande principale de validation. | -| `npm run lint` | ESLint (config dans `eslint.config.mjs`) | Apres chaque modification pour verifier le style et les erreurs statiques. | -| `npm run lint:fix` | ESLint avec auto-fix | Pour corriger automatiquement les erreurs de formatage. | -| `npm run build` | Build de production Nuxt (inclut le typecheck) | Avant un commit pour s'assurer que tout compile. Plus lent que `typecheck` seul. | -| `npx nuxi prepare` | Regenerer les types auto-generes (`.nuxt/`) | Si les imports auto (composables, components) ne sont pas reconnus par le typecheck. | - -> **Toutes les commandes frontend** sont executees depuis `Inventory_frontend/` : -> -> ```bash -> cd Inventory_frontend && npx nuxi typecheck -> ``` - -> **Note sur les erreurs pre-existantes :** Il y a ~120 erreurs TypeScript pre-existantes documentees -> (anterieures a la refacto). L'objectif est de ne pas en ajouter de nouvelles. -> Pour verifier : comparer le nombre d'erreurs avant/apres modification. - -### Backend (Symfony 8 / PHP 8.4) - -| Commande | Description | Quand l'utiliser | -| ---------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- | -| `vendor/bin/php-cs-fixer fix --dry-run --diff` | Verifie le style PHP (PSR-12 + Symfony) sans modifier | Apres chaque modification PHP. | -| `vendor/bin/php-cs-fixer fix` | Corrige automatiquement le style PHP | Avant chaque commit. | -| `bin/phpunit` | Lance les tests PHPUnit | Apres chaque modification backend. | -| `php bin/console cache:clear` | Vide le cache Symfony | Si des erreurs bizarres apparaissent apres un changement de config. | - -> **Les commandes backend** sont executees **dans le conteneur Docker** : -> -> ```bash -> docker compose exec web vendor/bin/php-cs-fixer fix --dry-run --diff -> docker compose exec web bin/phpunit -> ``` - -### Workflow de verification (checklist par tache) - -``` -1. Lire les fichiers concernes (AVANT toute modification) -2. Effectuer les modifications -3. Frontend : npx nuxi typecheck → verifier pas de nouvelles erreurs -4. Frontend : npm run lint:fix → corriger le formatage -5. Backend : php-cs-fixer fix → corriger le style PHP -6. Backend : bin/phpunit → verifier la non-regression -7. Commit si tout est OK -``` - ---- - -## Regles pour les agents - -1. **Avant de commencer une tache :** - - Mettre le statut a `[~]` dans ce fichier - - Inscrire son nom/ID dans la colonne "Agent" - - Lire les fichiers concernes AVANT de modifier quoi que ce soit - -2. **Pendant le travail :** - - Ne modifier QUE les fichiers listes dans la tache - - Respecter les conventions existantes (PSR-12, strict_types) - - Ne pas introduire de nouvelles dependances sans justification - - Lancer `php-cs-fixer` apres les modifications - -3. **Apres avoir termine :** - - Mettre le statut a `[x]` - - Ajouter une entree dans le "Journal des modifications" - - Lancer les tests existants (`make test`) pour verifier la non-regression - - Decrire brievement les changements effectues dans "Notes" - -4. **En cas de blocage :** - - Mettre le statut a `[!]` - - Documenter le blocage dans "Notes" - - Ne PAS passer a une autre tache sans signaler le blocage - -5. **Regles specifiques au frontend :** - - Ecrire en TypeScript (pas de JS pour les nouveaux fichiers) - - Pas de `any` - utiliser des types concrets - - Pas de `console.log` - utiliser le logger ou `useToast` - - Composants Vue : max 400 LOC par fichier - - Utiliser les composants DaisyUI existants (pas de CSS custom) - - Tester avec Vitest quand la config est en place - -6. **Regles specifiques au backend :** - - `declare(strict_types=1)` obligatoire - - Respecter PSR-12 + regles Symfony (php-cs-fixer) - - Pas de `exec()` direct - utiliser Symfony Process - - Tester avec PHPUnit diff --git a/migratebdd.md b/migratebdd.md deleted file mode 100644 index 65f3ae6..0000000 --- a/migratebdd.md +++ /dev/null @@ -1,35 +0,0 @@ -# Migration DB (manuel) - -Ce guide explique comment importer un dump SQL venant de pgAdmin dans la base Docker. - -## 1) Export pgAdmin - -Dans pgAdmin: - -- Format: Plain -- Options: Use INSERT commands + Use column inserts -- Fichier: `data.sql` - -## 2) Normaliser le dump - -Convertit les colonnes camelCase en lowercase compact. - -```bash -python3 scripts/normalize-dump.py data.sql data_norm.sql --lower -``` - -## 3) Importer dans la base Docker - -Utilise `session_replication_role` pour eviter les erreurs de contraintes circulaires. - -```bash -docker compose --env-file docker/.env.docker.local exec -T db psql -U root -d inventory -v ON_ERROR_STOP=1 -c "SET session_replication_role = replica;" -docker compose --env-file docker/.env.docker.local exec -T db psql -U root -d inventory -v ON_ERROR_STOP=1 < data_norm.sql -docker compose --env-file docker/.env.docker.local exec -T db psql -U root -d inventory -v ON_ERROR_STOP=1 -c "SET session_replication_role = DEFAULT;" -``` - -## 4) Verifier - -```bash -docker compose --env-file docker/.env.docker.local exec -T db psql -U $POSTGRES_USER -d $POSTGRES_DB -c "\\dt" -```