feat(po): email PO to vendor � PDF link in an Outlook draft (#14) #101

Merged
shad0w merged 3 commits from feat/email-po-to-vendor into master 2026-06-23 21:55:11 +00:00
Owner

Closes #14.

Summary

Adds an Email to vendor button on the PO detail. It's available once the PO is approved (MGR_APPROVED → CLOSED) — and again after payment — when the vendor has a primary-contact email. Clicking it opens an Outlook draft addressed to that contact with a time-limited PDF download link in the body. The user reviews and sends it.

Per the answered design question, this uses the R2 link approach (a mailto: can't attach a file): render the PO to a real PDF, store it, and link it.

Pipeline

  1. prepareVendorEmail(poId) (po/[id]/email-actions.ts) — auth + view check + approved-status + vendor-email checks.
  2. renderPoPdf → PdfService (new standalone Express + Playwright microservice, the GstService/EpfoService pattern) renders the existing /api/po/[id]/export?format=pdf&pdf=1 page to a real PDF via headless Chromium.
  3. Upload to R2 (po-pdf/…) → presigned 7-day download URL.
  4. Returns a mailto: (recipient + subject + body-with-link); the client opens it.

Notes

  • No DB model or migration.
  • The export route now accepts a server-only svc token (PDF_SERVICE_TOKEN) so PdfService can fetch the page without a user session (read-only, PDF only), and pdf=1 strips the on-screen print button + window.print() auto-trigger. PdfService is SSRF-guarded (shared token + optional ALLOWED_ORIGIN).
  • Gated by PDF_SERVICE_URL / PDF_SERVICE_TOKEN; if unset, the action returns a friendly "not configured" error (the rest of the app is unaffected).

Deploy

Run PdfService alongside the app (like GstService/EpfoService): cd PdfService && npm install && npm run dev (port 3005). Set PDF_SERVICE_URL, PDF_SERVICE_TOKEN, and (if PdfService can't reach the app via NEXTAUTH_URL) APP_INTERNAL_URL. In production, R2 must be configured so the presigned link is reachable by the external vendor.

Tests

  • email-vendor.test.ts (6): builds the mailto to the primary contact with the link, available post-payment, and refuses non-approved POs / missing vendor email / unconfigured service / unauthenticated callers (PdfService + storage mocked).
  • Full integration suite green (248), tsc --noEmit clean (app + PdfService).

🤖 Generated with Claude Code

Closes #14. ## Summary Adds an **Email to vendor** button on the PO detail. It's available once the PO is **approved** (`MGR_APPROVED` → `CLOSED`) — and again after payment — when the vendor has a primary-contact email. Clicking it opens an **Outlook draft** addressed to that contact with a **time-limited PDF download link** in the body. The user reviews and sends it. Per the answered design question, this uses the **R2 link** approach (a `mailto:` can't attach a file): render the PO to a real PDF, store it, and link it. ## Pipeline 1. `prepareVendorEmail(poId)` (`po/[id]/email-actions.ts`) — auth + view check + approved-status + vendor-email checks. 2. `renderPoPdf` → **PdfService** (new standalone Express + Playwright microservice, the GstService/EpfoService pattern) renders the existing `/api/po/[id]/export?format=pdf&pdf=1` page to a real PDF via headless Chromium. 3. Upload to R2 (`po-pdf/…`) → presigned **7-day** download URL. 4. Returns a `mailto:` (recipient + subject + body-with-link); the client opens it. ## Notes - **No DB model or migration.** - The export route now accepts a server-only `svc` token (`PDF_SERVICE_TOKEN`) so PdfService can fetch the page without a user session (read-only, PDF only), and `pdf=1` strips the on-screen print button + `window.print()` auto-trigger. PdfService is SSRF-guarded (shared token + optional `ALLOWED_ORIGIN`). - Gated by `PDF_SERVICE_URL` / `PDF_SERVICE_TOKEN`; if unset, the action returns a friendly "not configured" error (the rest of the app is unaffected). ## Deploy Run **PdfService** alongside the app (like GstService/EpfoService): `cd PdfService && npm install && npm run dev` (port 3005). Set `PDF_SERVICE_URL`, `PDF_SERVICE_TOKEN`, and (if PdfService can't reach the app via `NEXTAUTH_URL`) `APP_INTERNAL_URL`. In production, R2 must be configured so the presigned link is reachable by the external vendor. ## Tests - `email-vendor.test.ts` (6): builds the mailto to the primary contact with the link, available post-payment, and refuses non-approved POs / missing vendor email / unconfigured service / unauthenticated callers (PdfService + storage mocked). - Full integration suite green (248), `tsc --noEmit` clean (app + PdfService). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
shad0w added 1 commit 2026-06-23 21:16:09 +00:00
feat(po): email PO to vendor — PDF link in an Outlook draft (#14)
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 31s
3edd1ffcc5
Adds an "Email to vendor" button on the PO detail (available once approved,
through CLOSED, and again after payment) that opens an Outlook draft addressed
to the vendor's primary contact with a time-limited PDF download link.

Since mailto: can't attach files, the PDF is rendered and stored, and the draft
carries a link (the approach chosen for this issue):

- PdfService/: new standalone Express + Playwright microservice (GstService/
  EpfoService pattern) — POST /pdf { url } renders a page to a real PDF via
  headless Chromium. SSRF-guarded (shared token + optional origin allowlist).
- export route: accepts a server-only `svc` token (PDF_SERVICE_TOKEN) so
  PdfService can fetch /api/po/[id]/export?format=pdf without a user session;
  `pdf=1` drops the print button + window.print() auto-trigger.
- lib/pdf-service.ts renderPoPdf(); prepareVendorEmail() server action renders →
  uploads to R2 (po-pdf/…) → presigns a 7-day link → returns a mailto draft.
- po-detail: EmailVendorButton, shown when approved + vendor has a contact email.
- Gated by PDF_SERVICE_URL/PDF_SERVICE_TOKEN; friendly error if unconfigured.
- No DB model/migration. Tests: prepareVendorEmail (6, PdfService/storage mocked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shad0w added 1 commit 2026-06-23 21:20:34 +00:00
Merge remote-tracking branch 'origin/master' into feat/email-po-to-vendor
All checks were successful
PR checks / checks (pull_request) Successful in 43s
PR checks / integration (pull_request) Successful in 30s
8206483f88
# Conflicts:
#	App/app/api/po/[id]/export/route.ts
shad0w added 1 commit 2026-06-23 21:55:05 +00:00
Merge branch 'master' into feat/email-po-to-vendor
All checks were successful
PR checks / checks (pull_request) Successful in 53s
PR checks / integration (pull_request) Successful in 32s
470523a7a6
shad0w merged commit 7fe46c2448 into master 2026-06-23 21:55:11 +00:00
Sign in to join this conversation.
No description provided.