Spaces:
Sleeping
Sleeping
| # Bulk User Invitations via CSV | |
| ## Overview | |
| The bulk invitation feature allows admins to invite multiple users at once by uploading a CSV file. The system intelligently handles: | |
| - **Existing users in project** → Skip (already members) | |
| - **Existing users not in project** → Add to project team | |
| - **New users** → Send invitations | |
| ## CSV Format | |
| ### Organization-Level Invitations | |
| For inviting users to a client or contractor organization: | |
| ```csv | |
| email,first_name,last_name,phone,role,invitation_method | |
| john@example.com,John,Doe,+254712345678,field_agent,whatsapp | |
| jane@example.com,Jane,Smith,+254798765432,dispatcher,email | |
| ``` | |
| **Required columns:** | |
| - `email` - User email address | |
| - `role` - System role (field_agent, dispatcher, project_manager, etc.) | |
| **Optional columns:** | |
| - `first_name` - First name (for personalization) | |
| - `last_name` - Last name | |
| - `phone` - Phone with country code (+254...) | |
| - `invitation_method` - whatsapp, email, or both (default: whatsapp) | |
| ### Project-Level Invitations | |
| For inviting users directly to a project with role assignments: | |
| ```csv | |
| email,first_name,last_name,phone,role,invitation_method,project_role,region,subcontractor | |
| john@example.com,John,Doe,+254712345678,field_agent,whatsapp,Installer,Nairobi West, | |
| jane@example.com,Jane,Smith,+254798765432,dispatcher,email,Site Supervisor,, | |
| ``` | |
| **Additional columns for projects:** | |
| - `project_role` - Role name or ID on the project (e.g., "Installer", "Technician") | |
| - `region` - Region name or ID (optional, NULL = project-wide) | |
| - `subcontractor` - Subcontractor name or ID (optional, NULL = main contractor) | |
| ## API Workflow | |
| ### Step 1: Analyze CSV | |
| Upload CSV and get analysis of users: | |
| ```http | |
| POST /api/v1/invitations/bulk/analyze | |
| Content-Type: multipart/form-data | |
| csv_file: <file> | |
| project_id: <uuid> (OR client_id OR contractor_id) | |
| ``` | |
| **Response:** | |
| ```json | |
| { | |
| "total_rows": 50, | |
| "valid_rows": 48, | |
| "invalid_rows": 2, | |
| "analysis": { | |
| "existing_in_project": [ | |
| { | |
| "email": "john@example.com", | |
| "user_id": "uuid", | |
| "name": "John Doe", | |
| "current_role": "field_agent", | |
| "status": "already_member" | |
| } | |
| ], | |
| "existing_not_in_project": [ | |
| { | |
| "email": "jane@example.com", | |
| "user_id": "uuid", | |
| "name": "Jane Smith", | |
| "current_role": "dispatcher", | |
| "action": "add_to_project", | |
| "csv_data": {...} | |
| } | |
| ], | |
| "new_users_to_invite": [ | |
| { | |
| "email": "bob@example.com", | |
| "action": "send_invitation", | |
| "csv_data": {...} | |
| } | |
| ], | |
| "errors": [ | |
| { | |
| "row": 5, | |
| "email": "invalid-email", | |
| "errors": ["Invalid email format"] | |
| } | |
| ] | |
| } | |
| } | |
| ``` | |
| ### Step 2: Review & Execute | |
| Frontend displays analysis to user for review. User confirms, then: | |
| ```http | |
| POST /api/v1/invitations/bulk/execute | |
| Content-Type: application/json | |
| { | |
| "project_id": "uuid", | |
| "users_to_add": [ | |
| { | |
| "email": "jane@example.com", | |
| "user_id": "uuid", | |
| "csv_data": {...} | |
| } | |
| ], | |
| "users_to_invite": [ | |
| { | |
| "email": "bob@example.com", | |
| "csv_data": {...} | |
| } | |
| ], | |
| "bulk_operation_id": "BULK_20251211_123456" | |
| } | |
| ``` | |
| **Response:** | |
| ```json | |
| { | |
| "results": { | |
| "users_added": { | |
| "success": 10, | |
| "failed": 0, | |
| "details": [...] | |
| }, | |
| "invitations_sent": { | |
| "success": 35, | |
| "failed": 2, | |
| "details": [...] | |
| } | |
| }, | |
| "summary": { | |
| "total_processed": 47, | |
| "successful": 45, | |
| "failed": 2 | |
| } | |
| } | |
| ``` | |
| ## Download CSV Template | |
| Get a pre-formatted CSV template: | |
| ```http | |
| GET /api/v1/invitations/bulk/template/download?template_type=project | |
| ``` | |
| Template types: | |
| - `organization` - For org-level invites (simpler) | |
| - `project` - For project-level invites (includes project fields) | |
| ## Authorization | |
| All roles with `invite_users` permission can use bulk invitations: | |
| **Platform Admin:** | |
| - Can bulk invite to any context (organization or project) | |
| **Client Admin:** | |
| - Can bulk invite to their client organization only | |
| **Contractor Admin:** | |
| - Can bulk invite to their contractor organization only | |
| **Project Manager:** | |
| - Can bulk invite to their own projects | |
| - Must invite to their contractor's organization | |
| **Dispatcher:** | |
| - Can bulk invite to projects in their contractor | |
| - Must invite to their contractor's organization | |
| **Sales Manager:** | |
| - Can bulk invite to projects in their contractor | |
| - Must invite to their contractor's organization | |
| **Note:** Service-level authorization validates that users can only invite within their scope (own organization/projects) | |
| ## Tracking | |
| Bulk invitations are tracked in the `invitation_metadata` JSONB field: | |
| ```json | |
| { | |
| "bulk_import": true, | |
| "bulk_operation_id": "BULK_20251211_123456" | |
| } | |
| ``` | |
| This allows filtering/reporting on bulk vs individual invitations without needing a separate table. | |
| ## Validation Rules | |
| - Email format validation | |
| - Phone must start with + and country code | |
| - Role must be valid enum value | |
| - Project role/region/subcontractor must exist if specified | |
| - Max 1000 rows per CSV (configurable via `MAX_BULK_INVITATION_ROWS` env var) | |
| - Duplicate detection: won't create duplicate pending invitations | |
| ## Error Handling | |
| - Invalid CSV rows are reported but don't block valid rows | |
| - Each user operation (add/invite) is independent | |
| - Partial success is supported (some succeed, some fail) | |
| - Detailed error messages for each failure | |
| ## Frontend Flow | |
| 1. User uploads CSV | |
| 2. Call `/analyze` endpoint | |
| 3. Display categorized results: | |
| - "10 users will be added to project" | |
| - "35 invitations will be sent" | |
| - "5 users already in project (skipped)" | |
| - "2 errors found" | |
| 4. User reviews and optionally deselects users | |
| 5. User confirms | |
| 6. Call `/execute` endpoint with selected users | |
| 7. Display results with success/failure breakdown | |
| ## Implementation Notes | |
| ### No Database Table Needed | |
| - Analysis results are returned in-memory (not persisted) | |
| - Frontend manages the review/confirmation flow | |
| - Bulk operations tracked via `invitation_metadata.bulk_operation_id` in existing `user_invitations` table | |
| - Simpler architecture, no session cleanup needed | |
| ### Smart User Resolution | |
| - Project roles, regions, and subcontractors can be specified by name or UUID | |
| - Case-insensitive matching for names | |
| - Validates that referenced entities exist and belong to the project | |
| ### Files Created | |
| - `src/app/schemas/bulk_invitation.py` - Pydantic schemas | |
| - `src/app/services/bulk_invitation_service.py` - Business logic | |
| - `src/app/api/v1/bulk_invitations.py` - API endpoints | |
| - Registered in `src/app/api/v1/router.py` | |
| ### Reuses Existing Infrastructure | |
| - Uses existing `InvitationService` for sending invitations | |
| - Uses existing `UserInvitation` model with metadata tracking | |
| - Uses existing `ProjectTeam` model for adding users to projects | |
| - No new database migrations required | |