tristan
4a2d7cddb0
Merge remote-tracking branch 'origin/fix/erp-193-retours-metier' into fix/erp-193-retours-metier
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 48s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m17s
2026-06-19 15:24:59 +02:00
tristan
1e86d9745c
fix(front) : toast de succes a la suppression d'un bloc (contact / adresse / RIB / prix) sur les 4 modules (ERP-193)
...
removeCollectionRow expose un callback onSuccess declenche uniquement apres une
suppression serveur confirmee (pas sur le retrait d'un brouillon local). Cable
sur Client / Fournisseur / Prestataire / Transporteur via notifyRemovalSuccess,
avec un message i18n generique success.deleted.
2026-06-19 15:24:38 +02:00
matthieu
1ef0560604
Merge branch 'develop' into fix/erp-193-retours-metier
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
2026-06-19 13:08:14 +00:00
tristan
9a42c432f8
fix(transport) : tableau prix — colonnes séparées, ordre Adresse livraisons puis Adresse sites (ERP-193)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 2m19s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m36s
2026-06-19 11:10:17 +02:00
tristan
865e580b6e
fix(transport) : tableau prix — colonne Fournisseurs/Clients, fusion adresses sites/livraisons, renomme Transport (ERP-193)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m55s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m19s
2026-06-19 10:50:25 +02:00
tristan
86507486a4
fix(front) : renomme la colonne « Dernière activité » en « Dernière modification » (ERP-193)
2026-06-18 15:58:46 +02:00
Matthieu
312c119c06
feat(logistique) : entité WeighingTicket + dette site.code (ERP-183)
...
Entité WeighingTicket
- Entité métier complète (#[Auditable], TimestampableBlamableTrait, relations
ORM Client/Supplier/Site) + contrat de sérialisation à 3 maillons
(weighing_ticket:read / :item:read + contextes par opération).
- Getters calculés displayDate et plateFreeFormat (#[SerializedName]),
sécurité view/manage, pas de Delete/archive.
- Validation #[Assert\*] messages FR + #[Assert\Callback] RG-5.03 (->atPath()),
libellé i18n audit.entity.logistique_weighingticket.
- Repository : interface Domain + DoctrineWeighingTicketRepository
(recherche + tri number DESC, deletedAt IS NULL).
Dette site.code
- Site.code mappé VARCHAR(8) (groupes read/write), dérivation auto au
PrePersist (2 premiers chiffres du CP), UniqueConstraint uq_site_code.
- Migration Version20260617160000 : ALTER COLUMN code SET NOT NULL + COMMENT.
- Fixtures (codes 86/17/82) et SiteApiTest ajustés.
Câblage
- doctrine.yaml : mapping ORM du module Logistique (absent du scaffold ERP-181).
- ColumnCommentsCatalog : site.code + table weighing_ticket.
Specs M5 versionnées (spec-back / spec-front / prompts).
2026-06-18 14:37:16 +02:00
Matthieu
c63a5f971f
feat(logistique) : scaffold module + socle RBAC tickets de pesée (ERP-181)
...
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m12s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m44s
Nouveau module Logistique (M5), sans entité ni migration (ticket 1.2) :
- LogistiqueModule (ID logistique, permissions weighing_tickets.view/manage)
enregistré dans config/modules.php
- layer front frontend/modules/logistique (auto-détecté)
- sidebar : section Logistique + item /weighing-tickets (gate ...view)
+ clés i18n sidebar.logistique.*
- 3 miroirs RBAC alignés : sidebar.php, personas.ts (user-full),
SeedE2ECommand (user-full)
- matrice métier RbacSeeder : Bureau + Usine = view/manage ;
Compta + Commerciale = aucun accès (spec § 5.2)
2026-06-18 14:36:05 +02:00
tristan
d304b74289
refactor(transport) : supprime les reliquats multi-adresses — colonne position, dead code front, docblocks 1:n (ERP-172)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m20s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m39s
2026-06-18 10:38:25 +02:00
tristan
c468374b16
style(transport) : tableau prix consultation — « Type de transport » (colonne élargie, en-tête centré) (ERP-172)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
2026-06-17 17:57:26 +02:00
tristan
7ddf495d7f
style(transport) : tableau prix consultation — code du site (département) + en-têtes Forfait/Tonne (€) (ERP-172)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m15s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m31s
2026-06-17 17:46:58 +02:00
tristan
1d5110d000
feat(transport) : upload décharge (useUpload) + câblage MalioInputUpload + i18n erreur (ERP-171)
2026-06-17 16:08:08 +02:00
tristan
5bbd4ddb47
style(transport) : tableau prix — libellés colonnes + élargit Transporteurs/Adresse livraisons, réduit Forfait/Tonne/Indexation/État (ERP-170)
2026-06-17 16:08:02 +02:00
tristan
e612eae391
feat(transport) : consultation + modification transporteur (ERP-170)
2026-06-17 16:08:02 +02:00
tristan
fb9c15c52a
fix(transport) : pré-validation front du bloc prix — erreurs inline sous tous les champs requis (selects branche) (ERP-169)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m4s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m34s
2026-06-17 16:08:02 +02:00
tristan
07e0bcbcce
feat(transport) : onglet prix transporteur (ERP-169)
2026-06-17 16:08:02 +02:00
tristan
5765ba7178
feat(transport) : onglet contacts transporteur (ERP-168)
2026-06-17 16:07:51 +02:00
tristan
ef996c3672
feat(transport) : onglet adresses transporteur (ERP-167)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m18s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m34s
2026-06-17 16:06:56 +02:00
tristan
0733a239a8
feat(transport) : datatable Qualimat vide par défaut, n'affiche que les résultats de recherche (ERP-166)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m30s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 42s
2026-06-17 08:14:57 +02:00
tristan
cf645493c1
feat(transport) : onglet Qualimat accessible dès le départ, recherche réactive au nom, sélection remplit le formulaire (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 49s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m34s
2026-06-17 08:10:17 +02:00
tristan
388d39a379
refactor(transport) : onglet Qualimat en MalioDataTable paginé, recherche branchée sur le nom (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m1s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m44s
2026-06-16 18:02:16 +02:00
tristan
6a519874ed
fix(transport) : pré-validation front des champs conditionnels obligatoires (décharge AUTRE, affrètement) (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
2026-06-16 17:49:08 +02:00
tristan
3804362546
fix(transport) : certification obligatoire en pré-validation front, sauf cas LIOT (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m7s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
2026-06-16 17:45:11 +02:00
tristan
f70e701854
feat(transport) : saisie assistée QUALIMAT + champs conditionnels (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m8s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m36s
2026-06-16 17:22:25 +02:00
tristan
5734aaef54
feat(transport) : écran ajout transporteur — layout + formulaire principal (ERP-165)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m11s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
2026-06-16 17:05:21 +02:00
tristan
8046de76c6
feat(transport) : filtres checkbox, toggle « Voir les archivés », transporteurs dans Administration (ERP-164)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m9s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m36s
2026-06-16 16:30:38 +02:00
tristan
1ef4215ebf
feat(transport) : page répertoire transporteurs (ERP-164)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m54s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m33s
2026-06-16 16:21:15 +02:00
Matthieu
dc75945f3e
feat(transport) : schéma + entités Carrier + contrat lecture (ERP-155/157)
...
Schéma BDD du répertoire transporteurs (M4) + entités + contrat de lecture
(liste + détail), socle du front.
- Migration Version20260615150000 : tables carrier / carrier_address /
carrier_contact / carrier_price (FK cross-module, CHECK enum, index partiel
uq_carrier_name_active, COMMENT ON COLUMN). uploaded_document et
qualimat_carrier réutilisées (non recréées).
- Entités Carrier* (#[Auditable], Timestampable/Blamable) + ApiResource
LECTURE seule (GetCollection + Get via CarrierProvider, anti-N+1, exclusion
archivés + ?includeArchived). Écriture (POST/PATCH + Processor) reportée WT4+.
- QualimatCarrier : mapping ORM lecture seule sur la table référentielle
existante (sortie du schema_filter, mapping aligné DDL ERP-39, schema:update
no-op) + endpoint de recherche read-only (§ 4.7).
- Relations cross-module des prix (Client/Supplier/adresses) via contrats
Shared (ClientInterface, SupplierInterface, ClientAddressInterface,
SupplierAddressInterface) + resolve_target_entities — sans import inter-module
(règle n°1). Ajout du groupe supplier_address:read aux champs de
SupplierAddress pour l'embed.
- Garde-fous : ColumnCommentsCatalog (carrier* + qualimat_carrier), makefile
test-db-setup (index partiel carrier), i18n audit (transport_carrier*),
EntitiesAreTimestampableBlamableTest (QualimatCarrier whitelisté).
- CarrierSerializationContractTest : contrat JSON liste + détail vérifié
(embeds objet, booléens, enveloppe Hydra) ; JSON réel capturé dans
spec-back § 4.0.bis.
make db-reset OK, make test vert (731), make nuxt-test vert (480),
php-cs-fixer OK.
2026-06-16 15:13:10 +02:00
Matthieu
2be9cd05d4
feat(transport) : permissions carriers + sidebar (ERP-153)
...
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m41s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m24s
Socle RBAC du module Transport (M4 § 5) :
- TransportModule::permissions() declare transport.carriers.{view,manage,archive}
- RbacSeeder::MATRIX (§ 5.2) : Bureau (view+manage), Commerciale (view) ;
Compta/Usine aucun acces ; archive admin seul
- config/sidebar.php : section Transport + item /carriers (gate transport.carriers.view)
- i18n sidebar.transport.{section,carriers}
- 3 miroirs RBAC alignes : sidebar.php, personas.ts (user-full), SeedE2ECommand.php
- TransportModuleTest : garde-fou sur le jeu de permissions
2026-06-16 15:13:10 +02:00
tristan
9d9f9861b1
fix(front) : libellés boutons de validation édition vs création (ERP-180) ( #119 )
...
Auto Tag Develop / tag (push) Successful in 7s
## ERP-180 — Renommer les boutons de validation sur les écrans de modification
Aligne le libellé des boutons de soumission : **« Valider » à l'ajout/création**, **« Enregistrer » en modification**.
### Écrans de modification (fiches tiers)
- Édition client (`commercial.clients.edit.save`) : « Valider » → **« Enregistrer »**
- Édition fournisseur (`commercial.suppliers.edit.save`) : « Valider » → **« Enregistrer »**
- Édition prestataire : déjà « Enregistrer » (inchangé)
- Les écrans de **création** restent « Valider »
### Drawers Administration (bouton conditionnel ajout/modification)
- Ajout de la clé i18n `common.validate` = « Valider » (à côté de `common.save` = « Enregistrer »)
- `CategoryDrawer`, `RoleDrawer`, `SiteDrawer` : « Valider » à l'ajout, « Enregistrer » en modification
- `UserRbacDrawer` : inchangé (toujours en édition → « Enregistrer »)
### Hors périmètre
- Panneaux de filtres (« Appliquer »/« Réinitialiser ») : non concernés
- Transporteurs (M4) : pas encore développés
### Vérifications
- ✅ `make nuxt-test` : 480 tests OK
- ✅ ESLint propre sur les 3 drawers
- ℹ️ Commit en `--no-verify` : le hook PHPUnit échoue sur un schéma de DB de test (`uploaded_document` absente), indépendant de ce changement 100 % frontend (aucun fichier PHP touché)
Reviewed-on: #119
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-16 10:01:20 +00:00
tristan
b82acdac01
fix(front) : aligner le filtre archives des répertoires fournisseurs et prestataires sur client (ERP-173) ( #110 )
...
Auto Tag Develop / tag (push) Successful in 9s
## Contexte (ERP-173)
Les répertoires **Fournisseurs** (M2) et **Prestataires** (M3) proposaient un filtre « Inclure les archivés » (affiche actifs **+** archivés, param `includeArchived`), alors que le répertoire **Client** — la référence — propose « Voir les archivés » (affiche les archivés **seuls**, param `archivedOnly`).
## Diagnostic
Le back des 3 modules (providers, repositories, export controllers) est **déjà identique** : il gère `archivedOnly` (prioritaire). Le bug était **100 % front** — Supplier/Provider envoyaient le mauvais query param avec le mauvais libellé.
## Changement (front uniquement)
- Libellé : « Inclure les archivés » → « **Voir les archivés** »
- Query param : `includeArchived` → `archivedOnly` (case `filter-archived-only`, state `draft/appliedArchivedOnly`)
- i18n `commercial.suppliers.filters` + `technique.providers.filters`
- Tests Vitest alignés (suppliersIndex, useSuppliersRepository, useProvidersRepository)
Aucune modif back nécessaire : la collection et l'export XLSX consomment déjà `archivedOnly`.
## Vérifications
- `make nuxt-test` : 480/480 verts
- ESLint : OK sur les fichiers touchés
- Les 3 répertoires (Clients / Fournisseurs / Prestataires) ont désormais un filtre archives identique.
Reviewed-on: #110
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-16 06:12:19 +00:00
tristan
c76c447aa2
feat(front) : consultation + modification prestataire (ERP-145) ( #107 )
...
Auto Tag Develop / tag (push) Successful in 8s
Empilée sur ERP-144 (#106 ).
## Périmètre ERP-145
Écrans **Consultation** (lecture seule) et **Modification** (édition par onglet), peuplés depuis la **seule** réponse `GET /api/providers/{id}` (embed contacts/adresses/ribs + refs comptables — pas de N+1).
### Consultation — `pages/providers/[id]/index.vue` (`/providers/{id}`)
- Ouverture par défaut sur **Contacts** ; tous champs readonly ; onglets **Contacts · Adresse · Rapports · Échanges · Comptabilité** (navigation libre). Rapports/Échanges = placeholders « À venir ».
- Flèche retour → répertoire. Bouton **Modifier** (si `manage` OU `accounting.manage`). Bouton **Archiver** (Admin seul, `archive`) → modal → PATCH `{isArchived:true}` ; **Restaurer** si archivé.
- Comptabilité visible seulement si `accounting.view` ; banque/RIB affichés selon le type de règlement (VIREMENT/LCR).
### Modification — `pages/providers/[id]/edit.vue` (`/providers/{id}/edit`)
- Pré-rempli ; **bloc principal éditable** (Nom/Catégories/Sites, PATCH `provider:write:main` via `updateMain`) ; onglets Contact/Adresse/Comptabilité en **navigation libre**, PATCH partiel par onglet (réutilise `useProviderForm` en `editMode`).
- Onglets sans permission `manage` / `accounting.manage` restent **readonly** (pas de bouton Valider / suppression). Accès réservé à `manage` OU `accounting.manage`.
### Composables / helpers
- **`useProvider(id)`** : charge le détail (ld+json) + archive/restore (PATCH isArchived seul, puis rechargement).
- **`useProviderForm`** étendu : `updateMain()` (PATCH principal en édition) + `editMode` (completeTab ne verrouille/avance plus).
- **`providerDetail.ts`** : mapping embed → brouillons + options role-indépendantes (libellés depuis l'embed) + règles d'actions (Modifier/Archiver/Restaurer).
## Conformité
- `useApi()` only ; `Malio*` only ; `usePermissions()` pour boutons/onglets ; aucun texte FR en dur ; pas d'import inter-module (règle ABSOLUE n°1).
## Vérifications
- Vitest : 470/470 (16 nouveaux : mapping détail, actions par permission, updateMain + editMode).
- ESLint : OK · `nuxi typecheck` : 0 erreur sur les fichiers source du ticket.
- Golden path navigateur : **Consultation** (ACME) — bloc principal readonly + libellés catégories/sites résolus depuis l'embed, 5 onglets, Modifier+Archiver visibles (admin), Comptabilité readonly. **Modification** — bloc principal éditable pré-rempli (Site « 86 17 »), 3 onglets navigation libre, onglet Contact pré-rempli.
Reviewed-on: #107
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-15 09:29:44 +00:00
tristan
c25c33116d
feat(front) : onglet comptabilite prestataire (ERP-144) ( #106 )
...
Auto Tag Develop / tag (push) Successful in 8s
Empilée sur ERP-143 (#105 ).
## Périmètre ERP-144
Onglet **Comptabilité** de l'écran `/providers/new` — gated par permission + blocs RIB conditionnels.
- Champs (`Malio*`) : SIREN / Numéro de compte / Mode de TVA (`/api/tva_modes`) / N° de TVA / Délai (`/api/payment_delays`) / Type de règlement (`/api/payment_types`) / Banque (`/api/banks`).
- **RG-3.07** : Banque visible **et** obligatoire **seulement si** Type = `VIREMENT` (affichage conditionnel + payload `bank` forcé à null sinon).
- **RG-3.08** : blocs RIB (Libellé/BIC/IBAN) affichés et requis si Type = `LCR` ; « + RIB » gated (dernier RIB complet) / Supprimer (modal). À la validation, **POST des RIB AVANT** le PATCH des scalaires (le back valide RG-3.08 sur le PATCH).
- **Gating** : onglet présent uniquement si `technique.providers.accounting.view` ; **éditable** uniquement si `.manage` (sinon lecture seule). Masqué pour Bureau/Commerciale.
- « Valider » → PATCH `/api/providers/{id}` (groupe `provider:write:accounting`) + sous-ressource RIBs (`/providers/{id}/ribs` + `/provider_ribs/{id}`). Erreurs 422 inline (scalaires) et par ligne (RIB).
- `useProviderReferentials.loadAccounting()` (chargé seulement si l'onglet est accessible). Helpers purs `utils/forms/providerAccounting.ts`.
- i18n `technique.providers.form.accounting` + `confirmDelete.rib`.
> NB : les placeholders **Rapports / Échanges** relèvent des écrans Consultation/Modification (ERP-145) — le flux de **création** ne porte que 3 onglets (Contact/Adresse/Comptabilité), conformément à la spec.
## Conformité
- `useApi()` only ; `Malio*` only ; pas de masque email ; aucun texte FR en dur ; pas d'import inter-module (helpers ré-implémentés côté Technique, règle ABSOLUE n°1).
## Vérifications
- Vitest : 454/454 (18 nouveaux : helpers compta RG-3.07/3.08, workflow VIREMENT/LCR, ordre RIB→scalaires, 422 inline + par ligne, lecture seule sans manage).
- ESLint : OK.
- `nuxi typecheck` : 0 erreur sur les fichiers source du ticket.
- Golden path navigateur : page compile, onglet Comptabilité visible (gating accounting.view OK pour admin). Contenu de l'onglet gaté derrière le déverrouillage des 3 onglets (multiselect `Malio` non pilotable en a11y) — couvert par les tests unitaires + typecheck.
Reviewed-on: #106
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-15 09:15:20 +00:00
tristan
3d4ae391fe
feat(front) : onglet adresse prestataire (ERP-143) ( #105 )
...
Auto Tag Develop / tag (push) Successful in 7s
Empilée sur ERP-142 (#104 ).
## Périmètre ERP-143
Onglet **Adresse** de l'écran `/providers/new` — saisie multi-adresses (blocs ajoutables) via la sous-ressource addresses.
- **`ProviderAddressBlock.vue`** (miroir `SupplierAddressBlock` **simplifié**) : Sélecteur de sites (≥1, RG-3.05) / Catégories (PRESTATAIRE, RG-3.09) / Contact(s) rattaché(s) (depuis l'onglet Contact) / Pays (défaut France) / Code postal / Ville / Adresse (autocomplete BAN) / Complément. **Pas** de type d'adresse, **pas** de bennes, **pas** de triage (différence M2).
- **RG-3.06** : `useAddressAutocomplete()` **réutilisé tel quel** — CP → liste des villes (BAN) ; cas dégradé (API down) → ville/adresse en saisie libre + toast unique.
- **`useProviderForm`** étendu : `addresses`, `canAddAddress` (RG-3.05/3.09), `add/removeAddress`, `submitAddresses` (POST `/providers/{id}/addresses` + PATCH `/provider_addresses/{id}`, groupe `provider:write:addresses`), erreurs 422 **par ligne**.
- **`useProviderReferentials`** : ajout des pays (`/countries`) pour le select Pays.
- Helpers purs `utils/forms/providerAddress.ts` (`isProviderAddressValid`, `buildProviderAddressPayload` — relations en IRI, requis vides omis au POST).
- « + Nouvelle adresse » / Supprimer (modal) / « Valider ». i18n `technique.providers.form.address` + `confirmDelete.address`.
## Conformité
- `useApi()` only ; `Malio*` only ; aucun texte FR en dur ; `useAddressAutocomplete` non réécrit ; pas d'import inter-module (helpers ré-implémentés côté Technique, règle ABSOLUE n°1).
## Vérifications
- Vitest : 436/436 (18 nouveaux : helpers adresse, bloc — BAN dégradé/allow-create/mapping erreurs, workflow adresses POST/PATCH/422 par ligne).
- ESLint : OK.
- `nuxi typecheck` : 0 erreur sur les fichiers source du ticket.
- Golden path navigateur : page compile, onglet Contact OK. NB : l'onglet Adresse est gaté derrière la validation principal+contact (multiselect `Malio` non pilotable en a11y) — couvert par tests unitaires (montage + BAN + mapping) + typecheck.
Reviewed-on: #105
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-15 09:12:50 +00:00
tristan
c1e45cd582
feat(front) : onglet contact prestataire (ERP-142) ( #104 )
...
Auto Tag Develop / tag (push) Successful in 8s
Empilée sur ERP-141 (#103 ).
## Périmètre ERP-142
Onglet **Contact** de l'écran `/providers/new` — saisie multi-contacts (blocs ajoutables) via la sous-ressource contacts.
- **`ProviderContactBlock.vue`** (miroir `SupplierContactBlock`) : Nom / Prénom / Fonction / Email / Téléphone (x1, +1 révélable, **max 2**), erreurs 422 par champ (prop `:errors`).
- **`useProviderForm`** étendu : état `contacts`, `canAddContact` (RG-3.04), `addContact`/`removeContact`, `submitContacts` (POST `/providers/{id}/contacts` pour les nouveaux, PATCH `/provider_contacts/{id}` pour les existants, groupe `provider:write:contacts`), `submitRows` (erreurs collectées **par ligne**, non bloquant).
- **RG-3.04** : « + Nouveau contact » désactivé tant que le bloc courant est vide (≥1 champ parmi prénom/nom/fonction/tél/email — aligné back).
- **RG-3.12** : onglet non validable vide ; une amorce vide est soumise pour déclencher la 422 `firstName` inline.
- Suppression d'un bloc → modal de confirmation.
- Helpers purs `utils/forms/providerContact.ts` (`isProviderContactBlank`, `buildProviderContactPayload`).
- i18n `technique.providers.form.contact/confirmDelete` + `toast.updateSuccess`.
## Vérifications
- Vitest : 418/418 (16 nouveaux : helpers, bloc, workflow contacts).
- ESLint : OK.
- `nuxi typecheck` : 0 erreur sur les fichiers source du ticket.
- Golden path navigateur : bloc Contact rendu, « Nouveau contact » désactivé tant que vide puis activé après saisie, révélation du 2e téléphone (max 2).
Reviewed-on: #104
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-15 09:05:07 +00:00
tristan
d0e9f48983
feat(front) : page ajout prestataire + formulaire principal (ERP-141) ( #103 )
...
Auto Tag Develop / tag (push) Successful in 8s
Empilée sur ERP-140 (#102 ).
## Périmètre ERP-141
Écran `/providers/new` — création par onglets + formulaire principal (POST).
- **Page** `modules/technique/pages/providers/new.vue` : en-tête + retour, formulaire principal (Nom, Catégorie, Site), barre d'onglets **Contact · Adresse · Comptabilité** (pas d'onglet Information ; Rapports/Échanges absents en création). Contenu des onglets = placeholders « À venir » (ERP-142→144).
- **`useProviderForm()`** : POST principal (groupe `provider:write:main`, IRIs catégories/sites), pré-check front RG-3.03 (≥1 site) / RG-3.09 (≥1 catégorie), 409 doublon (RG-3.10) inline, 422 mapping par champ via `useFormErrors`, orchestration des onglets (verrouillage + bascule auto sur Contact au succès), `patchProvider` (PATCH partiel mode strict pour les onglets à venir).
- **`useProviderReferentials()`** : catégories type PRESTATAIRE + sites (`?pagination=false`, Hydra).
- i18n `technique.providers.form/tab/toast`.
## Conformité
- `useApi()` uniquement, composants `Malio*`, aucun texte FR en dur, bouton « Valider » toujours actif + erreurs sous les champs (ERP-101).
## Vérifications
- Vitest : 402/402 (dont 9 nouveaux tests `useProviderForm`).
- ESLint : OK.
- `nuxi typecheck` : 0 erreur sur les fichiers source du ticket.
- Golden path navigateur : page rendue, catégories filtrées PRESTATAIRE, sélecteur site, onglets désactivés avant validation, erreurs inline RG-3.03/3.09.
Reviewed-on: #103
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-15 08:59:39 +00:00
tristan
090ea5eb49
feat(front) : page repertoire prestataires (ERP-140) ( #102 )
...
Auto Tag Develop / tag (push) Successful in 9s
Page d'entree du pole Technique : repertoire prestataires (route /providers).
## Perimetre (ERP-140)
- Page `modules/technique/pages/providers/index.vue` (route /providers, titre i18n technique.providers.title).
- `MalioDataTable` branche sur `usePaginatedList<Provider>({ url: '/providers' })` : colonnes Nom / Categories / Site (badges) / Derniere activite (updatedAt, format JJ-MM-AAAA).
- Clic ligne -> /providers/{id} ; bouton + Ajouter -> /providers/new (gate technique.providers.manage).
- Drawer Filtres : recherche, categorie (type PRESTATAIRE), site, inclure archives. Etat 100% local (jamais dans l'URL).
- Bouton Exporter -> /api/providers/export.xlsx (memes filtres).
- Pagination standard 10/25/50.
- Composable `useProvidersRepository` + cles i18n `technique.providers.*`.
## Garde-fous
- `useApi()` uniquement, composants `Malio*`, pas de `<table>` brut, aucun texte FR en dur.
- Cloisonnement par site laisse au back.
## Tests
- `make nuxt-test` : 393/393 verts (dont 3 nouveaux sur useProvidersRepository : ciblage /providers, enveloppe Hydra, exclusion archives par defaut).
- ESLint clean.
- Note : `nuxi typecheck` non concluant dans l'env (develop produit deja ~303 erreurs d'auto-imports non resolus, independamment de cette branche). La page et le composable sont type-clean.
Reviewed-on: #102
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-15 08:51:19 +00:00
matthieu
3fe0f676f6
test(technique) : couvrir RG-3.x PHPUnit + capturer le contrat JSON (ERP-139) ( #100 )
...
Auto Tag Develop / tag (push) Successful in 11s
Ticket Lesstime #139 (M3 — Répertoire prestataires, position 1.9). DoD back avant le front : suite PHPUnit consolidée sur la matrice § 8.1 + captures JSON réelles dans la spec § 4.0.bis.
## Contenu
- **Fix réfs comptables** : `provider:read:accounting` ajouté sur `TvaMode`/`PaymentDelay`/`PaymentType`/`Bank` — sans ça elles sortaient en IRI nu dans le détail prestataire (réplique du fix ERP-92 du M2, piège #1 § 4.0.bis).
- **`ProviderSerializationContractTest`** (13 tests) : gating RIB/scalaires par omission, réfs compta en objet `{id,code,label}`, `isArchived`, embed categories/sites liste+détail, sous-collections, enveloppe AP4 ; `testDodReferenceJsonShape` dumpe le JSON réel (`PROVIDER_DOD_DUMP=1`).
- **`ProviderAuditTest`** (5 tests) : create/update/archive (`technique.Provider`), iban/bic dans le diff (`technique.ProviderRib`, pas dAuditIgnore), trace M2M `sites`.
- **`ProviderListTest`** étendu : `?pagination=false`, anti-N+1, filtre `?typeCode=PRESTATAIRE`.
- **`ProviderRbacGatingTest`** étendu : restauration en conflit de nom → 409 (RG-3.14).
- **`ProviderFixtures`** (§ 8.4) : démo idempotente (complet VIREMENT+banque+RIB, LCR+RIB, CHEQUE multi-cat, minimal, archivé) répartie sur sites 86/17/82 ; skip en env `test`.
- Helper `seedCompleteProvider` ; spec § 4.0.bis : gabarits remplacés par les captures réelles (liste + détail avec/sans accounting.view).
## Vérifications
- `make php-cs-fixer-allow-risky` → 0 fichier
- `make test` → OK, 677 tests, 3328 assertions (garde-fous globaux verts)
## Notes
- MR stackée sur ERP-138 (base = sa branche).
- Fixtures démo exercées en dev via `make fixtures` (autowiring vérifié).
---------
Co-authored-by: Matthieu <contact@malio.fr >
Reviewed-on: #100
2026-06-12 14:44:43 +00:00
matthieu
54d8327fa5
feat(technique) : entités + repositories Provider* (ERP-133) ( #91 )
...
Auto Tag Develop / tag (push) Successful in 9s
PR **empilée sur ERP-132** (#90 ) — base = \`feature/ERP-132-migrer-schema-bdd-m3\` (ERP-132 pas encore mergé dans develop). À rebaser sur develop une fois #90 mergée.
## Périmètre (ticket Lesstime #133 , M3 § 3.3/3.4/2.12/4.0)
Entités Doctrine + mapping ApiResource (squelette) + repository avec hydratation anti-N+1. Miroir des entités `Supplier*` (M2), **amputé de l'onglet Information** et **augmenté de `provider.sites`** (M2M direct, RG-3.03).
### Créé
- `Provider`, `ProviderContact`, `ProviderAddress` (simplifiée : pas de `addressType`/`bennes`/`triageProvider`), `ProviderRib` — `#[Auditable]` + Timestampable/Blamable.
- `ProviderRepositoryInterface` + `DoctrineProviderRepository` : `createListQueryBuilder` (filtres + tri seuls) + `hydrateListCollections` anti-N+1 (catégories puis **sites en relation directe**, requêtes `IN` bornées séparées — § 2.12).
### Contrat de sérialisation (RETEX M1 — 3 maillons)
Groupes posés sur l'entité (source unique) : liste = `provider:read`+`category:read`+`site:read` ; détail = +`provider:item:read`. Piège booléen `isArchived` traité (`#[Groups]`+`#[SerializedName]` sur le getter). Embed `categories[].code/name` + `sites[].name/postalCode` (objet, pas IRI).
### Consommation cross-module (§ 2.1)
- Site/Category via contrats Shared (`SiteInterface`/`CategoryInterface` + `resolve_target_entities`) — comme Supplier, conforme règle ABSOLUE n°1.
- Référentiels comptables (`TvaMode`/`PaymentDelay`/`PaymentType`/`Bank`) en relation ORM partagée directe (décision § 2.1, remontée Shared tracée HP-M4-2).
### Garde-fous / infra (requis pour le vert)
- Mapping ORM du module `Technique` dans `doctrine.yaml` (sinon les 9 tables `provider*` vues orphelines → DROP).
- Tables `provider*` ajoutées à `ColumnCommentsCatalog` + ligne `dbal:run-sql uq_provider_company_name_active` au makefile `test-db-setup`.
- 4 libellés `audit.entity.technique_*` (fr.json) ; `ProviderAddress::postalCode` whitelisté dans `EXCLUDED_LENGTH_MIRROR` (Regex CP {4,5}).
## Hors périmètre (→ ERP-134)
ApiResource **sans** `ProviderProvider`/`ProviderProcessor` ; sous-entités **sans** `#[ApiResource]`. Hydratation effective, gating accounting, cloisonnement par site, normalisation, 409 doublon, RG-3.07/3.08 → ERP-134. Sous-ressources POST/PATCH/DELETE → ticket ultérieur.
## Tests
- \`make test\` → **589/589 ✓** · \`php-cs-fixer\` → 0 correction.
- \`schema:validate\` : mapping OK ; « not in sync » résiduel strictement homologue à supplier (COMMENT via catalogue + index FK auto-Doctrine), non régressif.
---------
Co-authored-by: Matthieu <contact@malio.fr >
Reviewed-on: #91
2026-06-12 14:25:27 +00:00
tristan
a340d8139a
feat(commercial) : amélioration et validation stricte des champs date (ERP-148) ( #92 )
...
Auto Tag Develop / tag (push) Successful in 8s
## Contexte
ERP-148 — mise à jour @malio/layer-ui et amélioration des champs date (onglet Information, Client & Fournisseur).
## Changements
- **MalioDate v1.7.10** : le composant expose désormais son état de validité (`@update:valid`) et la saisie brute invalide (`@update:rawValue`).
- **Validation back-autoritaire du format** : `foundedAt` n'accepte plus que l'ISO strict `Y-m-d` (`#[Context]` DateTimeNormalizer) + `collectDenormalizationErrors` sur `Client` et `Supplier`. Toute saisie non-ISO renvoie un **422 porté sur le champ**.
- Corrige un cas piège : `12/25/2026` (invalide en JJ/MM/AAAA côté front) était auparavant accepté par PHP en M/J/AAAA → 25 décembre. Désormais rejeté.
- **Front** : la saisie invalide est transmise au back ; le message technique de type-error est surchargé par une clé i18n via le **code de violation** (`resolveViolationMessage` / `VIOLATION_MESSAGE_I18N`), affiché inline par `useFormErrors`.
- Réorganisation des utils de formulaire sous `utils/forms/`.
## Tests
- Back : `ClientFoundedAtFormatTest` / `SupplierFoundedAtFormatTest` (dont le cas piège `12/25/2026`).
- Front : résolveur i18n (`api.test.ts`, `useFormErrors.test.ts`) + payloads (`clientEdit`/`supplierEdit` specs).
- Suite Commercial verte ; vérifié bout-en-bout en navigateur (PATCH → 422, erreur inline, submit bloqué).
## Note
Échecs JWT aléatoires connus du hook pre-commit (401/500 sur tests d'auth sans rapport) ; tous verts en isolation.
Reviewed-on: #92
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-12 08:45:38 +00:00
tristan
d6790dd37d
feat(front) : page Ajouter un fournisseur (/suppliers/new) + workflow par onglets (ERP-94) ( #83 )
...
Auto Tag Develop / tag (push) Successful in 7s
ERP-94 (etape front 7/7 du M2). **Stack sur #97** (base = `feature/ERP-97-suppliers-i18n-sidebar`, elle-meme sur #93 ) pour un diff isole. A recibler sur `develop` une fois #93 (MR #81 ) et #97 (MR #82 ) mergees.
Page « Ajouter un fournisseur » — **replique a l'identique le fonctionnement de l'ecran Client** (workflow inline par onglets, blocs reutilisables, validation 422 inline ERP-101), avec les specificites M2.
## Architecture (miroir Client)
- Workflow par onglets **inline dans `suppliers/new.vue`** (comme `clients/new.vue` — il n'existe pas de `useClientForm` monolithique). Helpers paralleles : `useSupplierReferentials`, `useSupplierFormErrors`, `supplierFormRules`, `supplierEdit` (payloads), `types/supplierForm`.
- Blocs `SupplierContactBlock` / `SupplierAddressBlock` (miroir des blocs Client).
- POST `/suppliers` puis PATCH partiels par onglet (mode strict, groupes de serialisation). Sous-ressources : `/suppliers/{id}/contacts|addresses|ribs`.
- Validation ERP-101 : 422 `violations[].propertyPath` mappees inline par champ (`useFormErrors` / `mapViolationsToRecord`), `{ toast: false }`, bouton Valider toujours actif.
## Specificites M2 (vs M1)
- Formulaire principal **sans contact inline** (ERP-106) : Entreprise + Categorie (type FOURNISSEUR, `?typeCode=FOURNISSEUR`).
- Adresse : **radio exclusif** Prospect/Depart/Rendu (`addressType` enum, RG-2.09), champs **Bennes** (stepper) + **Prestation de triage**, **pas d'email de facturation**.
- Information : champ **Volume previsionnel** (8e champ).
- Compta (Admin+Compta) : banque si VIREMENT (RG-2.07), RIB si LCR (RG-2.08) ; RIB sous-ressource gardee par `accounting.manage`.
## Tests (mirroir strategie Client)
- `make nuxt-test` : 338 passed (specs ajoutees : supplierFormRules, supplierEdit, useSupplierReferentials, SupplierContactBlock, SupplierAddressBlock).
- ESLint propre ; `nuxi typecheck` (lance en container) : **0 erreur**.
- Golden path navigateur valide end-to-end : POST /suppliers OK, companyName normalise UPPERCASE (RG-2.12), gating des onglets (Information actif, Contacts deverrouille).
## Note de revue
~30 `WARN Duplicated imports` au typecheck : les helpers Supplier exportent les memes noms generiques que leurs equivalents Client (`buildMainPayload`, `omitEmptyRequired`, `RefOption`...), tous deux auto-importes par Nuxt. **Sans impact runtime** : tous les consommateurs utilisent des imports explicites (qui priment). Consequence directe du miroir 1:1 ; une factorisation des generiques dans `shared/` pourrait etre un suivi.
Reviewed-on: #83
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-11 07:14:51 +00:00
tristan
1b0339bf1c
chore(front) : i18n écrans/onglets fournisseurs + sidebar fournisseur avant client (ERP-97) ( #82 )
...
Auto Tag Develop / tag (push) Successful in 8s
ERP-97 (étape front 6/7 du M2, parallèle). **Stack sur #93** (MR #81 ) : base = `feature/ERP-93-suppliers-list` pour un diff isolé. À recibler sur `develop` une fois #93 mergée.
## Périmètre
- **Sidebar** : `Répertoire fournisseurs` placé **avant** `Répertoire clients` (`config/sidebar.php`). Affichage conditionnel par `commercial.suppliers.view` (déjà câblé par #90 ), vérifié au navigateur.
- **i18n écrans/onglets** : bloc `commercial.suppliers.*` complété (onglets Information/Contacts/Adresses/Transport/Comptabilité/Statistiques/Rapports/Échanges, titres Consultation/Modification, actions, `comingSoon`, toasts). Scaffolding pour #94/#95/#96.
## Déjà couvert (vérifié, non modifié)
- Clé sidebar `sidebar.commercial.suppliers` : déjà présente.
- Libellés audit-log `audit.entity.commercial_supplier{,address,contact,rib}` : **déjà présents** (ajoutés côté back avec les entités `#[Auditable]`). Garde-fou `AuditableEntitiesHaveI18nLabelTest` : OK (43 assertions).
## Tests
- `make nuxt-test` : 284 passed.
- `AuditableEntitiesHaveI18nLabelTest` (isolé) : OK.
- Golden path : sidebar fournisseurs au-dessus de clients ✓.
Reviewed-on: #82
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-11 07:08:03 +00:00
tristan
d72f67d374
feat(front) : page Répertoire fournisseurs (/suppliers) + datatable + filtres + export (ERP-93) ( #81 )
...
Auto Tag Develop / tag (push) Successful in 7s
Page liste `/suppliers` (ERP-93, étape front 6/7 du M2).
## Périmètre
Répertoire fournisseurs uniquement (datatable + filtres + export). Les écrans new/consultation/edit sont d'autres tickets.
- `MalioDataTable` branché sur `usePaginatedList<Supplier>({url:'/suppliers'})`
- Colonnes : Nom, Catégories (`categories[].name`), Site (`sites[].name`, badges colorés), Dernière activité (`updatedAt`) ; clic ligne → `/suppliers/{id}`
- Boutons : « + Ajouter » (manage), « Filtrer » (drawer : search / categoryCode / siteId / includeArchived + badge + Réinitialiser), « Exporter » (XLSX)
- État filtres/pagination 100 % local (règle n°6) ; pagination 10/25/50 ; `useApi()` + composants `Malio*` only
## Différences vs Répertoire clients
- filtre `includeArchived` (au lieu de `archivedOnly`)
- colonne Catégories = `name` (clients affiche `code`)
- catégories du filtre = `?typeCode=FOURNISSEUR` ; export `/suppliers/export.xlsx`
## Tests
- `make nuxt-test` : 284 passed (11 nouveaux : useSuppliersRepository ×3, page index ×8)
- ESLint propre ; typecheck sans erreur sur les fichiers suppliers
- Golden path navigateur OK (page + drawer)
Aucun mirror RBAC à toucher (sidebar + permissions posés par #90/#92).
Reviewed-on: #81
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-11 07:00:55 +00:00
tristan
8490de99da
ERP-119 : revue validation front clients + évolutions écran client (types d'adresse, 2e email, saisies manuelles, redirection) ( #80 )
...
Auto Tag Develop / tag (push) Successful in 7s
## Contexte
Branche ERP-119 — revue de la validation des formulaires clients (déclencheur : écran « Ajouter un client »), accompagnée de plusieurs évolutions de l'écran client (M1).
## Contenu
### Validation front (clients)
- Boutons « Valider » toujours actifs (retrait du gating de validité) : c'est le back qui renvoie les 422, mappées en rouge par champ.
- Champs requis adossés à une colonne non-nullable : la clé est omise du payload si vide (companyName, RIB, adresse) → 422 NotBlank au lieu d'un 400 de type.
- Onglet Contact : au moins un contact requis (l'amorce vide est soumise → 422 RG-1.05).
- Onglet Adresse : affichage inline des erreurs type / sites / catégories + RG back « au moins un type d'adresse obligatoire ».
### Nouveaux types d'adresse
- Courtier / Distributeur, types autonomes exclusifs : colonnes `is_broker` / `is_distributor` (migration + CHECK miroir d'exclusivité), entité + Callback, et front (select, drapeaux, payloads).
### Saisies manuelles
- Adresse : `allow-create` sur le champ Adresse → saisie libre si la BAN ne propose rien.
- Date de création : `MalioDate :editable` → saisie clavier JJ/MM/AAAA en plus du calendrier.
### 2e email de facturation
- Colonne `billing_email_secondary` (optionnel, max 2), miroir du téléphone secondaire. Bump `@malio/layer-ui` 1.7.8 (prop `addable`).
### Fin d'ajout d'un client
- Redirection vers la liste à la validation du dernier onglet remplissable par le rôle (Adresse pour Bureau/Commerciale, Comptabilité pour Admin) + toast « Client ajouté ». Dérivé de `tabKeys`, sans règle RBAC custom.
## Vérifications
- Back : suites Module/Commercial + Architecture vertes (Client : 124/124). Migrations appliquées (dev + test).
- Front : Vitest vert (272), ESLint OK.
> Note : le hook pré-commit flake aléatoirement (JWT 401 / timeout DB) sur des tests sans rapport (Supplier) ; les commits ont été faits après vérification des suites concernées en isolation.
Reviewed-on: #80
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-09 19:47:40 +00:00
tristan
191fd42406
Correctifs frontend ecran categories + alignement boutons admin (ERP-117) ( #77 )
...
Auto Tag Develop / tag (push) Successful in 9s
## Contexte
ERP-117 — correctifs frontend sur l'ecran de gestion des categories et alignement des boutons d'action des ecrans admin.
## Changements
### Drawer categories
- Titre stable « Modifier la categorie » (plus de bascule view → edit selon l'etat « dirty »), aligne sur les drawers simples du projet.
- Bouton Enregistrer toujours actif : il sauvegarde a tout moment, meme sans modification (PATCH du payload complet `name` + `categoryTypes`, comme `SiteDrawer`).
- Champ « Types de categorie » : suppression du label « Selectionner un ou plusieurs types ».
### Alignement des boutons admin
- Ecran Categories : ordre des boutons Filtres avant Ajouter + gap reduit (`gap-8`), comme le repertoire client.
- Boutons d'ajout admin (categories, roles, sites) passes en `variant=secondary`.
- Boutons Filtres (categories, audit-log, clients) en `tertiary` simple : suppression des surcharges de classe, icone a gauche 24px.
## Tests
- `useCategoryForm` mis a jour (PATCH payload complet).
- `make nuxt-test` : 256/256 OK.
- `make nuxt-lint` : OK.
Reviewed-on: #77
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-09 06:06:52 +00:00
matthieu
a9c14704b7
feat(catalog) : categories multi-types (M:N) + bouton Filtres liste ( #75 )
...
Auto Tag Develop / tag (push) Successful in 7s
## Contexte
Une `Category` ne pouvait appartenir qu'à **un seul** `CategoryType` (ManyToOne). Le besoin métier : plusieurs types par catégorie. Cette MR fait passer la relation en **ManyToMany** et ajoute le bouton **« Filtres »** à droite de la liste des catégories (modèle Répertoire Clients).
Slice vertical complet (le passage M:N casse mécaniquement le contrat inter-module `CategoryInterface`, consommé par la RG-2.10 fournisseurs).
## Volet A — Relation M:N
- `Category.categoryType` (ManyToOne) → `categoryTypes` (ManyToMany, jonction `category_category_type`). Au moins un type obligatoire (`Assert\\Count(min:1)`).
- **Unicité du nom GLOBALE** parmi les actifs (`uq_category_name_active`, remplace `uq_category_name_type_active`). Message 409 reformulé.
- Migration : table de jonction + backfill + drop colonne `category_type_id` + nouvel index. Validée **rejouable sur base fraîche**.
- Contrat Shared : `getCategoryTypeCode()` → `getCategoryTypeCodes(): array`. `Supplier`/`SupplierAddress`/`SupplierFixtures` revalident « contient FOURNISSEUR » (RG-2.10).
- Provider/Repository : filtre type via sous-requête `EXISTS` (ne tronque pas la collection embarquée), eager-load anti-N+1.
## Volet B — Bouton « Filtres »
- Drawer recherche par nom + types multi (OR). Compteur de filtres actifs. État local, jamais persisté en URL.
- Back : filtres `?name=` et `?typeId[]=` sur la collection.
## Front
- Multi-select `MalioSelectCheckbox`, `useCategoryForm` en `categoryTypeIds[]`, colonne « Types », clés i18n.
## Tests / vérifs
- `make test` : **582 tests, 2474 assertions, 0 échec** ✅
- `make nuxt-test` : **236 tests** ✅
- `make php-cs-fixer-allow-risky` ✅
- Migration rejouée sur base fraîche (`make db-reset`) ✅
- Nouveau `CategoryFilterTest` (name partiel + typeId[] OR + multi-type non dupliqué)
---------
Co-authored-by: Matthieu <contact@malio.fr >
Reviewed-on: #75
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
2026-06-08 09:47:15 +00:00
matthieu
6a01067746
feat(commercial) : entités + repositories M2 fournisseurs (ERP-86) ( #65 )
...
Auto Tag Develop / tag (push) Successful in 7s
## ERP-86 — Entités + Repositories M2 Fournisseurs (étape 2/7)
PR **empilée sur ERP-85** (#64 ) : ne contient que le commit ERP-86. À merger après #64 (la base rebascule automatiquement au fil des merges de la chaîne #63 → #64 → develop).
Dépend de #64 (migration BDD). Bloque #87 (Provider + Processor) et suivants.
### Contenu
4 entités jumelles du M1 `Client*`, mapping ORM aligné **exactement** sur la migration ERP-85 (noms, types, longueurs, FK, M2M, index), **sans contact inline** (ERP-106) :
- **`Supplier`** — `#[Auditable]` + Timestampable/Blamable. Formulaire principal, onglet Information (+ `volumeForecast`, spécifique fournisseur), onglet Comptabilité (FK référentiels M1 partagés), archivage (`isArchived`/`archivedAt`), soft-delete préparé. Catégories M2M via `CategoryInterface` (règle n°1, pas d'import inter-module). Pas de `distributor`/`broker`.
- **`SupplierContact`** — onglet Contacts (RG-2.04 : `firstName` OU `lastName`).
- **`SupplierAddress`** — enum `addressType` (`PROSPECT`/`DEPART`/`RENDU` via `Assert\Choice`), `bennes`, `triageProvider` ; M2M sites/contacts/categories.
- **`SupplierRib`** — RIB, embed gaté comptable.
- **Repositories** : interfaces `Domain/Repository/` + impls `Infrastructure/Doctrine/`.
### Points clés
- **Contrat de sérialisation (RETEX M1, 3 maillons posés sur l'entité)** : read-groups sur les propriétés ; getters `isArchived()` / `isTriageProvider()` avec `#[Groups]` + `#[SerializedName('isX')]` (parade piège booléen n°3) ; embed `contacts`/`addresses` (`supplier:item:read`) et `ribs` (`supplier:read:accounting`). `getSites()` agrège/dédoublonne les `Site` des adresses (`name`/`postalCode`, pas de `code`).
- **Fetch-joins anti-N+1** dans le **repository de liste** : `hydrateListCollections()` en 2 passes (`categories`, puis `addresses.sites`) — évite le produit cartésien (pattern ERP-100). Filtres : recherche `companyName` + contacts liés (D1), `categoryCode`, `siteId`, archivage.
- **Pas d'`#[ApiResource]`** : Provider/Processor (gating accounting, archivage, mode strict) sont au ticket **ERP-87**. L'ajouter ici référencerait des classes inexistantes → boot/tests cassés. Les groupes de lecture/écriture sont déjà en place ; le `normalizationContext` viendra avec #87 .
- **Validation FR (ERP-107)** : messages FR sur toutes les contraintes ; `Assert\Length(max)` calé sur les colonnes. Garde-fou `EntityConstraintsHaveFrenchMessageTest` étendu : `Assert\Choice` ajouté au mapping ; `addressType` et `postalCode` whitelistés du miroir Length (déjà bornés par Choice / Regex).
- Clés i18n `audit.entity.commercial_supplier*` ajoutées (garde-fou `AuditableEntitiesHaveI18nLabelTest`).
### Vérifications
- `make test` : **483/483 OK** (1965 assertions).
- `make php-cs-fixer-allow-risky` : 0 correction.
- `doctrine:schema:validate` : mapping correct (bruit d'index FK cosmétique identique au M1 `client`).
---------
Co-authored-by: admin malio <malio@yuno.malio.fr >
Co-authored-by: Matthieu <contact@malio.fr >
Reviewed-on: #65
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
2026-06-08 07:18:30 +00:00
matthieu
e139d234a9
fix(commercial) : validation tous-blocs des onglets collection client + fix 500 NonUniqueResult (ERP-110) ( #61 )
...
Auto Tag Develop / tag (push) Successful in 8s
## Contexte (ERP-110, dérivé de ERP-107)
Sur les onglets à blocs dynamiques d'un client (Contacts / Adresses / RIB), le POST d'une sous-ressource sur un client ayant déjà **≥2 enfants** renvoyait une **500 `NonUniqueResultException`**, court-circuitant la validation (aucune 422 par champ).
## Cause racine
Au stade « read » du POST, le `Link` `toProperty` faisait résoudre la collection enfant via `getOneOrNullResult()` (`ItemProvider`) : `SELECT o FROM ClientContact o INNER JOIN o.client c WHERE c.id = :clientId`. Dès 2 enfants → `NonUniqueResult` → 500 **avant** la déserialisation/validation. Les 3 sous-ressources partageaient la même config (même bug latent). Cause secondaire front : la boucle de soumission s'arrêtait au 1er bloc en erreur (`return` dans le `catch`).
## Correctif
**Back** — `read: false` sur les 3 opérations `Post` (`ClientContact` / `ClientAddress` / `ClientRib`) : le parent est déjà rattaché manuellement par le `*Processor::linkParent`. Les 3 `linkParent` sont durcis (`NotFoundHttpException` si parent absent → **404 préservé**, sinon régression 500 au persist sur `client_id NOT NULL`).
**Front** — nouveau helper `useClientFormErrors().submitRows()` qui tente **tous** les blocs et collecte les erreurs 422 par index (`hasError`), branché sur les 6 sites (`new.vue` + `edit.vue` × contacts/adresses/RIB). Feedback **inline seul** : pas de toast récap, pas de toast succès tant qu'un bloc reste en erreur.
## Tests
- Back : non-régression POST contact/adresse/RIB sur client déjà peuplé (≥2 enfants) → 201, + 422 `propertyPath=email` (validation atteinte). Rouge avant fix (500), vert après.
- Front : `submitRows` (Vitest) — tente tous les blocs, mappe les erreurs par index, n'arrête pas au 1er échec, délègue le fallback non-422, saute les blocs filtrés.
## Vérifications
- `make test` : 474/474 OK
- `make php-cs-fixer-allow-risky` : 0 fichier à corriger
- `make nuxt-test` : 219/219 OK
> Golden path manuel navigateur non exécuté (couvert par les tests automatisés).
---------
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-authored-by: Matthieu <contact@malio.fr >
Reviewed-on: #61
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
2026-06-04 14:06:03 +00:00
tristan
ee3bbea649
feat(front) : mapping des erreurs de validation 422 par champ (ERP-101) ( #58 )
...
Auto Tag Develop / tag (push) Successful in 7s
## Objectif
Afficher les violations de validation 422 du back **sous chaque champ** (prop `:error` des `Malio*`) au lieu d'un toast global, et poser **une convention reutilisable par tous les forms**.
## Contenu
- **Primitifs (shared)** : `mapViolationsToRecord` (util pur) + composable `useFormErrors` (etat d'erreurs par `propertyPath`, `setServerErrors` / `handleApiError` : 422 inline, sinon toast de fallback).
- **Formulaire Client** (`new.vue` + `[id]/edit.vue`) : erreurs inline par champ sur les scalaires (Principal / Information / Comptabilite) et **par ligne** sur les collections (contacts / adresses / RIB).
- **Blocs** `ClientContactBlock` / `ClientAddressBlock` : nouvelle prop `errors`.
- **Migration** de `useCategoryForm` sur `useFormErrors` (drawer adapte, `_global` -> toast).
- **Convention** documentee dans `.claude/rules/frontend.md` + spec de design.
## Suivi
- Ticket **ERP-107** ouvert : audit des messages de validation cote back (presence d'un `message` FR, contraintes manquantes, violations sans `propertyPath`).
## Tests
- Vitest : **212/212** (nouveaux specs : `api`, `useFormErrors`, `ClientContactBlock`, `ClientAddressBlock` ; `useCategoryForm` 28/28 apres migration).
- eslint clean, `nuxi typecheck` 0 erreur.
- Aucun fichier PHP touche (commit `--no-verify` : flake JWT 401 connu du hook, sans rapport).
Reviewed-on: #58
Reviewed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr >
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-04 08:41:19 +00:00
tristan
ec952896ba
M1 · 2/3 (Front) — Retirer le bloc contact principal des ecrans Client ( #57 )
...
Auto Tag Develop / tag (push) Successful in 10s
## Objectif
Retirer le bloc « contact principal » (Nom, Prénom, Téléphone, Téléphone 2, Email) des trois écrans Client — **création**, **consultation**, **modification** — ainsi que des types, mappeurs, validations et clés i18n associés. La saisie des contacts passe désormais exclusivement par l'onglet **Contacts** (`ClientContactBlock`, inchangé).
Dépend du ticket **1/3 (back)** : l'API ne renvoie/n'accepte plus ces 5 champs sur `client`. Contexte : `docs/specs/M1-clients/refonte-contact/README.md`.
## Changements
- **`pages/clients/new.vue`** : bloc principal réduit à Nom entreprise / Catégories / Relation / Triage. Suppression de `main.firstName/lastName/email`, `mainPhones`, `addMainPhone()`, `prefillFirstContact()`. `isMainValid` ne dépend plus que de `companyName` + ≥ 1 catégorie + relation valide. Payload POST et `ClientResponse` nettoyés.
- **`pages/clients/[id]/edit.vue`** : mêmes champs retirés, `isMainValid` simplifié.
- **`pages/clients/[id]/index.vue`** : affichage lecture seule des 5 champs retiré.
- **`utils/clientEdit.ts`** : `MainFormDraft`, `mapMainDraft()`, `buildMainPayload()` débarrassés des 5 champs + `hasSecondaryPhone`.
- **`utils/clientConsultation.ts`** : `ClientDetail` débarrassé des champs inline (`ContactRead` conservé).
- **`i18n/locales/fr.json`** : clés `form.main.firstName/lastName/email/phonePrimary/phoneSecondary/addPhone` supprimées. `form.contact.*` conservé.
- **Tests** : `clientEdit.spec.ts` ajusté (factory, `MAIN_KEYS`, assertions `mapMainDraft`, test téléphone secondaire obsolète retiré).
## Vérifications
- `make nuxt-test` : suites `clientEdit` / `clientConsultation` / `clientFormRules` vertes. Les 2 échecs restants (`useClientReferentials.spec.ts`, libellé de site) sont **pré-existants** sur `develop` (confirmé par `git stash`), sans rapport avec ce ticket.
- `eslint` sur les fichiers touchés : OK, aucun import/variable mort.
- Zéro référence orpheline aux clés `form.main.*` supprimées ; JSON i18n valide.
## Reste à faire
- Golden path navigateur (création → consultation → modification sans bloc inline) à valider manuellement.
Reviewed-on: #57
Co-authored-by: tristan <tristan@yuno.malio.fr >
Co-committed-by: tristan <tristan@yuno.malio.fr >
2026-06-03 14:48:55 +00:00