ia_dev/projects/lecoffreio/docs/OPERATIONS.md
Nicolas Cantu 61cec6f430 Sync ia_dev: token resolution via .secrets/<env>/ia_token, doc updates
**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
2026-03-16 15:00:23 +01:00

93 KiB
Raw Blame History

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 lancrage de documents notariés :

  • Unsupported state or unable to authenticate data (Decipheriv.final) puis Fichier 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) :

  1. Échec déchiffrement : La clé doffice 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é doffice incorrecte en BDD.
  2. PDF invalide après déchiffrement : Le buffer déchiffré commence par %PDF mais la structure du PDF est invalide ou non supportée par pdf-lib (référence dobjet 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 lUID du fichier (File <uid>: ...) dans FilesNotaryService (déchiffrement et fallback clé globale) pour faciliter le diagnostic en logs.
  • Message derreur 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 lerreur de téléchargement/déchiffrement avec le fileUid dans 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éfixe File ${uid}:.
  • lecoffre-back-main/src/services/common/WatermarkService/helpers/WatermarkPdfProcessorHelper.ts : dans le catch de addWatermark, si le message contient catalog, Pages is not a function ou Invalid object ref, ajouter un hint dans le message et le log.
  • lecoffre-back-main/src/services/common/WatermarkService/helpers/WatermarkBufferProcessorHelper.ts : dans validatePdfBuffer, même hint lorsque PDFDocument.load échoue avec une de ces chaînes.
  • lecoffre-back-main/src/services/notary/DocumentAnchorsService/helpers/DocumentAnchoringWatermarkHelper.ts : .catch sur filesService.download(fileUid) pour renvoyer une erreur incluant fileUid.

Impact : Les logs en prod contiennent lUID du fichier en erreur et un message plus explicite pour les PDF invalides. Comportement fonctionnel inchangé : lancrage échoue toujours pour ces cas ; le diagnostic est amélioré.

Modalités de déploiement : Déploiement standard backend (build-and-deploy).

Modalités danalyse : Consulter les logs backend (notary, ancrage) ; en cas derreur dancrage, 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é doffice 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é à lajout dun confrère (annuaire)

Problème : Lors de lajout dun confrère depuis lannuaire (recherche notaire), le champ « Email de préférence pour les envois » restait vide au lieu dêtre pré-rempli avec lemail du notaire sélectionné.

Cause : La recherche utilise lAPI Annuaire V2 : dabord lendpoint PB lookup (/api/v2/directory/lookup), puis en fallback lendpoint 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 lemail à une chaîne vide et ne lisait pas déventuels champs email/mail renvoyés par lAPI. De plus, lorsque le lookup renvoyait des résultats, lappel 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 lobjet personne dans la réponse lookup. (2) Absence denrichissement des résultats lookup par un appel PP pour compléter les emails manquants.

Correction :

  • Dans buildResultFromPersonne, extraction de lemail depuis lobjet personne via pickFirst(..., "email", "mail", "courriel") au lieu dune chaîne vide ; types AnnuaireLookupPerson et paramètre de buildResultFromPersonne étendus avec mail? et email?.
  • Après le PB lookup, si des résultats existent, appel dune nouvelle méthode enrichLookupResultsWithPpEmails : un appel unique à lendpoint PP (première page) avec la même requête (prenom, nomUsuel), construction dune 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 : types AnnuaireLookupPerson et paramètre de buildResultFromPersonne avec mail?, email? ; buildResultFromPersonne utilise pickFirst pour lemail et linclut dans le displayName de fallback ; nouvelle méthode privée enrichLookupResultsWithPpEmails ; après le lookup réussi, appel à cette méthode avant de retourner.

Impact : Lorsquun confrère est choisi dans lannuaire, lemail de préférence pour les envois est pré-rempli lorsque lAPI (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 danalyse : Depuis linterface dajout 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 lannuaire retourne un email pour ce notaire. Vérifier en logs backend les messages denrichissement (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 loffice actif est GUEST_FOLDERS (400 : « Active office is required to list shared folders »). Pour un notaire invité, loffice 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 lutilisateur 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 useRolePermissions et activeOfficeStore.
    • Détection de GUEST_FOLDERS via permissionContext?.activeOfficeUid ou activeOfficeStore.activeOffice?.office_uid.
    • Si GUEST_FOLDERS : rendu de DefaultNotaryDashboard sans prop folders (chargement interne via GET /notary/folders).
    • Sinon : comportement inchangé (chargement via getSharedFolders(), mapping en OfficeFolderExtended).

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 safficher sans erreur.

Évolution Personne en charge du dossier (main_contact_uid) 2026-03-12

Objectif : Afficher dans lespace 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 à lenregistrement.

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 danalyse : 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 -e autour de l'appel Phase 4 : set +e avant ssh_run, set -e après capture de phase4_rc=$?, afin que les échecs partiels (documents ignorés/skip) ne fassent pas quitter le script.
    • Capture du code de sortie de ssh_run pour 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..." sans exit 1.
    • Si phase4_rc -eq 0 : success "Phase 4 terminée avec succès" comme avant.

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 boucle while IFS= read -r line qui 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).

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 hasThirdParties via safeArrayLength(folderWithRelations?.third_parties) > 0.
    • Retour de doesFolderHaveParticipants étendu à hasThirdParties || (en plus de hasCustomers, hasFolderSharings, hasThirdPartyDocuments).
    • Typage du folder étendu pour inclure third_parties dans le cast (aligné sur folder_sharings).

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, lajout seul dun 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 linclude le demande. Après déploiement backend, les onglets membres saffichent 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 lajout dun confrère, le filtre ne remonte que quelques comptes alors que les notaires ont bien leurs noms et prénoms dans lannuaire / IdNot.

Cause : Lorsque lAPI Annuaire V2 nest 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 lAPI é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, doù 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) à lAPI.

Correction : Passer la requête à lAPI comme pour lAnnuaire 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 lURL avec prenom et nomUsuel dé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 dutiliser lAPI 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 dAPI 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 sappuie 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 lannuaire nest pas configuré : le backend renvoie { results: [], annuaireConfigured: false, message } ; le front affiche un message dalerte, sans fallback. Réponse API : { results, annuaireConfigured, message? }. Plus de fallback legacy IdNot lorsque lannuaire nest 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) : Lannuaire 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, lAPI 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 lannuaire 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 lURL appelée (sans la clé) ; ✅ ... lookup page 0: X entité(s), Y notaire(s) retenus indique si lAPI 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 lenvironnement (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 lURL 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 :

  1. deploy.sh appelle sync-secrets-to-target.sh : le fichier local .secrets/<env>/.env.<env>.connectDB est copié vers la cible APP_ROOT/.secrets/<env>/. Si le fichier local est absent, aucune copie n'est faite et la cible garde son fichier existant.
  2. import-v1.sh au début recopie le connectDB local vers la cible (deploy/ puis cp vers .secrets/<env>/).
  3. 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 lancien fichier déjà présent sur la cible (éventuellement incorrect). En cas derreur « 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) : Lancienne ligne export "${key}=${value}" provoquait lexpansion par le shell de ${value} : un mot de passe contenant $ ou ` était modifié (ex. my$ecr3tmy si la variable ecr3t est vide). La correction : exporter la valeur sans expansion en lenveloppant 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 à largument 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>.connectDB avec load_connectdb_v2 pour 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 via getV1DatabaseConfig (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 finalizeUserLoginFlow avec ensuredPayload.userId (utilisateur authentifié). Les offices sont dérivés de V1 via user_office_affiliations WHERE user_uid = $1 ; toutes les tables mergées et l'ancrage sont filtrés par ces officeUids. 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_dump vers 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 fichier backend.env est supprimé sur chaque cible à chaque exécution de sync-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.env ni router.env. Elles chargent uniquement les fichiers de APP_ROOT/.secrets/<env>/ via des drop-ins par instance : .env.<env> pour tous les services, et .env.<env>.connectDB en plus pour le backend. Ces fichiers ne sont pas modifiés par le déploiement. Le fichier .env.<env> doit contenir notamment : pour le backend ENV, NODE_ENV, APP_PORT ; pour le frontend PORT ; pour le router ROUTER_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 dunicité sur (name, office_uid)

Problème : Le mode MERGE peut échouer sur des tables dont lunicité métier est portée par (name, office_uid) (ex. deed_types, document_types) lorsque des lignes V1 utilisent un uid différent dune 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 lUPSERT, appliquer un mécanisme générique piloté par allowlist :

Optionnel : limiter limport 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_v2 depuis la clé métier (ex. (name, office_uid)) et lexporter 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 dun 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 dun usage systématique des appels API au moment de la demande.

Correction :

  • Suppression des points dentré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 dusage 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 quun utilisateur était identifié comme super-admin, y compris sur des pages non liées (ex: /folders). En cas dalé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 dadministration 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 derreur réseau sur GET /super-admin/role-permissions lors 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 danalyse :

  • Se connecter en super-admin et naviguer vers /folders puis vérifier labsence dappel GET /api/v1/super-admin/role-permissions.
  • Naviguer vers /super-admin/role-permissions et vérifier que lappel 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 RequestOptions et ProcessResponseOptions afin 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/types pour simplifier les imports.

Impact :

  • Code plus modulaire et plus facile à tester.
  • Réduction significative des erreurs de lint max-lines-per-function, max-params et complexity.
  • 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" dans configureThirdPartyQuery et configureCustomerQuery.
    • Ajout de la vérification du statut dans getOneByUid.

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.getUniqueFileName pour garantir l'unicité des noms dans l'archive.
  • Fichier : lecoffre-back-main/src/services/common/FileNamingService/FileNamingService.ts
    • Ajout de la méthode getUniqueFileName pour gérer la déduplication avec suffixe (n).

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 lUI 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 densure).

Correction : Backfill idempotent (insert-only) des entrées admin à partir des entrées collaborator pour les ressources notariales :

  • Script densure : deploy/scripts_v2/remote/sql/ensure-role-permissions-matrix-folders.sql (clonage collaboratoradmin avec ON 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 idempotent collaboratoradmin si 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 densure habituel.

  • Depuis deploy/scripts_v2/deploy.sh : exécution systématique de deploy/scripts_v2/remote/ensure-role-permissions-matrix-folders.sh après migrations.

  • Diagnostics (logs) :

    • deploy/scripts_v2/run-check-user-offices-logs.sh <env> : journalctl backend centré sur UserOffices et sync offices (utile quand le front loggue OfficeSelector hidden: no offices available).
    • Les scripts de diagnostic scripts_v2/remote/* utilisent print_journalctl_matches / count_journalctl_matches (définis dans deploy/scripts_v2/remote/_lib.sh) pour rester stables avec set -euo pipefail même quand il ny 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 de Notaire / 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 doffice par défaut (Notaire, Collaborateur) et liaison aux règles attendues.
  • Vérification/correction systématique au déploiement : exécution dun 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.ts
  • lecoffre-back-main/src/services/common/AuthService/helpers/JwtPayloadGenerationHelper.ts
  • lecoffre-back-main/src/services/common/IdNotDirectoryService/helpers/DirectoryRequiredFieldsHelper.ts
  • deploy/scripts_v2/remote/sql/ensure-default-office-roles.sql
  • deploy/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.

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 useSiteTextContent avec 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 dans SITE_TEXTS_KEYS_INDEX de lenv-full. Le boot (page /_app) précharge désormais aussi le scope components (en plus de login) via lecoffre-front-main/src/front/Stores/siteTextsBootConfig.ts, pour éviter les logs Missing site text quand 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 scope components ou ajouter des scopes métier et les clés correspondantes au seed puis à lenv-full.
  • Backend : Les messages derreur 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 nest en place ; une évolution ultérieure pourrait exposer des clés i18n côté API si besoin.
  • Ressources : Le package le-coffre-resources fournit des types (Notary, SuperAdmin, etc.) ; pas de chaînes utilisateur à migrer.

Modifications effectuées :

  • MyAccount / Mon compte : Utilisation des clés myAccount.office.idNot.label et myAccount.office.idNot.placeholder déjà présentes dans le seed ; passage de ces libellés depuis le parent vers MyAccountOfficeSection (plus de texte en dur pour lidentifiant 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.ts et de .secrets/test/env-full-test-for-bdd-injection.txt (SITE_TEXTS_KEYS_INDEX). Page offices/[officeUid] enveloppée dans SiteTextsProvider avec scopes={["office"]} ; composant OfficeInformations migré pour utiliser useSiteTextContent et 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 sassurant que le fichier seed et lenv-full sont bien déployés (voir deploy/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>.ts et env-full-<env>-for-bdd-injection.txt) puis exécuter le seed sur lenvironnement cible.

Modalités danalyse :

  • 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_INDEX dans lenv-full contient toutes les clés du seed (ordre identique recommandé).
  • Vérifier au login que officeRole est résoluble et que les rules associé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 quau regroupement demails ou à la vérification des relances, pas pour lupload de documents.

Impacts

  • Un seul point de vérité pour : résolution de lemail 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 — exporte verifyGuestNotaryAccess et 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 (lemail 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 denvironnement.

Modalités danalyse

  • Téléchargement single fichier notary en notaire invité : accès autorisé uniquement si linvité 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 dun abonnement ACTIVE possède bien un siège (licence) pour cet office. Cette vérification est effectuée :

  1. À la connexion (login) : De manière automatique et non bloquante.

  2. Via script batch : Pour remédiation de masse.

Modifications

1. Backend - Services
  • UserOfficeAffiliationsService : Ajout de la méthode ensureSeatsForUserActiveAffiliations(userUid).

    • Récupère les affiliations actives.
    • Pour chaque affiliation, vérifie/crée le siège via SubscriptionsService.
  • SubscriptionsService : Modification de isUserSubscribed.

    • 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 false si pas de siège).
2. Backend - Flux de connexion
  • UserControllerLoginFlowAuthHelper :
    • Appel de ensureSeatsForUserActiveAffiliations après la synchronisation IdNot.
    • Exécuté dans un try/catch pour ne pas bloquer le login en cas d'erreur technique.
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.

Modalités de déploiement

  1. 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
    
  2. Exécution du script de remédiation :

    # Dry-run (simulation)
    npm run ensure:seats -- --dry-run
    
    # Exécution réelle
    npm run ensure:seats
    
  3. 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

  1. Lien retour après visualisation dun 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.

  2. Téléchargement de fichier avec accent
    Télécharger un fichier (filigrane + métadonnées) dont le nom contient des accents pouvait mal safficher ou mal se nommer (Content-Disposition sans guillemets autour du filename).

  3. Note à la création du dossier
    La note saisie à la création du dossier ne saffichait pas sur la page « Préparer le dossier » (add/clients) après redirection ; elle napparaissait quaprès modification ultérieure sur la fiche dossier.

Cause / root cause

  1. Retour
    LURL de retour dans ViewDocuments (Folder) est calculée via useReceivedDocumentBackUrl. Pour le notaire invité, elle dépend de document.shared_to_office et du statut SENT/DOWNLOADED. Lorsque le document est chargé via lAPI DocumentsNotary (customer), shared_to_office nest pas inclus dans la réponse, donc la condition échouait et on retombait sur lURL par défaut (FolderInformation ou vide → home). Root cause : pas de fallback quand lutilisateur est notaire invité avec un folderUid connu.

  2. Téléchargement
    Dans FilesNotaryDownloadHelper, le header Content-Disposition utilisait filename=${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 uniquement file_name au lieu de privilégier display_name quand disponible.

  3. 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 dans folderMetadata que number et name, pas description, et naffichait nulle part la note sur « Préparer le dossier ».

Correctifs

  1. Retour (viewDocumentsBackUrlHelpers.ts)

    • Ajout dun fallback : si lutilisateur est notaire invité et que folderUid est défini, lURL de retour est toujours InvitedReceivedDocuments (liste des documents envoyés), même lorsque le document na pas encore shared_to_office dans la réponse (ex. chargement via DocumentsNotary API).
  2. Téléchargement (FilesNotaryDownloadHelper.ts)

    • Utilisation de display_name si présent, sinon file_name.
    • Content-Disposition : filename="${encodeURIComponent(fileName)}" (guillemets autour de la valeur).
  3. Note (AddClientToFolder)

    • FolderMetadata étendu avec description?: string | null.
    • Dans folderPageDataLoaderHelpers, passage de description depuis le dossier chargé à setFolderMetadata.
    • Sur la page add/clients, affichage dun bloc « Note du dossier » sous le sous-titre lorsque folderMetadata.description est renseigné (style .folder-note dans classes.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 sappliquent à 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 denvironnement.

Modalités danalyse

  • 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_description et status sont également correctement traités s'ils sont fournis à la création (bien que status ait 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), mais OfficesRibService (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
  1. Remplacement des imports statiques par import type.

  2. Remplacement des getters synchrones par des méthodes privées asynchrones getOfficesRibService() et getOfficesAnchorService().

  3. Utilisation de await import("./OfficesRibService") pour récupérer la classe au runtime.

  4. Mise à jour de toutes les méthodes appelantes pour être async (elles l'étaient déjà) et utiliser await 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 OfficesService et OfficesRibService (probablement via OfficesRibProcessingHelper ou d'autres dépendances partagées).

  • Impact : Le service OfficesService tentait d'accéder à OfficesRibService dans 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
  1. Suppression de officesRibService et officesAnchorService du constructeur de OfficesService.

  2. 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 WatermarkService pour 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

  1. Modification de FilesBatchDownloadHelper et FilesBatchDownloadZipHelper :

    • Ajout du paramètre isNotaryFiles (booléen, défaut false) pour indiquer si le téléchargement concerne des fichiers notaire (FilesNotaryService) ou standards.
    • Propagation de ce paramètre jusqu'à ZipService.createZipFromFiles qui supportait déjà cette option.
  2. Mise à jour de FilesNotaryController :

    • Injection de ZipService.
    • Instanciation de FilesBatchDownloadHelper.
    • Ajout de la méthode downloadMultiple exposant la route POST /api/v1/notary/files-notary/download-multiple et appelant le helper avec isNotaryFiles: true.

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 à ladresse « ... » préchargée avec le préchargement de lien na 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 :

  1. Le navigateur téléchargeait la police préchargée par next/font.

  2. 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.

  3. Risque de Cumulative Layout Shift (CLS) accru.

Correctifs

  1. Variables globales (variables.scss) :

    • Remplacement de font-family: "Poppins", ... par font-family: var(--font-poppins), ....
  2. Styles globaux (index.scss) :

    • Remplacement de font-family: "Inter", ... par font-family: var(--font-inter), ....
  3. Composants spécifiques :

    • Remplacement des fallbacks Poppins par sans-serif dans les fichiers suivants pour utiliser correctement la variable CSS héritée ou définie :
      • src/front/Components/Elements/NumberPicker/classes.module.scss
      • src/front/Components/DesignSystem/SearchBar/classes.module.scss
      • src/front/Components/DesignSystem/Form/TextField/classes.module.scss
      • src/front/Components/DesignSystem/Form/TextareaField/classes.module.scss
      • src/front/Components/DesignSystem/AutocompleteMultiSelect/ChipContainer/classes.module.scss
      • src/front/Components/DesignSystem/Footer/classes.module.scss

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 :

  1. Avertissement "Ressource préchargée non utilisée".

  2. Polices non appliquées sur les éléments rendus via React Portals (modales, tooltips, etc.).

  3. Polices non appliquées sur le body (fallback vers Serif).

Cause

  1. next/font génère des noms de classes et de variables CSS uniques (hashés).

  2. Pour que Next.js inclue les définitions @font-face dans le build, les classes générées (inter.variable, etc.) doivent être utilisées dans le rendu.

  3. 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).

  4. Le fichier index.scss ne définissait pas explicitement font-family sur le body, laissant le navigateur utiliser la police par défaut (Serif) si aucune autre règle ne s'appliquait.

  5. variables.scss utilisait Poppins comme police de texte par défaut au lieu de Inter.

Solution

1. Chargement des polices (src/pages/_app.tsx)
  • Configuration de Inter et Poppins avec next/font/google et l'option variable.

  • Application des classes générées (inter.variable, poppins.variable) sur un div conteneur 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 body pour 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-family pour utiliser Inter (via var(--font-inter)) au lieu de Poppins.
// 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 .woff2 sont bien effectuées (Code 200).

  • Vérifier que les variables CSS --font-inter et --font-poppins sont définies sur :root et sur le conteneur principal.

  • Vérifier que le texte du corps utilise bien Inter et les titres Poppins.

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 warn et error).

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, info et debug dans lecoffre-back-main/src/common/helpers/LogSanitizer.ts.

  • Désactivation des méthodes info et debug dans lecoffre-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.ts détectée lors du typecheck.

Modifications

  • lecoffre-back-main/src/common/helpers/LogSanitizer.ts : Commentaire du corps des fonctions log, info, debug.

  • lecoffre-front-main/src/front/Services/LoggerService/LoggerServiceClass.ts : Commentaire du corps des fonctions info, debug.

  • lecoffre-front-main/src/front/Api/BaseApiServiceFactory.ts : Suppression de la variable token inutilisé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) :

  1. Via le décorateur @Service() sur la classe.

  2. 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

  1. Suppression du décorateur @Service() dans OfficesRibService.ts pour forcer l'utilisation de la factory définie par Container.set().

  2. Suppression du décorateur @Service() dans OfficesAnchorService.ts (même pattern identifié).

  3. Ajout de vérifications de sécurité dans le constructeur de OfficesService.ts pour 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

  1. Sécurisation de handleErrorResponse (BaseApiServiceResponseHandler.ts) :

    • Ajout d'une vérification if (!response.bodyUsed) avant de tenter response.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).
  2. 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.

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.ts pour cloner la réponse (response.clone()) avant de consommer le corps avec response.text().

Modifications

  • lecoffre-front-main/src/front/Api/helpers/responseHelpers.ts : Déplacement de l'appel à response.clone() avant le bloc try/catch de 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 : colonne email (type character varying(255), NOT NULL). Les clients (customers) sont liés via customers.contact_uidcontacts.uid. Lemail client est donc contacts.email.
  • Les utilisateurs (users) ont aussi un lien users.contact_uidcontacts.uid et peuvent avoir un champ email propre sur users.

Vérification en lecture seule sur la base V1 (ex. depuis prod) :

  • Ne pas se connecter en SSH depuis lautomatisation (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 table contacts, effectifs contacts/customers, statistiques sur les longueurs demail (aucun email en clair).
  • Prérequis sur la cible : psql, et le fichier .env.<env>.connectDB présent dans .secrets/<env>/.

Lien avec Gitea et traitement des issues par un agent

Le dépôt est hébergé sur Gitea à lURL 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 nexiste pas de convention formelle « une branche par issue » ou « nom de branche = numéro dissue » dans la doc.
  • Lien possible : pour associer une branche à une issue, on peut utiliser par exemple issue/<numéro> ou feature/issue-<numéro>-titre-court. Le numéro dissue 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

Laccè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.

LAPI Gitea (lister les issues, lire une issue, ajouter un commentaire, etc.) est en HTTPS et nécessite un token daccès (Personal Access Token), pas la clé SSH.

Prérequis sur la machine qui lance lagent :

  1. Token Gitea : créer un token dans Gitea (Settings → Applications → Generate New Token) avec les scopes nécessaires (ex. read:issue, write:issue pour commenter). Stocker le token dans une variable denvironnement (ex. GITEA_TOKEN) ou dans un fichier non versionné (ex. .secrets/gitea-token), jamais dans le dépôt.
  2. 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 :

  1. Récupérer la liste des issues ouvertes (ou filtrer par label/milestone si besoin).
  2. Pour chaque issue (ou une issue ciblée) :
    • Créer une branche dédiée (ex. issue/123 ou feature/issue-123-titre) depuis la branche de travail (ex. test).
    • Lancer lagent 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 lissue comme consigne.
    • Après correction/évolution : commit, push (/push-by-script), ne jamais créer de pull request. Optionnellement commenter l'issue via lAPI Gitea (POST /repos/4nk/lecoffre_ng/issues/<num>/comments).

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.