Audit et complete les messages des contraintes #[Assert\*] des entites metier (pendant back du mapping d'erreur par champ front ERP-101) : - Message FR explicite sur toutes les contraintes (Email, NotBlank, Length, Bic, Iban, PositiveOrZero, Count...) des entites Client, ClientContact, ClientAddress, ClientRib, Category, Role, User. - Ajout des Assert\Length manquantes calees sur le length de la colonne ORM (telephones VARCHAR(20), siren, nTva, accountNumber, username...) : evite une erreur Postgres 500 non rattachee au champ au profit d'une 422 propre. - Locale FR globale (symfony/translation + default_locale: fr) comme filet pour les messages natifs non surcharges. - Garde-fou tests/Architecture/EntityConstraintsHaveFrenchMessageTest : echoue si une contrainte n'a pas de message FR explicite ou si Assert\Length.max diverge du length ORM (whitelist justifiee pour les formats Bic/Iban/Regex). - Test fonctionnel du JSON 422 reel (message FR + propertyPath consommable par useFormErrors cote front). - Convention documentee dans .claude/rules/backend.md. Tests : 469 verts (1793 assertions).
7.0 KiB
Frontend — Regles Nuxt 4 / Vue 3 / @malio/layer-ui
Base
- TypeScript strict
- 4 espaces d'indentation
- Commentaires (JSDoc, inline, bloc) en francais ; code (variables, types) en anglais
- Chaque module front = un layer Nuxt auto-detecte (
frontend/modules/*/nuxt.config.tsminimal)
Appels API
- Toujours
useApi()— jamais$fetch,ofetch,axiosen direct useApi()gere : cookies JWT, erreurs, toasts i18n, parsing Hydra
Stores (Pinia)
useAuthStorepour l'authentificationuseUiStorepour l'etat UI global (sidebar, modales, etc.)- Composables avec state singleton (refs module-level) : exposer une fonction
reset*()et la rappeler au logout (ex:useSidebar().resetSidebar())
Middlewares globaux
auth.global.tsprotege les routes + charge la sidebar apres loginmodules.global.tsredirige si la route demandee est dansdisabledRoutes
i18n et sidebar
- Labels de sidebar = cles i18n
sidebar.<module>.*, jamais du texte brut - Le layout
default.vueappliquet()sur les labels retournes par/api/sidebar - Traductions dans
frontend/i18n/locales/
Composants formulaires — @malio/layer-ui obligatoire
Tout champ de formulaire / filtre doit utiliser les composants Malio* plutot que <input> / <select> bruts :
MalioInputText,MalioInputNumber,MalioInputAmount,MalioInputPassword,MalioInputTextAreaMalioSelect,MalioSelectCheckbox,MalioCheckbox,MalioRadioButtonMalioInputUpload,MalioTimeMalioButton,MalioButtonIcon
Exceptions autorisees (commenter un // TODO pour migrer quand la lib couvrira le cas) :
- Type non couvert :
datetime-local,date, color picker, file drag & drop - Bug connu bloquant (ex:
MalioSelectavec options string) — documenter le bug en commentaire
Toute autre exception requiert validation avant merge.
Tableaux de donnees — MalioDataTable obligatoire
Tout affichage LISTE tabulaire (donnees metier paginees, CRUD admin) doit passer par MalioDataTable :
- Pagination integree
- Slots
#header-*pour filtres,#cell-*pour rendu custom - Pas de
<table>brut avec pagination custom
Exception : tableaux purement presentationnels non paginables (diff field/old/new, grille de comparaison, matrice RBAC d'admin, etc.) peuvent rester en <table> HTML brut.
Listes paginees (standard) — usePaginatedList obligatoire
Toute liste qui consomme une GetCollection API doit passer par usePaginatedList (frontend/shared/composables/usePaginatedList.ts). Le composable est le pendant front de la regle ABSOLUE n°13 (« toute collection est paginee cote back ») : il consomme l'envelope Hydra (member / totalItems / view) et expose un etat reactif a brancher directement sur MalioDataTable.
Pattern de reference :
const {
items,
totalItems,
currentPage,
itemsPerPage,
itemsPerPageOptions,
fetch: loadList,
goToPage,
setItemsPerPage,
} = usePaginatedList<MyEntity>({ url: '/my-resources' })
onMounted(loadList)
<MalioDataTable
:columns="columns"
:items="rows"
:total-items="totalItems"
:page="currentPage"
:per-page="itemsPerPage"
:per-page-options="itemsPerPageOptions"
:empty-message="t('foo.empty')"
@update:page="goToPage"
@update:per-page="setItemsPerPage"
/>
Garanties offertes par le composable :
- Force
Accept: application/ld+json→ API Platform 4 renvoie bienmember/totalItems(sans Accept, retour tableau plat sans pagination). - Defaut 10 items/page, choix client 10 / 25 / 50, aligne sur le defaut serveur.
- Mutation
setFilters/setSort/setItemsPerPage→ retombe systematiquement en page 1. - Cas limite « page hors borne apres filtre » : retombe automatiquement sur la derniere page valide (
tests/usePaginatedList.test.ts). - Etat 100 % local (refs internes a l'instance) — jamais reflete dans l'URL, conformement a la regle « Etat des tableaux — pas de persistance URL » ci-dessous.
A NE PAS faire :
- Charger une collection complete via
?itemsPerPage=999pour bypasser la pagination. Le seul cas legitime de retour complet est l'alimentation d'un<select>sur un referentiel ≤ quelques dizaines d'entrees, et il passe par?pagination=false(echappatoire prevue parpagination_client_enabled: true). - Reimplementer la pagination prev/next a la main au-dessus de
MalioDataTable— le composant porte deja le selecteur items/page et les boutons Prev/Next. - Persister
page/tri/filtredans la query string — meme regle que pour<MalioDataTable>brut (cf. section suivante).
Etat des tableaux — pas de persistance URL
Interdit de persister l'etat d'un tableau (filtres, pagination, tri par colonne, selection, ligne active, scroll) dans la query string ou de le reinjecter depuis route.query au montage.
- L'etat vit uniquement dans le composant (
reactive,reflocales) - Seuls les deep links "de navigation metier" (ex: ouvrir un detail precis
/users/42) sont dans l'URL - Exceptions autorisees sur demande explicite de l'utilisateur
Validation des formulaires (standard ERP-101)
Regle transverse a TOUS les formulaires front (et a rappeler a l'ecriture de chaque ticket back/front portant un formulaire). Decidee en ERP-101 (declencheur : ecran « Ajouter un client » ERP-63).
- Champs obligatoires : prop
requireddu composantMalio*+ etoile (asterisque) rouge dans le label. Ne JAMAIS griser le bouton « Valider » sans feedback : bouton toujours actif + erreurs affichees sous les champs. - Couche de validation autoritaire = le back : les RG sont re-validees serveur (mode strict). Au
422, mapperviolations[].propertyPathvers la properrordu champ viaextractApiViolations(deja utilise paruseCategoryForm). Zero duplication de RG, zero drift. - Feedback instantane au blur : uniquement requis / min / max / format (pas de re-implementation des RG metier cote front).
- Regles front-only : celles sans equivalent back (ex. FK nullable cote back mais obligatoire selon un choix UI) sont validees et affichees cote front.
- Email — PAS de masque : un email n'a pas de structure fixe. Normalisation via la prop
lowercasedeMalioInputEmail(trim + suppression des espaces + lowercase, coherent avec la normalisation serveur RG-1.21). Le format est valide par la properror(violations serveur ou check au blur), jamais par un masque. Retirer tout shaping email ad hoc des ecrans. - Contrat back attendu : tout
422issu d'un Processor/Validator doit porterviolations[].propertyPathaligne sur les noms de champs du formulaire, pour etre consommable parextractApiViolations. - Dependance : le branchement des props
requiredsuppose@malio/layer-uia jour (propsrequired+ etoile — MUI-41 / ERP-101).
Interdits
modules-loader.ts,.module.ts— le scan des layers est automatique- Hardcode de la sidebar cote front — elle vient de
/api/sidebar - Edition manuelle de
extendsdansfrontend/nuxt.config.ts— les layers sont scannes - Import d'un module front depuis un autre module — passer par
frontend/shared/