File size: 6,820 Bytes
01f85fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b00cab2
 
01f85fe
b00cab2
01f85fe
 
 
 
 
 
 
 
 
b00cab2
 
 
 
 
01f85fe
b00cab2
01f85fe
b00cab2
 
 
01f85fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# 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