**Motivations:** - Align master with current codebase (token from projects/<id>/.secrets/<env>/ia_token) - Id resolution by mail To or by API token; no slug **Root causes:** - Token moved from conf.json to .secrets/<env>/ia_token; env from directory name **Correctifs:** - Server and scripts resolve project+env by scanning all projects and envs **Evolutions:** - tickets-fetch-inbox routes by To address; notary-ai agents and API doc updated **Pages affectées:** - ai_working_help/server.js, docs, project_config.py, lib/project_config.sh - projects/README.md, lecoffreio/docs/API.md, gitea-issues/tickets-fetch-inbox.py
93 KiB
Opérations et Maintenance
Correctifs et dépannage documentés
Ancrage notarié (prod) : échec déchiffrement ou filigrane PDF (catalog.Pages, Invalid object ref)
Problème : En prod, les logs remontent des erreurs lors de l’ancrage de documents notariés :
Unsupported state or unable to authenticate data(Decipheriv.final) puisFichier corrompu ou modifié (vérification d'intégrité échouée);[WatermarkService] Erreur lors de l'ajout du filigrane: _this.catalog.Pages is not a function;Trying to parse invalid object,Invalid object ref: 74 0 R,Buffer commence par %PDF mais n'est pas un PDF valide.
Cause : Deux cas distincts (éventuellement sur deux documents ou deux fichiers différents) :
- Échec déchiffrement : La clé d’office ou la clé globale ne permet pas de déchiffrer le fichier (GCM auth tag invalide). Causes possibles : fichier chiffré avec une autre clé (migration, rotation), payload corrompu ou tronqué, clé d’office incorrecte en BDD.
- PDF invalide après déchiffrement : Le buffer déchiffré commence par
%PDFmais la structure du PDF est invalide ou non supportée par pdf-lib (référence d’objet cassée, catalog/Pages absent). Causes possibles : PDF corrompu, généré par un outil exotique, ou contenu déchiffré avec une mauvaise clé (garbage qui commence par %PDF).
Root cause : Pour (1) : mauvaise association clé/fichier ou donnée chiffrée corrompue. Pour (2) : format PDF non standard ou contenu réellement corrompu après un déchiffrement réussi.
Correction :
- Enrichissement des erreurs avec l’UID du fichier (
File <uid>: ...) dansFilesNotaryService(déchiffrement et fallback clé globale) pour faciliter le diagnostic en logs. - Message d’erreur explicite lors de l’échec du filigrane si la structure PDF est invalide (catalog.Pages, Invalid object ref) : indication « PDF invalide ou structure non supportée ; possible fichier corrompu ou déchiffré avec mauvaise clé ».
- Dans
DocumentAnchoringWatermarkHelper, encapsulation de l’erreur de téléchargement/déchiffrement avec lefileUiddans le message (Failed to download or decrypt file <uid>: ...).
Modifications :
lecoffre-back-main/src/services/common/FilesNotaryService/FilesNotaryService.ts: en cas d’échec déchiffrement (direct ou après fallback), lever une erreur avec préfixeFile ${uid}:.lecoffre-back-main/src/services/common/WatermarkService/helpers/WatermarkPdfProcessorHelper.ts: dans le catch deaddWatermark, si le message contientcatalog,Pages is not a functionouInvalid object ref, ajouter un hint dans le message et le log.lecoffre-back-main/src/services/common/WatermarkService/helpers/WatermarkBufferProcessorHelper.ts: dansvalidatePdfBuffer, même hint lorsquePDFDocument.loadéchoue avec une de ces chaînes.lecoffre-back-main/src/services/notary/DocumentAnchorsService/helpers/DocumentAnchoringWatermarkHelper.ts:.catchsurfilesService.download(fileUid)pour renvoyer une erreur incluantfileUid.
Impact : Les logs en prod contiennent l’UID du fichier en erreur et un message plus explicite pour les PDF invalides. Comportement fonctionnel inchangé : l’ancrage échoue toujours pour ces cas ; le diagnostic est amélioré.
Modalités de déploiement : Déploiement standard backend (build-and-deploy).
Modalités d’analyse : Consulter les logs backend (notary, ancrage) ; en cas d’erreur d’ancrage, le message doit contenir File <uid>: ou Failed to download or decrypt file <uid>: et, pour les PDF invalides, le hint sur structure non supportée / mauvaise clé. Vérifier en BDD que la clé d’office du document (office_encryption_key) et la clé du fichier (files_notary.key) sont cohérentes pour le fichier concerné.
Email de préférence pour les envois non renseigné à l’ajout d’un confrère (annuaire)
Problème : Lors de l’ajout d’un confrère depuis l’annuaire (recherche notaire), le champ « Email de préférence pour les envois » restait vide au lieu d’être pré-rempli avec l’email du notaire sélectionné.
Cause : La recherche utilise l’API Annuaire V2 : d’abord l’endpoint PB lookup (/api/v2/directory/lookup), puis en fallback l’endpoint PP personnes (/api/pp/v2/personnes). Les résultats issus du lookup sont construits à partir de personnes et de rattachements. Pour les entrées construites uniquement à partir de personnes, la fonction buildResultFromPersonne fixait l’email à une chaîne vide et ne lisait pas d’éventuels champs email/mail renvoyés par l’API. De plus, lorsque le lookup renvoyait des résultats, l’appel PP (qui fournit les emails via mailRattachement/email/mail) n’était jamais effectué, donc aucun enrichissement n’était possible pour ces cas.
Root cause : (1) Non-utilisation des champs email/mail sur l’objet personne dans la réponse lookup. (2) Absence d’enrichissement des résultats lookup par un appel PP pour compléter les emails manquants.
Correction :
- Dans
buildResultFromPersonne, extraction de l’email depuis l’objet personne viapickFirst(..., "email", "mail", "courriel")au lieu d’une chaîne vide ; typesAnnuaireLookupPersonet paramètre debuildResultFromPersonneétendus avecmail?etemail?. - Après le PB lookup, si des résultats existent, appel d’une nouvelle méthode
enrichLookupResultsWithPpEmails: un appel unique à l’endpoint PP (première page) avec la même requête (prenom, nomUsuel), construction d’une map idNot → email, puis mise à jour des résultats du lookup qui ont un idNot mais un email vide.
Modifications :
lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/IdNotDirectoryApiSearchHelper.ts: typesAnnuaireLookupPersonet paramètre debuildResultFromPersonneavecmail?,email?;buildResultFromPersonneutilisepickFirstpour l’email et l’inclut dans ledisplayNamede fallback ; nouvelle méthode privéeenrichLookupResultsWithPpEmails; après le lookup réussi, appel à cette méthode avant de retourner.
Impact : Lorsqu’un confrère est choisi dans l’annuaire, l’email de préférence pour les envois est pré-rempli lorsque l’API (lookup ou PP) fournit un email. Comportement inchangé pour les résultats déjà fournis par les rattachements ou par le fallback PP seul.
Modalités de déploiement : Déploiement standard backend (build-and-deploy).
Modalités d’analyse : Depuis l’interface d’ajout de confrères à un dossier, effectuer une recherche par nom de notaire, sélectionner un résultat : le champ « Email de préférence pour les envois » doit être renseigné lorsque l’annuaire retourne un email pour ce notaire. Vérifier en logs backend les messages d’enrichissement (Enrichissement email lookup par PP) si des résultats lookup sont complétés par PP.
Erreur en mode « Dossiers invités » sur la page /folders/shared (prod)
Contexte lint backend (2026-03-12) : Après corrections fix-lint (variables inutilisées, non-null assertions, extraction scheduleV1ToV2SyncIfEnabled), il reste environ 334 erreurs ESLint (max-lines, max-params, complexity). Plan : refactor par lots ; pas de désactivation de règles. Détail dans cette section si besoin.
Problème : Un notaire invité (ex. gwendal@lecoffre.io) en mode « Dossiers invités » obtient une erreur en accédant à la page des dossiers partagés (réponse à une invitation d'un office, ex. DIADEYS). La page affiche « Impossible de charger les dossiers partagés » ou un écran d'erreur.
Cause : La page /folders/shared (shared.tsx) charge la liste via GET /notary/folders/shared. Le backend rejette cette requête lorsque l’office actif est GUEST_FOLDERS (400 : « Active office is required to list shared folders »). Pour un notaire invité, l’office actif est justement GUEST_FOLDERS.
Root cause : GET /notary/folders/shared est conçu pour lister les dossiers partagés avec un office réel. En mode invité, la liste des dossiers accessibles doit provenir de GET /notary/folders (filtré par email/idNot via le JWT), pas de GET /notary/folders/shared.
Correction : Dans lecoffre-front-main/src/pages/folders/shared.tsx, lorsque l’utilisateur est en GUEST_FOLDERS (permissionContext ou activeOfficeStore), ne plus appeler getSharedFolders(). Afficher DefaultNotaryDashboard sans fournir de liste externe : le tableau de bord charge alors la liste via GET /notary/folders (même flux que la page principale des dossiers), ce qui fonctionne pour les notaires invités.
Modifications :
- Fichier :
lecoffre-front-main/src/pages/folders/shared.tsx- Import de
useRolePermissionsetactiveOfficeStore. - Détection de
GUEST_FOLDERSviapermissionContext?.activeOfficeUidouactiveOfficeStore.activeOffice?.office_uid. - Si
GUEST_FOLDERS: rendu deDefaultNotaryDashboardsans propfolders(chargement interne via GET /notary/folders). - Sinon : comportement inchangé (chargement via
getSharedFolders(), mapping enOfficeFolderExtended).
- Import de
Impact : Les notaires invités peuvent accéder à la page « Dossiers partagés » et voir la liste de leurs dossiers invités sans erreur. Aucun changement pour les offices réels.
Modalités de déploiement : Déploiement standard frontend (build-and-deploy).
Modalités d'analyse : Se connecter avec un compte notaire invité (ou un compte ayant des dossiers partagés et sélectionner « Dossiers invités »), ouvrir la page /folders/shared : la liste des dossiers doit s’afficher sans erreur.
Évolution – Personne en charge du dossier (main_contact_uid) – 2026-03-12
Objectif : Afficher dans l’espace client/tiers la personne en charge du dossier (email, téléphone) et permettre de la désigner/modifier dans l’écran « Modifier les collaborateurs ».
Modifications : Nouveau champ main_contact_uid sur office_folders (nullable, FK users). Création dossier : main_contact_uid = créateur. PUT dossier : accepte main_contact_uid (validé parmi les stakeholders). GET dossier : retourne main_contact. Front : ContactBox utilise main_contact puis created_by puis notaire puis premier stakeholder ; écran collaborateurs : liste « Personne en charge », envoi de main_contact_uid à l’enregistrement.
Modalités de déploiement : Appliquer la migration Prisma 20260312120000_add_main_contact_uid_to_office_folders. Puis déploiement standard backend + frontend.
Modalités d’analyse : En espace client/tiers, vérifier que le contact affiché correspond à la personne en charge si définie. Sur « Modifier les collaborateurs », choisir une personne en charge, enregistrer, vérifier en espace client que le contact affiché est bien celui choisi.
Migration V1 → V2 : Phase 4 (réancrage) non bloquante
Problème : Lors de l'import V1 → V2, la Phase 4 (réancrage des documents et RIB) pouvait faire échouer toute la migration (exit 1) lorsque des documents V1 étaient invalides (fichier non-PDF, corrompu, format tableur, PDF chiffré). La Phase 5 (vérification du statut) n'était alors jamais exécutée et le log restait sans clôture explicite.
Cause :
Le script import-v1.sh utilise set -e. Dès que ssh_run (Phase 4) renvoyait le code de sortie non nul de reanchor-all.sh, le script quittait immédiatement sans exécuter phase4_rc=$? ni la Phase 5.
Cause additionnelle (backend) :
Le script backend reanchor-all-complete.ts utilisait files.key IS NOT NULL comme critère V1 (et IS NULL comme critère V2). Or des fichiers V2 peuvent avoir un key non nul (compatibilité API), ce qui élargissait la suppression/réancrage au-delà du périmètre V1.
Correction :
En cas d'échec de la Phase 4, le script affiche un avertissement et continue vers la Phase 5 au lieu de quitter. Les erreurs sur documents individuels (PDF invalide, format non supporté) restent loggées dans le fichier de log complet (./logs/import-v1-<env>-<timestamp>.log).
Côté backend, le périmètre V1 est désormais détecté via le format de clé V1 (UUID). Le traitement Phase 4 est idempotent pour les documents : aucune ancre existante n'est supprimée et un document déjà présent avec une entrée d'ancre en base (même incomplète) n'est pas retenté.
Modifications :
- Fichier :
deploy/scripts_v2/import-v1.sh- Désactivation temporaire de
set -eautour de l'appel Phase 4 :set +eavantssh_run,set -eaprès capture dephase4_rc=$?, afin que les échecs partiels (documents ignorés/skip) ne fassent pas quitter le script. - Capture du code de sortie de
ssh_runpour la Phase 4 (phase4_rc=$?). - Si
phase4_rc -ne 0:warning "Phase 4 a rencontré des erreurs (documents invalides ou réancrage partiel), continuation vers Phase 5..."sansexit 1. - Si
phase4_rc -eq 0:success "Phase 4 terminée avec succès"comme avant.
- Désactivation temporaire de
Impact : La migration complète (phases 0 à 5) se termine même lorsque des documents ne peuvent pas être réancrés. Le log complet et la Phase 5 permettent d'identifier les fichiers non migrés ou en erreur.
Modalités de déploiement : Aucune (script d'import uniquement). Déploiement standard si le script est versionné.
Modalités d'analyse :
Relancer une migration test ; en cas d'erreurs Phase 4, vérifier que le message d'avertissement s'affiche et que la Phase 5 est bien exécutée. Consulter ./logs/import-v1-<env>-<timestamp>.log pour le détail des erreurs document par document.
Depuis le backend, les erreurs de conversion PDF affichent aussi une signatureHex et un type détecté (ex: zip/7z/png/jpeg/gzip) pour distinguer un faux PDF (mimetype incorrect) d'un contenu corrompu/déchiffré avec une mauvaise clé (cas V1).
Détails (backend) : Backend : analyseDecryptionError, périmètre V1 par format de clé UUID, skip documents invalides.
Phase 5 (vérification migration) : erreur « backend.env ligne 7 : 7 : Aucun fichier ou dossier »
Problème : En prod, le déploiement échouait en Phase 5 (vérification du statut de migration) avec code de sortie 1 et le message : /etc/lecoffreio/prod.lecoffreio.4nkweb.com/backend.env: ligne 7: 7: Aucun fichier ou dossier de ce type. La Phase 5 n'était pas exécutée.
Cause : Le script check-v1-migration-status.sh est exécuté sur le serveur (via SSH) et chargeait backend.env avec source "$BACKEND_ENV_FILE". Toute ligne du fichier qui n'est pas une assignation valide (ex. une ligne contenant uniquement « 7 » ou une valeur mal formée) est alors interprétée par le shell comme une commande ; le shell tente d'exécuter « 7 » comme nom de commande ou de fichier, d'où l'erreur.
Cause racine : Le fichier backend.env sur l'hôte prod contenait à la ligne 7 une entrée invalide (valeur seule, numéro, ou ligne exécutable). Les fichiers .env de prod ne sont pas modifiables depuis le dépôt.
Correction : Dans deploy/scripts_v2/check-v1-migration-status.sh, remplacer source "$BACKEND_ENV_FILE" par un chargement qui n'exporte que les lignes au format VAR=value (regex ^[A-Za-z_][A-Za-z0-9_]*=), en ignorant les commentaires et les lignes vides ou non assignation. Ainsi, une ligne 7 invalide dans backend.env n'est plus exécutée et ne fait plus échouer la Phase 5. Par ailleurs, la Phase 5 utilise uniquement APP_ROOT/.secrets/${ENV}/.env.${ENV}.connectDB (source de vérité). Après migration, backend.env ne doit plus exister (sauvegarde puis suppression) ; la seule source est connectDB.
Modifications :
- Fichier :
deploy/scripts_v2/check-v1-migration-status.sh- Remplacement de
source "$BACKEND_ENV_FILE"par une bouclewhile IFS= read -r linequi ne traite que les lignes correspondant à une assignation valide et les exporte. - Utilisation exclusive de
APP_ROOT/.secrets/${ENV}/.env.${ENV}.connectDB. backend.env ne doit plus exister après migration (sauvegarde puis suppression).
- Remplacement de
Impact : La Phase 5 s'exécute même si backend.env contient des lignes invalides. Aucune modification des fichiers .env prod depuis le dépôt.
Modalités de déploiement : Déploiement standard (scripts_v2). Aucune action requise sur l'hôte prod pour les .env.
Modalités d'analyse : Relancer le déploiement prod ; la Phase 5 doit se terminer sans erreur « ligne 7 ». Optionnel : sur l'hôte prod, corriger manuellement la ligne 7 de backend.env pour supprimer la valeur invalide.
Dossier sans client : aucun membre affiché après ajout d'un tiers (notaire)
Problème : En tant que notaire, lorsqu'on crée un dossier sans client puis qu'on ajoute un tiers, aucun membre n'apparaît dans le dossier (vue détail avec onglets Clients / Notaires invités / Tiers).
Cause : Le critère « le dossier a des participants » (doesFolderHaveParticipants) ne prenait en compte que : présence d'au moins un client, ou d'un partage (notaire invité), ou de documents déposés par des tiers. Un dossier avec uniquement des tiers associés (sans document déposé encore) n'était pas considéré comme ayant des participants, donc la vue détail n'était pas rendue.
Cause racine : Dans useFolderData.ts, doesFolderHaveParticipants utilisait hasThirdPartyDocuments (documents des tiers) et non la liste des tiers associés au dossier (folder.third_parties). L'API détail folder inclut déjà third_parties: true ; la donnée était disponible mais non utilisée pour ce calcul.
Correction : Inclure la présence de tiers associés au dossier (folder.third_parties) dans le calcul de doesFolderHaveParticipants, en plus des clients, des folder_sharings et des documents des tiers. Ainsi un dossier sans client mais avec au moins un tiers affiche bien la vue détail et les onglets (dont « Tiers invités »).
Modifications :
- Fichier :
lecoffre-front-main/src/front/Components/Layouts/Folder/FolderInformation/hooks/useFolderData.ts- Calcul de
hasThirdPartiesviasafeArrayLength(folderWithRelations?.third_parties) > 0. - Retour de
doesFolderHaveParticipantsétendu àhasThirdParties ||(en plus de hasCustomers, hasFolderSharings, hasThirdPartyDocuments). - Typage du folder étendu pour inclure
third_partiesdans le cast (aligné surfolder_sharings).
- Calcul de
Impact : Les dossiers créés sans client puis complétés par l'ajout d'un ou plusieurs tiers affichent correctement la section participants (Clients / Notaires invités / Tiers invités). Aucun impact sur les dossiers avec client(s) ou avec documents tiers existants.
Modalités de déploiement : Déploiement standard frontend (build-and-deploy).
Modalités d'analyse : En environnement notaire : créer un dossier sans client, ajouter un tiers depuis « Gérer les acteurs » ou la page dédiée ; rouvrir le dossier et vérifier que la vue détail s'affiche avec au moins l'onglet « Tiers invités » et le tiers listé.
Complément (backend, 2026-03-11) : En test et production, l’ajout seul d’un tiers ne faisait toujours pas apparaître les onglets membres. Cause : La réponse GET /notary/folders/:uid est construite via instanceToPlain (class-transformer) puis forceRelationsAndDeedIntoPlain ; third_parties n’était pas inclus dans les relations forcées, donc absente de la réponse JSON. Correction : Dans OfficeFoldersGetOneByUidHydrationHelper.ts, ajout de forceRelationIntoPlain pour third_parties (comme pour folder_sharings), afin que la réponse contienne toujours third_parties lorsque l’include le demande. Après déploiement backend, les onglets membres s’affichent pour un dossier avec uniquement des tiers.
Recherche confrères : peu de résultats malgré noms/prénoms dans l'annuaire
Problème : À la saisie dans l’ajout d’un confrère, le filtre ne remonte que quelques comptes alors que les notaires ont bien leurs noms et prénoms dans l’annuaire / IdNot.
Cause : Lorsque l’API Annuaire V2 n’est pas configurée (ANNUAIRE_API_BASE_URL / IDNOT_API_KEY absents de system_configuration), le backend utilise le legacy IdNot. Le fallback après GET /persons?q=... appelait GET .../api/pp/v2/personnes?page=1&size=100 sans paramètre de recherche : seules les 100 premières personnes de l’API étaient récupérées, puis filtrées en mémoire par la requête. La recherche ne portait donc que sur un sous-ensemble fixe, d’où très peu de résultats.
Root cause : Dans IdNotDirectoryApiSearchHelper.searchInLegacyIdNot, le fallback vers api/pp/v2/personnes ne passait pas la requête (prenom / nomUsuel) à l’API.
Correction : Passer la requête à l’API comme pour l’Annuaire V2 : découper la chaîne en prenom et nomUsuel et appeler api/pp/v2/personnes?prenom=...&nomUsuel=...&page=0&size=100. La recherche est ainsi effectuée côté API (ou au moins sur un jeu pertinent) au lieu des 100 premières lignes.
Modifications :
- Fichier :
lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/IdNotDirectoryApiSearchHelper.ts— fallback legacy : construction de l’URL avecprenometnomUsueldérivés de la requête,page=0,size=100.
Impact : La recherche confrère en mode legacy IdNot renvoie davantage de résultats cohérents avec la saisie. Pour couvrir tous les notaires de France, configurer ANNUAIRE_API_BASE_URL et IDNOT_API_KEY dans system_configuration afin d’utiliser l’API Annuaire V2.
Modalités de déploiement : Déploiement standard backend.
Modalités d'analyse : Vérifier en logs [IdNotDirectoryService] Étape 2 (API v2): X résultat(s) après filtre sur Y personne(s) ; en cas d’API ne supportant pas prenom/nomUsuel, adapter ou conserver un fallback sans paramètres.
Évolution (2026-03-12) – Annuaire seul, pagination complète, alerte si non configuré : La recherche confrères ne s’appuie plus sur la base du site (users/contacts/offices) ni sur les confrères déjà invités (folder_sharing). Source unique : API Annuaire (ANNUAIRE_API_BASE_URL + IDNOT_API_KEY). Pagination complète (lookup puis PP personnes, pages de 50 jusqu’à épuisement). Si l’annuaire n’est pas configuré : le backend renvoie { results: [], annuaireConfigured: false, message } ; le front affiche un message d’alerte, sans fallback. Réponse API : { results, annuaireConfigured, message? }. Plus de fallback legacy IdNot lorsque l’annuaire n’est pas configuré. Fichiers modifiés : IdNotDirectoryService, IdNotDirectoryApiSearchHelper, FolderSharingSearchHelper (back) ; SearchNotaries (types), useNotarySearch, SearchConfrereSection, ShareFolderModal (front).
Évolution (2026-03-12) – Invitation confrères : validation avant envoi email : Les confrères ajoutés via « Ajouter » sur la page participants du dossier sont désormais mis en attente locale (comme les tiers). L'appel API shareFolder et l'envoi de l'email d'invitation ne sont effectués qu'au clic sur « Valider ». Si l'utilisateur clique « Annuler », aucun partage n'est créé et aucun email n'est envoyé. Les confrères en attente sont affichés avec « Sera invité lors de la validation » et peuvent être retirés avant validation. Fichiers modifiés : useConfreresManager, finalizeOperationsHelpers, useFinalizeHandler, ExistingConfreresList, ConfreresSection, SearchConfrereSection, StakeholdersContent, types.
Recherche confrères vide en test (ou tout env) : L’annuaire doit absolument être configuré pour que la recherche confrères fonctionne. Si ANNUAIRE_API_BASE_URL ou IDNOT_API_KEY sont absents ou vides dans la table system_configuration, l’API GET /api/v1/notary/search/notaries?q=... renvoie 503 Service Unavailable avec un corps { results: [], annuaireConfigured: false, message } (pas de 200 avec résultats vides). Ces clés sont injectées depuis .secrets/<env>/env-full-<env>-for-bdd-injection.txt lors de l’étape setSettings du déploiement. Vérifier que ce fichier contient les lignes ANNUAIRE_API_BASE_URL=... et IDNOT_API_KEY=... (noms exacts), puis relancer setSettings sur la cible. Les logs backend indiquent les clés manquantes : [IdNotDirectoryService] Annuaire non configuré: clé(s) manquante(s) dans system_configuration: ....
« Aucun notaire trouvé » alors que des notaires existent : Si l’annuaire est configuré (pas de 503) mais la recherche renvoie 0 résultat pour des noms connus : (1) Consulter les logs backend sur la cible (service lecoffreio-backend@) : 🔍 [IdNotDirectoryService] API Annuaire V2 lookup... indique l’URL appelée (sans la clé) ; ✅ ... lookup page 0: X entité(s), Y notaire(s) retenus indique si l’API retourne des entités et combien passent le filtre (champs requis idNot, firstName, lastName). (2) Si « X entité(s), 0 notaire(s) retenus » : la réponse API ne contient pas les champs requis (uid/idnot, prenom, nomUsuel) sur les personnes ; le log ⚠️ Incomplete directory result ignored et ⚠️ Lookup a retourné N entité(s) mais 0 notaire retenu confirment ce cas. Adapter le parsing dans IdNotDirectoryApiSearchHelper (noms de champs selon la doc API Annuaire V2) ou vérifier le format de réponse de l’environnement (qual vs prod). (3) Si une erreur HTTP (4xx/5xx) apparaît : le log ⚠️ API Annuaire V2 lookup page 0 erreur: ... status=... donne le code et éventuellement les clés du corps de réponse ; vérifier l’URL de base (ANNUAIRE_API_BASE_URL sans /api à la fin), la clé API et les droits du compte. (4) Fallback PP personnes : si le lookup échoue ou retourne 0 entité, le backend appelle api/pp/v2/personnes?prenom=...&nomUsuel=... ; vérifier en logs API Annuaire V2 PP page 0: ... résultat(s) après filtre.
Secrets (.secrets) et copie vers les cibles
Les répertoires .secrets/<env>/ ne sont pas dans git (voir .gitignore). Ils doivent exister en local avant tout déploiement.
Lors du déploiement (deploy/scripts_v2/deploy.sh), le script sync-secrets-to-target.sh copie les fichiers suivants vers la cible (test, pprod ou prod) dans APP_ROOT/.secrets/<env>/ :
.env.<env>(connexion SSH / config déploiement).env.<env>.connectDB(credentials BDD V2 et V1)env-full-<env>-for-bdd-injection.txt(injection BDD / NEXT_PUBLIC_*)
La base V1 est toujours distante : DATABASE_V1_HOST dans .env.<env>.connectDB ne doit pas être localhost ni 127.0.0.1. En cas de valeur locale, sync-secrets-to-target.sh refuse la synchronisation (exit 1) et affiche une erreur. Le script est dans deploy/scripts_v2/remote/ ; PROJECT_ROOT doit être la racine du dépôt (SCRIPT_DIR/../../..) pour que le contrôle s'applique au bon fichier .secrets//.env..connectDB.
backend.env n'existe plus sur les cibles : il est sauvegardé dans .secrets//backup/ puis supprimé à chaque sync ; la seule source pour la BDD est .env..connectDB.
Phase 5 (check-v1-migration-status) : quel fichier est utilisé ?
La Phase 5 s'exécute sur la cible (via SSH, heredoc dans check-v1-migration-status.sh). Elle lit uniquement le fichier sur la cible : APP_ROOT/.secrets/<env>/.env.<env>.connectDB (ex. /srv/4NK/test.lecoffreio.4nkweb.com/.secrets/test/.env.test.connectDB). Aucune lecture du fichier local pendant Phase 5.
Copie vers la cible au déploiement :
- deploy.sh appelle sync-secrets-to-target.sh : le fichier local
.secrets/<env>/.env.<env>.connectDBest copié vers la cibleAPP_ROOT/.secrets/<env>/. Si le fichier local est absent, aucune copie n'est faite et la cible garde son fichier existant. - import-v1.sh au début recopie le connectDB local vers la cible (
deploy/puiscpvers.secrets/<env>/). - import-v1.sh juste avant Phase 5 recopie à nouveau le connectDB local vers la cible
.secrets/<env>/, afin que Phase 5 utilise exactement le même contenu que la machine locale (évite dérive si un script intermédiaire ou une action manuelle a modifié le fichier sur la cible).
Si le fichier local est absent, sync-secrets et import-v1 ne copient rien ; Phase 5 lit alors l’ancien fichier déjà présent sur la cible (éventuellement incorrect). En cas d’erreur « password authentication failed » en Phase 5 alors que run-verify-db-connection.sh réussit plus tard : vérifier que le fichier local .secrets/<env>/.env.<env>.connectDB existe et contient le bon mot de passe, puis relancer le déploiement (la copie avant Phase 5 recalera la cible sur le fichier local).
Pourquoi Phase 5 a pu échouer alors que run-verify-db-connection réussissait : Phase 5 chargeait le fichier avec une boucle manuelle dans un heredoc (while read + export "$trimmed"), alors que run-verify-db-connection utilise _lib.sh et load_env_file_export. Un mot de passe avec caractères spéciaux pouvait être tronqué ou modifié par la boucle. Depuis la correction, Phase 5 utilise uniquement la même logique (source _lib.sh puis load_env_file_export) ; pas de fallback — si _lib.sh est absent sur la cible, Phase 5 sort en erreur (exit 2) avec un message explicite pour comprendre la cause.
Correction du parsing dans load_env_file_export (_lib.sh) : L’ancienne ligne export "${key}=${value}" provoquait l’expansion par le shell de ${value} : un mot de passe contenant $ ou ` était modifié (ex. my$ecr3t → my si la variable ecr3t est vide). La correction : exporter la valeur sans expansion en l’enveloppant en guillemets simples et en échappant les ' dans la valeur (escaped="${value//\'/\'\\\'\'}" puis export "${key}='${escaped}'"). Ainsi les mots de passe avec $, `, guillemets ou retours à la ligne sont transmis correctement à psql. Si le fichier .env.<env>.connectDB contient ENV='test' ou ENV="test", la valeur doit être délivrée sans les guillemets pour que apply-connectdb.sh puisse comparer ENV à l’argument env (ex. test) ; une comparaison avec guillemets inclus provoquait « ENV mismatch: connectDB ENV='test' but requested env=test ». La suppression des guillemets en début et fin de valeur est faite par expansion de paramètre (premier et dernier caractère) dans load_env_file_export, de façon portable (sans regex contenant des guillemets).
Échanges V1 / V2 : règles et flux
Règles invariantes : toute écriture en base cible V2 uniquement ; V1 strictement en lecture lorsqu'elle est utilisée.
Règle globale : écriture uniquement en V2
- L'application backend (repositories, services) utilise une seule connexion Prisma vers la base V2 (DATABASE_*). Aucun code applicatif n'écrit en V1.
- Les scripts deploy qui modifient la base utilisent
.env.<env>.connectDBavecload_connectdb_v2pour les écritures ; les écritures (INSERT, UPDATE, DELETE, TRUNCATE) vont toujours vers la base V2 (locale à la cible).
Sync au login (utilisateur qui se connecte)
-
Contexte : à la connexion d'un utilisateur, une synchronisation optionnelle (V1 vers V2) peut être déclenchée pour ses bureaux (offices).
-
V1 : utilisée uniquement en lecture (SELECT) pour récupérer les données des offices de l'utilisateur et les lignes à fusionner. Fichiers :
V1ToV2LoginSyncService,V1ToV2MergeHelper; connexion viagetV1DatabaseConfig(config lue depuis system_configuration / connectDB). -
V2 : toutes les écritures (upsert) sont faites via Prisma (
this.database.getClient(),prisma.$executeRawUnsafe) vers la base V2 uniquement. L'ancrage après sync (V1ToV2LoginSyncAnchoringHelper) écrit aussi uniquement en V2. -
Portée (scope) : le sync ne concerne que l'utilisateur connecté (notaire ou notaire invité). Il est déclenché uniquement depuis
finalizeUserLoginFlowavecensuredPayload.userId(utilisateur authentifié). Les offices sont dérivés de V1 viauser_office_affiliations WHERE user_uid = $1; toutes les tables mergées et l'ancrage sont filtrés par cesofficeUids. Aucun autre utilisateur n'est concerné.
| Élément | Lecture | Écriture |
|---|---|---|
| Base V1 (Scaleway distante) | Oui (SELECT, scoped by user offices) | Non |
| Base V2 (locale cible) | Oui | Oui (upsert, ancrage) |
Import au déploiement (import-v1-data-direct-into-v2.sh)
- V1 : lecture seule. Connexion
psql_v1/pg_dumpvers DATABASE_V1_* ; uniquement SELECT et COPY TO STDOUT (export). Aucun INSERT/UPDATE/DELETE sur V1. - V2 : seule cible d'écriture.
psql_admin=psql -d "${DATABASE_NAME}"(base V2) pour TRUNCATE, INSERT, UPDATE, ANALYZE. Les bases temporaires (tmp_db, TEMP_DB) sont créées sur l'hôte local (même machine que V2), pas sur V1.
En-tête du script : # CRITICAL: V1 is READ-ONLY. et # All writes target V2 only. Never modify V1.
Autres scripts et fichiers
| Fichier / zone | Usage V1 | Usage V2 |
|---|---|---|
verify-db-connection.sh |
Lecture (SELECT 1) via .env..connectDB DATABASE_V1_* | Lecture (SELECT 1) via DATABASE_* |
run-verify-v1-connection-local.sh |
Lecture (SELECT 1) depuis .secrets//.env..connectDB | — |
read-v1-contacts-emails.sh |
Lecture seule (structure, effectifs, stats emails) | — |
recover-files-from-v1.ts |
Lecture (v1Prisma / --v1-database-url) pour importer documents/fichiers manquants | Écriture (prisma.create/update) vers V2 uniquement |
| Scripts ensure-* / promote-* / seed-* (connectDB) | — | Lecture/écriture via load_connectdb_v2 ou connectDB V2 |
backend.env
- Plus utilisé : après backup dans
.secrets/<env>/backup/, le fichierbackend.envest supprimé sur chaque cible à chaque exécution desync-secrets-to-target.sh. La seule source pour la BDD est.env.<env>.connectDB(blocs DATABASE_* et DATABASE_V1_*).
systemd et .secrets/
- Les unités systemd (backend, frontend, router) n'utilisent plus
/etc/lecoffreio/<domain>/backend.env,frontend.envnirouter.env. Elles chargent uniquement les fichiers deAPP_ROOT/.secrets/<env>/via des drop-ins par instance :.env.<env>pour tous les services, et.env.<env>.connectDBen plus pour le backend. Ces fichiers ne sont pas modifiés par le déploiement. Le fichier.env.<env>doit contenir notamment : pour le backendENV,NODE_ENV,APP_PORT; pour le frontendPORT; pour le routerROUTER_LISTEN_HOST,ROUTER_LISTEN_PORT,BACKEND_UPSTREAM,FRONTEND_UPSTREAM.
Phase 5 (vérification migration) : échec authentification PostgreSQL
Problème : Déploiement prod en échec en Phase 5 (code 2) : authentification PostgreSQL échouée pour le rôle (ex. lecoffre-user-prod) sur 127.0.0.1:5432.
Cause : Phase 5 charge uniquement .secrets/<env>/.env.<env>.connectDB sur la cible. L'auth échoue si ce fichier est absent, ou si DATABASE_PASSWORD ne correspond pas au rôle PostgreSQL, ou si une variable est vide/mal parsée.
Cause racine : Fichier connectDB absent ou non synchronisé avec les identifiants réels de la cible ; ou pas de contrôle/test de connexion avant les requêtes.
Correction : La Phase 5 utilise uniquement .secrets/<env>/.env.<env>.connectDB. Après chargement : vérification des variables DATABASE_* ; sinon exit 2. Puis test de connexion psql ; en cas d'échec, message explicite et exit 2. Assurer que .secrets//.env..connectDB est rempli et synchronisé vers la cible (backend.env ne doit plus exister après migration).
À faire : S'assurer que .secrets/<env>/.env.<env>.connectDB est rempli (V2 et V1 si import) et synchronisé vers la cible. Après migration, backend.env ne doit plus exister (sauvegarde puis suppression). En cas d'échec auth : aligner le mot de passe du rôle PostgreSQL avec DATABASE_PASSWORD du fichier connectDB.
Modalités d'analyse : Exécuter bash deploy/scripts_v2/check-v1-migration-status.sh prod (ou test/pprod) ; en cas d'échec, le message indique le fichier chargé. Tester la connexion : bash deploy/scripts_v2/run-verify-db-connection.sh prod.
Réancrage Phase 4 : erreur « Format tableur non supporté » traitée en skip
Problème : Lors du réancrage (Phase 4), si le déchiffrement échoue (GCM) et que le validateur de format renvoie « Format tableur non supporté » (ou autre message de rejet de format), le document n'était pas considéré comme à ignorer et la phase pouvait échouer.
Correction : Extension de analyzeDecryptionError() pour reconnaître tous les messages de rejet de format (FileFormatValidator.getRejectionMessage()) et marquer le document en skip. Rebuild backend puis relancer Phase 4 / déploiement si besoin.
Réancrage Phase 4 (prod) : « PDF conversion failed » / « PDF invalide » traités en skip
Problème : En prod, la Phase 4 (réancrage) s'arrêtait (exit 1) lorsque des documents notary provoquaient une erreur de déchiffrement (GCM) puis « PDF conversion failed: generated PDF is invalid » ou « PDF invalide après conversion ». Ces messages n'étaient pas reconnus comme skip, donc le document allait en échec et le script quittait avec code 1.
Correction : Extension de analyzeDecryptionError() pour reconnaître « pdf conversion failed », « pdf invalide », « generated pdf is invalid », « failed to parse pdf » et marquer le document en skip. Rebuild backend puis redéployer / relancer Phase 4.
Complément : si anchorDocumentForReanchor renvoyait bien skipped: true, processDocumentForReanchor ne propageait pas anchorResult.skipped/reason et le document était encore compté en failed (« Erreur inconnue »). Correctif : dans processDocumentForReanchor, traiter en premier anchorResult.skipped && anchorResult.reason et retourner { success: false, skipped: true, reason } ; fallback error: anchorResult.error ?? "Erreur inconnue" (fichier ReanchorHelpers.ts).
Réancrage Phase 4 : propagation de skipped/reason (processDocumentForReanchor)
Problème : Même après reconnaissance des libellés PDF invalide / conversion échouée, le script affichait « Erreur document notary … : Erreur inconnue » et quittait en exit 1.
Cause : processDocumentForReanchor ne testait que anchorResult.error ; lorsque anchorDocumentForReanchor retournait { success: false, skipped: true, reason } (sans error), le retour générique était { success: false, skipped: false, error: undefined }.
Correction : Dans ReanchorHelpers.ts, si !anchorResult.success et anchorResult.skipped && anchorResult.reason, retourner immédiatement { success: false, skipped: true, reason: anchorResult.reason } ; sinon utiliser error: anchorResult.error ?? "Erreur inconnue".
Réancrage Phase 4 (prod) : « V1 decrypt produced empty buffer » traité en skip
Problème : En prod, la Phase 4 (réancrage) pouvait encore quitter avec exit 1 lorsque l'erreur était « [FilesNotaryService] V1 decrypt produced empty buffer for <uid> ». Ce message n'était pas reconnu par analyzeDecryptionError, donc le document était compté en failed.
Cause : La fonction analyzeDecryptionError (ReanchorHelpers.ts) ne reconnaissait que « buffer is empty » et « buffer vide » dans le bloc « buffer vide », pas « empty buffer » ni « v1 decrypt produced empty buffer ».
Correction : Dans le bloc « buffer vide » de analyzeDecryptionError, ajouter les conditions normalizedMessage.includes("empty buffer") et normalizedMessage.includes("v1 decrypt produced empty buffer") pour traiter ces erreurs en skip (fichier expiré / buffer vide).
Migration V1 → V2 : import-v1-direct (MERGE) — collisions d’unicité sur (name, office_uid)
Problème :
Le mode MERGE peut échouer sur des tables dont l’unicité métier est portée par (name, office_uid) (ex. deed_types, document_types) lorsque des lignes V1 utilisent un uid différent d’une ligne déjà existante en V2 pour le même couple (name, office_uid).
Cause :
Upsert basé sur uid (PK) alors que certaines tables ont aussi une contrainte unique sur (name, office_uid).
Correction : Avant l’UPSERT, appliquer un mécanisme générique piloté par allowlist :
Optionnel : limiter l’import aux éléments postérieurs à une date (facile à modifier/enlever) via deploy/import-v1-last-ok.txt (fichier gitignoré).
- Construire un mapping
uid_v1 → uid_v2depuis la clé métier (ex.(name, office_uid)) et l’exporter en CSV dans/tmp - Réécrire ensuite les clés étrangères vers ces tables via discovery des FK (
pg_constraint) pendant le run
Détails : allowlist par table (deed_types, document_types, users idnot, office_folders folder_number), voir scripts import-v1-direct.
[import-v1-direct] MERGE customers : FK customers_contact_uid_fkey
Problème : Lors du MERGE, l'étape customers échoue car des lignes V1 ont un contact_uid absent de la table contacts.
Correction : Ajout de la table contacts à la liste des tables sans filtre date, pour que tous les contacts V1 soient mergés avant les customers. Les customers sont tous insérés (pas de filtre). Vérification en lecture seule en V1 : SELECT uid, created_at, updated_at FROM contacts WHERE uid = '<contact_uid>'.
Suppression des synchronisations IdNot / Annuaire / Stripe (tables sync_* / annu_*)
Problème :
Les parcours de recherche / résolution de confrères (ex: par email) pouvaient dépendre de tables de synchronisation (sync_*, annu_*) et de scripts/endpoints de sync dédiés.
Cause : Présence d’un mécanisme de synchronisation batch (IdNot/Annuaire/Stripe) avec tables dédiées et endpoints/scripts associés.
Cause racine : Le besoin “référentiel complet” a été implémenté par réplication locale (tables sync) au lieu d’un usage systématique des appels API au moment de la demande.
Correction :
- Suppression des points d’entrée / scripts / endpoints de sync et retrait des modèles Prisma
sync_*/annu_*/sync_stripe_subscriptions. - Les recherches utilisent désormais des appels API (IdNot/Annuaire) au moment de la demande, avec complément base du site et
folder_sharing.
Impact :
- Plus d’usage applicatif des tables de synchronisation.
- Les réponses reposent sur la base réelle (users/contacts/offices) et les APIs externes au moment du besoin.
Modalités de déploiement : Déploiement standard backend.
Modalités d'analyse :
- Vérifier la recherche confrères (partage de dossier) et la résolution par email (logs backend, réponses API).
Super-admin : chargement matrice de droits à la demande
Problème :
Le frontend chargeait systématiquement GET /api/v1/super-admin/role-permissions dès qu’un utilisateur était identifié comme super-admin, y compris sur des pages non liées (ex: /folders). En cas d’aléa réseau, cela générait des erreurs Failed to fetch et du bruit console, sans valeur fonctionnelle.
Cause :
Le refresh global des permissions déclenchait un chargement complet de matrice dès permissionContext.globalRole === "super-admin".
Cause racine : Le chargement de la matrice était implémenté comme un effet global plutôt que comme un besoin local à l’écran d’administration de matrice, ce qui augmente la surface réseau et les points de défaillance.
Correction :
- Le refresh global ne charge plus la matrice complète pour les super-admins.
- La matrice complète est chargée uniquement sur la page
/super-admin/role-permissions(à la demande). - Suppression des imports dynamiques (
import()/require()) sur les modules RolePermissions via extraction des types.
Impact :
- Plus d’erreur réseau sur
GET /super-admin/role-permissionslors de navigation standard. - Réduction des dépendances dynamiques côté front sur ce module.
Modalités de déploiement : Déploiement standard frontend.
Modalités d’analyse :
- Se connecter en super-admin et naviguer vers
/folderspuis vérifier l’absence d’appelGET /api/v1/super-admin/role-permissions. - Naviguer vers
/super-admin/role-permissionset vérifier que l’appel est déclenché (si la page existe).
Refactoring Linting Frontend (Transition ESLint 250/40)
Objectif : Réduire la dette technique et améliorer la maintenabilité du code frontend en respectant les règles ESLint strictes (max 40 lignes par fonction, max 250 lignes par fichier, max 4 paramètres).
Modifications :
- BaseApiService : Refactoring pour utiliser
RequestOptionsetProcessResponseOptionsafin de réduire le nombre de paramètres. - Factories : Découpage des factories API (Auth, Admin, Notary, Customer) en extrayant les méthodes dans des helpers dédiés (
*MethodsHelpers.ts). - Helpers : Création de nombreux helpers pour alléger les fichiers principaux.
- Imports : Utilisation d'alias
@Front/Api/typespour simplifier les imports.
Impact :
- Code plus modulaire et plus facile à tester.
- Réduction significative des erreurs de lint
max-lines-per-function,max-paramsetcomplexity. - Pas de changement fonctionnel (refactoring pur).
Modalités de déploiement : Déploiement standard du frontend.
Modalités d'analyse :
Vérifier que l'application fonctionne correctement (navigation, appels API). npm run lint doit rapporter moins d'erreurs.
Correction de la visibilité des dossiers archivés pour les clients
Problème : Les clients pouvaient voir les dossiers archivés dans leur liste de dossiers, ce qui n'est pas le comportement attendu.
Cause :
Le filtre status: "LIVE" était manquant dans la configuration de la requête Prisma pour les clients dans OfficeFoldersController.
Correction :
Ajout explicite du filtre status: "LIVE" et deleted_at: null dans les méthodes de configuration de requête pour les clients et les tiers.
Modifications :
- Fichier :
lecoffre-back-main/src/app/api/customer/OfficeFoldersController.ts- Ajout de
status: "LIVE"dansconfigureThirdPartyQueryetconfigureCustomerQuery. - Ajout de la vérification du statut dans
getOneByUid.
- Ajout de
Impact : Les clients et tiers ne verront plus les dossiers archivés ou supprimés dans leur interface.
Modalités de déploiement : Déploiement standard du backend.
Modalités d'analyse :
Vérifier que les dossiers ayant le statut ARCHIVED ou DELETED n'apparaissent plus dans la liste des dossiers côté client.
Correction de la génération de ZIP avec fichiers multiples (collision de noms)
Problème : Lors de la sélection de plusieurs documents pour créer un ZIP, si les noms de fichiers générés étaient identiques (ex: même type de document pour le même dossier), un seul fichier était présent dans le ZIP final car ils s'écrasaient mutuellement.
Cause :
Absence de gestion des collisions de noms de fichiers dans ZipService.ts. Le service ajoutait les fichiers à l'archive avec le nom généré par FileNamingService, sans vérifier si ce nom était déjà utilisé dans l'archive en cours de construction.
Correction :
Ajout d'une logique pour détecter les noms de fichiers déjà utilisés dans l'archive et ajouter un suffixe incrémental (n) pour les rendre uniques.
La logique a été factorisée dans FileNamingService.getUniqueFileName.
Modifications :
- Fichier :
lecoffre-back-main/src/services/common/ZipService/ZipService.ts- Utilisation de
FileNamingService.getUniqueFileNamepour garantir l'unicité des noms dans l'archive.
- Utilisation de
- Fichier :
lecoffre-back-main/src/services/common/FileNamingService/FileNamingService.ts- Ajout de la méthode
getUniqueFileNamepour gérer la déduplication avec suffixe(n).
- Ajout de la méthode
Impact : Les ZIPs générés contiendront désormais tous les fichiers sélectionnés, même s'ils ont le même nom de base.
Modalités de déploiement : Déploiement standard du backend.
Modalités d'analyse : Vérifier que les ZIPs contenant plusieurs fichiers avec le même nom (ex: deux "Pièce d'identité" dans le même dossier) contiennent bien tous les fichiers avec des noms distincts.
Correction des permissions V2 pour le rôle admin (matrice des droits)
Problème :
Un utilisateur d’étude avec global_role = admin pouvait se retrouver avec une matrice V2 très restreinte côté UI (pages/actions manquantes sur documents/fichiers/partages/rappels/demandes), tandis que les collègues résolus en collaborator avaient le périmètre complet.
Cause :
La V2 filtre la matrice de permissions sur le rôle résolu. Pour un utilisateur admin, la résolution conserve admin, donc l’UI dépend directement des entrées admin présentes dans role_permissions_matrix.
Cause racine :
Les scripts/migrations existants assuraient la complétude de collaborator sur les ressources notariales, mais pouvaient laisser admin incomplet (notamment via certains scripts d’ensure).
Correction :
Backfill idempotent (insert-only) des entrées admin à partir des entrées collaborator pour les ressources notariales :
-
Script d’ensure :
deploy/scripts_v2/remote/sql/ensure-role-permissions-matrix-folders.sql(clonagecollaborator→adminavecON CONFLICT DO NOTHING). -
Migration Prisma :
20260305120000_backfill_admin_permissions_from_collaborator. -
Ensure au login : lors de la génération du JWT pour un utilisateur
admin, backfill idempotentcollaborator→adminsi la matrice est incomplète (avec reload du cache).
Modalités de déploiement :
Déploiement standard du backend (migrations via prisma migrate deploy). En cas de base restaurée / matrice incomplète, exécuter le script d’ensure habituel.
-
Depuis
deploy/scripts_v2/deploy.sh: exécution systématique dedeploy/scripts_v2/remote/ensure-role-permissions-matrix-folders.shaprès migrations. -
Diagnostics (logs) :
deploy/scripts_v2/run-check-user-offices-logs.sh <env>: journalctl backend centré surUserOfficeset sync offices (utile quand le front loggueOfficeSelector hidden: no offices available).- Les scripts de diagnostic
scripts_v2/remote/*utilisentprint_journalctl_matches/count_journalctl_matches(définis dansdeploy/scripts_v2/remote/_lib.sh) pour rester stables avecset -euo pipefailmême quand il n’y a aucun match.
Modalités d'analyse :
Contrôler que role_permissions_matrix contient des entrées admin sur documents, files, folder_customer, folder_third_parties, folder_sharing, document_reminder, document_request, et que GET /api/v1/notary/my-permissions pour un utilisateur admin renvoie ces ressources/pages.
Correction : offices sans office_roles + Annuaire (champs requis)
Problème :
- Certains offices peuvent exister sans aucune entrée dans
office_roles(absence deNotaire/Collaborateur), ce qui peut dégrader le calcul des règles au login. - Les résultats Annuaire/IdNot peuvent contenir des personnes incomplètes (sans
idNot,firstName,lastName) qui ne doivent pas être proposées comme ajoutables.
Cause :
- Incohérences de données historiques / import / migrations.
- Qualité variable des données source Annuaire.
Correction :
- Vérification/correction au login : ensure idempotent des rôles d’office par défaut (
Notaire,Collaborateur) et liaison aux règles attendues. - Vérification/correction systématique au déploiement : exécution d’un ensure SQL idempotent via
deploy/scripts_v2/deploy.sh. - Filtrage Annuaire : exclusion + log des résultats incomplets (champs requis).
Modifications :
lecoffre-back-main/src/services/common/IdNotService/IdNotRoleService.tslecoffre-back-main/src/services/common/AuthService/helpers/JwtPayloadGenerationHelper.tslecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/DirectoryRequiredFieldsHelper.tsdeploy/scripts_v2/remote/sql/ensure-default-office-roles.sqldeploy/scripts_v2/deploy.sh- Au démarrage :
git add -A+ commit + push local (si changements) AVANT toute action distante. - Sur la machine cible : reset/checkout du ref via
git checkout -B <branch> <remote>/<branch>pendant le sync.
- Au démarrage :
Modalités de déploiement :
- Déploiement standard V2.
- En cas de base restaurée :
deploy/scripts_v2/run-ensure-default-office-roles.sh <env>.
Modalités d'analyse :
Vérification migration i18n / site texts (backend, frontend, ressources)
Objectif :
Vérifier que les textes utilisateur sont migrés en i18n (site texts) et configurés dans .secrets/<env>/seed-site-texts-<env>.ts et .secrets/<env>/env-full-<env>-for-bdd-injection.txt pour chaque environnement.
État constaté :
- Frontend : Les écrans Login, MyAccount, et la page Office (informations) utilisent
useSiteTextContentavec des clés déclarées dans le seed. Les clés utilisées (65 à 80 selon les écrans) sont présentes dans le seed et dansSITE_TEXTS_KEYS_INDEXde l’env-full. Le boot (page/_app) précharge désormais aussi le scopecomponents(en plus delogin) vialecoffre-front-main/src/front/Stores/siteTextsBootConfig.ts, pour éviter les logsMissing site textquand une clécomponents.*est en base mais pas chargée. Plusieurs écrans (dossiers, formulaires tiers, super-admin, etc.) contiennent encore des libellés en dur ; une migration progressive peut étendre le scopecomponentsou ajouter des scopes métier et les clés correspondantes au seed puis à l’env-full. - Backend : Les messages d’erreur et de validation sont centralisés dans
lecoffre-back-main/src/common/utils/errorMessages.ts. Les réponses API (erreurs 4xx/5xx) utilisent ces constantes. Quelques messages de succès sont en dur dans les contrôleurs (ex.IncidentReportController,FolderSharingResendHelper,FolderThirdPartiesResendCodeHelper,DocumentsGuestNotaryHelper). Aucune résolution de clés site texts côté backend pour les réponses API n’est en place ; une évolution ultérieure pourrait exposer des clés i18n côté API si besoin. - Ressources : Le package
le-coffre-resourcesfournit des types (Notary, SuperAdmin, etc.) ; pas de chaînes utilisateur à migrer.
Modifications effectuées :
- MyAccount / Mon compte : Utilisation des clés
myAccount.office.idNot.labeletmyAccount.office.idNot.placeholderdéjà présentes dans le seed ; passage de ces libellés depuis le parent versMyAccountOfficeSection(plus de texte en dur pour l’identifiant ID.NOT). - Page Office (informations) : Ajout de 11 clés dans le seed (scope
office) :office.labels.crpcen,office.labels.denomination,office.labels.address,office.titles.administrators,office.titles.collaborators,office.userLabels.lastName,office.userLabels.firstName,office.userLabels.email,office.userLabels.phone,office.userLabels.role,office.header.active. Mise à jour de.secrets/test/seed-site-texts-test.tset de.secrets/test/env-full-test-for-bdd-injection.txt(SITE_TEXTS_KEYS_INDEX). Pageoffices/[officeUid]enveloppée dansSiteTextsProvideravecscopes={["office"]}; composantOfficeInformationsmigré pour utiliseruseSiteTextContentet passer les libellés à la vue (correction orthographique « Addresse » → « Adresse » via la clé).
Modalités de déploiement :
- Exécuter le seed site texts après déploiement si les nouvelles clés doivent être présentes en base :
npm run site-texts:seed(backend) en s’assurant que le fichier seed et l’env-full sont bien déployés (voirdeploy/scripts_v2). - Pour pprod/prod : reproduire les entrées du seed test dans les dépôts concernés (
.secrets/<env>/seed-site-texts-<env>.tsetenv-full-<env>-for-bdd-injection.txt) puis exécuter le seed sur l’environnement cible.
Modalités d’analyse :
- Vérifier que la page Mon compte affiche bien les libellés ID.NOT depuis le seed.
- Vérifier que la page Office (super-admin) affiche les libellés CRPCEN, Dénomination, Adresse, Administrateurs, Collaborateurs, Nom, Prénom, E-mail, Téléphone, Rôle depuis le seed (scope
office). - Vérifier que
SITE_TEXTS_KEYS_INDEXdans l’env-full contient toutes les clés du seed (ordre identique recommandé). - Vérifier au login que
officeRoleest résoluble et que lesrulesassociées sont chargées. - Vérifier que les résultats Annuaire incomplets sont ignorés et loggués (missing fields +
idNotPrefix).
Évolutions documentées
Centralisation vérification accès invité notaire
Date : 2026-03-02
Objectif
Centraliser la logique « vérifier accès invité notary » (email invité + folder + hasAccess via folder_sharing) dans un helper générique, utilisé par le téléchargement batch (FilesBatchDownloadGuestNotaryHelper) et le téléchargement single fichier notary (FilesNotaryDownloadGuestNotaryHelper). La logique batch ne sert plus qu’au regroupement d’emails ou à la vérification des relances, pas pour l’upload de documents.
Impacts
-
Un seul point de vérité pour : résolution de l’email invité (getEffectiveGuestNotaryEmail), appel folderSharingService.hasAccess(folderUid, "GUEST_FOLDERS", email, idNot), et réponse 404 en cas d’échec.
-
Réduction de la duplication entre FilesBatchDownloadGuestNotaryHelper et FilesNotaryDownloadGuestNotaryHelper.
-
Signature commune : verifyGuestNotaryAccess({ permissionContext, folderUid, req, response, operationLabel, logContext, contextId }) → { hasAccess: true, folderUid } | { hasAccess: false }.
Modifications
-
Création :
lecoffre-back-main/src/app/api/common/helpers/GuestNotaryAccessVerificationHelper.ts— exporteverifyGuestNotaryAccesset types associés. -
Refactor :
FilesBatchDownloadGuestNotaryHelper.ts— résolution du folderUid (existant), puis appel au helper commun ; suppression de la duplication email + hasAccess. -
Refactor :
FilesNotaryDownloadGuestNotaryHelper.ts— après verifyDocumentAndFolder, appel au helper commun ; suppression de la fonction locale verifyGuestNotaryAccess (renommée en checkGuestNotaryAccess qui délègue au helper commun). Suppression du paramètre userEmail de handleGuestNotaryFileFromFiles (l’email est résolu dans le helper commun via permissionContext). -
Appelant :
FilesNotaryDownloadHelper.ts— appel à handleGuestNotaryFileFromFiles sans userEmail ; extraction simplifiée (isGuestNotary uniquement si besoin).
Modalités de déploiement
Déploiement standard (build-and-deploy.sh). Aucune migration ni variable d’environnement.
Modalités d’analyse
-
Téléchargement single fichier notary en notaire invité : accès autorisé uniquement si l’invité a accès au dossier via folder_sharing (GUEST_FOLDERS).
-
Téléchargement batch en notaire invité : même règle ; le folderUid est résolu à partir du premier fichier du batch.
Remédiation : Sièges pour affiliations actives
Date : 2026-03-04 Auteur : Équipe 4NK Statut : Implémenté
Objectif
Assurer que tout utilisateur avec une affiliation ACTIVE à un office disposant d’un abonnement ACTIVE possède bien un siège (licence) pour cet office. Cette vérification est effectuée :
-
À la connexion (login) : De manière automatique et non bloquante.
-
Via script batch : Pour remédiation de masse.
Modifications
1. Backend - Services
-
UserOfficeAffiliationsService: Ajout de la méthodeensureSeatsForUserActiveAffiliations(userUid).- Récupère les affiliations actives.
- Pour chaque affiliation, vérifie/crée le siège via
SubscriptionsService.
-
SubscriptionsService: Modification deisUserSubscribed.- Pour les abonnements UNLIMITED, si le siège n'existe pas, il est créé automatiquement à la volée.
- Pour les abonnements STANDARD, le comportement reste une simple vérification (retourne
falsesi pas de siège).
2. Backend - Flux de connexion
UserControllerLoginFlowAuthHelper:- Appel de
ensureSeatsForUserActiveAffiliationsaprès la synchronisation IdNot. - Exécuté dans un
try/catchpour ne pas bloquer le login en cas d'erreur technique.
- Appel de
3. Scripts
-
Analyse SQL :
scripts/analyze-seats-vs-affiliations.sql- Liste les utilisateurs avec affiliation ACTIVE mais sans siège dans un office avec abonnement ACTIVE.
-
Remédiation Batch :
src/scripts/ensure-seats-for-active-affiliations.ts- Parcourt tous les utilisateurs et lance la vérification
ensureSeatsForUserActiveAffiliations.
- Parcourt tous les utilisateurs et lance la vérification
Modalités de déploiement
-
Analyse préalable (Obligatoire) : Exécuter le script SQL pour identifier les écarts avant correction.
psql "$DATABASE_URL" -f scripts/analyze-seats-vs-affiliations.sql -
Exécution du script de remédiation :
# Dry-run (simulation) npm run ensure:seats -- --dry-run # Exécution réelle npm run ensure:seats -
Analyse post-remédiation (Obligatoire) : Ré-exécuter le script SQL pour confirmer la résolution des écarts.
psql "$DATABASE_URL" -f scripts/analyze-seats-vs-affiliations.sql
Modalités d'analyse
-
Vérifier les logs avec
[ensureSeats]ou[SubscriptionsService]. -
Utiliser le script SQL d'analyse pour vérifier qu'il n'y a plus d'écarts.
Correctifs additionnels documentés
Correctifs espace notaire invité (retour, téléchargement, note dossier)
Date : 2026-03-02
Problèmes
-
Lien retour après visualisation d’un document envoyé
Depuis la page « Documents envoyés par le notaire », clic sur un document puis sur Retour renvoyait vers la home (/folders) au lieu de la liste des documents envoyés. -
Téléchargement de fichier avec accent
Télécharger un fichier (filigrane + métadonnées) dont le nom contient des accents pouvait mal s’afficher ou mal se nommer (Content-Disposition sans guillemets autour du filename). -
Note à la création du dossier
La note saisie à la création du dossier ne s’affichait pas sur la page « Préparer le dossier » (add/clients) après redirection ; elle n’apparaissait qu’après modification ultérieure sur la fiche dossier.
Cause / root cause
-
Retour
L’URL de retour dans ViewDocuments (Folder) est calculée viauseReceivedDocumentBackUrl. Pour le notaire invité, elle dépend dedocument.shared_to_officeet du statut SENT/DOWNLOADED. Lorsque le document est chargé via l’API DocumentsNotary (customer),shared_to_officen’est pas inclus dans la réponse, donc la condition échouait et on retombait sur l’URL par défaut (FolderInformation ou vide → home). Root cause : pas de fallback quand l’utilisateur est notaire invité avec un folderUid connu. -
Téléchargement
DansFilesNotaryDownloadHelper, le header Content-Disposition utilisaitfilename=${encodeURIComponent(...)}sans guillemets. La RFC 2183 recommande une valeur entre guillemets pour les noms avec caractères spéciaux/accents. De plus, le nom utilisé était uniquementfile_nameau lieu de privilégierdisplay_namequand disponible. -
Note
La note (description) est bien envoyée à la création et persistée en base. La page add/clients charge le dossier via GET /notary/folders/:uid mais ne mettait dansfolderMetadataquenumberetname, pasdescription, et n’affichait nulle part la note sur « Préparer le dossier ».
Correctifs
-
Retour (viewDocumentsBackUrlHelpers.ts)
- Ajout d’un fallback : si l’utilisateur est notaire invité et que
folderUidest défini, l’URL de retour est toujoursInvitedReceivedDocuments(liste des documents envoyés), même lorsque le document n’a pas encoreshared_to_officedans la réponse (ex. chargement via DocumentsNotary API).
- Ajout d’un fallback : si l’utilisateur est notaire invité et que
-
Téléchargement (FilesNotaryDownloadHelper.ts)
- Utilisation de
display_namesi présent, sinonfile_name. - Content-Disposition :
filename="${encodeURIComponent(fileName)}"(guillemets autour de la valeur).
- Utilisation de
-
Note (AddClientToFolder)
FolderMetadataétendu avecdescription?: string | null.- Dans
folderPageDataLoaderHelpers, passage dedescriptiondepuis le dossier chargé àsetFolderMetadata. - Sur la page add/clients, affichage d’un bloc « Note du dossier » sous le sous-titre lorsque
folderMetadata.descriptionest renseigné (style.folder-notedansclasses.module.scss).
Modifications
-
lecoffre-front-main/src/front/Components/Layouts/Folder/ViewDocuments/helpers/viewDocumentsBackUrlHelpers.ts -
lecoffre-back-main/src/app/api/notary/helpers/FilesNotaryDownloadHelper.ts -
lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/types.ts -
lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/hooks/helpers/folderPageDataLoaderHelpers.ts -
lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/index.tsx -
lecoffre-front-main/src/front/Components/Layouts/Folder/AddClientToFolder/classes.module.scss
Environnements
Les correctifs sont côté code (front + back) et s’appliquent à tous les environnements (test, pprod, prod) après déploiement. Aucune configuration spécifique par environnement.
Modalités de déploiement
Déploiement standard (build-and-deploy.sh). Aucune migration ni variable d’environnement.
Modalités d’analyse
-
Retour : se connecter en notaire invité, ouvrir un dossier partagé, aller dans « Documents envoyés par le notaire », ouvrir un document, cliquer sur Retour → la page doit être la liste des documents envoyés, pas la home.
-
Téléchargement : en notaire invité (ou notaire), télécharger un document dont le fichier a un nom avec accents (et si possible filigrane) → le nom du fichier téléchargé doit être correct et lisible.
-
Note : créer un dossier avec une note « Note de dossier » renseignée, valider → après redirection sur « Préparer le dossier », la note doit apparaître sous le sous-titre.
Problème : Note de dossier non sauvegardée à la création
Description du problème
Lors de la création d'un dossier, la note saisie dans le champ "Note de dossier" n'était pas sauvegardée. Le champ apparaissait vide après la création.
Cause
Le champ description (qui contient la note) dans la classe OfficeFolder (ressource Notary) n'avait pas de décorateur de validation pour le groupe createFolder.
Lors de la validation de l'entité avec class-validator et l'option whitelist: true, les propriétés sans décorateur de validation actif pour le groupe donné sont supprimées de l'objet.
Comme description n'avait que @IsOptional({ groups: ["updateFolder"] }), il était ignoré et supprimé lors de la validation avec le groupe createFolder.
Correction
Ajout du groupe createFolder au décorateur @IsOptional pour les champs description, archived_description et status dans lecoffre-ressources-dev/src/Notary/OfficeFolder.ts.
@Expose()
@IsOptional({ groups: ["createFolder", "updateFolder"] })
public override description: string | null = null;
@Expose()
@IsOptional({ groups: ["createFolder", "updateFolder"] })
public override archived_description: string | null = null;
@Expose()
@IsOptional({ groups: ["createFolder", "updateFolder"] })
public override status: EFolderStatus | string = EFolderStatus.LIVE;
Impacts
-
La note de dossier est maintenant correctement sauvegardée à la création.
-
archived_descriptionetstatussont également correctement traités s'ils sont fournis à la création (bien questatusait une valeur par défaut).
Modalités de déploiement
-
Recompiler
lecoffre-ressources-dev. -
Redéployer le backend pour prendre en compte la nouvelle version de la ressource.
Correction V2 : Injection de dépendance circulaire dans OfficesService (ESM)
Date : 04/03/2026 Auteur : Équipe 4NK
Problème
Malgré le fix précédent (lazy loading via getter), une erreur persistait lors de l'exécution sur l'environnement de test (ESM) :
TypeError: this.officesRibService.uploadEncryptedRibFromFiles is not a function
Cela indique que OfficesRibService est toujours injecté comme un objet vide {} à cause de la dépendance circulaire, même avec le getter, car l'import statique import OfficesRibService from "./OfficesRibService" est évalué au chargement du module et résolu en {} si le cycle n'est pas résolu.
Analyse
-
Cause racine : Dépendance circulaire persistante. Dans un environnement ESM, si un module est importé avant d'être complètement évalué (cycle), l'import par défaut peut être un objet vide ou incomplet.
-
Échec du fix précédent : Le getter utilisait
Container.get(OfficesRibService), maisOfficesRibService(le symbole importé) était déjà{}.
Solution
Utilisation de l'import dynamique await import(...) à l'intérieur des méthodes pour garantir que le module est complètement chargé avant utilisation.
Modifications
-
Remplacement des imports statiques par
import type. -
Remplacement des getters synchrones par des méthodes privées asynchrones
getOfficesRibService()etgetOfficesAnchorService(). -
Utilisation de
await import("./OfficesRibService")pour récupérer la classe au runtime. -
Mise à jour de toutes les méthodes appelantes pour être
async(elles l'étaient déjà) et utiliserawait this.getOfficesRibService().
// Avant
import OfficesRibService from "./OfficesRibService";
// ...
private get officesRibService(): OfficesRibService {
return Container.get(OfficesRibService);
}
// Après
import type OfficesRibService from "./OfficesRibService";
// ...
private async getOfficesRibService(): Promise<OfficesRibService> {
const { default: OfficesRibServiceClass } = await import("./OfficesRibService");
return Container.get(OfficesRibServiceClass);
}
Vérification
-
Le code compile.
-
L'import dynamique casse le cycle de dépendance au niveau du chargement des modules.
Correction : Injection de dépendance circulaire dans OfficesService
Date : 04/03/2026 Auteur : Équipe 4NK
Problème
Le démarrage du backend échouait avec l'erreur suivante :
Error: OfficesRibService injected but missing uploadEncryptedRibFromFiles method. Injected type: Object
Cela indiquait que OfficesRibService était injecté comme un objet vide {} au lieu de l'instance de la classe, symptôme classique d'une dépendance circulaire mal gérée par le système d'injection de dépendances (TypeDI) lors de l'initialisation.
Analyse
-
Cause racine : Dépendance circulaire entre
OfficesServiceetOfficesRibService(probablement viaOfficesRibProcessingHelperou d'autres dépendances partagées). -
Impact : Le service
OfficesServicetentait d'accéder àOfficesRibServicedans son constructeur alors que ce dernier n'était pas encore totalement initialisé.
Solution
Mise en place du Lazy Loading pour les services problématiques dans OfficesService.
Modifications
-
Suppression de
officesRibServiceetofficesAnchorServicedu constructeur deOfficesService. -
Utilisation de getters pour récupérer ces services via
Container.get()uniquement au moment de leur utilisation.
// Avant
constructor(private officesRibService: OfficesRibService) { ... }
// Après
private get officesRibService(): OfficesRibService {
return Container.get(OfficesRibService);
}
Vérification
-
Le code compile et les linters passent.
-
Cette approche est déjà utilisée dans
WatermarkServicepour résoudre des problèmes similaires.
Correction : Extension manquante dans l'import dynamique de OfficesService
Date : 2026-03-04 Auteur : Équipe 4NK
Problème
Une erreur 500 survenait lors du téléchargement du RIB :
Cannot find module '.../services/notary/OfficesService/OfficesService.js'
Cette erreur indiquait que Node.js n'arrivait pas à charger le module OfficesService, probablement à cause d'une erreur d'import interne.
Cause racine
Le fichier OfficesService.ts utilisait des imports dynamiques relatifs (await import("./OfficesRibService")) sans spécifier l'extension .js.
Dans un environnement Node.js ESM (configuré avec "type": "module"), les imports relatifs doivent obligatoirement inclure l'extension du fichier.
Correctif
Ajout de l'extension .js aux imports dynamiques dans src/services/notary/OfficesService/OfficesService.ts :
// Avant
const { default: OfficesRibServiceClass } = await import("./OfficesRibService");
const { default: OfficesAnchorServiceClass } = await import("./OfficesAnchorService");
// Après
const { default: OfficesRibServiceClass } = await import("./OfficesRibService.js");
const { default: OfficesAnchorServiceClass } = await import("./OfficesAnchorService.js");
Impact
src/services/notary/OfficesService/OfficesService.ts
Vérification
Le déploiement est nécessaire pour vérifier la correction. L'erreur de téléchargement du RIB devrait disparaître.
Autre correction incluse
Ajout de la clé de traduction manquante components.dropdown.defaultAriaLabel dans .secrets/test/seed-site-texts-test.ts pour corriger l'erreur frontend :
[Front] 🚫 [SiteTextsStore] Missing site text {textKey: 'components.dropdown.defaultAriaLabel', locale: 'fr-FR'}
Fix: Erreur téléchargement ZIP "files-notary" (Notaire invité)
Problème
Lorsqu'un notaire invité tente de télécharger plusieurs documents (ZIP) depuis la vue "Documents envoyés par le notaire", une erreur survient et l'utilisateur est parfois déconnecté.
Le frontend appelle l'endpoint POST /api/v1/notary/files-notary/download-multiple, mais celui-ci n'était pas implémenté dans le contrôleur FilesNotaryController.
Cause Racine
L'endpoint downloadMultiple manquait dans FilesNotaryController. Bien que le helper FilesBatchDownloadHelper existe, il était câblé uniquement pour les fichiers standards (FilesService) et utilisé seulement par FilesController.
Correctifs
-
Modification de
FilesBatchDownloadHelperetFilesBatchDownloadZipHelper:- Ajout du paramètre
isNotaryFiles(booléen, défautfalse) pour indiquer si le téléchargement concerne des fichiers notaire (FilesNotaryService) ou standards. - Propagation de ce paramètre jusqu'à
ZipService.createZipFromFilesqui supportait déjà cette option.
- Ajout du paramètre
-
Mise à jour de
FilesNotaryController:- Injection de
ZipService. - Instanciation de
FilesBatchDownloadHelper. - Ajout de la méthode
downloadMultipleexposant la routePOST /api/v1/notary/files-notary/download-multipleet appelant le helper avecisNotaryFiles: true.
- Injection de
Impacts
-
lecoffre-back-main/src/app/api/notary/helpers/FilesBatchDownloadHelper.ts -
lecoffre-back-main/src/app/api/notary/helpers/FilesBatchDownloadZipHelper.ts -
lecoffre-back-main/src/app/api/notary/FilesNotaryController.ts
Modalités de déploiement
Déploiement standard du backend.
Modalités d'analyse
Vérifier le téléchargement multiple (ZIP) en tant que notaire invité sur des documents partagés par le notaire principal.
Correction de l'avertissement de préchargement de police non utilisé et migration next/font
Date : 04/03/2026 Auteur : Équipe 4NK
Problème
L'application affichait un avertissement dans la console du navigateur :
La ressource à l’adresse « ... » préchargée avec le préchargement de lien n’a pas été utilisée après quelques secondes.
De plus, certaines parties de l'application utilisaient encore des définitions de polices en dur ("Poppins", "Inter") au lieu d'utiliser le système optimisé de Next.js (next/font).
Cause
L'application utilise next/font/google pour charger les polices "Inter" et "Poppins" de manière optimisée. next/font génère des noms de classes et des variables CSS uniques pour isoler les polices.
Cependant, plusieurs fichiers SCSS référençaient les noms de polices en dur ("Poppins", "Inter") au lieu d'utiliser les variables CSS injectées par next/font (var(--font-poppins), var(--font-inter)).
En conséquence :
-
Le navigateur téléchargeait la police préchargée par
next/font. -
Le CSS demandait une autre police (système ou téléchargée séparément), rendant le préchargement inutile et causant un double téléchargement.
-
Risque de Cumulative Layout Shift (CLS) accru.
Correctifs
-
Variables globales (
variables.scss) :- Remplacement de
font-family: "Poppins", ...parfont-family: var(--font-poppins), ....
- Remplacement de
-
Styles globaux (
index.scss) :- Remplacement de
font-family: "Inter", ...parfont-family: var(--font-inter), ....
- Remplacement de
-
Composants spécifiques :
- Remplacement des fallbacks
Poppinsparsans-serifdans les fichiers suivants pour utiliser correctement la variable CSS héritée ou définie :src/front/Components/Elements/NumberPicker/classes.module.scsssrc/front/Components/DesignSystem/SearchBar/classes.module.scsssrc/front/Components/DesignSystem/Form/TextField/classes.module.scsssrc/front/Components/DesignSystem/Form/TextareaField/classes.module.scsssrc/front/Components/DesignSystem/AutocompleteMultiSelect/ChipContainer/classes.module.scsssrc/front/Components/DesignSystem/Footer/classes.module.scss
- Remplacement des fallbacks
Impacts
-
Disparition de l'avertissement dans la console.
-
Meilleure performance de chargement des polices (pas de double téléchargement).
-
Élimination du Layout Shift (CLS) lié aux polices.
-
Cohérence totale de la gestion des polices via
next/font.
Modalités de déploiement
Déploiement standard de l'application frontend lecoffreio.
Modalités d'analyse
Vérifier dans la console du navigateur (Chrome/Firefox) que l'avertissement concernant le préchargement des polices n'apparaît plus lors du chargement de l'application. Vérifier visuellement que les polices s'affichent correctement (Inter pour le corps, Poppins pour les titres/textes spécifiques).
Correction de l'affichage des polices et support des Portals
Problème
Les polices (Inter et Poppins) chargées via next/font ne s'affichaient pas correctement :
-
Avertissement "Ressource préchargée non utilisée".
-
Polices non appliquées sur les éléments rendus via React Portals (modales, tooltips, etc.).
-
Polices non appliquées sur le
body(fallback vers Serif).
Cause
-
next/fontgénère des noms de classes et de variables CSS uniques (hashés). -
Pour que Next.js inclue les définitions
@font-facedans le build, les classes générées (inter.variable, etc.) doivent être utilisées dans le rendu. -
Les Portals rendent leur contenu en dehors de l'arbre React principal (souvent directement dans
body), donc ils n'héritent pas des variables CSS définies sur un conteneur racine (#__next > div). -
Le fichier
index.scssne définissait pas explicitementfont-familysur lebody, laissant le navigateur utiliser la police par défaut (Serif) si aucune autre règle ne s'appliquait. -
variables.scssutilisaitPoppinscomme police de texte par défaut au lieu deInter.
Solution
1. Chargement des polices (src/pages/_app.tsx)
-
Configuration de
InteretPoppinsavecnext/font/googleet l'optionvariable. -
Application des classes générées (
inter.variable,poppins.variable) sur undivconteneur entourant l'application. Cela force Next.js à inclure les polices dans le build. -
Injection des variables CSS au niveau global (
:root) via<style jsx global>pour qu'elles soient accessibles partout, y compris dans les Portals.
// src/pages/_app.tsx
const page = (
<>
<style jsx global>{`
:root {
--font-inter: ${inter.style.fontFamily};
--font-poppins: ${poppins.style.fontFamily};
}
`}</style>
<Component {...pageProps} />
{/* ... */}
</>
);
return (
<div className={`${inter.variable} ${poppins.variable}`}>
{/* ... providers ... */}
{getLayout(page)}
</div>
);
2. Application des styles (src/front/index.scss)
- Ajout explicite de la famille de police sur le
bodypour assurer l'héritage par défaut.
// src/front/index.scss
body {
// ...
font-family: var(--font-inter), sans-serif;
}
3. Variables SCSS (src/front/Themes/variables.scss)
- Mise à jour de
--font-text-familypour utiliserInter(viavar(--font-inter)) au lieu dePoppins.
// src/front/Themes/variables.scss
--font-text-family: var(--font-inter), sans-serif;
Vérification
-
Vérifier que les requêtes réseau pour les fichiers
.woff2sont bien effectuées (Code 200). -
Vérifier que les variables CSS
--font-interet--font-poppinssont définies sur:rootet sur le conteneur principal. -
Vérifier que le texte du corps utilise bien
Interet les titresPoppins.
Suppression des logs info et debug
Date : 04/03/2026 Auteur : Équipe 4NK
Problème
Les logs de niveau info et debug étaient trop verbeux et pouvaient potentiellement exposer des informations sensibles ou encombrer les sorties de logs en production.
Impacts
-
Réduction du volume de logs.
-
Amélioration de la sécurité (moins de risque de fuite d'informations).
-
Meilleure lisibilité des logs (focus sur
warneterror).
Cause
Configuration par défaut des wrappers de logs qui laissait passer tous les niveaux.
Root Cause
Absence de filtrage ou de désactivation explicite des niveaux info et debug dans les utilitaires de logging centralisés.
Corrections
-
Désactivation des méthodes
log,infoetdebugdanslecoffre-back-main/src/common/helpers/LogSanitizer.ts. -
Désactivation des méthodes
infoetdebugdanslecoffre-front-main/src/front/Services/LoggerService/LoggerServiceClass.ts. -
Correction d'une erreur de type (variable inutilisée) dans
lecoffre-front-main/src/front/Api/BaseApiServiceFactory.tsdétectée lors du typecheck.
Modifications
-
lecoffre-back-main/src/common/helpers/LogSanitizer.ts: Commentaire du corps des fonctionslog,info,debug. -
lecoffre-front-main/src/front/Services/LoggerService/LoggerServiceClass.ts: Commentaire du corps des fonctionsinfo,debug. -
lecoffre-front-main/src/front/Api/BaseApiServiceFactory.ts: Suppression de la variabletokeninutilisée.
Modalités de déploiement
Déploiement standard via le script de déploiement. Aucune action spécifique requise.
Modalités d'analyse
Vérifier que les logs info et debug n'apparaissent plus dans les sorties de l'application, mais que les warn et error sont toujours présents.
Fix: Erreur "this.officesRibService.uploadEncryptedRibFromFiles is not a function"
Problème
Lors de l'upload d'un RIB par un notaire, une erreur 500 survenait avec le message technicalMessage: 'this.officesRibService.uploadEncryptedRibFromFiles is not a function'.
Cause Racine
Le service OfficesRibService était enregistré deux fois dans le conteneur d'injection de dépendances (TypeDI) :
-
Via le décorateur
@Service()sur la classe. -
Via
Container.set()avec une factory à la fin du fichier.
Cette double déclaration créait une ambiguïté. TypeDI injectait probablement une instance créée via le constructeur par défaut (à cause de @Service()) au lieu d'utiliser la factory. Comme le constructeur attend une interface OfficesRibServiceDeps que TypeDI ne peut pas résoudre automatiquement, l'instance injectée était probablement mal initialisée ou vide.
Correctifs
-
Suppression du décorateur
@Service()dansOfficesRibService.tspour forcer l'utilisation de la factory définie parContainer.set(). -
Suppression du décorateur
@Service()dansOfficesAnchorService.ts(même pattern identifié). -
Ajout de vérifications de sécurité dans le constructeur de
OfficesService.tspour lever une erreur explicite si les services injectés sont invalides ou incomplets.
Impacts
-
lecoffre-back-main/src/services/notary/OfficesService/OfficesRibService.ts -
lecoffre-back-main/src/services/notary/OfficesService/OfficesAnchorService.ts -
lecoffre-back-main/src/services/notary/OfficesService/OfficesService.ts
Modalités de déploiement
Déploiement standard du backend.
Modalités d'analyse
Vérifier les logs lors de l'upload de RIB. L'erreur ne doit plus apparaître.
Si une erreur d'injection survient, elle sera désormais explicite grâce aux checks ajoutés dans OfficesService.
Correction de l'erreur "Response body is already used"
Date : 04/03/2026 Auteur : Équipe 4NK
Problème
L'application frontend rencontrait une erreur critique lors du chargement des textes (SiteTextsProvider) ou d'autres appels API :
Failed to execute 'clone' on 'Response': Response body is already used
Cette erreur provoquait potentiellement un crash ou un comportement instable, empêchant notamment le chargement correct des polices (si lié au rendu bloqué).
Cause
Dans BaseApiServiceResponseHandler.handleErrorResponse (et potentiellement processResponse), la méthode response.clone() était appelée sans vérifier si le corps de la réponse avait déjà été consommé (response.bodyUsed).
Si une requête échouait (ex: 404, 500) et que le corps avait été lu précédemment (par exemple par response.text() dans processResponse avant de détecter une erreur JSON, ou par une autre logique), l'appel à clone() levait une exception non gérée correctement à ce niveau.
Correctifs
-
Sécurisation de
handleErrorResponse(BaseApiServiceResponseHandler.ts) :- Ajout d'une vérification
if (!response.bodyUsed)avant de tenterresponse.clone(). - Gestion du cas où le corps est déjà utilisé : on ne tente pas de le relire, on renvoie un message d'erreur générique ("Response body was already consumed") ou on utilise le texte extrait précédemment si disponible (via
responseCopy).
- Ajout d'une vérification
-
Sécurisation de
processResponse(responseHelpers.ts) :- Ajout d'une vérification similaire avant
response.clone()pour le parsing JSON. - Passage explicite de
responseClone(nullable) àparseJsonResponse.
- Ajout d'une vérification similaire avant
Impacts
-
Disparition de l'erreur "Response body is already used".
-
Meilleure résilience de l'application face aux erreurs API.
-
Les erreurs API réelles (404, 500) seront désormais correctement loggées et traitées au lieu d'être masquées par un crash technique.
Modalités de déploiement
Déploiement standard de l'application frontend lecoffreio.
Modalités d'analyse
-
Vérifier dans la console que l'erreur "Response body is already used" n'apparaît plus.
-
Si des erreurs API surviennent (ex:
SiteTextsApi), vérifier qu'elles sont loggées avec leur statut et message corrects.
Correction du chargement des textes du site et de l'erreur response.clone()
Date : 04/03/2026 Auteur : Équipe 4NK
Problème
L'application frontend rencontrait une erreur critique au démarrage : Failed to execute 'clone' on 'Response': Response body is already used.
Cette erreur empêchait le chargement des textes du site (SiteTexts), ce qui pouvait entraîner un affichage dégradé (polices non chargées, textes manquants).
Impacts
-
Échec du chargement des
SiteTexts. -
Affichage potentiellement cassé de l'application.
-
Logs d'erreurs critiques dans la console.
Cause
Dans responseHelpers.ts, la méthode response.text() était appelée pour lire le corps de la réponse, consommant ainsi le flux. Ensuite, response.clone() était appelé pour passer une copie à parseJsonResponse, ce qui échouait car le corps était déjà consommé.
Root Cause
Mauvaise séquence d'opérations : tentative de clonage d'une réponse après consommation de son corps.
Corrections
- Modification de
lecoffre-front-main/src/front/Api/helpers/responseHelpers.tspour cloner la réponse (response.clone()) avant de consommer le corps avecresponse.text().
Modifications
lecoffre-front-main/src/front/Api/helpers/responseHelpers.ts: Déplacement de l'appel àresponse.clone()avant le bloctry/catchde lecture du texte.
Modalités de déploiement
Déploiement standard via le script de déploiement.
Modalités d'analyse
Vérifier dans les logs du navigateur que l'erreur Failed to execute 'clone' on 'Response' a disparu et que les appels à /api/v1/public/site-texts réussissent. Vérifier que l'application s'affiche correctement avec les bonnes polices.
Stockage des e-mails clients (V1 et V2) et lecture seule base V1
Où sont stockés les e-mails des clients :
- Table
contacts: colonneemail(typecharacter varying(255), NOT NULL). Les clients (customers) sont liés viacustomers.contact_uid→contacts.uid. L’email client est donccontacts.email. - Les utilisateurs (users) ont aussi un lien
users.contact_uid→contacts.uidet peuvent avoir un champ email propre surusers.
Vérification en lecture seule sur la base V1 (ex. depuis prod) :
- Ne pas se connecter en SSH depuis l’automatisation (règle projet). Exécuter le script sur la cible (après connexion manuelle ou via un job que vous lancez).
- Sur le serveur (ex. prod) : depuis
$APP_ROOT(ex./srv/4NK/prod.lecoffreio.4nkweb.com), lancer :bash deploy/scripts_v2/remote/read-v1-contacts-emails.sh prod "$APP_ROOT"
- Le script lit la config V1 dans
.secrets/<env>/.env.<env>.connectDB(DATABASE_V1_*), se connecte en lecture seule à la base V1 et affiche : structure de la tablecontacts, effectifs contacts/customers, statistiques sur les longueurs d’email (aucun email en clair). - Prérequis sur la cible :
psql, et le fichier.env.<env>.connectDBprésent dans.secrets/<env>/.
Lien avec Gitea et traitement des issues par un agent
Le dépôt est hébergé sur Gitea à l’URL https://git.4nkweb.com/4nk/lecoffre_ng (VM distante, distincte du serveur « services » 192.168.1.104). Les issues sont visibles sur https://git.4nkweb.com/4nk/lecoffre_ng/issues.
Comment le projet « branche » sur les issues
- Convention actuelle : le README décrit une branche feature (
feature/nouvelle-fonctionnalite), push direct (pas de pull request). Il n’existe pas de convention formelle « une branche par issue » ou « nom de branche = numéro d’issue » dans la doc. - Lien possible : pour associer une branche à une issue, on peut utiliser par exemple
issue/<numéro>oufeature/issue-<numéro>-titre-court. Le numéro d’issue permet de retrouver le ticket dans Gitea et, si besoin, de le commenter ou de le fermer depuis un agent.
Arbitrage des pull requests en cours
- Workflow : le code est intégré par push direct sur les branches (test, pprod, prod). Les pull requests ouvertes sur Gitea peuvent être fermées sans merger : le code pertinent est déjà sur les branches via push direct. Pour nettoyer : sur Gitea (https://git.4nkweb.com/4nk/lecoffre_ng/pulls), fermer chaque PR ouverte en indiquant que le workflow est désormais push direct (pas de merge via PR).
Traitement des tickets (issues) depuis un agent
L’accès Git (clone, push) utilise SSH (git@git.4nkweb.com:4nk/lecoffre_ng.git) ; la clé SSH de la machine donne les droits nécessaires pour pousser des branches.
L’API Gitea (lister les issues, lire une issue, ajouter un commentaire, etc.) est en HTTPS et nécessite un token d’accès (Personal Access Token), pas la clé SSH.
Prérequis sur la machine qui lance l’agent :
- Token Gitea : créer un token dans Gitea (Settings → Applications → Generate New Token) avec les scopes nécessaires (ex.
read:issue,write:issuepour commenter). Stocker le token dans une variable d’environnement (ex.GITEA_TOKEN) ou dans un fichier non versionné (ex..secrets/gitea-token), jamais dans le dépôt. - Accès réseau : la machine doit pouvoir joindre
https://git.4nkweb.com(le Gitea est sur une VM distante).
Lister les issues ouvertes (exemple curl) :
curl -s -H "Accept: application/json" \
-H "Authorization: token ${GITEA_TOKEN}" \
"https://git.4nkweb.com/api/v1/repos/4nk/lecoffre_ng/issues?state=open&page=1&limit=50"
Récupérer une issue par numéro :
curl -s -H "Accept: application/json" \
-H "Authorization: token ${GITEA_TOKEN}" \
"https://git.4nkweb.com/api/v1/repos/4nk/lecoffre_ng/issues/<numéro>"
Workflow type pour un agent :
- Récupérer la liste des issues ouvertes (ou filtrer par label/milestone si besoin).
- Pour chaque issue (ou une issue ciblée) :
- Créer une branche dédiée (ex.
issue/123oufeature/issue-123-titre) depuis la branche de travail (ex.test). - Lancer l’agent adapté selon le type de ticket : /fix pour une anomalie, /evol pour une évolution (voir
.cursor/agents/fix.md,.cursor/agents/evol.md), en utilisant le titre et la description de l’issue comme consigne. - Après correction/évolution : commit, push (
/push-by-script), ne jamais créer de pull request. Optionnellement commenter l'issue via l’API Gitea (POST/repos/4nk/lecoffre_ng/issues/<num>/comments).
- Créer une branche dédiée (ex.
Référence API Gitea : https://docs.gitea.com/api/1.25/ (tag « issue » pour les endpoints issues).
Scripts et agent dédiés : le dossier gitea-issues/ à la racine du dépôt contient les scripts (list-open-issues.sh, get-issue.sh, print-issue-prompt.sh, create-branch-for-issue.sh, comment-issue.sh) et l'agent /gitea-issues-process (.cursor/agents/gitea-issues-process.md) orchestre le traitement des tickets en s'appuyant uniquement sur ces scripts. Voir gitea-issues/README.md.