Skip to content

Locking & Restatements

Status: Final Version: 1.0


Purpose

Define the workflow for locking reporting periods (making data immutable) and the restatement process for correcting locked data.


Locking Workflow

Prerequisites

Before a period can be locked: - [ ] All mandatory submissions are in APPROVED state - [ ] No submissions in PENDING, REJECTED, or UNDER_REVIEW - [ ] Period state is IN_REVIEW

Lock Process

public function lock(ReportingPeriod $period, User $approver, string $justification)
{
    // Validate prerequisites
    if ($period->hasUnreviewedSubmissions()) {
        throw new StatePrerequisiteException('All submissions must be reviewed');
    }

    // Generate content hash (cryptographic integrity)
    $contentHash = $this->generateContentHash($period);

    DB::transaction(function () use ($period, $approver, $justification, $contentHash) {
        $period->update([
            'state' => 'LOCKED',
            'locked_at' => now(),
            'locked_by_user_id' => $approver->id,
            'lock_justification' => $justification,
            'content_hash' => $contentHash,
        ]);

        AuditLog::log('reporting_period.locked', $period, $approver, $justification);
    });

    event(new ReportingPeriodLocked($period));
}

protected function generateContentHash(ReportingPeriod $period): string
{
    $submissions = MetricSubmission::where('reporting_period_id', $period->id)
        ->where('state', 'APPROVED')
        ->orderBy('id')
        ->get(['id', 'submission_uuid', 'metric_definition_id', 'processed_data'])
        ->toJson();

    return hash('sha256', $submissions);
}

Restatement Workflow

Triggers for Restatement

  1. Error Correction: Data entry error discovered post-lock
  2. Methodology Change: Updated calculation method applied retrospectively
  3. Acquisition/Divestment: Baseline recalculation (>30% change)
  4. Audit Finding: External auditor identifies discrepancy

Restatement Process

Step 1: Unlock Period (Admin Only)

public function unlock(ReportingPeriod $period, User $admin, string $reason)
{
    if (!$admin->hasRole('admin')) {
        throw new AuthorizationException('Only admins can unlock periods');
    }

    $period->update([
        'state' => 'IN_REVIEW',
        'unlocked_at' => now(),
        'unlocked_by_user_id' => $admin->id,
        'unlock_reason' => $reason,
    ]);

    AuditLog::log('reporting_period.unlocked', $period, $admin, $reason);
}

Step 2: Create Restatement Record

$restatement = Restatement::create([
    'reporting_period_id' => $period->id,
    'restatement_date' => now(),
    'trigger' => 'error_correction', // or 'methodology_change', 'acquisition', etc.
    'description' => 'Corrected electricity consumption for Site A (meter reading error)',
    'before_values' => $period->getApprovedMetricSnapshot(),
    'after_values' => null, // Will be updated after corrections
    'impact_percentage' => null, // Calculated after corrections
    'approved_by_user_id' => null, // Pending approval
]);

Step 3: Submit Corrections

Collector resubmits corrected data (new submission version).

Step 4: Re-Lock Period

public function reLock(ReportingPeriod $period, Restatement $restatement, User $approver)
{
    $newContentHash = $this->generateContentHash($period);

    DB::transaction(function () use ($period, $restatement, $approver, $newContentHash) {
        // Update restatement with final values
        $restatement->update([
            'after_values' => $period->getApprovedMetricSnapshot(),
            'impact_percentage' => $this->calculateImpact($restatement),
            'approved_by_user_id' => $approver->id,
        ]);

        // Re-lock period with new hash
        $period->update([
            'state' => 'LOCKED',
            'locked_at' => now(),
            'locked_by_user_id' => $approver->id,
            'content_hash' => $newContentHash,
            'restatement_count' => $period->restatement_count + 1,
        ]);

        AuditLog::log('reporting_period.restated', $period, $approver);
    });
}

GRI 2-4 Disclosure (Restatements of Information)

Platform auto-generates restatement table for reports:

Metric Original Value Restated Value Change Reason
GRI 302-1 Electricity 10,500 MWh 10,250 MWh -2.4% Meter reading correction

Acceptance Criteria

  • Periods can only be locked if all submissions approved
  • Content hash generated and stored at lock time
  • Only admins can unlock locked periods
  • All restatements tracked in dedicated table
  • Restatement count incremented on each re-lock
  • GRI 2-4 restatement disclosure auto-generated

Cross-References


Change Log

Version Date Author Changes
1.0 2026-01-03 Senior Product Architect Initial locking/restatement specification