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