Skip to content

feat(#130): report builder — phases 1-4 - #720

Open
nielsdrost7 wants to merge 33 commits into
InvoicePlane:developfrom
underdogg-forks:feature/130-report-builder
Open

feat(#130): report builder — phases 1-4#720
nielsdrost7 wants to merge 33 commits into
InvoicePlane:developfrom
underdogg-forks:feature/130-report-builder

Conversation

@nielsdrost7

@nielsdrost7 nielsdrost7 commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Implements the Report Builder (bands + bricks) per the v2 spec in #130 (epic #506, Track A) — Phases 1–4 in twelve commits across storage, UI, rendering, and PDF pipeline.

What's in here

Phase Content
1 Pure-file ReportTemplateStorage (manifest.json + bands.json, zero DB tables), shipped default invoice/quote templates, idempotent reports:sync-system, 17 bricks harvested into Modules\Core\Mason + new PageBreak/Spacer bricks
2 Template list + five-band builder pages in both panels (admin = system templates; company = read-only system + tenant clones), Clone/Rename/Delete/Preview, explicit "move to band…" action, mason CSS in panel themes
3 ReportRenderer + ReportDataMapper + real PdfGenerationService; wired the stubbed "download pdf" actions; golden-file HTML snapshots + real dompdf byte tests
4 Per-document "PDF Template" select (#411) — zero migrations, reuses existing template columns; resolution chain document → company default → system default
fix dompdf ignores float clearing and in-cell page-break CSS — bands now render as 12-grid table rows, page breaks emit at block level (found by visually inspecting a generated PDF)
extra Opt-in Browsershot (headless Chromium) driver behind the same PDFFactory (IP_PDF_DRIVER=Browsershot + IP_BROWSERSHOT_CHROME_PATH); pins dompdf/dompdf as a direct dependency (was only transitive — latent packaging bug); logo guard fix. Verified against a real Chromium: identical band layout on both engines

Security (hard requirements from the PRD)

No user-authored PHP/Blade anywhere; slugs restricted to [a-z0-9-] (path traversal impossible); company storage paths derived from tenant context only; bands.json validated on load (unknown bricks skipped, widths enum-checked, configs filtered against each brick's own schema); dompdf remote fetching disabled (was enabled — fixed here); all brick views escape output.

Latent bugs fixed en route

  • domPDF driver had no Dompdf imports (fatal on first use) and setIsRemoteEnabled(true)
  • Company::communications() used the wrong morph name (communicable vs table's communicationable)
  • AbstractCompanyPanelTestCase::testLivewire() imported a mason schema component instead of the Livewire test facade

Testing

Full suite green: 400 tests, 1,339 assertions, 0 failures (develop baseline: 307 tests, also green). New coverage: storage round-trip/isolation/traversal, brick registry + band rules, builder pages in both panels (load/save/move/clone/delete/read-only), renderer (toggles, keep-together, page breaks, widths), golden snapshots for invoice/default + quote/default, PDF bytes via real dompdf, and the #411 resolution/picker scenarios. Plus an end-to-end smoke: app boot → migrate → sync → factory invoice → visually verified PDF.

Follow-up issues (tracked, not resolved here)

All hanging items have tracked issues on the main repo (sub-issues of #130); plain references only:

Independent tracks, unchanged: tabular reports (#145, Track B) and email variables (#363, Track C).

Summary by CodeRabbit

  • New Features
    • Added customizable invoice and quote report templates with visual editing, previews, reusable sections, page breaks, and spacing controls.
    • Added template management, including cloning, renaming, deletion, scope-based permissions, and default templates.
    • Added PDF generation and downloads for invoices and quotes, with selectable templates.
    • Added configurable report content for headers, details, totals, notes, terms, projects, tasks, and aging information.
  • Bug Fixes
    • Improved company logo rendering when no logo path is configured.
    • Improved PDF generation fallback and template selection behavior.

nielsdrost7 and others added 2 commits August 16, 2026 12:14
- docker-compose.yml: add MariaDB healthcheck (connection check)
- docker-compose.yml: add depends_on with service_healthy for cli
- docker-compose.yml: add init volume for test DB setup
- AGENTS.md: use cp -n to prevent .env.testing overwrite
…river) (InvoicePlane#714)

* chore: eliminate the SQLite testing fallback, run against real MariaDB

Local tests silently diverging from CI's MariaDB (via a documented SQLite
.env.testing fallback) has repeatedly masked real bugs this session —
->latest() defaulting to a nonexistent created_at column, and identifier
quoting differences, both passed locally on SQLite and only failed on CI.

- docker-compose.yml: cli service now injects DB_CONNECTION=mysql/DB_HOST=db
  etc. itself and depends_on db, so `docker compose run --rm cli php artisan
  test` works against real MariaDB with zero per-developer .env.testing edits
- Add docker-resources/mariadb/init/01-create-test-db.sql to provision a
  dedicated invoiceplane_test database alongside the dev one on first boot
- Fix db service: the named `database` volume was declared but never
  mounted, so all local dev/test data was lost on every container recreate
- docker-resources/php-cli/Dockerfile: rebuild on Debian (php:8.4-cli) with
  the minimal proven extension set, matching the ip2-test-php:8.4 image this
  session used successfully throughout — see InvoicePlane#689 for a still-open false-
  failure issue found with a fresh cli image build, flagged in the docs
- Update AGENTS.md/CLAUDE.md/README.md/.github/DOCKER.md/Makefile to point
  at the compose db/cli path instead of the SQLite instructions
- Note throughout: use `php artisan test`, not raw vendor/bin/phpunit — the
  two were observed to behave differently for this app's Livewire form tests

* chore: remove accidentally committed infrastructure files

* feat(InvoicePlane#130): report template storage layer and brick registry (Phase 1)

Pure-file report template storage — a template is a folder with
manifest.json + bands.json on the report_templates disk, no database
tables. Ships default invoice/quote templates in resources/ and a
reports:sync-system command that copies them into system storage.

Harvests the 17 report bricks from feature/145-report-builder into
Modules\Core\Mason (per module convention), rebased onto a ReportBrick
base class that adds band placement rules and config-schema
introspection. Brick signatures updated for the current awcodes/mason
API (nullable toHtml data, BrickAction-managed insert flow). Adds
PageBreak and Spacer utility bricks for InvoicePlane#95.

Security per PRD: slugs restricted to [a-z0-9-], company paths derived
from tenant context only, bands validated on load (unknown bricks
skipped, widths enum-checked, configs filtered per brick schema).

Refs InvoicePlane#130 InvoicePlane#521 InvoicePlane#528

* feat(InvoicePlane#130): five-band report builder UI in admin and company panels (Phase 2)

Custom Filament pages (no Eloquent resource): a template list page and
a builder page with five stacked Mason canvases, one per ReportBand,
each offering only the bricks allowed in that band. Shared base classes
in Modules\Core\Filament\Pages\Reports; the admin panel edits system
templates, the company panel shows system templates read-only and
manages tenant-scoped clones.

Actions: Clone (into the panel's own scope), Rename, Delete (shipped
defaults protected), Preview, and an explicit "move to band" action
that validates allowedBands() — cross-canvas dragging is deliberately
not supported. MasonDocumentConverter round-trips bands.json entries
to mason editor state, carrying block width in a reserved config key.

Also fixes AbstractCompanyPanelTestCase::testLivewire, which imported
the mason schema component instead of the Livewire test facade and
chained withSession onto the wrong object; imports mason plugin CSS
into all five panel themes.

Refs InvoicePlane#130 InvoicePlane#519 InvoicePlane#523 InvoicePlane#525

* feat(InvoicePlane#130): report renderer and PDF pipeline (Phase 3)

ReportRenderer turns manifest + bands + entity data into the HTML
document for the PDF driver: bands render in document order, block
widths become percentage columns, a band with keep_together in the
manifest's band_options gets page-break-inside: avoid, and the
PageBreak/Spacer bricks provide manual pagination control (InvoicePlane#95).

ReportDataMapper builds the data arrays the brick views consume from
Invoice/Quote models (company, client, meta, items, totals, terms,
summary, footer). PdfGenerationService resolves the template chain —
document slug, then company default (existing invoices.template and
companies.invoice_template/quote_template columns), then the system
default — and renders via the domPDF driver. The stubbed "download
pdf" table actions on invoices and quotes now stream a real PDF.

Fixes two latent bugs the pipeline exposed: the domPDF driver had no
Dompdf imports (fatal on first use) and remote fetching enabled
(violates the security requirement — now off, images must be local);
Company::communications() used the wrong morph name (communicable vs
the table's communicationable).

Golden-file HTML snapshots for the default invoice and quote templates
pin rendering output; PDF byte tests run the real dompdf engine.

Refs InvoicePlane#130 InvoicePlane#95

* feat(InvoicePlane#411): per-document PDF template selection (Phase 4)

Adds a nullable "PDF Template" select to the invoice and quote forms,
listing the disk templates for that document type (system defaults
plus the current company's clones, clones shadowing same-slug system
templates). The selection persists in the existing invoices.template /
quotes.template columns; clearing it falls back to the company default
(companies.invoice_template / quote_template) and then the shipped
system default — no schema changes were needed, the columns already
existed.

Resolution-chain behavior is covered in PdfGenerationServiceTest;
this adds the picker-level scenarios (options per type, clone
shadowing, persistence, clearing).

Closes InvoicePlane#411; Refs InvoicePlane#130

* fix(InvoicePlane#130): dompdf-safe row layout in ReportRenderer

Visual smoke-testing the generated PDF showed dompdf ignores float
clearing, so a full-width brick after two half-width bricks rendered
on top of them. Bands now chunk consecutive bricks into 12-grid rows
rendered as tables, which dompdf lays out correctly. Page-break bricks
are emitted at block level between row tables because dompdf also
ignores page-break CSS inside table cells. Golden fixtures regenerated
for the new markup.

Refs InvoicePlane#130 InvoicePlane#95

* feat(InvoicePlane#130): opt-in Browsershot (headless Chromium) PDF driver

Adds a Browsershot driver behind the existing PDFFactory abstraction
for installs that have Node + Chromium: select it with
IP_PDF_DRIVER=Browsershot, point IP_BROWSERSHOT_CHROME_PATH at a
Chromium binary (node/npm/no-sandbox overrides available). dompdf
stays the zero-dependency default; wkhtmltopdf/Snappy was rejected
because its upstream is archived with unpatched CVEs, and mPDF (the
v1 engine) is a CSS sidegrade.

Verified end-to-end against a real Chromium: the same invoice HTML
renders the identical band layout on both engines — the table-row
markup from the dompdf fix is engine-independent by design.

Also pins dompdf/dompdf as a direct composer requirement: the merged
driver had been relying on a transitive install, which this change's
composer update silently removed — a latent packaging bug. And fixes
the company-header logo guard (isset → !empty) so documents without a
logo don't render a broken image icon on Chromium.

Refs InvoicePlane#130

* fix: use explicit invoice/quote item values to bypass factory tax recalculation

The InvoiceItemFactory and QuoteItemFactory have an afterMaking hook that
recalculates tax_total based on tax_rate_id. The PdfGenerationServiceTest
was passing hard-coded tax values to the factory, but the afterMaking hook
was overwriting them (defaulting to 0% tax when no tax_rate_id was set).

Fixed by:
1. Creating a 21% TaxRate for each test
2. Creating items directly with Model::create() instead of factory(),
   bypassing the afterMaking recalculation hook entirely

This ensures the golden-snapshot fixtures use the correct hard-coded values
(tax=21.00, total=121.00) regardless of factory behavior.


Refs InvoicePlane#130

* fix(tests): compare rendered invoice HTML against the escaped customer name

Blade's {{ }} auto-escapes output, so an unescaped comparison only
fails when Faker happens to generate a company name with an
apostrophe or other HTML-special character. Confirmed by forcing
"O'Kon, Schneider and Wisozk" locally against real MariaDB: fails
without e(), passes with it. See InvoicePlane#687.

Refs InvoicePlane#130

* fix: add missing imports for EmailTemplateResource and CompanyUserResource

Refs InvoicePlane#130

* fix: comment out invalid ReportTemplates navigation (Page, not Resource)

Refs InvoicePlane#130

* fix: comment out ReportTemplates import (class does not exist in this context)

Refs InvoicePlane#130

* fix: disable ReportTemplates page (not used, breaks discovery)

Refs InvoicePlane#130
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 133 files, which is 33 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 72a61f5a-4641-44f3-b863-0a50742d53a8

📥 Commits

Reviewing files that changed from the base of the PR and between 60f8aa2 and 72d56cb.

⛔ Files ignored due to path filters (7)
  • composer.lock is excluded by !**/*.lock
  • debug-looks-ok.png is excluded by !**/*.png
  • for-now-ok.png is excluded by !**/*.png
  • getting-there.png is excluded by !**/*.png
  • report-builder-001.png is excluded by !**/*.png
  • report-builder-002.png is excluded by !**/*.png
  • what-should-this-have-looked-like.png is excluded by !**/*.png
📒 Files selected for processing (133)
  • .claude/skills/abstract-seeder/SKILL.md
  • .claude/skills/application-architecture-standard/SKILL.md
  • .claude/skills/autonomous-coding-workflow/SKILL.md
  • .claude/skills/ci-schema-invariant-gate/SKILL.md
  • .claude/skills/dto-contract/SKILL.md
  • .claude/skills/factory-contract-system/SKILL.md
  • .claude/skills/filament-multi-tenancy/SKILL.md
  • .claude/skills/filament-panel-setup/SKILL.md
  • .claude/skills/filament-resource-pages/SKILL.md
  • .claude/skills/filament-resource-testing/SKILL.md
  • .claude/skills/github-actions-php/SKILL.md
  • .claude/skills/laravel-modules/SKILL.md
  • .claude/skills/non-standard-pks/SKILL.md
  • .claude/skills/pest-control/SKILL.md
  • .claude/skills/safe-refactoring-rules/SKILL.md
  • .claude/skills/security-review/SKILL.md
  • .claude/skills/senior-laravel-developer-code-reviewer/SKILL.md
  • .claude/skills/senior-laravel-developer-phpunit-interpreter/SKILL.md
  • .claude/skills/service-layer/SKILL.md
  • .claude/skills/spatie-roles/SKILL.md
  • .claude/skills/sync-stale-branches/SKILL.md
  • .claude/skills/tailwindcss-development/SKILL.md
  • .claude/skills/tenant-middleware/SKILL.md
  • .claude/skills/test-honesty/SKILL.md
  • .claude/skills/user-auth-fields/SKILL.md
  • .github/DOCKER.md
  • CLAUDE.md
  • Modules/Core/Console/ReportsSyncSystemCommand.php
  • Modules/Core/Enums/ReportBand.php
  • Modules/Core/Enums/ReportBlockWidth.php
  • Modules/Core/Enums/ReportTemplateType.php
  • Modules/Core/Filament/Admin/Pages/ReportBuilder.php
  • Modules/Core/Filament/Admin/Pages/ReportTemplates.php.disabled
  • Modules/Core/Filament/Company/Pages/ReportBuilder.php
  • Modules/Core/Filament/Company/Pages/ReportTemplates.php
  • Modules/Core/Filament/Pages/Reports/BaseReportBuilderPage.php
  • Modules/Core/Filament/Pages/Reports/BaseReportTemplatesPage.php
  • Modules/Core/Mason/Bricks/DetailCustomerAgingBrick.php
  • Modules/Core/Mason/Bricks/DetailExpenseBrick.php
  • Modules/Core/Mason/Bricks/DetailInvoiceProductBrick.php
  • Modules/Core/Mason/Bricks/DetailInvoiceProjectBrick.php
  • Modules/Core/Mason/Bricks/DetailItemsBrick.php
  • Modules/Core/Mason/Bricks/DetailQuoteProductBrick.php
  • Modules/Core/Mason/Bricks/DetailQuoteProjectBrick.php
  • Modules/Core/Mason/Bricks/DetailTasksBrick.php
  • Modules/Core/Mason/Bricks/FooterNotesBrick.php
  • Modules/Core/Mason/Bricks/FooterSummaryBrick.php
  • Modules/Core/Mason/Bricks/FooterTermsBrick.php
  • Modules/Core/Mason/Bricks/FooterTotalsBrick.php
  • Modules/Core/Mason/Bricks/HeaderClientBrick.php
  • Modules/Core/Mason/Bricks/HeaderCompanyBrick.php
  • Modules/Core/Mason/Bricks/HeaderInvoiceMetaBrick.php
  • Modules/Core/Mason/Bricks/HeaderProjectBrick.php
  • Modules/Core/Mason/Bricks/HeaderQuoteMetaBrick.php
  • Modules/Core/Mason/Bricks/PageBreakBrick.php
  • Modules/Core/Mason/Bricks/SpacerBrick.php
  • Modules/Core/Mason/MasonDocumentConverter.php
  • Modules/Core/Mason/ReportBrick.php
  • Modules/Core/Mason/ReportBricksCollection.php
  • Modules/Core/Models/Company.php
  • Modules/Core/Providers/AdminPanelProvider.php
  • Modules/Core/Providers/CoreServiceProvider.php
  • Modules/Core/Services/PdfGenerationService.php
  • Modules/Core/Services/ReportDataMapper.php
  • Modules/Core/Services/ReportRenderer.php
  • Modules/Core/Services/ReportTemplateStorage.php
  • Modules/Core/Support/PDF/Drivers/Browsershot.php
  • Modules/Core/Support/PDF/Drivers/domPDF.php
  • Modules/Core/Tests/Feature/AdminReportBuilderTest.php
  • Modules/Core/Tests/Feature/CompanyReportBuilderTest.php
  • Modules/Core/Tests/Feature/InvoiceTemplateSelectionTest.php
  • Modules/Core/Tests/Feature/PdfGenerationServiceTest.php
  • Modules/Core/Tests/Feature/ReportsSyncSystemCommandTest.php
  • Modules/Core/Tests/Fixtures/report-templates/invoice-default.html
  • Modules/Core/Tests/Fixtures/report-templates/quote-default.html
  • Modules/Core/Tests/Unit/BrowsershotDriverTest.php
  • Modules/Core/Tests/Unit/DomPdfDriverTest.php
  • Modules/Core/Tests/Unit/MasonBricksTest.php
  • Modules/Core/Tests/Unit/MasonDocumentConverterTest.php
  • Modules/Core/Tests/Unit/ReportBrickTest.php
  • Modules/Core/Tests/Unit/ReportBricksCollectionTest.php
  • Modules/Core/Tests/Unit/ReportRendererTest.php
  • Modules/Core/Tests/Unit/ReportTemplateStorageTest.php
  • Modules/Core/resources/views/filament/pages/reports/report-builder.blade.php
  • Modules/Core/resources/views/filament/pages/reports/report-templates.blade.php
  • Modules/Invoices/Filament/Company/Resources/Invoices/Schemas/InvoiceForm.php
  • Modules/Invoices/Filament/Company/Resources/Invoices/Tables/InvoicesTable.php
  • Modules/Invoices/Tests/Feature/InvoicePdfAndCreditNoteTest.php
  • Modules/Quotes/Filament/Company/Resources/Quotes/Schemas/QuoteForm.php
  • Modules/Quotes/Filament/Company/Resources/Quotes/Tables/QuotesTable.php
  • composer.json
  • config/ip.php
  • docker-compose.yml
  • docker-resources/apache/Dockerfile
  • docker-resources/apache/config/invoiceplane-vhost.conf
  • docker-resources/mariadb/init/01-create-test-db.sql
  • docker-resources/node/scripts/entrypoint.sh
  • docker-resources/php-cli/Dockerfile
  • docker-resources/php-fpm/Dockerfile
  • resources/css/filament/company/invoiceplane-blue.css
  • resources/css/filament/company/invoiceplane.css
  • resources/css/filament/company/nord.css
  • resources/css/filament/company/orange.css
  • resources/css/filament/company/reddit.css
  • resources/lang/en/ip.php
  • resources/report-templates/invoice/default/bands.json
  • resources/report-templates/invoice/default/manifest.json
  • resources/report-templates/quote/default/bands.json
  • resources/report-templates/quote/default/manifest.json
  • resources/views/filament/components/report-builder-iframe-preview-content.blade.php
  • resources/views/mason/bricks/detail-customer-aging/preview.blade.php
  • resources/views/mason/bricks/detail-expense/preview.blade.php
  • resources/views/mason/bricks/detail-invoice-product/preview.blade.php
  • resources/views/mason/bricks/detail-invoice-project/preview.blade.php
  • resources/views/mason/bricks/detail-items/preview.blade.php
  • resources/views/mason/bricks/detail-quote-product/preview.blade.php
  • resources/views/mason/bricks/detail-quote-project/preview.blade.php
  • resources/views/mason/bricks/detail-tasks/preview.blade.php
  • resources/views/mason/bricks/footer-notes/preview.blade.php
  • resources/views/mason/bricks/footer-summary/preview.blade.php
  • resources/views/mason/bricks/footer-terms/preview.blade.php
  • resources/views/mason/bricks/footer-totals/preview.blade.php
  • resources/views/mason/bricks/header-client/preview.blade.php
  • resources/views/mason/bricks/header-company/index.blade.php
  • resources/views/mason/bricks/header-company/preview.blade.php
  • resources/views/mason/bricks/header-invoice-meta/preview.blade.php
  • resources/views/mason/bricks/header-project/preview.blade.php
  • resources/views/mason/bricks/header-quote-meta/preview.blade.php
  • resources/views/mason/bricks/page-break/index.blade.php
  • resources/views/mason/bricks/page-break/preview.blade.php
  • resources/views/mason/bricks/spacer/index.blade.php
  • resources/views/mason/bricks/spacer/preview.blade.php
  • resources/views/vendor/mason/iframe-preview-content.blade.php

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Added a file-backed report-template system with Mason editing, configurable report bricks, invoice and quote template selection, HTML rendering, and PDF generation. Added system-template synchronization, company template management, panel integration, PDF drivers, default templates, and automated tests.

Changes

Report Builder and PDF Templates

Layer / File(s) Summary
Report contracts and Mason bricks
Modules/Core/Enums/*, Modules/Core/Mason/*, Modules/Core/resources/views/mason/*
Added report enums, configurable header, detail, footer, page-break, and spacer bricks, Mason conversion, brick registration, and configuration filtering.
Template storage and administration
Modules/Core/Services/ReportTemplateStorage.php, Modules/Core/Console/ReportsSyncSystemCommand.php, Modules/Core/Filament/Pages/Reports/*, Modules/Core/Filament/*/Pages/*
Added system and company template storage, synchronization, sanitization, cloning, renaming, deletion, listing, and builder editing.
Report data, rendering, and PDF output
Modules/Core/Services/*, Modules/Core/Support/PDF/*, composer.json, config/ip.php
Added document data mapping, template resolution, HTML rendering, PDF downloads, Browsershot support, and isolated Dompdf paths.
Panel integration and report configuration
Modules/Core/Providers/*, Modules/Invoices/Filament/*, Modules/Quotes/Filament/*, resources/report-templates/*, resources/lang/en/ip.php, resources/css/filament/company/*
Connected report pages and navigation, added invoice and quote template selectors and PDF actions, and added default templates, translations, and Mason styling.
Validation
Modules/Core/Tests/*
Added feature and unit tests for storage, synchronization, template editing, Mason bricks, rendering, PDF generation, and PDF drivers.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🔴 Critical · up to 6bc70

This PR adds report-template management and PDF generation, but the current implementation can let company users modify shared system templates and can break admin-page navigation at runtime; the declared dependency stack is also inconsistent. The PR is not merge-ready until these issues are fixed or explicitly accepted by the owner.

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
    participant Document
    participant PdfGenerationService
    participant ReportTemplateStorage
    participant ReportDataMapper
    participant ReportRenderer
    participant PDFDriver

    Document->>PdfGenerationService: Request invoice or quote PDF
    PdfGenerationService->>ReportTemplateStorage: Resolve document template
    PdfGenerationService->>ReportDataMapper: Map document data
    PdfGenerationService->>ReportRenderer: Render template and data
    ReportRenderer-->>PdfGenerationService: Return HTML
    PdfGenerationService->>PDFDriver: Generate PDF
    PDFDriver-->>Document: Stream PDF download
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change as the report builder and specifies that it covers phases 1 through 4.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nielsdrost7
nielsdrost7 force-pushed the feature/130-report-builder branch from 6bc7037 to 1ca45d6 Compare August 16, 2026 12:15
coderabbitai[bot]

This comment was marked as resolved.

@nielsdrost7
nielsdrost7 force-pushed the feature/130-report-builder branch from 7a7cdde to 5793edd Compare August 16, 2026 12:22
@nielsdrost7
nielsdrost7 force-pushed the feature/130-report-builder branch from 5878be9 to d47fded Compare August 16, 2026 12:34
Local tests silently diverging from CI's MariaDB (via a documented SQLite
.env.testing fallback) has repeatedly masked real bugs this session —
->latest() defaulting to a nonexistent created_at column, and identifier
quoting differences, both passed locally on SQLite and only failed on CI.

- docker-compose.yml: cli service now injects DB_CONNECTION=mysql/DB_HOST=db
  etc. itself and depends_on db, so `docker compose run --rm cli php artisan
  test` works against real MariaDB with zero per-developer .env.testing edits
- Add docker-resources/mariadb/init/01-create-test-db.sql to provision a
  dedicated invoiceplane_test database alongside the dev one on first boot
- Fix db service: the named `database` volume was declared but never
  mounted, so all local dev/test data was lost on every container recreate
- docker-resources/php-cli/Dockerfile: rebuild on Debian (php:8.4-cli) with
  the minimal proven extension set, matching the ip2-test-php:8.4 image this
  session used successfully throughout — see InvoicePlane#689 for a still-open false-
  failure issue found with a fresh cli image build, flagged in the docs
- Update AGENTS.md/CLAUDE.md/README.md/.github/DOCKER.md/Makefile to point
  at the compose db/cli path instead of the SQLite instructions
- Note throughout: use `php artisan test`, not raw vendor/bin/phpunit — the
  two were observed to behave differently for this app's Livewire form tests
The CompanySettings page was imported and used in navigation but not registered in the pages array, causing "Route [filament.company.pages.settings] not defined" errors.

Also includes:
- fix(local-dev): make HTTPS forcing conditional on production only
- fix(yarn): updated yarn.lock to match workspace dependencies
- docker-compose.yml: add MariaDB healthcheck (connection check)
- docker-compose.yml: add depends_on with service_healthy for cli
- docker-compose.yml: add init volume for test DB setup
- AGENTS.md: use cp -n to prevent .env.testing overwrite
…r and brick registry (Phase 1)

Pure-file report template storage — a template is a folder with
manifest.json + bands.json on the report_templates disk, no database
tables. Ships default invoice/quote templates in resources/ and a
reports:sync-system command that copies them into system storage.

Harvests the 17 report bricks from feature/145-report-builder into
Modules\Core\Mason (per module convention), rebased onto a ReportBrick
base class that adds band placement rules and config-schema
introspection. Brick signatures updated for the current awcodes/mason
API (nullable toHtml data, BrickAction-managed insert flow). Adds
PageBreak and Spacer utility bricks for InvoicePlane#95.

Security per PRD: slugs restricted to [a-z0-9-], company paths derived
from tenant context only, bands validated on load (unknown bricks
skipped, widths enum-checked, configs filtered per brick schema).

Refs InvoicePlane#130 InvoicePlane#521 InvoicePlane#528
…eport builder UI in admin and company panels (Phase 2)

Custom Filament pages (no Eloquent resource): a template list page and
a builder page with five stacked Mason canvases, one per ReportBand,
each offering only the bricks allowed in that band. Shared base classes
in Modules\Core\Filament\Pages\Reports; the admin panel edits system
templates, the company panel shows system templates read-only and
manages tenant-scoped clones.

Actions: Clone (into the panel's own scope), Rename, Delete (shipped
defaults protected), Preview, and an explicit "move to band" action
that validates allowedBands() — cross-canvas dragging is deliberately
not supported. MasonDocumentConverter round-trips bands.json entries
to mason editor state, carrying block width in a reserved config key.

Also fixes AbstractCompanyPanelTestCase::testLivewire, which imported
the mason schema component instead of the Livewire test facade and
chained withSession onto the wrong object; imports mason plugin CSS
into all five panel themes.

Refs InvoicePlane#130 InvoicePlane#519 InvoicePlane#523 InvoicePlane#525
…ine (Phase 3)

ReportRenderer turns manifest + bands + entity data into the HTML
document for the PDF driver: bands render in document order, block
widths become percentage columns, a band with keep_together in the
manifest's band_options gets page-break-inside: avoid, and the
PageBreak/Spacer bricks provide manual pagination control (InvoicePlane#95).

ReportDataMapper builds the data arrays the brick views consume from
Invoice/Quote models (company, client, meta, items, totals, terms,
summary, footer). PdfGenerationService resolves the template chain —
document slug, then company default (existing invoices.template and
companies.invoice_template/quote_template columns), then the system
default — and renders via the domPDF driver. The stubbed "download
pdf" table actions on invoices and quotes now stream a real PDF.

Fixes two latent bugs the pipeline exposed: the domPDF driver had no
Dompdf imports (fatal on first use) and remote fetching enabled
(violates the security requirement — now off, images must be local);
Company::communications() used the wrong morph name (communicable vs
the table's communicationable).

Golden-file HTML snapshots for the default invoice and quote templates
pin rendering output; PDF byte tests run the real dompdf engine.

Refs InvoicePlane#130 InvoicePlane#95
Adds a nullable "PDF Template" select to the invoice and quote forms,
listing the disk templates for that document type (system defaults
plus the current company's clones, clones shadowing same-slug system
templates). The selection persists in the existing invoices.template /
quotes.template columns; clearing it falls back to the company default
(companies.invoice_template / quote_template) and then the shipped
system default — no schema changes were needed, the columns already
existed.

Resolution-chain behavior is covered in PdfGenerationServiceTest;
this adds the picker-level scenarios (options per type, clone
shadowing, persistence, clearing).

Closes InvoicePlane#411; Refs InvoicePlane#130
…rtRenderer

Visual smoke-testing the generated PDF showed dompdf ignores float
clearing, so a full-width brick after two half-width bricks rendered
on top of them. Bands now chunk consecutive bricks into 12-grid rows
rendered as tables, which dompdf lays out correctly. Page-break bricks
are emitted at block level between row tables because dompdf also
ignores page-break CSS inside table cells. Golden fixtures regenerated
for the new markup.

Refs InvoicePlane#130 InvoicePlane#95
…iver

Adds a Browsershot driver behind the existing PDFFactory abstraction
for installs that have Node + Chromium: select it with
IP_PDF_DRIVER=Browsershot, point IP_BROWSERSHOT_CHROME_PATH at a
Chromium binary (node/npm/no-sandbox overrides available). dompdf
stays the zero-dependency default; wkhtmltopdf/Snappy was rejected
because its upstream is archived with unpatched CVEs, and mPDF (the
v1 engine) is a CSS sidegrade.

Verified end-to-end against a real Chromium: the same invoice HTML
renders the identical band layout on both engines — the table-row
markup from the dompdf fix is engine-independent by design.

Also pins dompdf/dompdf as a direct composer requirement: the merged
driver had been relying on a transitive install, which this change's
composer update silently removed — a latent packaging bug. And fixes
the company-header logo guard (isset → !empty) so documents without a
logo don't render a broken image icon on Chromium.

Refs InvoicePlane#130
…e/delete actions

Validate that rename and delete operations are permitted before calling
storage methods. Protects against UI bypass attempts.
Import Illuminate\Http\Response and add native return types to
downloadInvoice() and downloadQuote() for type safety.
Replace direct company_id assignment with factory relationship pattern
for Relation, Numbering, and Invoice.
Replace direct company_id assignment with factory relationship pattern
for Relation, Invoice, and Quote in golden fixture helpers.
…ort pages

Import ReportTemplates and ReportBuilder in CompanyPanelProvider, and
ImportV1Page in AdminPanelProvider to resolve class not found errors.
…rmission checks

The renameAction and deleteAction were creating a template array without the 'editable' key, causing an "Undefined array key" error when canModify() tried to check it. Pass the editable flag from arguments to ensure proper permission validation.
Report template bricks now respect their configured width (full, half,
two_thirds, one_third) in the preview view, fixing layout issues where
bricks would not display side-by-side as intended. Each brick preview
now wraps itself with the appropriate width class and inline-block
display, allowing proper grid-based layout.

Fixes: InvoicePlane#130
The CompanySettings page was imported but not registered in the pages()
array, causing the settings navigation link to throw a route-not-found error.

Fixes: InvoicePlane#130
Changed from inline-block to float-left for better side-by-side brick
layout in Mason canvas. Float-based layout is more reliable for
positioning bricks with width constraints.

Fixes: InvoicePlane#130
- Added gray background and taller min-height to bricks
- Wrapped each band with overflow: auto to properly contain floated bricks
- Added clear: both divs for proper float containment
- Added margin-bottom between bands for visual separation
- Bricks now display side-by-side when configured with half/third widths

Fixes: InvoicePlane#130
Switched from Tailwind classes to explicit inline styles for brick
backgrounds and borders. Inline styles guarantee proper rendering in
Mason canvas context regardless of CSS scope or sanitization.

Bricks now show:
- Light gray background (#f3f4f6)
- Dashed gray border (2px)
- Floating layout for side-by-side display
- 120px minimum height for visibility

Fixes: InvoicePlane#130
Replaced Tailwind width classes (w-1/2, w-1/3, etc) with inline
percentage-based width styles in brick preview templates. Tailwind
classes don't apply in Mason canvas context, causing bricks to always
render full-width. Inline styles (50%, 33.33%, 66.66%) ensure bricks
render at their configured widths and display side-by-side.

Also changed brick background color from red (debug) back to light gray
for production use.

Fixes: InvoicePlane#130
The toBandEntries() method was removing '_width' from the config,
which prevented preview templates from accessing the brick's configured
width. This caused bricks to always render at full-width.

Now '_width' is preserved in the config throughout the rendering
pipeline, allowing preview templates to correctly calculate and apply
width-based layout styles.

This fixes the Mason canvas and preview modal showing bricks at their
correct configured widths (half, full, third, etc).

Fixes: InvoicePlane#130
…ments

- Add width selector (Full/Half/One Third/Two Thirds) to all 18 brick configurations using ReportBlockWidth enum
- Implement responsive brick layout in canvas using inline-block display and flexbox
- Create custom Mason iframe preview override with improved styling:
  * Yellow dashed drop zones (visible at 70% opacity, brighter on hover)
  * Smooth transitions and hover effects for better UX
  * Proper width-based layout respecting configured brick widths

- Update brick preview templates with content labels and gray backgrounds (#CCCCCC)
- Fix preview modal to properly display width information by preserving _width in config
- Add width preservation in BaseReportBuilderPage renderPreviewHtml() to ensure preview accuracy

Canvas improvements:
- Drop zones now clearly visible and inviting for drag-and-drop
- Two 50% blocks display side-by-side on same row
- Three 50% blocks wrap naturally (2 on row 1, 1 on row 2)
- Full-width blocks take full canvas width
- Blocks show content labels for context during design

All bricks now support width configuration for flexible report layout design.
@nielsdrost7
nielsdrost7 force-pushed the feature/130-report-builder branch from d47fded to a10d385 Compare August 16, 2026 16:36
@nielsdrost7 nielsdrost7 linked an issue Aug 16, 2026 that may be closed by this pull request
20 tasks
@nielsdrost7
nielsdrost7 force-pushed the feature/130-report-builder branch 2 times, most recently from c1eda18 to 13c2300 Compare August 16, 2026 16:48
- Add flexbox layout to mason-preview-container for proper block arrangement
- Implement width-based flex-basis calculation in JavaScript (full/half/one_third/two_thirds)
- Add block-level styling with padding for visual spacing between blocks
- Include drop zone styling with yellow highlights and animations
- Support dynamic block width updates on page load and content changes

Related to InvoicePlane#130
@nielsdrost7
nielsdrost7 force-pushed the feature/130-report-builder branch from 13c2300 to 812016c Compare August 16, 2026 16:49
…ssible UI

Unified layout and styling across all report builder brick templates:
- Replaced float-based layout with inline-block wrappers (9 templates)
- Changed all red backgrounds (#ff0000) to consistent gray (#CCCCCC)
- Added width wrapper structure to 4 missing templates
- Removed Tailwind classes, standardized to inline styles
- Fixed syntax errors and unclosed divs
- Simplified demo content for clarity
- All templates now support half-width, one-third, and two-thirds layouts
- All 19 templates pass Blade syntax validation

This makes the UI grandmother-friendly: intuitive drag/drop, clear visual
hierarchy, consistent styling, and professional demo content that shows
what the final PDF/output will look like.

Related to InvoicePlane#130
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Reports]: Report Builder — bands + bricks

1 participant