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 ProjectFinancingRequestOwnerGuard (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/:id
analysis Projet blocked — réponse à une demande Projet Analyse documentRequestId DELETE …/analysis/document-request/:documentRequestId/document/:documentId
finalization Phase 2 — pièces KYB / signature finalizationCategory (signedCompanyStatutes, rib, kbis, beneficialOwnersDeclaration) schéma posé ; HTTP porteur à venir

GET …/documents ne liste aujourd'hui que les documents onboarding (listOnboardingByProjectId). Les documents analysis sont exposés via GET …/analysis/status ; finalization sera branché avec l'écran collecte.

La suppression onboarding vs analyse partage le même service : documentType choisit le guard (project-not-draft vs project-not-blocked).

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

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

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

Méthode Path Fenêtre Réponse
DELETE /documents/:documentId Onboarding, projet draft 200 (corps vide)
DELETE /analysis/document-request/:documentRequestId/document/:documentId Analyse, projet blocked { documentRequestId, documents[] } à jour

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).

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
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