From 57ccd9a7403755cd4a94654750ff255ccc38e1ba Mon Sep 17 00:00:00 2001 From: Matthieu Date: Sat, 20 Jun 2026 19:18:09 +0200 Subject: [PATCH] feat(directory) : add Clients/Prospects repertoire front layer LST-58 (2.4) front. Completes the Directory module. - New frontend/modules/directory/ layer (auto-detected): /directory page with Clients and Prospects tabs. - Client front moved into the layer (clients service + client DTO + ClientDrawer). New prospects service, prospect DTO and ProspectDrawer (with a "Convert to client" action calling POST /prospects/{id}/convert). - Consumers repointed to ~/modules/directory/... (admin client tab, PM project drawer + project pages + project DTO, time-tracking page + export drawer). - Sidebar admin item /directory gated by the directory module; /directory protected by the admin middleware. i18n keys added (directory.*, prospects.*). nuxt build passes; routes preserved. Adds the 2.4 plan doc. --- config/sidebar.php | 1 + .../2026-06-20-lst-58-directory-prospect.md | 66 + frontend/components/admin/AdminClientTab.vue | 4 +- frontend/i18n/locales/fr.json | 1741 +++++++++-------- .../directory/components}/ClientDrawer.vue | 4 +- .../directory/components/ProspectDrawer.vue | 221 +++ frontend/modules/directory/nuxt.config.ts | 1 + .../modules/directory/pages/directory.vue | 240 +++ .../directory}/services/clients.ts | 0 .../directory}/services/dto/client.ts | 0 .../directory/services/dto/prospect.ts | 34 + .../modules/directory/services/prospects.ts | 44 + .../components/ProjectDrawer.vue | 2 +- .../pages/projects/[id]/index.vue | 4 +- .../pages/projects/index.vue | 4 +- .../services/dto/project.ts | 2 +- .../components/TimeTrackingExportDrawer.vue | 2 +- .../time-tracking/pages/time-tracking.vue | 2 +- 18 files changed, 1514 insertions(+), 858 deletions(-) create mode 100644 docs/superpowers/plans/2026-06-20-lst-58-directory-prospect.md rename frontend/{components/client => modules/directory/components}/ClientDrawer.vue (95%) create mode 100644 frontend/modules/directory/components/ProspectDrawer.vue create mode 100644 frontend/modules/directory/nuxt.config.ts create mode 100644 frontend/modules/directory/pages/directory.vue rename frontend/{ => modules/directory}/services/clients.ts (100%) rename frontend/{ => modules/directory}/services/dto/client.ts (100%) create mode 100644 frontend/modules/directory/services/dto/prospect.ts create mode 100644 frontend/modules/directory/services/prospects.ts diff --git a/config/sidebar.php b/config/sidebar.php index 7bee8f3..12d9a50 100644 --- a/config/sidebar.php +++ b/config/sidebar.php @@ -31,6 +31,7 @@ return [ 'roles' => ['ROLE_ADMIN'], 'items' => [ ['label' => 'sidebar.admin.teamAbsences', 'to' => '/team-absences', 'icon' => 'mdi:calendar-account-outline', 'module' => 'absence'], + ['label' => 'sidebar.admin.directory', 'to' => '/directory', 'icon' => 'mdi:contact-multiple-outline', 'module' => 'directory'], ['label' => 'sidebar.admin.administration', 'to' => '/admin', 'icon' => 'mdi:cog-outline', 'permission' => 'core.users.view'], ], ], diff --git a/docs/superpowers/plans/2026-06-20-lst-58-directory-prospect.md b/docs/superpowers/plans/2026-06-20-lst-58-directory-prospect.md new file mode 100644 index 0000000..b6df3d6 --- /dev/null +++ b/docs/superpowers/plans/2026-06-20-lst-58-directory-prospect.md @@ -0,0 +1,66 @@ +# LST-58 (2.4) — Module Directory : Prospect + front répertoire (plan) + +> Suite de la migration Directory. Client (back) déjà livré (`c5738d2`). +> Reste : **entité Prospect** (nouvelle) + **front répertoire** (Clients + Prospects). +> Spec produit non fournie → design défini ici de façon raisonnable, à valider au test. +> Additif, sans régression. Branche `integration/modular-monolith-0.1-1.3`. + +## Design Prospect (décidé, à valider) +Aligné sur `Client` (même module Directory), enrichi des concepts de prospection commerciale. + +**Entité `App\Module\Directory\Domain\Entity\Prospect`** (table `prospect`) : +- `id` int PK +- `name` string(255) NOT NULL — contact ou société +- `company` string(255) nullable +- `email` string(255) nullable +- `phone` string(50) nullable +- `street` string(255) nullable / `city` string(255) nullable / `postalCode` string(20) nullable (alignés Client) +- `status` enum `ProspectStatus` NOT NULL (default `New`) +- `source` string(255) nullable — origine (recommandation, salon, site web…) +- `notes` text nullable +- `convertedClient` ManyToOne `ClientInterface` nullable, JoinColumn ON DELETE SET NULL — rempli à la conversion +- Timestampable/Blamable (trait) + `#[Auditable]` +- Groupes : `prospect:read` / `prospect:write` + +**Enum `App\Module\Directory\Domain\Enum\ProspectStatus`** : `New` (nouveau), `Contacted` (contacté), `Qualified` (qualifié), `Won` (gagné/converti), `Lost` (perdu). Méthode `label(): string` (FR), comme les autres enums. + +**API Platform** (aligné Client) : +- `GetCollection` paginationEnabled:false, `is_granted('ROLE_USER')` +- `Get` ROLE_USER ; `Post`/`Patch`/`Delete` ROLE_ADMIN +- Opération custom **`Post /prospects/{id}/convert`** (processor `ConvertProspectProcessor`) : crée un `Client` à partir du Prospect (name/company→name, email, phone, adresse), lie `convertedClient`, passe `status=Won`. Sécurité ROLE_ADMIN. Renvoie le Prospect mis à jour. Idempotent si déjà converti (renvoie l'existant). +- `#[ApiFilter]` SearchFilter sur `status` (filtre répertoire). + +**Repo** : `ProspectRepositoryInterface` (Domain) + `DoctrineProspectRepository` (Infra) + binding. + +**MCP** (cohérent avec clients, sous `Infrastructure/Mcp/Tool/`) : `list-prospects`, `get-prospect`, `create-prospect`, `update-prospect`, `delete-prospect`, `convert-prospect`. Serializer : ajouter `prospect()` dans `src/Mcp/Tool/Serializer.php`. + +**DirectoryModule.permissions()** : ajouter `directory.prospects.view`, `directory.prospects.manage` (additif). + +**Migration additive** : CREATE TABLE prospect (colonnes + FK converted_client→client ON DELETE SET NULL + created_by/updated_by FK user + index + COMMENT). Down = DROP TABLE. + +**Fixtures** : 2-3 prospects de démo (statuts variés), dont un converti. + +## Front répertoire (`frontend/modules/directory/`) +Aujourd'hui : pas de page client dédiée (AdminClientTab + picker ProjectDrawer). On crée un vrai répertoire. +- `nuxt.config.ts` vide. +- `services/` : `clients.ts` (move depuis racine), `prospects.ts` (nouveau) + `dto/{client,prospect}.ts`. +- `pages/directory.vue` : page à 2 onglets (Clients / Prospects), tableaux paginés côté client (paginationEnabled:false back), recherche/filtre statut pour prospects. +- `components/` : `ClientDrawer.vue` (move depuis `components/client/`), `ProspectDrawer.vue` (nouveau, create/edit + bouton « Convertir en client »). +- Sidebar : ajouter item `sidebar.general.directory` → `/directory`, `'module' => 'directory'`, gate ROLE_ADMIN (gestion référentiel). +- Réécrire imports consommateurs de `~/services/clients` / `~/services/dto/client` (AdminClientTab, ProjectDrawer, pages projects) → `~/modules/directory/services/...`. AdminClientTab : soit le retirer de /admin au profit de /directory, soit le laisser pointer le nouveau service. Décision : garder AdminClientTab fonctionnel (repoint service) ET ajouter la page /directory (les deux coexistent ; /directory = vue dédiée). +- i18n global : ajouter clés `directory.*`, `prospects.*`, `sidebar.general.directory`. + +## Vagues d'exécution +1. **Back Prospect** : enum + entité + repo + API (CRUD + convert) + MCP (6 tools) + Serializer + permissions module + fixtures + migration. Vérif cache:clear/migrate/phpunit/cs-fixer → commit. +2. **Front Directory** : layer (move client front + page répertoire + ProspectDrawer + prospects service/dto) + sidebar + imports + i18n. Vérif nuxt build → commit. + +## Critères d'acceptation (ticket #58) +- [x] Clients en module (fait, c5738d2) +- [ ] Prospects en module + front répertoire fonctionnel +- [x] resolve_target_entities → Directory\Client +- [ ] make test vert, aucune migration destructive +- [ ] toggle module directory (sidebar + route /directory) + +## Suite phase 2 (après 2.4) +- 2.5 (#67) Module Mail — WIP `docs/mail-integration.md`, à traiter avec précaution. +- 2.6 (#68) Module Integration (Gitea/BookStack/Zimbra/Share). diff --git a/frontend/components/admin/AdminClientTab.vue b/frontend/components/admin/AdminClientTab.vue index 12d3863..36af726 100644 --- a/frontend/components/admin/AdminClientTab.vue +++ b/frontend/components/admin/AdminClientTab.vue @@ -40,8 +40,8 @@ diff --git a/frontend/modules/directory/nuxt.config.ts b/frontend/modules/directory/nuxt.config.ts new file mode 100644 index 0000000..268da7f --- /dev/null +++ b/frontend/modules/directory/nuxt.config.ts @@ -0,0 +1 @@ +export default defineNuxtConfig({}) diff --git a/frontend/modules/directory/pages/directory.vue b/frontend/modules/directory/pages/directory.vue new file mode 100644 index 0000000..2bdaae3 --- /dev/null +++ b/frontend/modules/directory/pages/directory.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/frontend/services/clients.ts b/frontend/modules/directory/services/clients.ts similarity index 100% rename from frontend/services/clients.ts rename to frontend/modules/directory/services/clients.ts diff --git a/frontend/services/dto/client.ts b/frontend/modules/directory/services/dto/client.ts similarity index 100% rename from frontend/services/dto/client.ts rename to frontend/modules/directory/services/dto/client.ts diff --git a/frontend/modules/directory/services/dto/prospect.ts b/frontend/modules/directory/services/dto/prospect.ts new file mode 100644 index 0000000..17a8f66 --- /dev/null +++ b/frontend/modules/directory/services/dto/prospect.ts @@ -0,0 +1,34 @@ +import type { Client } from './client' + +export type ProspectStatus = 'new' | 'contacted' | 'qualified' | 'won' | 'lost' + +export type Prospect = { + id: number + '@id'?: string + name: string + company: string | null + email: string | null + phone: string | null + street: string | null + city: string | null + postalCode: string | null + status: ProspectStatus + source: string | null + notes: string | null + convertedClient: Client | string | null + createdAt?: string + updatedAt?: string +} + +export type ProspectWrite = { + name: string + company: string | null + email: string | null + phone: string | null + street: string | null + city: string | null + postalCode: string | null + status: ProspectStatus + source: string | null + notes: string | null +} diff --git a/frontend/modules/directory/services/prospects.ts b/frontend/modules/directory/services/prospects.ts new file mode 100644 index 0000000..a07e9b0 --- /dev/null +++ b/frontend/modules/directory/services/prospects.ts @@ -0,0 +1,44 @@ +import type { Prospect, ProspectStatus, ProspectWrite } from './dto/prospect' +import type { HydraCollection } from '~/utils/api' +import { extractHydraMembers } from '~/utils/api' + +export function useProspectService() { + const api = useApi() + + async function getAll(status?: ProspectStatus): Promise { + const query: Record = {} + if (status) query.status = status + const data = await api.get>('/prospects', query) + return extractHydraMembers(data) + } + + async function getById(id: number): Promise { + return api.get(`/prospects/${id}`) + } + + async function create(payload: ProspectWrite): Promise { + return api.post('/prospects', payload as Record, { + toastSuccessKey: 'prospects.created', + }) + } + + async function update(id: number, payload: Partial): Promise { + return api.patch(`/prospects/${id}`, payload as Record, { + toastSuccessKey: 'prospects.updated', + }) + } + + async function remove(id: number): Promise { + await api.delete(`/prospects/${id}`, {}, { + toastSuccessKey: 'prospects.deleted', + }) + } + + async function convert(id: number): Promise { + return api.post(`/prospects/${id}/convert`, {}, { + toastSuccessKey: 'prospects.converted', + }) + } + + return { getAll, getById, create, update, remove, convert } +} diff --git a/frontend/modules/project-management/components/ProjectDrawer.vue b/frontend/modules/project-management/components/ProjectDrawer.vue index cd9aef4..0cef9b8 100644 --- a/frontend/modules/project-management/components/ProjectDrawer.vue +++ b/frontend/modules/project-management/components/ProjectDrawer.vue @@ -124,7 +124,7 @@