Activity Approval
Data Entity
Description
Records the review and approval lifecycle for a submitted activity registration. Each activity has exactly one approval record tracking whether it has been approved, rejected, or flagged by a coordinator or org admin. Approved activities are the authoritative source of truth for Bufdir grant reporting, reimbursement processing, and organizational KPIs.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key uniquely identifying the approval record | PKrequiredunique |
activity_id |
uuid |
Foreign key to the activity under review; enforces the one-to-one relationship — each activity has exactly one approval record created at submission time | requiredunique |
org_id |
uuid |
Organization the activity belongs to; denormalized onto this table to enable org-scoped approval queue queries without joining activities | required |
local_association_id |
uuid |
Local association the submitting peer mentor belongs to; enables coordinator-scoped queue filtering without joining users or user_local_associations | - |
status |
enum |
Current approval lifecycle state. pending_review is the initial state on activity submission; approved and rejected are terminal states set by a reviewer; flagged marks suspected duplicates or policy violations pending investigation | required |
reviewer_id |
uuid |
User ID of the coordinator or org admin who made the approval or rejection decision; null while status is pending_review or flagged | - |
reviewed_at |
datetime |
Timestamp when the approval or rejection decision was recorded; null until the reviewer acts | - |
rejection_reason |
text |
Mandatory human-readable explanation when status transitions to rejected; surfaced to the submitting peer mentor as feedback. Must be non-empty when status is rejected. | - |
approval_comment |
text |
Optional internal reviewer comment attached to an approval decision; not shown to the peer mentor but visible in audit log and admin portal | - |
corrections |
json |
JSONB map of field corrections applied by the reviewer before approving (e.g. {"activity_type": "phone_call", "duration_minutes": 45}). Populated by the correctAndApprove workflow and stored for audit trail purposes. | - |
version |
integer |
Optimistic concurrency control counter, incremented on every status mutation. The optimistic-lock-adapter checks that the client-supplied version matches the current DB value before writing to prevent concurrent approval conflicts. | required |
flagged_by |
uuid |
User ID of the coordinator or admin who raised the flag; null when the record has never been flagged | - |
flagged_at |
datetime |
Timestamp when the activity was flagged; null when not flagged | - |
flag_reason |
text |
Reason supplied by the flagger explaining why the activity is suspicious (e.g. suspected duplicate, implausible duration, wrong contact); null when not flagged | - |
flag_resolved_at |
datetime |
Timestamp when the flag investigation was closed; null when the flag is still open | - |
flag_resolved_by |
uuid |
User ID of the coordinator or org admin who resolved the flag; null when unresolved | - |
flag_resolution_action |
enum |
Action taken when closing a flag investigation; null when no flag has been resolved | - |
created_at |
datetime |
Timestamp when the approval record was created, which coincides with the activity submission event | required |
updated_at |
datetime |
Timestamp of the most recent write to this record; maintained by an updated_at trigger or ORM hook on every mutation | required |
Database Indexes
idx_activity_approval_activity_id
Columns: activity_id
idx_activity_approval_org_status
Columns: org_id, status
idx_activity_approval_local_assoc_status
Columns: local_association_id, status
idx_activity_approval_reviewer_id
Columns: reviewer_id
idx_activity_approval_reviewed_at
Columns: reviewed_at
idx_activity_approval_flagged_org
Columns: org_id, flagged_at
idx_activity_approval_created_at
Columns: created_at
Validation Rules
valid_status_transition
error
Validation failed
rejection_reason_non_empty
error
Validation failed
corrections_json_schema
error
Validation failed
version_match_on_write
error
Validation failed
flag_reason_required_on_flag
error
Validation failed
org_id_matches_activity
error
Validation failed
reviewed_at_set_on_decision
error
Validation failed
Business Rules
one_approval_per_activity
Each activity has exactly one approval record. The UNIQUE constraint on activity_id at the database level enforces this. The approval record is created atomically with or immediately after activity submission.
rejection_requires_reason
A reviewer must supply a non-empty rejection_reason when transitioning status to rejected. This ensures submitting peer mentors receive actionable feedback.
reviewer_must_have_coordinator_or_admin_role
Only users with Coordinator or Organization Admin roles may set status to approved or rejected. The permission-validation-middleware enforces this at the API boundary; activity-approval-service enforces it at the service layer.
coordinator_scoped_to_local_association
Coordinators may only approve or reject activities from peer mentors within their own local association(s). The review-queue-service and approval-store apply local_association_id scoping to all queries.
optimistic_locking_required
Any status update must include the current version value. If the client-supplied version does not match the DB row, the write is rejected with a conflict error to prevent concurrent approvals from overwriting each other.
flagged_activities_excluded_from_bufdir
Activities with status=flagged must not be included in Bufdir report aggregations until the flag is resolved. The bufdir-report-service and report-data-aggregator filter by status=approved only.
corrections_audit_trail
When a reviewer corrects fields before approving (correctAndApprove), the original activity fields must remain unchanged and the corrections JSONB column must capture all modified fields and their new values for audit purposes.
flag_resolution_closes_flag
Resolving a flag must set flag_resolved_at, flag_resolved_by, and flag_resolution_action atomically. If the resolution action is reject or correct_and_approve, the status field must be updated in the same transaction.
all_decisions_written_to_audit_log
Every status transition (approve, reject, flag, resolve flag) must produce an immutable audit_log entry within the same database transaction capturing actor_id, action type, entity reference, and timestamp.