diff --git a/.claude/rules/backend.md b/.claude/rules/backend.md
index 7534f0c..e21a688 100644
--- a/.claude/rules/backend.md
+++ b/.claude/rules/backend.md
@@ -74,3 +74,53 @@ Exemple : pour qu'`User.profile` soit embarque au lieu d'un lien IRI sous le gro
## PostgreSQL
- Noms de colonnes toujours en **minuscules** dans le SQL brut (commun a tous les projets MALIO)
+
+## Migrations Doctrine
+
+### Documentation SQL obligatoire (`COMMENT ON COLUMN`)
+
+**Toute migration qui cree ou modifie une colonne d'une table metier doit poser un `COMMENT ON COLUMN` decrivant le champ.** La description est stockee dans `pg_description` et visible dans tous les outils d'admin BDD (DBeaver, DataGrip, pgAdmin), sans avoir a lire les annotations PHP.
+
+**Format de la description** :
+- En francais
+- ≤ 200 caracteres
+- Semantique du champ — contraintes / lien RG si pertinent
+- Pour les colonnes d'identifiant ou FK, mentionner la cible
+
+Exemples :
+
+```php
+// Migration : creation d'une colonne avec son commentaire dans la meme migration
+$this->addSql("ALTER TABLE client ADD COLUMN siren VARCHAR(9) DEFAULT NULL");
+$this->addSql("COMMENT ON COLUMN client.siren IS 'SIREN (9 chiffres) — identifiant legal entreprise. Unique parmi non-archives (RG-1.15).'");
+
+// Cas FK : preciser la cible
+$this->addSql("COMMENT ON COLUMN client.legal_form_id IS 'Reference forme juridique (SARL, SAS, SA...) — FK -> legal_form.id, ON DELETE RESTRICT.'");
+
+// Cas booleen : preciser le sens et la valeur par defaut
+$this->addSql("COMMENT ON COLUMN user.is_admin IS 'Drapeau super-administrateur — bypass complet RBAC. Faux par defaut.'");
+
+// Bonus : decrire la table elle-meme
+$this->addSql("COMMENT ON TABLE client IS 'Repertoire clients (M1 Commercial) — entites archivables.'");
+```
+
+### Helper Timestampable/Blamable
+
+Les 4 colonnes `created_at`, `updated_at`, `created_by`, `updated_by` ajoutees par `TimestampableBlamableTrait` recoivent une description **standardisee** via le helper centralise pour eviter la duplication. Helper a creer ou appeler :
+
+```php
+// Dans la migration, apres avoir ajoute les 4 colonnes :
+$this->addStandardTimestampableBlamableComments($schema, 'client');
+```
+
+L'implementation du helper applique :
+- `created_at` : « Horodatage de creation de la ligne (UTC, rempli automatiquement par TimestampableBlamableSubscriber). »
+- `updated_at` : « Horodatage de derniere modification de la ligne (UTC, rempli automatiquement par TimestampableBlamableSubscriber). »
+- `created_by` : « ID de l'utilisateur ayant cree la ligne — null pour les creations hors HTTP (CLI, migration, fixture). FK -> user.id, ON DELETE SET NULL. »
+- `updated_by` : « ID de l'utilisateur ayant modifie la ligne en dernier — null pour les modifications hors HTTP. FK -> user.id, ON DELETE SET NULL. »
+
+### Garde-fou architecture
+
+`tests/Architecture/ColumnsHaveSqlCommentTest` parcourt `information_schema.columns` filtre sur le schema `public` et echoue si **une seule colonne** n'a pas de `col_description`. Seules les tables system (`doctrine_migration_versions`) et la whitelist `EXCLUDED_TABLES` explicite (commentaire de justification + ticket Lesstime ouvert pour le retrofit) sont tolerees.
+
+Conclusion : si tu crees une colonne sans poser son `COMMENT ON COLUMN`, `make test` casse en CI.
diff --git a/.gitea/workflows/pull-request.yml b/.gitea/workflows/pull-request.yml
index c0f9425..56062ab 100644
--- a/.gitea/workflows/pull-request.yml
+++ b/.gitea/workflows/pull-request.yml
@@ -84,6 +84,9 @@ jobs:
php bin/console doctrine:database:create --env=test --if-not-exists --no-interaction
php bin/console doctrine:migrations:migrate --env=test --no-interaction
php bin/console doctrine:schema:update --env=test --force --no-interaction
+ # Rejoue le catalogue COMMENT ON apres schema:update (cf. ERP-67) :
+ # schema:update drop les commentaires des tables managees par l'ORM.
+ php bin/console app:apply-column-comments --env=test --no-interaction
php bin/console doctrine:fixtures:load --env=test --no-interaction
php bin/console app:sync-permissions --env=test --no-interaction
php bin/console --env=test dbal:run-sql "CREATE UNIQUE INDEX IF NOT EXISTS uq_category_name_type_active ON category (LOWER(name), category_type_id) WHERE deleted_at IS NULL"
diff --git a/CLAUDE.md b/CLAUDE.md
index 137720e..df64645 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -24,6 +24,7 @@ Doc humaine : @README.md — Spec audit : @doc/audit-log.md
9. **Jamais commit sans demande explicite** de l'utilisateur ; jamais force push sans confirmation.
10. **Jamais mentionner Claude, Anthropic ou une IA** dans un commit (message, titre, body, footer, trailer) ou une PR (titre, description). Pas de `Co-Authored-By: Claude`, pas de `Generated with Claude Code`, pas de `🤖`, pas d'emoji robot, rien. Les commits sont signes par l'utilisateur uniquement.
11. **Migrations d'initialisation au namespace racine** `DoctrineMigrations` dans `migrations/` (setup user, RBAC, seed de base). Les migrations modulaires (`src/Module/*/Infrastructure/Doctrine/Migrations/`) sont reservees aux evolutions post-schema (ajout de colonnes, index) — cf. @.claude/rules/architecture.md pour la raison.
+12. **Toujours documenter chaque colonne BDD via `COMMENT ON COLUMN`** dans la migration qui la cree ou la modifie. Description en francais, courte (≤ 200 caracteres), explique la semantique metier + contraintes implicites (unicite partielle, FK importante, lien RG). Garde-fou : `tests/Architecture/ColumnsHaveSqlCommentTest` echoue si une colonne `public` n'a pas de description (`col_description IS NULL`). Details et exemples : @.claude/rules/backend.md § Migrations Doctrine.
## Conventions
@.claude/rules/architecture.md
diff --git a/config/version.yaml b/config/version.yaml
index 3fde966..8e6a858 100644
--- a/config/version.yaml
+++ b/config/version.yaml
@@ -1,2 +1,2 @@
parameters:
- app.version: '0.1.49'
+ app.version: '0.1.53'
diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json
index a71d4db..77849a7 100644
--- a/frontend/i18n/locales/fr.json
+++ b/frontend/i18n/locales/fr.json
@@ -230,6 +230,39 @@
"updated": "Site mis à jour avec succès",
"deleted": "Site supprimé avec succès"
}
+ },
+ "categories": {
+ "title": "Gestion des catégories",
+ "newCategory": "Ajouter",
+ "editCategory": "Modifier la catégorie",
+ "createCategory": "Créer une catégorie",
+ "viewCategory": "Détail de la catégorie",
+ "noCategories": "Aucune catégorie pour l'instant.",
+ "table": {
+ "name": "Nom",
+ "type": "Type"
+ },
+ "form": {
+ "name": "Nom",
+ "type": "Type de catégorie",
+ "typePlaceholder": "Sélectionner un type"
+ },
+ "validation": {
+ "nameRequired": "Le nom est obligatoire.",
+ "nameLength": "Le nom doit faire entre 2 et 120 caractères.",
+ "typeRequired": "Le type de catégorie est obligatoire."
+ },
+ "delete": {
+ "title": "Supprimer la catégorie",
+ "message": "Êtes-vous sûr de vouloir supprimer la catégorie \"{name}\" ? Cette action est irréversible."
+ },
+ "toast": {
+ "created": "Catégorie créée avec succès",
+ "updated": "Catégorie mise à jour avec succès",
+ "deleted": "Catégorie supprimée avec succès",
+ "duplicate": "Une catégorie nommée « {name} » existe déjà pour ce type.",
+ "typesLoadFailed": "Impossible de charger les types de catégorie. Réessayez."
+ }
}
}
}
diff --git a/frontend/modules/catalog/components/CategoryDeleteModal.vue b/frontend/modules/catalog/components/CategoryDeleteModal.vue
new file mode 100644
index 0000000..5842904
--- /dev/null
+++ b/frontend/modules/catalog/components/CategoryDeleteModal.vue
@@ -0,0 +1,48 @@
+
+
+
+