From 6b2df82acf27c20519f0bcd9a56c80008c6ceccc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?BITARD=20Micha=C3=ABl?= <michael.bitard@beta.gouv.fr>
Date: Tue, 3 Dec 2024 09:06:46 +0000
Subject: [PATCH] =?UTF-8?q?fix(proc=C3=A9dure=20sp=C3=A9cifique):=20on=20p?=
 =?UTF-8?q?eut=20modifier=20les=20premi=C3=A8res=20=C3=A9tapes=20(pub/pnm-?=
 =?UTF-8?q?public/camino!1580)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../api/rest/etape-creer.test.integration.ts  | 67 ++++++++++++++++++-
 packages/api/src/api/rest/etapes.queries.ts   | 20 +++++-
 .../api/src/api/rest/etapes.queries.types.ts  | 16 +++++
 packages/api/src/api/rest/etapes.ts           |  7 +-
 4 files changed, 104 insertions(+), 6 deletions(-)

diff --git a/packages/api/src/api/rest/etape-creer.test.integration.ts b/packages/api/src/api/rest/etape-creer.test.integration.ts
index e3ffaa7e0..2f846ac13 100644
--- a/packages/api/src/api/rest/etape-creer.test.integration.ts
+++ b/packages/api/src/api/rest/etape-creer.test.integration.ts
@@ -11,12 +11,22 @@ import { RestEtapeCreation, defaultHeritageProps } from 'camino-common/src/etape
 import { HTTP_STATUS } from 'camino-common/src/http'
 import { toCaminoDate } from 'camino-common/src/date'
 import { entrepriseIdValidator } from 'camino-common/src/entreprise'
-import { TitreTypeId } from 'camino-common/src/static/titresTypes'
-import { newDemarcheId, newTitreId } from '../../database/models/_format/id-create'
+import { TITRES_TYPES_IDS, TitreTypeId } from 'camino-common/src/static/titresTypes'
+import { newDemarcheId, newEtapeId, newTitreId } from '../../database/models/_format/id-create'
 import { insertTitreGraph } from '../../../tests/integration-test-helper'
+import { TitresStatutIds } from 'camino-common/src/static/titresStatuts'
+import { DEMARCHES_TYPES_IDS } from 'camino-common/src/static/demarchesTypes'
+import { DemarchesStatutsIds } from 'camino-common/src/static/demarchesStatuts'
+import { ETAPE_IS_NOT_BROUILLON } from 'camino-common/src/etape'
+import { ETAPES_TYPES } from 'camino-common/src/static/etapesTypes'
+import { ETAPES_STATUTS } from 'camino-common/src/static/etapesStatuts'
+import { communeIdValidator } from 'camino-common/src/static/communes'
+import { km2Validator } from 'camino-common/src/number'
 
 console.info = vi.fn()
 console.error = vi.fn()
+console.debug = vi.fn()
+console.warn = vi.fn()
 let dbPool: Pool
 
 beforeAll(async () => {
@@ -101,6 +111,59 @@ describe('etapeCreer', () => {
 
     expect(result.statusCode).toBe(HTTP_STATUS.FORBIDDEN)
   })
+
+  test('peut créer une recevabilité favorable sur la procédure spécifique', async () => {
+    const titreId = newTitreId('titreId')
+    const demarcheId = newDemarcheId('demarcheId')
+    const demandeId = newEtapeId('etapeIdDemande')
+    const enregistrementId = newEtapeId('etapeIdEnregistrement')
+    await insertTitreGraph({
+      id: titreId,
+      nom: 'test',
+      typeId: TITRES_TYPES_IDS.AUTORISATION_D_EXPLOITATION_METAUX,
+      titreStatutId: TitresStatutIds.DemandeInitiale,
+      propsTitreEtapesIds: {},
+      demarches: [
+        {
+          id: demarcheId,
+          titreId: titreId,
+          typeId: DEMARCHES_TYPES_IDS.Octroi,
+          statutId: DemarchesStatutsIds.EnInstruction,
+          etapes: [
+            {
+              id: demandeId,
+              date: toCaminoDate('2024-11-01'),
+              isBrouillon: ETAPE_IS_NOT_BROUILLON,
+              titreDemarcheId: demarcheId,
+              typeId: ETAPES_TYPES.demande,
+              statutId: ETAPES_STATUTS.FAIT,
+              communes: [{ id: communeIdValidator.parse('31200'), surface: 12 }],
+              surface: km2Validator.parse(12),
+            },
+            {
+              id: enregistrementId,
+              date: toCaminoDate('2024-11-02'),
+              isBrouillon: ETAPE_IS_NOT_BROUILLON,
+              titreDemarcheId: demarcheId,
+              typeId: ETAPES_TYPES.enregistrementDeLaDemande,
+              statutId: ETAPES_STATUTS.FAIT,
+            },
+          ],
+        },
+      ],
+    })
+
+    const res = await restNewPostCall(dbPool, '/rest/etapes', {}, userSuper, {
+      typeId: ETAPES_TYPES.recevabiliteDeLaDemande,
+      statutId: ETAPES_STATUTS.FAVORABLE,
+      titreDemarcheId: demarcheId,
+      date: toCaminoDate('2024-11-03'),
+      ...blankEtapeProps,
+    })
+
+    expect(res.statusCode, JSON.stringify(res.body)).toBe(HTTP_STATUS.OK)
+  })
+
   test('ne peut pas créer une étape (utilisateur administration)', async () => {
     const result = await restNewPostCall(dbPool, '/rest/etapes', {}, { role: 'editeur', administrationId: 'ope-onf-973-01' }, {
       typeId: 'mfr',
diff --git a/packages/api/src/api/rest/etapes.queries.ts b/packages/api/src/api/rest/etapes.queries.ts
index 0f981d387..cad42c662 100644
--- a/packages/api/src/api/rest/etapes.queries.ts
+++ b/packages/api/src/api/rest/etapes.queries.ts
@@ -2,7 +2,7 @@ import { EtapeDocumentId, EtapeId, EtapeIdOrSlug, etapeBrouillonValidator, etape
 import { ETAPE_TYPE_FOR_CONCURRENCY_DATA, etapeTypeIdValidator } from 'camino-common/src/static/etapesTypes'
 import { Pool } from 'pg'
 import { z } from 'zod'
-import { DBNotFound, DbQueryAccessError, Redefine, dbNotFoundError, dbQueryAndValidate, effectDbQueryAndValidate } from '../../pg-database'
+import { DBNotFound, DbQueryAccessError, EffectDbQueryAndValidateErrors, Redefine, dbNotFoundError, dbQueryAndValidate, effectDbQueryAndValidate } from '../../pg-database'
 import { sql } from '@pgtyped/runtime'
 import {
   IGetAdministrationsLocalesByEtapeIdQuery,
@@ -12,6 +12,7 @@ import {
   IGetEtapeDataForEditionDbQuery,
   IGetLargeobjectIdByEtapeDocumentIdInternalQuery,
   IGetTitulairesAmodiatairesTitreEtapeQuery,
+  IHasTitreFromDbQuery,
 } from './etapes.queries.types'
 import { demarcheIdValidator } from 'camino-common/src/demarche'
 import { sdomZoneIdValidator } from 'camino-common/src/static/sdom'
@@ -29,6 +30,7 @@ import { CaminoError } from 'camino-common/src/zod-tools'
 import { ZodUnparseable } from '../../tools/fp-tools'
 import { Effect, pipe } from 'effect'
 import { DATE_DEBUT_PROCEDURE_SPECIFIQUE } from 'camino-common/src/machines'
+import { TitreId } from 'camino-common/src/validators/titres'
 
 const getEtapeByIdValidator = z.object({
   etape_id: etapeIdValidator,
@@ -209,6 +211,22 @@ where
     and (etape_titulaires.titulaire_ids ? e.id
         or etape_amodiataires.amodiataire_ids ? e.id)
 `
+const titreInexistant = "le titre n'existe pas" as const
+const hasTitreFromValidator = z.object({ has_titre_from: z.boolean() })
+type HasTitreFromErrors = EffectDbQueryAndValidateErrors | typeof titreInexistant
+export const hasTitreFrom = (pool: Pool, titreId: TitreId): Effect.Effect<boolean, CaminoError<HasTitreFromErrors>> =>
+  pipe(
+    effectDbQueryAndValidate(hasTitreFromDb, { titreId }, pool, hasTitreFromValidator),
+    Effect.filterOrFail(
+      values => values.length === 1,
+      () => ({ message: titreInexistant })
+    ),
+    Effect.map(values => values[0].has_titre_from)
+  )
+
+const hasTitreFromDb = sql<Redefine<IHasTitreFromDbQuery, { titreId: TitreId }, z.infer<typeof hasTitreFromValidator>>>`
+  SELECT EXISTS(select 1 from titres__titres tt where tt.titre_to_id=$titreId!) as has_titre_from
+  `
 
 export const getDemandesPotentialConcurrence = (pool: Pool): Effect.Effect<EtapeId[], CaminoError<DbQueryAccessError | ZodUnparseable>> => {
   return pipe(
diff --git a/packages/api/src/api/rest/etapes.queries.types.ts b/packages/api/src/api/rest/etapes.queries.types.ts
index be3ea8ac5..f3dd34786 100644
--- a/packages/api/src/api/rest/etapes.queries.types.ts
+++ b/packages/api/src/api/rest/etapes.queries.types.ts
@@ -98,6 +98,22 @@ export interface IGetTitulairesAmodiatairesTitreEtapeQuery {
   result: IGetTitulairesAmodiatairesTitreEtapeResult;
 }
 
+/** 'HasTitreFromDb' parameters type */
+export interface IHasTitreFromDbParams {
+  titreId: string;
+}
+
+/** 'HasTitreFromDb' return type */
+export interface IHasTitreFromDbResult {
+  has_titre_from: boolean | null;
+}
+
+/** 'HasTitreFromDb' query type */
+export interface IHasTitreFromDbQuery {
+  params: IHasTitreFromDbParams;
+  result: IHasTitreFromDbResult;
+}
+
 /** 'GetDemandesPotentialConcurrenceDb' parameters type */
 export interface IGetDemandesPotentialConcurrenceDbParams {
   dateDebutProcedureSpecifique: string;
diff --git a/packages/api/src/api/rest/etapes.ts b/packages/api/src/api/rest/etapes.ts
index ab02e7618..e8d107e75 100644
--- a/packages/api/src/api/rest/etapes.ts
+++ b/packages/api/src/api/rest/etapes.ts
@@ -52,7 +52,7 @@ import {
   updateEtapeDocuments,
   UpdateEtapeDocumentsErrors,
 } from '../../database/queries/titres-etapes.queries'
-import { getEtapeDataForEdition } from './etapes.queries'
+import { getEtapeDataForEdition, hasTitreFrom } from './etapes.queries'
 import { SDOMZoneId } from 'camino-common/src/static/sdom'
 import { objectClone } from '../../tools/index'
 import { titreEtapeAdministrationsEmailsSend, titreEtapeUtilisateursEmailsSend } from '../graphql/resolvers/_titre-etape-email'
@@ -515,7 +515,8 @@ export const createEtape: RestNewPostCall<'/rest/etapes'> = (rootPipe): Effect.E
         }),
       () => ({ message: droitsInsuffisants })
     ),
-    Effect.bind('flattenEtapeAndPerimetreInfo', ({ titreDemarche, isBrouillon, body: etape, pool }) =>
+    Effect.bind('hasTitreFrom', ({ titreDemarche, pool }) => hasTitreFrom(pool, titreDemarche.titre.id)),
+    Effect.bind('flattenEtapeAndPerimetreInfo', ({ titreDemarche, isBrouillon, body: etape, pool, hasTitreFrom }) =>
       getFlattenEtape(
         etape as RestEtapeCreation, // TODO 2024-11-14 comment on fait là, si on met le deepReadonly ça transpire partout et c'est violent :(
         titreDemarche,
@@ -523,7 +524,7 @@ export const createEtape: RestNewPostCall<'/rest/etapes'> = (rootPipe): Effect.E
         isBrouillon,
         etapeSlugValidator.parse('unknown'),
         etape.typeId === ETAPES_TYPES.demande ? { amIFirst: true } : 'non-applicable',
-        etape.typeId === ETAPES_TYPES.demande ? true : 'non-applicable',
+        hasTitreFrom,
         [],
         pool
       )
-- 
GitLab