Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

5.5 KiB

PRD: PDF Generation Service Consolidation

Problem Statement

The invoice and quote PDF generation services evolved independently, resulting in two separate service files with nearly identical Puppeteer orchestration, storage/persist logic, and rendering pipelines. Both use the same shared template AST evaluator, renderer, and server-render infrastructure. This duplication increases maintenance burden — bug fixes (e.g., image inlining, base href for Puppeteer) must be applied in multiple places, and new entity types (e.g., proposals) would require yet another copy.

Goals

  1. Single PDF generation service that handles invoices, quotes, and documents through one unified class
  2. Eliminate duplicated Puppeteer orchestration (generatePDFBuffer) and storage logic (generateAndStore)
  3. Preserve all existing behavior: invoice workflow events, quote preview rendering, document HTML wrapping
  4. Delete the old server-side service (server/src/services/pdf-generation.service.ts) and the standalone quote service (quotePdfGenerationService.ts)
  5. Update all callers to use the consolidated service with backward-compatible aliases during transition
  6. Migrate tests to cover the consolidated service

Non-Goals

  • Renaming InvoiceTemplateAst and related "invoice"-prefixed types to entity-agnostic names (follow-up)
  • Adding invoice preview rendering (parity with quotes) — can be done later
  • Changing the template evaluation/rendering pipeline itself
  • Modifying the designer or workspace AST handling

Target Users

  • Developers maintaining the billing/PDF generation code
  • No user-facing changes — this is an internal refactor

Architecture

Before (3 services)

server/src/services/pdf-generation.service.ts     → invoices + documents (old location)
packages/billing/src/services/pdfGenerationService.ts  → invoices (new, partial)
packages/billing/src/services/quotePdfGenerationService.ts → quotes

After (1 service)

packages/billing/src/services/pdfGenerationService.ts → invoices + quotes + documents

Consolidated PDFGenerationService API

class PDFGenerationService {
  constructor(tenant: string)

  // Generate PDF buffer without storing
  generatePDF(options: { invoiceId?: string; quoteId?: string; documentId?: string; userId: string; templateAst?: InvoiceTemplateAst }): Promise<Buffer>

  // Generate and persist to storage + publish workflow event
  generateAndStore(options: PDFGenerationOptions): Promise<FileStore>

  // Quote-specific: render HTML+CSS for live preview panel
  renderQuotePreview(options: QuotePDFOptions): Promise<{ html: string; css: string; templateAst: InvoiceTemplateAst | null }>
}

Internal dispatch

  • getInvoiceHtml(invoiceId) — template resolution, DB fetch, adapter, evaluate, render
  • getQuoteHtml(options) — template resolution (standard/custom/tenant-default), DB fetch, adapter, evaluate, render
  • getDocumentHtml(documentId) — block content or markdown/text, wrap in minimal HTML
  • generatePDFBuffer(html, templateAst?) — Puppeteer browser pool, page.pdf()

Backward Compatibility

  • createQuotePDFGenerationService — deprecated alias for createPDFGenerationService
  • QuotePDFGenerationService type — deprecated alias for PDFGenerationService

Caller Migration

Caller Change
server/src/lib/api/services/InvoiceService.ts Already imports from @alga-psa/billing/services — no change needed
server/src/lib/jobs/handlers/invoiceEmailHandler.ts Already imports from @alga-psa/billing/services — no change needed
server/src/lib/jobs/handlers/invoiceZipHandler.ts Already imports from @alga-psa/billing/services — no change needed
packages/billing/src/actions/quoteActions.ts Already imports createPDFGenerationService — no change needed
packages/billing/src/actions/invoiceJobActions.ts Already imports from local service — no change needed
packages/client-portal/src/actions/client-billing.ts Verify import path
server/src/app/api/documents/download/[fileId]/route.ts Verify import path

Test Migration

Test File Status
packages/billing/src/actions/invoicePdfGenerationAstWiring.test.ts Update: reads source file — assertions still valid
server/src/services/pdf-generation.service.printSettings.test.ts Update: re-point mocks to @alga-psa/billing paths
packages/billing/tests/quote/quotePdfGenerationService.test.ts Update: import from consolidated service

Risks

  • Import path breakage: The old server/src/services/pdf-generation.service.ts may have undiscovered importers. Mitigated by grep/build verification.
  • Workflow event behavior change: Quotes now publish DOCUMENT_GENERATED events (previously they didn't). This is intentional but should be verified with downstream workflow handlers.

Acceptance Criteria

  1. Single PDFGenerationService class in packages/billing/src/services/pdfGenerationService.ts
  2. server/src/services/pdf-generation.service.ts deleted
  3. packages/billing/src/services/quotePdfGenerationService.ts deleted
  4. All callers compile and pass tests
  5. Invoice PDF generation works end-to-end (template resolution, rendering, storage, workflow events)
  6. Quote PDF generation works end-to-end (template resolution, rendering, storage, preview)
  7. Document PDF generation works end-to-end (block content, markdown, plain text)
  8. Backward-compatible aliases exported from services/index.ts