From 313e14e62c90b82cf1f85c21a1bfdaf11be072d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com>
Date: Mon, 31 Mar 2025 16:22:24 +0200
Subject: [PATCH 1/4] fix etapesTypesPossible

---
 .../titre-demarche-etat-validate.test.ts      | 224 ++++++++++++++++++
 .../titre-demarche-etat-validate.ts           |  38 ++-
 2 files changed, 249 insertions(+), 13 deletions(-)

diff --git a/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts b/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts
index 16e21079b..5d4d48f48 100644
--- a/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts
+++ b/packages/api/src/business/validations/titre-demarche-etat-validate.test.ts
@@ -806,6 +806,230 @@ describe('getPossiblesEtapesTypes', () => {
     `)
   })
 
+  test("peut déplacer la saisineDuPrefet qui est le même jour que d'autres", () => {
+    const sppId = newEtapeId('spp')
+    const etapes: TitreEtapeForMachine[] = [
+      {
+        typeId: ETAPES_TYPES.demande,
+        date: toCaminoDate('2023-02-28'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('demandeId'),
+        ordre: 1,
+        statutId: 'fai',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.avisDeMiseEnConcurrenceAuJORF,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('anf'),
+        ordre: 5,
+        statutId: 'ter',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('men'),
+        ordre: 2,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.saisineDuPrefet,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: sppId,
+        ordre: 3,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('mcr'),
+        ordre: 4,
+        statutId: 'fav',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+    ]
+
+    expect(
+      getPossiblesEtapesTypes(
+        new PrmOctMachine(TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX, DEMARCHES_TYPES_IDS.Octroi),
+        TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX,
+        DEMARCHES_TYPES_IDS.Octroi,
+        ETAPES_TYPES.saisineDuPrefet,
+        sppId,
+        toCaminoDate('2023-05-03'),
+        etapes
+      )
+    ).toMatchInlineSnapshot(`
+      {
+        "spp": {
+          "etapeStatutIds": [
+            "fai",
+          ],
+          "mainStep": true,
+        },
+      }
+    `)
+  })
+
+  test("peut déplacer l'enregistrement de la demande le même jour", () => {
+    const menId = newEtapeId('menId')
+    const etapes: TitreEtapeForMachine[] = [
+      {
+        typeId: ETAPES_TYPES.demande,
+        date: toCaminoDate('2023-02-28'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('demandeId'),
+        ordre: 1,
+        statutId: 'fai',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.avisDeMiseEnConcurrenceAuJORF,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('anf'),
+        ordre: 5,
+        statutId: 'ter',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: menId,
+        ordre: 2,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.saisineDuPrefet,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('spp'),
+        ordre: 3,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('mcr'),
+        ordre: 4,
+        statutId: 'fav',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+    ]
+
+    expect(
+      getPossiblesEtapesTypes(
+        new PrmOctMachine(TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX, DEMARCHES_TYPES_IDS.Octroi),
+        TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX,
+        DEMARCHES_TYPES_IDS.Octroi,
+        ETAPES_TYPES.enregistrementDeLaDemande,
+        menId,
+        toCaminoDate('2023-05-03'),
+        etapes
+      )
+    ).toMatchInlineSnapshot(`
+      {
+        "men": {
+          "etapeStatutIds": [
+            "fai",
+          ],
+          "mainStep": true,
+        },
+      }
+    `)
+  })
+
+  test("ne peut pas déplacer l'enregistrement de la demande après les autres étapes", () => {
+    const menId = newEtapeId('menId')
+    const etapes: TitreEtapeForMachine[] = [
+      {
+        typeId: ETAPES_TYPES.demande,
+        date: toCaminoDate('2023-02-28'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('demandeId'),
+        ordre: 1,
+        statutId: 'fai',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.avisDeMiseEnConcurrenceAuJORF,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('anf'),
+        ordre: 5,
+        statutId: 'ter',
+        communes: [{ id: toCommuneId('64012') }],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: menId,
+        ordre: 2,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.saisineDuPrefet,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('spp'),
+        ordre: 3,
+        statutId: 'fai',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+      {
+        typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+        date: toCaminoDate('2023-05-03'),
+        isBrouillon: ETAPE_IS_NOT_BROUILLON,
+        id: newEtapeId('mcr'),
+        ordre: 4,
+        statutId: 'fav',
+        communes: [],
+        demarcheIdsConsentement: [],
+      },
+    ]
+
+    expect(
+      getPossiblesEtapesTypes(
+        new PrmOctMachine(TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX, DEMARCHES_TYPES_IDS.Octroi),
+        TITRES_TYPES_IDS.PERMIS_EXCLUSIF_DE_RECHERCHES_METAUX,
+        DEMARCHES_TYPES_IDS.Octroi,
+        ETAPES_TYPES.enregistrementDeLaDemande,
+        menId,
+        toCaminoDate('2023-06-01'),
+        etapes
+      )
+    ).toMatchInlineSnapshot(`
+      {}
+    `)
+  })
+
   test('peut créer une étape sur une procédure spécifique vide', () => {
     expect(getPossiblesEtapesTypes(new ProcedureSpecifiqueMachine('cxm', 'oct'), 'cxm', 'oct', undefined, undefined, toCaminoDate('4000-02-01'), [])).toMatchInlineSnapshot(`
       {
diff --git a/packages/api/src/business/validations/titre-demarche-etat-validate.ts b/packages/api/src/business/validations/titre-demarche-etat-validate.ts
index df69a3fec..d3d4c7fad 100644
--- a/packages/api/src/business/validations/titre-demarche-etat-validate.ts
+++ b/packages/api/src/business/validations/titre-demarche-etat-validate.ts
@@ -192,25 +192,37 @@ export const getPossiblesEtapesTypes = (
 const etapesTypesPossibleACetteDateOuALaPlaceDeLEtape = (machine: CaminoMachines, etapes: TitreEtapeForMachine[], titreEtapeId: string | null, date: CaminoDate): EtapeTypeEtapeStatutWithMainStep => {
   const sortedEtapes = titreEtapesSortAscByOrdre(etapes).filter(etape => etape.id !== titreEtapeId)
   const etapesAvant: Etape[] = []
+  const etapesPendant: Etape[] = []
+
   const etapesApres: Etape[] = []
 
-  // TODO 2022-07-12: Il faudrait mieux gérer les étapes à la même date que l'étape qu'on veut rajouter
-  // elles ne sont ni avant, ni après, mais potentiellement au milieu de toutes ces étapes
-  // UPDATE 2025-03-31: il n'y en a pas en prod
-  etapesAvant.push(...toMachineEtapes(sortedEtapes.filter(etape => etape.date <= date)))
-  etapesApres.push(...toMachineEtapes(sortedEtapes.slice(etapesAvant.length)))
+  etapesAvant.push(...toMachineEtapes(sortedEtapes.filter(etape => etape.date < date)))
+  etapesPendant.push(...toMachineEtapes(sortedEtapes.filter(etape => etape.date === date)))
+  etapesApres.push(...toMachineEtapes(sortedEtapes.slice(etapesAvant.length + etapesPendant.length)))
 
-  const etapesPossiblesRaw = machine.possibleNextEtapes(etapesAvant, date)
+  if (!machine.isEtapesOk(etapesAvant)) {
+    return {}
+  }
   const etapesPossibles = []
-  for (const et of etapesPossiblesRaw) {
-    const newEtapes = [...etapesAvant]
 
-    const items = { ...et, date }
-    newEtapes.push(items)
-    newEtapes.push(...etapesApres)
+  for (let i = 0; i <= etapesPendant.length; i++) {
+    const etapeEnCours = [...etapesAvant, ...etapesPendant.slice(0, i)]
+    if (machine.isEtapesOk(etapeEnCours)) {
+      const etapesPossiblesRaw = machine.possibleNextEtapes(etapeEnCours, date)
+
+      for (const et of etapesPossiblesRaw) {
+        const newEtapes = [...etapeEnCours]
 
-    if (machine.isEtapesOk(newEtapes)) {
-      etapesPossibles.push(et)
+        const items = { ...et, date }
+        newEtapes.push(items)
+        newEtapes.push(...etapesPendant.slice(i))
+
+        newEtapes.push(...etapesApres)
+
+        if (machine.isEtapesOk(newEtapes)) {
+          etapesPossibles.push(et)
+        }
+      }
     }
   }
 
-- 
GitLab


From 685c793b4cd393e9cb3d7200e05ba920204304a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com>
Date: Mon, 31 Mar 2025 16:35:11 +0200
Subject: [PATCH 2/4] remove assertGoTo, use goTo

---
 packages/api/src/api/rest/titres.ts           |  11 +-
 .../rules-demarches/axm/oct.machine.test.ts   |  29 +-
 .../rules-demarches/machine-helper.test.ts    | 419 +++++++++---------
 .../rules-demarches/machine-helper.ts         |  53 ++-
 .../procedure-specifique.machine.test.ts      |   4 +-
 .../titre-demarche-etat-validate.ts           |   7 +-
 6 files changed, 284 insertions(+), 239 deletions(-)

diff --git a/packages/api/src/api/rest/titres.ts b/packages/api/src/api/rest/titres.ts
index 709ef81db..3f31cf708 100644
--- a/packages/api/src/api/rest/titres.ts
+++ b/packages/api/src/api/rest/titres.ts
@@ -155,10 +155,17 @@ export const titresAdministrations =
                 derniereEtape = etapesDerniereDemarche[etapesDerniereDemarche.length - 1]
                 if (isNotNullNorUndefined(machine)) {
                   try {
-                    enAttenteDeAdministration = machine.whoIsBlocking(etapesDerniereDemarche).includes(user.administrationId)
+                    const whoIsBlocking = machine.whoIsBlocking(etapesDerniereDemarche)
+                    if (!whoIsBlocking.valid) {
+                      throw new Error(whoIsBlocking.error)
+                    }
+                    enAttenteDeAdministration = whoIsBlocking.value.includes(user.administrationId)
                     const nextEtapes = machine.possibleNextEtapes(etapesDerniereDemarche, getCurrent())
+                    if (!nextEtapes.valid) {
+                      throw new Error(nextEtapes.error)
+                    }
                     prochainesEtapes.push(
-                      ...nextEtapes
+                      ...nextEtapes.value
                         .map(etape => etape.etapeTypeId)
                         .filter(onlyUnique)
                         .filter(etape => !etapesAMasquer.includes(etape))
diff --git a/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts b/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts
index 616a7a027..03fe94628 100644
--- a/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts
+++ b/packages/api/src/business/rules-demarches/axm/oct.machine.test.ts
@@ -19,7 +19,7 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "ENREGISTRER_DEMANDE (confidentielle, déposé                 ) -> [DEMANDER_COMPLEMENTS_POUR_RECEVABILITE,FAIRE_CLASSEMENT_SANS_SUITE,FAIRE_DESISTEMENT_DEMANDEUR,FAIRE_RECEVABILITE_DEMANDE_DEFAVORABLE,FAIRE_RECEVABILITE_DEMANDE_FAVORABLE,RENDRE_DECISION_IMPLICITE_REJET]",
       ]
     `)
-    expect(axmOctMachine.whoIsBlocking(etapes)).toStrictEqual([ADMINISTRATION_IDS['DGTM - GUYANE']])
+    expect(axmOctMachine.whoIsBlocking(etapes)).toStrictEqual({ valid: true, value: [ADMINISTRATION_IDS['DGTM - GUYANE']] })
   })
 
   test('peut faire l’avis du DREAL sans aucun autre avis 30 jours après la saisine des services', () => {
@@ -42,9 +42,13 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "RENDRE_AVIS_DREAL                                     (publique      , en instruction         ) -> [FAIRE_CLASSEMENT_SANS_SUITE,FAIRE_DESISTEMENT_DEMANDEUR,FAIRE_SAISINE_COMMISSION_DEPARTEMENTALE_DES_MINES,RENDRE_AVIS_COMMISSION_DEPARTEMENTALE_DES_MINES,RENDRE_AVIS_COMMISSION_DEPARTEMENTALE_DES_MINES_AJOURNE]",
       ]
     `)
+    const possibleNextEtapes = machine.possibleNextEtapes(etapes, toCaminoDate('2022-06-15'))
+    expect(possibleNextEtapes.valid).toBe(true)
+    if (!possibleNextEtapes.valid) {
+      throw new Error(possibleNextEtapes.error)
+    }
     expect(
-      machine
-        .possibleNextEtapes(etapes, toCaminoDate('2022-06-15'))
+      possibleNextEtapes.value
         .map(({ type }) => type)
         .filter(onlyUnique)
         .toSorted()
@@ -134,7 +138,7 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "FAIRE_CLASSEMENT_SANS_SUITE (confidentielle, classé sans suite      ) -> []",
       ]
     `)
-    expect(machine.whoIsBlocking(etapes)).toStrictEqual([])
+    expect(machine.whoIsBlocking(etapes)).toStrictEqual({ valid: true, value: [] })
   })
 
   test('ne peut pas faire deux fois la même étape à la même date', () => {
@@ -204,9 +208,13 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
       ETES.avisDesCollectivites.FAIT,
       ETES.avisDesServicesEtCommissionsConsultatives.FAIT,
     ])
+    let possibleNextEtapes = machine.possibleNextEtapes(etapes, toCaminoDate('2022-05-06'))
+    expect(possibleNextEtapes.valid).toBe(true)
+    if (!possibleNextEtapes.valid) {
+      throw new Error(possibleNextEtapes.error)
+    }
     expect(
-      machine
-        .possibleNextEtapes(etapes, toCaminoDate('2022-05-06'))
+      possibleNextEtapes.value
         .map(({ type }) => type)
         .filter(onlyUnique)
         .toSorted()
@@ -218,9 +226,14 @@ describe('vérifie l’arbre d’octroi d’AXM', () => {
         "RENDRE_AVIS_DREAL",
       ]
     `)
+
+    possibleNextEtapes = machine.possibleNextEtapes(etapes, toCaminoDate('2022-05-05'))
+    expect(possibleNextEtapes.valid).toBe(true)
+    if (!possibleNextEtapes.valid) {
+      throw new Error(possibleNextEtapes.error)
+    }
     expect(
-      machine
-        .possibleNextEtapes(etapes, toCaminoDate('2022-05-05'))
+      possibleNextEtapes.value
         .map(({ type }) => type)
         .filter(onlyUnique)
         .toSorted()
diff --git a/packages/api/src/business/rules-demarches/machine-helper.test.ts b/packages/api/src/business/rules-demarches/machine-helper.test.ts
index 671000eda..da8b1a570 100644
--- a/packages/api/src/business/rules-demarches/machine-helper.test.ts
+++ b/packages/api/src/business/rules-demarches/machine-helper.test.ts
@@ -364,7 +364,7 @@ describe('whoIsBlocking', () => {
           date: toCaminoDate('2021-02-03'),
         },
       ])
-    ).toStrictEqual([ADMINISTRATION_IDS['DGTM - GUYANE']])
+    ).toStrictEqual({ valid: true, value: [ADMINISTRATION_IDS['DGTM - GUYANE']] })
   })
 
   test('on attend la DGTM pour la validation du paiement des frais de dossier', () => {
@@ -391,7 +391,7 @@ describe('whoIsBlocking', () => {
           date: toCaminoDate('2021-02-04'),
         },
       ])
-    ).toStrictEqual([ADMINISTRATION_IDS['DGTM - GUYANE']])
+    ).toStrictEqual({ valid: true, value: [ADMINISTRATION_IDS['DGTM - GUYANE']] })
   })
 
   test('on attend personne', () => {
@@ -428,7 +428,7 @@ describe('whoIsBlocking', () => {
           date: toCaminoDate('2021-02-06'),
         },
       ])
-    ).toStrictEqual([])
+    ).toStrictEqual({ valid: true, value: [] })
   })
 })
 
@@ -451,36 +451,39 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "pfd",
-          "mainStep": true,
-          "type": "PAYER_FRAIS_DE_DOSSIER",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "pfd",
+            "mainStep": true,
+            "type": "PAYER_FRAIS_DE_DOSSIER",
+          },
+        ],
+      }
     `)
   })
   test('possibleNextEtapes après une recevabilité favorable on rendre un avis des services et commissions consultatives', () => {
@@ -497,50 +500,53 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "asc",
-          "mainStep": true,
-          "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fav",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "def",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "asc",
+            "mainStep": true,
+            "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fav",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "def",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+        ],
+      }
     `)
   })
   test('après un avis des services et commissions consultatives on doit avoir la saisine de la commission des autorisations de recherches minières', () => {
@@ -558,50 +564,53 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fav",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "def",
-          "etapeTypeId": "exp",
-          "mainStep": false,
-          "type": "RECEVOIR_EXPERTISE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "sca",
-          "mainStep": true,
-          "type": "FAIRE_SAISINE_CARM",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fav",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "def",
+            "etapeTypeId": "exp",
+            "mainStep": false,
+            "type": "RECEVOIR_EXPERTISE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "sca",
+            "mainStep": true,
+            "type": "FAIRE_SAISINE_CARM",
+          },
+        ],
+      }
     `)
   })
   test('après la validation de frais de paiement on doit faire une recevabilité', () => {
@@ -617,57 +626,60 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mca",
-          "mainStep": false,
-          "type": "DEMANDER_COMPLEMENTS_MCR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "def",
-          "etapeTypeId": "mcr",
-          "mainStep": false,
-          "type": "DECLARER_DEMANDE_DEFAVORABLE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fav",
-          "etapeTypeId": "mcr",
-          "mainStep": true,
-          "type": "DECLARER_DEMANDE_FAVORABLE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mim",
-          "mainStep": false,
-          "type": "DEMANDER_INFORMATION_MCR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mca",
+            "mainStep": false,
+            "type": "DEMANDER_COMPLEMENTS_MCR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "def",
+            "etapeTypeId": "mcr",
+            "mainStep": false,
+            "type": "DECLARER_DEMANDE_DEFAVORABLE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fav",
+            "etapeTypeId": "mcr",
+            "mainStep": true,
+            "type": "DECLARER_DEMANDE_FAVORABLE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mim",
+            "mainStep": false,
+            "type": "DEMANDER_INFORMATION_MCR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+        ],
+      }
     `)
   })
   test('après une recevabilité défavorable on doit avoir un avis des services et commissions consultatives', () => {
@@ -684,36 +696,39 @@ describe('mainStep', () => {
         toCaminoDate('2021-02-03')
       )
     ).toMatchInlineSnapshot(`
-      [
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "asc",
-          "mainStep": true,
-          "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "css",
-          "mainStep": false,
-          "type": "CLASSER_SANS_SUITE",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "des",
-          "mainStep": false,
-          "type": "DESISTER_PAR_LE_DEMANDEUR",
-        },
-        {
-          "contenu": undefined,
-          "etapeStatutId": "fai",
-          "etapeTypeId": "mod",
-          "mainStep": false,
-          "type": "MODIFIER_DEMANDE",
-        },
-      ]
+      {
+        "valid": true,
+        "value": [
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "asc",
+            "mainStep": true,
+            "type": "RENDRE_AVIS_DES_SERVICES_ET_COMMISSIONS_CONSULTATIVES",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "css",
+            "mainStep": false,
+            "type": "CLASSER_SANS_SUITE",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "des",
+            "mainStep": false,
+            "type": "DESISTER_PAR_LE_DEMANDEUR",
+          },
+          {
+            "contenu": undefined,
+            "etapeStatutId": "fai",
+            "etapeTypeId": "mod",
+            "mainStep": false,
+            "type": "MODIFIER_DEMANDE",
+          },
+        ],
+      }
     `)
   })
 })
diff --git a/packages/api/src/business/rules-demarches/machine-helper.ts b/packages/api/src/business/rules-demarches/machine-helper.ts
index 9142bd5c2..c4c604acb 100644
--- a/packages/api/src/business/rules-demarches/machine-helper.ts
+++ b/packages/api/src/business/rules-demarches/machine-helper.ts
@@ -186,7 +186,7 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
   }
 
   private goTo(etapes: readonly Etape[]):
-    | { valid: false; etapeIndex: number }
+    | { valid: false; etapeIndex: number; error: string }
     | {
         valid: true
         state: CaminoState<CaminoContext, CaminoEvent>
@@ -208,7 +208,7 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
         if (!service.getSnapshot().can(event) || service.getSnapshot().status === 'done') {
           service.stop()
 
-          return { valid: false, etapeIndex: i }
+          return { valid: false, etapeIndex: i, error: `Les étapes '${JSON.stringify(etapes)}' sont invalides à partir de l’étape ${i}` }
         }
         service.send(event)
       }
@@ -286,21 +286,20 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
     }
   }
 
-  private assertGoTo(etapes: readonly Etape[]): CaminoState<CaminoContext, CaminoEvent> {
-    const value = this.goTo(etapes)
-    if (!value.valid) {
-      throw new Error(`Les étapes '${JSON.stringify(etapes)}' sont invalides à partir de l’étape ${value.etapeIndex}`)
-    } else {
-      return value.state
+  public whoIsBlocking(etapes: readonly Etape[]):
+    | { valid: false; etapeIndex: number; error: string }
+    | {
+        valid: true
+        value: Intervenant[]
+      } {
+    const state = this.goTo(etapes)
+    if (!state.valid) {
+      return state
     }
-  }
-
-  public whoIsBlocking(etapes: readonly Etape[]): Intervenant[] {
-    const state = this.assertGoTo(etapes)
 
-    const responsables: string[] = [...state.tags]
+    const responsables: string[] = [...state.state.tags]
 
-    return intervenants.filter(r => responsables.includes(tags.responsable[r]))
+    return { valid: true, value: intervenants.filter(r => responsables.includes(tags.responsable[r])) }
   }
 
   // visibleForTesting
@@ -318,17 +317,29 @@ export abstract class CaminoMachine<CaminoContext extends CaminoCommonContext, C
       .toSorted((a, b) => a.type.localeCompare(b.type))
   }
 
-  public possibleNextEtapes(etapes: readonly Etape[], date: CaminoDate): (OmitDistributive<Etape, 'date' | 'titreTypeId' | 'demarcheTypeId'> & { mainStep: boolean; type: CaminoEvent['type'] })[] {
-    const state = this.assertGoTo(etapes)
+  public possibleNextEtapes(
+    etapes: readonly Etape[],
+    date: CaminoDate
+  ):
+    | { valid: false; etapeIndex: number; error: string }
+    | { valid: true; value: (OmitDistributive<Etape, 'date' | 'titreTypeId' | 'demarcheTypeId'> & { mainStep: boolean; type: CaminoEvent['type'] })[] } {
+    const state = this.goTo(etapes)
+
+    if (!state.valid) {
+      return state
+    }
 
     if (isNotNullNorUndefined(state)) {
-      return this.possibleNextEvents(state, date)
-        .flatMap(this.caminoXStateEventToEtapes.bind(this))
-        .filter(isNotNullNorUndefined)
-        .toSorted((a, b) => a.etapeTypeId.localeCompare(b.etapeTypeId))
+      return {
+        valid: true,
+        value: this.possibleNextEvents(state.state, date)
+          .flatMap(this.caminoXStateEventToEtapes.bind(this))
+          .filter(isNotNullNorUndefined)
+          .toSorted((a, b) => a.etapeTypeId.localeCompare(b.etapeTypeId)),
+      }
     }
 
-    return []
+    return { valid: true, value: [] }
   }
 }
 
diff --git a/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts b/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts
index cfa13d081..9ef758337 100644
--- a/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts
+++ b/packages/api/src/business/rules-demarches/procedure-specifique/procedure-specifique.machine.test.ts
@@ -676,7 +676,7 @@ describe('vérifie l’arbre des procédures spécifique', () => {
       ETES.decisionDeLAutoriteAdministrative.REJETE_DECISION_IMPLICITE,
     ])
 
-    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual([])
+    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual({ valid: true, value: [] })
     expect(machine.demarcheStatut(etapes)).toMatchInlineSnapshot(`
        {
          "demarcheDateDebut": {
@@ -710,7 +710,7 @@ describe('vérifie l’arbre des procédures spécifique', () => {
       ETES.decisionDeLAutoriteAdministrative.REJETE_DECISION_IMPLICITE,
     ])
 
-    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual([])
+    expect(machine.possibleNextEtapes(etapes, dateFin)).toStrictEqual({ valid: true, value: [] })
     expect(machine.demarcheStatut(etapes)).toMatchInlineSnapshot(`
       {
         "demarcheDateDebut": {
diff --git a/packages/api/src/business/validations/titre-demarche-etat-validate.ts b/packages/api/src/business/validations/titre-demarche-etat-validate.ts
index d3d4c7fad..611b6d24b 100644
--- a/packages/api/src/business/validations/titre-demarche-etat-validate.ts
+++ b/packages/api/src/business/validations/titre-demarche-etat-validate.ts
@@ -207,10 +207,9 @@ const etapesTypesPossibleACetteDateOuALaPlaceDeLEtape = (machine: CaminoMachines
 
   for (let i = 0; i <= etapesPendant.length; i++) {
     const etapeEnCours = [...etapesAvant, ...etapesPendant.slice(0, i)]
-    if (machine.isEtapesOk(etapeEnCours)) {
-      const etapesPossiblesRaw = machine.possibleNextEtapes(etapeEnCours, date)
-
-      for (const et of etapesPossiblesRaw) {
+    const etapesPossiblesRaw = machine.possibleNextEtapes(etapeEnCours, date)
+    if (etapesPossiblesRaw.valid) {
+      for (const et of etapesPossiblesRaw.value) {
         const newEtapes = [...etapeEnCours]
 
         const items = { ...et, date }
-- 
GitLab


From 2db4673a842e7643b4a62151b5d0b862918d9dc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com>
Date: Mon, 31 Mar 2025 17:13:27 +0200
Subject: [PATCH 3/4] remove deprecated

---
 .../api/src/api/rest/perimetre.queries.ts     | 30 +++++--------------
 packages/api/src/api/rest/perimetre.ts        |  1 +
 packages/api/src/tools/fp-tools.ts            |  5 ++--
 3 files changed, 12 insertions(+), 24 deletions(-)

diff --git a/packages/api/src/api/rest/perimetre.queries.ts b/packages/api/src/api/rest/perimetre.queries.ts
index a76f15078..34577fee7 100644
--- a/packages/api/src/api/rest/perimetre.queries.ts
+++ b/packages/api/src/api/rest/perimetre.queries.ts
@@ -14,7 +14,7 @@ import { foretIdValidator } from 'camino-common/src/static/forets'
 import { sdomZoneIdValidator } from 'camino-common/src/static/sdom'
 import { KM2, M2, km2Validator, m2Validator } from 'camino-common/src/number'
 import { isNullOrUndefined, onlyUnique } from 'camino-common/src/typescript-tools'
-import { ZodUnparseable, zodParseEffect, zodParseEffectCallback } from '../../tools/fp-tools'
+import { zodParseEffect, zodParseEffectTyped, ZodUnparseable } from '../../tools/fp-tools'
 import { CaminoError } from 'camino-common/src/zod-tools'
 import { Effect, pipe } from 'effect'
 import { departementIdValidator, toDepartementId } from 'camino-common/src/static/departement'
@@ -58,14 +58,7 @@ export const convertPoints = <T extends z.ZodTypeAny>(
       coordinates => coordinates.length === geojsonPoints.features.length,
       () => ({ message: convertPointsInvalidNumberOfFeaturesError })
     ),
-    Effect.flatMap((result: [number, number][]) => {
-      const check = zodParseEffect(arrayTuple4326CoordinateValidator, result)
-
-      return Effect.matchEffect(check, {
-        onSuccess: () => Effect.succeed(result),
-        onFailure: error => Effect.fail({ ...error, message: invalidSridError, detail: 'Vérifiez que le géosystème correspond bien à celui du fichier' }),
-      })
-    }),
+    Effect.tap((result: [number, number][]) => zodParseEffectTyped(arrayTuple4326CoordinateValidator, result, invalidSridError, 'Vérifiez que le géosystème correspond bien à celui du fichier')),
     Effect.map(coordinates => {
       return {
         type: 'FeatureCollection',
@@ -87,14 +80,14 @@ const perimetreInvalideError = "Le périmètre n'est pas valide dans le référe
 const conversionGeometrieError = 'Impossible de convertir la géométrie en JSON' as const
 const getGeojsonByGeoSystemeIdValidator = z.object({ geojson: multiPolygonValidator })
 const polygon4326CoordinatesValidator = z.array(z.array(arrayTuple4326CoordinateValidator.min(3)).min(1)).min(1)
-
+const transformationImpossible = "Impossible de transformer le geojson dans le référentiel donné" as const
 export type GetGeojsonByGeoSystemeIdErrorMessages =
   | EffectDbQueryAndValidateErrors
-  | ZodUnparseable
   | typeof conversionSystemeError
   | typeof perimetreInvalideError
   | typeof conversionGeometrieError
   | typeof invalidSridError
+  | typeof transformationImpossible
 export const getGeojsonByGeoSystemeId = (
   pool: Pool,
   fromGeoSystemeId: GeoSystemeId,
@@ -117,16 +110,9 @@ export const getGeojsonByGeoSystemeId = (
       result => result.length === 1,
       () => ({ message: conversionSystemeError, extra: to4326GeoSystemeId })
     ),
-    Effect.flatMap(result => {
-      const coordinates: [number, number][][][] = result[0].geojson.coordinates
-
-      const check = zodParseEffect(polygon4326CoordinatesValidator, coordinates)
-
-      return Effect.matchEffect(check, {
-        onSuccess: () => Effect.succeed(result),
-        onFailure: error => Effect.fail({ ...error, message: invalidSridError, detail: 'Vérifiez que le géosystème correspond bien à celui du fichier' }),
-      })
-    }),
+    Effect.tap(result =>
+      zodParseEffectTyped(polygon4326CoordinatesValidator, result[0].geojson.coordinates, invalidSridError, 'Vérifiez que le géosystème correspond bien à celui du fichier')
+    ),
     Effect.map(result => {
       if (fromGeoSystemeId === to4326GeoSystemeId) {
         return geojson
@@ -139,7 +125,7 @@ export const getGeojsonByGeoSystemeId = (
 
       return feature
     }),
-    Effect.flatMap(zodParseEffectCallback(featureMultiPolygonValidator))
+    Effect.flatMap((result =>  zodParseEffectTyped(featureMultiPolygonValidator, result, transformationImpossible)))
   )
 }
 
diff --git a/packages/api/src/api/rest/perimetre.ts b/packages/api/src/api/rest/perimetre.ts
index 8015b9c44..f51a56eda 100644
--- a/packages/api/src/api/rest/perimetre.ts
+++ b/packages/api/src/api/rest/perimetre.ts
@@ -415,6 +415,7 @@ export const geojsonImport: RestNewPostCall<'/rest/geojson/import/:geoSystemeId'
           'Une erreur inattendue est survenue lors de la récupération des informations geojson en base',
           "Impossible d'exécuter la requête dans la base de données",
           'Les données en base ne correspondent pas à ce qui est attendu',
+          'Impossible de transformer le geojson dans le référentiel donné',
           () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR })
         ),
         Match.whenOr(
diff --git a/packages/api/src/tools/fp-tools.ts b/packages/api/src/tools/fp-tools.ts
index b670998c5..e316e0467 100644
--- a/packages/api/src/tools/fp-tools.ts
+++ b/packages/api/src/tools/fp-tools.ts
@@ -1,3 +1,4 @@
+import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools'
 import { CaminoError, CaminoZodErrorReadableMessage, translateIssue } from 'camino-common/src/zod-tools'
 import { Cause, Effect, Exit, pipe } from 'effect'
 import { ZodTypeAny } from 'zod'
@@ -36,10 +37,10 @@ export const zodParseEffect = <T extends ZodTypeAny>(validator: T, item: unknown
   })
 }
 
-export const zodParseEffectTyped = <T extends ZodTypeAny, U extends string>(validator: T, item: T['_output'], errorMessage: U): Effect.Effect<T['_output'], CaminoError<U>> => {
+export const zodParseEffectTyped = <T extends ZodTypeAny, U extends string>(validator: T, item: T['_output'], errorMessage: U, detail?: string): Effect.Effect<T['_output'], CaminoError<U>> => {
   return Effect.try({
     try: () => validator.parse(item),
-    catch: myError => ({ message: errorMessage, detail: zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
+    catch: myError => ({ message: errorMessage, detail: isNotNullNorUndefined(detail) ?  detail  : zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
   })
 }
 
-- 
GitLab


From c30e5ca1af083975ba16c14022a55dc5dc1a03d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bitard=20Micha=C3=ABl?= <bitard.michael@gmail.com>
Date: Mon, 31 Mar 2025 17:20:20 +0200
Subject: [PATCH 4/4] lint

---
 packages/api/src/api/rest/perimetre.queries.ts | 8 +++-----
 packages/api/src/tools/fp-tools.ts             | 2 +-
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/packages/api/src/api/rest/perimetre.queries.ts b/packages/api/src/api/rest/perimetre.queries.ts
index 34577fee7..5e0b3ebeb 100644
--- a/packages/api/src/api/rest/perimetre.queries.ts
+++ b/packages/api/src/api/rest/perimetre.queries.ts
@@ -80,7 +80,7 @@ const perimetreInvalideError = "Le périmètre n'est pas valide dans le référe
 const conversionGeometrieError = 'Impossible de convertir la géométrie en JSON' as const
 const getGeojsonByGeoSystemeIdValidator = z.object({ geojson: multiPolygonValidator })
 const polygon4326CoordinatesValidator = z.array(z.array(arrayTuple4326CoordinateValidator.min(3)).min(1)).min(1)
-const transformationImpossible = "Impossible de transformer le geojson dans le référentiel donné" as const
+const transformationImpossible = 'Impossible de transformer le geojson dans le référentiel donné' as const
 export type GetGeojsonByGeoSystemeIdErrorMessages =
   | EffectDbQueryAndValidateErrors
   | typeof conversionSystemeError
@@ -110,9 +110,7 @@ export const getGeojsonByGeoSystemeId = (
       result => result.length === 1,
       () => ({ message: conversionSystemeError, extra: to4326GeoSystemeId })
     ),
-    Effect.tap(result =>
-      zodParseEffectTyped(polygon4326CoordinatesValidator, result[0].geojson.coordinates, invalidSridError, 'Vérifiez que le géosystème correspond bien à celui du fichier')
-    ),
+    Effect.tap(result => zodParseEffectTyped(polygon4326CoordinatesValidator, result[0].geojson.coordinates, invalidSridError, 'Vérifiez que le géosystème correspond bien à celui du fichier')),
     Effect.map(result => {
       if (fromGeoSystemeId === to4326GeoSystemeId) {
         return geojson
@@ -125,7 +123,7 @@ export const getGeojsonByGeoSystemeId = (
 
       return feature
     }),
-    Effect.flatMap((result =>  zodParseEffectTyped(featureMultiPolygonValidator, result, transformationImpossible)))
+    Effect.flatMap(result => zodParseEffectTyped(featureMultiPolygonValidator, result, transformationImpossible))
   )
 }
 
diff --git a/packages/api/src/tools/fp-tools.ts b/packages/api/src/tools/fp-tools.ts
index e316e0467..73202de02 100644
--- a/packages/api/src/tools/fp-tools.ts
+++ b/packages/api/src/tools/fp-tools.ts
@@ -40,7 +40,7 @@ export const zodParseEffect = <T extends ZodTypeAny>(validator: T, item: unknown
 export const zodParseEffectTyped = <T extends ZodTypeAny, U extends string>(validator: T, item: T['_output'], errorMessage: U, detail?: string): Effect.Effect<T['_output'], CaminoError<U>> => {
   return Effect.try({
     try: () => validator.parse(item),
-    catch: myError => ({ message: errorMessage, detail: isNotNullNorUndefined(detail) ?  detail  : zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
+    catch: myError => ({ message: errorMessage, detail: isNotNullNorUndefined(detail) ? detail : zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }),
   })
 }
 
-- 
GitLab