Skip to content

feat(quote-preview): add "Télécharger" button to every quote-preview modal#44

Open
AntoinePoindron wants to merge 2 commits intomainfrom
feat/quote-preview-download-button
Open

feat(quote-preview): add "Télécharger" button to every quote-preview modal#44
AntoinePoindron wants to merge 2 commits intomainfrom
feat/quote-preview-download-button

Conversation

@AntoinePoindron
Copy link
Copy Markdown
Collaborator

Summary

The "Voir le devis" modal already shows the same content the downloadable PDF renders (both go through _pdf_preview.html → the WeasyPrint-backed services.quote_pdf.render_quote_pdf), but only the caterer's editor exposed a download. This PR adds a Télécharger button to every modal that uses the preview, on both sides of the marketplace.

Validated end-to-end manually by @AntoinePoindron before opening this PR.

What changed

Surface Before After
caterer/requests/detail.html — single per-request modal opened from the right-hand recap card only ✕ close new Télécharger ghost-pill, hits the existing caterer.quote_pdf route
client/requests/detail.html — one modal per received quote (compare-3 mode can show up to 3) only ✕ close new Télécharger ghost-pill on each, hits the new client.quote_pdf route

Backend

  • New GET /client/requests/<request_id>/quote/<q_id>/pdf route in blueprints/client/requests.py. Mirrors the existing caterer.quote_pdf but the scope check goes the other way: the quote must belong to a request whose company_id == viewer.company_id. 404s on scope mismatch (rather than 403) so we don't leak the existence of a quote outside the viewer's perimeter.
  • Same _MAX_PDF_LINES = 500 cap as the caterer route — refuses WeasyPrint amplification on a corrupted or oversized row.
  • Reuses services.quote_pdf.render_quote_pdf(quote, qr, caterer) so the PDF byte stream is identical to what the caterer would download — no template drift between sides.

UI

Ghost-pill Télécharger button with a download lucide icon, sitting to the left of the close X in the modal header. Same compact style as the rest of the app's secondary-action buttons.

Test plan

  • 198 existing tests pass.
  • Manually validated: caterer side download from the per-request modal, client side download from each per-quote modal in a compare-3 request.
  • Once merged: smoke on staging — open a request as alice, confirm download for each received quote.

🤖 Generated with Claude Code

Antoine Poindron and others added 2 commits May 9, 2026 09:55
…modal

The "Voir le devis" modal already shows the same content the
downloadable PDF does (both go through `_pdf_preview.html` →
the WeasyPrint-backed `services.quote_pdf.render_quote_pdf`),
but only the caterer's editor exposed a download. Surface the
PDF download next to the close icon in every modal that uses
the preview, on both sides of the marketplace.

Two surfaces:
- caterer/requests/detail.html — single per-request modal
  ("Voir le devis" on the right-hand recap card). Wired to the
  existing `caterer.quote_pdf` route.
- client/requests/detail.html — one modal per received quote
  (mode comparateur 3 devis can show up to 3). Each "Télécharger"
  hits the new `client.quote_pdf` route.

Backend:
- new GET /client/requests/<request_id>/quote/<q_id>/pdf route in
  `blueprints/client/requests.py`. Mirrors `caterer.quote_pdf`
  but the scope check goes the other way: the quote must belong
  to a request whose company == viewer's company. 404s on
  scope mismatch (rather than 403) so we don't leak the
  existence of a quote outside the viewer's perimeter. Uses the
  same `_MAX_PDF_LINES = 500` cap as the caterer route to refuse
  WeasyPrint amplification on a corrupted/oversized row.
- reuses `services.quote_pdf.render_quote_pdf(quote, qr, caterer)`
  so the PDF byte stream is identical to what the caterer would
  download — no template drift between sides.

UI: ghost-pill "Télécharger" button with a download icon, sitting
to the left of the close X in the modal header. Same compact style
as the rest of the app's secondary-action buttons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to fix/client-hides-draft-quotes : the request-detail
listing now filters drafts at the DB level, but the new
client.quote_pdf route this PR introduces could still hand out a
brouillon PDF if a client guessed the quote ID directly. Add the
same `Quote.status != QuoteStatus.draft` filter to the SELECT so
the route 404s on a draft regardless of how it was reached.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant