# 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.

## Stack

| Layer | Tech | Version |
|-------|------|---------|
| Backend | Symfony + API Platform | 8.0 / ^4.2 |
| PHP | PHP | >=8.4 |
| Database | PostgreSQL | 16 |
| 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) | |
| 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.

## 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)
```

## 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)

# 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)
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)

# 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

# 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

# Release
./scripts/release.sh patch    # Bump patch version (ou minor/major)
```

## Git Conventions

### Branches
- `master` — production
- `develop` — branche principale de dev (cible des PR)
- `feat/xxx`, `fix/xxx`, `refactor/xxx` — branches de travail

### Commit Message Format (enforced by hook)
```
<type>(<scope optionnel>) : <message>
```
**Espace obligatoire autour du `:`**. Types autorisés (minuscules) :
`build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`, `wip`

Exemples :
- `feat(auth) : add login page`
- `fix(machines) : prevent null crash on skeleton creation`

### 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
```

### 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`)

## 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`

## 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)

### 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

### 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

### 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`).

## 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()`

## 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)

## 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.
