Indicator verification
Indicators: Verification Flow
Audience overview
This document explains how indicator submissions are verified. Non‑developers can read the overview to understand the user journey; technical details follow for engineers maintaining the flow.
- What happens when an entrepreneur submits? If the indicator requires verification, it goes to Level 1 review (and optionally Level 2). If it does not require verification, the task is completed automatically.
- Approvals: Move the submission to the next verification level or complete it if it was the final required approval.
- Rejections: Mark the submission rejected and send the task back for revision.
Admin Verification Interface
The verification interface is built as a Filament resource within the Indicators cluster, providing verifiers with a dedicated workspace to review and approve/reject indicator submissions.
Filament Resource Structure
The IndicatorVerificationsResource provides the admin interface for managing verification tasks:
// app/Filament/Admin/Resources/IndicatorVerificationsResource.php
class IndicatorVerificationsResource extends Resource
{
protected static ?string $model = IndicatorReviewTask::class;
protected static ?string $cluster = Indicators::class;
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->where('verifier_user_id', auth()->id())
->with(['submission.task.indicator', 'submission.task.entrepreneur']);
}
}
User-Scoped Queries
Unlike typical tenant-scoped resources, verification tasks are scoped to the authenticated verifier:
- Only tasks where
verifier_user_idmatches the current user are visible - Eager loading prevents N+1 queries on related submission and task data
- Cross-tenant visibility when verifiers work across multiple organizations
Tabbed Interface
The resource provides two primary tabs for organizing verification work:
- Pending: Active verification tasks awaiting review (
status = PENDING) - Completed: Finished verifications that have been approved or resulted in rejection (
status = COMPLETED)
Verify Mode Modal
When a verifier opens a pending verification task, they are presented with:
- Submission Details: The indicator title, submitted value, comments, and any attached files
- Context Information: Entrepreneur, organization, and programme details
- Approval Actions:
- Approve: Advances to next verification level or completes the workflow
- Reject: Returns the submission to the submitter with feedback
- Feedback Field: Required when rejecting, optional when approving
- Review History: Any previous verification decisions for context
The modal interface adapts based on verification level (Level 1 vs Level 2) and displays appropriate context for the verifier's decision.
How it works (technical)
- Service entry points:
app/Services/Indicators/IndicatorVerificationService.phpprocessSubmissionForVerificationIfRequired(IndicatorSubmission $submission)handleApprovedReview(IndicatorSubmissionReview $review)handleRejectedReview(IndicatorSubmissionReview $review)completeTaskAndSubmission(IndicatorSubmission $submission)
Submission → Level 1 verification
processSubmissionForVerificationIfRequiredloads required relationships on the submission’sIndicatorTask(indicatable, organisation, programme, entrepreneur).- If the task does not require verification, it is completed immediately by emitting
IndicatorTaskCompleted. - If verification is required, the service ensures an entrepreneur exists and then creates a Level 1 review task via
initiateVerificationForLevel($submission, 1). - A reviewer is resolved (see Role resolution below). A review task is created with a due date of
now + config('success-compliance-indicators.review_task_days', 7)days and theIndicatorSubmissionAwaitingVerificationevent is emitted. If no matching user is found for the configured role, the task is created unassigned and no event is emitted.
Level 2 verification
When a Level 1 review is approved and the indicator has verifier_2_role_id configured, handleApprovedReview moves the submission to PENDING_VERIFICATION_2 and creates the Level 2 review task (emits IndicatorSubmissionAwaitingVerification for level 2). If no Level 2 is configured, the submission is treated as finally approved and completion is triggered.
Rejections
handleRejectedReview updates:
IndicatorSubmission.status = REJECTEDIndicatorTask.status = NEEDS_REVISION
This allows the entrepreneur to revise and resubmit.
Approvals and completion
- If the current approval is not the final required level, the service advances to the next level.
- If it is the final required level, the service emits
IndicatorTaskCompleted($submission)and, when finalizing,completeTaskAndSubmissionsets:IndicatorSubmission.status = APPROVED(task status is then finalized by theIndicatorSubmissionobserver to ensure ordering).
Events and listeners
Events in app/Events/Indicator used by the verification flow:
IndicatorSubmissionSubmittedIndicatorSubmissionAwaitingVerificationIndicatorSubmissionApprovedIndicatorSubmissionRejectedIndicatorTaskReadyForSubmissionIndicatorTaskCompleted
Listeners in app/Listeners/Indicator react to these events (notifications, side‑effects, etc.). The verification service emits IndicatorSubmissionAwaitingVerification and IndicatorTaskCompleted at the relevant points.
Role configuration and resolution
-
Where roles are set: Each indicator (the
indicatableonIndicatorTask) stores the verifier roles:verifier_1_role_id(required when verification is needed)verifier_2_role_id(optional; enables two‑level approval)
-
When roles are resolved: At submission time, the service determines the actual user for a given role and context in
findVerifierUser(IndicatorTask $task, ?int $roleId). The role is loaded (Role::find($roleId)), and the resolver is chosen based onRole.slug:mentor→resolveMentor(Organisation)from organisation guides/mentorsprogramme-manager→resolveProgrammeManager(Programme)programme-coordinator→resolveProgrammeCoordinator(Programme)regional-coordinator→resolveRegionalCoordinator(Organisation.sessionDeliveryLocation)regional-manager→resolveRegionalManager(Organisation.sessionDeliveryLocation)eso-manager→resolveEsoManager(Organisation|User.primaryTenant → TenantCluster)
If a resolver cannot find a user, the review task is created without verifier_user_id, and the event/note indicates an unassigned task so operational processes can handle assignment.
Resolver details by role (exact logic)
-
Mentor
- Source:
Organisation.guides()->isGuide() - Resolution:
- If there is exactly one mentor, that user is selected.
- If there are zero mentors, no user is resolved. A debug log entry is written with
organisation_id,entrepreneur_id, andsubmission_id. - If there are multiple mentors, no user is resolved (ambiguous). A debug log entry is written with
organisation_id,entrepreneur_id, andsubmission_id.
- Notes: No fallback to programme or entrepreneur-level associations; ambiguity intentionally results in an unassigned review task.
- Source:
-
Programme manager
- Source:
Programme.programmeManagers()(throughprogramme_user_roles) - Selection rule: The most recently added manager (
programme_user_roles.created_atdescending). - Not resolved when: None exist for the programme. A debug log entry is written with
programme_id.
- Source:
-
Programme coordinator
- Source:
Programme.programmeCoordinators()(throughprogramme_user_roles) - Selection rule: The most recently added coordinator (
programme_user_roles.created_atdescending). - Not resolved when: None exist for the programme. A debug log entry is written with
programme_id.
- Source:
-
Regional coordinator
- Prerequisite:
Organisation.sessionDeliveryLocationmust be present. - Source:
DeliveryLocation.regionalCoordinators()(throughdelivery_location_user_roles) - Selection rule: The most recently added coordinator (
delivery_location_user_roles.created_atdescending). - Not resolved when:
- The organisation has no delivery location (debug log with
organisation_id). - The delivery location has no regional coordinators (debug log with
delivery_location_id).
- The organisation has no delivery location (debug log with
- Prerequisite:
-
Regional manager
- Prerequisite:
Organisation.sessionDeliveryLocationmust be present. - Source:
DeliveryLocation.regionalManagers()(throughdelivery_location_user_roles) - Selection rule: The most recently added manager (
delivery_location_user_roles.created_atdescending). - Not resolved when:
- The organisation has no delivery location (debug log with
organisation_id). - The delivery location has no regional managers (debug log with
delivery_location_id).
- The organisation has no delivery location (debug log with
- Prerequisite:
-
ESO manager
- Tenant selection: Use
Organisation.getPrimaryTenant()if available; otherwise fall back toEntrepreneur.getPrimaryTenant(). - Cluster requirement: The resolved tenant must have a
cluster. - Source:
TenantCluster.esoManagers()(throughtenant_cluster_user_roles) - Selection rule: The most recently added ESO manager (
tenant_cluster_user_roles.created_atdescending). - Not resolved when:
- Neither the organisation nor the entrepreneur has a primary tenant (debug log with
entrepreneur_id,organisation_id). - The tenant has no cluster (debug log with
tenant_id). - The cluster has no ESO managers (debug log with
tenant_cluster_id).
- Neither the organisation nor the entrepreneur has a primary tenant (debug log with
- Tenant selection: Use
All of the above resolvers intentionally choose the most recently added related user where multiple may exist, except for Mentors where ambiguity results in no resolution. There is no load‑balancing or round‑robin selection.
Configuration
success-compliance-indicators.review_task_days(default: 7) controls the review task due date.
Exceptions you may see
The service uses explicit exceptions to enforce required relationships:
MissingIndicatorAssociationException(e.g., no entrepreneur/organisation/programme)RoleNotFoundForVerificationLevelException(no role configured or role not found)TaskNotFoundForSubmissionException/TaskNotFoundForReviewExceptionSubmissionNotFoundForReviewExceptionIndicatorReviewTaskCreationException
Testing Verification Workflows
Test Coverage
The verification feature includes comprehensive test coverage:
- Service Tests:
IndicatorVerificationServiceTest.php- covers the core verification logic, role resolution, and workflow progression - Filament Resource Tests:
IndicatorVerificationsResourceTest.php- validates the admin UI, user scoping, and action handling
Test Setup Patterns
Tests for verification resources require proper context setup:
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 verifier user with proper role assignment
$this->verifier = User::factory()->create();
$verifierRole = Role::firstOrCreate(['name' => 'verifier_1'], [...]);
$this->verifier->roles()->attach($verifierRole, ['tenant_id' => config('multitenancy.landlord_id')]);
$this->actingAs($this->verifier);
});
Parallel Test Execution Considerations
When running tests in parallel:
- Role Creation: Use
firstOrCreate()to prevent duplicate role creation across test processes - Verifier Assignment: Ensure unique verifier user creation per test to avoid conflicts
- Event Dispatching: Verification events should dispatch immediately in tests for proper assertion
- Database Isolation: Each test process requires isolated submission and review task data
Testing Approval/Rejection Actions
Example test for approving a verification:
it('can approve a submission at level 1', function () {
$reviewTask = IndicatorReviewTask::factory()
->for(IndicatorSubmission::factory()->create())
->create(['verifier_user_id' => $this->verifier->id, 'verification_level' => 1]);
livewire(IndicatorVerificationsResource\Pages\ListIndicatorVerifications::class)
->callTableAction('approve', $reviewTask)
->assertNotified();
expect($reviewTask->refresh()->status)->toBe(IndicatorReviewTaskStatusEnum::COMPLETED);
});
Entity relationship diagram (developer‑focused)
The following ER diagram shows the key domain entities and how verification tasks relate to submissions, tasks, roles, users, and organisational context used when resolving verifiers.
File references
- Service:
app/Services/Indicators/IndicatorVerificationService.php - Filament Resource:
app/Filament/Admin/Resources/IndicatorVerificationsResource.php - Events:
app/Events/Indicator/* - Listeners:
app/Listeners/Indicator/*