Skip to content

Admin Indicator Submissions

Intro

The Admin Indicator Submissions feature provides a comprehensive workflow for managing indicator task submissions within the Filament admin panel. This feature allows administrators to submit their assigned indicator tasks with supporting documentation, values, and comments through an intuitive interface.

The system supports both Success Indicators and Compliance Indicators, with conditional file upload requirements and a robust event-driven workflow that ensures proper task progression and status management.

Note: For details on how submissions are verified after submission, see Indicator Verification.

Architecture Overview

The Admin Indicator Submissions feature is built as a Filament Resource within the Indicators cluster, providing administrators with a dedicated interface for completing their assigned indicator tasks.

Core Models

  • IndicatorTask: Represents tasks assigned to administrators for indicator completion
  • IndicatorSubmission: Contains the actual submission data (values, files, comments)

Event-Driven Workflow

The system uses Laravel events to manage the submission workflow:

  • IndicatorSubmissionSubmitted → Triggers verification workflow (see Indicator Verification)
  • IndicatorSubmissionRejected → Returns task to submitter with feedback for revision

Workflow Process

1. Task Assignment

  • Administrators are assigned IndicatorTasks through existing mechanisms
  • Tasks appear in the Indicators cluster when users have assigned tasks
  • Tasks are scoped by user (not tenant) for proper visibility control

2. Submission Process

  • Admin opens submission modal from their task list
  • Form dynamically populates based on indicator type and requirements
  • File uploads are conditionally required based on supporting_documentation field
  • Comments and values are captured along with file attachments
  • Submission triggers IndicatorSubmissionSubmitted event

3. Status Management

  • Tasks progress through: Pending → Submitted → In Verification → Complete
  • Status display uses separate displayLabel() for UI and displayStatus() for logic
  • Failed submissions return to "Needs Revision" status with previous data pre-filled

Implementation Details

Filament Cluster Structure

// app/Filament/Admin/Clusters/Indicators.php
class Indicators extends Cluster
{
    protected static ?string $navigationGroup = 'Manage';

    public static function canAccess(): bool
    {
        return static::shouldRegisterNavigation();
    }

    public static function shouldRegisterNavigation(): bool
    {
        $user = auth()->user();
        return $user->indicatorTasks()->exists() ||
               $user->indicatorReviewTasks()->exists();
    }
}

User-Scoped Queries

Unlike most Filament resources, Indicators use user-based scoping instead of tenant scoping:

public static function getEloquentQuery(): Builder
{
    return parent::getEloquentQuery()
        ->where('responsible_user_id', auth()->id())
        ->with(['indicator', 'entrepreneur', 'organisation', 'programme']);
}

Service Layer Integration

The IndicatorSubmissionService was extended with Filament adapter methods:

// Service adapter for Filament compatibility
public function createSubmissionFromFilament(array $data, IndicatorTask $task): IndicatorSubmission
{
    $transformedData = $this->transformFilamentData($data, $task);
    return $this->createSubmission($transformedData, $task);
}

private function transformFilamentData(array $data, IndicatorTask $task): array
{
    // Transforms Filament form data to service-expected format
    // Handles file upload path conversions
    // Maintains backward compatibility
}

File Upload Handling

File uploads are converted from Filament's storage paths to proper UploadedFile instances:

private function transformFilamentAttachments(?array $attachments): array
{
    if (empty($attachments)) return [];

    return collect($attachments)->map(function ($path) {
        // Handle various Filament path formats
        $storagePath = str_replace(['storage/app/public/', 'public/'], '', $path);
        return new UploadedFile(
            storage_path("app/public/{$storagePath}"),
            basename($storagePath),
            Storage::disk('public')->mimeType($storagePath),
            null,
            true
        );
    })->toArray();
}

Event Listeners Integration

Event listeners handle workflow progression after submission:

// When a submission is created, it triggers the IndicatorSubmissionSubmitted event
public function createSubmissionFromFilament(array $data, IndicatorTask $task): IndicatorSubmission
{
    $transformedData = $this->transformFilamentData($data, $task);
    $submission = $this->createSubmission($transformedData, $task);

    // Event is dispatched automatically by the service
    event(new IndicatorSubmissionSubmitted($submission));

    return $submission;
}

The IndicatorSubmissionSubmitted event automatically initiates the verification workflow if required. If no verification is needed, the task is marked as complete immediately. For details on how verification is handled, see Indicator Verification.

User Interface

Tabbed Interface

The Admin Submissions resource uses tabbed interfaces for organization:

  • Pending: Shows Pending, Needs Revision, and Overdue tasks
  • In Verification: Shows Submitted tasks
  • Complete: Shows Completed tasks

Dynamic Modal System

Modals adapt based on task status:

  • Submit Mode: Interactive form for pending/revision tasks with full edit capabilities
  • View Mode: Read-only display for submitted/complete tasks showing submission details

Form Field Behavior

Forms are dynamically generated based on indicator data:

  • Title from indicator.title
  • Helper text from additional_instruction
  • File uploads conditional on supporting_documentation
  • Pre-filled data for revision tasks

Permission and Access Control

Cluster Visibility

The Indicators cluster only appears for users with relevant tasks:

public static function shouldRegisterNavigation(): bool
{
    $user = auth()->user();
    return $user->indicatorTasks()->exists() ||
           $user->indicatorReviewTasks()->exists();
}

Filament Panel Requirements

App Panel Access requires:

  • User must be admin: $user->isAdmin()
  • Current tenant must be landlord: $tenant->isLandlord()
  • OR user has MANAGE_SESSIONS_SCHEDULER permission

Admin Panel Access requires:

  • User has VIEW_ADMIN_DASHBOARD permission
  • Admin role assignment

Data Scoping

  • User-scoped: Only assigned tasks visible to each user
  • Non-tenant scoped: Works across tenant boundaries
  • Relationship eager loading: Prevents N+1 queries

Testing Considerations

Unit Test Coverage

The feature includes comprehensive test coverage:

  • Service Tests: IndicatorSubmissionServiceTest.php
  • Filament Resource Tests: IndicatorsClusterTest.php, IndicatorSubmissionsResourceTest.php

Note: For verification-related test coverage, see Indicator Verification.

Parallel Test Isolation

Key considerations for parallel test execution:

  1. Tenant Context: Tests require proper landlord tenant setup
  2. Role/Permission Creation: Use firstOrCreate() to prevent conflicts
  3. Database Constraints: Handle foreign key and unique constraints properly
  4. Event Dispatching: Remove ShouldDispatchAfterCommit for immediate testing

Test Setup Pattern

beforeEach(function () {
    // Create tenant context for Filament panel access
    $this->tenant = Tenant::firstOrCreate(
        ['id' => config('multitenancy.landlord_id')],
        ['id' => config('multitenancy.landlord_id'), 'name' => 'Landlord Tenant']
    );
    app()->instance('currentTenant', $this->tenant);

    // Create admin with proper permissions
    $this->admin = User::factory()->create();
    $adminRole = Role::firstOrCreate(['name' => 'admin'], [...]);
    $this->admin->roles()->attach($adminRole, ['tenant_id' => config('multitenancy.landlord_id')]);

    $this->actingAs($this->admin);
});

Performance Considerations

Query Optimization

  • Eager loading relationships: ->with(['indicator', 'entrepreneur', 'organisation'])
  • User-scoped queries prevent large dataset issues
  • Pagination implemented for table views

File Storage

  • Production: Files stored in S3 via indicator_submissions disk (s3://bucket/indicator_submissions/)
  • Testing: Files stored locally via indicator_submissions_local disk (storage/app/public/indicator_submissions/)
  • Proper MIME type detection and validation
  • Secure file access through Laravel's storage system

Caching

  • No explicit caching implemented (relies on Laravel's query cache)
  • Consider adding model-level caching for frequently accessed data

Maintenance Notes

Important Considerations for Future Developers

  1. Service Layer: The IndicatorSubmissionService maintains dual compatibility - both controller and Filament usage. Don't break this pattern.

  2. Event System: Events are crucial for workflow progression. Always test event dispatching when making changes.

  3. Status Management: The dual-status system (displayLabel vs displayStatus) serves different purposes - don't merge them.

  4. File Handling: Filament file uploads require transformation to UploadedFile objects. This is handled in the service adapter.

  5. User Scoping: This feature intentionally uses user-based scoping instead of tenant scoping. Don't convert to tenant scoping.

Common Pitfalls

  • Missing Tenant Context: Filament panels require proper tenant setup in tests
  • Event Timing: Be careful with event dispatching timing - some tests need immediate dispatch
  • File Path Handling: Filament provides various path formats that need normalization
  • Permission Stacking: Admin users need both role assignment AND specific permissions

Extension Points

The feature is designed for extension:

  • Additional indicator types can be added easily
  • Verification levels can be increased beyond 2
  • Additional file types and validation rules can be implemented
  • Custom business logic can be added to event listeners

Configuration

Required Configuration

// config/multitenancy.php
'landlord_id' => 1,

// Required roles (created via seeder or migration)
- 'admin' role with appropriate permissions

// Required permissions
- VIEW_ADMIN_DASHBOARD
- MANAGE_SESSIONS_SCHEDULER (for app panel alternative access)

// File storage disks (see Storage Configuration section)
- 'indicator_submissions'

Note: For verifier role configuration, see Indicator Verification.

Storage Configuration

// config/filesystems.php

// Production S3 storage (default)
'indicator_submissions' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'visibility' => 'public',
    'root' => 'indicator_submissions/',
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],

Bulk Import Feature

Overview

Allows administrators to submit multiple indicator tasks at once via CSV upload. Only works for indicators that don't require supporting documentation.

How to Use

  1. Generate Template: Select tasks in the table → Click "Generate CSV Template" bulk action → Download pre-populated CSV
  2. Fill Values: Complete the value and comment columns following the format instructions in the CSV
  3. Import: Click "Import Submissions" → Upload CSV → Review results

Value Formats

  • Boolean: 1, 0, true, false (case-insensitive)
  • Percentage: Number between 0-100
  • Monetary: Number ≥ 0
  • Numeric: Any number

Key Limitations

  1. No attachments - Indicators requiring supporting documentation cannot be bulk imported
  2. User scoped - Can only import for your own assigned tasks
  3. CSV only - Excel files not supported
  4. Creates new submissions - Doesn't update existing ones

Important Notes

Permission Required: bulk-submit-indicators

Files:

  • Importer: app/Filament/Imports/IndicatorSubmissionImporter.php
  • Export: app/Exports/IndicatorSubmissionTemplateExport.php
  • Tests: tests/Feature/IndicatorBulkImportTest.php

Integration: Uses IndicatorSubmissionService::createSubmissionFromFilament() so all events, verification workflows, and status management work identically to manual submissions.

Common Issues

Import succeeds but no records created:

  • resolveRecord() must return the created IndicatorSubmission model, not null
  • Filament uses the return value to track successful imports

Template generation fails with documentation error:

  • Some selected indicators require supporting documentation
  • Unselect those indicators or submit them manually with attachments

This comprehensive documentation should provide future developers with everything needed to understand, maintain, and extend the Admin Indicator Submissions feature, including the bulk import functionality. For verification workflow details, see Indicator Verification.