matthieu 38f9f164f1
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m40s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m15s
[ERP-58] Implémenter l'export XLSX du répertoire clients (#37)
## Contexte
Ticket ERP-58 (M1 Commercial, spec-back § 4.6) — export XLSX du répertoire clients.
Branche stackée sur ERP-57. **Cible la MR sur \`feature/ERP-57-...\`** (squash merge).

## Objectif d'archi : un service d'export RÉUTILISABLE
Le générique vit dans \`Shared\`, le module Client ne déclare que QUOI exporter.

### Shared (le COMMENT — sans métier)
- \`Shared/Domain/Contract/SpreadsheetExporterInterface\` : \`export(string $sheetTitle, array $headers, iterable $rows): string\`. Zéro connaissance métier.
- \`Shared/Infrastructure/Export/PhpSpreadsheetExporter\` : implémentation PhpSpreadsheet (en-tête ligne 1 + lignes, retour binaire via fichier temporaire). Titre d'onglet assaini (≤ 31 car., caractères Excel interdits retirés). Supporte un \`iterable\` paresseux (generator).
- Auto-aliasé (un seul implémenteur) → \`SpreadsheetExporterInterface\` résout vers \`PhpSpreadsheetExporter\`.

> Tout futur module réutilise \`SpreadsheetExporterInterface\` sans toucher au Client.

### Commercial (le QUOI)
- \`ClientExportController\` (controller custom, \`#[Route('/api/clients/export.xlsx', priority: 1)]\` — **priority:1 obligatoire** pour éviter le conflit API Platform \`{id}\`). Security \`commercial.clients.view\`.
- Mêmes filtres que \`GET /api/clients\` (non archivés par défaut, \`?search\`, \`?categoryType\`, \`?includeArchived\`). **Filtrage factorisé dans \`ClientRepository::createListQueryBuilder()\`** (search + categoryType déplacés depuis \`ClientProvider\`) → liste paginée et export partagent strictement la même logique, zéro duplication.
- Colonnes (§ 4.6) : Nom entreprise, Nom contact principal, Prénom, Tél. principal, Tél. secondaire, Email, Catégories (CSV), Sites (CSV = union distincte des sites des adresses), **SIREN (omis si pas \`commercial.clients.accounting.view\`)**, Date de création.
- Réponse : \`Content-Type: …spreadsheetml.sheet\`, \`Content-Disposition: attachment; filename="repertoire-clients-{YYYYMMDD}.xlsx"\`.

## Dépendance
\`composer require phpoffice/phpspreadsheet\` (^5.7). Nettoyage recipes vérifié : seuls \`composer.json\`/\`composer.lock\` modifiés (pas de scaffolding parasite, \`symfony.lock\` désormais versionné).

## Tests (404 OK)
- **Unitaire Shared** : XLSX relisible (en-têtes + 2 lignes), generator, titre assaini.
- **Fonctionnel** : 200 (Content-Type + filename), exclusion archives par défaut, \`?search\`/\`?categoryType\`, SIREN présent (accounting.view) / absent (view seul), 403 sans \`clients.view\`, 401 anonyme.

## Note
Au démarrage, \`symfony/intl\` (requis par ERP-57, contrainte \`Bic\`) manquait du vendor → \`composer install\` joué pour rétablir une base saine.

## ⚠️ Heads-up review (@Tristan) — fichiers « propriété » d'ERP-55 touchés
Cette MR refactore deux fichiers introduits par ERP-55 :
- **`ClientRepository::createListQueryBuilder()`** accueille désormais le filtrage `search` + `categoryType` (signature `(bool $includeArchived, ?string $search, ?string $categoryType)`).
- **`ClientProvider`** délègue ce filtrage au repository → il **perd sa dépendance `EntityManager`** et ses méthodes privées `applySearch` / `applyCategoryType`.

**Pourquoi** : DRY entre la liste paginée (`GET /api/clients`) et l'export — une seule source de vérité pour la sélection des clients. Effet de bord positif : ça résout **plus proprement la fuite d'abstraction** que tu avais pointée en revue ERP-55 (P2) — la sous-requête `categoryType` n'est plus construite via l'`EntityManager` injecté dans le provider, mais à l'intérieur du repository (là où l'accès Doctrine est légitime).

Pas de changement de comportement de l'API liste : régression couverte par `ClientApiTest` (tri, exclusion archives, includeArchived, pagination) — tout vert.

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #37
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
2026-06-01 19:28:34 +00:00
2026-04-07 10:56:57 +02:00
2026-04-07 10:56:57 +02:00
2026-04-07 11:42:56 +02:00
2026-05-19 08:24:19 +02:00
2026-05-19 08:24:19 +02:00
2026-05-19 08:24:19 +02:00

Starseed

CRM/ERP — Symfony 8 (API Platform 4) + Nuxt 4

Stack

  • Backend : PHP 8.4, Symfony 8, API Platform 4, Doctrine ORM, PostgreSQL 16
  • Frontend : Nuxt 4 (SPA), Vue 3, Pinia, Tailwind CSS, @malio/layer-ui
  • Auth : JWT HTTP-only cookie (Lexik)
  • Infra : Docker Compose (dev + prod multi-stage)
  • CI/CD : Gitea Actions (auto-tag + build Docker)

Quick Start

make start           # Demarrer les containers Docker
make install         # Composer, migrations, fixtures, build Nuxt

Dev frontend (hot reload) :

make dev-nuxt        # Port 3003

Ports

Service Port
API (Nginx) 8083
Frontend 3004
PostgreSQL 5437

Commandes

Commande Description
make start Demarrer les containers
make stop Arreter les containers
make restart Redemarrer les containers
make install Install complet
make reset Tout supprimer et reinstaller
make dev-nuxt Serveur dev Nuxt (hot reload)
make shell Shell dans le container PHP
make cache-clear Vider le cache Symfony
make migration-migrate Lancer les migrations
make fixtures Charger les fixtures
make db-reset Reset BDD + migrations + fixtures
make test PHPUnit (tests back)
make nuxt-test Vitest (tests unitaires front)
make test-e2e Playwright (tests E2E front)
make test-e2e-ui Playwright UI interactive (debug)
make seed-e2e Seed les 6 personas E2E
make install-e2e-deps One-time : Chromium + libs systeme (sudo)
make php-cs-fixer-allow-risky Fix code style PHP
make logs-dev Tail logs Symfony

Tests

  • Back : make test (PHPUnit). Fixtures dediees sous tests/Fixtures/.
  • Front unitaire : make nuxt-test (Vitest, happy-dom). Composables, utils, stores — rapide, <30s.
  • Front E2E : make test-e2e (Playwright). Couvre login + matrice RBAC sidebar. Suite volontairement minimaliste (11 tests) — voir la regle d'or dans CLAUDE.md.

Bootstrap E2E (une fois par poste) :

make install-e2e-deps   # Telecharge Chromium + libs systeme via apt (sudo)

Workflow E2E :

# Terminal 1 : containers + dev server
make start && make seed-e2e && make dev-nuxt

# Terminal 2 : tests
make test-e2e

Architecture

Modular Monolith DDD : chaque module est un bounded context autonome, activable/desactivable par tenant. Le backend est la seule source de verite pour l'activation et l'organisation de la sidebar.

  • config/modules.php — liste des modules actifs
  • config/sidebar.php — structure de la sidebar (sections + items avec module owner)
  • GET /api/sidebar — retourne les sections filtrees par les modules actifs + les routes desactivees
  • Frontend : chaque frontend/modules/*/ est auto-detecte comme layer Nuxt, la sidebar est fetchee de l'API

Pour desactiver un module : commenter sa ligne dans config/modules.php, clear cache. Ses items de sidebar disparaissent et ses routes sont bloquees par le middleware front.

Pour reorganiser la sidebar (ex: deplacer un item d'une section a l'autre) : editer config/sidebar.php uniquement, le code des modules n'est pas touche.

Structure

src/                              # Backend Symfony
  Kernel.php
  Shared/                         # Noyau technique partage
    Domain/
      ValueObject/                # Email, ...
      Event/                      # DomainEventInterface
      Contract/                   # Interfaces inter-modules
    Application/
      Bus/                        # CommandBusInterface, QueryBusInterface
    Infrastructure/
      ApiPlatform/
        Resource/                 # AppVersion, ModulesResource, SidebarResource
        State/                    # AppVersionProvider, ModulesProvider, SidebarProvider
  Module/
    Core/                         # Module obligatoire (auth, users)
      CoreModule.php              # Declaration (ID, LABEL, REQUIRED)
      Domain/
        Entity/                   # User
        Repository/               # UserRepositoryInterface
        Event/                    # 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                     # Source de verite activation
  sidebar.php                     # Source de verite navigation
  version.yaml
  packages/                       # Config Symfony
  jwt/                            # Cles JWT
migrations/                       # Anciennes migrations
frontend/                         # App Nuxt 4 (SPA)
  app/
    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, UserData
    utils/                        # api (Hydra)
  modules/                        # Modules auto-detectes comme layers Nuxt
    core/
      nuxt.config.ts              # Marqueur layer
      pages/                      # index, login, logout
    commercial/
      nuxt.config.ts
      pages/                      # commercial.vue
  app.vue
  nuxt.config.ts                  # Scanne modules/*/ automatiquement
  i18n/locales/                   # Traductions (sidebar.*, etc.)
  assets/                         # CSS, images
  public/                         # Fichiers statiques
infra/
  dev/                            # Docker dev (Dockerfile, nginx, php.ini, xdebug)
  prod/                           # Docker prod (multi-stage, nginx, php-prod.ini)
.gitea/workflows/                 # CI Gitea (auto-tag, build Docker)
.claude/
  skills/create-module/           # Skill Claude Code pour scaffolder un module

CI/CD

  • Auto Tag : push sur develop → bump config/version.yaml → tag vX.Y.Z
  • Build Docker : push tag v* → build image multi-stage → push Gitea Registry

Secrets requis dans Gitea :

  • RELEASE_TOKEN — PAT avec droits write:repository
  • REGISTRY_TOKEN — token pour le registry Docker

Credentials (dev)

Username Password Role
admin admin ROLE_ADMIN
alice alice ROLE_USER
bob bob ROLE_USER

Conventions

Commits

<type>(<scope optionnel>) : <message>

Types : build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test

S
Description
No description provided
Readme 5.4 MiB
Languages
PHP 75.2%
TypeScript 13.2%
Vue 9.3%
Makefile 1.2%
Dockerfile 0.4%
Other 0.6%