diff --git a/packages/api/src/api/rest/etapes.test.integration.ts b/packages/api/src/api/rest/etapes.test.integration.ts index 981a27f8b6f13e9708a0dfe52099fd7a4e3b6b5c..3f59754ef2bb0134e2607f198880cf88afe0332e 100644 --- a/packages/api/src/api/rest/etapes.test.integration.ts +++ b/packages/api/src/api/rest/etapes.test.integration.ts @@ -1,29 +1,34 @@ import { dbManager } from '../../../tests/db-manager' import { userSuper } from '../../database/user-super' -import { restCall, restDeleteCall, restNewCall } from '../../../tests/_utils/index' +import { restCall, restDeleteCall, restNewCall, restNewPostCall } from '../../../tests/_utils/index' import { caminoDateValidator, dateAddDays, toCaminoDate } from 'camino-common/src/date' import { afterAll, beforeAll, test, expect, describe, vi } from 'vitest' import type { Pool } from 'pg' import { HTTP_STATUS } from 'camino-common/src/http' import { Role, isAdministrationRole } from 'camino-common/src/roles' import { titreEtapeUpdate } from '../../database/queries/titres-etapes' -import { entrepriseIdValidator } from 'camino-common/src/entreprise' +import { entrepriseDocumentIdValidator, EntrepriseDocumentInput, entrepriseIdValidator, newEntrepriseId } from 'camino-common/src/entreprise' import { TestUser, testBlankUser } from 'camino-common/src/tests-utils' import { entrepriseUpsert } from '../../database/queries/entreprises' import { Knex } from 'knex' -import { ETAPE_IS_BROUILLON, etapeAvisIdValidator, TempEtapeAvis } from 'camino-common/src/etape' -import { insertEtapeAvisWithLargeObjectId } from '../../database/queries/titres-etapes.queries' +import { ETAPE_IS_BROUILLON, ETAPE_IS_NOT_BROUILLON, etapeAvisIdValidator, TempEtapeAvis } from 'camino-common/src/etape' +import { insertEtapeAvisWithLargeObjectId, insertTitreEtapeEntrepriseDocument } from '../../database/queries/titres-etapes.queries' import { largeObjectIdValidator } from '../../database/largeobjects' import { AvisVisibilityIds } from 'camino-common/src/static/avisTypes' import { tempDocumentNameValidator } from 'camino-common/src/document' -import { newDemarcheId, newEtapeId, newTitreId } from '../../database/models/_format/id-create' +import { idGenerate, newDemarcheId, newEtapeId, newTitreId } from '../../database/models/_format/id-create' import { insertTitreGraph } from '../../../tests/integration-test-helper' import { callAndExit } from '../../tools/fp-tools' import { DATE_DEBUT_PROCEDURE_SPECIFIQUE_AXM_ARM } from 'camino-common/src/machines' +import { ETAPES_TYPES } from 'camino-common/src/static/etapesTypes' +import { ETAPES_STATUTS } from 'camino-common/src/static/etapesStatuts' +import { copyFileSync, mkdirSync } from 'node:fs' +import { z } from 'zod' console.info = vi.fn() console.error = vi.fn() +const dir = `${process.cwd()}/files/tmp/` let knex: Knex<any, unknown[]> let dbPool: Pool beforeAll(async () => { @@ -36,6 +41,74 @@ afterAll(async () => { await dbManager.closeKnex() }) +describe('getEtapeEntrepriseDocuments', () => { + test("retourne les documents d'entreprises d'une étape", async () => { + const titreId = newTitreId() + const demarcheId = newDemarcheId() + const etapeId = newEtapeId() + await insertTitreGraph({ + id: titreId, + nom: 'nomTitre', + typeId: 'arm', + titreStatutId: 'val', + propsTitreEtapesIds: {}, + demarches: [ + { + id: demarcheId, + titreId, + typeId: 'oct', + etapes: [ + { + id: etapeId, + typeId: ETAPES_TYPES.demande, + date: toCaminoDate('2025-01-31'), + isBrouillon: ETAPE_IS_NOT_BROUILLON, + statutId: ETAPES_STATUTS.FAIT, + titreDemarcheId: demarcheId, + entrepriseDocumentIds: [entrepriseDocumentIdValidator.parse('toto')], + }, + ], + }, + ], + }) + + const tested = await restNewCall(dbPool, '/rest/etapes/:etapeId/entrepriseDocuments', { etapeId }, userSuper) + expect(tested.statusCode).toBe(HTTP_STATUS.OK) + expect(tested.body).toMatchInlineSnapshot(`[]`) + + // on insère un document et on reteste + const entrepriseId = newEntrepriseId('get-entreprise-document-entreprise-id') + await entrepriseUpsert({ id: entrepriseId, nom: entrepriseId }) + + const fileName = `existing_temp_file_${idGenerate()}` + mkdirSync(dir, { recursive: true }) + copyFileSync(`./src/tools/small.pdf`, `${dir}/${fileName}`) + const documentToInsert: EntrepriseDocumentInput = { + typeId: 'kbi', + date: toCaminoDate('2025-01-31'), + description: 'document', + tempDocumentName: tempDocumentNameValidator.parse(fileName), + } + const documentCall = await restNewPostCall(dbPool, '/rest/entreprises/:entrepriseId/documents', { entrepriseId }, userSuper, documentToInsert) + expect(documentCall.statusCode).toBe(HTTP_STATUS.OK) + + const entrepriseDocument = z.object({ id: entrepriseDocumentIdValidator }).parse(documentCall.body) + await callAndExit(insertTitreEtapeEntrepriseDocument(dbPool, { entreprise_document_id: entrepriseDocument.id, titre_etape_id: etapeId })) + + const testedWithDocument = await restNewCall(dbPool, '/rest/etapes/:etapeId/entrepriseDocuments', { etapeId }, userSuper) + expect(testedWithDocument.statusCode).toBe(HTTP_STATUS.OK) + expect(testedWithDocument.body).to.be.eql([ + { + date: '2025-01-31', + description: 'document', + entreprise_document_type_id: 'kbi', + entreprise_id: 'get-entreprise-document-entreprise-id', + id: entrepriseDocument.id, + }, + ]) + }) +}) + describe('getEtapesTypesEtapesStatusWithMainStep', () => { test('nouvelle étapes possibles procédure dédiée octroi ARM', async () => { const titreId = newTitreId() diff --git a/packages/api/src/api/rest/etapes.ts b/packages/api/src/api/rest/etapes.ts index 0c1c1d68e99dd688b7923bf2eca5a487067d1e6d..408f32dcf7beb920021bf6d87da650dce0bc96b4 100644 --- a/packages/api/src/api/rest/etapes.ts +++ b/packages/api/src/api/rest/etapes.ts @@ -72,24 +72,22 @@ import { CaminoError } from 'camino-common/src/zod-tools' import { machineFind } from '../../business/rules-demarches/machines' import { TitreEtapeForMachine } from '../../business/rules-demarches/machine-common' -export const getEtapeEntrepriseDocuments = - (pool: Pool) => - async (req: CaminoRequest, res: CustomResponse<EtapeEntrepriseDocument[]>): 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 result = await callAndExit(getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: etapeIdParsed.data }, pool, user)) - res.json(result) - } catch (e) { - res.sendStatus(HTTP_STATUS.INTERNAL_SERVER_ERROR) - console.error(e) - } - } - } +type GetEtapeEntrepriseDocumentsErrors = EffectDbQueryAndValidateErrors +export const getEtapeEntrepriseDocuments: RestNewGetCall<'/rest/etapes/:etapeId/entrepriseDocuments'> = ( + rootPipe +): Effect.Effect<EtapeEntrepriseDocument[], CaminoApiError<GetEtapeEntrepriseDocumentsErrors>> => + rootPipe.pipe( + Effect.flatMap(({ pool, user, params }) => getEntrepriseDocumentIdsByEtapeId({ titre_etape_id: params.etapeId }, pool, user)), + Effect.mapError(caminoError => + Match.value(caminoError.message).pipe( + 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', () => ({ + ...caminoError, + status: HTTP_STATUS.INTERNAL_SERVER_ERROR, + })), + Match.exhaustive + ) + ) + ) export const getEtapeDocuments = (pool: Pool) => diff --git a/packages/api/src/server/rest.ts b/packages/api/src/server/rest.ts index 23e4532552485033dae87612ffa7edaafa15383b..f0a6b4ee91aab4ff1e698a208beef4f68885ee7d 100644 --- a/packages/api/src/server/rest.ts +++ b/packages/api/src/server/rest.ts @@ -235,7 +235,7 @@ const restRouteImplementations: Readonly<{ [key in CaminoRestRoute]: Transform<k '/rest/etapes/:etapeIdOrSlug': { deleteCall: deleteEtape, getCall: getEtape, ...CaminoRestRoutes['/rest/etapes/:etapeIdOrSlug'] }, '/rest/etapes': { newPostCall: createEtape, newPutCall: updateEtape, ...CaminoRestRoutes['/rest/etapes'] }, '/rest/etapes/:etapeId/depot': { newPutCall: deposeEtape, ...CaminoRestRoutes['/rest/etapes/:etapeId/depot'] }, - '/rest/etapes/:etapeId/entrepriseDocuments': { getCall: getEtapeEntrepriseDocuments, ...CaminoRestRoutes['/rest/etapes/:etapeId/entrepriseDocuments'] }, + '/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/activites/:activiteId': { getCall: getActivite, putCall: updateActivite, deleteCall: deleteActivite, ...CaminoRestRoutes['/rest/activites/:activiteId'] }, diff --git a/packages/common/src/rest.ts b/packages/common/src/rest.ts index ddfe8a7f8795f1674fae86a06463a4ef8ce3b9c4..3d78d7505c51b7f69bdd3069b75750ca5054a9d5 100644 --- a/packages/common/src/rest.ts +++ b/packages/common/src/rest.ts @@ -220,7 +220,7 @@ export const CaminoRestRoutes = { '/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/entrepriseDocuments': { params: etapeIdParamsValidator, get: { output: z.array(etapeEntrepriseDocumentValidator) } }, + '/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 }) } }, '/rest/etapes': { diff --git a/packages/ui/src/components/entreprise/entreprise-api-client.ts b/packages/ui/src/components/entreprise/entreprise-api-client.ts index 112c9449f101b1ec15aaf8ed1c6b43461572ec70..c8acd52027fc577ca27a36779bb7a19f997717e9 100644 --- a/packages/ui/src/components/entreprise/entreprise-api-client.ts +++ b/packages/ui/src/components/entreprise/entreprise-api-client.ts @@ -13,7 +13,7 @@ export interface EntrepriseApiClient { creerEntreprise: (siren: Siren) => Promise<{ id: EntrepriseId } | CaminoError<string>> getEntreprise: (id: EntrepriseId) => Promise<EntrepriseType | CaminoError<string>> getEntrepriseDocuments: (id: EntrepriseId) => Promise<EntrepriseDocument[] | CaminoError<string>> - getEtapeEntrepriseDocuments: (etapeId: EtapeId) => Promise<EtapeEntrepriseDocument[]> + getEtapeEntrepriseDocuments: (etapeId: EtapeId) => Promise<EtapeEntrepriseDocument[] | CaminoError<string>> creerEntrepriseDocument: ( entrepriseId: EntrepriseId, entrepriseDocumentInput: UiEntrepriseDocumentInput, @@ -48,8 +48,8 @@ export const entrepriseApiClient: EntrepriseApiClient = { entrepriseId, }) }, - getEtapeEntrepriseDocuments: async (etapeId: EtapeId): Promise<EtapeEntrepriseDocument[]> => { - return getWithJson('/rest/etapes/:etapeId/entrepriseDocuments', { + getEtapeEntrepriseDocuments: async (etapeId: EtapeId): Promise<EtapeEntrepriseDocument[] | CaminoError<string>> => { + return newGetWithJson('/rest/etapes/:etapeId/entrepriseDocuments', { etapeId, }) }, diff --git a/packages/ui/src/components/etape/entreprises-documents-edit.stories.tsx b/packages/ui/src/components/etape/entreprises-documents-edit.stories.tsx index b48600665dbfdb9da44ee9fdbe246e8acd2313da..faddd92d365a5bc631d7a24a75fd21a77254a647 100644 --- a/packages/ui/src/components/etape/entreprises-documents-edit.stories.tsx +++ b/packages/ui/src/components/etape/entreprises-documents-edit.stories.tsx @@ -489,3 +489,32 @@ export const ArmDocumentOptionnel: StoryFn = () => ( etapeId={etapeIdValidator.parse('hello')} /> ) + +export const ErreurDeChargementDesDocuments: StoryFn = () => ( + <EntrepriseDocumentsEdit + tde={{ titreTypeId: 'arm', demarcheTypeId: 'oct', etapeTypeId: 'mod' }} + apiClient={{ + creerEntrepriseDocument: async (entrepriseId, entrepriseDocumentInput) => { + creerEntrepriseDocumentAction(entrepriseId, entrepriseDocumentInput) + + return { id: toEntrepriseDocumentId(toCaminoDate('2023-05-17'), 'arm', 'hash') } + }, + uploadTempDocument: async document => { + uploadTempDocumentAction(document) + return tempDocumentNameValidator.parse(new Date().toISOString()) + }, + getEntrepriseDocuments: () => new Promise(() => ({})), + getEtapeEntrepriseDocuments: async etapeId => { + getEtapeEntrepriseDocumentsAction(etapeId) + + return { + message: 'Ceci est une erreur', + detail: 'Ceci est un détail', + } + }, + }} + entreprises={[{ id: newEntrepriseId('id'), nom: 'nom entreprise' }]} + completeUpdate={completeUpdateAction} + etapeId={etapeIdValidator.parse('hello')} + /> +) diff --git a/packages/ui/src/components/etape/entreprises-documents-edit.stories_snapshots_ErreurDeChargementDesDocuments.html b/packages/ui/src/components/etape/entreprises-documents-edit.stories_snapshots_ErreurDeChargementDesDocuments.html new file mode 100644 index 0000000000000000000000000000000000000000..15093dbe0864df3b44c0d9b1f84a625c0cd93378 --- /dev/null +++ b/packages/ui/src/components/etape/entreprises-documents-edit.stories_snapshots_ErreurDeChargementDesDocuments.html @@ -0,0 +1,7 @@ +<div class="" style="display: flex; justify-content: center;"> + <!----> + <div class="fr-alert fr-alert--error" role="alert"> + <p class="fr-alert__title fr-h4">Ceci est une erreur</p>Ceci est un détail + </div> + <!----> +</div> \ No newline at end of file diff --git a/packages/ui/src/components/etape/entreprises-documents-edit.tsx b/packages/ui/src/components/etape/entreprises-documents-edit.tsx index 73f25a8180d01f99dd61bd587ea6cf6d86cb675a..4973f9c84737b628033678d36a1789b2fa0057be 100644 --- a/packages/ui/src/components/etape/entreprises-documents-edit.tsx +++ b/packages/ui/src/components/etape/entreprises-documents-edit.tsx @@ -45,19 +45,19 @@ export const EntrepriseDocumentsEdit = defineComponent<Props>(props => { const loadEtapeEntrepriseDocuments = async () => { etapeEntrepriseDocumentIds.value = { status: 'LOADING' } - try { - if (isNotNullNorUndefined(props.etapeId)) { - const etapeDocuments = await props.apiClient.getEtapeEntrepriseDocuments(props.etapeId) - etapeEntrepriseDocumentIds.value = { status: 'LOADED', value: etapeDocuments.map(({ id }) => id) } + if (isNotNullNorUndefined(props.etapeId)) { + const etapeDocuments = await props.apiClient.getEtapeEntrepriseDocuments(props.etapeId) + if ('message' in etapeDocuments) { + console.error('error', etapeDocuments.message) + etapeEntrepriseDocumentIds.value = { + status: 'NEW_ERROR', + error: etapeDocuments, + } } else { - etapeEntrepriseDocumentIds.value = { status: 'LOADED', value: [] } - } - } catch (e: any) { - console.error('error', e) - etapeEntrepriseDocumentIds.value = { - status: 'ERROR', - message: e.message ?? "Une erreur s'est produite", + etapeEntrepriseDocumentIds.value = { status: 'LOADED', value: etapeDocuments.map(({ id }) => id) } } + } else { + etapeEntrepriseDocumentIds.value = { status: 'LOADED', value: [] } } }