Compare commits

..

4 Commits

Author SHA1 Message Date
Matthieu ae70e90db2 feat(catalog) : implement CategoryProvider and CategoryProcessor with soft delete and 409 mapping
- CategoryProvider applique le filtre soft-delete par defaut (RG-1.08), respecte ?includeDeleted=true (RG-1.09), trie par name ASC (RG-1.10) et renvoie 404 sur Get d'une soft-deleted hors flag (RG-1.11). Cable aussi sur Patch+Delete pour fermer la fuite sur PATCH.
- CategoryProcessor : trim du name au POST/PATCH (RG-1.03), conversion DELETE en UPDATE avec deletedAt=now() via persist_processor (RG-1.12), mapping UniqueConstraintViolationException -> HTTP 409 avec le message attendu (RG-1.07).
- Cablage des Provider/Processor dans #[ApiResource] de Category.
- DoctrineCategoryRepository expose createListQueryBuilder($includeDeleted) pour le Provider.
2026-05-28 11:40:40 +02:00
Matthieu e0575de646 feat(catalog) : add Category and CategoryType entities with Timestampable+Blamable pattern
- Category : ApiResource (5 ops), #[Auditable], TimestampableBlamableTrait +
  interfaces, asserts (NotBlank/Length sur name, NotNull sur categoryType),
  soft delete via deletedAt, groupes category:read/category:write + default:read
- CategoryType : referentiel statique en lecture seule (GetCollection + Get),
  embarque dans Category via le groupe category:read
- Repositories : interfaces Domain + impl Doctrine pour les deux entites
- doctrine.yaml : mapping ORM Catalog inconditionnel (miroir Sites) pour que
  l'ORM reconnaisse les entites ; declaration du module = ticket 0.5
- EntitiesAreTimestampableBlamableTest : CategoryType ajoute a EXCLUDED (RG-1.17)
- Index nommes declares sur les entites (match migration) ; index unique partiel
  uq_category_name_type_active possede par la migration seule
2026-05-28 11:40:40 +02:00
Matthieu 44d8c77718 ci : retire tout le caching (backend de cache runner injoignable, timeout 4m30)
Les logs montrent que chaque operation actions/cache attend ~4m30 avant
ETIMEDOUT sur le serveur de cache du runner Gitea (51.91.78.99:39531) :
- cache: npm de setup-node = tout le 'Setup Node 22' (271s)
- cache node_modules et cache .nuxt : timeouts additionnels
- cache Composer cote backend : meme risque

Node 22 est deja dans le tool-cache (install instantane), npm ci a froid
~30s, build ~20s : le caching n'apportait rien ici. A re-activer si le
serveur de cache du runner est repare.
2026-05-28 11:40:40 +02:00
Matthieu 670c58e02e ci(frontend) : accelere le job PR (nuxt build + cache node_modules & build Nuxt/Vite)
- remplace build:dist (nuxt generate + prerender inutile en SPA) par nuxt build
- cache node_modules sur hash du lockfile, npm ci uniquement en cache miss
- regenere les types Nuxt (postinstall) en cache hit
- cache des artefacts .nuxt / Vite avec restore-keys pour eviter le build a froid
2026-05-28 11:40:40 +02:00
8 changed files with 5 additions and 67 deletions
-2
View File
@@ -1,7 +1,6 @@
<?php
declare(strict_types=1);
use App\Module\Catalog\CatalogModule;
use App\Module\Commercial\CommercialModule;
use App\Module\Core\CoreModule;
use App\Module\Sites\SitesModule;
@@ -10,5 +9,4 @@ return [
CoreModule::class,
CommercialModule::class,
SitesModule::class,
CatalogModule::class,
];
-7
View File
@@ -83,13 +83,6 @@ return [
'module' => 'sites',
'permission' => 'sites.view',
],
[
'label' => 'sidebar.catalog.categories',
'to' => '/admin/categories',
'icon' => 'mdi:tag-multiple-outline',
'module' => 'catalog',
'permission' => 'catalog.categories.view',
],
[
'label' => 'sidebar.core.audit_log',
'to' => '/admin/audit-log',
+1 -1
View File
@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.47'
app.version: '0.1.42'
-3
View File
@@ -32,9 +32,6 @@
},
"sites": {
"admin": "Sites"
},
"catalog": {
"categories": "Gestion des catégories"
}
},
"dashboard": {
+4 -6
View File
@@ -35,7 +35,7 @@ export interface Persona {
// sidebar-visibility pour driver la matrice. Les valeurs correspondent
// aux slugs de route (`/admin/<slug>`), volontairement stables quand
// la copie/i18n change.
expectedAdminLinks: Array<'users' | 'roles' | 'sites' | 'audit-log' | 'categories'>
expectedAdminLinks: Array<'users' | 'roles' | 'sites' | 'audit-log'>
}
const SHARED_PASSWORD = 'e2e-secret'
@@ -47,7 +47,7 @@ export const personas: Record<PersonaKey, Persona> = {
password: SHARED_PASSWORD,
isAdmin: true,
permissions: [],
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'],
expectedAdminLinks: ['users', 'roles', 'sites', 'audit-log'],
},
'user-full': {
key: 'user-full',
@@ -63,10 +63,8 @@ export const personas: Record<PersonaKey, Persona> = {
'sites.view',
'sites.manage',
'sites.bypass_scope',
'catalog.categories.view',
'catalog.categories.manage',
],
expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'],
expectedAdminLinks: ['users', 'roles', 'sites', 'audit-log'],
},
'user-readonly': {
key: 'user-readonly',
@@ -111,4 +109,4 @@ export function getPersona(key: PersonaKey): Persona {
return personas[key]
}
export const ALL_ADMIN_LINKS = ['users', 'roles', 'sites', 'categories', 'audit-log'] as const
export const ALL_ADMIN_LINKS = ['users', 'roles', 'sites', 'audit-log'] as const
-43
View File
@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Module\Catalog;
final class CatalogModule
{
public const string ID = 'catalog';
public const string LABEL = 'Catalogue';
// REQUIRED = true : Category sera FK NOT NULL cote futurs modules Tiers
// (M-Clients, M-Fournisseurs, M-Prestataires). Desactiver Catalog casserait
// tout le metier au boot Doctrine. Cf. review Tristan MR #12 + spec M0 § 2.1.
public const bool REQUIRED = true;
/**
* Liste declarative des permissions RBAC exposees par le module Catalog.
*
* Consommee par la commande `app:sync-permissions` (SyncPermissionsCommand)
* qui se charge d'upserter ces entrees dans la table `permission`, de
* reactiver les codes precedemment marques orphelins et de marquer comme
* orphelins ceux qui ont disparu du code source.
*
* La cle `module` est auto-injectee par le sync command a partir de
* `self::ID`, il est donc inutile de la repeter dans chaque entree.
*
* Convention de nommage des codes : `module.resource[.sub].action` en
* snake_case, le prefixe module devant correspondre exactement a
* `self::ID` (verifie par la commande de synchronisation).
*
* Granularite alignee sur Core (view + manage), pas view/create/edit/delete
* (cf. spec M0 § 2.7).
*
* @return array<int, array{code: string, label: string}>
*/
public static function permissions(): array
{
return [
['code' => 'catalog.categories.view', 'label' => 'Voir les categories'],
['code' => 'catalog.categories.manage', 'label' => 'Gerer les categories (creer, editer, supprimer)'],
];
}
}
@@ -29,9 +29,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
new GetCollection(
security: "is_granted('catalog.categories.view')",
normalizationContext: ['groups' => ['category_type:read']],
// Tri par defaut requis par la spec M0 § 4.6 : ordre alphabetique
// stable pour alimenter le <MalioSelect> du formulaire Category.
order: ['label' => 'ASC'],
),
new Get(
security: "is_granted('catalog.categories.view')",
@@ -184,8 +184,6 @@ final class SeedE2ECommand extends Command
'sites.view',
'sites.manage',
'sites.bypass_scope',
'catalog.categories.view',
'catalog.categories.manage',
],
],
[