Skip to content

PDFs

Location: dg_smart_pos_api/modules/Pdfs (directory is Pdfs on disk; PDFs in prose/config is the same module, the filesystem is case-insensitive locally so both resolve). Confirmed generation-only, no owned data: there is no Models/ directory at all, matching the earlier scan. The record of an export in progress (GeneratedPdfExport, with uuid, kind, status, payload_json, disk/path) is an App\Models class living in the main app, not in this module, and the “kind” catalog plus the actual validation/dependency wiring into other modules’ data lives in App\Services\Pdf\PdfExportSubmissionService, also outside the module. What the module itself owns is the thin async HTTP surface (PdfExportController), the actual rendering engine (A4PdfRenderer, AsyncPdfDocumentBuilder), and the Blade views/partials every export kind renders from.

None owned. App\Models\GeneratedPdfExport (a tenant-DB model, despite its App\Models namespace) is the manifest row the module’s controller reads and writes: uuid, idempotency_key, user_id, kind, workload_queue, status (pending/processing/completed/failed), payload_json, disk/path, download_filename, error_message, completed_at.

PdfExportController::store takes {kind, payload} and delegates entirely to PdfExportSubmissionService::submit(), which validates kind against a fixed allow-list (credit_statement, invoice_statement, debt_invoice, quotation, proforma, job_quotation, sale_receipt_a4, sale_receipt_thermal, day_closing, grn, lpo, sale_delivery_note_a4, invoice_delivery_note_a4, stock_taking_report, stock_taking_blank_sheet), validates/normalizes the payload for that kind, and dispatches App\Jobs\Pdf\ProcessPdfExportJob to actually render it. The controller itself never renders synchronously; it returns 200 immediately only if the service happens to complete inline, otherwise 202 with a poll_path. That submission service is what pulls in the module’s real dependencies: it references Modules\Jobs\Models\Job (for job_quotation), Modules\Inventory\Models\GrnHeader/GrnLine, Modules\Purchasing\Models\LpoHeader/LpoLine, and Modules\Sales\Models\Sale, which is why config/modules.php declares Pdfs as requiring Jobs, Inventory, Purchasing, and Sales.

show returns the current status/download_path/error_message for a uuid so a client can poll. download streams the file: it first tries a short-lived Redis byte cache (pdf_export_bytes:{id}:{path}, populated on the previous download if the file was under pdf.export_byte_cache_max_bytes, default 3MB) to avoid re-hitting R2 on a rapid “view, print, reprint” burst, and falls back to Storage::disk($export->disk)->response() on any cache miss or error, byte-for-byte identical to the non-cached path.

Both show and download call authorizeView, which only blocks the request if the export has an owning user_id that doesn’t match the caller; an export with user_id === null is viewable/downloadable by any authenticated user who has the uuid.

A4PdfRenderer and AsyncPdfDocumentBuilder (both in this module) aren’t just used by PdfExportController’s own job. Modules\ReceiptSettings\Services\ReceiptService calls A4PdfRenderer directly, and App\Jobs\WhatsApp\SendLpoWhatsAppDocumentJob calls AsyncPdfDocumentBuilder directly, both bypassing this module’s HTTP surface entirely and using it purely as a rendering library. A4PdfRenderer wraps Pdf::view($view, $data)->format('a4') and injects a shared chromium_footer_strip partial.

auth:sanctum, prefix apiv2, registered via modules/Pdfs/Routes/pdfs.php (required once from routes/tenant.php):

Method Path Controller method Route name
POST pdf/exports PdfExportController::store pdfs.exports.store
GET pdf/exports/{uuid} PdfExportController::show pdfs.exports.show
GET pdf/exports/{uuid}/download PdfExportController::download pdfs.exports.download

{uuid} is constrained to a UUID pattern on both show and download. Pdfs is listed in ModulesServiceProvider::TENANT_ROUTE_MODULES, and the single require in routes/tenant.php matches it correctly (no duplicate or missing registration, unlike what turned up in Jobs).

  • Jobs - job_quotation export kind reads Job directly; note Jobs’ own routes currently aren’t reachable at all, see that page
  • Inventory - grn export kind
  • Purchasing - lpo export kind
  • Sales - receipt/invoice export kinds
  • ReceiptSettings - ReceiptService calls this module’s A4PdfRenderer directly