Compare commits
5 Commits
v1.1.1
...
ba1114e78b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba1114e78b | ||
|
|
5ccc3b30f0 | ||
| 8d83076be0 | |||
|
|
997a3ae822 | ||
|
|
034c193e4b |
Submodule Inventory_frontend updated: adccfa9b46...9ee348fff0
681
REFACTORING_PLAN.md
Normal file
681
REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,681 @@
|
||||
# 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 - Nettoyage 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` (4308 LOC)
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichier :** `Inventory_frontend/app/pages/machine/[id].vue`
|
||||
- **Probleme :** Page monolithique de 4308 lignes. Contient la vue detail, l'edition, la gestion du skeleton, les composants, les pieces, les produits, les documents, l'historique.
|
||||
- **Action :**
|
||||
1. Identifier les sections logiques (header, detail, skeleton, composants, pieces, produits, documents, historique)
|
||||
2. Extraire chaque section en composant dedie :
|
||||
- `components/machine/MachineHeader.vue`
|
||||
- `components/machine/MachineDetail.vue`
|
||||
- `components/machine/MachineSkeletonEditor.vue`
|
||||
- `components/machine/MachineComponentsList.vue`
|
||||
- `components/machine/MachinePiecesList.vue`
|
||||
- `components/machine/MachineProductsList.vue`
|
||||
- `components/machine/MachineDocuments.vue`
|
||||
- `components/machine/MachineHistory.vue`
|
||||
3. La page `[id].vue` ne doit plus etre qu'un orchestrateur (<300 LOC)
|
||||
4. Utiliser `provide/inject` ou un composable partage pour l'etat machine
|
||||
- **Agent :** -
|
||||
- **Notes :** Tache la plus impactante du frontend. A faire en premier.
|
||||
|
||||
### F1.2 Decouper `machines/new.vue` (2313 LOC)
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichier :** `Inventory_frontend/app/pages/machines/new.vue`
|
||||
- **Probleme :** Page de creation de machine avec selection de type et heritage de structure, trop volumineuse.
|
||||
- **Action :**
|
||||
1. Extraire le formulaire de creation en composant `MachineCreateForm.vue`
|
||||
2. Extraire la selection de type en `MachineTypeSelector.vue`
|
||||
3. Extraire l'apercu de structure en composant separe
|
||||
4. Objectif : page <200 LOC
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### 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 :** `[ ]`
|
||||
- **Fichiers :**
|
||||
- `Inventory_frontend/app/components/PieceItem.vue` (1588 LOC)
|
||||
- `Inventory_frontend/app/components/ComponentItem.vue` (1336 LOC)
|
||||
- **Probleme :** Composants d'affichage/edition inline tres volumineux.
|
||||
- **Action :**
|
||||
1. Separer la vue lecture de la vue edition
|
||||
2. Extraire les sous-sections (details, documents, fournisseurs) en sous-composants
|
||||
3. Objectif : <400 LOC par composant
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
---
|
||||
|
||||
## Phase F2 - Elimination de la duplication frontend
|
||||
|
||||
> **Priorite :** HAUTE - DRY
|
||||
|
||||
### F2.1 Extraire `extractCollection()` dans un utilitaire partage
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers concernes :**
|
||||
- `composables/useSites.js`
|
||||
- `composables/useProducts.js`
|
||||
- `composables/usePieces.js`
|
||||
- `composables/useComposants.js`
|
||||
- `composables/useMachineTypesApi.js`
|
||||
- `composables/useConstructeurs.js`
|
||||
- **Probleme :** La fonction `extractCollection()` (parsing `hydra:member` / `member` / array) est dupliquee dans 6+ fichiers.
|
||||
- **Action :**
|
||||
1. Creer `shared/utils/apiHelpers.ts`
|
||||
2. Y placer `extractCollection()`, `extractRelationId()`, `normalizeRelationIds()`
|
||||
3. Remplacer les implementations locales par un import
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### F2.2 Fusionner les 3 composables d'historique
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers concernes :**
|
||||
- `composables/useComponentHistory.ts` (67 LOC)
|
||||
- `composables/usePieceHistory.ts` (67 LOC)
|
||||
- `composables/useProductHistory.ts` (67 LOC)
|
||||
- **Probleme :** 3 fichiers quasi identiques (seul le endpoint differe).
|
||||
- **Action :**
|
||||
1. Creer `composables/useEntityHistory.ts` parametrable par type d'entite
|
||||
2. Supprimer les 3 fichiers specifiques
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### F2.3 Factoriser les composables de types (Component/Piece/Product)
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers concernes :**
|
||||
- `composables/useComponentTypes.js` (140 LOC)
|
||||
- `composables/usePieceTypes.js` (140 LOC)
|
||||
- `composables/useProductTypes.js` (132 LOC)
|
||||
- **Probleme :** 3 composables tres similaires pour gerer les categories/types.
|
||||
- **Action :**
|
||||
1. Creer `composables/useEntityTypes.ts` generique
|
||||
2. Parametrer par type d'entite et endpoint
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
---
|
||||
|
||||
## Phase F3 - Migration TypeScript
|
||||
|
||||
> **Priorite :** HAUTE - Securite du typage
|
||||
|
||||
### F3.1 Definir les types pour les reponses API
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichier a creer :** `Inventory_frontend/app/shared/types/api.ts`
|
||||
- **Probleme :** Aucun type pour les reponses API. Les composables travaillent avec `any` implicite.
|
||||
- **Action :**
|
||||
1. Definir les interfaces pour chaque entite API :
|
||||
- `ApiMachine`, `ApiProduct`, `ApiPiece`, `ApiComposant`
|
||||
- `ApiSite`, `ApiDocument`, `ApiConstructeur`
|
||||
- `ApiAuditLog`, `ApiProfile`, `ApiCustomField`
|
||||
2. Definir `ApiCollectionResponse<T>` (hydra:member, totalItems, etc.)
|
||||
3. Definir `ApiErrorResponse`
|
||||
4. Typer les retours de `useApi()`
|
||||
- **Agent :** -
|
||||
- **Notes :** Partir de `shared/types/inventory.ts` (289 LOC) qui contient deja des types partiels.
|
||||
|
||||
### F3.2 Convertir les composables JS en TS
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers concernes (10 fichiers JS) :**
|
||||
- `useApi.js` -> `useApi.ts`
|
||||
- `useMachines.js` -> `useMachines.ts`
|
||||
- `useProducts.js` -> `useProducts.ts`
|
||||
- `usePieces.js` -> `usePieces.ts`
|
||||
- `useComposants.js` -> `useComposants.ts`
|
||||
- `useConstructeurs.js` -> `useConstructeurs.ts`
|
||||
- `useSites.js` -> `useSites.ts`
|
||||
- `useDocuments.js` -> `useDocuments.ts`
|
||||
- `useMachineTypesApi.js` -> `useMachineTypesApi.ts`
|
||||
- `useCustomFields.js` -> `useCustomFields.ts`
|
||||
- **Action :**
|
||||
1. Renommer `.js` en `.ts`
|
||||
2. Ajouter les types de retour, parametres, et variables reactives
|
||||
3. Utiliser les types API definis en F3.1
|
||||
4. Eliminer tous les `any` explicites et implicites
|
||||
- **Agent :** -
|
||||
- **Notes :** Depend de F3.1 (types API).
|
||||
|
||||
### F3.3 Eliminer les `any` restants
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers concernes :**
|
||||
- `components/common/ProductSelect.vue`
|
||||
- `components/common/ManagementView.vue`
|
||||
- `components/ComponentStructureAssignmentNode.vue`
|
||||
- `components/model-types/ComponentModelStructureEditor.vue`
|
||||
- `components/model-types/ModelTypeForm.vue`
|
||||
- **Probleme :** 20+ usages de `any` type identifies.
|
||||
- **Action :** Remplacer chaque `any` par un type concret ou un type union.
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
---
|
||||
|
||||
## Phase F4 - Qualite du code frontend
|
||||
|
||||
> **Priorite :** MOYENNE
|
||||
|
||||
### F4.1 Activer les regles ESLint critiques
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichier :** `Inventory_frontend/eslint.config.mjs`
|
||||
- **Probleme :** Presque toutes les regles sont desactivees (`no-console: off`, `no-unused-vars: off`, `no-explicit-any: off`).
|
||||
- **Action :**
|
||||
1. Activer `@typescript-eslint/no-explicit-any: warn`
|
||||
2. Activer `no-console: warn` (ou `error` sauf pour `console.error`)
|
||||
3. Activer `no-unused-vars: warn`
|
||||
4. Fixer les violations progressivement
|
||||
- **Agent :** -
|
||||
- **Notes :** 94 appels `console.*` a nettoyer.
|
||||
|
||||
### F4.2 Nettoyer les console.log/console.error
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers :** ~94 occurrences dans le frontend
|
||||
- **Probleme :** Appels de debug laisses dans le code de production.
|
||||
- **Action :**
|
||||
1. Remplacer les `console.error` utiles par un logger centralise (ou `useToast`)
|
||||
2. Supprimer les `console.log` de debug
|
||||
3. Creer un utilitaire `logger.ts` si necessaire (qui respecte `enableDebug` du runtime config)
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### 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 :** `[ ]`
|
||||
- **Fichier :** `Inventory_frontend/app/shared/modelUtils.ts` (1017 LOC)
|
||||
- **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 modules :
|
||||
- `shared/model/structureUtils.ts`
|
||||
- `shared/model/customFieldUtils.ts`
|
||||
- `shared/model/requirementUtils.ts`
|
||||
- `shared/model/serializationUtils.ts`
|
||||
3. Re-exporter depuis un `shared/model/index.ts` pour ne pas casser les imports
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
---
|
||||
|
||||
## Phase F6 - Tests frontend
|
||||
|
||||
> **Priorite :** HAUTE - Aucun test actuellement
|
||||
|
||||
### F6.1 Configurer Vitest
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichier a creer :** `Inventory_frontend/vitest.config.ts`
|
||||
- **Action :**
|
||||
1. Installer `vitest`, `@vue/test-utils`, `@nuxt/test-utils`
|
||||
2. Configurer Vitest avec support Vue/Nuxt
|
||||
3. Ajouter un script `test` dans `package.json`
|
||||
4. Creer un premier test smoke
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### F6.2 Tests unitaires des composables
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichiers a creer :**
|
||||
- `tests/composables/useApi.test.ts`
|
||||
- `tests/composables/useProducts.test.ts`
|
||||
- `tests/composables/useToast.test.ts`
|
||||
- `tests/shared/apiHelpers.test.ts` (apres F2.1)
|
||||
- **Action :**
|
||||
1. Tester `useApi` (requetes, timeout, erreurs)
|
||||
2. Tester `extractCollection()` (tous les formats de reponse)
|
||||
3. Tester les composables CRUD (mock des appels API)
|
||||
4. Tester le toast (ajout, suppression, max toasts)
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### 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 :** `[ ]`
|
||||
- **Probleme :** Les confirmations de suppression utilisent `window.confirm()` (UI native, non-stylee).
|
||||
- **Action :**
|
||||
1. Creer un composant `ConfirmModal.vue` reutilisable
|
||||
2. Ou creer un composable `useConfirm()` qui affiche une modale DaisyUI
|
||||
3. Remplacer tous les `confirm()` dans les composants
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
### F7.3 Nettoyer `app.vue` (861 LOC)
|
||||
- **Statut :** `[ ]`
|
||||
- **Fichier :** `Inventory_frontend/app/app.vue` (861 LOC)
|
||||
- **Probleme :** Le fichier racine contient le layout principal, la navbar, la sidebar, et du state management.
|
||||
- **Action :**
|
||||
1. Extraire la navbar en `components/layout/AppNavbar.vue`
|
||||
2. Extraire la sidebar en `components/layout/AppSidebar.vue`
|
||||
3. Utiliser un layout Nuxt (`layouts/default.vue`)
|
||||
4. `app.vue` ne doit contenir que `<NuxtLayout>` et les providers globaux
|
||||
- **Agent :** -
|
||||
- **Notes :** -
|
||||
|
||||
---
|
||||
|
||||
## 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) |
|
||||
| | | | | | |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
@@ -1,6 +1,6 @@
|
||||
api_platform:
|
||||
title: Hello API Platform
|
||||
version: 1.1.1
|
||||
version: 1.2.0
|
||||
defaults:
|
||||
stateless: false
|
||||
cache_headers:
|
||||
|
||||
@@ -21,3 +21,15 @@ services:
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
||||
App\EventSubscriber\ProductAuditSubscriber:
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber }
|
||||
|
||||
App\EventSubscriber\PieceAuditSubscriber:
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber }
|
||||
|
||||
App\EventSubscriber\ComposantAuditSubscriber:
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber }
|
||||
|
||||
@@ -6,4 +6,4 @@ POSTGRES_DB=inventory
|
||||
POSTGRES_USER=root
|
||||
POSTGRES_PASSWORD=root
|
||||
POSTGRES_PORT=5432
|
||||
XDEBUG_CLIENT_HOST=host.docker.internal
|
||||
XDEBUG_CLIENT_HOST=host.docker.internal
|
||||
24
frontend/.gitignore
vendored
24
frontend/.gitignore
vendored
@@ -1,24 +0,0 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -1,75 +0,0 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<NuxtPage/>
|
||||
</template>
|
||||
@@ -1,9 +0,0 @@
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
ssr: false,
|
||||
modules: ['@nuxtjs/tailwindcss'],
|
||||
typescript: {
|
||||
strict: true
|
||||
}
|
||||
})
|
||||
11892
frontend/package-lock.json
generated
11892
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"nuxt": "^4.2.2",
|
||||
"vue": "^3.5.26",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.14.0"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<h1 class="text-3xl font-bold">Nuxt OK ✅</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,2 +0,0 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,165 +20,792 @@ final class Version20260125143939 extends AbstractMigration
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER INDEX idx_f95a3199df92e79b RENAME TO IDX_F95A3199CC8A4CEE');
|
||||
$this->addSql('ALTER INDEX idx_f95a3199a3fdb2a7 RENAME TO IDX_F95A319936799605');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT "_ComposantConstructeurs_A_fkey"');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT "_ComposantConstructeurs_B_fkey"');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f95a3199df92e79b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f95a3199df92e79b RENAME TO IDX_F95A3199CC8A4CEE';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f95a3199a3fdb2a7') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f95a3199a3fdb2a7 RENAME TO IDX_F95A319936799605';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT IF EXISTS "_ComposantConstructeurs_A_fkey"');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs DROP CONSTRAINT IF EXISTS "_ComposantConstructeurs_B_fkey"');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs ALTER A TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs ALTER B TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs ADD CONSTRAINT FK_60760125D3D99E8B FOREIGN KEY (A) REFERENCES composants (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs ADD CONSTRAINT FK_607601254AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _composantconstructeurs ADD PRIMARY KEY (A, B)');
|
||||
$this->addSql('ALTER INDEX idx_5b97d813e8b7be43 RENAME TO IDX_60760125D3D99E8B');
|
||||
$this->addSql('ALTER INDEX _composantconstructeurs_b_index RENAME TO IDX_607601254AD0CF31');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff6736d61 RENAME TO IDX_6B64D7FF5C4A705F');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7fff6bae05f RENAME TO IDX_6B64D7FF633EC4FD');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ffa1dac1c6 RENAME TO IDX_6B64D7FF345EE564');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff96428d73 RENAME TO IDX_6B64D7FF3C6A9D1');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ffa3fdb2a7 RENAME TO IDX_6B64D7FF36799605');
|
||||
$this->addSql('ALTER INDEX idx_4a48378c158582c3 RENAME TO IDX_4A48378C2F024C2');
|
||||
$this->addSql('ALTER INDEX idx_4a48378cdf92e79b RENAME TO IDX_4A48378CCC8A4CEE');
|
||||
$this->addSql('ALTER INDEX idx_4a48378c4ca601c8 RENAME TO IDX_4A48378C169F1CF6');
|
||||
$this->addSql('ALTER INDEX idx_4a48378c40c2d03b RENAME TO IDX_4A48378C57B7763A');
|
||||
$this->addSql('ALTER INDEX idx_a2b07288f6bae05f RENAME TO IDX_A2B07288633EC4FD');
|
||||
$this->addSql('ALTER INDEX idx_a2b07288a1dac1c6 RENAME TO IDX_A2B07288345EE564');
|
||||
$this->addSql('ALTER INDEX idx_a2b0728896428d73 RENAME TO IDX_A2B072883C6A9D1');
|
||||
$this->addSql('ALTER INDEX idx_a2b07288a3fdb2a7 RENAME TO IDX_A2B0728836799605');
|
||||
$this->addSql('ALTER INDEX idx_a2b07288fcf7805f RENAME TO IDX_A2B072886973A4FD');
|
||||
$this->addSql('ALTER INDEX idx_528efe19f6bae05f RENAME TO IDX_528EFE19633EC4FD');
|
||||
$this->addSql('ALTER INDEX idx_528efe19a1dac1c6 RENAME TO IDX_528EFE19345EE564');
|
||||
$this->addSql('ALTER INDEX idx_528efe197d44d2df RENAME TO IDX_528EFE19EF6CF34B');
|
||||
$this->addSql('ALTER INDEX idx_528efe19bcced9e3 RENAME TO IDX_528EFE19C44B383C');
|
||||
$this->addSql('ALTER INDEX idx_62941615f6bae05f RENAME TO IDX_62941615633EC4FD');
|
||||
$this->addSql('ALTER INDEX idx_6294161596428d73 RENAME TO IDX_629416153C6A9D1');
|
||||
$this->addSql('ALTER INDEX idx_629416157d44d2df RENAME TO IDX_62941615EF6CF34B');
|
||||
$this->addSql('ALTER INDEX idx_6294161532c54aaf RENAME TO IDX_62941615F957D314');
|
||||
$this->addSql('ALTER INDEX machine_product_links_machineid_idx RENAME TO IDX_8CC32259633EC4FD');
|
||||
$this->addSql('ALTER INDEX machine_product_links_productid_idx RENAME TO IDX_8CC3225936799605');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259357fdbff RENAME TO IDX_8CC32259B590B209');
|
||||
$this->addSql('ALTER INDEX idx_8cc322597d44d2df RENAME TO IDX_8CC32259EF6CF34B');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259bcd7dad6 RENAME TO IDX_8CC32259A63AC5DC');
|
||||
$this->addSql('ALTER INDEX idx_8cc3225987ceb33f RENAME TO IDX_8CC32259937A1D7C');
|
||||
$this->addSql('ALTER INDEX idx_f1ce8dedfcf7805f RENAME TO IDX_F1CE8DED6973A4FD');
|
||||
$this->addSql('ALTER INDEX idx_f1ce8ded158582c3 RENAME TO IDX_F1CE8DED2F024C2');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT "_MachineConstructeurs_B_fkey"');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT "_MachineConstructeurs_A_fkey"');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_5b97d813e8b7be43') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_5b97d813e8b7be43 RENAME TO IDX_60760125D3D99E8B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('_composantconstructeurs_b_index') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX _composantconstructeurs_b_index RENAME TO IDX_607601254AD0CF31';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff6736d61') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff6736d61 RENAME TO IDX_6B64D7FF5C4A705F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7fff6bae05f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7fff6bae05f RENAME TO IDX_6B64D7FF633EC4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ffa1dac1c6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ffa1dac1c6 RENAME TO IDX_6B64D7FF345EE564';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff96428d73') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff96428d73 RENAME TO IDX_6B64D7FF3C6A9D1';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ffa3fdb2a7') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ffa3fdb2a7 RENAME TO IDX_6B64D7FF36799605';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378c158582c3') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378c158582c3 RENAME TO IDX_4A48378C2F024C2';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378cdf92e79b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378cdf92e79b RENAME TO IDX_4A48378CCC8A4CEE';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378c4ca601c8') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378c4ca601c8 RENAME TO IDX_4A48378C169F1CF6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378c40c2d03b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378c40c2d03b RENAME TO IDX_4A48378C57B7763A';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b07288f6bae05f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b07288f6bae05f RENAME TO IDX_A2B07288633EC4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b07288a1dac1c6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b07288a1dac1c6 RENAME TO IDX_A2B07288345EE564';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b0728896428d73') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b0728896428d73 RENAME TO IDX_A2B072883C6A9D1';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b07288a3fdb2a7') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b07288a3fdb2a7 RENAME TO IDX_A2B0728836799605';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b07288fcf7805f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b07288fcf7805f RENAME TO IDX_A2B072886973A4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19f6bae05f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19f6bae05f RENAME TO IDX_528EFE19633EC4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19a1dac1c6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19a1dac1c6 RENAME TO IDX_528EFE19345EE564';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe197d44d2df') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe197d44d2df RENAME TO IDX_528EFE19EF6CF34B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19bcced9e3') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19bcced9e3 RENAME TO IDX_528EFE19C44B383C';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_62941615f6bae05f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_62941615f6bae05f RENAME TO IDX_62941615633EC4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6294161596428d73') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6294161596428d73 RENAME TO IDX_629416153C6A9D1';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_629416157d44d2df') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_629416157d44d2df RENAME TO IDX_62941615EF6CF34B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6294161532c54aaf') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6294161532c54aaf RENAME TO IDX_62941615F957D314';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('machine_product_links_machineid_idx') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX machine_product_links_machineid_idx RENAME TO IDX_8CC32259633EC4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('machine_product_links_productid_idx') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX machine_product_links_productid_idx RENAME TO IDX_8CC3225936799605';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259357fdbff') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259357fdbff RENAME TO IDX_8CC32259B590B209';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc322597d44d2df') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc322597d44d2df RENAME TO IDX_8CC32259EF6CF34B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259bcd7dad6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259bcd7dad6 RENAME TO IDX_8CC32259A63AC5DC';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc3225987ceb33f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc3225987ceb33f RENAME TO IDX_8CC32259937A1D7C';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f1ce8dedfcf7805f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f1ce8dedfcf7805f RENAME TO IDX_F1CE8DED6973A4FD';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f1ce8ded158582c3') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f1ce8ded158582c3 RENAME TO IDX_F1CE8DED2F024C2';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT IF EXISTS "_MachineConstructeurs_B_fkey"');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs DROP CONSTRAINT IF EXISTS "_MachineConstructeurs_A_fkey"');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs ALTER A TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs ALTER B TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs ADD CONSTRAINT FK_E6A040CCD3D99E8B FOREIGN KEY (A) REFERENCES machines (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs ADD CONSTRAINT FK_E6A040CC4AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _machineconstructeurs ADD PRIMARY KEY (A, B)');
|
||||
$this->addSql('ALTER INDEX idx_4f225b32e8b7be43 RENAME TO IDX_E6A040CCD3D99E8B');
|
||||
$this->addSql('ALTER INDEX _machineconstructeurs_b_index RENAME TO IDX_E6A040CC4AD0CF31');
|
||||
$this->addSql('DROP INDEX "ModelType_category_name_key"');
|
||||
$this->addSql('DROP INDEX "ModelType_code_key"');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4f225b32e8b7be43') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4f225b32e8b7be43 RENAME TO IDX_E6A040CCD3D99E8B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('_machineconstructeurs_b_index') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX _machineconstructeurs_b_index RENAME TO IDX_E6A040CC4AD0CF31';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE model_types DROP CONSTRAINT IF EXISTS "ModelType_category_name_key"');
|
||||
$this->addSql('ALTER TABLE model_types DROP CONSTRAINT IF EXISTS "ModelType_code_key"');
|
||||
$this->addSql('ALTER TABLE model_types ALTER id TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE model_types ALTER category TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE model_types ALTER createdAt DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE model_types ALTER componentSkeleton TYPE JSON');
|
||||
$this->addSql('ALTER TABLE model_types ALTER pieceSkeleton TYPE JSON');
|
||||
$this->addSql('ALTER TABLE model_types ALTER productSkeleton TYPE JSON');
|
||||
$this->addSql('ALTER INDEX idx_b92d74724ca601c8 RENAME TO IDX_B92D7472169F1CF6');
|
||||
$this->addSql('ALTER INDEX idx_b92d7472a3fdb2a7 RENAME TO IDX_B92D747236799605');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT "_PieceConstructeurs_A_fkey"');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT "_PieceConstructeurs_B_fkey"');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_b92d74724ca601c8') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_b92d74724ca601c8 RENAME TO IDX_B92D7472169F1CF6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_b92d7472a3fdb2a7') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_b92d7472a3fdb2a7 RENAME TO IDX_B92D747236799605';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT IF EXISTS "_PieceConstructeurs_A_fkey"');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs DROP CONSTRAINT IF EXISTS "_PieceConstructeurs_B_fkey"');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs ALTER A TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs ALTER B TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs ADD CONSTRAINT FK_E94732E5D3D99E8B FOREIGN KEY (A) REFERENCES pieces (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs ADD CONSTRAINT FK_E94732E54AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _piececonstructeurs ADD PRIMARY KEY (A, B)');
|
||||
$this->addSql('ALTER INDEX idx_77fc120e8b7be43 RENAME TO IDX_E94732E5D3D99E8B');
|
||||
$this->addSql('ALTER INDEX _piececonstructeurs_b_index RENAME TO IDX_E94732E54AD0CF31');
|
||||
$this->addSql('ALTER INDEX idx_b3ba5a5a40c2d03b RENAME TO IDX_B3BA5A5A57B7763A');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT "_ProductConstructeurs_B_fkey"');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT "_ProductConstructeurs_A_fkey"');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_77fc120e8b7be43') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_77fc120e8b7be43 RENAME TO IDX_E94732E5D3D99E8B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('_piececonstructeurs_b_index') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX _piececonstructeurs_b_index RENAME TO IDX_E94732E54AD0CF31';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_b3ba5a5a40c2d03b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_b3ba5a5a40c2d03b RENAME TO IDX_B3BA5A5A57B7763A';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT IF EXISTS "_ProductConstructeurs_B_fkey"');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs DROP CONSTRAINT IF EXISTS "_ProductConstructeurs_A_fkey"');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs ALTER A TYPE VARCHAR(36)');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs ALTER B TYPE VARCHAR(36)');
|
||||
// Clean orphaned relations before re-adding foreign keys.
|
||||
$this->addSql('DELETE FROM _productconstructeurs WHERE A IS NULL OR B IS NULL');
|
||||
$this->addSql('DELETE FROM _productconstructeurs pc WHERE NOT EXISTS (SELECT 1 FROM products p WHERE p.id = pc.A)');
|
||||
$this->addSql('DELETE FROM _productconstructeurs pc WHERE NOT EXISTS (SELECT 1 FROM constructeurs c WHERE c.id = pc.B)');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs ADD CONSTRAINT FK_CF7403FCD3D99E8B FOREIGN KEY (A) REFERENCES products (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs ADD CONSTRAINT FK_CF7403FC4AD0CF31 FOREIGN KEY (B) REFERENCES constructeurs (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE _productconstructeurs ADD PRIMARY KEY (A, B)');
|
||||
$this->addSql('ALTER INDEX idx_66f61802e8b7be43 RENAME TO IDX_CF7403FCD3D99E8B');
|
||||
$this->addSql('ALTER INDEX _productconstructeurs_b_index RENAME TO IDX_CF7403FC4AD0CF31');
|
||||
$this->addSql('DROP INDEX uniq_profiles_email');
|
||||
$this->addSql('ALTER INDEX idx_96958790158582c3 RENAME TO IDX_969587902F024C2');
|
||||
$this->addSql('ALTER INDEX idx_96958790df92e79b RENAME TO IDX_96958790CC8A4CEE');
|
||||
$this->addSql('ALTER INDEX idx_f609e59e158582c3 RENAME TO IDX_F609E59E2F024C2');
|
||||
$this->addSql('ALTER INDEX idx_f609e59e4ca601c8 RENAME TO IDX_F609E59E169F1CF6');
|
||||
$this->addSql('ALTER INDEX idx_29a51f98158582c3 RENAME TO IDX_29A51F982F024C2');
|
||||
$this->addSql('ALTER INDEX idx_29a51f9840c2d03b RENAME TO IDX_29A51F9857B7763A');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_66f61802e8b7be43') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_66f61802e8b7be43 RENAME TO IDX_CF7403FCD3D99E8B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('_productconstructeurs_b_index') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX _productconstructeurs_b_index RENAME TO IDX_CF7403FC4AD0CF31';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('DROP INDEX IF EXISTS uniq_profiles_email');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_96958790158582c3') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_96958790158582c3 RENAME TO IDX_969587902F024C2';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_96958790df92e79b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_96958790df92e79b RENAME TO IDX_96958790CC8A4CEE';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f609e59e158582c3') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f609e59e158582c3 RENAME TO IDX_F609E59E2F024C2';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f609e59e4ca601c8') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f609e59e4ca601c8 RENAME TO IDX_F609E59E169F1CF6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_29a51f98158582c3') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_29a51f98158582c3 RENAME TO IDX_29A51F982F024C2';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_29a51f9840c2d03b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_29a51f9840c2d03b RENAME TO IDX_29A51F9857B7763A';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT FK_60760125D3D99E8B');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT FK_607601254AD0CF31');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT _ComposantConstructeurs_pkey');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT IF EXISTS FK_60760125D3D99E8B');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT IF EXISTS FK_607601254AD0CF31');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs DROP CONSTRAINT IF EXISTS _ComposantConstructeurs_pkey');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs ALTER a TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs ALTER b TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs ADD CONSTRAINT "_ComposantConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES composants (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE _ComposantConstructeurs ADD CONSTRAINT "_ComposantConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER INDEX idx_607601254ad0cf31 RENAME TO "_ComposantConstructeurs_B_index"');
|
||||
$this->addSql('ALTER INDEX idx_60760125d3d99e8b RENAME TO IDX_5B97D813E8B7BE43');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT FK_E6A040CCD3D99E8B');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT FK_E6A040CC4AD0CF31');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT _MachineConstructeurs_pkey');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_607601254ad0cf31') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_607601254ad0cf31 RENAME TO "_ComposantConstructeurs_B_index"';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_60760125d3d99e8b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_60760125d3d99e8b RENAME TO IDX_5B97D813E8B7BE43';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT IF EXISTS FK_E6A040CCD3D99E8B');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT IF EXISTS FK_E6A040CC4AD0CF31');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs DROP CONSTRAINT IF EXISTS _MachineConstructeurs_pkey');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs ALTER a TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs ALTER b TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs ADD CONSTRAINT "_MachineConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE _MachineConstructeurs ADD CONSTRAINT "_MachineConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES machines (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER INDEX idx_e6a040cc4ad0cf31 RENAME TO "_MachineConstructeurs_B_index"');
|
||||
$this->addSql('ALTER INDEX idx_e6a040ccd3d99e8b RENAME TO IDX_4F225B32E8B7BE43');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT FK_E94732E5D3D99E8B');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT FK_E94732E54AD0CF31');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT _PieceConstructeurs_pkey');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_e6a040cc4ad0cf31') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_e6a040cc4ad0cf31 RENAME TO "_MachineConstructeurs_B_index"';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_e6a040ccd3d99e8b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_e6a040ccd3d99e8b RENAME TO IDX_4F225B32E8B7BE43';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT IF EXISTS FK_E94732E5D3D99E8B');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT IF EXISTS FK_E94732E54AD0CF31');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs DROP CONSTRAINT IF EXISTS _PieceConstructeurs_pkey');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs ALTER a TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs ALTER b TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs ADD CONSTRAINT "_PieceConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES pieces (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE _PieceConstructeurs ADD CONSTRAINT "_PieceConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER INDEX idx_e94732e54ad0cf31 RENAME TO "_PieceConstructeurs_B_index"');
|
||||
$this->addSql('ALTER INDEX idx_e94732e5d3d99e8b RENAME TO IDX_77FC120E8B7BE43');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT FK_CF7403FCD3D99E8B');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT FK_CF7403FC4AD0CF31');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT _ProductConstructeurs_pkey');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_e94732e54ad0cf31') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_e94732e54ad0cf31 RENAME TO "_PieceConstructeurs_B_index"';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_e94732e5d3d99e8b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_e94732e5d3d99e8b RENAME TO IDX_77FC120E8B7BE43';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT IF EXISTS FK_CF7403FCD3D99E8B');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT IF EXISTS FK_CF7403FC4AD0CF31');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs DROP CONSTRAINT IF EXISTS _ProductConstructeurs_pkey');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs ALTER a TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs ALTER b TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs ADD CONSTRAINT "_ProductConstructeurs_B_fkey" FOREIGN KEY (b) REFERENCES products (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE _ProductConstructeurs ADD CONSTRAINT "_ProductConstructeurs_A_fkey" FOREIGN KEY (a) REFERENCES constructeurs (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER INDEX idx_cf7403fc4ad0cf31 RENAME TO "_ProductConstructeurs_B_index"');
|
||||
$this->addSql('ALTER INDEX idx_cf7403fcd3d99e8b RENAME TO IDX_66F61802E8B7BE43');
|
||||
$this->addSql('ALTER INDEX idx_f95a319936799605 RENAME TO IDX_F95A3199A3FDB2A7');
|
||||
$this->addSql('ALTER INDEX idx_f95a3199cc8a4cee RENAME TO IDX_F95A3199DF92E79B');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff345ee564 RENAME TO IDX_6B64D7FFA1DAC1C6');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff5c4a705f RENAME TO IDX_6B64D7FF6736D61');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff633ec4fd RENAME TO IDX_6B64D7FFF6BAE05F');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff3c6a9d1 RENAME TO IDX_6B64D7FF96428D73');
|
||||
$this->addSql('ALTER INDEX idx_6b64d7ff36799605 RENAME TO IDX_6B64D7FFA3FDB2A7');
|
||||
$this->addSql('ALTER INDEX idx_4a48378c57b7763a RENAME TO IDX_4A48378C40C2D03B');
|
||||
$this->addSql('ALTER INDEX idx_4a48378c2f024c2 RENAME TO IDX_4A48378C158582C3');
|
||||
$this->addSql('ALTER INDEX idx_4a48378c169f1cf6 RENAME TO IDX_4A48378C4CA601C8');
|
||||
$this->addSql('ALTER INDEX idx_4a48378ccc8a4cee RENAME TO IDX_4A48378CDF92E79B');
|
||||
$this->addSql('ALTER INDEX idx_a2b07288345ee564 RENAME TO IDX_A2B07288A1DAC1C6');
|
||||
$this->addSql('ALTER INDEX idx_a2b07288633ec4fd RENAME TO IDX_A2B07288F6BAE05F');
|
||||
$this->addSql('ALTER INDEX idx_a2b072886973a4fd RENAME TO IDX_A2B07288FCF7805F');
|
||||
$this->addSql('ALTER INDEX idx_a2b072883c6a9d1 RENAME TO IDX_A2B0728896428D73');
|
||||
$this->addSql('ALTER INDEX idx_a2b0728836799605 RENAME TO IDX_A2B07288A3FDB2A7');
|
||||
$this->addSql('ALTER INDEX idx_528efe19345ee564 RENAME TO IDX_528EFE19A1DAC1C6');
|
||||
$this->addSql('ALTER INDEX idx_528efe19633ec4fd RENAME TO IDX_528EFE19F6BAE05F');
|
||||
$this->addSql('ALTER INDEX idx_528efe19ef6cf34b RENAME TO IDX_528EFE197D44D2DF');
|
||||
$this->addSql('ALTER INDEX idx_528efe19c44b383c RENAME TO IDX_528EFE19BCCED9E3');
|
||||
$this->addSql('ALTER INDEX idx_62941615ef6cf34b RENAME TO IDX_629416157D44D2DF');
|
||||
$this->addSql('ALTER INDEX idx_62941615633ec4fd RENAME TO IDX_62941615F6BAE05F');
|
||||
$this->addSql('ALTER INDEX idx_629416153c6a9d1 RENAME TO IDX_6294161596428D73');
|
||||
$this->addSql('ALTER INDEX idx_62941615f957d314 RENAME TO IDX_6294161532C54AAF');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259633ec4fd RENAME TO "machine_product_links_machineId_idx"');
|
||||
$this->addSql('ALTER INDEX idx_8cc3225936799605 RENAME TO "machine_product_links_productId_idx"');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259ef6cf34b RENAME TO IDX_8CC322597D44D2DF');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259b590b209 RENAME TO IDX_8CC32259357FDBFF');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259a63ac5dc RENAME TO IDX_8CC32259BCD7DAD6');
|
||||
$this->addSql('ALTER INDEX idx_8cc32259937a1d7c RENAME TO IDX_8CC3225987CEB33F');
|
||||
$this->addSql('ALTER INDEX idx_f1ce8ded2f024c2 RENAME TO IDX_F1CE8DED158582C3');
|
||||
$this->addSql('ALTER INDEX idx_f1ce8ded6973a4fd RENAME TO IDX_F1CE8DEDFCF7805F');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_cf7403fc4ad0cf31') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_cf7403fc4ad0cf31 RENAME TO "_ProductConstructeurs_B_index"';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_cf7403fcd3d99e8b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_cf7403fcd3d99e8b RENAME TO IDX_66F61802E8B7BE43';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f95a319936799605') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f95a319936799605 RENAME TO IDX_F95A3199A3FDB2A7';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f95a3199cc8a4cee') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f95a3199cc8a4cee RENAME TO IDX_F95A3199DF92E79B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff345ee564') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff345ee564 RENAME TO IDX_6B64D7FFA1DAC1C6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff5c4a705f') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff5c4a705f RENAME TO IDX_6B64D7FF6736D61';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff633ec4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff633ec4fd RENAME TO IDX_6B64D7FFF6BAE05F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff3c6a9d1') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff3c6a9d1 RENAME TO IDX_6B64D7FF96428D73';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_6b64d7ff36799605') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_6b64d7ff36799605 RENAME TO IDX_6B64D7FFA3FDB2A7';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378c57b7763a') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378c57b7763a RENAME TO IDX_4A48378C40C2D03B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378c2f024c2') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378c2f024c2 RENAME TO IDX_4A48378C158582C3';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378c169f1cf6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378c169f1cf6 RENAME TO IDX_4A48378C4CA601C8';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_4a48378ccc8a4cee') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_4a48378ccc8a4cee RENAME TO IDX_4A48378CDF92E79B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b07288345ee564') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b07288345ee564 RENAME TO IDX_A2B07288A1DAC1C6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b07288633ec4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b07288633ec4fd RENAME TO IDX_A2B07288F6BAE05F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b072886973a4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b072886973a4fd RENAME TO IDX_A2B07288FCF7805F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b072883c6a9d1') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b072883c6a9d1 RENAME TO IDX_A2B0728896428D73';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_a2b0728836799605') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_a2b0728836799605 RENAME TO IDX_A2B07288A3FDB2A7';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19345ee564') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19345ee564 RENAME TO IDX_528EFE19A1DAC1C6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19633ec4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19633ec4fd RENAME TO IDX_528EFE19F6BAE05F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19ef6cf34b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19ef6cf34b RENAME TO IDX_528EFE197D44D2DF';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_528efe19c44b383c') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_528efe19c44b383c RENAME TO IDX_528EFE19BCCED9E3';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_62941615ef6cf34b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_62941615ef6cf34b RENAME TO IDX_629416157D44D2DF';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_62941615633ec4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_62941615633ec4fd RENAME TO IDX_62941615F6BAE05F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_629416153c6a9d1') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_629416153c6a9d1 RENAME TO IDX_6294161596428D73';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_62941615f957d314') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_62941615f957d314 RENAME TO IDX_6294161532C54AAF';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259633ec4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259633ec4fd RENAME TO "machine_product_links_machineId_idx"';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc3225936799605') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc3225936799605 RENAME TO "machine_product_links_productId_idx"';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259ef6cf34b') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259ef6cf34b RENAME TO IDX_8CC322597D44D2DF';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259b590b209') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259b590b209 RENAME TO IDX_8CC32259357FDBFF';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259a63ac5dc') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259a63ac5dc RENAME TO IDX_8CC32259BCD7DAD6';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_8cc32259937a1d7c') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_8cc32259937a1d7c RENAME TO IDX_8CC3225987CEB33F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f1ce8ded2f024c2') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f1ce8ded2f024c2 RENAME TO IDX_F1CE8DED158582C3';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f1ce8ded6973a4fd') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f1ce8ded6973a4fd RENAME TO IDX_F1CE8DEDFCF7805F';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('ALTER TABLE model_types ALTER id TYPE TEXT');
|
||||
$this->addSql('ALTER TABLE model_types ALTER category TYPE VARCHAR');
|
||||
$this->addSql('ALTER TABLE model_types ALTER componentskeleton TYPE JSONB');
|
||||
@@ -187,15 +814,78 @@ final class Version20260125143939 extends AbstractMigration
|
||||
$this->addSql('ALTER TABLE model_types ALTER createdat SET DEFAULT CURRENT_TIMESTAMP');
|
||||
$this->addSql('CREATE UNIQUE INDEX "ModelType_category_name_key" ON model_types (category, name)');
|
||||
$this->addSql('CREATE UNIQUE INDEX "ModelType_code_key" ON model_types (code)');
|
||||
$this->addSql('ALTER INDEX idx_b92d7472169f1cf6 RENAME TO IDX_B92D74724CA601C8');
|
||||
$this->addSql('ALTER INDEX idx_b92d747236799605 RENAME TO IDX_B92D7472A3FDB2A7');
|
||||
$this->addSql('ALTER INDEX idx_b3ba5a5a57b7763a RENAME TO IDX_B3BA5A5A40C2D03B');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_b92d7472169f1cf6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_b92d7472169f1cf6 RENAME TO IDX_B92D74724CA601C8';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_b92d747236799605') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_b92d747236799605 RENAME TO IDX_B92D7472A3FDB2A7';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_b3ba5a5a57b7763a') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_b3ba5a5a57b7763a RENAME TO IDX_B3BA5A5A40C2D03B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE UNIQUE INDEX uniq_profiles_email ON profiles (email)');
|
||||
$this->addSql('ALTER INDEX idx_969587902f024c2 RENAME TO IDX_96958790158582C3');
|
||||
$this->addSql('ALTER INDEX idx_96958790cc8a4cee RENAME TO IDX_96958790DF92E79B');
|
||||
$this->addSql('ALTER INDEX idx_f609e59e169f1cf6 RENAME TO IDX_F609E59E4CA601C8');
|
||||
$this->addSql('ALTER INDEX idx_f609e59e2f024c2 RENAME TO IDX_F609E59E158582C3');
|
||||
$this->addSql('ALTER INDEX idx_29a51f9857b7763a RENAME TO IDX_29A51F9840C2D03B');
|
||||
$this->addSql('ALTER INDEX idx_29a51f982f024c2 RENAME TO IDX_29A51F98158582C3');
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_969587902f024c2') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_969587902f024c2 RENAME TO IDX_96958790158582C3';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_96958790cc8a4cee') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_96958790cc8a4cee RENAME TO IDX_96958790DF92E79B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f609e59e169f1cf6') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f609e59e169f1cf6 RENAME TO IDX_F609E59E4CA601C8';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_f609e59e2f024c2') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_f609e59e2f024c2 RENAME TO IDX_F609E59E158582C3';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_29a51f9857b7763a') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_29a51f9857b7763a RENAME TO IDX_29A51F9840C2D03B';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF to_regclass('idx_29a51f982f024c2') IS NOT NULL THEN
|
||||
EXECUTE 'ALTER INDEX idx_29a51f982f024c2 RENAME TO IDX_29A51F98158582C3';
|
||||
END IF;
|
||||
END $$;
|
||||
SQL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
41
migrations/Version20260125170000.php
Normal file
41
migrations/Version20260125170000.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260125170000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add audit_logs table to store per-entity history entries.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE audit_logs (
|
||||
id VARCHAR(36) NOT NULL,
|
||||
entityType VARCHAR(50) NOT NULL,
|
||||
entityId VARCHAR(36) NOT NULL,
|
||||
action VARCHAR(20) NOT NULL,
|
||||
diff JSON DEFAULT NULL,
|
||||
snapshot JSON DEFAULT NULL,
|
||||
actorProfileId VARCHAR(36) DEFAULT NULL,
|
||||
createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql('CREATE INDEX idx_audit_entity ON audit_logs (entityType, entityId)');
|
||||
$this->addSql('CREATE INDEX idx_audit_created_at ON audit_logs (createdAt)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE audit_logs');
|
||||
}
|
||||
}
|
||||
80
src/Controller/ComposantHistoryController.php
Normal file
80
src/Controller/ComposantHistoryController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\AuditLogRepository;
|
||||
use App\Repository\ComposantRepository;
|
||||
use App\Repository\ProfileRepository;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class ComposantHistoryController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ComposantRepository $components,
|
||||
private readonly AuditLogRepository $auditLogs,
|
||||
private readonly ProfileRepository $profiles,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/api/composants/{id}/history', name: 'api_composant_history', methods: ['GET'])]
|
||||
public function __invoke(string $id): JsonResponse
|
||||
{
|
||||
$component = $this->components->find($id);
|
||||
if (!$component) {
|
||||
return new JsonResponse(
|
||||
['message' => 'Composant introuvable.'],
|
||||
Response::HTTP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
$logs = $this->auditLogs->findEntityHistory('composant', $id, 200);
|
||||
|
||||
$actorIds = array_values(array_unique(array_filter(array_map(
|
||||
static fn ($log) => $log->getActorProfileId(),
|
||||
$logs,
|
||||
))));
|
||||
|
||||
$actorMap = [];
|
||||
if ($actorIds !== []) {
|
||||
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||
foreach ($profiles as $profile) {
|
||||
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||
if ($label === '') {
|
||||
$label = $profile->getEmail() ?? $profile->getId();
|
||||
}
|
||||
$actorMap[$profile->getId()] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_map(
|
||||
static function ($log) use ($actorMap) {
|
||||
$actorId = $log->getActorProfileId();
|
||||
|
||||
return [
|
||||
'id' => $log->getId(),
|
||||
'action' => $log->getAction(),
|
||||
'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'actor' => $actorId
|
||||
? [
|
||||
'id' => $actorId,
|
||||
'label' => $actorMap[$actorId] ?? $actorId,
|
||||
]
|
||||
: null,
|
||||
'diff' => $log->getDiff(),
|
||||
'snapshot' => $log->getSnapshot(),
|
||||
];
|
||||
},
|
||||
$logs,
|
||||
);
|
||||
|
||||
return new JsonResponse([
|
||||
'items' => array_values($items),
|
||||
'total' => count($items),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
80
src/Controller/PieceHistoryController.php
Normal file
80
src/Controller/PieceHistoryController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\AuditLogRepository;
|
||||
use App\Repository\PieceRepository;
|
||||
use App\Repository\ProfileRepository;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class PieceHistoryController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PieceRepository $pieces,
|
||||
private readonly AuditLogRepository $auditLogs,
|
||||
private readonly ProfileRepository $profiles,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/api/pieces/{id}/history', name: 'api_piece_history', methods: ['GET'])]
|
||||
public function __invoke(string $id): JsonResponse
|
||||
{
|
||||
$piece = $this->pieces->find($id);
|
||||
if (!$piece) {
|
||||
return new JsonResponse(
|
||||
['message' => 'Pièce introuvable.'],
|
||||
Response::HTTP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
$logs = $this->auditLogs->findEntityHistory('piece', $id, 200);
|
||||
|
||||
$actorIds = array_values(array_unique(array_filter(array_map(
|
||||
static fn ($log) => $log->getActorProfileId(),
|
||||
$logs,
|
||||
))));
|
||||
|
||||
$actorMap = [];
|
||||
if ($actorIds !== []) {
|
||||
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||
foreach ($profiles as $profile) {
|
||||
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||
if ($label === '') {
|
||||
$label = $profile->getEmail() ?? $profile->getId();
|
||||
}
|
||||
$actorMap[$profile->getId()] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_map(
|
||||
static function ($log) use ($actorMap) {
|
||||
$actorId = $log->getActorProfileId();
|
||||
|
||||
return [
|
||||
'id' => $log->getId(),
|
||||
'action' => $log->getAction(),
|
||||
'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'actor' => $actorId
|
||||
? [
|
||||
'id' => $actorId,
|
||||
'label' => $actorMap[$actorId] ?? $actorId,
|
||||
]
|
||||
: null,
|
||||
'diff' => $log->getDiff(),
|
||||
'snapshot' => $log->getSnapshot(),
|
||||
];
|
||||
},
|
||||
$logs,
|
||||
);
|
||||
|
||||
return new JsonResponse([
|
||||
'items' => array_values($items),
|
||||
'total' => count($items),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
80
src/Controller/ProductHistoryController.php
Normal file
80
src/Controller/ProductHistoryController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Repository\AuditLogRepository;
|
||||
use App\Repository\ProductRepository;
|
||||
use App\Repository\ProfileRepository;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
final class ProductHistoryController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepository $products,
|
||||
private readonly AuditLogRepository $auditLogs,
|
||||
private readonly ProfileRepository $profiles,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/api/products/{id}/history', name: 'api_product_history', methods: ['GET'])]
|
||||
public function __invoke(string $id): JsonResponse
|
||||
{
|
||||
$product = $this->products->find($id);
|
||||
if (!$product) {
|
||||
return new JsonResponse(
|
||||
['message' => 'Produit introuvable.'],
|
||||
Response::HTTP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
$logs = $this->auditLogs->findEntityHistory('product', $id, 200);
|
||||
|
||||
$actorIds = array_values(array_unique(array_filter(array_map(
|
||||
static fn ($log) => $log->getActorProfileId(),
|
||||
$logs,
|
||||
))));
|
||||
|
||||
$actorMap = [];
|
||||
if ($actorIds !== []) {
|
||||
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||
foreach ($profiles as $profile) {
|
||||
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||
if ($label === '') {
|
||||
$label = $profile->getEmail() ?? $profile->getId();
|
||||
}
|
||||
$actorMap[$profile->getId()] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_map(
|
||||
static function ($log) use ($actorMap) {
|
||||
$actorId = $log->getActorProfileId();
|
||||
|
||||
return [
|
||||
'id' => $log->getId(),
|
||||
'action' => $log->getAction(),
|
||||
'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
||||
'actor' => $actorId
|
||||
? [
|
||||
'id' => $actorId,
|
||||
'label' => $actorMap[$actorId] ?? $actorId,
|
||||
]
|
||||
: null,
|
||||
'diff' => $log->getDiff(),
|
||||
'snapshot' => $log->getSnapshot(),
|
||||
];
|
||||
},
|
||||
$logs,
|
||||
);
|
||||
|
||||
return new JsonResponse([
|
||||
'items' => array_values($items),
|
||||
'total' => count($items),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
117
src/Entity/AuditLog.php
Normal file
117
src/Entity/AuditLog.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AuditLogRepository;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AuditLogRepository::class)]
|
||||
#[ORM\Table(name: 'audit_logs')]
|
||||
#[ORM\Index(name: 'idx_audit_entity', columns: ['entityType', 'entityId'])]
|
||||
#[ORM\Index(name: 'idx_audit_created_at', columns: ['createdAt'])]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
class AuditLog
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||
private ?string $id = null;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 50)]
|
||||
private string $entityType;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||
private string $entityId;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 20)]
|
||||
private string $action;
|
||||
|
||||
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||
private ?array $diff = null;
|
||||
|
||||
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||
private ?array $snapshot = null;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 36, nullable: true)]
|
||||
private ?string $actorProfileId = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||
private DateTimeImmutable $createdAt;
|
||||
|
||||
public function __construct(
|
||||
string $entityType,
|
||||
string $entityId,
|
||||
string $action,
|
||||
?array $diff = null,
|
||||
?array $snapshot = null,
|
||||
?string $actorProfileId = null,
|
||||
) {
|
||||
$this->entityType = $entityType;
|
||||
$this->entityId = $entityId;
|
||||
$this->action = $action;
|
||||
$this->diff = $diff;
|
||||
$this->snapshot = $snapshot;
|
||||
$this->actorProfileId = $actorProfileId;
|
||||
}
|
||||
|
||||
#[ORM\PrePersist]
|
||||
public function initializeAuditLog(): void
|
||||
{
|
||||
if (!isset($this->createdAt)) {
|
||||
$this->createdAt = new DateTimeImmutable();
|
||||
}
|
||||
|
||||
if ($this->id === null) {
|
||||
$this->id = $this->generateCuid();
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEntityType(): string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
public function getEntityId(): string
|
||||
{
|
||||
return $this->entityId;
|
||||
}
|
||||
|
||||
public function getAction(): string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function getDiff(): ?array
|
||||
{
|
||||
return $this->diff;
|
||||
}
|
||||
|
||||
public function getSnapshot(): ?array
|
||||
{
|
||||
return $this->snapshot;
|
||||
}
|
||||
|
||||
public function getActorProfileId(): ?string
|
||||
{
|
||||
return $this->actorProfileId;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
private function generateCuid(): string
|
||||
{
|
||||
// Keep the same lightweight CUID-like strategy used across the project.
|
||||
return 'cl'.substr(strtolower(base_convert(bin2hex(random_bytes(12)), 16, 36)), 0, 24);
|
||||
}
|
||||
}
|
||||
300
src/EventSubscriber/ComposantAuditSubscriber.php
Normal file
300
src/EventSubscriber/ComposantAuditSubscriber.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Entity\AuditLog;
|
||||
use App\Entity\Composant;
|
||||
use App\Entity\ModelType;
|
||||
use App\Entity\Product;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
#[AsDoctrineListener(event: Events::onFlush)]
|
||||
final class ComposantAuditSubscriber implements EventSubscriber
|
||||
{
|
||||
public function __construct(private readonly RequestStack $requestStack)
|
||||
{
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
Events::onFlush,
|
||||
];
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $args): void
|
||||
{
|
||||
$em = $args->getObjectManager();
|
||||
if (!$em instanceof EntityManagerInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uow = $em->getUnitOfWork();
|
||||
$actorProfileId = $this->resolveActorProfileId();
|
||||
$pendingUpdates = [];
|
||||
$pendingSnapshots = [];
|
||||
$pendingComponents = [];
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||
if (!$entity instanceof Composant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||
$snapshot = $this->snapshotComposant($entity);
|
||||
$this->persistAuditLog($em, new AuditLog('composant', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||
if (!$entity instanceof Composant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$componentId = (string) $entity->getId();
|
||||
if ($componentId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||
if ($diff !== []) {
|
||||
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
||||
$pendingSnapshots[$componentId] = $this->snapshotComposant($entity);
|
||||
$pendingComponents[$componentId] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||
if (!$entity instanceof Composant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snapshot = $this->snapshotComposant($entity);
|
||||
$this->persistAuditLog($em, new AuditLog('composant', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionUpdates() as $collection) {
|
||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||
}
|
||||
foreach ($uow->getScheduledCollectionDeletions() as $collection) {
|
||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||
}
|
||||
|
||||
foreach ($pendingUpdates as $componentId => $diff) {
|
||||
if ($diff === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$component = $pendingComponents[$componentId] ?? null;
|
||||
if (!$component instanceof Composant) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snapshot = $pendingSnapshots[$componentId] ?? $this->snapshotComposant($component);
|
||||
$this->persistAuditLog($em, new AuditLog('composant', $componentId, 'update', $diff, $snapshot, $actorProfileId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||
* @param array<string, Composant> $pendingComponents
|
||||
*/
|
||||
private function collectCollectionUpdate(
|
||||
object $collection,
|
||||
array &$pendingUpdates,
|
||||
array &$pendingSnapshots,
|
||||
array &$pendingComponents,
|
||||
): void {
|
||||
if (!$collection instanceof PersistentCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
$owner = $collection->getOwner();
|
||||
if (!$owner instanceof Composant) {
|
||||
return;
|
||||
}
|
||||
|
||||
$componentId = (string) $owner->getId();
|
||||
if ($componentId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$mapping = $collection->getMapping();
|
||||
$fieldName = $mapping['fieldName'] ?? null;
|
||||
if ($fieldName !== 'constructeurs') {
|
||||
return;
|
||||
}
|
||||
|
||||
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||
$after = $this->normalizeCollection($collection->toArray());
|
||||
|
||||
if ($before === $after) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diff = [
|
||||
'constructeurIds' => [
|
||||
'from' => $before,
|
||||
'to' => $after,
|
||||
],
|
||||
];
|
||||
|
||||
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
||||
$pendingSnapshots[$componentId] = $this->snapshotComposant($owner);
|
||||
$pendingComponents[$componentId] = $owner;
|
||||
}
|
||||
|
||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||
{
|
||||
$uow = $em->getUnitOfWork();
|
||||
$log->initializeAuditLog();
|
||||
$em->persist($log);
|
||||
|
||||
$meta = $em->getClassMetadata(AuditLog::class);
|
||||
$uow->computeChangeSet($meta, $log);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||
* @return array<string, array{from:mixed, to:mixed}>
|
||||
*/
|
||||
private function buildDiffFromChangeSet(array $changeSet): array
|
||||
{
|
||||
$diff = [];
|
||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalizedOld = $this->normalizeValue($oldValue);
|
||||
$normalizedNew = $this->normalizeValue($newValue);
|
||||
|
||||
if ($normalizedOld === $normalizedNew) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff[$field] = [
|
||||
'from' => $normalizedOld,
|
||||
'to' => $normalizedNew,
|
||||
];
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
private function snapshotComposant(Composant $component): array
|
||||
{
|
||||
return [
|
||||
'id' => $component->getId(),
|
||||
'name' => $component->getName(),
|
||||
'reference' => $component->getReference(),
|
||||
'prix' => $component->getPrix(),
|
||||
'structure' => $component->getStructure(),
|
||||
'typeComposant' => $this->normalizeValue($component->getTypeComposant()),
|
||||
'product' => $this->normalizeValue($component->getProduct()),
|
||||
'constructeurIds' => $this->normalizeCollection($component->getConstructeurs()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $items
|
||||
* @return list<string>
|
||||
*/
|
||||
private function normalizeCollection(iterable $items): array
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($items as $item) {
|
||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
||||
$id = $item->getId();
|
||||
if ($id !== null && $id !== '') {
|
||||
$ids[] = (string) $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort($ids);
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
private function normalizeValue(mixed $value): mixed
|
||||
{
|
||||
if ($value === null || \is_scalar($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value->format(\DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
if ($value instanceof ModelType) {
|
||||
return [
|
||||
'id' => $value->getId(),
|
||||
'name' => $value->getName(),
|
||||
'code' => $value->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($value instanceof Product) {
|
||||
return [
|
||||
'id' => $value->getId(),
|
||||
'name' => $value->getName(),
|
||||
'reference' => $value->getReference(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
return $this->normalizeCollection($value);
|
||||
}
|
||||
|
||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
||||
return (string) $value->getId();
|
||||
}
|
||||
|
||||
if (\is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||
* @return array<string, array{from:mixed, to:mixed}>
|
||||
*/
|
||||
private function mergeDiffs(array $base, array $extra): array
|
||||
{
|
||||
foreach ($extra as $field => $change) {
|
||||
$base[$field] = $change;
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
private function resolveActorProfileId(): ?string
|
||||
{
|
||||
$session = $this->requestStack->getSession();
|
||||
if (!$session instanceof SessionInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$profileId = $session->get('profileId');
|
||||
if (!$profileId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $profileId;
|
||||
}
|
||||
}
|
||||
|
||||
300
src/EventSubscriber/PieceAuditSubscriber.php
Normal file
300
src/EventSubscriber/PieceAuditSubscriber.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Entity\AuditLog;
|
||||
use App\Entity\ModelType;
|
||||
use App\Entity\Piece;
|
||||
use App\Entity\Product;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
#[AsDoctrineListener(event: Events::onFlush)]
|
||||
final class PieceAuditSubscriber implements EventSubscriber
|
||||
{
|
||||
public function __construct(private readonly RequestStack $requestStack)
|
||||
{
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
Events::onFlush,
|
||||
];
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $args): void
|
||||
{
|
||||
$em = $args->getObjectManager();
|
||||
if (!$em instanceof EntityManagerInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uow = $em->getUnitOfWork();
|
||||
$actorProfileId = $this->resolveActorProfileId();
|
||||
$pendingUpdates = [];
|
||||
$pendingSnapshots = [];
|
||||
$pendingPieces = [];
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||
if (!$entity instanceof Piece) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||
$snapshot = $this->snapshotPiece($entity);
|
||||
$this->persistAuditLog($em, new AuditLog('piece', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||
if (!$entity instanceof Piece) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pieceId = (string) $entity->getId();
|
||||
if ($pieceId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||
if ($diff !== []) {
|
||||
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
||||
$pendingSnapshots[$pieceId] = $this->snapshotPiece($entity);
|
||||
$pendingPieces[$pieceId] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||
if (!$entity instanceof Piece) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snapshot = $this->snapshotPiece($entity);
|
||||
$this->persistAuditLog($em, new AuditLog('piece', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionUpdates() as $collection) {
|
||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||
}
|
||||
foreach ($uow->getScheduledCollectionDeletions() as $collection) {
|
||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||
}
|
||||
|
||||
foreach ($pendingUpdates as $pieceId => $diff) {
|
||||
if ($diff === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$piece = $pendingPieces[$pieceId] ?? null;
|
||||
if (!$piece instanceof Piece) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snapshot = $pendingSnapshots[$pieceId] ?? $this->snapshotPiece($piece);
|
||||
$this->persistAuditLog($em, new AuditLog('piece', $pieceId, 'update', $diff, $snapshot, $actorProfileId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||
* @param array<string, Piece> $pendingPieces
|
||||
*/
|
||||
private function collectCollectionUpdate(
|
||||
object $collection,
|
||||
array &$pendingUpdates,
|
||||
array &$pendingSnapshots,
|
||||
array &$pendingPieces,
|
||||
): void {
|
||||
if (!$collection instanceof PersistentCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
$owner = $collection->getOwner();
|
||||
if (!$owner instanceof Piece) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pieceId = (string) $owner->getId();
|
||||
if ($pieceId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$mapping = $collection->getMapping();
|
||||
$fieldName = $mapping['fieldName'] ?? null;
|
||||
if ($fieldName !== 'constructeurs') {
|
||||
return;
|
||||
}
|
||||
|
||||
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||
$after = $this->normalizeCollection($collection->toArray());
|
||||
|
||||
if ($before === $after) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diff = [
|
||||
'constructeurIds' => [
|
||||
'from' => $before,
|
||||
'to' => $after,
|
||||
],
|
||||
];
|
||||
|
||||
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
||||
$pendingSnapshots[$pieceId] = $this->snapshotPiece($owner);
|
||||
$pendingPieces[$pieceId] = $owner;
|
||||
}
|
||||
|
||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||
{
|
||||
$uow = $em->getUnitOfWork();
|
||||
$log->initializeAuditLog();
|
||||
$em->persist($log);
|
||||
|
||||
$meta = $em->getClassMetadata(AuditLog::class);
|
||||
$uow->computeChangeSet($meta, $log);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||
* @return array<string, array{from:mixed, to:mixed}>
|
||||
*/
|
||||
private function buildDiffFromChangeSet(array $changeSet): array
|
||||
{
|
||||
$diff = [];
|
||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalizedOld = $this->normalizeValue($oldValue);
|
||||
$normalizedNew = $this->normalizeValue($newValue);
|
||||
|
||||
if ($normalizedOld === $normalizedNew) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff[$field] = [
|
||||
'from' => $normalizedOld,
|
||||
'to' => $normalizedNew,
|
||||
];
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
private function snapshotPiece(Piece $piece): array
|
||||
{
|
||||
return [
|
||||
'id' => $piece->getId(),
|
||||
'name' => $piece->getName(),
|
||||
'reference' => $piece->getReference(),
|
||||
'prix' => $piece->getPrix(),
|
||||
'typePiece' => $this->normalizeValue($piece->getTypePiece()),
|
||||
'product' => $this->normalizeValue($piece->getProduct()),
|
||||
'productIds' => $piece->getProductIds(),
|
||||
'constructeurIds' => $this->normalizeCollection($piece->getConstructeurs()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $items
|
||||
* @return list<string>
|
||||
*/
|
||||
private function normalizeCollection(iterable $items): array
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($items as $item) {
|
||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
||||
$id = $item->getId();
|
||||
if ($id !== null && $id !== '') {
|
||||
$ids[] = (string) $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort($ids);
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
private function normalizeValue(mixed $value): mixed
|
||||
{
|
||||
if ($value === null || \is_scalar($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value->format(\DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
if ($value instanceof ModelType) {
|
||||
return [
|
||||
'id' => $value->getId(),
|
||||
'name' => $value->getName(),
|
||||
'code' => $value->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($value instanceof Product) {
|
||||
return [
|
||||
'id' => $value->getId(),
|
||||
'name' => $value->getName(),
|
||||
'reference' => $value->getReference(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
return $this->normalizeCollection($value);
|
||||
}
|
||||
|
||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
||||
return (string) $value->getId();
|
||||
}
|
||||
|
||||
if (\is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||
* @return array<string, array{from:mixed, to:mixed}>
|
||||
*/
|
||||
private function mergeDiffs(array $base, array $extra): array
|
||||
{
|
||||
foreach ($extra as $field => $change) {
|
||||
$base[$field] = $change;
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
private function resolveActorProfileId(): ?string
|
||||
{
|
||||
$session = $this->requestStack->getSession();
|
||||
if (!$session instanceof SessionInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$profileId = $session->get('profileId');
|
||||
if (!$profileId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $profileId;
|
||||
}
|
||||
}
|
||||
|
||||
298
src/EventSubscriber/ProductAuditSubscriber.php
Normal file
298
src/EventSubscriber/ProductAuditSubscriber.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Entity\AuditLog;
|
||||
use App\Entity\ModelType;
|
||||
use App\Entity\Product;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* Record a lightweight, per-product audit trail.
|
||||
*
|
||||
* This MVP focuses on Product updates and captures:
|
||||
* - scalar field changes (from Doctrine change sets)
|
||||
* - constructeur collection changes (from collection updates)
|
||||
*/
|
||||
#[AsDoctrineListener(event: Events::onFlush)]
|
||||
final class ProductAuditSubscriber implements EventSubscriber
|
||||
{
|
||||
public function __construct(private readonly RequestStack $requestStack)
|
||||
{
|
||||
}
|
||||
|
||||
public function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
Events::onFlush,
|
||||
];
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $args): void
|
||||
{
|
||||
$em = $args->getObjectManager();
|
||||
if (!$em instanceof EntityManagerInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uow = $em->getUnitOfWork();
|
||||
$actorProfileId = $this->resolveActorProfileId();
|
||||
$pendingUpdates = [];
|
||||
$pendingSnapshots = [];
|
||||
$pendingProducts = [];
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||
if (!$entity instanceof Product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||
$snapshot = $this->snapshotProduct($entity);
|
||||
$this->persistAuditLog($em, new AuditLog('product', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||
if (!$entity instanceof Product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$productId = (string) $entity->getId();
|
||||
if ($productId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||
if ($diff !== []) {
|
||||
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
||||
$pendingSnapshots[$productId] = $this->snapshotProduct($entity);
|
||||
$pendingProducts[$productId] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||
if (!$entity instanceof Product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snapshot = $this->snapshotProduct($entity);
|
||||
$this->persistAuditLog($em, new AuditLog('product', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||
}
|
||||
|
||||
// Capture constructeur collection updates, which are not included in the change set.
|
||||
foreach ($uow->getScheduledCollectionUpdates() as $collection) {
|
||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||
}
|
||||
foreach ($uow->getScheduledCollectionDeletions() as $collection) {
|
||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||
}
|
||||
|
||||
foreach ($pendingUpdates as $productId => $diff) {
|
||||
if ($diff === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$product = $pendingProducts[$productId] ?? null;
|
||||
if (!$product instanceof Product) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snapshot = $pendingSnapshots[$productId] ?? $this->snapshotProduct($product);
|
||||
$this->persistAuditLog($em, new AuditLog('product', $productId, 'update', $diff, $snapshot, $actorProfileId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||
* @param array<string, Product> $pendingProducts
|
||||
*/
|
||||
private function collectCollectionUpdate(
|
||||
object $collection,
|
||||
array &$pendingUpdates,
|
||||
array &$pendingSnapshots,
|
||||
array &$pendingProducts,
|
||||
): void {
|
||||
if (!$collection instanceof PersistentCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
$owner = $collection->getOwner();
|
||||
if (!$owner instanceof Product) {
|
||||
return;
|
||||
}
|
||||
|
||||
$productId = (string) $owner->getId();
|
||||
if ($productId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$mapping = $collection->getMapping();
|
||||
$fieldName = $mapping['fieldName'] ?? null;
|
||||
if ($fieldName !== 'constructeurs') {
|
||||
return;
|
||||
}
|
||||
|
||||
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||
$after = $this->normalizeCollection($collection->toArray());
|
||||
|
||||
if ($before === $after) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diff = [
|
||||
'constructeurIds' => [
|
||||
'from' => $before,
|
||||
'to' => $after,
|
||||
],
|
||||
];
|
||||
|
||||
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
||||
$pendingSnapshots[$productId] = $this->snapshotProduct($owner);
|
||||
$pendingProducts[$productId] = $owner;
|
||||
}
|
||||
|
||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||
{
|
||||
$uow = $em->getUnitOfWork();
|
||||
// Ensure identifiers and timestamps are set even when persisting during onFlush.
|
||||
$log->initializeAuditLog();
|
||||
$em->persist($log);
|
||||
|
||||
$meta = $em->getClassMetadata(AuditLog::class);
|
||||
$uow->computeChangeSet($meta, $log);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||
* @return array<string, array{from:mixed, to:mixed}>
|
||||
*/
|
||||
private function buildDiffFromChangeSet(array $changeSet): array
|
||||
{
|
||||
$diff = [];
|
||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||
// Skip noisy timestamps managed automatically.
|
||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalizedOld = $this->normalizeValue($oldValue);
|
||||
$normalizedNew = $this->normalizeValue($newValue);
|
||||
|
||||
if ($normalizedOld === $normalizedNew) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff[$field] = [
|
||||
'from' => $normalizedOld,
|
||||
'to' => $normalizedNew,
|
||||
];
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
private function snapshotProduct(Product $product): array
|
||||
{
|
||||
return [
|
||||
'id' => $product->getId(),
|
||||
'name' => $product->getName(),
|
||||
'reference' => $product->getReference(),
|
||||
'supplierPrice' => $product->getSupplierPrice(),
|
||||
'typeProduct' => $this->normalizeValue($product->getTypeProduct()),
|
||||
'constructeurIds' => $this->normalizeCollection($product->getConstructeurs()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||
* @return array<string, array{from:mixed, to:mixed}>
|
||||
*/
|
||||
private function mergeDiffs(array $base, array $extra): array
|
||||
{
|
||||
foreach ($extra as $field => $change) {
|
||||
$base[$field] = $change;
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $items
|
||||
* @return list<string>
|
||||
*/
|
||||
private function normalizeCollection(iterable $items): array
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($items as $item) {
|
||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
||||
$id = $item->getId();
|
||||
if ($id !== null && $id !== '') {
|
||||
$ids[] = (string) $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort($ids);
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
private function normalizeValue(mixed $value): mixed
|
||||
{
|
||||
if ($value === null || \is_scalar($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value->format(\DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
if ($value instanceof ModelType) {
|
||||
return [
|
||||
'id' => $value->getId(),
|
||||
'name' => $value->getName(),
|
||||
'code' => $value->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
return $this->normalizeCollection($value);
|
||||
}
|
||||
|
||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
||||
return (string) $value->getId();
|
||||
}
|
||||
|
||||
if (\is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
private function resolveActorProfileId(): ?string
|
||||
{
|
||||
$session = $this->requestStack->getSession();
|
||||
if (!$session instanceof SessionInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$profileId = $session->get('profileId');
|
||||
if (!$profileId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) $profileId;
|
||||
}
|
||||
}
|
||||
37
src/Repository/AuditLogRepository.php
Normal file
37
src/Repository/AuditLogRepository.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\AuditLog;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<AuditLog>
|
||||
*/
|
||||
final class AuditLogRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, AuditLog::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<AuditLog>
|
||||
*/
|
||||
public function findEntityHistory(string $entityType, string $entityId, int $limit = 100): array
|
||||
{
|
||||
return $this->createQueryBuilder('a')
|
||||
->andWhere('a.entityType = :entityType')
|
||||
->andWhere('a.entityId = :entityId')
|
||||
->setParameter('entityType', $entityType)
|
||||
->setParameter('entityId', $entityId)
|
||||
->orderBy('a.createdAt', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user