-
Closes two open wiring gaps left by the P1.9 and P1.8 chunks — `PluginContext.files` and `PluginContext.reports` both previously threw `UnsupportedOperationException` because the host's `DefaultPluginContext` never received the concrete beans. This commit plumbs both through and exercises them end-to-end via a new printing-shop plug-in endpoint that generates a quote PDF, stores it in the file store, and returns the file handle. With this chunk the reference printing-shop plug-in demonstrates **every extension seam the framework provides**: HTTP endpoints, JDBC, metadata YAML, i18n, BPMN + TaskHandlers, JobHandlers, custom fields on core entities, event publishing via EventBus, ReportRenderer, and FileStorage. There is no major public plug-in surface left unexercised. ## Wiring: DefaultPluginContext + VibeErpPluginManager - `DefaultPluginContext` gains two new constructor parameters (`sharedFileStorage: FileStorage`, `sharedReportRenderer: ReportRenderer`) and two new overrides. Each is wired via Spring — they live in platform-files and platform-reports respectively, but platform-plugins only depends on api.v1 (the interfaces) and NOT on those modules directly. The concrete beans are injected by Spring at distribution boot time when every `@Component` is on the classpath. - `VibeErpPluginManager` adds `private val fileStorage: FileStorage` and `private val reportRenderer: ReportRenderer` constructor params and passes them through to every `DefaultPluginContext` it builds per plug-in. The `files` and `reports` getters in api.v1 `PluginContext` still have their default-throw backward-compat shim — a plug-in built against v0.8 of api.v1 loading on a v0.7 host would still fail loudly at first call with a clear "upgrade to v0.8" message. The override here makes the v0.8+ host honour the interface. ## Printing-shop reference — quote PDF endpoint - New `resources/reports/quote-template.jrxml` inside the plug-in JAR. Parameters: plateCode, plateName, widthMm, heightMm, status, customerName. Produces a single-page A4 PDF with a header, a table of plate attributes, and a footer. - New endpoint `POST /api/v1/plugins/printing-shop/plates/{id}/generate-quote-pdf`. Request body `{"customerName": "..."}`, response: `{"plateId", "plateCode", "customerName", "fileKey", "fileSize", "fileContentType", "downloadUrl"}` The handler does ALL of: 1. Reads the plate row via `context.jdbc.queryForObject(...)` 2. Loads the JRXML from the PLUG-IN's own classloader (not the host classpath — `this::class.java.classLoader .getResourceAsStream("reports/quote-template.jrxml")` — so the host's built-in `vibeerp-ping-report.jrxml` and the plug-in's template live in isolated namespaces) 3. Renders via `context.reports.renderPdf(template, data)` — uses the host JasperReportRenderer under the hood 4. Persists via `context.files.put(key, contentType, content)` under a plug-in-scoped key `plugin-printing-shop/quotes/quote-<code>.pdf` 5. Returns the file handle plus a `downloadUrl` pointing at the framework's `/api/v1/files/download` endpoint the caller can immediately hit ## Smoke test (fresh DB + staged plug-in) ``` # create a plate POST /api/v1/plugins/printing-shop/plates {code: PLATE-200, name: "Premium cover", widthMm: 420, heightMm: 594} → 201 {id, code: PLATE-200, status: DRAFT, ...} # generate + store the quote PDF POST /api/v1/plugins/printing-shop/plates/<id>/generate-quote-pdf {customerName: "Acme Inc"} → 201 { plateId, plateCode: "PLATE-200", customerName: "Acme Inc", fileKey: "plugin-printing-shop/quotes/quote-PLATE-200.pdf", fileSize: 1488, fileContentType: "application/pdf", downloadUrl: "/api/v1/files/download?key=plugin-printing-shop/quotes/quote-PLATE-200.pdf" } # download via the framework's file endpoint GET /api/v1/files/download?key=plugin-printing-shop/quotes/quote-PLATE-200.pdf → 200 Content-Type: application/pdf Content-Length: 1488 body: valid PDF 1.5, 1 page $ file /tmp/plate-quote.pdf /tmp/plate-quote.pdf: PDF document, version 1.5, 1 pages (zip deflate encoded) # list by prefix GET /api/v1/files?prefix=plugin-printing-shop/ → [{"key":"plugin-printing-shop/quotes/quote-PLATE-200.pdf", "size":1488, "contentType":"application/pdf", ...}] # plug-in log [plugin:printing-shop] registered 8 endpoints under /api/v1/plugins/printing-shop/ [plugin:printing-shop] generated quote PDF for plate PLATE-200 (1488 bytes) → plugin-printing-shop/quotes/quote-PLATE-200.pdf ``` Four public surfaces composed in one flow: plug-in JDBC read → plug-in classloader resource load → host ReportRenderer compile/ fill/export → host FileStorage put → host file controller download. Every step stays on api.v1; zero plug-in code reaches into a concrete platform class. ## Printing-shop plug-in — full extension surface exercised After this commit the reference printing-shop plug-in contributes via every public seam the framework offers: | Seam | How the plug-in uses it | |-------------------------------|--------------------------------------------------------| | HTTP endpoints (P1.3) | 8 endpoints under /api/v1/plugins/printing-shop/ | | JDBC (P1.4) | Reads/writes its own plugin_printingshop__* tables | | Liquibase | Own changelog.xml, 2 tables created at plug-in start | | Metadata YAML (P1.5) | 2 entities, 5 permissions, 2 menus | | Custom fields on CORE (P3.4) | 5 plug-in fields on Partner/Item/SalesOrder/WorkOrder | | i18n (P1.6) | Own messages_<locale>.properties, quote number msgs | | EventBus (P1.7) | Publishes WorkOrderRequestedEvent from a TaskHandler | | TaskHandlers (P2.1) | 2 handlers (plate-approval, quote-to-work-order) | | Plug-in BPMN (P2.1 followup) | 2 BPMNs in processes/ auto-deployed at start | | JobHandlers (P1.10 followup) | PlateCleanupJobHandler using context.jdbc + logger | | ReportRenderer (P1.8) | Quote PDF from JRXML via context.reports | | FileStorage (P1.9) | Persists quote PDF via context.files | Everything listed in this table is exercised end-to-end by the current smoke test. The plug-in is the framework's executable acceptance test for the entire public extension surface. ## Tests No new unit tests — the wiring change is a plain constructor addition, the existing `DefaultPluginContext` has no dedicated test class (it's a thin dataclass-shaped bean), and `JasperReportRenderer` + `LocalDiskFileStorage` each have their own unit tests from the respective parent chunks. The change is validated end-to-end by the above smoke test; formalizing that into an integration test would need Testcontainers + a real plug-in JAR and belongs to a different (test-infra) chunk. - Total framework unit tests: 337 (unchanged), all green. ## Non-goals (parking lot) - Pre-compiled `.jasper` caching keyed by template hash. A hot-path benchmark would tell us whether the cache is worth shipping. - Multipart upload of a template into a plug-in's own `files` namespace so non-bundled templates can be tried without a plug-in rebuild. Nice-to-have for iteration but not on the v1.0 critical path. - Scoped file-key prefixes per plug-in enforced by the framework (today the plug-in picks its own prefix by convention; a `plugin.files.keyPrefix` config would let the host enforce that every plug-in-contributed file lives under `plugin-<id>/`). Future hardening chunk.