Dealer Management API
Authors
- Sanket Mal
- Ayan Ghosh
- Ashik Ikbal
Last Updated Date
2025-11-16
SRS References
Version History
| Version | Date | Changes | Author |
|---|---|---|---|
| 1.0 | 2025-11-16 | Initial draft - Dealer API design | Ashik |
| 1.1 | 2025-11-17 | Added latitude/longitude to DealerAddress, auto-populate from zipcodedetails | Ashik |
Feature Overview
Objective: Provide secure, role-based APIs for managing dealer entities in the MBDN platform. The Dealer Management API enables creation, retrieval, updating, and address suggestion for dealer records, supporting administrative workflows and dealer self-service.
Scope:
- Dealer creation, update, and retrieval
- Paginated dealer listing with advanced filtering
- Dealer address suggestion for onboarding and updates
- Strict access control based on user roles and claims
- Comprehensive error handling and status reporting
- Geo-coordinates (latitude, longitude) for dealer addresses auto-populated from zipcodedetails
Dependencies:
- IAMService - For authentication, JWT validation, and user claims
- DealerManagementService.Helper - Business logic for dealer operations
- PostgreSQL Database - For dealer data persistence
- CommonLibrary/ContractLibrary - For shared models and enums
- zipcodedetails table - for address geo-coordinates
Request & Response Models
Request Models
Dealer (ContractLibrary.Dealer.Dealer)
public record Dealer
{
public Guid DealerId { get; set; }
public string? DealerCode { get; set; }
public required string PrimaryEmail { get; init; }
public required string CompanyName { get; init; }
public string? ContactPersonName { get; init; }
public required string Phone { get; init; }
public string? Website { get; init; }
public List<DealershipType>? DealershipType { get; init; }
public List<Brand>? Brand { get; init; }
public Status Status { get; set; } = Status.Active;
public string? DealerLogoUrl { get; set; }
public List<DealerAddress>? Addresses { get; init; } = new List<DealerAddress>();
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
public string? LogUsername { get; set; }
public DateTimeOffset LogDts { get; set; } = DateTimeOffset.UtcNow;
}
DealerAddress (ContractLibrary.Dealer.DealerAddress)
public record DealerAddress
{
public AddressType AddressType { get; init; }
public string? AddressLine1 { get; init; }
public string? City { get; init; }
public string? State { get; init; }
public string? ZipCode { get; init; }
public double? Latitude { get; set; } // auto-populated from zipcodedetails
public double? Longitude { get; set; } // auto-populated from zipcodedetails
public bool IsPrimary { get; set; } = false; // indicates primary address for dealer
public double? Radius { get; set; } // optional radius for this address
}
DealerFilter (ContractLibrary.Dealer.DealerFilter)
public record DealerFilter
{
public string? DealerCode { get; set; }
public string? CompanyName { get; set; }
public string? ContactPersonName { get; set; }
public string? PrimaryEmail { get; set; }
public string? Phone { get; set; }
public List<Status>? Status { get; set; }
public List<DealershipType>? DealershipTypes { get; set; }
public List<Brand>? Brands { get; set; }
public string? Website { get; set; }
public DateTimeOffset? CreatedAtFrom { get; set; }
public DateTimeOffset? CreatedAtTo { get; set; }
public DateTimeOffset? UpdatedAtFrom { get; set; }
public DateTimeOffset? UpdatedAtTo { get; set; }
public string? SearchKeyword { get; set; }
public DealerSortBy SortBy { get; set; }
public SortOrder SortDirection { get; set; }
[Range(1, int.MaxValue, ErrorMessage = "Page number must be greater than 0")]
public int PageNumber { get; set; }
[Range(1,100, ErrorMessage = "Page size must be between 1 and 100")]
public int RowsPerPage { get; set; }
}
UpdateDealerRadiusRequest (ContractLibrary.Dealer.UpdateDealerRadiusRequest)
public record UpdateDealerRadiusRequest
{
[Range(1, 1000, ErrorMessage = "Radius must be between 1 and 1000 KM")]
public required double Radius { get; init; }
}
Response Models
CommonResponse (ContractLibrary.Common.CommonResponse)
public record CommonResponse
{
public int Status { get; init; }
public string? Message { get; init; }
}
ServerPaginatedData<T>
From ContractLibrary.Common.ServerPaginatedData<T>
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; }
}
Dealer (Response)
- Same as request model, with all properties populated.
List<DealerAddress> (Response)
- List of
DealerAddressrecords as defined above, includingLatitudeandLongitude.
Geo-coordinates Handling
- On create and update dealer, for each address:
- The system will fetch
latitudeandlongitudefrom thezipcodedetailstable using the providedZipCode. - These values are stored in the
dealeraddresstable and returned in API responses. - If the zip code is not found,
LatitudeandLongitudewill benull.
- The system will fetch
Primary Address Management
- Each dealer must have exactly one primary address (
isprimary = true) - When updating dealer addresses:
- If a new address is marked as primary, all other addresses for that dealer are automatically set to
isprimary = false - If no address is marked as primary, the system will default the first address as primary
- If a new address is marked as primary, all other addresses for that dealer are automatically set to
- The primary address is used for:
- Geo-radius filtering calculations
- Default location for auction notifications
- Dealer's main service location
Requirements
Functional Requirements
- Dealer Creation
- Only Admin users can create new dealers
- Validates user context and required claims
- Returns appropriate status and error messages
- Dealer Listing
- Admins can retrieve a paginated, filterable list of dealers
- Supports filtering by code, name, status, type, brand, and date
- Returns paginated data with total count and navigation info
- Dealer Details
- Any authorized user with the correct scope can fetch full dealer details by ID
- Returns 404 if dealer not found
- Dealer Update
- Admins and Dealers can update dealer information
- Dealers can only update their own record (validated via entity_id claim)
- Validates user context and permissions
- Auto-populates latitude and longitude from zipcodedetails table based on zip code
- Ensures only one address has
isprimary = trueat a time - Updates or inserts addresses with populated geo-coordinates
- Address Suggestions
- Admins can get address suggestions for any dealer (dealerId required)
- Dealers can only get suggestions for their own entity (entity_id from JWT)
- Returns list of suggested addresses or error if unauthorized
- Update Dealer Radius
- Dealers can update their service radius
- Dealer ID is retrieved from entity_id claim in JWT token
- Validates radius value (must be positive number, maximum 1000 KM)
- Updates the radius field in dealermaster table
- Returns success message with updated radius value
Non-Functional Requirements
- Performance
- API response time under 500ms for list endpoints
- API response time under 300ms for create/update endpoints
- Efficient database queries and pagination
- Security
- All endpoints require JWT authentication
- Role and scope-based access control
- Input validation and error handling
- Scalability
- Supports large dealer datasets with pagination and filtering
- Designed for multi-tenant, multi-role access
- Reliability
- Consistent error responses
- Logging for all critical operations and errors
- Usability
- Clear error messages and status codes
- Swagger/OpenAPI documentation for all endpoints
Design Specifications
API Interfaces
| Endpoint | Method | Parameters | Response | Response Status Codes |
|---|---|---|---|---|
/api/dealer/create | POST | Dealer (body) | CommonResponse | 201, 400, 401, 403, 500 |
/api/dealer/list | GET | DealerFilter (query) | ServerPaginatedData<Dealer> | 200, 400, 401, 403, 500 |
/api/dealer/details/{dealerId} | GET | dealerId (route) | Dealer | 200, 401, 404, 500 |
/api/dealer/update/{dealerId} | PUT | dealerId (route), Dealer (body) | CommonResponse | 200, 400, 401, 403, 404, 500 |
/api/dealer/address-suggestion | GET | dealerId (query, optional) | List<DealerAddress> | 200, 400, 401, 403, 500 |
/api/dealer/update-radius | PUT | UpdateDealerRadiusRequest (body) | CommonResponse | 200, 400, 401, 403, 500 |
API Request & Response Examples
1. Create Dealer API
Endpoint: POST /api/dealer/create
Request Headers:
Authorization: Bearer <jwt-token>
Content-Type: application/json
Request Body (Dealer):
{
"primaryEmail": "dealer@example.com",
"companyName": "ABC Motors",
"phone": "1234567890",
"dealershipType": [1],
"brand": [12],
"status":1,
"addresses": [
{
"addressType":1,
"addressLine1": "123 Main St",
"city": "Metropolis",
"state": "NY",
"zipCode": "10001"
}
]
}
Success Response (201 Created):
{
"status":0,
"message": "Dealer created successfully"
}
Error Response (400 Bad Request):
{
"status":400,
"message": "Invalid dealer data"
}
Error Response (403 Forbidden):
{
"status":403,
"message": "You are not allowed to create dealers."
}
2. Get Dealer List API
Endpoint: GET /api/dealer/list
Request Headers:
Authorization: Bearer <jwt-token>
Query Parameters (DealerFilter):
?companyName=ABC&status=1&pageNumber=1&rowsPerPage=20
Success Response (200 OK):
{
"data": [
{
"dealerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"dealerCode": "DLR-001234",
"primaryEmail": "dealer@example.com",
"companyName": "ABC Motors",
"contactPersonName": "John Doe",
"phone": "1234567890",
"website": "https://abcmotors.com",
"dealershipType": [1],
"brand": [12],
"status":1,
"dealerLogoUrl": "https://cdn.example.com/dealers/abc-motors-logo.jpg",
"addresses": [],
"createdAt": "2025-11-16T10:25:00Z",
"updatedAt": "2025-11-16T10:25:00Z"
}
],
"totalNumber":1,
"hasPreviousPage": false,
"hasNextPage": false,
"totalPages":1,
"pageNumber":1,
"rowsPerPage":20
}
Error Response (400 Bad Request):
{
"status":400,
"message": "Invalid filter parameters"
}
3. Get Dealer Details API
Endpoint: GET /api/dealer/details/{dealerId}
Request Headers:
Authorization: Bearer <jwt-token>
Success Response (200 OK):
{
"dealerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"dealerCode": "DLR-001234",
"primaryEmail": "dealer@example.com",
"companyName": "ABC Motors",
"contactPersonName": "John Doe",
"phone": "1234567890",
"website": "https://abcmotors.com",
"dealershipType": [1],
"brand": [12],
"status":1,
"dealerLogoUrl": "https://cdn.example.com/dealers/abc-motors-logo.jpg",
"addresses": [],
"createdAt": "2025-11-16T10:25:00Z",
"updatedAt": "2025-11-16T10:25:00Z"
}
Error Response (404 Not Found):
{
"status":404,
"message": "Dealer not found"
}
4. Update Dealer API
Endpoint: PUT /api/dealer/update/{dealerId}
Request Headers:
Authorization: Bearer <jwt-token>
Content-Type: application/json
Request Body (Dealer):
{
"primaryEmail": "dealer@example.com",
"companyName": "ABC Motors",
"phone": "1234567890",
"dealershipType": [1],
"brand": [12],
"status":1,
"addresses": []
}
Success Response (200 OK):
{
"status":0,
"message": "Dealer updated successfully"
}
Error Response (403 Forbidden):
{
"status":403,
"message": "You are not allowed to update dealers."
}
Error Response (404 Not Found):
{
"status":404,
"message": "Dealer not found"
}
Implementation Logic:
-
Receive Update Request
- Accept dealer ID from route parameter
- Accept dealer information including addresses from request body
- Validate user has permission (Admin or Dealer with matching entity_id)
-
Fetch Latitude & Longitude from zipcodedetails
- For each address in the update request:
- Query
zipcodedetailstable using the provided zip code - Retrieve
latandlngvalues - Auto-populate latitude and longitude fields for the address
- If zip code not found, set lat/lng to null
- Query
- For each address in the update request:
-
Manage Primary Address
- Ensure only one address has
isprimary = true - If a new primary address is specified:
- Set all other addresses'
isprimary = falsefor this dealer - Set the new address's
isprimary = true
- Set all other addresses'
- If no address is marked as primary:
- Default the first address to
isprimary = true
- Default the first address to
- Ensure only one address has
-
Update Dealer and Address Records
- Update dealer information in
dealermastertable - Update or insert addresses in
dealeraddresstable with:- Populated lat/lng values
- Correct isprimary flag
- Optional radius value
- Log the update with username and timestamp
- Update dealer information in
-
Return Response
- Return success message confirming dealer update
5. Get Address Suggestions API
Endpoint: GET /api/dealer/address-suggestion
Request Headers:
Authorization: Bearer <jwt-token>
Query Parameters:
?dealerId=3fa85f64-5717-4562-b3fc-2c963f66afa6
Success Response (200 OK):
[
{
"addressType":1,
"addressLine1": "123 Main St",
"city": "Metropolis",
"state": "NY",
"zipCode": "10001"
}
]
Error Response (400 Bad Request):
{
"status":400,
"message": "Invalid dealer context. Entity ID claim is required."
}
6. Update Dealer Radius API
Endpoint: PUT /api/dealer/update-radius
Request Headers:
Authorization: Bearer <jwt-token>
Content-Type: application/json
Request Body (UpdateDealerRadiusRequest):
{
"radius": 150.5
}
Success Response (200 OK):
{
"status": 0,
"message": "Dealer radius updated successfully"
}
Error Response (400 Bad Request):
{
"status": 400,
"message": "Radius must be between 1 and 1000 KM"
}
Implementation Logic:
-
Receive Radius Update Request
- Extract dealer ID from entity_id claim in JWT token
- Accept new radius value from request body (in KM)
- Validate radius value (must be positive number, max 1000 KM)
-
Update Dealer Radius
- Update the radius field in
dealermastertable for the authenticated dealer - Example SQL:
UPDATE dealermaster SET radius = {radius} WHERE dealerid = '{dealerId}' - Log the update with username and timestamp
- Update the radius field in
-
Return Response
- Return success message confirming radius update
Development Tasks & Estimates
| No | Task Name | Estimate (Hours) | Dependencies | Notes |
|---|---|---|---|---|
| 1 | DealerController API implementation | 10 | None | All endpoints, validation, error handling (includes UpdateDealerRadius) |
| 2 | DealerHelper business logic and integration | 7 | Task1 | Includes radius update logic |
| 3 | Unit and integration tests for DealerController | 10 | Task1,2 | Includes tests for radius update API |
| 4 | Swagger/OpenAPI documentation | 2 | Task1 | |
| Grand Total | 29 hours |
Testing & Quality Assurance
Integration Test Scenarios:
- Dealer creation with valid and invalid data
- Dealer listing with various filters and pagination
- Dealer update by admin and dealer roles
- Dealer radius update with valid and invalid values (1-1000 KM)
- Dealer radius update only by dealer role
- Address suggestion for admin and dealer
- Unauthorized and forbidden access attempts
- Error handling for missing/invalid claims
Testing Tools
- xUnit - Unit and integration testing
- FluentAssertions - Test assertions
- Moq - Mocking dependencies
- WebApplicationFactory - API integration tests
- Postman/Swagger - Manual API testing
Acceptance Criteria
Feature Complete When:
- ✅ Admins can create, update, and list dealers
- ✅ Dealers can update their own information
- ✅ Dealers can update their service radius (validated 1-1000 KM, dealer-only access)
- ✅ All endpoints enforce correct access control
- ✅ Pagination and filtering work as expected
- ✅ Address suggestions are accurate and secure
- ✅ All error cases return correct status and message
- ✅ Unit test coverage >85%
- ✅ All integration tests pass
Deployment Considerations
Configuration Changes
- Ensure database schema for dealers and addresses is up to date
- Configure JWT authentication and required claims in IAMService
- Update Swagger documentation
Risks & Mitigations
| Risk | Impact | Likelihood | Mitigation Strategy |
|---|---|---|---|
| Unauthorized access to endpoints | High | Medium | Strict JWT and claim validation, logging, and auditing |
| Data inconsistency on update | Medium | Low | Use transactions and validation in business logic |
Review & Approval
-
Reviewer: Sanket
-
Approval Date: 2025-11-17
Notes
Future Enhancements
- Bulk dealer import/export
- Dealer status change notifications
- Dealer audit log endpoints
- Advanced address validation and enrichment
Dependencies on Other Services
- IAMService: For authentication and user claims
- CommonLibrary/ContractLibrary: For shared models and enums