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 7e61235b66f77e54dc764e5a68d494d1d754ab89..61701ed9bc30fd0c76046de5ce65179f1794bb2c 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 4fd77d2654b011745a793e4fabeaa4e4e57d012e..baa9514d40ea78eade3d3f9c3ecd7991e697f8e7 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 3f59754ef2bb0134e2607f198880cf88afe0332e..26487e6eeca311709d4ec1392db2ad857d41004c 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 408f32dcf7beb920021bf6d87da650dce0bc96b4..46487ac625e747f3c1a3afbb28f812096cb12d17 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 bb00c98595bb5d2e71e409031ea779d4b7353d50..fe74b8f2aee18bcd7266cd3edf242229b0dae1cf 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 e6327853053501c6a559b743d25b2db2964ef257..d659a33cbf3b6d8a3ab31a865e79759ea20cc007 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 7a65fd75067786b0d27393267971c0be22942bc1..a0a2dc717e326e45873db0ea4c18eb64374d8958 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 e0fd86bc57f197ad4568278cb23b8b45f138a114..f4cc9f320fa23dc772655a7c1a5ec58be99ae62d 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 117f54a1c22575a622aea994bea30f6e8009f34d..51776c97b5d8d3110e784f476a1a2308fdce9f46 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 f0a6b4ee91aab4ff1e698a208beef4f68885ee7d..891111944144f516ff3a462f005b7b85e39e0aa3 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 0a34a3edddd591e39ce4035fa0aa3bbf7cb2eb2a..b670998c55db688ec376adf1c39af32f85ab9c74 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 3d78d7505c51b7f69bdd3069b75750ca5054a9d5..c5e809b9600dc3203f33cc0a319e4f5ad2a440f6 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 5d327396332f46463af8ad9a1f61f5ea037eabd7..529076e74836ee5ffde7dcfabf47351e64577ec1 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 21059693928cb80c2ee9a90f8dd7b1e0a801470a..37235b203a7cfb20f4c2bac3d5e67e85053705e3 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 b7c791db971ed298b0071f3830d8f87509067bff..ed976f7639412bd4a9b75b7e2fd58572d4656d44 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 a3a852ad792a6ea3ab9296a1a12b317808232898..a9038a3fe016838f05e23dd1a282571501aa7263 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 5cd6a49518801e7d551131667e88a40de522a282..96ab73995c352d5866bce731b0fffe7f6fcbc61a 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 5035f9a2f0acc876298d33714ca33d53dc8110c5..9a1fb0df659286732ee91a6dfd5b28615c01bf0d 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 554591a0ca0d782180088b1870e28b4966fa0c5f..9ce7e8bde0f56bf8ed1b1353b063e174a7e231b6 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 05badb3cf843a41085a4dab1b472db715f58208d..b625a4bc8a9b5e336ee41c3f824fd24c05e43fdf 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) } }