Skip to content

Report Builder: file-based persistence (manifest + bands JSON), clone + sync-system #528

Description

@nielsdrost7

Status 2026-07-13: implemented on feature/130-report-builder (#608) — as-built notes in the #130 status section.


Parent: #130 (Report Builder — bands + bricks). Phase 1. Part of epic #506, Track A.

Scope

Persistence is file-based only — no report_templates table, no DB rows. Layout:

resources/report-templates/                  # shipped, version-controlled
  invoice/default/manifest.json              # { name, slug, type, version }
  invoice/default/bands.json                 # { header: […], group_header: […], details: […], group_footer: […], footer: […] }
  quote/default/{manifest,bands}.json
storage/app/report_templates/                # runtime disk 'report_templates'
  system/…                                   # published via `php artisan reports:sync-system`
  {company_id}/{slug}/{manifest,bands}.json  # company clones

bands.json entries are data only: { "brick": "header_company", "width": "half", "config": { … } } — never code.

Build ReportTemplateStorage (rework of the old branch's MasonTemplateStorage + ReportTemplateFileRepository):

  • list($companyId) — disk scan of system/ + the company folder
  • loadManifest()/loadBands()/save()/delete()
  • clone($from, $companyId, $newName) — copy folder + rewrite manifest (name, slug, cloned_from)
  • Validation on load: unknown brick ids skipped, config filtered against the brick's schema, width/band values validated against enums
  • Slug sanitization: [a-z0-9-] only, .. rejected; company path always derived from the current tenant, never from user input

Plus the reports:sync-system artisan command (idempotent publish of resources/report-templates/storage/app/report_templates/system/).

Acceptance criteria

  • Save → load round-trips manifest + bands unchanged.
  • Clone copies the folder and rewrites the manifest; the source stays untouched; system templates are read-only.
  • ../, uppercase, and non-slug characters are rejected with an exception.
  • Company A cannot list or load company B's templates.
  • reports:sync-system is idempotent and never touches company folders.
  • Loading a bands.json with an unknown brick id or invalid width silently drops that entry (and logs a warning).

Test scenarios (PHPUnit, #[Test], no Pest)

  • Round-trip, clone, sanitization, tenant-isolation and sync-idempotency tests (adapt the harvested MasonStorageAdapterTest / ReportTemplateFileRepositoryTest suites to the no-DB design).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    refinedRefined with SMART plan / test suggestions

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions