From 893848e5c65ceaa0194ecfbc71df158a1731d7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BITARD=20Micha=C3=ABl?= <michael.bitard@beta.gouv.fr> Date: Tue, 25 Mar 2025 10:23:24 +0000 Subject: [PATCH] refactor(api): utilise effect sur get etapes documents et get etapes avis (pub/pnm-public/camino!1684) --- .../rest/etape-modifier.test.integration.ts | 6 +- packages/api/src/api/rest/etapes.queries.ts | 44 ++++-- .../src/api/rest/etapes.test.integration.ts | 6 +- packages/api/src/api/rest/etapes.ts | 147 ++++++++++------- packages/api/src/api/rest/fichiers.ts | 2 +- packages/api/src/business/daily.ts | 8 +- packages/api/src/database/init.ts | 3 +- .../database/queries/titres-etapes.queries.ts | 11 +- packages/api/src/scripts/daily.ts | 2 +- packages/api/src/server/rest.ts | 4 +- packages/api/src/tools/fp-tools.ts | 16 ++ packages/common/src/rest.ts | 4 +- packages/ui/src/api/client-rest.ts | 10 ++ .../src/components/etape/etape-api-client.ts | 8 +- .../src/components/etape/etape-avis-edit.tsx | 26 ++- .../etape/etape-documents-edit.stories.tsx | 2 +- ...ents-edit.stories_snapshots_WithError.html | 4 +- .../components/etape/etape-documents-edit.tsx | 26 ++- .../src/components/etape/etape-edit-form.tsx | 148 +++++++++--------- packages/ui/src/components/titre.tsx | 4 +- 20 files changed, 271 insertions(+), 210 deletions(-) diff --git a/packages/api/src/api/rest/etape-modifier.test.integration.ts b/packages/api/src/api/rest/etape-modifier.test.integration.ts index 7e61235b6..61701ed9b 100644 --- a/packages/api/src/api/rest/etape-modifier.test.integration.ts +++ b/packages/api/src/api/rest/etape-modifier.test.integration.ts @@ -1,5 +1,5 @@ import { dbManager } from '../../../tests/db-manager' -import { restNewPutCall, restCall, restNewPostCall } from '../../../tests/_utils/index' +import { restNewPutCall, restNewPostCall, restNewCall } from '../../../tests/_utils/index' import Titres from '../../database/models/titres' import { userSuper } from '../../database/user-super' import { ADMINISTRATION_IDS } from 'camino-common/src/static/administrations' @@ -343,7 +343,7 @@ describe('etapeModifier', () => { expect(res.statusCode, JSON.stringify(res.body)).toBe(HTTP_STATUS.OK) - let documents = await restCall(dbPool, '/rest/etapes/:etapeId/etapeDocuments', { etapeId: titreEtapeId }, userSuper) + let documents = await restNewCall(dbPool, '/rest/etapes/:etapeId/etapeDocuments', { etapeId: titreEtapeId }, userSuper) expect(documents.statusCode).toBe(HTTP_STATUS.OK) expect(documents.body.etapeDocuments).toHaveLength(1) expect(documents.body.etapeDocuments[0]).toMatchInlineSnapshot( @@ -362,7 +362,7 @@ describe('etapeModifier', () => { expect(res.statusCode).toBe(HTTP_STATUS.OK) - documents = await restCall(dbPool, '/rest/etapes/:etapeId/etapeDocuments', { etapeId: titreEtapeId }, userSuper) + documents = await restNewCall(dbPool, '/rest/etapes/:etapeId/etapeDocuments', { etapeId: titreEtapeId }, userSuper) expect(documents.statusCode).toBe(HTTP_STATUS.OK) expect(documents.body.etapeDocuments).toHaveLength(0) }) diff --git a/packages/api/src/api/rest/etapes.queries.ts b/packages/api/src/api/rest/etapes.queries.ts index 4fd77d265..baa9514d4 100644 --- a/packages/api/src/api/rest/etapes.queries.ts +++ b/packages/api/src/api/rest/etapes.queries.ts @@ -24,13 +24,14 @@ import { EntrepriseId, entrepriseIdValidator } from 'camino-common/src/entrepris import { User } from 'camino-common/src/roles' import { LargeObjectId, largeObjectIdValidator } from '../../database/largeobjects' import { canReadDocument } from './permissions/documents' -import { memoize, Memoized } from 'camino-common/src/typescript-tools' +import { isNotNullNorUndefinedNorEmpty, memoize, Memoized } from 'camino-common/src/typescript-tools' import { etapeStatutIdValidator } from 'camino-common/src/static/etapesStatuts' import { CaminoError } from 'camino-common/src/zod-tools' import { Effect, pipe } from 'effect' import { DATE_DEBUT_PROCEDURE_SPECIFIQUE } from 'camino-common/src/machines' import { TitreId } from 'camino-common/src/validators/titres' import { communeIdValidator } from 'camino-common/src/static/communes' +import { callAndExit } from '../../tools/fp-tools' const getEtapeByIdValidator = z.object({ etape_id: etapeIdValidator, @@ -84,7 +85,7 @@ export const getLargeobjectIdByEtapeDocumentId = async (pool: Pool, user: User, if (result.length === 1) { const etapeDocument = result[0] - const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await getEtapeDataForEdition(pool, etapeDocument.etape_id) + const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await callAndExit(getEtapeDataForEdition(pool, etapeDocument.etape_id)) if ( await canReadDocument(etapeDocument, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, etapeData.etape_type_id, { @@ -113,22 +114,33 @@ where LIMIT 1 ` -export const getEtapeDataForEdition = async ( +const etapeNotFound = "l'étape n'a pas été trouvée" as const +export type GetEtapeDataForEditionErrors = EffectDbQueryAndValidateErrors | typeof etapeNotFound +export const getEtapeDataForEdition = ( pool: Pool, etapeId: EtapeId -): Promise<{ - etapeData: GetEtapeDataForEdition - titreTypeId: Memoized<TitreTypeId> - administrationsLocales: Memoized<AdministrationId[]> - entreprisesTitulairesOuAmodiataires: Memoized<EntrepriseId[]> -}> => { - const etapeData = (await dbQueryAndValidate(getEtapeDataForEditionDb, { etapeId }, pool, getEtapeDataForEditionValidator))[0] - - const titreTypeId = memoize(() => Promise.resolve(etapeData.titre_type_id)) - const administrationsLocales = memoize(() => administrationsLocalesByEtapeId(etapeId, pool)) - const entreprisesTitulairesOuAmodiataires = memoize(() => entreprisesTitulairesOuAmoditairesByEtapeId(etapeId, pool)) - - return { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } +): Effect.Effect< + { + etapeData: GetEtapeDataForEdition + titreTypeId: Memoized<TitreTypeId> + administrationsLocales: Memoized<AdministrationId[]> + entreprisesTitulairesOuAmodiataires: Memoized<EntrepriseId[]> + }, + CaminoError<GetEtapeDataForEditionErrors> +> => { + return Effect.Do.pipe( + Effect.flatMap(() => effectDbQueryAndValidate(getEtapeDataForEditionDb, { etapeId }, pool, getEtapeDataForEditionValidator)), + Effect.filterOrFail( + result => isNotNullNorUndefinedNorEmpty(result) && result.length === 1, + () => ({ message: etapeNotFound }) + ), + Effect.map(result => ({ + etapeData: result[0], + titreTypeId: memoize(() => Promise.resolve(result[0].titre_type_id)), + administrationsLocales: memoize(() => administrationsLocalesByEtapeId(etapeId, pool)), + entreprisesTitulairesOuAmodiataires: memoize(() => entreprisesTitulairesOuAmoditairesByEtapeId(etapeId, pool)), + })) + ) } const getEtapeDataForEditionValidator = z.object({ diff --git a/packages/api/src/api/rest/etapes.test.integration.ts b/packages/api/src/api/rest/etapes.test.integration.ts index 3f59754ef..26487e6ee 100644 --- a/packages/api/src/api/rest/etapes.test.integration.ts +++ b/packages/api/src/api/rest/etapes.test.integration.ts @@ -417,12 +417,12 @@ describe('getEtapeAvis', () => { ], }) - let getAvis = await restCall(dbPool, '/rest/etapes/:etapeId/etapeAvis', { etapeId: etapeId }, userSuper) + let getAvis = await restNewCall(dbPool, '/rest/etapes/:etapeId/etapeAvis', { etapeId: etapeId }, userSuper) expect(getAvis.statusCode).toBe(HTTP_STATUS.OK) expect(getAvis.body).toStrictEqual([]) await titreEtapeUpdate(etapeId, { typeId: 'asc' }, userSuper, titreId) - getAvis = await restCall(dbPool, '/rest/etapes/:etapeId/etapeAvis', { etapeId: etapeId }, userSuper) + getAvis = await restNewCall(dbPool, '/rest/etapes/:etapeId/etapeAvis', { etapeId: etapeId }, userSuper) expect(getAvis.statusCode).toBe(HTTP_STATUS.OK) expect(getAvis.body).toStrictEqual([]) @@ -445,7 +445,7 @@ describe('getEtapeAvis', () => { await callAndExit(insertEtapeAvisWithLargeObjectId(dbPool, etapeId, avis, etapeAvisIdValidator.parse('avisId'), largeObjectIdValidator.parse(42))) - getAvis = await restCall(dbPool, '/rest/etapes/:etapeId/etapeAvis', { etapeId: etapeId }, userSuper) + getAvis = await restNewCall(dbPool, '/rest/etapes/:etapeId/etapeAvis', { etapeId: etapeId }, userSuper) expect(getAvis.statusCode).toBe(HTTP_STATUS.OK) expect(getAvis.body).toMatchInlineSnapshot(` [ diff --git a/packages/api/src/api/rest/etapes.ts b/packages/api/src/api/rest/etapes.ts index 408f32dcf..46487ac62 100644 --- a/packages/api/src/api/rest/etapes.ts +++ b/packages/api/src/api/rest/etapes.ts @@ -7,7 +7,6 @@ import { ETAPE_IS_NOT_BROUILLON, etapeIdOrSlugValidator, GetEtapeAvisByEtapeId, - getEtapeAvisByEtapeIdValidator, EtapeBrouillon, etapeSlugValidator, EtapeSlug, @@ -30,6 +29,7 @@ import { EntrepriseDocument, EntrepriseDocumentId, EtapeEntrepriseDocument } fro import { deleteTitreEtapeEntrepriseDocument, getDocumentsByEtapeId, + GetDocumentsByEtapeIdErrors, getEntrepriseDocumentIdsByEtapeId, getEtapeAvisLargeObjectIdsByEtapeId, GetEtapeAvisLargeObjectIdsByEtapeIdErrors, @@ -44,7 +44,7 @@ import { updateEtapeDocuments, UpdateEtapeDocumentsErrors, } from '../../database/queries/titres-etapes.queries' -import { getEtapeDataForEdition, hasTitreFrom } from './etapes.queries' +import { getEtapeDataForEdition, GetEtapeDataForEditionErrors, hasTitreFrom } from './etapes.queries' import { SDOMZoneId } from 'camino-common/src/static/sdom' import { titreEtapeAdministrationsEmailsSend, titreEtapeUtilisateursEmailsSend } from '../graphql/resolvers/_titre-etape-email' import { ConvertPointsErrors, GetGeojsonInformation, GetGeojsonInformationErrorMessages, convertPoints, getGeojsonInformation } from './perimetre.queries' @@ -89,64 +89,95 @@ export const getEtapeEntrepriseDocuments: RestNewGetCall<'/rest/etapes/:etapeId/ ) ) -export const getEtapeDocuments = - (pool: Pool) => - async (req: CaminoRequest, res: CustomResponse<GetEtapeDocumentsByEtapeId>): Promise<void> => { - const etapeIdParsed = etapeIdValidator.safeParse(req.params.etapeId) - const user = req.auth - - if (!etapeIdParsed.success) { - res.sendStatus(HTTP_STATUS.BAD_REQUEST) - } else { - try { - const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await getEtapeDataForEdition(pool, etapeIdParsed.data) - - const result = await callAndExit( - getDocumentsByEtapeId(etapeIdParsed.data, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, etapeData.etape_type_id, { - demarche_type_id: etapeData.demarche_type_id, - entreprises_lecture: etapeData.demarche_entreprises_lecture, - public_lecture: etapeData.demarche_public_lecture, - titre_public_lecture: etapeData.titre_public_lecture, +type GetEtapeDocumentsErrors = EffectDbQueryAndValidateErrors | GetEtapeDataForEditionErrors | GetDocumentsByEtapeIdErrors +export const getEtapeDocuments: RestNewGetCall<'/rest/etapes/:etapeId/etapeDocuments'> = (rootPipe): Effect.Effect<GetEtapeDocumentsByEtapeId, CaminoApiError<GetEtapeDocumentsErrors>> => + rootPipe.pipe( + Effect.bind('etapeDataForEdition', ({ pool, params }) => getEtapeDataForEdition(pool, params.etapeId)), + Effect.flatMap(({ pool, user, params, etapeDataForEdition }) => + getDocumentsByEtapeId( + params.etapeId, + pool, + user, + etapeDataForEdition.titreTypeId, + etapeDataForEdition.administrationsLocales, + etapeDataForEdition.entreprisesTitulairesOuAmodiataires, + etapeDataForEdition.etapeData.etape_type_id, + { + demarche_type_id: etapeDataForEdition.etapeData.demarche_type_id, + entreprises_lecture: etapeDataForEdition.etapeData.demarche_entreprises_lecture, + public_lecture: etapeDataForEdition.etapeData.demarche_public_lecture, + titre_public_lecture: etapeDataForEdition.etapeData.titre_public_lecture, + } + ) + ), + Effect.map(result => ({ + etapeDocuments: result, + })), + Effect.mapError(caminoError => + Match.value(caminoError.message).pipe( + Match.whenOr("l'étape n'a pas été trouvée", () => ({ + ...caminoError, + status: HTTP_STATUS.NOT_FOUND, + })), + Match.whenOr( + "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', + "une erreur s'est produite lors de la vérification des droits de lecture d'un document", + "Impossible de transformer le document en base en document d'API", + () => ({ + ...caminoError, + status: HTTP_STATUS.INTERNAL_SERVER_ERROR, }) - ) - - res.json({ etapeDocuments: result }) - } catch (e) { - res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR) - console.error(e) - } - } - } - -export const getEtapeAvis = - (pool: Pool) => - async (req: CaminoRequest, res: CustomResponse<GetEtapeAvisByEtapeId>): Promise<void> => { - const etapeIdParsed = etapeIdValidator.safeParse(req.params.etapeId) - const user = req.auth - - if (!etapeIdParsed.success) { - res.sendStatus(HTTP_STATUS.BAD_REQUEST) - } else { - try { - const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await getEtapeDataForEdition(pool, etapeIdParsed.data) + ), + Match.exhaustive + ) + ) + ) - const result = await callAndExit( - getEtapeAvisLargeObjectIdsByEtapeId(etapeIdParsed.data, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, etapeData.etape_type_id, { - demarche_type_id: etapeData.demarche_type_id, - entreprises_lecture: etapeData.demarche_entreprises_lecture, - public_lecture: etapeData.demarche_public_lecture, - titre_public_lecture: etapeData.titre_public_lecture, +type GetEtapeAvisErrors = EffectDbQueryAndValidateErrors | GetEtapeDataForEditionErrors | GetEtapeAvisLargeObjectIdsByEtapeIdErrors +export const getEtapeAvis: RestNewGetCall<'/rest/etapes/:etapeId/etapeAvis'> = (rootPipe): Effect.Effect<GetEtapeAvisByEtapeId, CaminoApiError<GetEtapeAvisErrors>> => + rootPipe.pipe( + Effect.bind('etapeDataForEdition', ({ pool, params }) => getEtapeDataForEdition(pool, params.etapeId)), + Effect.flatMap(({ pool, params, user, etapeDataForEdition }) => + getEtapeAvisLargeObjectIdsByEtapeId( + params.etapeId, + pool, + user, + etapeDataForEdition.titreTypeId, + etapeDataForEdition.administrationsLocales, + etapeDataForEdition.entreprisesTitulairesOuAmodiataires, + etapeDataForEdition.etapeData.etape_type_id, + { + demarche_type_id: etapeDataForEdition.etapeData.demarche_type_id, + entreprises_lecture: etapeDataForEdition.etapeData.demarche_entreprises_lecture, + public_lecture: etapeDataForEdition.etapeData.demarche_public_lecture, + titre_public_lecture: etapeDataForEdition.etapeData.titre_public_lecture, + } + ) + ), + Effect.map(result => { + const avis: GetEtapeAvisByEtapeId = result.map(a => ({ ...a, has_file: isNotNullNorUndefined(a.largeobject_id) })) + return avis + }), + Effect.mapError(caminoError => + Match.value(caminoError.message).pipe( + Match.whenOr("l'étape n'a pas été trouvée", () => ({ + ...caminoError, + status: HTTP_STATUS.NOT_FOUND, + })), + Match.whenOr( + "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', + "une erreur s'est produite lors de la vérification des droits de lecture d'un avis", + () => ({ + ...caminoError, + status: HTTP_STATUS.INTERNAL_SERVER_ERROR, }) - ) - - const avis: GetEtapeAvisByEtapeId = result.map(a => ({ ...a, has_file: isNotNullNorUndefined(a.largeobject_id) })) - res.json(getEtapeAvisByEtapeIdValidator.parse(avis)) - } catch (e) { - res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR) - console.error(e) - } - } - } + ), + Match.exhaustive + ) + ) + ) export const deleteEtape = (pool: Pool) => @@ -899,6 +930,7 @@ type DeposeEtapeErrors = | TitreEtapeToFlattenEtapeErrors | GetEtapeDocumentLargeObjectIdsByEtapeIdErrors | GetEtapeAvisLargeObjectIdsByEtapeIdErrors + | GetDocumentsByEtapeIdErrors export const deposeEtape: RestNewPutCall<'/rest/etapes/:etapeId/depot'> = (rootPipe): Effect.Effect<{ id: EtapeId }, CaminoApiError<DeposeEtapeErrors>> => { return rootPipe.pipe( Effect.bind('titreEtape', ({ params, user }) => @@ -1092,6 +1124,7 @@ export const deposeEtape: RestNewPutCall<'/rest/etapes/:etapeId/depot'> = (rootP 'une erreur est survenue lors des tâches annexes', "une erreur s'est produite lors de la vérification des droits de lecture d'un avis", "une erreur s'est produite lors de la vérification des droits de lecture d'un document", + "Impossible de transformer le document en base en document d'API", () => ({ ...caminoError, status: HTTP_STATUS.INTERNAL_SERVER_ERROR }) ), Match.exhaustive diff --git a/packages/api/src/api/rest/fichiers.ts b/packages/api/src/api/rest/fichiers.ts index bb00c9859..fe74b8f2a 100644 --- a/packages/api/src/api/rest/fichiers.ts +++ b/packages/api/src/api/rest/fichiers.ts @@ -32,7 +32,7 @@ export const etapeTelecharger = throw new Error("id d'étape absent") } - const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await getEtapeDataForEdition(pool, etapeId) + const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await callAndExit(getEtapeDataForEdition(pool, etapeId)) const documents = await callAndExit( getEtapeDocumentLargeObjectIdsByEtapeId(etapeId, pool, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, etapeData.etape_type_id, { diff --git a/packages/api/src/business/daily.ts b/packages/api/src/business/daily.ts index e63278530..d659a33cb 100644 --- a/packages/api/src/business/daily.ts +++ b/packages/api/src/business/daily.ts @@ -26,7 +26,7 @@ import { allEtapesConsentementUpdate } from './processes/titres-etapes-consentem import { etapesCompletesCheck } from '../tools/etapes/etapes-complete-check' import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, NonEmptyArray } from 'camino-common/src/typescript-tools' import { config } from '../config' -import { createReadStream, createWriteStream, readFileSync, writeFileSync } from 'node:fs' +import { createReadStream, readFileSync, writeFileSync, WriteStream } from 'node:fs' import { createInterface } from 'node:readline' import { fetch } from 'undici' import { mailjetSend } from '../tools/api-mailjet/emails' @@ -187,9 +187,7 @@ const dailyResult = (daily: Daily | null): DailyResult => { return { changes: false } } -export const daily = async (pool: Pool, logFile: string): Promise<void> => { - const output = createWriteStream(logFile, { flush: true, autoClose: true }) - +export const daily = async (pool: Pool, logFile: string, stream: WriteStream): Promise<void> => { // Réinitialise les logs qui seront envoyés par email writeFileSync(logFile, '') let resume: DailyResult = { changes: true, logs: ["Le daily ne s'est pas lancé"] } @@ -201,7 +199,7 @@ export const daily = async (pool: Pool, logFile: string): Promise<void> => { if (isNotNullNorUndefined(config().CAMINO_STAGE)) { await new Promise<void>(resolve => { - output.end(() => resolve()) + stream.end(() => resolve()) }) const emailBody = `Résultats de ${config().ENV} \n${resume.changes ? resume.logs.join('\n') : 'Pas de changement\n'}\n${readFileSync(logFile).toString()}` try { diff --git a/packages/api/src/database/init.ts b/packages/api/src/database/init.ts index 7a65fd750..a0a2dc717 100644 --- a/packages/api/src/database/init.ts +++ b/packages/api/src/database/init.ts @@ -3,11 +3,12 @@ import { daily } from '../business/daily' import type { Pool } from 'pg' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' import { config } from '../config' +import { createWriteStream } from 'node:fs' export const databaseInit = async (pool: Pool): Promise<void> => { await knex.migrate.latest() if (isNotNullNorUndefined(config().CAMINO_STAGE)) { // pas de await pour ne pas bloquer le démarrage de l’appli - daily(pool, '/tmp/unused') // eslint-disable-line @typescript-eslint/no-floating-promises + daily(pool, '/tmp/unused', createWriteStream('/tmp/unused')) // eslint-disable-line @typescript-eslint/no-floating-promises } } diff --git a/packages/api/src/database/queries/titres-etapes.queries.ts b/packages/api/src/database/queries/titres-etapes.queries.ts index e0fd86bc5..f4cc9f320 100644 --- a/packages/api/src/database/queries/titres-etapes.queries.ts +++ b/packages/api/src/database/queries/titres-etapes.queries.ts @@ -66,7 +66,7 @@ import { contenuValidator, FlattenedContenu, heritageContenuValidator } from 'ca import { DemarcheTypeId, demarcheTypeIdValidator } from 'camino-common/src/static/demarchesTypes' import { Effect, Option, pipe } from 'effect' import { CaminoError } from 'camino-common/src/zod-tools' -import { shortCircuitError, zodParseEffect, ZodUnparseable } from '../../tools/fp-tools' +import { callAndExit, shortCircuitError, zodParseEffectTyped } from '../../tools/fp-tools' import { TempDocumentName } from 'camino-common/src/document' import { DemarcheId } from 'camino-common/src/demarche' @@ -485,7 +485,7 @@ export const getLargeobjectIdByEtapeAvisId = async (pool: Pool, user: User, etap if (result.length === 1) { const etapeAvis = result[0] - const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await getEtapeDataForEdition(pool, etapeAvis.etape_id) + const { etapeData, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires } = await callAndExit(getEtapeDataForEdition(pool, etapeAvis.etape_id)) if ( await canReadAvis(etapeAvis, user, titreTypeId, administrationsLocales, entreprisesTitulairesOuAmodiataires, etapeData.etape_type_id, { @@ -512,6 +512,9 @@ where d.id = $ etapeAvisId ! LIMIT 1 ` + +const errorParseGetDocumentsByEtapeId = "Impossible de transformer le document en base en document d'API" as const +export type GetDocumentsByEtapeIdErrors = GetEtapeDocumentLargeObjectIdsByEtapeIdErrors | typeof errorParseGetDocumentsByEtapeId export const getDocumentsByEtapeId = ( titre_etape_id: EtapeId, pool: Pool, @@ -521,9 +524,9 @@ export const getDocumentsByEtapeId = ( entreprisesTitulairesOuAmodiataires: SimplePromiseFn<EntrepriseId[]>, etapeTypeId: EtapeTypeId, demarche: CanReadDemarche -): Effect.Effect<EtapeDocument[], CaminoError<GetEtapeDocumentLargeObjectIdsByEtapeIdErrors | ZodUnparseable>> => +): Effect.Effect<EtapeDocument[], CaminoError<GetDocumentsByEtapeIdErrors>> => getEtapeDocumentLargeObjectIdsByEtapeId(titre_etape_id, pool, user, titreTypeId, titresAdministrationsLocales, entreprisesTitulairesOuAmodiataires, etapeTypeId, demarche).pipe( - Effect.flatMap(result => zodParseEffect(z.array(etapeDocumentValidator), result)) + Effect.flatMap(result => zodParseEffectTyped(z.array(etapeDocumentValidator), result, errorParseGetDocumentsByEtapeId)) ) const getEtapesWithAutomaticStatutValidator = z.object({ diff --git a/packages/api/src/scripts/daily.ts b/packages/api/src/scripts/daily.ts index 117f54a1c..51776c97b 100644 --- a/packages/api/src/scripts/daily.ts +++ b/packages/api/src/scripts/daily.ts @@ -35,7 +35,7 @@ const tasks = async () => { // Réinitialise les logs qui seront envoyés par email writeFileSync(logFile, '') try { - await daily(pool, logFile) + await daily(pool, logFile, output) } catch (e) { console.error('Erreur durant le daily', e) } diff --git a/packages/api/src/server/rest.ts b/packages/api/src/server/rest.ts index f0a6b4ee9..891111944 100644 --- a/packages/api/src/server/rest.ts +++ b/packages/api/src/server/rest.ts @@ -236,8 +236,8 @@ const restRouteImplementations: Readonly<{ [key in CaminoRestRoute]: Transform<k '/rest/etapes': { newPostCall: createEtape, newPutCall: updateEtape, ...CaminoRestRoutes['/rest/etapes'] }, '/rest/etapes/:etapeId/depot': { newPutCall: deposeEtape, ...CaminoRestRoutes['/rest/etapes/:etapeId/depot'] }, '/rest/etapes/:etapeId/entrepriseDocuments': { newGetCall: getEtapeEntrepriseDocuments, ...CaminoRestRoutes['/rest/etapes/:etapeId/entrepriseDocuments'] }, - '/rest/etapes/:etapeId/etapeDocuments': { getCall: getEtapeDocuments, ...CaminoRestRoutes['/rest/etapes/:etapeId/etapeDocuments'] }, - '/rest/etapes/:etapeId/etapeAvis': { getCall: getEtapeAvis, ...CaminoRestRoutes['/rest/etapes/:etapeId/etapeAvis'] }, + '/rest/etapes/:etapeId/etapeDocuments': { newGetCall: getEtapeDocuments, ...CaminoRestRoutes['/rest/etapes/:etapeId/etapeDocuments'] }, + '/rest/etapes/:etapeId/etapeAvis': { newGetCall: getEtapeAvis, ...CaminoRestRoutes['/rest/etapes/:etapeId/etapeAvis'] }, '/rest/activites/:activiteId': { getCall: getActivite, putCall: updateActivite, deleteCall: deleteActivite, ...CaminoRestRoutes['/rest/activites/:activiteId'] }, '/rest/communes': { newGetCall: getCommunes, ...CaminoRestRoutes['/rest/communes'] }, '/rest/geojson/import/:geoSystemeId': { newPostCall: geojsonImport, ...CaminoRestRoutes['/rest/geojson/import/:geoSystemeId'] }, diff --git a/packages/api/src/tools/fp-tools.ts b/packages/api/src/tools/fp-tools.ts index 0a34a3edd..b670998c5 100644 --- a/packages/api/src/tools/fp-tools.ts +++ b/packages/api/src/tools/fp-tools.ts @@ -3,8 +3,14 @@ import { Cause, Effect, Exit, pipe } from 'effect' import { ZodTypeAny } from 'zod' import { fromError, isZodErrorLike } from 'zod-validation-error' +/** + * @deprecated use more precise message + */ export type ZodUnparseable = 'Problème de validation de données' +/** + * @deprecated use zodParseEffectTyped + */ export const zodParseEffectCallback = <T extends ZodTypeAny>(validator: T) => (value: unknown): Effect.Effect<T['_output'], CaminoError<ZodUnparseable>> => @@ -20,6 +26,9 @@ const zodErrorToDetail = (myError: unknown): string | undefined => { return undefined } +/** + * @deprecated use zodParseEffectTyped + */ export const zodParseEffect = <T extends ZodTypeAny>(validator: T, item: unknown): Effect.Effect<T['_output'], CaminoError<ZodUnparseable>> => { return Effect.try({ try: () => validator.parse(item), @@ -27,6 +36,13 @@ 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>> => { + return Effect.try({ + try: () => validator.parse(item), + catch: myError => ({ message: errorMessage, detail: zodErrorToDetail(myError), zodErrorReadableMessage: zodErrorToReadableMessage(myError) }), + }) +} + export const callAndExit = async <A>(toCall: Effect.Effect<A, CaminoError<string>, never>): Promise<A> => { const pipeline = await pipe(toCall, Effect.runPromiseExit) diff --git a/packages/common/src/rest.ts b/packages/common/src/rest.ts index 3d78d7505..c5e809b96 100644 --- a/packages/common/src/rest.ts +++ b/packages/common/src/rest.ts @@ -218,8 +218,8 @@ export const CaminoRestRoutes = { '/rest/demarches/:demarcheId/miseEnConcurrence': { params: z.object({ demarcheId: demarcheIdValidator }), newGet: { output: z.array(getDemarcheMiseEnConcurrenceValidator) } }, '/rest/demarches/:demarcheId/resultatMiseEnConcurrence': { params: z.object({ demarcheId: demarcheIdValidator }), newGet: { output: getResultatMiseEnConcurrenceValidator } }, '/rest/etapes/:etapeId/geojson': { params: z.object({ etapeId: etapeIdOrSlugValidator }), newGet: { output: perimetreInformationsValidator } }, - '/rest/etapes/:etapeId/etapeDocuments': { params: etapeIdParamsValidator, get: { output: getEtapeDocumentsByEtapeIdValidator } }, - '/rest/etapes/:etapeId/etapeAvis': { params: etapeIdParamsValidator, get: { output: getEtapeAvisByEtapeIdValidator } }, + '/rest/etapes/:etapeId/etapeDocuments': { params: etapeIdParamsValidator, newGet: { output: getEtapeDocumentsByEtapeIdValidator } }, + '/rest/etapes/:etapeId/etapeAvis': { params: etapeIdParamsValidator, newGet: { output: getEtapeAvisByEtapeIdValidator } }, '/rest/etapes/:etapeId/entrepriseDocuments': { params: etapeIdParamsValidator, newGet: { output: z.array(etapeEntrepriseDocumentValidator) } }, '/rest/etapes/:etapeIdOrSlug': { params: z.object({ etapeIdOrSlug: etapeIdOrSlugValidator }), delete: true, get: { output: flattenEtapeValidator } }, '/rest/etapes/:etapeId/depot': { params: etapeIdParamsValidator, newPut: { input: z.object({}), output: z.object({ id: etapeIdValidator }) } }, diff --git a/packages/ui/src/api/client-rest.ts b/packages/ui/src/api/client-rest.ts index 5d3273963..529076e74 100644 --- a/packages/ui/src/api/client-rest.ts +++ b/packages/ui/src/api/client-rest.ts @@ -33,6 +33,16 @@ export class CaminoHttpError extends Error { export type AsyncData<T> = Loading | { status: 'LOADED'; value: T } | CaminoApiError | NewCaminoApiError +export const asyncDataAutomaticLoad = async <T extends object>(value: () => Promise<T | CaminoError<string>>, setState: (newState: AsyncData<T>) => void): Promise<void> => { + setState({ status: 'LOADING' }) + const result = await value() + if ('message' in result) { + setState({ status: 'NEW_ERROR', error: result }) + } else { + setState({ status: 'LOADED', value: result }) + } +} + type UiRestRoute = string & { __camino: 'RestRoute' } const baseRoute = '/apiUrl' diff --git a/packages/ui/src/components/etape/etape-api-client.ts b/packages/ui/src/components/etape/etape-api-client.ts index 210596939..37235b203 100644 --- a/packages/ui/src/components/etape/etape-api-client.ts +++ b/packages/ui/src/components/etape/etape-api-client.ts @@ -82,9 +82,9 @@ export interface EtapeApiClient { getEtapesTypesEtapesStatuts: (titreDemarcheId: DemarcheId, titreEtapeId: EtapeId | null, date: CaminoDate) => Promise<EtapeTypeEtapeStatutWithMainStep | CaminoError<string>> deleteEtape: (titreEtapeId: EtapeId) => Promise<void> deposeEtape: (titreEtapeId: EtapeId) => Promise<{ id: EtapeId } | CaminoError<string>> - getEtapeDocumentsByEtapeId: (etapeId: EtapeId) => Promise<GetEtapeDocumentsByEtapeId> + getEtapeDocumentsByEtapeId: (etapeId: EtapeId) => Promise<GetEtapeDocumentsByEtapeId | CaminoError<string>> getEtapeHeritagePotentiel: (etape: Pick<CoreEtapeCreationOrModification, 'id' | 'date' | 'typeId'>, titreDemarcheId: DemarcheId) => Promise<GetEtapeHeritagePotentiel> - getEtapeAvisByEtapeId: (etapeId: EtapeId) => Promise<GetEtapeAvisByEtapeId> + getEtapeAvisByEtapeId: (etapeId: EtapeId) => Promise<GetEtapeAvisByEtapeId | CaminoError<string>> getEtape: (etapeIdOrSlug: EtapeIdOrSlug) => Promise<{ etape: FlattenEtape; demarche: GetDemarcheByIdOrSlug | CaminoError<string> }> etapeCreer: (etape: RestEtapeCreation) => Promise<CaminoError<string> | { id: EtapeId }> etapeModifier: (etape: RestEtapeModification) => Promise<CaminoError<string> | { id: EtapeId }> @@ -98,8 +98,8 @@ export const etapeApiClient: EtapeApiClient = { }, deposeEtape: async etapeId => newPutWithJson('/rest/etapes/:etapeId/depot', { etapeId }, {}), - getEtapeDocumentsByEtapeId: async etapeId => getWithJson('/rest/etapes/:etapeId/etapeDocuments', { etapeId }), - getEtapeAvisByEtapeId: async etapeId => getWithJson('/rest/etapes/:etapeId/etapeAvis', { etapeId }), + getEtapeDocumentsByEtapeId: async etapeId => newGetWithJson('/rest/etapes/:etapeId/etapeDocuments', { etapeId }), + getEtapeAvisByEtapeId: async etapeId => newGetWithJson('/rest/etapes/:etapeId/etapeAvis', { etapeId }), getEtape: async etapeIdOrSlug => { const etape = await getWithJson('/rest/etapes/:etapeIdOrSlug', { etapeIdOrSlug }) diff --git a/packages/ui/src/components/etape/etape-avis-edit.tsx b/packages/ui/src/components/etape/etape-avis-edit.tsx index b7c791db9..ed976f763 100644 --- a/packages/ui/src/components/etape/etape-avis-edit.tsx +++ b/packages/ui/src/components/etape/etape-avis-edit.tsx @@ -6,7 +6,7 @@ import { ApiClient } from '../../api/api-client' import { FunctionalComponent, computed, defineComponent, onMounted, ref, watch } from 'vue' import { isNonEmptyArray, isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, NonEmptyArray } from 'camino-common/src/typescript-tools' import { LoadingElement } from '../_ui/functional-loader' -import { AsyncData } from '../../api/client-rest' +import { AsyncData, asyncDataAutomaticLoad } from '../../api/client-rest' import { DsfrButtonIcon } from '../_ui/dsfr-button' import { AddEtapeAvisPopup } from './add-etape-avis-popup' import { dateFormat, FirstEtapeDate } from 'camino-common/src/date' @@ -23,6 +23,7 @@ import { Column, TableSimple } from '../_ui/table-simple' import { TableRow } from '../_ui/table' import { getAvisTypes } from 'camino-common/src/avisTypes' import { isArmMecanise } from 'camino-common/src/static/mecanise' +import { useState } from '@/utils/vue-tsx-utils' interface Props { tde: { @@ -44,25 +45,16 @@ type WithIndex = { index: number } type EtapeAvisModificationWithIndex = EtapeAvisModification & WithIndex export const EtapeAvisEdit = defineComponent<Props>(props => { - const etapeAvis = ref<AsyncData<EtapeAvis[]>>({ status: 'LOADING' }) + const [etapeAvis, setEtapeAvis] = useState<AsyncData<EtapeAvis[]>>({ status: 'LOADING' }) onMounted(async () => { - if (isNotNullNorUndefined(props.etapeId)) { - etapeAvis.value = { status: 'LOADING' } - try { - const result = await props.apiClient.getEtapeAvisByEtapeId(props.etapeId) - - etapeAvis.value = { status: 'LOADED', value: result } - } catch (e: any) { - console.error('error', e) - etapeAvis.value = { - status: 'ERROR', - message: e.message ?? "Une erreur s'est produite", - } + await asyncDataAutomaticLoad(() => { + if (isNotNullNorUndefined(props.etapeId)) { + return props.apiClient.getEtapeAvisByEtapeId(props.etapeId) + } else { + return Promise.resolve([]) } - } else { - etapeAvis.value = { status: 'LOADED', value: [] } - } + }, setEtapeAvis) if (etapeAvis.value.status === 'LOADED') { props.onChange(etapeAvis.value.value) } diff --git a/packages/ui/src/components/etape/etape-documents-edit.stories.tsx b/packages/ui/src/components/etape/etape-documents-edit.stories.tsx index a3a852ad7..a9038a3fe 100644 --- a/packages/ui/src/components/etape/etape-documents-edit.stories.tsx +++ b/packages/ui/src/components/etape/etape-documents-edit.stories.tsx @@ -323,7 +323,7 @@ export const Loading: StoryFn = () => ( ) export const WithError: StoryFn = () => ( <EtapeDocumentsEdit - apiClient={{ ...apiClient, getEtapeDocumentsByEtapeId: () => Promise.reject(new Error('Une erreur est survenue')) }} + apiClient={{ ...apiClient, getEtapeDocumentsByEtapeId: () => Promise.resolve({ message: 'Une erreur est survenue' }) }} contenu={{}} etapeId={etapeIdValidator.parse('etapeId')} sdomZoneIds={[]} diff --git a/packages/ui/src/components/etape/etape-documents-edit.stories_snapshots_WithError.html b/packages/ui/src/components/etape/etape-documents-edit.stories_snapshots_WithError.html index 5cd6a4951..96ab73995 100644 --- a/packages/ui/src/components/etape/etape-documents-edit.stories_snapshots_WithError.html +++ b/packages/ui/src/components/etape/etape-documents-edit.stories_snapshots_WithError.html @@ -1,7 +1,7 @@ <div class="" style="display: flex; justify-content: center;"> - <div class="fr-alert fr-alert--error fr-alert--sm"> + <!----> + <div class="fr-alert fr-alert--error fr-alert--sm" role="alert"> <p>Une erreur est survenue</p> </div> <!----> - <!----> </div> \ No newline at end of file diff --git a/packages/ui/src/components/etape/etape-documents-edit.tsx b/packages/ui/src/components/etape/etape-documents-edit.tsx index 5035f9a2f..9a1fb0df6 100644 --- a/packages/ui/src/components/etape/etape-documents-edit.tsx +++ b/packages/ui/src/components/etape/etape-documents-edit.tsx @@ -8,7 +8,7 @@ import { SDOMZoneId } from 'camino-common/src/static/sdom' import { isNonEmptyArray, isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined, NonEmptyArray } from 'camino-common/src/typescript-tools' import { AutreDocumentType, AutreDocumentTypeId, DocumentType, DocumentTypeId, DocumentsTypes } from 'camino-common/src/static/documentsTypes' import { LoadingElement } from '../_ui/functional-loader' -import { AsyncData } from '../../api/client-rest' +import { AsyncData, asyncDataAutomaticLoad } from '../../api/client-rest' import { DsfrButtonIcon } from '../_ui/dsfr-button' import { getVisibilityLabel, sortDocumentsColumn } from './etape-documents' import { AddEtapeDocumentPopup } from './add-etape-document-popup' @@ -21,6 +21,7 @@ import { Column, TableSimple } from '../_ui/table-simple' import { TableRow } from '../_ui/table' import { getDocuments } from 'camino-common/src/static/titresTypes_demarchesTypes_etapesTypes/documents' import { isArmMecanise } from 'camino-common/src/static/mecanise' +import { useState } from '@/utils/vue-tsx-utils' interface Props { tde: { @@ -43,25 +44,16 @@ type WithIndex = { index: number } type EtapeDocumentModificationWithIndex = EtapeDocumentModification & WithIndex export const EtapeDocumentsEdit = defineComponent<Props>(props => { - const etapeDocuments = ref<AsyncData<GetEtapeDocumentsByEtapeId>>({ status: 'LOADING' }) + const [etapeDocuments, setEtapeDocuments] = useState<AsyncData<GetEtapeDocumentsByEtapeId>>({ status: 'LOADING' }) onMounted(async () => { - if (isNotNullNorUndefined(props.etapeId)) { - etapeDocuments.value = { status: 'LOADING' } - try { - const result = await props.apiClient.getEtapeDocumentsByEtapeId(props.etapeId) - - etapeDocuments.value = { status: 'LOADED', value: result } - } catch (e: any) { - console.error('error', e) - etapeDocuments.value = { - status: 'ERROR', - message: e.message ?? "Une erreur s'est produite", - } + await asyncDataAutomaticLoad(() => { + if (isNotNullNorUndefined(props.etapeId)) { + return props.apiClient.getEtapeDocumentsByEtapeId(props.etapeId) } - } else { - etapeDocuments.value = { status: 'LOADED', value: { etapeDocuments: [] } } - } + return Promise.resolve({ etapeDocuments: [] }) + }, setEtapeDocuments) + if (etapeDocuments.value.status === 'LOADED') { props.completeUpdate(etapeDocuments.value.value.etapeDocuments) } diff --git a/packages/ui/src/components/etape/etape-edit-form.tsx b/packages/ui/src/components/etape/etape-edit-form.tsx index 554591a0c..9ce7e8bde 100644 --- a/packages/ui/src/components/etape/etape-edit-form.tsx +++ b/packages/ui/src/components/etape/etape-edit-form.tsx @@ -82,11 +82,9 @@ export type Props = { > } -type EtapeEditFormDocuments = { - etapeDocuments: (EtapeDocument | TempEtapeDocument)[] - entrepriseDocuments: SelectedEntrepriseDocument[] - etapeAvis: (EtapeAvis | TempEtapeAvis)[] -} +type EtapeDocumentEdit = (EtapeDocument | TempEtapeDocument)[] +type EntrepriseDocumentEdit = SelectedEntrepriseDocument[] +type EtapeAvisEdit = (EtapeAvis | TempEtapeAvis)[] const mergeFlattenEtapeWithNewHeritage = ( etape: CoreEtapeCreationOrModification, @@ -218,11 +216,10 @@ export const EtapeEditForm = defineComponent<Props>(props => { const [etape, setEtape] = useState<AsyncData<CoreEtapeCreationOrModification | null>>({ status: 'LOADED', value: null }) const [perimetreInfos, setPerimetreInfos] = useState<PerimetreInformations>(props.perimetre) - const [documents, setDocuments] = useState<EtapeEditFormDocuments>({ - etapeDocuments: [], - entrepriseDocuments: [], - etapeAvis: [], - }) + const [etapeDocuments, setEtapeDocuments] = useState<EtapeDocumentEdit>([]) + const [entrepriseDocuments, setEntrepriseDocuments] = useState<EntrepriseDocumentEdit>([]) + const [etapeAvis, setEtapeAvis] = useState<EtapeAvisEdit>([]) + onMounted(async () => { if (isNotNullNorUndefined(props.etape.date) && isNotNullNorUndefined(props.etape.typeId) && isNotNullNorUndefined(props.etape.statutId)) { setEtape({ status: 'LOADED', value: { ...props.etape, date: props.etape.date, typeId: props.etape.typeId, statutId: props.etape.statutId } }) @@ -257,9 +254,8 @@ export const EtapeEditForm = defineComponent<Props>(props => { } } - const setEtapeAndDocument = (etape: CoreEtapeCreationOrModification, documents: EtapeEditFormDocuments) => { + const setEtapeInternal = (etape: CoreEtapeCreationOrModification) => { setEtape({ status: 'LOADED', value: etape }) - setDocuments(documents) } const alertes = computed<EtapeAlerte[]>(() => { @@ -299,11 +295,11 @@ export const EtapeEditForm = defineComponent<Props>(props => { props.titreTypeId, props.demarcheId, props.demarcheTypeId, - documents.value.etapeDocuments, - documents.value.entrepriseDocuments.map(e => ({ entreprise_document_type_id: e.documentTypeId, entreprise_id: e.entrepriseId })), + etapeDocuments.value, + entrepriseDocuments.value.map(e => ({ entreprise_document_type_id: e.documentTypeId, entreprise_id: e.entrepriseId })), props.perimetre.sdomZoneIds, props.perimetre.communes, - documents.value.etapeAvis, + etapeAvis.value, props.user, props.firstEtapeDate ?? firstEtapeDateValidator.parse(etape.value.value.date) ) @@ -322,11 +318,11 @@ export const EtapeEditForm = defineComponent<Props>(props => { props.demarcheId, props.demarcheTypeId, etape.value.value, - documents.value.etapeDocuments, - documents.value.entrepriseDocuments.map(({ documentTypeId, entrepriseId }) => ({ entreprise_document_type_id: documentTypeId, entreprise_id: entrepriseId })), + etapeDocuments.value, + entrepriseDocuments.value.map(({ documentTypeId, entrepriseId }) => ({ entreprise_document_type_id: documentTypeId, entreprise_id: entrepriseId })), props.perimetre.sdomZoneIds, props.perimetre.communes, - documents.value.etapeAvis, + etapeAvis.value, props.firstEtapeDate ?? firstEtapeDateValidator.parse(etape.value.value.date) ) } @@ -387,17 +383,18 @@ export const EtapeEditForm = defineComponent<Props>(props => { ...etape.value.value, id: props.etape.id, titreDemarcheId: props.demarcheId, - ...documents.value, - entrepriseDocumentIds: documents.value.entrepriseDocuments.map(({ id }) => id), + etapeAvis: etapeAvis.value, + etapeDocuments: etapeDocuments.value, + entrepriseDocumentIds: entrepriseDocuments.value.map(({ id }) => id), ...heritage, }) } else { etapeId = await props.apiClient.etapeCreer({ ...etape.value.value, titreDemarcheId: props.demarcheId, - etapeAvis: documents.value.etapeAvis, - etapeDocuments: documents.value.etapeDocuments.filter(value => 'temp_document_name' in value), - entrepriseDocumentIds: documents.value.entrepriseDocuments.map(({ id }) => id), + etapeAvis: etapeAvis.value, + etapeDocuments: etapeDocuments.value.filter(value => 'temp_document_name' in value), + entrepriseDocumentIds: entrepriseDocuments.value.map(({ id }) => id), ...heritage, }) } @@ -461,7 +458,19 @@ export const EtapeEditForm = defineComponent<Props>(props => { <> {isNotNullNorUndefined(etapeLoaded) ? ( <> - <EtapeEditFormInternal {...props} perimetre={perimetreInfos.value} etape={etapeLoaded} documents={documents.value} setEtape={setEtapeAndDocument} alertesUpdate={setPerimetreInfos} /> + <EtapeEditFormInternal + {...props} + perimetre={perimetreInfos.value} + etape={etapeLoaded} + setEtape={setEtapeInternal} + setEtapeDocuments={setEtapeDocuments} + setEntrepriseDocuments={setEntrepriseDocuments} + setEtapeAvis={setEtapeAvis} + alertesUpdate={setPerimetreInfos} + etapeDocuments={etapeDocuments.value} + entrepriseDocuments={entrepriseDocuments.value} + etapeAvis={etapeAvis.value} + /> <PureFormSaveBtn class="fr-mt-2w fr-pt-2w fr-pb-2w" style={{ position: 'sticky', bottom: 0, background: 'white', zIndex: 100000 }} @@ -485,16 +494,18 @@ export const EtapeEditForm = defineComponent<Props>(props => { const EtapeEditFormInternal = defineComponent< { etape: CoreEtapeCreationOrModification - documents: EtapeEditFormDocuments - setEtape: (etape: CoreEtapeCreationOrModification, documents: EtapeEditFormDocuments) => void + etapeDocuments: EtapeDocumentEdit + entrepriseDocuments: EntrepriseDocumentEdit + etapeAvis: EtapeAvisEdit + setEtape: (etape: CoreEtapeCreationOrModification) => void + setEtapeDocuments: (values: EtapeDocumentEdit) => void + setEntrepriseDocuments: (values: EntrepriseDocumentEdit) => void + setEtapeAvis: (values: EtapeAvisEdit) => void alertesUpdate: (alertes: PerimetreInformations) => void } & Omit<Props, 'etape'> >(props => { const documentsCompleteUpdate = (etapeDocuments: (EtapeDocument | TempEtapeDocument)[]) => { - props.setEtape(props.etape, { - ...props.documents, - etapeDocuments, - }) + props.setEtapeDocuments(etapeDocuments) } const firstEtapeDate = computed<FirstEtapeDate>(() => { @@ -502,73 +513,64 @@ const EtapeEditFormInternal = defineComponent< }) const avisCompleteUpdate = (etapeAvis: (EtapeAvis | TempEtapeAvis)[]) => { - props.setEtape(props.etape, { - ...props.documents, - etapeAvis, - }) + props.setEtapeAvis(etapeAvis) } const entrepriseDocumentsCompleteUpdate = (entrepriseDocuments: SelectedEntrepriseDocument[]) => { - props.setEtape(props.etape, { ...props.documents, entrepriseDocuments }) + props.setEntrepriseDocuments(entrepriseDocuments) } const onEtapePerimetreChange = (perimetreInfos: GeojsonInformations) => { - props.setEtape( - { - ...props.etape, - perimetre: { - ...props.etape.perimetre, - value: { - geojson4326Forages: isNotNullNorUndefined(props.etape.perimetre.value) ? props.etape.perimetre.value.geojson4326Forages : null, - geojsonOrigineForages: isNotNullNorUndefined(props.etape.perimetre.value) ? props.etape.perimetre.value.geojsonOrigineForages : null, - geojson4326Perimetre: perimetreInfos.geojson4326_perimetre, - geojson4326Points: perimetreInfos.geojson4326_points, - geojsonOriginePerimetre: perimetreInfos.geojson_origine_perimetre, - geojsonOriginePoints: perimetreInfos.geojson_origine_points, - geojsonOrigineGeoSystemeId: perimetreInfos.geojson_origine_geo_systeme_id, - surface: perimetreInfos.surface, - }, + props.setEtape({ + ...props.etape, + perimetre: { + ...props.etape.perimetre, + value: { + geojson4326Forages: isNotNullNorUndefined(props.etape.perimetre.value) ? props.etape.perimetre.value.geojson4326Forages : null, + geojsonOrigineForages: isNotNullNorUndefined(props.etape.perimetre.value) ? props.etape.perimetre.value.geojsonOrigineForages : null, + geojson4326Perimetre: perimetreInfos.geojson4326_perimetre, + geojson4326Points: perimetreInfos.geojson4326_points, + geojsonOriginePerimetre: perimetreInfos.geojson_origine_perimetre, + geojsonOriginePoints: perimetreInfos.geojson_origine_points, + geojsonOrigineGeoSystemeId: perimetreInfos.geojson_origine_geo_systeme_id, + surface: perimetreInfos.surface, }, }, - props.documents - ) + }) props.alertesUpdate({ superposition_alertes: perimetreInfos.superposition_alertes, sdomZoneIds: perimetreInfos.sdomZoneIds, communes: perimetreInfos.communes.map(({ id }) => id) }) } const onEtapePerimetreHeritageChange = (perimetre: FlattenEtape['perimetre']) => { - props.setEtape( - { - ...props.etape, - perimetre, - }, - props.documents - ) + props.setEtape({ + ...props.etape, + perimetre, + }) } const onEtapePointsChange = (geojson4326Points: FeatureCollectionPoints, geojsonOriginePoints: FeatureCollectionPoints) => { if (isNotNullNorUndefined(props.etape.perimetre.value)) { - props.setEtape({ ...props.etape, perimetre: { ...props.etape.perimetre, value: { ...props.etape.perimetre.value, geojson4326Points, geojsonOriginePoints } } }, props.documents) + props.setEtape({ ...props.etape, perimetre: { ...props.etape.perimetre, value: { ...props.etape.perimetre.value, geojson4326Points, geojsonOriginePoints } } }) } } const onEtapeForagesChange = (geojson4326Forages: FeatureCollectionForages, geojsonOrigineForages: FeatureCollectionForages) => { if (isNotNullNorUndefined(props.etape.perimetre.value)) { - props.setEtape({ ...props.etape, perimetre: { ...props.etape.perimetre, value: { ...props.etape.perimetre.value, geojson4326Forages, geojsonOrigineForages } } }, props.documents) + props.setEtape({ ...props.etape, perimetre: { ...props.etape.perimetre, value: { ...props.etape.perimetre.value, geojson4326Forages, geojsonOrigineForages } } }) } } const sectionCompleteUpdate = (sectionsEtape: SectionsEditEtape) => { - props.setEtape({ ...props.etape, contenu: sectionsEtape.contenu }, props.documents) + props.setEtape({ ...props.etape, contenu: sectionsEtape.contenu }) } const onUpdateNotes = (notes: string) => { - props.setEtape({ ...props.etape, note: { is_avertissement: props.etape.note.is_avertissement, valeur: notes } }, props.documents) + props.setEtape({ ...props.etape, note: { is_avertissement: props.etape.note.is_avertissement, valeur: notes } }) } const onUpdateNoteAvertissement = (isAvertissement: boolean) => { - props.setEtape({ ...props.etape, note: { valeur: props.etape.note.valeur, is_avertissement: isAvertissement } }, props.documents) + props.setEtape({ ...props.etape, note: { valeur: props.etape.note.valeur, is_avertissement: isAvertissement } }) } const fondamentalesCompleteUpdate = (etapeFondamentale: EtapeFondamentaleEdit) => { - props.setEtape({ ...props.etape, ...etapeFondamentale }, props.documents) + props.setEtape({ ...props.etape, ...etapeFondamentale }) } const titulairesAndAmodiataires = computed<Entreprise[]>(() => { @@ -628,10 +630,7 @@ const EtapeEditFormInternal = defineComponent< {etapeDocumentsStepIsVisible() ? ( <Bloc step={{ name: 'Liste des documents', help: null }} - complete={ - etapeDocumentsStepIsComplete(props.etape, props.demarcheTypeId, props.titreTypeId, props.demarcheId, props.documents.etapeDocuments, props.perimetre.sdomZoneIds, firstEtapeDate.value) - .valid - } + complete={etapeDocumentsStepIsComplete(props.etape, props.demarcheTypeId, props.titreTypeId, props.demarcheId, props.etapeDocuments, props.perimetre.sdomZoneIds, firstEtapeDate.value).valid} > <EtapeDocumentsEdit apiClient={props.apiClient} @@ -655,7 +654,7 @@ const EtapeEditFormInternal = defineComponent< {etapeAvisStepIsVisible(props.etape, props.titreTypeId, props.demarcheTypeId, props.demarcheId, firstEtapeDate.value, props.perimetre.communes) ? ( <Bloc step={{ name: 'Liste des avis', help: null }} - complete={etapeAvisStepIsComplete(props.etape, props.documents.etapeAvis, props.titreTypeId, props.demarcheTypeId, props.demarcheId, firstEtapeDate.value, props.perimetre.communes).valid} + complete={etapeAvisStepIsComplete(props.etape, props.etapeAvis, props.titreTypeId, props.demarcheTypeId, props.demarcheId, firstEtapeDate.value, props.perimetre.communes).valid} > <EtapeAvisEdit apiClient={props.apiClient} @@ -684,7 +683,7 @@ const EtapeEditFormInternal = defineComponent< ? "Les documents d’entreprise sont des documents propres à l'entreprise, et pourront être réutilisés pour la création d'un autre dossier et mis à jour si nécessaire. Ces documents d’entreprise sont consultables dans la fiche entreprise de votre société. Cette section permet de protéger et de centraliser les informations d'ordre privé relatives à la société et à son personnel." : null, }} - complete={entrepriseDocumentsStepIsComplete(props.etape, props.demarcheTypeId, props.titreTypeId, props.documents.entrepriseDocuments).valid} + complete={entrepriseDocumentsStepIsComplete(props.etape, props.demarcheTypeId, props.titreTypeId, props.entrepriseDocuments).valid} > <EntrepriseDocumentsEdit entreprises={titulairesAndAmodiataires.value} @@ -715,6 +714,13 @@ EtapeEditFormInternal.props = [ 'etape', 'demarcheId', 'demarcheTypeId', + 'etapeDocuments', + 'entrepriseDocuments', + 'etapeAvis', + 'setEtape', + 'setEtapeDocuments', + 'setEntrepriseDocuments', + 'setEtapeAvis', 'titreTypeId', 'titreSlug', 'user', diff --git a/packages/ui/src/components/titre.tsx b/packages/ui/src/components/titre.tsx index 05badb3cf..b625a4bc8 100644 --- a/packages/ui/src/components/titre.tsx +++ b/packages/ui/src/components/titre.tsx @@ -197,10 +197,8 @@ export const PureTitre = defineComponent<Props>(props => { watch( () => props.titreIdOrSlug, - async (newValue, oldValue) => { - console.log('PLOP', props.titreIdOrSlug, newValue, oldValue) + async (_newValue, _oldValue) => { if (titreData.value.status !== 'LOADED' || (titreData.value.value.id !== props.titreIdOrSlug && titreData.value.value.slug !== props.titreIdOrSlug)) { - console.log('Changement de titre') await updateTitre(props.titreIdOrSlug) } } -- GitLab