diff --git a/CLAUDE.md b/CLAUDE.md index 824d308..b7b3c0d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,9 +1,7 @@ # CLAUDE.md — Inventory Project -## Project Overview - Application de gestion d'inventaire industriel (machines, pièces, composants, produits). -Mono-repo : backend Symfony et frontend Nuxt (`frontend/`) dans le **même dépôt git** (plus de submodule). Un seul commit/push couvre backend + frontend. +**Monorepo** : backend Symfony + frontend Nuxt (`frontend/`) dans le **même dépôt git** (plus de submodule). Un seul commit/push couvre backend + frontend. ## Stack @@ -15,267 +13,134 @@ Mono-repo : backend Symfony et frontend Nuxt (`frontend/`) dans le **même dép | Frontend | Nuxt (SPA, SSR off) | 4 | | UI | Vue 3 Composition API + TypeScript | 3.5 / 5.7 | | CSS | TailwindCSS 4 + DaisyUI 5 | | -| Auth | Session-based (cookies, pas JWT) | | +| Auth | Session-based (cookies, **pas JWT**) | | | Containers | Docker Compose | | -## Glossaire Métier -Voir `docs/GLOSSAIRE_METIER.md` — glossaire complet du domaine métier (concepts, workflows utilisateur, correspondance métier↔code). À consulter pour comprendre le "pourquoi" derrière le code. +## Documentation détaillée (lire à la demande, ne pas dupliquer ici) + +Vu la complexité du projet, le détail vit dans `docs/` — y aller plutôt que de deviner : + +- **`docs/FONCTIONNEMENT.md`** — le métier : à quoi sert l'app, entités, ModelType/skeleton, cycle de vie, rôles, fonctionnalités clés. +- **`docs/GLOSSAIRE_METIER.md`** — glossaire complet, correspondance métier ↔ code (le « pourquoi »). +- **`docs/BACKEND.md`** — catalogue backend complet : toutes les entités, **tous les controllers** (routes), audit, services, migrations, auth, rôles. +- **`docs/FRONTEND.md`** — catalogue frontend : composables, composants, useApi, IRIs, content-types, auth, style. +- **`docs/REVIEW_ARCHITECTURE.md`** — top 10 des sources de complexité et effets de bord (God controllers, canaux cachés, doubles flush…). **À consulter avant tout refacto.** ## Project Structure ``` Inventory/ # Backend Symfony (repo principal) -├── src/Entity/ # Entités Doctrine (annotations PHP 8 attributes) -│ └── Trait/ # CuidEntityTrait (génération d'ID CUID) -├── src/Controller/ # Controllers custom (session, comments, audit…) -├── src/EventSubscriber/ # Audit subscribers (onFlush) -├── src/Service/ # Services métier (sync, conversion, storage…) -├── src/Enum/ # Enums PHP (DocumentType, ModelCategory) -├── src/DTO/ # Data Transfer Objects (sync workflow) -├── src/Filter/ # Filtres API Platform custom -├── src/Command/ # Commandes Symfony CLI (compress-pdf, create-profile…) -├── config/ # Config Symfony -├── migrations/ # Migrations Doctrine (raw SQL PostgreSQL) -├── docker/ # Dockerfile + .env.docker -├── scripts/ # release.sh, normalize-dump.py -├── fixtures/ # SQL fixtures -├── tests/ # PHPUnit -├── pre-commit, commit-msg # Git hooks -├── makefile # Commandes Docker/dev -├── VERSION # Source unique de version (semver) -├── frontend/ # ← Frontend Nuxt (DANS le même repo, pas un submodule) -│ ├── app/pages/ # Pages Nuxt (file-based routing) -│ ├── app/components/ # Composants Vue (auto-imported) -│ ├── app/composables/ # Composables Vue -│ ├── app/shared/ # Types, utils, validation -│ ├── app/middleware/ # Auth middleware global -│ └── app/services/ # Service layer (wrappers useApi) +├── src/Entity/ (+ Trait/) # Entités Doctrine (attributs PHP 8), CuidEntityTrait +├── src/Controller/ # Controllers custom (session, comments, audit, structure…) +├── src/EventSubscriber/ # Audit (onFlush) + sync/contraintes +├── src/Service/ (+ Sync/) # Services métier (sync, conversion, storage, versions…) +├── src/Enum/ src/DTO/ src/Filter/ src/Command/ +├── config/ migrations/ docker/ scripts/ fixtures/ tests/ +├── makefile VERSION # VERSION = source unique de version (semver) +└── frontend/ # ← Frontend Nuxt (MÊME repo, pas un submodule) + └── app/{pages,components,composables,shared,middleware,services}/ ``` ## Key Commands ```bash # Docker -make start # Démarrer les containers -make stop # Arrêter -make shell # Shell interactif (nécessite un TTY) -make install # Install complet (composer + npm + build) +make start / make stop # Démarrer / arrêter les containers +make shell # Shell interactif (nécessite un TTY) +make install # Install complet (composer + npm + build) # Backend -make test # PHPUnit (tous les tests) -make test FILES=tests/Api/Entity/MachineTest.php # Un test spécifique -make php-cs-fixer-allow-risky # Linter PHP (cs-fixer) +make test # PHPUnit (tous) +make test FILES=tests/Api/Entity/MachineTest.php # Un test +make test-setup # Créer/MAJ le schéma de test +make php-cs-fixer-allow-risky # Linter PHP docker exec -u www-data php-inventory-apache php bin/console doctrine:migrations:migrate # Frontend (dans frontend/) -npm run dev # Dev server (port 3001) -npm run build # Build production -npm run lint:fix # ESLint fix -npx nuxi typecheck # TypeScript check (0 errors attendu) +npm run dev # Dev server (port 3001) +npm run lint:fix # ESLint fix +npx nuxi typecheck # TypeScript check (0 erreur attendu) # Database / Fixtures -make db-reset # Reset database (drop + recreate schema) -make fixtures-dump # Dump la DB vers fixtures/data.sql -make fixtures-load # Charger les fixtures SQL (désactive FK) -make fixtures-reset # Reset DB + recharger fixtures -make import-data # Importer les dumps SQL normalisés -make cache-clear # Clear cache Symfony +make db-reset # Reset DB (drop + recreate schema) +make fixtures-reset # Reset DB + recharger fixtures SQL +make import-data # Importer les dumps SQL normalisés +make cache-clear -# Import fournisseurs (customer.json → Constructeur + ConstructeurCategorie + ConstructeurTelephone) -docker exec -u www-data php-inventory-apache php bin/console app:import-fournisseurs # dry-run (par défaut) -docker exec -u www-data php-inventory-apache php bin/console app:import-fournisseurs --force # applique -# Non destructif : find-or-create par nom normalisé, ne change jamais un ID existant, n'ajoute que les téléphones/catégories manquants +# Import fournisseurs (non destructif : find-or-create par nom normalisé) +docker exec -u www-data php-inventory-apache php bin/console app:import-fournisseurs # dry-run +docker exec -u www-data php-inventory-apache php bin/console app:import-fournisseurs --force # applique # Release -./scripts/release.sh patch # Bump patch version (ou minor/major) +./scripts/release.sh patch # Bump version (patch/minor/major) ``` ## Git Conventions -### Branches -- `master` — production -- `develop` — branche principale de dev (cible des PR) -- `feat/xxx`, `fix/xxx`, `refactor/xxx` — branches de travail +- **Branches** : `master` (prod), `develop` (cible des PR), `feat/* fix/* refactor/*`. +- **Commit** (enforced par hook) : `() : ` — **espace obligatoire autour du `:`**. Types : `build chore ci docs feat fix perf refactor revert style test wip`. + - Ex : `feat(auth) : add login page`, `fix(machines) : prevent null crash` +- **Pre-commit hook** : php-cs-fixer + PHPUnit (bloque si échec). +- **Workflow commit** : backend + frontend = **un seul commit/push** depuis la racine (pas de submodule). Le hook étant lent, committer avec `git commit --no-verify`. Push rejeté → `git pull --rebase` puis `git push`. +- **Sync master ↔ develop** : `git checkout master && git merge develop && git push` puis revenir sur `develop`. -### Commit Message Format (enforced by hook) -``` -() : -``` -**Espace obligatoire autour du `:`**. Types autorisés (minuscules) : -`build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`, `wip` +## Pièges & patterns non-évidents -Exemples : -- `feat(auth) : add login page` -- `fix(machines) : prevent null crash on skeleton creation` +> Le catalogue complet est dans `docs/BACKEND.md` / `docs/FRONTEND.md`. Ci-dessous **uniquement** ce qui n'est pas évident en lisant le code. -### Pre-commit Hook -1. php-cs-fixer sur les fichiers PHP stagés -2. PHPUnit — bloque le commit si tests échouent - -### Workflow commit (backend + frontend dans le même repo) -Le frontend n'est **pas** un submodule : `frontend/` est versionné dans le dépôt principal. Un changement backend et/ou frontend se commite et se push en **une seule fois** depuis la racine `Inventory/`. Pas de double commit ni de pointeur de submodule à gérer. -- Commit avec `git commit --no-verify` (le pre-commit hook php-cs-fixer + PHPUnit est trop lent). -- Si le push est rejeté (distant en avance), faire `git pull --rebase` puis `git push`. - -## Architecture Backend - -### Entités Principales -`Machine`, `Piece`, `Composant`, `Product`, `Constructeur`, `ConstructeurCategorie`, `ConstructeurTelephone`, `Site`, `ModelType`, `CustomField`, `CustomFieldValue`, `Document`, `AuditLog`, `Comment`, `Profile`, `MachineComponentLink`, `MachinePieceLink`, `MachineProductLink` - -> **Constructeur (Fournisseur)** : possède `name`, `email`, une collection `telephones` (1-N → `ConstructeurTelephone`, cascade/orphanRemoval) et `categories` (M2M → `ConstructeurCategorie`, table `constructeur_categories`). Sérialisation API Platform via les groupes `constructeur:read` / `constructeur:write` (téléphones & catégories embarqués). ⚠️ L'adder M2M s'appelle `addCategory()`/`removeCategory()` (l'inflector singularise `categories` → `category`), pas `addCategorie`. `ConstructeurCategorie` et `ConstructeurTelephone` sont aussi des `ApiResource` à part entière (`/api/constructeur_categories`, `/api/constructeur_telephones`). - -#### Entités de normalisation (slots & skeleton requirements) -Remplacent les anciennes colonnes JSON `structure` et `productIds` par des tables relationnelles : -- **Slots composant** (données réelles d'un composant) : `ComposantPieceSlot`, `ComposantSubcomponentSlot`, `ComposantProductSlot` -- **Slots pièce** (données réelles d'une pièce) : `PieceProductSlot` -- **Skeleton Requirements** (définitions du ModelType) : `SkeletonPieceRequirement`, `SkeletonProductRequirement`, `SkeletonSubcomponentRequirement` - -### Patterns -- **IDs** : CUID-like strings (`'cl' + bin2hex(random_bytes(12))`), pas d'auto-increment -- **ORM** : Attributs PHP 8 (`#[ORM\Column(...)]`, `#[Groups([...])]`) -- **Lifecycle** : `#[ORM\HasLifecycleCallbacks]` avec `PrePersist`/`PreUpdate` pour `createdAt`/`updatedAt` -- **Sécurité** : `security: "is_granted('ROLE_...')"` sur chaque opération API Platform -- **Audit** : Subscribers Doctrine `onFlush` capturent diff + snapshot complet -- **Migrations** : Raw SQL PostgreSQL avec `IF NOT EXISTS`/`IF EXISTS` pour idempotence - -### Custom Controllers (pas API Platform) -- `MachineStructureController` — `/api/machines/{id}/structure` (GET/PATCH), `/api/machines/{id}/clone` (POST) : hiérarchie complète machine avec normalisation JSON manuelle. Source principale de données pour la page détail machine. -- `MachineCustomFieldsController` — `/api/machines/{id}/add-custom-fields` (POST) : initialise les CustomFieldValue manquants pour une machine. -- `CustomFieldValueController` — `/api/custom-fields/values/*` : CRUD + upsert pour les valeurs de champs perso. -- `ComposantPieceSlotController` — `/api/composant-piece-slots/{id}` (PATCH) : mise à jour des slots pièce d'un composant. -- `ComposantProductSlotController` — `/api/composant-product-slots/{id}` (PATCH) : mise à jour des slots produit d'un composant. -- `ComposantSubcomponentSlotController` — `/api/composant-subcomponent-slots/{id}` (PATCH) : mise à jour des slots sous-composant d'un composant. -- `SessionProfileController` — `/api/session/profile` (GET/POST/DELETE) : auth session (login/logout/current user). -- `SessionProfilesController` — `/api/session/profiles` (GET) : liste des profils disponibles pour la session. -- `AdminProfileController` — `/api/admin/profiles` : CRUD profils, gestion rôles et mots de passe (ROLE_ADMIN). -- `CommentController` — `/api/comments` : création, résolution, compteur non-résolus. -- `ActivityLogController` — `/api/activity-logs` (GET) : journal d'activité global. -- `EntityHistoryController` — `/api/{entity}/{id}/history` (GET) : historique audit par entité (machines, pièces, composants, produits). -- `DocumentQueryController` — `/api/documents/{entity}/{id}` (GET) : documents par site/machine/composant/pièce/produit. -- `DocumentServeController` — `/api/documents/{id}/file|download` (GET) : servir/télécharger fichiers. -- `ModelTypeConversionController` — `/api/model_types/{id}/conversion-check|convert` : vérification et conversion de ModelType. -- `ModelTypeSyncController` — `/api/model_types/{id}/sync-preview|sync-confirm` (POST) : prévisualisation et application de sync ModelType→Composants. -- `EntityVersionController` — `/api/{entity}/{id}/versions` (GET), `/api/{entity}/{id}/versions/{version}/restore` (POST) : historique de versions numérotées et restauration. -- `HealthCheckController` — `/api/health` (GET) : health check. - -### Custom Fields — Architecture -- **Composants/Pièces/Produits** : définitions dans les entités `SkeletonPieceRequirement`, `SkeletonProductRequirement`, `SkeletonSubcomponentRequirement` du ModelType (anciennement JSON `structure`, normalisé en tables relationnelles). Les custom fields de ces entités sont définis dans `customFields` JSON sur chaque Skeleton*Requirement. -- **Machines** : définitions = entités `CustomField` liées directement via `machineId` FK (pas de ModelType) -- Les deux partagent la même entité `CustomFieldValue` pour stocker les valeurs - -### Enums (`src/Enum/`) -- `DocumentType` — types de documents (photo, schéma, facture, etc.) -- `ModelCategory` — catégories de ModelType - -### Services (`src/Service/`) -- `ModelTypeSyncService` — synchronise les skeleton requirements d'un ModelType vers les composants existants -- `ModelTypeCategoryConversionService` — conversion de catégorie d'un ModelType -- `SkeletonStructureService` — gestion de la structure skeleton (requirements) -- `DocumentStorageService` — stockage et gestion des fichiers documents -- `PdfCompressorService` — compression des PDFs uploadés -- `EntityVersionService` — gestion des versions numérotées (snapshot, restore) pour machines, pièces, composants, produits -- `ReferenceAutoGenerator` — génération automatique de références pour pièces et composants à partir de formules ModelType -- `src/Service/Sync/` — stratégies de sync par type de slot (tagged `app.sync_strategy`) - -### DTOs (`src/DTO/`) -- `SyncConfirmation`, `SyncPreviewResult`, `SyncExecutionResult` — objets de transfert pour le workflow de sync ModelType - -### Filters (`src/Filter/`) -- `MultiSearchFilter` — filtre API Platform pour recherche OR sur plusieurs champs (ex: name + reference) - -### EventSubscribers notables (non-audit) -- `PieceProductSyncSubscriber` — sync automatique des PieceProductSlots -- `UniqueConstraintSubscriber` — traduit les erreurs de contrainte unique PG en messages utilisateur lisibles -- `ReferenceAutoSubscriber` — recalcule les références auto des pièces/composants quand les CustomFieldValues changent (onFlush) - -### Rôles (hiérarchie) -``` -ROLE_ADMIN → ROLE_GESTIONNAIRE → ROLE_VIEWER → ROLE_USER -``` +### Backend +- **IDs CUID** : strings `'cl' + bin2hex(random_bytes(12))`, **pas** d'auto-increment. +- **Lifecycle** : `#[ORM\HasLifecycleCallbacks]` + `PrePersist`/`PreUpdate` pour `createdAt`/`updatedAt`. +- **Sécurité** : `security: "is_granted('ROLE_...')"` sur chaque opération API Platform. Hiérarchie : `ROLE_ADMIN → ROLE_GESTIONNAIRE → ROLE_VIEWER → ROLE_USER`. +- **Audit** : subscribers Doctrine `onFlush` (diff + snapshot complet). +- **Migrations** : raw SQL PostgreSQL avec `IF NOT EXISTS`/`IF EXISTS` (idempotence). +- **Constructeur (Fournisseur)** : collection `telephones` (1-N, cascade/orphanRemoval) + `categories` (M2M, table `constructeur_categories`). ⚠️ L'adder M2M est `addCategory()`/`removeCategory()` (l'inflector singularise `categories` → `category`), **pas** `addCategorie`. Groupes API `constructeur:read` / `constructeur:write`. +- **Normalisation slots/skeleton** : les anciennes colonnes JSON `structure`/`productIds` sont remplacées par des tables relationnelles — slots réels (`ComposantPieceSlot`, `ComposantSubcomponentSlot`, `ComposantProductSlot`, `PieceProductSlot`) vs définitions ModelType (`SkeletonPieceRequirement`, `SkeletonProductRequirement`, `SkeletonSubcomponentRequirement`). +- **Custom Fields** : Composants/Pièces/Produits → définitions dans les `Skeleton*Requirement` du ModelType (clé `customFields` JSON) ; Machines → entités `CustomField` liées par `machineId` FK (pas de ModelType). Les deux partagent l'entité `CustomFieldValue` pour les valeurs. +- **`MachineStructureController`** (`/api/machines/{id}/structure`, `/clone`) : source principale de données de la page détail machine (normalisation JSON manuelle). Cf. `REVIEW_ARCHITECTURE.md` (God controller). ### PostgreSQL — ATTENTION -- Les noms de colonnes sont **TOUJOURS EN MINUSCULES** dans PG -- Doctrine utilise camelCase (`typePieceId`) mais PG stocke `typepieceid` -- Le SQL brut doit utiliser les noms lowercase -- Tables de jointure many-to-many : colonnes `a` et `b` (ex: `_piececonstructeurs`) +- Noms de colonnes **TOUJOURS EN MINUSCULES** en PG. Doctrine camelCase (`typePieceId`) → PG `typepieceid`. Le **SQL brut doit être lowercase**. +- Tables de jointure M2M : colonnes `a` et `b` (ex : `_piececonstructeurs`). -## Architecture Frontend - -### Patterns -- **Composables** : `interface Deps { ... }` + `export function useXxx(deps: Deps)` -- **Communication composants** : Props + Events uniquement (pas de provide/inject) -- **API** : `useApi.ts` wraps fetch avec `credentials: 'include'` pour les cookies session -- **⚠️ Préfixe `/api`** : `useApi()` **prepend déjà** `apiBaseUrl` (= `/api` par défaut, cf. `nuxt.config.ts`). Les appels doivent donc utiliser des chemins **sans** `/api` au début. Ex : `api.get('/custom-fields/names')` et **PAS** `api.get('/api/custom-fields/names')` (sinon 404 sur `/api/api/...`). -- **Content-Type** : `application/ld+json` pour POST/PUT, `application/merge-patch+json` pour PATCH -- **Auth** : `useProfileSession` + middleware global `profile.global.ts` -- **Permissions** : `usePermissions.ts` miroir de la hiérarchie backend côté client -- **Auto-imports** : Nuxt auto-importe composants (`components/`) et composables (`composables/`) - -### DaisyUI Classes -- Input : `input input-bordered input-sm md:input-md` -- Textarea : `textarea textarea-bordered textarea-sm md:textarea-md` -- Select : `select select-bordered select-sm md:select-md` -- Button : `btn btn-sm md:btn-md btn-primary` +### Frontend +- **Composables** : `interface Deps { ... }` + `export function useXxx(deps: Deps)`. +- **Communication composants** : Props + Events uniquement (**pas** de provide/inject). +- **API** : `useApi.ts` wrappe fetch avec `credentials: 'include'`. ⚠️ `useApi()` **préfixe déjà** `/api` → appeler **sans** `/api` au début. Ex : `api.get('/custom-fields/names')` **et PAS** `'/api/custom-fields/names'` (sinon 404 sur `/api/api/...`). +- **Content-Type** : `application/ld+json` (POST/PUT), `application/merge-patch+json` (PATCH). +- **Auth** : `useProfileSession` + middleware global `profile.global.ts`. Permissions : `usePermissions.ts` (miroir de la hiérarchie backend). +- **Classes DaisyUI** : `input input-bordered input-sm md:input-md` (idem textarea/select/btn, `btn-primary`). ## Règles Importantes -### CLAUDE.md — Maintenance obligatoire -- **Toujours consulter** ce fichier en début de conversation pour respecter les conventions -- **Mettre à jour** ce fichier quand une nouvelle convention, pattern ou décision architecturale est établie -- **Utiliser comme source de vérité** pour les commandes, patterns et règles du projet - -### Toujours faire AVANT de modifier du code -1. **Lire le fichier** avant de l'éditer — ne jamais proposer de changements sur du code non lu -2. **Comprendre le pattern existant** — reproduire le style du fichier (noms, indentation, structure) -3. **Vérifier backend ET frontend** — un changement peut impacter les deux (même repo) +### Avant de modifier du code +1. **Lire le fichier** avant de l'éditer. +2. **Reproduire le pattern existant** (noms, indentation, structure). +3. **Vérifier backend ET frontend** — un changement peut impacter les deux (même repo). ### Après chaque modification 1. Backend PHP : `make php-cs-fixer-allow-risky` -2. Frontend : `npm run lint:fix` puis `npx nuxi typecheck` si fichiers TS modifiés +2. Frontend TS : `npm run lint:fix` puis `npx nuxi typecheck` ### Ne jamais faire -- Ajouter des features non demandées, du code mort, ou des abstractions prématurées -- Utiliser `provide/inject` — le codebase utilise Props + Events -- Utiliser JWT/tokens — l'auth est session-based -- Écrire du SQL avec des noms camelCase — PostgreSQL = lowercase -- Committer sans que l'utilisateur le demande explicitement -- Force push sans confirmation explicite -- Modifier la config git +- Features non demandées, code mort, abstractions prématurées +- `provide/inject` (le code utilise Props + Events) · JWT/tokens (auth session-based) +- SQL en camelCase (PG = lowercase) +- Committer sans demande explicite · force push sans confirmation · modifier la config git -### Synchronisation master ↔ develop -Un seul repo (backend + frontend). Quand `master` et `develop` divergent : -`git checkout master && git merge develop && git push` (puis revenir sur `develop`). +### Maintenir ce fichier +Mettre à jour quand une nouvelle convention/pattern/décision archi est établie. Source de vérité pour commandes, pièges et règles ; le **détail** descriptif va dans `docs/`. ## Tests -### Stack de test -- **PHPUnit 12** + **API Platform Test** (`ApiTestCase`) -- **DAMA DoctrineTestBundle** — wrappe chaque test dans une transaction avec rollback automatique (pas de TRUNCATE) -- Base de test : même PG, env `test` - -### Commandes -Voir section "Key Commands". Commande additionnelle : -```bash -make test-setup # Créer/mettre à jour le schéma test -``` - -### Pattern de test -- Hériter de `AbstractApiTestCase` (helpers auth + factories) -- Ne PAS faire de TRUNCATE/cleanup dans tearDown — DAMA s'en occupe par rollback -- Factories : `createProfile()`, `createMachine()`, `createSite()`, `createComposant()`, `createPiece()`, `createProduct()`, `createConstructeur()`, `createCustomField()`, `createCustomFieldValue()`, `createModelType()`, `createMachineComponentLink()`, `createMachinePieceLink()`, `createMachineProductLink()`, `createComposantPieceSlot()`, `createComposantSubcomponentSlot()`, `createComposantProductSlot()`, `createPieceProductSlot()`, `createConstructeurCategorie()`, `createConstructeurTelephone()` -- Auth : `createViewerClient()`, `createGestionnaireClient()`, `createAdminClient()`, `createUnauthenticatedClient()` +- **PHPUnit 12** + **API Platform Test** (`ApiTestCase`), env `test`, même PG. +- **DAMA DoctrineTestBundle** : chaque test wrappé en transaction + rollback auto → **ne PAS** faire de TRUNCATE/cleanup en `tearDown`. +- Hériter de `AbstractApiTestCase` (helpers auth + factories `create*()`). +- Auth : `createViewerClient()`, `createGestionnaireClient()`, `createAdminClient()`, `createUnauthenticatedClient()`. ## URLs Locales -- API Symfony : `http://localhost:8081/api` -- Nuxt dev : `http://localhost:3001` -- Adminer (PG) : `http://localhost:5050` -- PG direct : `localhost:5433` (user: root, pass: root, db: inventory) +- API Symfony : `http://localhost:8081/api` · Nuxt dev : `http://localhost:3001` +- Adminer : `http://localhost:5050` · PG direct : `localhost:5433` (user/pass `root`, db `inventory`) -## Delegation Codex - -Pour les taches mecaniques (tests, boilerplate, renommages, refacto repetitif), delegue a Codex via le plugin `codex`. Garde Claude pour la reflexion, l'architecture et la verification. - -- **Codex** = junior dev rapide et pas cher (executions mecaniques) -- **Claude** = senior dev qui verifie et reflechit (design, review, decisions) - -C'est le meilleur ratio qualite/credits. +## Délégation Codex +Pour les tâches mécaniques (tests, boilerplate, renommages, refacto répétitif), déléguer à Codex via le plugin `codex` (junior rapide/pas cher). Garder Claude pour la réflexion, l'architecture et la vérification (senior). Meilleur ratio qualité/crédits.