feat : mise à jour de la structure du projet

This commit is contained in:
2026-04-09 11:02:19 +02:00
parent bcfecb2281
commit 68d62c31ec
69 changed files with 4782 additions and 408 deletions

255
CLAUDE.md
View File

@@ -1,69 +1,154 @@
# Coltura
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. **Architecture DDD (Domain-Driven Design).**
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. **Architecture Modular Monolith DDD.**
## Architecture DDD
## Architecture Modulaire
Le projet suit une architecture DDD cote backend ET frontend. Le code est organise par **domaine metier** (Bounded Context), pas par type technique.
Le projet suit une architecture **modular monolith** pilotee par le backend : chaque module metier est un bounded context autonome, activable/desactivable par tenant. Le module `Core` est obligatoire.
### Backend — Organisation par domaine
**Principe fondamental : le backend est la source de verite unique.**
- Le backend dicte quels modules sont actifs (`config/modules.php`).
- Le backend dicte l'organisation de la sidebar (`config/sidebar.php`), decouplee des modules eux-memes.
- Le frontend ne connait rien : il scanne automatiquement les modules comme layers Nuxt et demande la sidebar au backend.
### Backend — Organisation par module
```
src/
Domain/ # Couche domaine (logique metier pure, aucune dependance framework)
{BoundedContext}/ # Ex: Customer, Sales, Catalog, Invoice...
Entity/ # Entites et Aggregates du domaine
ValueObject/ # Value Objects (Money, Address, Email...)
Repository/ # Interfaces des repositories (ports)
Service/ # Services domaine (logique metier)
Event/ # Domain Events
Exception/ # Exceptions metier
Application/ # Couche application (cas d'usage, orchestration)
{BoundedContext}/
Command/ # Commands (write) + Handlers
Query/ # Queries (read) + Handlers
DTO/ # Data Transfer Objects
Infrastructure/ # Couche infrastructure (implementations techniques)
{BoundedContext}/
Repository/ # Implementations Doctrine des repositories
Persistence/ # Mapping Doctrine (si XML/YAML)
Shared/ # Services techniques partages (mail, storage, etc.)
Api/ # Couche API (exposition HTTP)
{BoundedContext}/
Resource/ # ApiResource API Platform
State/ # Providers & Processors API Platform
Kernel.php
Shared/ # Noyau technique partage
Domain/
ValueObject/ # VO de base (Email...)
Event/ # DomainEventInterface
Contract/ # Interfaces inter-modules (UserResolverInterface, TenantAwareInterface)
Application/
Bus/ # CommandBusInterface, QueryBusInterface (interfaces seules)
Infrastructure/
ApiPlatform/
Resource/ # AppVersion, ModulesResource, SidebarResource
State/ # AppVersionProvider, ModulesProvider, SidebarProvider
Module/
Core/ # Module obligatoire (auth, users)
CoreModule.php # Declaration (ID, LABEL, REQUIRED)
Domain/
Entity/ # Entites Doctrine + API Platform (User)
Repository/ # Interfaces repositories (UserRepositoryInterface)
Event/ # Domain events (UserCreated)
Application/
DTO/ # UserOutput
Infrastructure/
Doctrine/ # DoctrineUserRepository, Migrations/
ApiPlatform/
State/
Provider/ # MeProvider
Processor/ # UserPasswordHasherProcessor
Console/ # CreateUserCommand
DataFixtures/ # AppFixtures
Commercial/ # Autre module (exemple)
CommercialModule.php
config/
modules.php # Liste des modules actifs (source de verite activation)
sidebar.php # Structure de la sidebar (source de verite navigation)
version.yaml
jwt/ # Cles JWT
packages/ # Config Symfony
migrations/ # Anciennes migrations Doctrine
infra/dev/ # Docker dev
infra/prod/ # Docker prod (multi-stage)
```
**Regles DDD backend :**
- Le domaine (`Domain/`) ne depend de RIEN (pas de Doctrine, pas de Symfony, pas d'API Platform)
- Les repositories dans `Domain/` sont des **interfaces** ; les implementations Doctrine sont dans `Infrastructure/`
- Les entites API Platform (`Api/Resource/`) sont decouples des entites domaine si necessaire
- Chaque Bounded Context est autonome — pas d'import croise entre contextes (communiquer via events ou services application)
- `User` et `Auth` restent dans `src/` (hors DDD) car c'est du framework pur (Security Bundle)
### Frontend — Organisation par domaine
### Frontend — Organisation modulaire (auto-detectee)
```
frontend/
domains/ # Modules metier
{bounded-context}/ # Ex: customer, sales, catalog, invoice...
components/ # Composants Vue specifiques au domaine
composables/ # Composables specifiques au domaine
services/ # Services API du domaine
dto/ # Types TypeScript du domaine
pages/ # Pages du domaine (optionnel, ou dans pages/)
stores/ # Store Pinia du domaine (si necessaire)
components/ # Composants UI partages (non lies a un domaine)
composables/ # Composables partages (useApi, useAppVersion)
stores/ # Stores globaux (auth, ui)
services/ # Services partages
app/ # Shell applicatif
layouts/ # default.vue, auth.vue
middleware/ # auth.global.ts, modules.global.ts
shared/ # Code partage (hors modules)
composables/ # useApi, useAppVersion, useSidebar
components/ui/ # AppTopNav, ...
stores/ # auth, ui
services/ # auth
types/ # SidebarSection, SidebarItem, UserData
utils/ # api (Hydra)
modules/ # Modules auto-detectes comme layers Nuxt
core/
nuxt.config.ts # Marqueur layer (vide)
pages/ # index.vue, login.vue
commercial/
nuxt.config.ts
pages/ # commercial.vue
app.vue # Composant racine
nuxt.config.ts # Scanne modules/*/ automatiquement
i18n/locales/ # Traductions (cles sidebar.*, etc.)
assets/ # CSS, images
public/ # Fichiers statiques
```
**Regles DDD frontend :**
- Chaque domaine est un dossier autonome dans `frontend/domains/`
- Un domaine ne doit pas importer depuis un autre domaine — utiliser les composables/stores partages
- Les composants, services et types partages restent a la racine (`components/`, `composables/`, etc.)
- Les pages peuvent etre dans `frontend/pages/` (routing Nuxt) et importer les composants du domaine
### Endpoints API cles
- `GET /api/version` (public) — version de l'app
- `GET /api/modules` (public) — liste des IDs de modules actifs
- `GET /api/sidebar` (public) — sections de la sidebar + `disabledRoutes`
- Filtre automatiquement les items dont le `module` owner n'est pas actif
- Les sections vides apres filtrage sont supprimees
- `disabledRoutes` = `to` des items filtres (utilise par le middleware front)
- `GET /api/me` (auth) — user courant
### Flux d'activation/desactivation d'un module
Pour activer/desactiver un module, tu touches **uniquement** `config/modules.php` :
```php
return [
\App\Module\Core\CoreModule::class,
// \App\Module\Commercial\CommercialModule::class, // commente = desactive
];
```
Cascade automatique :
1. `GET /api/modules` ne retourne plus `commercial`
2. `GET /api/sidebar` filtre les items `module: 'commercial'` → section "Commercial" disparait, ses routes passent dans `disabledRoutes`
3. Frontend : sidebar se met a jour, middleware `modules.global.ts` redirige toute navigation vers `/commercial` ou `/commercial/*`
4. Le code du module reste dans le bundle Nuxt (layer auto-detecte) → reactivation instantanee sans rebuild
### Reorganiser la sidebar sans toucher aux modules
Pour deplacer un item (ex: "Commandes fournisseurs") d'une section a une autre, tu edites juste `config/sidebar.php` :
```php
// Avant : sous Commercial
['label' => 'sidebar.commercial.suppliers', 'to' => '/commercial/suppliers', 'module' => 'commercial'],
// Apres : sous Production (l'item reste "owned" par Commercial, seule sa place change)
[
'label' => 'sidebar.production.section',
'items' => [
['label' => 'sidebar.commercial.suppliers', 'to' => '/commercial/suppliers', 'module' => 'commercial'],
],
],
```
Le code du module Commercial n'est pas touche.
### Regles d'architecture
**Backend :**
- Le domaine (`Domain/`) peut garder les attributs ORM (approche pragmatique) mais les repositories sont des interfaces
- Communication inter-modules par `Shared/Domain/Contract/` ou domain events — jamais d'import direct entre modules
- Chaque module declare un `*Module.php` avec `ID`, `LABEL`, `REQUIRED`
- `config/modules.php` = seule source de verite pour l'activation
- `config/sidebar.php` = seule source de verite pour l'organisation de la sidebar (chaque item reference son module owner via la cle `module`)
- Migrations par module dans `src/Module/{Module}/Infrastructure/Doctrine/Migrations/`
**Frontend :**
- Chaque module est un layer Nuxt auto-detecte (`modules/*/nuxt.config.ts` minimal)
- Un module front ne doit pas importer depuis un autre module — utiliser `shared/`
- `useSidebar()` fetch `/api/sidebar` et expose `sections`, `disabledRoutes`, `isRouteDisabled()`
- Le layout `default.vue` itere sur les sections retournees par l'API, applique `t()` sur les labels
- Middleware `auth.global.ts` charge la sidebar apres authentification
- Middleware `modules.global.ts` redirige si la route demandee est dans `disabledRoutes`
- **Interdit** : `.module.ts`, `modules-loader.ts`, hardcode de la sidebar, edition manuelle de `extends` dans `nuxt.config.ts`
## Stack
@@ -72,42 +157,6 @@ frontend/
- **Auth** : JWT HTTP-only cookie (lexik/jwt-authentication-bundle), login a `/login_check`, cookie `BEARER`
- **Docker** : PHP-FPM + Node 24, Nginx (port 8083), PostgreSQL (port 5436)
## Structure
```
src/
Domain/{Context}/Entity/ # Entites domaine
Domain/{Context}/ValueObject/ # Value Objects
Domain/{Context}/Repository/ # Interfaces repositories
Domain/{Context}/Service/ # Services domaine
Domain/{Context}/Event/ # Domain Events
Application/{Context}/Command/ # Commands + Handlers
Application/{Context}/Query/ # Queries + Handlers
Application/{Context}/DTO/ # Data Transfer Objects
Infrastructure/{Context}/Repository/ # Implementations Doctrine
Api/{Context}/Resource/ # ApiResource API Platform
Api/{Context}/State/ # Providers & Processors
Entity/ # Entites framework (User)
DataFixtures/ # Fixtures
config/ # Config Symfony
config/jwt/ # Cles JWT
migrations/ # Migrations Doctrine
infra/dev/ # Docker dev
infra/prod/ # Docker prod (multi-stage)
frontend/
domains/{context}/components/ # Composants du domaine
domains/{context}/composables/ # Composables du domaine
domains/{context}/services/ # Services API du domaine
domains/{context}/dto/ # Types TS du domaine
domains/{context}/stores/ # Store Pinia du domaine
components/ # Composants UI partages
composables/ # Composables partages (useApi, useAppVersion)
stores/ # Stores globaux (auth, ui)
pages/ # Pages (routing Nuxt)
layouts/ # Layouts
i18n/locales/ # Traductions
```
## Commandes
```bash
@@ -128,6 +177,12 @@ make php-cs-fixer-allow-risky # Fix code style PHP
make logs-dev # Tail logs Symfony
```
Si `make cache-clear` echoue pour cause de permissions sur `var/` :
```bash
docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var
docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear
```
## Conventions
### Commits
@@ -145,10 +200,28 @@ Exemples : `feat : add login page`, `fix(auth) : prevent null token crash`
- Faire un commit separe de bump : `chore : bump version to v<X.Y.Z>`
- Puis creer le tag et pusher : `git tag v<X.Y.Z> && git push origin develop --tags`
### Nommage
| Element | Convention | Exemple |
|---------|-----------|---------|
| Module back | PascalCase | `Module/Commercial/` |
| Module front | kebab-case | `modules/commercial/` |
| Module ID | snake_case | `commercial`, `gestion_rh` |
| Entity | PascalCase singulier | `User.php` |
| Repository interface | `*RepositoryInterface` | `UserRepositoryInterface.php` |
| Repository impl | `Doctrine*Repository` | `DoctrineUserRepository.php` |
| DTO | `*Output` / `*Input` | `UserOutput.php` |
| API Resource | classe dans `Infrastructure/ApiPlatform/Resource/` | `UserResource.php` |
| Provider | `*Provider` | `MeProvider.php` |
| Processor | `*Processor` | `UserPasswordHasherProcessor.php` |
| Module declaration back | `*Module.php` | `CommercialModule.php` |
| Composable front | `use*` | `useSidebar.ts` |
| Cles i18n sidebar | `sidebar.<module>.*` | `sidebar.commercial.overview` |
### Backend
- Toujours `declare(strict_types=1)` en haut des fichiers PHP
- API Platform : utiliser ApiResource, Providers (`src/State/`), Processors — pas de controllers
- API Platform : utiliser ApiResource, Providers, Processors — pas de controllers
- Routes API prefixees `/api` (via `config/routes/api_platform.yaml`)
- Le login (`/login_check`) est hors prefix `/api`, nginx reecrit `REQUEST_URI` vers `/login_check`
- PHP CS Fixer : regles Symfony + PSR-12 + strict types
@@ -163,9 +236,11 @@ Exemples : `feat : add login page`, `fix(auth) : prevent null token crash`
- TypeScript strict
- Composable `useApi()` pour tous les appels API (gere cookies, erreurs, toasts, i18n)
- Stores Pinia : `useAuthStore` (auth), `useUiStore` (ui)
- Middleware global `auth.global.ts` protege les routes
- Traductions dans `frontend/i18n/locales/`
- Middleware global `auth.global.ts` protege les routes + charge la sidebar apres login
- Middleware global `modules.global.ts` redirige les routes des modules desactives
- Traductions dans `frontend/i18n/locales/` avec le namespace `sidebar.*` pour la nav
- 4 espaces d'indentation
- Les labels de sidebar sont des cles i18n, jamais du texte brut (le layout applique `t()` dessus)
### Nginx