swiftops-backend / docs /api /projects /project-overview.md
kamau1's picture
add unified project overview endpoint with caching and role-based response
19dd95f

Project Overview Endpoint

Purpose

Single endpoint that replaces 4 separate calls to get complete project structure. Returns project details, regions, roles, subcontractors, and team info based on user permissions.

Replaces:

  • GET /projects/{id}
  • GET /projects/{id}/regions
  • GET /projects/{id}/project-roles
  • GET /projects/{id}/subcontractors

Endpoint

GET /api/v1/projects/{project_id}/overview

Query Params:

  • refresh (optional): true to force cache refresh

Cache: 12 hours

Response Structure

For Managers/Admins

{
  "project": {
    "id": "uuid",
    "title": "Project Name",
    "project_type": "customer_service",
    "status": "active",
    "client_id": "uuid",
    "client_name": "Client Name",
    "contractor_id": "uuid",
    "contractor_name": "Contractor Name",
    "primary_manager_id": "uuid",
    "primary_manager_name": "Manager Name",
    "service_type": "ftth",
    "planned_start_date": "2025-01-01",
    "planned_end_date": "2025-12-31",
    "activation_requirements": [...],
    "photo_requirements": [...],
    "budget": {...},
    "is_closed": false,
    "created_at": "2025-01-01T00:00:00Z"
  },
  "regions": [
    {
      "id": "uuid",
      "region_name": "Nairobi West",
      "region_code": "NRB-W",
      "manager_id": "uuid",
      "manager_name": "Regional Manager",
      "is_active": true,
      "city": "Nairobi",
      "latitude": -1.2921,
      "longitude": 36.8219
    }
  ],
  "roles": [
    {
      "id": "uuid",
      "role_name": "Technician",
      "compensation_type": "commission",
      "commission_percentage": 15.0,
      "base_amount": 500.0,
      "is_active": true
    }
  ],
  "subcontractors": [
    {
      "id": "uuid",
      "subcontractor_id": "uuid",
      "subcontractor_name": "SubCo Ltd",
      "scope_description": "Installation work",
      "project_region_id": "uuid",
      "region_name": "Nairobi West",
      "contract_value": 50000.0,
      "is_active": true
    }
  ],
  "team_summary": {
    "total_members": 25,
    "by_role": {
      "field_agent": 15,
      "dispatcher": 3,
      "sales_agent": 5,
      "project_manager": 2
    },
    "by_region": {
      "Nairobi West": 10,
      "Mombasa": 8,
      "Project-wide": 7
    }
  },
  "my_involvement": null,
  "cached_at": "2025-12-02T10:00:00Z",
  "cache_expires_in_seconds": 43200
}

For Field Agents/Sales Agents

{
  "project": {
    "id": "uuid",
    "title": "Project Name",
    "project_type": "customer_service",
    "status": "active",
    "client_name": "Client Name",
    "contractor_name": "Contractor Name",
    "service_type": "ftth",
    "activation_requirements": [...],
    "photo_requirements": [...]
  },
  "regions": [
    {
      "id": "uuid",
      "region_name": "Nairobi West",
      "is_active": true,
      "city": "Nairobi"
    }
  ],
  "roles": null,
  "subcontractors": null,
  "team_summary": null,
  "my_involvement": {
    "user_id": "uuid",
    "user_name": "John Doe",
    "user_email": "john@example.com",
    "user_role": "field_agent",
    "team_role": "technician",
    "project_role_id": "uuid",
    "project_role_name": "Senior Technician",
    "assigned_region_id": "uuid",
    "assigned_region_name": "Nairobi West",
    "is_lead": false,
    "assigned_at": "2025-01-15T08:00:00Z",
    "subcontractor_id": null,
    "subcontractor_name": null
  },
  "cached_at": "2025-12-02T10:00:00Z",
  "cache_expires_in_seconds": 43200
}

Frontend Usage

Display Project Overview Page

// Fetch overview
const response = await fetch(`/api/v1/projects/${projectId}/overview`);
const data = await response.json();

// Show project header
showProjectHeader({
  title: data.project.title,
  status: data.project.status,
  client: data.project.client_name,
  contractor: data.project.contractor_name
});

// Show regions (all users see all regions)
data.regions.forEach(region => {
  const isMyRegion = data.my_involvement?.assigned_region_id === region.id;
  renderRegionCard(region, isMyRegion); // Highlight user's region
});

// Role-specific rendering
if (data.my_involvement) {
  // Field agent/sales agent view
  showMyInvolvement({
    role: data.my_involvement.team_role,
    region: data.my_involvement.assigned_region_name,
    projectRole: data.my_involvement.project_role_name
  });
} else {
  // Manager/admin view
  showRoles(data.roles);
  showSubcontractors(data.subcontractors);
  showTeamSummary(data.team_summary);
}

Check User's Region Assignment

// Highlight user's assigned region in UI
const myRegionId = data.my_involvement?.assigned_region_id;

data.regions.forEach(region => {
  const card = createRegionCard(region);
  if (region.id === myRegionId) {
    card.classList.add('my-region', 'highlighted');
  }
});

Display Requirements (Field Agents)

// Show what photos are required
data.project.photo_requirements.forEach(req => {
  console.log(`${req.type}: ${req.min_photos}-${req.max_photos} photos`);
});

// Show activation form fields
data.project.activation_requirements.forEach(field => {
  renderFormField({
    name: field.field,
    label: field.label,
    type: field.type,
    required: field.required,
    options: field.options
  });
});

Key Points

  1. Single call - Replaces 4 separate API calls
  2. Role-based - Response adapts to user permissions automatically
  3. Long cache - 12 hours (structure rarely changes)
  4. All see regions - Everyone sees all regions, UI highlights user's assigned region
  5. Field agents - See their involvement only, not other team members
  6. Managers - See complete project structure and team composition

Error Responses

  • 404 - Project not found
  • 403 - User not authorized to access project
  • 500 - Server error