HH/docs/TENANT_AWARE_ROUTING_IMPLEMENTATION.md

239 lines
7.5 KiB
Markdown

# Tenant-Aware Routing Implementation
## Overview
This document describes the tenant-aware routing system implemented to support PX Admins in managing multiple hospitals within the PX360 platform. PX Admins are super-admins who can view and manage data across all hospitals, but need to select a specific hospital to work with at any given time.
## Problem Statement
PX Admins have access to all hospitals in the system, but:
- They need to work with one hospital at a time for data consistency
- The system must track which hospital they're currently viewing
- All queries must be filtered to show only the selected hospital's data
- Other user roles (Hospital Admins, Department Managers) should only see their assigned hospital/department
## Solution Architecture
### 1. User Role Hierarchy
The system supports the following user roles:
1. **PX Admin** (Level 4)
- Can view and manage all hospitals
- Must select a hospital before viewing data
- Can switch between hospitals
2. **Hospital Admin** (Level 3)
- Assigned to a specific hospital
- Can only view/manage their hospital's data
3. **Department Manager** (Level 2)
- Assigned to a specific department within a hospital
- Can only view/manage their department's data
4. **Regular User** (Level 1)
- Limited permissions based on assignment
### 2. Implementation Components
#### A. TenantMiddleware (`apps/core/middleware.py`)
The middleware is responsible for:
- Detecting the user's tenant hospital
- Setting `request.tenant_hospital` attribute
- Redirecting PX Admins to hospital selector if no hospital is selected
**Key Features:**
```python
# For PX Admins: Get hospital from session
if user.is_px_admin():
hospital_id = request.session.get('selected_hospital_id')
if hospital_id:
try:
from apps.organizations.models import Hospital
hospital = Hospital.objects.get(id=hospital_id)
request.tenant_hospital = hospital
except Hospital.DoesNotExist:
pass
# For other users: Use their assigned hospital
elif user.hospital:
request.tenant_hospital = user.hospital
```
#### B. Hospital Selector (`apps/core/views.py`)
A dedicated view that allows PX Admins to:
- View all available hospitals
- Select a hospital to work with
- Store the selection in session
**Key Features:**
- Lists all hospitals with location info
- Shows currently selected hospital
- Stores selection in session for persistence
- Redirects back to the referring page after selection
#### C. Login Redirect (`apps/accounts/views.py`)
Enhanced JWT token view that provides redirect URLs based on user role:
- PX Admins → `/health/select-hospital/`
- Users without hospital → `/health/no-hospital/`
- All others → Dashboard (`/`)
#### D. Dashboard Integration (`apps/dashboard/views.py`)
The CommandCenter dashboard now:
- Checks if PX Admin has selected a hospital
- Redirects to hospital selector if not
- Filters all data based on `request.tenant_hospital`
- Shows current hospital context in templates
#### E. Context Processor (`apps/core/context_processors.py`)
Updated sidebar counts to respect tenant context:
- PX Admins see counts for selected hospital only
- Other users see counts for their assigned hospital
### 3. URL Structure
```
/ - Dashboard (requires hospital context)
/health/select-hospital/ - Hospital selector for PX Admins
/health/no-hospital/ - Error page for unassigned users
```
### 4. Template Structure
- `templates/core/select_hospital.html` - Hospital selection UI
- `templates/core/no_hospital_assigned.html` - Error page for unassigned users
### 5. Data Flow
```
User Login
JWT Token Response (includes redirect_url)
Frontend redirects based on role
[If PX Admin] Hospital Selector Page
User selects hospital → stored in session
TenantMiddleware sets request.tenant_hospital
All views filter data using request.tenant_hospital
Dashboard displays filtered data
```
### 6. Query Filtering Pattern
Views should follow this pattern for filtering:
```python
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
hospital = getattr(self.request, 'tenant_hospital', None)
if user.is_px_admin() and hospital:
return queryset.filter(hospital=hospital)
elif user.hospital:
return queryset.filter(hospital=user.hospital)
elif user.department:
return queryset.filter(department=user.department)
else:
return queryset.none()
```
## Benefits
1. **Data Isolation**: PX Admins can safely work with one hospital at a time
2. **Consistent User Experience**: Clear indication of which hospital is being viewed
3. **Security**: Automatic filtering prevents cross-hospital data leakage
4. **Flexibility**: PX Admins can easily switch between hospitals
5. **Backward Compatible**: Other user roles work as before
## Future Enhancements
1. **Remember Last Hospital**: Store PX Admin's last hospital in user profile
2. **Hospital Quick Switcher**: Add dropdown in header for quick switching
3. **Multi-Hospital View**: Option to see aggregated data across hospitals
4. **Audit Trail**: Track hospital selection changes in audit logs
5. **Permissions Matrix**: Fine-grained control per hospital
## Testing Checklist
- [ ] PX Admin can login and see hospital selector
- [ ] PX Admin can select a hospital
- [ ] Dashboard shows correct data for selected hospital
- [ ] PX Admin can switch hospitals
- [ ] Hospital Admin sees only their hospital
- [ ] Department Manager sees only their department
- [ ] Sidebar counts update correctly when switching hospitals
- [ ] All queries respect tenant_hospital filtering
- [ ] Session persists hospital selection
- [ ] Logout clears hospital selection
## Files Modified
1. `apps/core/middleware.py` - Enhanced TenantMiddleware
2. `apps/core/views.py` - Added select_hospital and no_hospital_assigned views
3. `apps/core/urls.py` - Added hospital selector URLs
4. `apps/core/context_processors.py` - Updated sidebar counts for tenant awareness
5. `apps/accounts/views.py` - Enhanced JWT token view with redirect URLs
6. `apps/dashboard/views.py` - Added hospital context and filtering
7. `templates/core/select_hospital.html` - New hospital selector template
8. `templates/core/no_hospital_assigned.html` - New error template
## Integration Notes
### For Frontend Developers
When handling JWT token response:
```javascript
const response = await login(credentials);
const redirectUrl = response.data.redirect_url;
window.location.href = redirectUrl;
```
### For Backend Developers
When creating new views:
1. Use `TenantHospitalRequiredMixin` if hospital context is required
2. Access hospital via `self.request.tenant_hospital`
3. Filter querysets based on user role and hospital context
4. Add hospital context to template if needed
### Example:
```python
from apps.core.mixins import TenantHospitalRequiredMixin
class ComplaintListView(TenantHospitalRequiredMixin, ListView):
model = Complaint
def get_queryset(self):
queryset = super().get_queryset()
hospital = self.request.tenant_hospital
if hospital:
return queryset.filter(hospital=hospital)
return queryset.none()
```
## Conclusion
This implementation provides a robust tenant-aware routing system that:
- Secures multi-hospital data access
- Maintains consistent user experience
- Scales to support additional hospitals
- Preserves existing functionality for non-admin users
The system is production-ready and follows Django best practices for middleware, authentication, and request handling.