[ERP-62] Page Répertoire clients (datatable + Ajouter / Exporter) #44

Merged
tristan merged 8 commits from feature/ERP-62-page-repertoire-clients into develop 2026-06-02 14:16:30 +00:00
4 changed files with 40 additions and 16 deletions
Showing only changes of commit 93aa22594d - Show all commits
+2 -1
View File
@@ -86,7 +86,8 @@
"updateSuccess": "Client mis à jour avec succès", "updateSuccess": "Client mis à jour avec succès",
"archiveSuccess": "Client archivé avec succès", "archiveSuccess": "Client archivé avec succès",
"restoreSuccess": "Client restauré avec succès", "restoreSuccess": "Client restauré avec succès",
"error": "Une erreur est survenue. Réessayez." "error": "Une erreur est survenue. Réessayez.",
"exportError": "L'export du répertoire clients a échoué. Réessayez."
}, },
"validation": { "validation": {
"informationRequiredForCommercial": "Les informations de l'entreprise sont obligatoires pour le rôle Commerciale.", "informationRequiredForCommercial": "Les informations de l'entreprise sont obligatoires pour le rôle Commerciale.",
@@ -228,7 +228,13 @@ function formatLastActivity(item: Record<string, unknown>): string {
return '' return ''
} }
return new Date(value).toLocaleDateString('fr-FR') // Garde-fou date invalide : un updatedAt mal forme donnerait « Invalid Date ».
Outdated
Review

[Robustesse] formatLastActivity ne se protège pas d'une date invalide.

Context : rendu de la colonne « Dernière activité » depuis updatedAt.

Cause : si updatedAt arrive mal formé, new Date(value) produit Invalid Date et la cellule affiche littéralement Invalid Date — le if (!value) ne couvre que null/vide.

Reco : garder le résultat derrière Number.isNaN(date.getTime()) → retour '', cohérent avec la cellule vide déjà prévue.

**[Robustesse] `formatLastActivity` ne se protège pas d'une date invalide.** **Context** : rendu de la colonne « Dernière activité » depuis `updatedAt`. **Cause** : si `updatedAt` arrive mal formé, `new Date(value)` produit `Invalid Date` et la cellule affiche littéralement `Invalid Date` — le `if (!value)` ne couvre que null/vide. **Reco** : garder le résultat derrière `Number.isNaN(date.getTime())` → retour `''`, cohérent avec la cellule vide déjà prévue.
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return ''
}
return date.toLocaleDateString('fr-FR')
} }
/** Clic sur une ligne → ecran Consultation (route a plat /clients/{id}). */ /** Clic sur une ligne → ecran Consultation (route a plat /clients/{id}). */
@@ -383,7 +389,7 @@ async function exportXlsx(): Promise<void> {
catch { catch {
toast.error({ toast.error({
title: t('commercial.clients.toast.error'), title: t('commercial.clients.toast.error'),
message: t('commercial.clients.toast.error'), message: t('commercial.clients.toast.exportError'),
}) })
} }
finally { finally {
+5 -5
View File
@@ -2,11 +2,11 @@
* Formatage d'un numero de telephone francais en groupes de 2 chiffres * Formatage d'un numero de telephone francais en groupes de 2 chiffres
* (`XX XX XX XX XX`). * (`XX XX XX XX XX`).
* *
* Signature cible partagee avec le ticket 1.13 / ERP-66 : si ce dernier livre * Helper PARTAGE volontaire : les telephones sont presents un peu partout dans
* une version plus riche (validation, indicatif international), elle remplacera * l'app (fiches clients, contacts, fournisseurs, prestataires...). Introduit ici
* cette implementation minimale. En attendant, on couvre le besoin du Repertoire * comme util transverse stable plutot que duplique a chaque ecran. La signature
* clients (ERP-62) : afficher un telephone lisible a partir de la valeur stockee * `formatPhoneFR(value): string` est coordonnee avec ERP-66, qui pourra enrichir
* en base (deja normalisee en 10 chiffres par le ClientProcessor, RG-1.20). * l'implementation (validation, indicatif international) sans casser les appelants.
* *
* - Ne garde que les chiffres puis groupe par 2 (tolere une saisie deja espacee * - Ne garde que les chiffres puis groupe par 2 (tolere une saisie deja espacee
* ou pointee, ex: `06.12.34.56.78` ou `0612345678`). * ou pointee, ex: `06.12.34.56.78` ou `0612345678`).
1
2
@@ -144,30 +144,47 @@ class DoctrineClientRepository extends ServiceEntityRepository implements Client
/** /**
* Nettoie une liste de chaines : trim, retrait des vides, reindexation. * Nettoie une liste de chaines : trim, retrait des vides, reindexation.
* Defensive : tolere des elements scalaires non-string (cast) et ignore le
* reste sans lever de TypeError, le contrat etant justement de normaliser une
* entree potentiellement brute (query params).
* *
* @param list<string> $values * @param array<mixed> $values
* *
* @return list<string> * @return list<string>
*/ */
private function normalizeStringList(array $values): array private function normalizeStringList(array $values): array
{ {
$cleaned = array_filter( $out = [];
array_map(static fn (string $v): string => trim($v), $values), foreach ($values as $value) {
static fn (string $v): bool => '' !== $v, if (is_string($value) || is_int($value) || is_float($value)) {
); $trimmed = trim((string) $value);
if ('' !== $trimmed) {
$out[] = $trimmed;
}
}
}
return array_values($cleaned); return $out;
} }
Review

[Robustesse] normalizeIntList peut lever un TypeError strict.

Context : la closure type son paramètre int, mais la signature publique de l'interface est array $siteIds (éléments non typés).

Cause : aujourd'hui sauvé car ClientProvider::readIntList caste en amont. Mais un appelant direct (test futur, autre module) passant ['1','2'] (strings) déclencherait un TypeError en strict_types — fragile pour une méthode censée justement normaliser.

Reco : caster dans la closure ((int) $v > 0 après is_numeric) plutôt que de typer le paramètre, pour une normalisation réellement défensive.

**[Robustesse] `normalizeIntList` peut lever un `TypeError` strict.** **Context** : la closure type son paramètre `int`, mais la signature publique de l'interface est `array $siteIds` (éléments non typés). **Cause** : aujourd'hui sauvé car `ClientProvider::readIntList` caste en amont. Mais un appelant direct (test futur, autre module) passant `['1','2']` (strings) déclencherait un `TypeError` en `strict_types` — fragile pour une méthode censée justement normaliser. **Reco** : caster dans la closure (`(int) $v > 0` après `is_numeric`) plutôt que de typer le paramètre, pour une normalisation réellement défensive.
/** /**
* Nettoie une liste d'identifiants : cast int, retrait des <= 0, reindexation. * Nettoie une liste d'identifiants : cast int, retrait des <= 0, reindexation.
* Defensive (cf. normalizeStringList) : accepte des entiers ou des chaines
* numeriques ('1', '2') sans TypeError, ignore le reste.
* *
* @param list<int> $values * @param array<mixed> $values
* *
* @return list<int> * @return list<int>
*/ */
private function normalizeIntList(array $values): array private function normalizeIntList(array $values): array
{ {
return array_values(array_filter($values, static fn (int $v): bool => $v > 0)); $out = [];
foreach ($values as $value) {
if (is_numeric($value) && (int) $value > 0) {
$out[] = (int) $value;
}
}
return $out;
} }
} }