Skip to content

Report Outputs & Generation

Status: Final Version: 1.0


Purpose

Define report types, export formats, generation pipeline, versioning, and audit trail for ESG reporting outputs.


Report Types (v1)

Report Type Audience Format Content Frequency
GRI Sustainability Report External stakeholders PDF, DOCX Full GRI disclosures (E, S, G) Annual
Period Summary Internal management PDF, XLSX Metrics by site, period totals Quarterly/Annual
Site-Level Report Site managers PDF, XLSX Single site metrics for period On-demand
Disclosure Export Analysts JSON, CSV, XLSX Raw metric data On-demand
Audit Package External auditors PDF + ZIP All approved data + evidence Annual

Export Formats

PDF

  • Use Case: External reports, board presentations
  • Library: Laravel DomPDF or Snappy (wkhtmltopdf)
  • Template: Blade views with CSS styling

XLSX

  • Use Case: Data analysis, pivot tables
  • Library: Laravel Excel (PhpSpreadsheet)
  • Structure: One sheet per metric category (Environmental, Social, Governance)

JSON/CSV

  • Use Case: API integrations, data exports
  • Structure: Flat or nested JSON, CSV with headers

Report Versioning

Version Type Description Use Case
Draft Work-in-progress, data not locked Internal review
Final Locked period, ready for publication Official disclosure
Restated Corrected after initial publication Error correction, methodology change

Version Schema

class Report extends Model
{
    protected $fillable = [
        'tenant_id',
        'reporting_period_id',
        'report_type',
        'version_type', // ENUM: 'draft', 'final', 'restated'
        'version_number', // Incremented on restatements
        'generated_at',
        'generated_by_user_id',
        'file_path',
        'file_format',
        'content_hash', // SHA-256 of PDF/file
        'metadata', // JSONB: report params, filters
    ];
}

Generation Pipeline

Asynchronous Job

class GenerateReportJob implements ShouldQueue
{
    public $queue = 'reporting';
    public $tries = 3;
    public $timeout = 600; // 10 minutes

    public function handle()
    {
        $period = ReportingPeriod::find($this->reportingPeriodId);
        $report = Report::create([
            'tenant_id' => $this->tenantId,
            'reporting_period_id' => $period->id,
            'report_type' => $this->reportType,
            'version_type' => $period->state === 'LOCKED' ? 'final' : 'draft',
            'generated_by_user_id' => $this->userId,
        ]);

        // Generate PDF
        $pdf = $this->generatePDF($period);

        // Store
        $path = $pdf->save("reports/{$this->tenantId}/{$report->id}.pdf");

        $report->update([
            'file_path' => $path,
            'file_format' => 'pdf',
            'generated_at' => now(),
            'content_hash' => hash_file('sha256', storage_path($path)),
        ]);

        // Notify user
        User::find($this->userId)->notify(new ReportGenerated($report));
    }

    protected function generatePDF(ReportingPeriod $period)
    {
        $data = [
            'period' => $period,
            'metrics' => $this->getApprovedMetrics($period),
            'restatements' => $period->restatements,
        ];

        return PDF::loadView('reports.gri-sustainability', $data)
            ->setPaper('a4')
            ->setOption('margin-top', 20)
            ->setOption('margin-bottom', 20);
    }
}

Blade Template Example (GRI Report)

<!-- resources/views/reports/gri-sustainability.blade.php -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ $period->organisation->name }} - GRI Sustainability Report {{ $period->fiscal_year }}</title>
    <style>
        body { font-family: Arial, sans-serif; font-size: 10pt; }
        h1 { color: #2c3e50; font-size: 18pt; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #3498db; color: white; }
    </style>
</head>
<body>
    <h1>Sustainability Report {{ $period->fiscal_year }}</h1>
    <p><strong>Organization:</strong> {{ $period->organisation->name }}</p>
    <p><strong>Reporting Period:</strong> {{ $period->start_date->format('Y-m-d') }} to {{ $period->end_date->format('Y-m-d') }}</p>

    <h2>GRI 302: Energy</h2>
    <table>
        <thead>
            <tr>
                <th>Metric</th>
                <th>Value</th>
                <th>Unit</th>
            </tr>
        </thead>
        <tbody>
            @foreach($metrics->where('disclosure.code', '302-1') as $metric)
            <tr>
                <td>{{ $metric->name }}</td>
                <td>{{ number_format($metric->pivot->value, 2) }}</td>
                <td>{{ $metric->unit }}</td>
            </tr>
            @endforeach
        </tbody>
    </table>

    @if($restatements->isNotEmpty())
    <h2>GRI 2-4: Restatements of Information</h2>
    <table>
        <thead>
            <tr>
                <th>Metric</th>
                <th>Original</th>
                <th>Restated</th>
                <th>Reason</th>
            </tr>
        </thead>
        <tbody>
            @foreach($restatements as $r)
            <tr>
                <td>{{ $r->metric_name }}</td>
                <td>{{ $r->before_value }}</td>
                <td>{{ $r->after_value }}</td>
                <td>{{ $r->description }}</td>
            </tr>
            @endforeach
        </tbody>
    </table>
    @endif
</body>
</html>

Audit Trail

AuditLog::create([
    'action' => 'report.generated',
    'entity_type' => 'Report',
    'entity_id' => $report->id,
    'actor_user_id' => $this->userId,
    'metadata' => [
        'report_type' => $this->reportType,
        'period_id' => $period->id,
        'file_format' => 'pdf',
        'file_size_bytes' => Storage::size($report->file_path),
    ],
]);

Acceptance Criteria

  • GRI Sustainability Report includes all approved metrics
  • Report generation queued as background job (timeout 10 min)
  • PDF reports generated with Blade templates
  • XLSX exports include separate sheets per category
  • Report versioning tracks draft/final/restated
  • Content hash generated for integrity verification
  • All report generation logged to audit trail

Cross-References


Change Log

Version Date Author Changes
1.0 2026-01-03 Senior Product Architect Initial reporting outputs specification