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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
5.5 KiB
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
- Single PDF generation service that handles invoices, quotes, and documents through one unified class
- Eliminate duplicated Puppeteer orchestration (
generatePDFBuffer) and storage logic (generateAndStore) - Preserve all existing behavior: invoice workflow events, quote preview rendering, document HTML wrapping
- Delete the old server-side service (
server/src/services/pdf-generation.service.ts) and the standalone quote service (quotePdfGenerationService.ts) - Update all callers to use the consolidated service with backward-compatible aliases during transition
- Migrate tests to cover the consolidated service
Non-Goals
- Renaming
InvoiceTemplateAstand 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, rendergetQuoteHtml(options)— template resolution (standard/custom/tenant-default), DB fetch, adapter, evaluate, rendergetDocumentHtml(documentId)— block content or markdown/text, wrap in minimal HTMLgeneratePDFBuffer(html, templateAst?)— Puppeteer browser pool, page.pdf()
Backward Compatibility
createQuotePDFGenerationService— deprecated alias forcreatePDFGenerationServiceQuotePDFGenerationServicetype — deprecated alias forPDFGenerationService
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.tsmay have undiscovered importers. Mitigated by grep/build verification. - Workflow event behavior change: Quotes now publish
DOCUMENT_GENERATEDevents (previously they didn't). This is intentional but should be verified with downstream workflow handlers.
Acceptance Criteria
- Single
PDFGenerationServiceclass inpackages/billing/src/services/pdfGenerationService.ts server/src/services/pdf-generation.service.tsdeletedpackages/billing/src/services/quotePdfGenerationService.tsdeleted- All callers compile and pass tests
- Invoice PDF generation works end-to-end (template resolution, rendering, storage, workflow events)
- Quote PDF generation works end-to-end (template resolution, rendering, storage, preview)
- Document PDF generation works end-to-end (block content, markdown, plain text)
- Backward-compatible aliases exported from
services/index.ts