Skip to main content

PDF Module β€” Architecture Reference

Last Updated: 2026-03-24 Module: apps/backend/src/pdf/ Pattern: Hexagonal Architecture (Ports & Adapters)


Module Structure​

pdf/
β”œβ”€β”€ pdf.module.ts # NestJS module definition
β”œβ”€β”€ application/
β”‚ β”œβ”€β”€ dtos/
β”‚ β”‚ β”œβ”€β”€ pdf-controller.dto.ts # PDF generation DTOs
β”‚ β”‚ └── template.dto.ts # Template management DTOs
β”‚ β”œβ”€β”€ ports/
β”‚ β”‚ β”œβ”€β”€ pdf-generator.port.ts # PDF generation contract
β”‚ β”‚ └── template-renderer.port.ts # Template rendering contract
β”‚ β”œβ”€β”€ services/
β”‚ β”‚ └── template-resolver.service.ts # Template resolution chain
β”‚ β”œβ”€β”€ transformers/
β”‚ β”‚ β”œβ”€β”€ sale-data.transformer.ts # (21 transformers, one per document type)
β”‚ β”‚ └── ...
β”‚ └── use-cases/
β”‚ β”œβ”€β”€ base-generate-pdf.use-case.ts # Abstract base for all PDF generation
β”‚ β”œβ”€β”€ generate-sale-pdf.use-case.ts # (21 concrete use cases)
β”‚ β”œβ”€β”€ upload-template.use-case.ts # Template CRUD use cases
β”‚ β”œβ”€β”€ update-template.use-case.ts
β”‚ β”œβ”€β”€ delete-template.use-case.ts
β”‚ β”œβ”€β”€ get-templates.use-case.ts
β”‚ β”œβ”€β”€ test-template.use-case.ts
β”‚ β”œβ”€β”€ preview-pdf.use-case.ts
β”‚ β”œβ”€β”€ generate-custom-pdf.use-case.ts
β”‚ └── *-location-template-config.use-case.ts # Location config CRUD
β”œβ”€β”€ domain/
β”‚ β”œβ”€β”€ exceptions/
β”‚ β”‚ └── template.exceptions.ts # TemplateNotFoundError, etc.
β”‚ β”œβ”€β”€ ports/
β”‚ β”‚ β”œβ”€β”€ template-repository.port.ts # Repository contract + types
β”‚ β”‚ β”œβ”€β”€ location-template-config-repository.port.ts
β”‚ β”‚ └── template-audit.port.ts # Audit logging contract
β”‚ β”œβ”€β”€ services/
β”‚ β”‚ └── template-validator.service.ts # Pure domain validation (no deps)
β”‚ └── value-objects/
β”‚ └── pdf-options.vo.ts # Page size, margins, orientation
└── infrastructure/
β”œβ”€β”€ adapters/
β”‚ β”œβ”€β”€ puppeteer-pdf-generator.adapter.ts # Headless Chrome PDF gen (retry logic)
β”‚ β”œβ”€β”€ handlebars-template-renderer.adapter.ts # Handlebars compilation
β”‚ β”œβ”€β”€ file-system-pdf-storage.adapter.ts # File system storage
β”‚ β”œβ”€β”€ template-audit.adapter.ts # Kysely-based audit logging
β”‚ └── template-cache.adapter.ts # LRU cache for compiled templates
β”œβ”€β”€ controllers/
β”‚ β”œβ”€β”€ pdf.controller.ts # POST /pdf/generate, /pdf/preview
β”‚ β”œβ”€β”€ template.controller.ts # CRUD /pdf/templates/*
β”‚ └── location-template-config.controller.ts # CRUD /pdf/location-template-configs/*
β”œβ”€β”€ queues/
β”‚ └── preview-generation.processor.ts # BullMQ async preview generation
β”œβ”€β”€ repositories/
β”‚ β”œβ”€β”€ template.repository.ts # Kysely impl of TemplateRepositoryPort
β”‚ └── location-template-config.repository.ts
└── templates/
└── *.hbs # 18 Handlebars file-based templates (legacy)

Dependency Flow​

Controllers β†’ Use Cases β†’ Application Services β†’ Domain Ports
↑
Infrastructure Adapters β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

All cross-layer dependencies point inward toward the domain:

  • Domain: Zero framework dependencies. Ports (interfaces), exceptions, value objects, pure validators.
  • Application: Orchestration. Use cases depend on domain ports via injection. Contains DTOs, transformers, and the template resolver service.
  • Infrastructure: Concrete implementations. Adapters implement domain ports. Controllers handle HTTP. Repositories use Kysely.

Key Design Decisions​

Template Resolution Chain​

TemplateResolverService resolves templates in priority order:

  1. Explicit template ID β€” caller passes a specific template UUID
  2. Location-specific β€” location_template_config for the location + document type
  3. Business-level β€” default template for the business + document type
  4. System-level β€” global system template for the document type
  5. Fallback β€” file-based Handlebars template from infrastructure/templates/

Base Use Case Pattern​

All 21 PDF generation use cases extend BaseGeneratePdfUseCase<TEntity>:

  • Subclasses implement: getDocumentType(), getFallbackTemplateName(), getEntityBusinessId(), generateFilename()
  • Base class handles: template resolution, caching, PDF generation, error handling
  • Each use case has a paired DataTransformer that maps the entity to template data

Port Bindings (pdf.module.ts)​

Injection TokenConcrete Implementation
"PdfGeneratorPort"PuppeteerPdfGeneratorAdapter
"TemplateRendererPort"HandlebarsTemplateRendererAdapter
"PdfStoragePort"FileSystemPdfStorageAdapter
TEMPLATE_REPOSITORY_PORTTemplateRepository
LOCATION_TEMPLATE_CONFIG_REPOSITORY_PORTLocationTemplateConfigRepository
TEMPLATE_AUDIT_PORTTemplateAuditAdapter

Puppeteer Retry Logic​

PuppeteerPdfGeneratorAdapter uses executeWithRetry() (max 3 attempts) with exponential backoff for both generatePdf and generatePdfFromUrl. Retryable errors: protocol errors, target closed, navigation timeouts.

Template Cache​

TemplateCacheAdapter uses an LRU cache (100 entries) for compiled Handlebars templates. Cache key = template UUID. Invalidated on template update/delete.


API Endpoints​

PDF Generation (PdfController β€” /pdf)​

MethodPathDescription
POST/pdf/generateGenerate PDF from stored template + data
POST/pdf/generate-from-htmlGenerate PDF from raw HTML
POST/pdf/previewPreview template as rendered HTML
GET/pdf/template-filesList file-based templates (legacy)
POST/pdf/templates/clear-cacheClear file template cache
POST/pdf/templates/:templateId/reloadReload specific template
GET/pdf/healthService health check

Template Management (TemplateController β€” /pdf/templates)​

MethodPathDescription
POST/pdf/templates/uploadUpload new template (rate limited: 10/min)
GET/pdf/templatesList templates with pagination + search
GET/pdf/templates/availableAll templates (business + system)
GET/pdf/templates/healthHealth check with cache stats (public)
GET/pdf/templates/cache/statsCache statistics (public)
GET/pdf/templates/variables/:documentTypeTemplate variables for document type
GET/pdf/templates/:idGet template by ID
PATCH/pdf/templates/:idUpdate template
DELETE/pdf/templates/:idSoft-delete template
POST/pdf/templates/:id/testTest template with sample data β†’ PDF
POST/pdf/templates/testTest template (alt endpoint)
POST/pdf/templates/previewPreview template rendering as HTML
GET/pdf/templates/:id/auditAudit history for template
POST/pdf/templates/cache/clearClear all template caches

Location Template Config (LocationTemplateConfigController β€” /pdf/location-template-configs)​

MethodPathDescription
POST/pdf/location-template-configsCreate config (rate limited: 20/min)
GET/pdf/location-template-configsGet configs by location (query: locationId)
GET/pdf/location-template-configs/by-document-typeGet by location + doc type
GET/pdf/location-template-configs/:idGet config by ID
PATCH/pdf/location-template-configs/:idUpdate config (rate limited: 30/min)
DELETE/pdf/location-template-configs/:idDelete config (rate limited: 20/min)

Supported Document Types​

All types defined in DocumentType enum (packages/global/enums/document-type.enums.ts):

sale, purchase, purchase_order, quote, cash_register_session, cash_register_z_report, accounts_receivable_receipt, accounts_payable_payment, service_booking, inventory_adjustment, transfer_request, transfer_dispatch_note, transfer_goods_receipt, inventory_transfer, contractor_assignment, goods_received_note, material_consumption, production_run, credit_note, debit_note, cancellation


Bruno API Collections​

API collections for testing are at: api-client/flowpos/collections/pdf/

pdf/
β”œβ”€β”€ opencollection.yml
β”œβ”€β”€ Generate PDF from Template.yml
β”œβ”€β”€ Preview HTML (No PDF Generation).yml
β”œβ”€β”€ Generate PDF from HTML Content.yml
β”œβ”€β”€ Check health.yml
β”œβ”€β”€ ...
β”œβ”€β”€ templates/
β”‚ β”œβ”€β”€ folder.yml
β”‚ β”œβ”€β”€ templates.yml (list)
β”‚ β”œβ”€β”€ template - upload.yml
β”‚ β”œβ”€β”€ template - get by id.yml
β”‚ β”œβ”€β”€ template - update.yml
β”‚ β”œβ”€β”€ template - delete.yml
β”‚ β”œβ”€β”€ template - test.yml
β”‚ β”œβ”€β”€ template - test (alt).yml
β”‚ β”œβ”€β”€ template - preview.yml
β”‚ β”œβ”€β”€ template - available.yml
β”‚ β”œβ”€β”€ template - variables.yml
β”‚ β”œβ”€β”€ template - audit.yml
β”‚ β”œβ”€β”€ health.yml
β”‚ β”œβ”€β”€ cache stats.yml
β”‚ └── Clear cache.yml
└── location-template-configs/
β”œβ”€β”€ folder.yml
β”œβ”€β”€ create config.yml
β”œβ”€β”€ get by location.yml
β”œβ”€β”€ get by location and type.yml
β”œβ”€β”€ get by id.yml
β”œβ”€β”€ update config.yml
└── delete config.yml