Aller au contenu

Project Documents

Upload S3, checklist onboarding, compléments d'analyse et validation de l'étape Documents. Source : project-document.controller.ts.

Tous les endpoints porteur requièrent ProjectFinancingRequestUserGuard (session Better Auth + propriété du :projectId).

Modèle documentType

Chaque ligne project_financing_request_document porte un documentType qui pilote les règles métier (suppression, lecture, lien analyse). Schéma : project-financing-request-document.schema.ts.

Type Quand Champs spécifiques Endpoints porteur actuels
onboarding Projet draft — étape Documents phase 1 — GET …/documents, upload presigné, DELETE …/documents/:documentId
analysis Projet blocked — réponse à une demande Projet Analyse documentRequestId DELETE …/documents/:documentId (même route que onboarding ; guard project-not-blocked)
finalization Projet finalization — pièces KYB / collecte finalizationCategory (signedCompanyStatutes, rib, kbis, beneficialOwnersDeclaration) POST …/documents/request-upload + confirm-upload avec context: { type: 'finalization', category } ; DELETE …/documents/:documentId ; état agrégé via GET …/finalization

GET …/documents ne liste que les documents onboarding (listOnboardingByProjectId). Les documents analysis sont exposés via GET …/analysis/status. Les documents finalization sont exposés via GET …/finalization (une entrée par catégorie, last-wins à la re-upload).

La suppression onboarding / analyse / finalization partage le même endpoint DELETE …/documents/:documentId : documentType choisit le guard (project-not-draft, project-not-blocked, finalization-already-completed).

GET /project-financing-request/project/:projectId/documents

Liste les fichiers du projet avec URL de téléchargement presignée, plus l'état des checklists :

{
  "documents": [
    {
      "id": "uuid",
      "name": "business-plan.pdf",
      "mimeType": "application/pdf",
      "size": 204800,
      "downloadUrl": "https://…",
      "comment": { "value": "…", "createdAt": "…" },
      "createdAt": "…"
    }
  ],
  "onboarding": {
    "expectedDocuments": { "companyKbis": true, "worksQuote": false }
  },
  "analysis": {
    "missingDocumentsFromAnalysis": { "taxReturn": true }
  }
}
  • onboarding.expectedDocuments : renvoyé uniquement en draft (sinon {}). true = encore attendu, false = coché par le porteur.
  • analysis.missingDocumentsFromAnalysis : renvoyé uniquement en blocked (sinon {}). Même sémantique booléenne pour les noms poussés par Projet Analyse.

Upload S3 (presigned)

Même pattern que les images de présentation — le binaire ne transite pas par l'API.

Méthode Path Rôle
POST /documents/request-upload URLs PUT presignées (files[] : mime, size)
POST /documents/confirm-upload Vérifie HeadObject + insère les lignes document

Corps confirm-upload : discriminant context (onboarding | analysis + documentRequestId | finalization + category). Voir projectDocumentUploadContextSchema dans @bricks-common/api-communication-project-financing.

Préfixe S3 : projects/${projectId}/documents/${uuidv7}.${ext}.

Finalization : une seule pièce par confirm-upload ; re-upload sur une catégorie soft-delete la précédente. Refusé si le dossier n'est pas en finalization — détail dans project-finalization.md.

Après confirm-upload, si l'étape Documents était completed en draft, elle repasse en draft (réouverture pour re-validation).

Commentaires sur un fichier

Méthode Path Corps
POST /documents/:documentId/comment { "value": "…" }
DELETE /documents/:documentId/comment —

Même règle de réouverture de l'étape si elle était completed.

Supprimer un fichier

Endpoint unique : DELETE /project-financing-request/:projectId/documents/:documentId.

Fenêtre Guard Réponse 200
Onboarding, projet draft project-not-draft sinon { documentRequestId: null, documents[] } — liste onboarding restante
Analyse, projet blocked project-not-blocked sinon { documentRequestId, documents[] } — fichiers de la demande
Finalization, projet finalization project-not-in-finalization sinon { documentRequestId: null, documents[] } — pièces finalization restantes (lu via GET …/finalization)

Soft-delete : la ligne reste en base avec deletedAt posé (audit), exposé via la colonne générée deletedAt_view, et tous les reads porteur l'excluent (deletedAt_view IS NULL). L'objet S3 est retiré via deleteObject après commit (best-effort). L'ordre commit-puis-S3 est délibéré : un crash entre les deux laisse un objet S3 orphelin (invisible, aucun read ne le référence), jamais un document vivant pointant vers un fichier absent. L'orphelin est inerte ; une règle de cycle de vie S3 pourra le balayer (non implémentée, dette assumée). L'opération est idempotente : re-supprimer un document déjà supprimé ne réécrit rien et ne rappelle pas S3.

Un document refusé par l'analyse (isRefused) reste supprimable. Côté onboarding, supprimer le dernier fichier peut rouvrir l'étape completed (même règle que le commentaire). Finalization : la suppression explicite est l'alternative au re-upload last-wins sur une catégorie.

Checklist onboarding (phase 1, draft uniquement)

PUT /documents/base-document-list/check — corps { "documentKey": "…", "checked": true | false }.

Persiste expectedDocuments[key] = !checked. Modifier la checklist réouvre l'étape si elle était completed.

Clés : voir README — document keys.

Compléments d'analyse (blocked)

Porteur — cocher les pièces manquantes

PUT /documents/analysis-documents/check — corps { "documentName": "…", "checked": true | false }.

Uniquement en blocked. Persiste missingDocumentsFromAnalysis[name] = !checked.

Porteur — valider l'étape après upload

POST /documents/validate — vide missingDocumentsFromAnalysis et notifie Projet Analyse (notifyProjectUpdated, reason: 'missing-documents-provided'). Projet doit être blocked.

Projet Analyse — pousser la liste des manquants

PUT /internal/projet-analyse/project/:projectId/missing-documents (API key projet-analyse).

Corps : { "documentNames": ["taxReturn", "…"] }. Écrase l'ensemble en préservant l'état coché des noms déjà connus. Passe le projet en blocked si besoin.

Compléter l'étape Documents (phase 1)

PUT /documents/complete — marque documentsStep.status = 'completed'.

Prérequis :

  • Projet en draft
  • documentsStep.status !== 'unavailable' (la présentation doit avoir été complétée au moins une fois)
  • Au moins un fichier uploadé (documents-step-empty sinon)

La checklist expectedDocuments guide l'UX mais n'est pas un prérequis technique à la complétion.

Refus d'un document par Projet Analyse

PUT /internal/projet-analyse/project/:projectId/document/:documentId/refuse — voir Project Analysis.

Expose côté porteur via isRefused: true sur chaque document dans GET …/analysis/status et dans les documentRequests[].documents.

Erreurs courantes

Code HTTP Cause
project-not-draft 409 Action réservée au brouillon
project-not-blocked 409 Action réservée aux compléments d'analyse
documents-step-unavailable 409 Présentation pas encore complétée
documents-step-empty 409 Aucun fichier à la complétion
project-not-in-finalization 409 Suppression/upload finalization refusé — le dossier n'est plus en finalization
s3-key-invalid-prefix 400 Clé S3 hors du préfixe projet
analysis-missing-document-not-found 404 Nom inconnu dans la liste Analyse

Liens