Open
Conversation
Introduces a foundational, reusable template builder system that supports
designing invoice templates, shipping label templates, report templates,
receipts, and other documents.
New files:
- migrations/2026_03_03_000001_create_templates_table.php
- migrations/2026_03_03_000002_create_template_queries_table.php
- src/Models/Template.php
- src/Models/TemplateQuery.php
- src/Http/Resources/Template.php
- src/Http/Resources/TemplateQuery.php
- src/Http/Requests/Internal/CreateTemplateRequest.php
- src/Http/Controllers/Internal/v1/TemplateController.php
- src/Http/Controllers/Internal/v1/TemplateQueryController.php
- src/Services/TemplateRenderService.php
Modified files:
- src/routes.php — registers templates and template-queries routes
- src/Providers/CoreServiceProvider.php — registers TemplateRenderService singleton
- composer.json — adds spatie/laravel-pdf ^2.0 and mossadal/math-parser ^2.0
Key features:
- Three-tier context: global/ambient (company, user, date) + primary subject + query collections
- Variable syntax: {namespace.property} with dot-notation
- Formula syntax: [{ {invoice.subtotal} * 1.1 }] with safe arithmetic evaluation
- Iteration blocks: {{#each variable}} ... {{/each}} with {this.property}, {loop.index}
- Canvas-based element system: text, image, table, line, shape, qr_code, barcode
- PDF output via spatie/laravel-pdf; HTML preview endpoint
- registerContextType() static method for extensions to register context types
roncodes
pushed a commit
to fleetbase/ember-ui
that referenced
this pull request
Mar 3, 2026
Implements a full-featured, reusable template builder component for designing
invoice templates, shipping label templates, report templates, receipt templates,
and other document types.
## Components
- `TemplateBuilder` — top-level orchestrator, composes all panels
- `TemplateBuilder::Canvas` — pixel-perfect free-canvas with interact.js drag/resize
- `TemplateBuilder::ElementRenderer` — renders all element types (text, image, table, line, shape, qr_code, barcode)
- `TemplateBuilder::Toolbar` — element type picker, zoom controls, undo/redo, save/preview
- `TemplateBuilder::LayersPanel` — element tree with visibility toggle, reorder, delete
- `TemplateBuilder::PropertiesPanel` — contextual properties editor with Position, Size, Typography, Appearance, Border, Effects sections
- `TemplateBuilder::PropertiesPanel::Section` — collapsible section wrapper
- `TemplateBuilder::PropertiesPanel::Field` — labelled field row
- `TemplateBuilder::VariablePicker` — modal for browsing context schema variables and composing formulas
## Key Features
- Free-canvas drag and resize via interact.js with grid snapping
- 50-step undo/redo history
- Variable syntax: `{invoice.total}`, `{company.name}`
- Formula syntax: `[{ {invoice.subtotal} * 1.1 }]`
- Iteration syntax: `{{#each orders}}...{{/each}}`
- Three-tier context: global/ambient → primary subject → query collections
- Variable picker with search, namespace grouping, type icons, and formula editor
- Full dark mode support via Tailwind + data-theme='dark'
- Paper size and orientation support (A4, Letter, Legal, custom)
- Element types: text, image, table, line, shape, qr_code, barcode
## Dependencies
- interactjs ^1.10.27 (drag/resize)
Refs: fleetbase/core-api#198
roncodes
pushed a commit
to fleetbase/fleetbase
that referenced
this pull request
Mar 3, 2026
Adds two Ember Data models to the console app to support the new template builder system introduced in fleetbase/core-api#198 and fleetbase/ember-ui#121. ## Models ### template - Full attribute set matching the backend Template model - Computed: isDraft, isPublished, contextTypeLabel, dimensionLabel - hasMany: template-query (inverse) - Supports: name, description, context_type, paper_size, orientation, width, height, unit, background_color, content (array), is_default, status ### template-query - Full attribute set matching the backend TemplateQuery model - Computed: variableName, variableToken, resourceTypeLabel - belongsTo: template (inverse) - Supports: name, label, description, resource_type, filters (object), sort_by, sort_direction, limit Both models follow the existing Fleetbase model conventions: - @ember-data/model with attr/belongsTo/hasMany decorators - Standard date computed properties (updatedAgo, updatedAt, createdAt, etc.) - date-fns for date formatting Refs: fleetbase/core-api#198, fleetbase/ember-ui#121
…pdate Override createRecord and updateRecord to handle a 'queries' array in the request payload. After saving the template, _syncQueries() upserts each query (create if no UUID, update if UUID exists) and soft-deletes any queries not present in the incoming list (i.e. deleted in the builder). Temporary client-side UUIDs prefixed with '_new_' are treated as new records (UUID stripped before insert). The Template HTTP resource already serializes 'queries' via whenLoaded; both overridden methods eager-load 'queries' before returning the response so the frontend receives the full updated list in one round-trip.
…lateController Both methods were declared as returning JsonResponse but actually return a TemplateResource (which extends JsonResource, not JsonResponse). This caused a fatal TypeError at runtime: Return value must be of type Illuminate\Http\JsonResponse, Fleetbase\Http\Resources\Template returned Fix: widen the return type union to JsonResource|JsonResponse so both the TemplateResource path (normal request) and the JsonResponse path (error / internal request fallback) satisfy the type contract.
… createRecord/updateRecord Replace the manual overrides of createRecord() and updateRecord() with the proper HasApiControllerBehavior hook methods onAfterCreate() and onAfterUpdate(). getControllerCallback() in the trait checks whether these methods exist on the controller and wraps them as closures that are passed to createRecordFromRequest() and updateRecordFromRequest() in HasApiModelBehavior. Both hooks receive the same signature: (Request $request, Model $record, array $input). The hooks call _syncQueries() to upsert/delete TemplateQuery rows and then load the queries relationship so the response includes them — identical behaviour to before, but without duplicating the entire create/update flow or introducing a return-type mismatch. The _templateFromResponse() helper is also removed as it is no longer needed.
Previously only POST /templates/{id}/preview existed, which required a
persisted record. This meant the builder could not preview a template
before it was saved for the first time.
Changes:
routes.php
Register POST 'preview' (no {id}) before the existing {id}/preview
route so Laravel's router matches the literal segment first.
TemplateController::previewUnsaved()
New method that:
1. Reads the 'template' key from the request body.
2. Hydrates a transient (non-persisted) Template model via fill().
3. Hydrates transient TemplateQuery models from the nested 'queries'
array and sets them directly on the relation via setRelation() so
the render pipeline can iterate them without any DB access.
4. Resolves an optional subject model (subject_type / subject_id).
5. Delegates to TemplateRenderService::renderToHtml() and returns
{ html } — identical response shape to the persisted preview.
TemplateRenderService::buildContext()
Guard the loadMissing('queries') call with a relationLoaded() check.
When the relation has already been set in-memory (transient model case)
loadMissing would overwrite it with an empty DB result, breaking the
query context tier for unsaved previews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Template Builder System
Introduces a foundational, reusable template builder system for designing invoice templates, shipping label templates, report templates, receipts, and other documents.
Architecture
Templates are reusable design artefacts — they contain layout, styling, and variable syntax but have no permanent data binding. Data is only injected at render time by the caller passing a subject model.
New Files
Migrations
2026_03_03_000001_create_templates_table.php— Canvas-based template withcontext_type, dimensions (width/height/unit/orientation), margins, background, and a JSONcontentarray of element objects.2026_03_03_000002_create_template_queries_table.php— Query data sources attached to a template for iterable collections (e.g.orders in December→{orders}).Models
Models/Template.php— Template model withqueriesHasMany, scopes for context type and company availability.Models/TemplateQuery.php— TemplateQuery model with a self-containedexecute()method that applies conditions, sort, limit, and eager-loads.HTTP Layer
Http/Resources/Template.php+TemplateQuery.phpHttp/Requests/Internal/CreateTemplateRequest.phpHttp/Controllers/Internal/v1/TemplateController.php— extends FleetbaseController; addspreview(→ HTML),render(→ PDF download), andcontextSchemas(→ variable picker schemas) endpoints.Http/Controllers/Internal/v1/TemplateQueryController.php— standard CRUD.Service
Services/TemplateRenderService.php— core rendering engine with:{company.*},{user.*},{now},{today}) → primary subject → query collections{namespace.property}with dot-notation[{ {invoice.subtotal} * 1.1 }]with safe arithmetic evaluation (usesmossadal/math-parserwhen available, falls back to built-in recursive descent parser){{#each variable}} ... {{/each}}with{this.property},{loop.index},{loop.first},{loop.last}spatie/laravel-pdfwith configurable paper size and marginsregisterContextType()static method for extensions (Ledger, FleetOps, etc.) to register their own context types and variable schemasModified Files
src/routes.php— registerstemplatesandtemplate-queriesroutes under the protected internal v1 groupsrc/Providers/CoreServiceProvider.php— registersTemplateRenderServiceas a singletoncomposer.json— addsspatie/laravel-pdf ^2.0andmossadal/math-parser ^2.0API Endpoints
GET/int/v1/templatesPOST/int/v1/templatesGET/int/v1/templates/:idPUT/int/v1/templates/:idDELETE/int/v1/templates/:idGET/int/v1/templates/context-schemasPOST/int/v1/templates/:id/previewPOST/int/v1/templates/:id/renderGET/POST/PUT/DELETE/int/v1/template-queries