Fiche Express Lot — Génération PDF

Summary

A Pro (or Admin) requests a formatted PDF sheet for a single Lot within a Programme. The system fetches lot and programme data, resolves an optional floor-plan image (embedded as a base64 data URI to bypass Puppeteer’s AppArmor sandbox), renders an HTML template, and converts it to PDF via Puppeteer. The resulting PDF is either streamed directly to the browser as a download, or attached to a transactional email sent on behalf of the Pro to a prospect’s address. A per-day email rate limit guards against abuse.

State Diagram

stateDiagram-v2
    state "Requête reçue" as RequestReceived
    state "Chargement données" as DataFetch
    state "Lot introuvable" as LotNotFound
    state "Résolution du plan" as PlanResolution
    state "Rendu HTML" as HtmlRender
    state "Génération PDF" as PdfGeneration
    state "Mode téléchargement" as DownloadMode
    state "Mode email" as EmailMode
    state "Vérification taille" as SizeCheck
    state "PDF trop lourd" as TooLarge
    state "Vérification quota" as RateLimitCheck
    state "Quota dépassé" as RateLimitExceeded
    state "Email envoyé" as EmailSent

    [*] --> RequestReceived
    RequestReceived --> DataFetch: request received
    DataFetch --> LotNotFound: lot missing
    LotNotFound --> [*]
    DataFetch --> PlanResolution: lot found
    PlanResolution --> HtmlRender: plan resolved or skipped
    HtmlRender --> PdfGeneration
    PdfGeneration --> DownloadMode: mode = download
    PdfGeneration --> EmailMode: mode = email
    DownloadMode --> [*]: PDF streamed to browser
    EmailMode --> SizeCheck
    SizeCheck --> TooLarge: PDF > 4 MB
    TooLarge --> [*]
    SizeCheck --> RateLimitCheck: PDF <= 4 MB
    RateLimitCheck --> RateLimitExceeded: daily limit hit
    RateLimitExceeded --> [*]
    RateLimitCheck --> EmailSent: limit OK
    EmailSent --> [*]

Steps

1. Authentication (Actor: system)

Every request passes through authenticateProOrAdmin. A valid Pro or Admin JWT is required.

Triggers: Incoming POST request to /:lotId/generate Outcome: request.proId or request.adminId populated; 401 if missing

2. Body Validation (Actor: system)

The request body is validated against generateFicheBodySchema. Required fields: selectedDispositifs (array of financing scheme keys), mode (download | email). When mode = email, email field is required.

Triggers: Auth passed Outcome: Typed body available; 400 EMAIL_REQUIRED if mode is email but email is absent

3. Data Fetch (Actor: system)

buildFicheExpressData({ lotId, proId }) queries the database for the lot, its parent programme, and the Pro’s profile (for branding). Admin callers pass proId = null and receive default SeaLion branding.

Triggers: Validation passed Outcome: Structured data object with lot, programme, and pro; 404 LOT_NOT_FOUND on failure

4. Floor Plan Resolution (Actor: system)

resolveLotPlan searches for a PNG floor plan matching the lot number, looking first in lot-level documents then in programme-level documentsVente. If found, the PNG is read from disk and base64-encoded into a data URI so Puppeteer can render it without filesystem access issues.

Triggers: Data fetch succeeded Outcome: planMatch object with pngDataUri, or null if no plan exists (non-blocking)

5. HTML Rendering (Actor: system)

renderFicheExpressHtml({ data, selectedDispositifs, planMatch }) produces a complete HTML page containing lot details, programme information, selected financing schemes, Pro branding, and the optional floor plan.

Triggers: Plan resolution complete Outcome: HTML string ready for PDF conversion

6. PDF Generation (Actor: system)

renderHtmlToPdf(html) launches Puppeteer, renders the HTML in a headless browser, and prints to PDF.

Triggers: HTML rendered Outcome: Buffer containing the PDF bytes

7a. Download Mode (Actor: pro)

The PDF buffer is sent directly in the HTTP response with Content-Type: application/pdf and Content-Disposition: attachment. Filename format: SeaLion_{Programme}_{LotN}_{YYYY-MM-DD}.pdf.

Triggers: mode = download Outcome: Browser downloads the PDF

7b. Email Mode (Actor: system)

The PDF size is checked against the 4 MB limit. Then the Pro’s daily email counter is checked and incremented via checkAndIncrementEmailLimit. sendEmail sends the PDF as an attachment from noreply@sealion.cacio.fr with Reply-To set to the Pro’s own email address. An optional custom message from the Pro is included in the email body.

Triggers: mode = email Outcome: Email delivered to prospect; { data: { sent: true } } returned

Error States

  • Lot not found → 404, LOT_NOT_FOUND
  • Email missing in email mode → 400, EMAIL_REQUIRED
  • PDF exceeds 4 MB in email mode → 413, PDF_TOO_LARGE — “Ce PDF est trop lourd pour être envoyé par email, téléchargez-le et envoyez-le manuellement”
  • Daily email rate limit reached → 429, RATE_LIMIT_EXCEEDED — “Limite d’envois journalière atteinte”
  • Floor plan PNG unreadable → non-fatal, logged, fiche rendered without plan
  • fiche-express-programme — same PDF pipeline applied to a full Programme instead of a single Lot
  • registration — prerequisite: Pro must be registered and active to access this route