9.1 KiB
name, description
| name | description |
|---|---|
| create-module | Scaffold a new Coltura module (backend + frontend) and optionally wire its entries into the sidebar config. Use when the user asks to create, add, scaffold, or generate a new module — e.g., "crée un module Paie", "add a Pointage module", "ajoute un module RH". The backend is the source of truth for activation and sidebar layout; the frontend scans modules automatically. |
Create a new Coltura module
Scaffolds a new module across backend and frontend following Coltura's modular monolith DDD architecture.
Architecture reminder — read before acting
The module system has two concerns that are decoupled:
- Module = code + routes + pages. A module is a bounded context that owns feature code. Declared in
config/modules.php(backend) and scanned automatically on frontend vianuxt.config.ts(any directory underfrontend/modules/becomes a Nuxt layer). - Sidebar = navigation layout. Defined in
config/sidebar.php. Each sidebar item references the module that owns it via themodulekey. Items whose module is not active are filtered out by the backend. You can freely move a sidebar item from one section to another without touching module code.
Consequences:
- Frontend never hardcodes module metadata — no
.module.ts, nomodules-loader.ts, no manual registration. - Frontend never hardcodes the sidebar — it fetches
GET /api/sidebar. - To add a new module in the sidebar: edit
config/sidebar.php. - To move an item between sections: edit
config/sidebar.php. - To disable a module: remove/comment it from
config/modules.php. Its sidebar items are auto-filtered AND its routes land indisabledRoutes, which the frontend middlewaremodules.global.tsuses to redirect any direct URL access back to/.
The /api/sidebar response shape is:
{
"sections": [{ "label": "...", "icon": "...", "items": [...] }],
"disabledRoutes": ["/commercial", "/commercial/orders"]
}
Any item whose module key matches an inactive module is moved from sections into disabledRoutes automatically by SidebarProvider.
When to use
User wants to create a new vendable module. Example triggers:
- "Crée un module Paie"
- "Ajoute un module Pointage"
- "Scaffold a new Stock module"
Prerequisites — gather before acting
Ask the user (via AskUserQuestion if not already provided):
- Module name (French, human readable, e.g., "Paie", "Gestion RH", "Pointage"). Derive:
PascalCasefor backend folders/classes:Paie,GestionRh,Pointagekebab-casefor frontend folders/routes:paie,gestion-rh,pointagecamelCasefor i18n keys:paie,gestionRh,pointagesnake_casefor module ID:paie,gestion_rh,pointage
- Icon — an mdi icon identifier for the sidebar section (e.g.,
mdi:cash-multiple). If not given, suggest a relevant one. - Should it appear in the sidebar immediately? If yes, ask which section (new one or existing) and what nav items (label + route).
Don't guess these — ask.
Placeholders used below
{Pascal}= PascalCase (e.g.,Paie){kebab}= kebab-case (e.g.,paie){camel}= camelCase i18n slug (e.g.,paie,gestionRh){id}= module ID for backend + config (same as{camel}for single-word names,snake_caseotherwise){label}= French display label (e.g.,Paie){icon}= mdi icon string (e.g.,mdi:cash-multiple)
Files to create/edit
Backend — module declaration
1. src/Module/{Pascal}/{Pascal}Module.php
<?php
declare(strict_types=1);
namespace App\Module\{Pascal};
final class {Pascal}Module
{
public const string ID = '{id}';
public const string LABEL = '{label}';
public const bool REQUIRED = false;
}
2. Edit config/modules.php — append the module class:
return [
\App\Module\Core\CoreModule::class,
// ... existing modules
\App\Module\{Pascal}\{Pascal}Module::class,
];
Backend — sidebar config (only if the user wants sidebar entries)
3. Edit config/sidebar.php — either add items to an existing section or append a new section:
[
'label' => 'sidebar.{camel}.section',
'icon' => '{icon}',
'items' => [
[
'label' => 'sidebar.{camel}.overview',
'to' => '/{kebab}',
'icon' => '{icon}',
'module' => '{id}',
],
// more items from user input...
],
],
Every item must have a module key matching an active module ID — otherwise the backend will filter it out.
Frontend — module code (auto-detected)
4a. frontend/modules/{kebab}/nuxt.config.ts — required for Nuxt to treat the folder as a layer. Content:
export default defineNuxtConfig({})
4b. frontend/modules/{kebab}/pages/{kebab}.vue — placeholder page. The filename becomes the route /{kebab}.
<template>
<div>
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('{camel}.title') }}</h1>
<p class="mt-4 text-neutral-500">{{ $t('{camel}.welcome') }}</p>
</div>
</template>
<script setup lang="ts">
const { t } = useI18n()
useHead({ title: t('{camel}.title') })
</script>
Create one page file per nav item the user specified. Derive filenames from the to paths (e.g., /paie/bulletins → pages/paie/bulletins.vue).
Do NOT create any of these — they no longer exist in this architecture:
- ❌
frontend/modules/{kebab}/{kebab}.module.ts - ❌
frontend/plugins/modules-loader.ts - ❌ Any
extendsentry innuxt.config.ts— modules are auto-detected fromfrontend/modules/*/.
Frontend — translations
5. Edit frontend/i18n/locales/fr.json — add the sidebar keys and page content keys:
{
"sidebar": {
"{camel}": {
"section": "{label}",
"overview": "Vue d'ensemble"
}
},
"{camel}": {
"title": "{label}",
"welcome": "Module {label}"
}
}
Every label in the new config/sidebar.php entries must have a matching key here, and every page's $t('{camel}.*') calls must match too.
Implementation steps
Execute in this exact order:
- Clarify inputs — use
AskUserQuestionfor name, icon, and sidebar decisions. - Derive naming — confirm Pascal/kebab/camel/id are sensible (e.g., "Gestion RH" →
GestionRh/gestion-rh/gestionRh/gestion_rh). - Read current state — Read
config/modules.php,config/sidebar.php,frontend/i18n/locales/fr.jsonso you know what exists before editing. - Backend: declare the module — create
{Pascal}Module.php, editconfig/modules.php. - Frontend: create pages — create the placeholder
.vuefiles underfrontend/modules/{kebab}/pages/. - Backend: sidebar — if the user wants sidebar entries, edit
config/sidebar.php. - Frontend: translations — edit
frontend/i18n/locales/fr.json. - Verify — run:
docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var(avoid permission issues)docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear(validates backend)cd frontend && npx nuxi prepare(validates Nuxt auto-detection of the new layer)
- Report — list files created, the route(s) to test, and the sidebar items added.
Rules — do not violate
- Never create
.module.tsfiles — the oldModuleDefinition/registerModulepattern is gone. Activation and sidebar are 100% backend-driven. - Never edit
extendsinnuxt.config.ts— it auto-scansfrontend/modules/*/. Adding entries manually causes duplicates. - Never create
frontend/plugins/modules-loader.ts— it was deleted on purpose. - All sidebar labels are i18n keys, not raw text — the layout calls
t()on every label. Raw text will display as-is and look broken. - Every sidebar item needs a
modulekey — if omitted, the backend filters it out silently. - Backend module must be in
config/modules.php— otherwise/api/sidebarwill hide its items even though the sidebar config references them. - No DDD scaffolding unless asked — only create the
{Pascal}Module.phpinitially. Don't create emptyDomain/,Application/,Infrastructure/folders. Real domain code comes when the module gets features. - Don't create a page per nav item blindly — if the user didn't specify, create only the root
/{kebab}page and ask whether they want stubs for subpages. - Don't use
make cache-clear— it may hit permission issues. Use the docker commands directly (see Verify step).
Naming derivation examples
| User input | Pascal | kebab | camel | id |
|---|---|---|---|---|
| Paie | Paie | paie | paie | paie |
| Pointage | Pointage | pointage | pointage | pointage |
| Gestion RH | GestionRh | gestion-rh | gestionRh | gestion_rh |
| Stock & Inventaire | StockInventaire | stock-inventaire | stockInventaire | stock_inventaire |
| CRM | Crm | crm | crm | crm |
For accented characters (é, è, à, ç...): strip accents in folder/identifier names (Forêt → Foret/foret), keep them in the French label.