feat(pdf): cache the PO PDF per vendor email (reuse copy, refresh 7-day link) #128

Merged
shad0w merged 1 commit from fix/po-pdf-cache into master 2026-06-24 09:47:59 +00:00
Owner

Adds caching to "Email PO to vendor" so the same PO reuses its exported PDF and only refreshes the download-link timer.

Before

Every send re-rendered the PO via PdfService and re-uploaded to R2 under a timestamped key — wasteful (full Chromium render each click) and it orphaned a fresh object every time.

Now

  • The PDF is stored at a deterministic per-PO keybuildPoPdfKeypo-pdf/<poId>/<slug>.pdf (no timestamp).
  • On each send, statObject(key) does a lightweight HEAD/stat (no body transfer):
    • copy exists and is ≥ po.updatedAt → reuse it, no re-render / no re-upload, and mint a fresh presigned URL → the 7-day timer restarts.
    • no copy yet, or PO changed since → re-render + re-upload (so an edited PO never emails a stale PDF).

So repeat sends for the same unchanged PO are cheap and always hand out a link valid for another 7 days; a changed PO regenerates.

Verification

  • tsc clean.
  • email-vendor integration suite 8 passed, including two new cases:
    • second send reuses the cached PDF (render/upload called once across two sends) and re-presigns each time (link minted twice).
    • a PO changed since the cached copy re-renders.

Independent of the middleware fix (#127) — different files; both are needed for "Email to vendor" to work well on prod.

🤖 Generated with Claude Code

Adds caching to "Email PO to vendor" so the same PO reuses its exported PDF and only refreshes the download-link timer. ## Before Every send re-rendered the PO via PdfService and re-uploaded to R2 under a **timestamped** key — wasteful (full Chromium render each click) and it orphaned a fresh object every time. ## Now - The PDF is stored at a **deterministic per-PO key** — `buildPoPdfKey` → `po-pdf/<poId>/<slug>.pdf` (no timestamp). - On each send, `statObject(key)` does a lightweight HEAD/stat (no body transfer): - **copy exists and is ≥ `po.updatedAt`** → reuse it, **no re-render / no re-upload**, and mint a **fresh presigned URL** → the 7-day timer restarts. - **no copy yet, or PO changed since** → re-render + re-upload (so an edited PO never emails a stale PDF). So repeat sends for the same unchanged PO are cheap and always hand out a link valid for another 7 days; a changed PO regenerates. ## Verification - `tsc` clean. - `email-vendor` integration suite **8 passed**, including two new cases: - second send reuses the cached PDF (render/upload called once across two sends) and re-presigns each time (link minted twice). - a PO changed since the cached copy re-renders. Independent of the middleware fix (#127) — different files; both are needed for "Email to vendor" to work well on prod. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
shad0w added 1 commit 2026-06-24 09:31:44 +00:00
feat(pdf): cache the PO PDF per vendor email, refresh only the link timer
All checks were successful
PR checks / checks (pull_request) Successful in 47s
PR checks / integration (pull_request) Successful in 31s
a9fd927c1f
Previously every "Email to vendor" click re-rendered the PO via PdfService and
re-uploaded to R2 under a timestamped key — wasteful, and it orphaned a new
object each time.

Now the PDF is stored at a deterministic per-PO key (buildPoPdfKey →
po-pdf/<poId>/<slug>.pdf). On each send, statObject() checks for an existing
copy: if it exists and is at least as new as the PO's updatedAt, it's reused
(no re-render, no re-upload) and only a fresh presigned URL is minted —
refreshing the 7-day download timer. It re-renders only when there's no copy
yet or the PO changed since the cached one (so an edited PO never emails a
stale PDF).

- lib/storage.ts: buildPoPdfKey (deterministic) + statObject (HEAD/stat, no
  body transfer; null when absent).
- email-actions.ts: reuse-or-render decision keyed on updatedAt; always
  re-presign.
- Tests: +2 (reuse-on-second-send-only-refreshes-link, re-render-when-changed).
  email-vendor suite 8 green; tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shad0w merged commit 262ae5830b into master 2026-06-24 09:47:59 +00:00
Sign in to join this conversation.
No description provided.