Review & Approval Workflow
Status: Final Version: 1.0
Purpose
Define task assignment, review processes, approval sign-off, and segregation of duties for ESG data review.
Workflow Stages
VALIDATED → Assign Reviewer → UNDER_REVIEW → Reviewer Action
↓ (approve) ↓ (reject)
REVIEWED REJECTED
↓ ↓
Assign Approver Collector fixes
↓ ↓
APPROVED Resubmit (new version)
Task Assignment
Automatic Assignment Logic
class ReviewTaskAssignmentService
{
public function assignReviewer(MetricSubmission $submission)
{
// Find reviewer with:
// 1. Access to submission's site
// 2. NOT the submitter (SoD)
// 3. Lowest current workload
$reviewer = User::role('reviewer')
->whereHas('sites', fn($q) => $q->where('site_id', $submission->site_id))
->where('id', '!=', $submission->submitted_by_user_id)
->withCount(['assignedReviewTasks' => fn($q) => $q->where('state', 'pending')])
->orderBy('assigned_review_tasks_count')
->first();
ReviewTask::create([
'submission_id' => $submission->id,
'assigned_to_user_id' => $reviewer->id,
'due_date' => now()->addBusinessDays(3),
'state' => 'pending',
]);
// Notify reviewer
$reviewer->notify(new ReviewTaskAssigned($submission));
}
}
Manual Assignment (Admin Override)
Reviewer Actions
1. Approve (Transition to REVIEWED)
public function approve(MetricSubmission $submission, User $reviewer, string $comment)
{
if ($submission->submitted_by_user_id === $reviewer->id) {
throw new SegregationOfDutiesException('Cannot review own submission');
}
$submission->update([
'state' => 'REVIEWED',
'reviewed_by_user_id' => $reviewer->id,
'reviewed_at' => now(),
'reviewer_comment' => $comment,
]);
// Create approval task
$this->assignApprover($submission);
AuditLog::log('submission.reviewed', $submission, $reviewer);
}
2. Reject (Transition to REJECTED)
public function reject(MetricSubmission $submission, string $reason, array $corrections)
{
$submission->update([
'state' => 'REJECTED',
'rejection_reason' => $reason,
'required_corrections' => $corrections,
]);
// Notify collector
$submission->submittedBy->notify(new SubmissionRejected($submission));
AuditLog::log('submission.rejected', $submission);
}
Approver Actions
Approve (Transition to APPROVED)
public function approveSubmission(MetricSubmission $submission, User $approver, string $justification)
{
// Segregation of Duties checks
if ($submission->submitted_by_user_id === $approver->id) {
throw new SegregationOfDutiesException('Cannot approve own submission');
}
if ($submission->reviewed_by_user_id === $approver->id) {
Log::warning("Approver is also reviewer", ['submission_id' => $submission->id]);
// Allow but flag in audit log
}
$submission->update([
'state' => 'APPROVED',
'approved_by_user_id' => $approver->id,
'approved_at' => now(),
'approval_justification' => $justification,
]);
AuditLog::log('submission.approved', $submission, $approver, $justification);
event(new SubmissionApproved($submission));
}
SLA Tracking
class ReviewTask extends Model
{
public function isOverdue(): bool
{
return $this->state === 'pending' && $this->due_date->isPast();
}
public function getEscalationLevel(): int
{
if (!$this->isOverdue()) return 0;
$daysOverdue = now()->diffInDays($this->due_date);
return match(true) {
$daysOverdue >= 7 => 3, // Critical
$daysOverdue >= 3 => 2, // High
default => 1, // Medium
};
}
}
// Scheduled job: Escalate overdue reviews
class EscalateOverdueReviewsJob
{
public function handle()
{
ReviewTask::where('state', 'pending')
->where('due_date', '<', now()->subDays(3))
->each(function ($task) {
$task->update(['escalation_level' => $task->getEscalationLevel()]);
// Notify manager
$task->assignedTo->manager->notify(new ReviewTaskOverdue($task));
});
}
}
Acceptance Criteria
- Reviewers cannot review own submissions (enforced by policy)
- Approvers cannot approve own submissions (hard constraint)
- Review tasks auto-assigned based on workload
- SLA tracking with escalation for overdue reviews (3+ days)
- All approval actions logged to audit trail
Cross-References
Change Log
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-03 | Senior Product Architect | Initial review/approval workflow specification |