Skip to main content
Version: RK Auto

Admin Service Assignment

Author(s)

  • Sanket Mal
  • Ashik Ikbal
  • ...

Last Updated Date

[2026-02-18]


SRS References


Version History

VersionDateChangesAuthor
1.02026-02-09Initial draft with technician suggestions and assignment APIsSanket Mal, Ashik
1.12026-02-18Updated SignalR event model to ServiceRequestDetailedResponse with dual group broadcasting (Request-{requestId} and AllAdmins)Ashik
1.22026-02-18Added Get Current Job API, Start Journey API, and journey_started SignalR eventAshik

Feature Overview

Objective:
Enable admin/dispatcher to manually assign service requests to technicians in an efficient and data-driven manner. The system provides visibility into technician availability, current workload, and queued jobs, allowing admins to assign new service requests to the most suitable technician based on availability, location, and skill set.

Scope:

  • Technician availability and workload visualization
  • Service request assignment to technicians
  • Job queue management and prioritization
  • Real-time availability calculation
  • Multi-job support per technician
  • SignalR event notifications for assignment and updates

Dependencies:

  • SignalR (WebSocket for real-time events)
  • PostgreSQL (persistent storage of requests and assignments)
  • Service Request & Technician models (from van-location-tracking and service-booking features)
  • JWT authentication

Requirements

Functional:

  1. Admin can fetch a list of all available technicians with their current workload and availability.
  2. System calculates the availability of each technician based on their active and queued jobs.
  3. Admin can view all jobs (active and queued) assigned to a technician before making an assignment.
  4. Admin can assign a service request to a technician.
  5. Admin can specify a scheduled date/time for assignment (optional, for future scheduling).
  6. System prevents assignment if technician is at max capacity.
  7. System prevents assignment if technician would exceed maximum daily work hours (8 hours).
  8. System prevents duplicate assignment of the same request.
  9. Technician, Customer and all admins receive a SignalR request_assigned event with detailed service request information immediately upon assignment.
  10. Customer receives notification of technician assignment.
  11. Assignment audit trail is maintained for all operations.

Non-Functional:

  1. Low latency (<1s) for suggestions API.
  2. Accurate availability calculation based on job estimates.
  3. High reliability for assignment operations.
  4. Secure access (JWT, role-based: admin only).
  5. Support for 100+ concurrent technicians and requests.

Design Specifications

Architecture Overview

The admin service assignment feature integrates with the existing van-location-tracking and service-booking systems:

  1. Admin UI calls /service-request/{requestId}/technician-suggestions to fetch available technicians for a specific service request.
  2. Backend service calculates technician availability based on current and queued jobs.
  3. Admin selects a technician and calls /service-request/{requestId}/assign with technicianId and scheduledTime.
  4. Backend updates the service request, sends SignalR events, and maintains audit logs.

Data Models

Below are the main models used in the API responses.

// Core models (previously defined)
public record TechnicianBasicInfo
{
public Guid? UserId { get; set; }
public Guid TechnicianId { get; init; }
public string TechnicianCode { get; init; } = string.Empty;
public TechnicianAvailabilityStatus? AvailabilityStatus { get; init; }
public string FirstName { get; init; } = string.Empty;
public string LastName { get; init; } = string.Empty;
public string? Email { get; init; }
public string? PhoneNumber { get; init; }
public string? ProfileImageUrl { get; set; }
}

public record ServiceRequestBasicInfo
{
public Guid RequestId { get; init; }
public required string RequestNumber { get; set; }
public Guid CustomerId { get; init; }
public Guid? TechnicianId { get; init; }
public RequestStatus RequestStatus { get; init; }
public decimal Latitude { get; init; }
public decimal Longitude { get; init; }
public string? Location { get; init; }
public DateTime RequestedAt { get; init; }
}

public record ServiceRequestSummary : ServiceRequestBasicInfo
{
public DateTime ScheduledAt {get; init;}
public TimeSpan? EstimatedDuration { get; init; }
}

// New models for technician suggestions and assignment
public record TechnicianSuggestionsResponse
{
public TechnicianBasicInfo TechnicianInfo { get; init; }
public int Eta { get; init; }
public int JobCompletedToday { get; init; }
public int JobQueueToday { get; init; }
}

public record GetTechnicianSuggestionsFilter
{
public int PageNumber { get; init; } = 1;
public int RowsPerPage { get; init; } = 10;
public string? SearchKeyword { get; init; }
}

public record AssignServiceRequestModel
{
public Guid RequestId { get; init; }
public Guid TechnicianId { get; init; }
public DateTime ScheduledAt { get; init; }
}

public record CommonResponse
{
public int Status { get; init; } // 200, 400, 401, 403, 404, 409, 500
public string? Message { get; init; }
}

// SignalR Event Models
public record ServiceRequestDetailedResponse
{
// --- Request Core ---
public Guid RequestId { get; init; }
public Guid CustomerId { get; init; }
public Guid VehicleId { get; init; }
public required string RequestNumber { get; set; }
public ServiceScheduleType ServiceScheduleType { get; init; } // OnDemand / Scheduled
public LocationPreference LocationPreference { get; init; } // CustomerLocation / ServiceCenter
public RequestStatus RequestStatus { get; init; } // Pending / Accepted / InProgress / Completed / Canceled
public DateTime RequestedAt { get; init; }
public DateTime? ScheduledAt { get; init; }
public DateTime? StartedAt { get; init; }
public DateTime? StoppedAt { get; init; }
public DateTime? CompletedAt { get; init; }
public TimeSpan? Duration { get; init; }

public DateTime? CancelledAt { get; init; }
public UserBasicInfo? CancelledBy { get; init; }
public string? CancellationReason { get; init; }

// --- Location ---
public required decimal Latitude { get; init; }
public required decimal Longitude { get; init; }
public string? Location { get; init; }

// --- Description ---
public string? Description { get; init; }

// --- Payment ---
public decimal? EstimatedPrice { get; init; }
public PaymentStatus PaymentStatus { get; init; }
public string? PaymentMethod { get; init; }
public string? TransactionNo { get; init; }
public decimal? Subtotal { get; init; }
public decimal? PaidAmount { get; init; }
public DateTime? PaymentCompletedAt { get; init; }
/// <summary>
/// Nullable — populated only when a completed service request has an invoice.
/// Retrieved via LEFT JOIN on invoices table using servicerequestid.
/// </summary>
public Guid? InvoiceId { get; init; }
public string? InvoiceNumber { get; init; }

// --- Feedback ---
public int? Rating { get; init; }
public string? Feedback { get; init; }

// --- Completion Details ---
public int? Mileage { get; init; }
public List<string>? Evidences { get; init; }

// --- Nested objects ---
public required CustomerBasicInfo Customer { get; init; }

public required VehicleBasicInfo Vehicle { get; init; }

public VanInfo? Van { get; init; }
public TechnicianBasicInfo? Technician { get; init; }

public List<RequestedServiceInfo> Services { get; init; } = new();
}


public record RequestedServiceInfo
{
public Guid ServiceId { get; init; }
public required string ServiceCode { get; init; }
public required string ServiceName { get; init; }
public string? ServiceDescription { get; init; }
public decimal? ServicePrice { get; init; }
public ServiceStatus ServiceStatus { get; init; } // Pending / Completed / Rejected
public string? ReasonOfRejection { get; init; }
}

public record CustomerBasicInfo
{
public Guid UserId { get; init; }
// Customer identification
public Guid CustomerId { get; init; }
public string CustomerCode { get; init; } = string.Empty;
// User information
public string Email { get; init; } = string.Empty;
public string FirstName { get; init; } = string.Empty;
public string LastName { get; init; } = string.Empty;
public string PhoneNumber { get; init; } = string.Empty;
public Gender? Gender { get; init; }
}

public record VehicleBasicInfo
{
public required string Vin { get; init; }
// Basic Identity
public string? Make { get; init; }
public string? Model { get; init; }
public int? Year { get; init; }
public string? Trim { get; init; }
// Body
public string? VehicleType { get; init; } // Passenger Car
public string? BodyType { get; init; } // Coupe, Sedan
}

public record VanInfo
{
public Guid VanId { get; init; }
public string VanNumber { get; init; } = string.Empty;
public string RegistrationNumber { get; init; } = string.Empty;
public string VIN { get; init; } = string.Empty;
public string Make { get; init; } = string.Empty;
public string Model { get; init; } = string.Empty;
public int Year { get; init; }
}

public record CustomerAssignmentNotification
{
public Guid RequestId { get; init; }
public string TechnicianCode { get; init; } = string.Empty;
public string TechnicianName { get; init; } = string.Empty;
public string Message { get; init; } = string.Empty;
public DateTime ScheduledTime { get; init; }
public DateTime Timestamp { get; init; }
}

public class ServerPaginatedData<T>
{
public List<T> Data { get; set; } = [];
public int TotalNumber { get; set; }
public bool HasPreviousPage { get; set; }
public bool HasNextPage { get; set; }
public int TotalPages { get; set; }
public int PageNumber { get; set; }
public int RowsPerPage { get; set; }
}

// Enums
public enum RequestStatus
{
Pending = 1,
Assigned,
EnRoute,
InProgress,
Stopped,
Completed,
Canceled,
Accepted,
Ringing
}

public enum ServiceScheduleType
{
OnDemand = 1,
Scheduled = 2
}

public enum LocationPreference
{
CustomerLocation = 1,
ServiceCenter = 2
}

public enum PaymentStatus
{
Pending = 1,
Paid,
PartiallyPaid,
Refunded,
PartiallyRefunded,
Failed
}

public enum ServiceStatus
{
Pending = 1,
Completed = 2,
Rejected = 3
}

RequestStatus Enum

The RequestStatus enum represents the lifecycle state of a service request:

StatusValueDescription
Pending1Service request has been created but not yet accepted by customer/system. Initial state.
Accepted2Admin has accepted/verified the service request. Ready for admin to assign to a technician.
InProgress3Technician is actively working on the service. Technician may have arrived and started the job.
Completed4Service has been successfully completed. Technician submitted completion proof/invoice.
Canceled5Service request was canceled by customer, admin, or technician. No further action.
Ringing6
Assigned7Admin has assigned the request to a technician. Technician has been notified of the assignment.

In the context of Admin Service Assignment:

  • Request is created with status Pending
  • Admin reviews and accepts/verifies the request → status becomes Accepted
  • Admin assigns to technician → status becomes Assigned
  • Technician receives assignment notification
  • When technician starts work → status becomes InProgress
  • Queued jobs will have status Assigned (assigned but not yet started)

ETA Calculation Workflow

The eta field represents the estimated time in minutes from the current time until the technician becomes available to accept a new service request.

Calculation Formula:

eta = Time remaining for current job (if any)
+ Sum of estimated duration of all queued jobs
+ Travel time from last job → new job location

Detailed Example:

Assume today is February 9, 2026 at 02:00 PM (14:00) and Technician A has the following jobs assigned:

JobScheduled TimeFrom LocationTo LocationService TypeTravel TimeService DurationStatus
Job 110:00 AMBaseDelhiOil Change15 min45 minCompleted
Job 203:00 PMDelhiGurgaonTire Replacement20 min60 minInProgress (Started at 3:00 PM)
Job 305:30 PMGurgaonNoidaCar Wash25 min30 minAssigned (Queued)

New Service Request from customer at Bangalore (travel time: 40 minutes from Noida)

Calculation Step-by-Step:

  1. Current Time: 2:00 PM (14:00)

  2. Time remaining for current job (Job 2):

    • Job 2 started at: 3:00 PM
    • Job 2 is scheduled in the future, so wait time until start: 60 minutes
    • Job 2 service duration: 60 minutes
    • Time remaining for Job 2: 60 + 60 = 120 minutes
  3. Sum of estimated duration of all queued jobs:

    • Job 3 (Queued): Travel time 25 min + Service duration 30 min = 55 minutes
    • Total queued time: 55 minutes
  4. Travel time from last job to new job location:

    • Last job (Job 3) is in Noida
    • New service request customer is in Bangalore
    • Travel time from Noida → Bangalore: 40 minutes
  5. Calculate ETA:

    • ETA = 120 + 55 + 40 = 215 minutes
    • ETA = 3 hours 35 minutes from 2:00 PM = approximately 5:35 PM

Interpretation:

  • Technician A will be available to accept the new service request at approximately 5:35 PM (215 minutes from current time of 2:00 PM).

Another Example (Technician with No Active Jobs):

If Technician B has no active or queued jobs:

  • eta = Travel time from current location → new job location
  • Example: If Technician B is currently at location X and the new service request's customer is at location Y, with a travel time of 15 minutes:
    • eta = 15 minutes
    • Once the technician reaches the customer location, they can immediately start the service

Another Example (Technician with Only Queued Jobs):

If Technician C has no active jobs now, but 2 queued jobs scheduled later today:

  • eta = Sum of estimated duration of all queued jobs + Travel time from last job → new job location
  • Example:
    • Queued Job 1 (Gurgaon): 30 min travel + 45 min service = 75 minutes
    • Queued Job 2 (Noida): 20 min travel + 30 min service = 50 minutes
    • Travel time from Noida to new job location (Delhi): 35 minutes
    • eta = 75 + 50 + 35 = 160 minutes (2 hours 40 minutes)

Key Points:

  • Only InProgress and Assigned jobs are counted in the eta calculation
  • Completed jobs are ignored
  • Travel time between jobs is included
  • For current/in-progress jobs, use remaining time (not elapsed time)
  • For queued jobs, use full estimated duration (travel + service)
  • Always add travel time from the last job location to the new service location

Estimated vs. Actual Time Variance

In real-world scenarios, actual travel and service times often differ from estimates. The system must account for these variances:

Real-World Examples:

ScenarioEstimateActualVarianceImpact
Heavy traffic delay20 min travel43 min travel+23 min (115%)Technician arrives 23 minutes late
Complex service issue30 min service54 min service+24 min (80%)Service takes 24 minutes longer than expected
Quick job completion45 min service35 min service-10 min (22%)Service completes 10 minutes earlier
Optimized route25 min travel18 min travel-7 min (28%)Technician arrives 7 minutes early

How the System Handles Variance:

  1. During Planning (ETA Calculation):

    • Uses estimated travel and service times from database
    • Provides admin with realistic availability expectations
    • Example: If estimated time shows availability at 3:55 PM, actual could be 4:18 PM (with variance)
  2. During Execution (Real-Time Updates):

    • Technician updates job status via mobile app when starting/completing work
    • System records actual travel time (arrival time - departure time from previous location)
    • System records actual service time (completion time - arrival time at service location)
    • Remaining jobs' availability is recalculated dynamically
  3. Post-Job Analysis:

    • Variance between estimated and actual times is logged
    • Historical data is used to improve future estimates
    • Patterns are identified (e.g., traffic rush hours, complex vs. simple services)
    • Estimates are periodically refined based on actual performance

Common Variance Causes:

  • Traffic & Route: GPS routing estimates don't account for real-time traffic
  • Service Complexity: Unexpected issues extend service duration
  • Customer Behavior: Delayed arrivals, additional requests add time
  • Weather: Bad weather increases travel time
  • Technician Efficiency: Experience level affects service speed

Recommended Mitigation Strategies:

  1. Buffer Time: Add 10-15% buffer to estimates for planning
  2. Real-Time Tracking: Use GPS and app updates to monitor actual progress
  3. Dynamic Reassignment: If variance detected, reassign pending jobs to available technicians
  4. SLA Monitoring: Alert admin if variance risks missing SLAs
  5. Historical Accuracy: Continuously improve estimates based on actual data

Example Recalculation with Actual Times:

Assume Technician A's Job 2 starts taking longer than estimated:

  • Estimated completion: 4:00 PM (60 min service)
  • Actual progress (at 3:30 PM): Only 15 minutes into job, appears will take 50+ minutes
  • Revised estimated completion: ~4:20 PM
  • eta recalculated: Now ~140 minutes instead of 115
  • Admin notified: Job 3 assignment may be delayed

Admin Service Assignment Endpoints

All endpoints require JWT authentication with admin role.

EndpointMethodPurposeParametersRequest ModelResponse ModelStatus Codes
/service-request/{requestId}/technician-suggestionsGETFetch available technicians for a specific service request with workload, jobs, availabilityJWT in header, requestId, GetTechnicianSuggestionsFilter (query params)GetTechnicianSuggestionsFilterServerPaginatedData<TechnicianSuggestionsResponse>200, 400, 401, 403, 404, 500
/technician/current-requestGETGet the first job in technician's queue (queue position 1) with complete detailsJWT in header (technician role)ServiceRequestDetailedResponse200, 401, 403, 404, 500
/service-request/{technicianId}/scheduled-requestsGETFetch scheduled jobs for a specific technician on a given date (No Pagination)JWT in header, technicianId, date (query param - required), searchKeyword (optional)List<ServiceRequestSummary>200, 400, 401, 403, 404, 500
/service-request/assignPOSTManually assign a service request to a technician with optional schedulingJWT in header, requestIdAssignServiceRequestModelCommonResponse200, 400, 401, 403, 404, 409, 500
/service-request/{requestId}/start-journeyPOSTStart journey to customer location, transitions technician from Available to EnRoute statusJWT in header (technician role), requestIdCommonResponse200, 400, 401, 403, 404, 500

API Interfaces

1. GET /service-request/{requestId}/technician-suggestions - Technician Suggestions API

Purpose:
Fetch a list of all available technicians for a specific service request with their current workload, jobs, and availability. Admin uses this to find the best technician for assignment.

Authentication: JWT token in Authorization header

Path Parameters:

  • requestId (GUID): The unique identifier of the service request

Query Parameters (via GetTechnicianSuggestionsFilter):

  • pageNumber (int, optional): Page number for pagination (default: 1, min: 1)
  • rowsPerPage (int, optional): Number of records per page (default: 10, min: 1, max: 100)
  • searchKeyword (string, optional): Search keyword to filter technicians by:
    • First name
    • Last name
    • Full name (first + last)
    • Technician code
    • Email
    • Phone number
    • Technician ID
    • Search is case-insensitive and supports partial matches

Request Model: GetTechnicianSuggestionsFilter

Response Model: ServerPaginatedData<TechnicianSuggestionsResponse>

Status Codes: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 500 (Server Error)


2. GET /technician/current-request - Get Current Job API

Purpose:
Retrieve the first job in the technician's queue (queue position 1) with complete details. Technicians can only access their own current job.

Authentication: JWT token in Authorization header (technician role required)

Path Parameters: None

Query Parameters: None

Request Model: None

Response Model: ServiceRequestDetailedResponse

Status Codes: 200 (OK), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 500 (Server Error)

Business Rules:

  • Technician can only retrieve their own current job
  • Returns the job at queue position 1 (first in queue)
  • If no jobs in queue, returns 404 Not Found

3. POST /service-request/assign - Assign Service to Technician API

Purpose:
Manually assign a service request to a technician. Validates availability, prevents conflicts, and triggers assignment events.

Authentication: JWT token in Authorization header (admin role required)

Request Model: AssignServiceRequestModel

Response Model: CommonResponse

Status Codes: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 500 (Server Error)


4. POST /service-request/{requestId}/start-journey - Start Journey to Customer API

Purpose:
Transitions technician from Available to EnRoute status and notifies customer and admins. Request must be at queue position 1. This indicates the technician has started traveling to the customer location.

Authentication: JWT token in Authorization header (technician role required)

Path Parameters:

  • requestId (GUID): The unique identifier of the service request

Query Parameters: None

Request Model: None

Response Model: CommonResponse

Status Codes: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 500 (Server Error)

Business Rules:

  • Request must be assigned to the authenticated technician
  • Request must be at queue position 1 (first in queue)
  • Technician status changes from Available to EnRoute
  • Triggers journey_started SignalR event to customer and admins
  • Request must be in Assigned status

API Examples

1. Technician Suggestions API

Request (Basic):

GET /service-request/550e8400-e29b-41d4-a716-446655440010/technician-suggestions?pageNumber=1&rowsPerPage=10
Authorization: Bearer {JWT_TOKEN}

Request (With Search):

GET /service-request/550e8400-e29b-41d4-a716-446655440010/technician-suggestions?pageNumber=1&rowsPerPage=10&searchKeyword=john
Authorization: Bearer {JWT_TOKEN}

Response (200 OK):

{
"data": [
{
"technicianInfo": {
"technicianId": "770e8400-e29b-41d4-a716-446655440002",
"technicianCode": "TECH-001",
"availabilityStatus": "EnRoute",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phoneNumber": "+91-9876543210"
},
"eta": 80,
"jobCompletedToday": 1,
"jobQueueToday": 2
},
{
"technicianInfo": {
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"technicianCode": "TECH-002",
"availabilityStatus": "Available",
"firstName": "Priya",
"lastName": "Sharma",
"email": "priya.sharma@example.com",
"phoneNumber": "+91-9876543211"
},
"eta": 0,
"jobCompletedToday": 3,
"jobQueueToday": 0
}
],
"totalNumber": 12,
"hasPreviousPage": false,
"hasNextPage": true,
"totalPages": 3,
"pageNumber": 1,
"rowsPerPage": 5
}

Error Response (400 Bad Request - Invalid Pagination):

{
"status": 400,
"message": "Page number must be greater than 0"
}

Error Response (400 Bad Request - Invalid Request Status):

{
"status": 400,
"message": "Cannot get technician suggestions. Service request must be in Pending status. Current status: Assigned"
}

Error Response (401 Unauthorized):

{
"status": 401,
"message": "Missing or invalid JWT token"
}

2. Get Current Job API

Request:

GET /technician/current-request
Authorization: Bearer {JWT_TOKEN}

Response (200 OK):

{
"requestId": "550e8400-e29b-41d4-a716-446655440010",
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"vehicleId": "b2c3d4e5-e29b-41d4-a716-446655441000",
"requestNumber": "RQ-2026-001240",
"serviceScheduleType": 2,
"locationPreference": 1,
"requestStatus": 7,
"requestedAt": "2026-02-08T10:15:00Z",
"scheduledAt": "2026-02-09T12:00:00Z",
"latitude": 28.615,
"longitude": 77.211,
"location": "789 Service Hub, New Delhi, 110003",
"description": "Regular maintenance and oil change service",
"estimatedPrice": 2500.00,
"paymentStatus": 1,
"paymentMethod": "CreditCard",
"rating": null,
"feedback": null,
"customer": {
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"firstName": "Rajesh",
"lastName": "Kumar",
"email": "rajesh.kumar@example.com",
"phoneNumber": "+91-9876543210"
},
"vehicle": {
"vehicleId": "b2c3d4e5-e29b-41d4-a716-446655441000",
"make": "Maruti Suzuki",
"model": "Swift",
"year": 2022,
"licensePlate": "DL 12 AB 1234",
"vin": "MA3ERLF1S00123456"
},
"van": {
"vanId": "c3d4e5f6-e29b-41d4-a716-446655442000",
"vanCode": "VAN-001",
"vanName": "Service Van Alpha"
},
"technician": {
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"technicianCode": "TECH-002",
"availabilityStatus": 2,
"firstName": "Priya",
"lastName": "Sharma",
"email": "priya.sharma@example.com",
"phoneNumber": "+91-9876543211"
},
"services": [
{
"serviceId": "d4e5f6g7-e29b-41d4-a716-446655443000",
"serviceCode": "OIL-001",
"serviceName": "Oil Change",
"serviceDescription": "Full synthetic oil change service",
"servicePrice": 1500.00,
"serviceStatus": 1,
"reasonOfRejection": null
},
{
"serviceId": "e5f6g7h8-e29b-41d4-a716-446655444000",
"serviceCode": "INSP-001",
"serviceName": "Vehicle Inspection",
"serviceDescription": "Complete vehicle safety inspection",
"servicePrice": 1000.00,
"serviceStatus": 1,
"reasonOfRejection": null
}
]
}

Error Response (404 Not Found - No Current Job):

{
"status": 404,
"message": "No current job found"
}

Error Response (401 Unauthorized):

{
"status": 401,
"message": "Missing or invalid JWT token"
}

Error Response (403 Forbidden):

{
"status": 403,
"message": "Only technicians can access their current job"
}

3. GET /service-request/{technicianId}/service-requests - Technician Scheduled Jobs API

Purpose:
Fetch all scheduled jobs (Assigned and InProgress) for a specific technician on a given date.
Used by admin/dispatcher to review technician workload before making new assignments.


Authentication

  • JWT token required
  • Role: admin
Authorization: Bearer {JWT_TOKEN}

Response (200 OK):

[
{
"requestId": "550e8400-e29b-41d4-a716-446655440010",
"requestNumber": "RQ-2026-001240",
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"requestStatus": "Accepted",
"latitude": 28.6139,
"longitude": 77.209,
"location": "Connaught Place, New Delhi",
"requestedAt": "2026-02-08T10:15:00Z",
"scheduledAt": "2026-02-09T12:00:00Z",
"estimatedDuration": "01:00:00"
},
{
"requestId": "660e8400-e29b-41d4-a716-446655441111",
"requestNumber": "RQ-2026-001241",
"customerId": "f9e8d7c6-e29b-41d4-a716-446655442222",
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"requestStatus": "InProgress",
"latitude": 28.5355,
"longitude": 77.391,
"location": "Sector 62, Noida",
"requestedAt": "2026-02-08T14:20:00Z",
"scheduledAt": "2026-02-09T15:30:00Z",
"estimatedDuration": "00:45:00"
}
]

4. Assign Service to Technician API

Request:

POST /service-request/assign
Authorization: Bearer {JWT_TOKEN}
Content-Type: application/json

{
"requestId": "550e8400-e29b-41d4-a716-446655440010",
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"scheduledTime": "2026-02-09T12:00:00Z"
}

Response (200 OK):

{
"status": 200,
"message": "Service request assigned successfully to technician"
}

Error Response (400 Bad Request - Missing Fields):

{
"status": 400,
"message": "RequestId and TechnicianId are required"
}

Error Response (404 Not Found - Request Not Found):

{
"status": 404,
"message": "Service request not found"
}

Error Response (404 Not Found - Technician Not Found):

{
"status": 404,
"message": "Technician not found"
}

Error Response (409 Conflict - Already Assigned):

{
"status": 409,
"message": "Service request is already assigned to another technician"
}

Error Response (409 Conflict - Capacity Exceeded):

{
"status": 409,
"message": "Technician has reached maximum concurrent jobs (max: 2)"
}

Error Response (409 Conflict - Maximum Work Hours Exceeded):

{
"status": 409,
"message": "Technician exceeds maximum daily work hours (8). Current: 7.5h, Requested: 1h"
}

Error Response (403 Forbidden - Insufficient Role):

{
"status": 403,
"message": "Only admins can assign service requests"
}

5. Start Journey to Customer API

Request:

POST /service-request/550e8400-e29b-41d4-a716-446655440010/start-journey
Authorization: Bearer {JWT_TOKEN}

Response (200 OK):

{
"status": 200,
"message": "Journey started successfully"
}

Error Response (400 Bad Request - Not at Queue Position 1):

{
"status": 400,
"message": "Cannot start journey. Request is not at queue position 1"
}

Error Response (400 Bad Request - Invalid Status):

{
"status": 400,
"message": "Cannot start journey. Request must be in Assigned status. Current status: InProgress"
}

Error Response (404 Not Found - Request Not Found):

{
"status": 404,
"message": "Service request not found"
}

Error Response (403 Forbidden - Not Assigned to Technician):

{
"status": 403,
"message": "This request is not assigned to you"
}

Error Response (401 Unauthorized):

{
"status": 401,
"message": "Missing or invalid JWT token"
}

SignalR Events

SignalR events are sent to specific groups to ensure targeted communication with technicians, customers, and admins. Events are broadcasted using SignalR group-based messaging.

Event: request_assigned (Server → Technician, Customer & All Admins)

Model: ServiceRequestDetailedResponse

Broadcast Groups:

  • Request-{requestId} (assigned technician and customer connected to this request)
  • AllAdmins (all connected admin/dispatcher users)

Sent to the assigned technician and all admins immediately after successful assignment. This event provides complete service request details including customer, vehicle, services, and technician information.

Example Payload:

{
"requestId": "550e8400-e29b-41d4-a716-446655440010",
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"vehicleId": "b2c3d4e5-e29b-41d4-a716-446655441000",
"requestNumber": "RQ-2026-001240",
"serviceScheduleType": 2,
"locationPreference": 1,
"requestStatus": 7,
"requestedAt": "2026-02-08T10:15:00Z",
"scheduledAt": "2026-02-09T12:00:00Z",
"latitude": 28.615,
"longitude": 77.211,
"location": "789 Service Hub, New Delhi, 110003",
"description": "Regular maintenance and oil change service",
"estimatedPrice": 2500.00,
"paymentStatus": 1,
"paymentMethod": "CreditCard",
"rating": null,
"feedback": null,
"customer": {
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"firstName": "Rajesh",
"lastName": "Kumar",
"email": "rajesh.kumar@example.com",
"phoneNumber": "+91-9876543210"
},
"vehicle": {
"vehicleId": "b2c3d4e5-e29b-41d4-a716-446655441000",
"make": "Maruti Suzuki",
"model": "Swift",
"year": 2022,
"licensePlate": "DL 12 AB 1234",
"vin": "MA3ERLF1S00123456"
},
"van": {
"vanId": "c3d4e5f6-e29b-41d4-a716-446655442000",
"vanCode": "VAN-001",
"vanName": "Service Van Alpha"
},
"technician": {
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"technicianCode": "TECH-002",
"availabilityStatus": 2,
"firstName": "Priya",
"lastName": "Sharma",
"email": "priya.sharma@example.com",
"phoneNumber": "+91-9876543211"
},
"services": [
{
"serviceId": "d4e5f6g7-e29b-41d4-a716-446655443000",
"serviceCode": "OIL-001",
"serviceName": "Oil Change",
"serviceDescription": "Full synthetic oil change service",
"servicePrice": 1500.00,
"serviceStatus": 1,
"reasonOfRejection": null
},
{
"serviceId": "e5f6g7h8-e29b-41d4-a716-446655444000",
"serviceCode": "INSP-001",
"serviceName": "Vehicle Inspection",
"serviceDescription": "Complete vehicle safety inspection",
"servicePrice": 1000.00,
"serviceStatus": 1,
"reasonOfRejection": null
}
]
}

Event: journey_started (Customer, Technician & All Admins)

Model: ServiceRequestDetailedResponse

Broadcast Group: Request-{requestId}

Sent when a technician starts their journey to the customer location. Notifies customer and admins that the technician is en route.

{
"requestId": "550e8400-e29b-41d4-a716-446655440010",
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"vehicleId": "b2c3d4e5-e29b-41d4-a716-446655441000",
"requestNumber": "RQ-2026-001240",
"serviceScheduleType": 2,
"locationPreference": 1,
"requestStatus": 7,
"requestedAt": "2026-02-08T10:15:00Z",
"scheduledAt": "2026-02-09T12:00:00Z",
"latitude": 28.615,
"longitude": 77.211,
"location": "789 Service Hub, New Delhi, 110003",
"description": "Regular maintenance and oil change service",
"estimatedPrice": 2500.00,
"paymentStatus": 1,
"paymentMethod": "CreditCard",
"rating": null,
"feedback": null,
"customer": {
"customerId": "a1b2c3d4-e29b-41d4-a716-446655440999",
"firstName": "Rajesh",
"lastName": "Kumar",
"email": "rajesh.kumar@example.com",
"phoneNumber": "+91-9876543210"
},
"vehicle": {
"vehicleId": "b2c3d4e5-e29b-41d4-a716-446655441000",
"make": "Maruti Suzuki",
"model": "Swift",
"year": 2022,
"licensePlate": "DL 12 AB 1234",
"vin": "MA3ERLF1S00123456"
},
"van": {
"vanId": "c3d4e5f6-e29b-41d4-a716-446655442000",
"vanCode": "VAN-001",
"vanName": "Service Van Alpha"
},
"technician": {
"technicianId": "880e8400-e29b-41d4-a716-446655440003",
"technicianCode": "TECH-002",
"availabilityStatus": 3,
"firstName": "Priya",
"lastName": "Sharma",
"email": "priya.sharma@example.com",
"phoneNumber": "+91-9876543211"
},
"services": [
{
"serviceId": "d4e5f6g7-e29b-41d4-a716-446655443000",
"serviceCode": "OIL-001",
"serviceName": "Oil Change",
"serviceDescription": "Full synthetic oil change service",
"servicePrice": 1500.00,
"serviceStatus": 1,
"reasonOfRejection": null
},
{
"serviceId": "e5f6g7h8-e29b-41d4-a716-446655444000",
"serviceCode": "INSP-001",
"serviceName": "Vehicle Inspection",
"serviceDescription": "Complete vehicle safety inspection",
"servicePrice": 1000.00,
"serviceStatus": 1,
"reasonOfRejection": null
}
]
}

Notes:

  • Technician's availabilityStatus changes to EnRoute (3)
  • Customer receives notification that technician is on the way
  • Admin dashboards are updated with journey status

Workflows

Workflow 1: Admin Assigns a Service Request

  1. Customer creates a service request (status: Pending, technicianId: null).
  2. Admin opens assignment UI and calls /service-request/{requestId}/technician-suggestions.
  3. Frontend displays technicians sorted by eta (soonest first).
  4. Admin reviews workload for selected technician (completed jobs today and current assignments) and clicks "Assign".
  5. Frontend calls /service-request/assign with requestId, technicianId, and scheduledTime.
  6. Backend:
    • Validates request exists and is not already assigned.
    • Validates technician exists and is available.
    • Validates technician is not at max capacity.
    • Updates service request: technicianId is populated, status changes to "Assigned".
    • Creates audit log entry.
    • Sends SignalR request_assigned event to technician and all admins (broadcasts to Request-{requestId} and AllAdmins groups).
  7. Frontend shows success message to admin.
  8. Technician app receives assignment and displays new job in queue with full service request details.
  9. All connected admin dashboards receive assignment update with complete service request information.
  10. Customer app receives notification and shows technician details.

Workflow 2: Admin Views Technician Workload

  1. Admin calls /service-request/{requestId}/technician-suggestions with optional search filters.
  2. Backend:
    • Validates service request is in Pending status.
    • Fetches all available/active technicians for the specific service request.
    • Applies search filter if provided (searches by name, code, email, phone, ID).
    • For each technician:
      • Calculates eta based on last job's estimated completion.
      • Counts completed jobs today and assigned jobs today.
    • Sorts by ETA (soonest first).
    • Applies pagination.
    • Returns paginated list.
  3. Frontend displays:
    • Search box for filtering technicians.
    • Pagination controls.
    • Technician name, code, status.
    • Number of jobs completed today.
    • Number of jobs assigned today.
    • Expected availability time (eta) in minutes.
  4. Admin can:
    • Search for specific technicians.
    • Navigate through pages of results.
    • Click on a technician to see full job details or assign a new job.

Business Rules

  1. Technician Capacity:

    • Max 2 concurrent active jobs per technician.
    • Max 5 total jobs (active + queued) per technician.
    • Assignment is blocked if at limit.
  2. Maximum Work Hours (Daily Limit):

    • Max 8 work hours per technician per day (configurable).
    • Work hours = Sum of all service durations AND travel time for the day.
    • Example: If technician has jobs with (15 min travel + 45 min service), (20 min travel + 60 min service), and (25 min travel + 30 min service) = 195 minutes (3.25 hours), they can still accept jobs up to 8 hours total.
    • If technician has 7.5 hours of work already scheduled (including travel), they can only accept jobs up to 30 minutes (0.5 hours).
    • Assignment is rejected with status 409 Conflict if adding a new job would exceed max work hours.
    • Error Message: "Technician exceeds maximum daily work hours (8). Current: 7.5h, Requested: 1h"
  3. Availability Calculation (ETA):

    • If no jobs: eta = 0.
    • If jobs exist: eta = estimated minutes until last job completion.
    • Estimated job duration: Configurable (default: 30 minutes per service).
  4. Assignment Conflict Prevention:

    • Request cannot be assigned to more than one technician.
    • Request cannot be assigned twice to the same technician.
  5. Role-Based Access:

    • Only admin/dispatcher can call assignment APIs.
    • Authenticated via JWT with role "admin" or "dispatcher".
  6. Audit Trail:

    • All assignments are logged with admin ID, timestamp, and action.
    • Audit logs are immutable and retained indefinitely.
  7. Notification Timing:

    • SignalR events sent immediately upon successful assignment.
    • If technician is offline, event is queued and delivered upon reconnect.

Development Tasks & Estimates

NoTask NameEstimate (Hours)DependenciesNotes
1Technician Suggestions API4NoneQuery, availability calc, filtering
2Assign Service API61Validation, audit log, SignalR events
3Availability Calculation Service41Job duration estimates, logic
4SignalR Event Broadcasting32Technician and customer notifications
5Audit Logging & Database32Insertion, queries for history
6Error Handling & Validation31-5Edge cases, conflict detection
7Unit Tests41-6Services, APIs, edge cases
8Integration Tests41-6API, database, SignalR
9Documentation & Examples2AllAPI docs, examples, workflows
TotalDevelopment Time33 hours~1 week with 1 developer

Testing & Quality Assurance

Unit Tests

  • Availability calculation logic (various job scenarios).
  • Capacity validation (at limit, under limit, over limit).
  • Conflict detection (duplicate assignments, unavailable technicians).
  • Validation of request/response models.

Integration Tests

  • End-to-end assignment workflow (request creation → suggestions → assignment).
  • SignalR event delivery to technician and customer.
  • Audit log insertion and retrieval.
  • Database consistency after assignment.
  • Error scenarios (not found, already assigned, at capacity).

Acceptance Criteria

  • Technician suggestions API returns all available technicians in <1 second.
  • Availability calculation is accurate within ±5 minutes.
  • Assignment is atomic: either fully succeeds or fully fails.
  • SignalR events are delivered within <2 seconds.
  • Audit logs are created for all assignments.
  • Duplicate assignments are prevented.
  • Capacity limits are enforced.

Testing Tools

  • xUnit (unit testing).
  • Moq (mocking dependencies).
  • Testcontainers (PostgreSQL, Valkey).
  • SignalR test client library.

Deployment Considerations

  • Configuration:

    • Max concurrent jobs per technician (default: 2).
    • Max total jobs per technician (default: 5).
    • Max work hours per technician per day (default: 8 hours).
    • Estimated job duration (default: 30 minutes).
    • Admin role required for assignment APIs.
  • Rollout:

    • Deploy to staging first; test with admin team.
    • Enable feature toggle for admin service assignment.
    • Monitor assignment success rate and SignalR delivery.
  • Monitoring:

    • Track assignment API latency.
    • Monitor capacity violations and conflicts.
    • Log all failed assignments for troubleshooting.

Risks & Mitigations

RiskImpactLikelihoodMitigation
Assignment race conditionHighMediumUse transactional updates and optimistic locking
Technician offline during eventMediumMediumQueue events in Valkey, deliver on reconnect
Inaccurate availability calcMediumLowUse actual job durations; update estimates as jobs complete
Capacity exceeded due to time lagMediumLowRe-validate capacity at assignment time; use database locks
Audit log insertion failureLowVery LowUse async logging with retry; never block assignment on log fail



Review & Approval

  • Reviewer(s):

    • Sanket Mal
    • Ribhu Gautam
  • Approval Date: [To be completed after reviews]


Notes

  • Frontend Developers: Start with the "API Examples" and "Workflows" sections for integration.
  • Backend Developers: Use "Design Specifications", "API Interfaces", and "Development Tasks" for implementation.
  • Admin/Dispatcher: Review "Workflows" and "Business Rules" for operational expectations.
  • QA: Use "Testing & QA" section for test cases and acceptance criteria.