File size: 6,280 Bytes
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
"""
Bulk Invitation Management Endpoints - CSV bulk user invitations
"""
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form
from sqlalchemy.orm import Session
from typing import Optional
from uuid import UUID

from app.api.deps import get_db, get_current_active_user
from app.models.user import User
from app.schemas.bulk_invitation import (
    BulkAnalysisResult,
    BulkExecutionRequest,
    BulkExecutionResult
)
from app.services.bulk_invitation_service import BulkInvitationService
from app.core.permissions import require_permission
import logging

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/invitations/bulk", tags=["Bulk Invitations"])

# Initialize service
bulk_invitation_service = BulkInvitationService()


@router.post("/analyze", response_model=BulkAnalysisResult, status_code=status.HTTP_201_CREATED)
@require_permission("invite_users")
async def analyze_bulk_invitation_csv(
    csv_file: UploadFile = File(..., description="CSV file with user data"),
    project_id: Optional[UUID] = Form(None, description="Project context (for project-level bulk invite)"),
    client_id: Optional[UUID] = Form(None, description="Client context (for org-level bulk invite)"),
    contractor_id: Optional[UUID] = Form(None, description="Contractor context (for org-level bulk invite)"),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """
    Upload and analyze CSV file for bulk invitations
    
    **CSV Format:**
    - Required columns: email, role
    - Optional columns: first_name, last_name, phone, invitation_method
    - Project-specific columns: project_role, region, subcontractor
    
    **Context:**
    - Provide ONE of: project_id, client_id, or contractor_id
    - project_id: Bulk invite to specific project
    - client_id/contractor_id: Bulk invite to organization
    
    **Authorization:**
    - platform_admin: Can bulk invite to any context
    - project_manager: Can bulk invite to their projects
    - dispatcher/sales_manager: Can bulk invite to their contractor's projects
    - client_admin: Can bulk invite to their client
    - contractor_admin: Can bulk invite to their contractor
    
    **Returns:**
    - Analysis of CSV with categorized users (in-memory, not persisted)
    - Frontend should store this and send back selected users for execution
    """
    # Validate file type
    if not csv_file.filename.endswith('.csv'):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="File must be a CSV file"
        )
    
    # Validate context (one and only one)
    contexts = [project_id, client_id, contractor_id]
    non_none_count = sum(1 for ctx in contexts if ctx is not None)
    
    if non_none_count == 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Must provide either project_id, client_id, or contractor_id"
        )
    if non_none_count > 1:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Can only provide one context: project_id, client_id, or contractor_id"
        )
    
    result = await bulk_invitation_service.analyze_csv(
        csv_file=csv_file,
        project_id=str(project_id) if project_id else None,
        client_id=str(client_id) if client_id else None,
        contractor_id=str(contractor_id) if contractor_id else None,
        current_user=current_user,
        db=db
    )
    
    return result



@router.post("/execute", response_model=BulkExecutionResult)
@require_permission("invite_users")
async def execute_bulk_invitation(
    execution_request: BulkExecutionRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_active_user)
):
    """
    Execute bulk invitation import
    
    **Process:**
    1. Add existing users to project (if project context)
    2. Send invitations to new users
    3. Return detailed results
    
    **Request Body:**
    - users_to_add: List of existing users to add to project
    - users_to_invite: List of new users to invite
    - Context: project_id, client_id, or contractor_id
    
    **Authorization:**
    - Same as analyze endpoint
    
    **Returns:**
    - Detailed results with success/failure counts
    - Individual results for each user
    
    **Note:**
    - Bulk operations are tracked in invitation_metadata.bulk_operation_id
    - No session persistence - frontend manages the flow
    """
    result = await bulk_invitation_service.execute_bulk_import(
        execution_request=execution_request,
        current_user=current_user,
        db=db
    )
    
    return result


@router.get("/template/download")
async def download_csv_template(
    template_type: str = "organization"
):
    """
    Download CSV template for bulk invitations
    
    **Template Types:**
    - organization: For org-level bulk invites (simpler)
    - project: For project-level bulk invites (includes project fields)
    
    **Returns:**
    - CSV file with headers and example row
    """
    from fastapi.responses import StreamingResponse
    import io
    
    if template_type == "project":
        headers = [
            "email", "first_name", "last_name", "phone", "role",
            "invitation_method", "project_role", "region", "subcontractor"
        ]
        example_row = [
            "john@example.com", "John", "Doe", "+254712345678", "field_agent",
            "whatsapp", "Installer", "Nairobi West", ""
        ]
    else:
        headers = [
            "email", "first_name", "last_name", "phone", "role", "invitation_method"
        ]
        example_row = [
            "john@example.com", "John", "Doe", "+254712345678", "field_agent", "whatsapp"
        ]
    
    # Create CSV in memory
    output = io.StringIO()
    import csv
    writer = csv.writer(output)
    writer.writerow(headers)
    writer.writerow(example_row)
    
    # Return as downloadable file
    output.seek(0)
    return StreamingResponse(
        iter([output.getvalue()]),
        media_type="text/csv",
        headers={
            "Content-Disposition": f"attachment; filename=bulk_invitation_template_{template_type}.csv"
        }
    )