Skip to main content
Version: MyBestDealNow

Dealer Management System

Author(s)

  • Faizal Khan
  • Pritam Dutta
  • Sanket Mal
  • Ayan Ghosh
  • Ashik Ikbal

Last Updated Date

2025-12-19


SRS References


Version History

VersionDateChangesAuthor
1.02025-11-16Initial draft - Dealer API designAshik
1.12025-11-17Added latitude/longitude to DealerAddress, auto-populate from zipcodedetailsAshik
1.22025-12-19Feature ListingFaizal Khan

Dealer Management

Feature Overview

Objective
Provide backend APIs for dealer lifecycle management (create dealer, list dealers with filters, fetch dealer details, update dealer profile and addresses, fetch address suggestions, and update dealer radius) used by admin and dealer users.

Scope

  • Dealer creation and onboarding with detailed information (including addresses, managers, business hours).
  • Dealer listing with pagination and advanced filtering.
  • Dealer detail retrieval with addresses, Business Hours, Contact Persons, etc.
  • Dealer profile and address updates with validation rules.
  • Address suggestion retrieval for a dealer.
  • Dealer radius update.
  • Geo-coordinates (latitude, longitude) for dealer addresses auto-populated from zipcodedetails

Dependencies

  • Authentication & Authorization

    • JWT-based authentication.
    • Scope-based authorization.
  • Contract Libraries & Models

    • ContractLibrary.Dealer:
      • Dealer
      • DealerAddress
      • DealerFilter
      • UpdateDealerRadiusRequest
      • DealerCreationResponse
      • GetDealersRequest
      • DealerMinimalInfo
    • ContractLibrary.Common:
      • CommonResponse
      • ServerPaginatedData<T>
      • Enums: Status, UserType, DealershipType, Brand, AddressType, DealerSortBy, SortOrder.
  • Database

    • DAL uses the following tables:
      • dealermaster
      • dealeraddress
      • zipcodedetails
      • adminmaster
  • Messaging & Notifications

    • MassTransit (IBus, request clients).
    • Requests to Auth services:
      • UserCreateRequest / UserCreateResponse
      • PrimaryUserUpdateRequest
    • Notifications via ContractLibrary.Notification:
      • NotificationRequest
      • BatchNotificationRequest
      • NotificationType.NewDealerCreate
  • Geolocation

    • IGeolocationService and GeolocationService (from CommonLibrary.Services), used to resolve zipcode to latitude/longitude, with fallback to Nominatim API.

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 DealerAddress records as defined above, including Latitude and Longitude.


Geo-coordinates Handling

  • On create and update dealer, for each address:
    • The system will fetch latitude and longitude from the zipcodedetails table using the provided ZipCode.
    • These values are stored in the dealeraddress table and returned in API responses.
    • If the zip code is not found, Latitude and Longitude will be null.

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
  • The primary address is used for:
    • Geo-radius filtering calculations
    • Default location for auction notifications
    • Dealer's main service location

Requirements

Functional Requirements

1. Dealer Creation

  • Only users with:
    • Valid JWT
    • user_type claim = Admin
    • Scopes Right.admin_user and Right.dealer_create can create dealers.
  • Request body is a Dealer object with at least:
    • primaryEmail (required by validation)
    • companyName
    • phone
  • System responsibilities:
    • Validate that Dealer is non-null and PrimaryEmail is present.
    • Check duplicates:
      • Email (GetDealerByEmail)
      • Phone (GetDealerByPhone) if provided.
    • Generate unique dealer code using sequence from DB and pattern:
      • DL-{sequence:D5} (e.g., DL-00001).
    • Assign:
      • New dealerId (Guid.NewGuid()).
      • Status = Status.Active.
      • Logging fields LogUsername, LogDts.
    • Persist dealer into dealermaster.
    • Persist dealer addresses into dealeraddress with:
      • Exactly one AddressType.Legal.
      • Exactly one AddressType.Billing.
    • For each address:
      • If zipcode known in zipcodedetails, populate latitude and longitude.
      • If zipcode missing, fetch from Nominatim via IGeolocationService and insert into zipcodedetails.
    • Create a primary user for the dealer (UserType = Dealer) via UserCreateRequest.
    • Publish a welcome notification (NotificationType.NewDealerCreate) via MassTransit.

If address collection is missing, empty, or does not contain exactly one legal and one billing address, creation fails with MbdnStatusCode.INVALID_REQUEST.

2. Dealer Listing

  • Only Admin users with:
    • Scope Right.admin_user and Right.dealer_view_all.
    • user_type = Admin. can access this endpoint.
  • Client sends query parameters mapped to DealerFilter:
    • Basic filters: dealerCode, companyName, ownerName, primaryEmail, phone, website.
    • Status list (Status enum).
    • Dealership types (DealershipType enum list).
    • Brands (Brand enum list).
    • Created/Updated date ranges.
    • searchKeyword (global search across multiple columns).
    • Sorting via DealerSortBy and SortOrder.
    • Pagination via pageNumber and rowsPerPage.
  • DAL builds dynamic SQL over dealermaster with:
    • Filtering based on provided parameters.
    • Global search using unnest(@Keywords) and multiple ILIKE conditions.
    • Sorting by chosen column.
    • Pagination using LIMIT and OFFSET.
  • Addresses are joined by separate query on dealeraddress and attached to each Dealer.
  • Response shape is ServerPaginatedData<Dealer>:
    • data (list of Dealer)
    • Pagination metadata (totalNumber, totalPages, hasPreviousPage, hasNextPage, pageNumber, rowsPerPage).

3. Dealer Details

  • Requires Right.dealer_view scope.
  • Accepts dealerId as route parameter (Guid).
  • DAL:
    • Fetches dealer core data from dealermaster.
    • Fetches addresses from dealeraddress ordered by addressType.
    • Populates DealershipType and Brand from text arrays.
  • Returns Dealer with:
    • Core profile fields.
    • Status.
    • Full list of addresses with type, geolocation, and isPrimary.
  • If dealer does not exist:
    • Returns MbdnStatusCode.RECORD_NOT_FOUND and 404.

4. Dealer Updates

  • Requires:
    • Right.dealer_update scope.
    • Valid dealerId in route.
    • Dealer body (Dealer) in request.
  • Authorization rules:
    • user_type must be Admin or Dealer.
    • If user is Dealer (non-admin):
      • entity_id claim must be present and equal to dealerId (cannot update other dealers).
  • Validation (ValidateDealerUpdateData):
    • Dealer cannot be null.
    • DealerId must be non-empty.
    • Reuse of basic Dealer validation (e.g., PrimaryEmail).
    • Duplicate checks:
      • Email must not belong to another dealer.
      • Phone must not belong to another dealer.
  • Address validation and update:
    • Negative Radius is rejected.
    • If no address is marked IsPrimary, first address is made primary.
    • Only one primary address allowed.
    • There must be exactly one Legal and one Billing address (EnsureSingleLegalAndBillingAddress).
    • It is not allowed to:
      • Delete existing legal or billing address.
      • Add a second legal or billing address (ValidateLegalAndBillingModification).
    • New addresses:
      • Cannot be of type Legal or Billing.
      • Get new AddressId (Guid.NewGuid()).
    • Non-admin users cannot update legal address (AddressType.Legal).
    • All address insert/update/delete operations are classified and applied via DAL:
      • SaveDealerAddress
      • UpdateDealerAddresses
      • DeleteDealerAddresses
    • Zipcode geolocation is refreshed (same as in creation) using zipcodedetails and Nominatim for any missing zipcodes.
  • If primary email or phone changed:
    • PrimaryUserUpdateRequest is sent to update dealer's primary user details.

5. Address Suggestions

  • Requires Right.dealer_view scope.
  • Behavior depends on user_type:
    • Admin:
      • Must provide dealerId as query parameter.
    • Dealer:
      • Uses entity_id claim as dealer ID.
      • If dealerId query parameter is also provided and does not match entity_id, request is rejected.
  • Helper:
    • Validates non-empty dealer ID.
    • Fetches addresses from dealeraddress.
    • Returns:
      • 200 with addresses if found.
      • RECORD_NOT_FOUND if dealer not found.

6. Radius Update

  • Requires Right.dealer_update scope.
  • Uses UpdateDealerRadiusRequest body:
    • radius (decimal, required).
  • Authorization:
    • User must be Admin or Dealer.
    • entity_id claim is parsed as dealerId.
  • Validation:
    • radius must be between 1 and 1000 KM.
  • DAL:
    • Confirms dealer existence via GetDealerMinimalInfo.
    • Updates radius in dealermaster and audit fields (updatedAt, logUsername, logDts).
  • Returns:
    • OK on success.
    • RECORD_NOT_FOUND if dealer not found.

Non-Functional Requirements

1. Performance

  • All database access for dealer listing uses paginated queries with LIMIT/OFFSET and server-side filtering to avoid loading all dealers at once.
  • Queries are built with parameterized SQL and leverage indexes implicitly on key columns (e.g., dealerCode, companyName, status) based on their usage.

2. Security

  • JWT validation is configured.
  • All DealerController actions are protected by:
    • [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)].
    • Scope attributes (CustomRequiredScope, RequiredScope).
  • Fine-grained authorization rules based on:
    • user_type and entity_id claims.
    • Specific rights such as Right.dealer_create, Right.dealer_view_all, Right.dealer_view, Right.dealer_update.

3. Scalability

  • Pagination and filterable listing are implemented to support large dealer datasets.

4. Usability

  • Easy-to-use interface for both buyers and dealers.
  • Mobile-friendly design to support access from any device.

5. Reliability

  • Consistent error responses.
  • Logging for all critical operations and errors.

Workflow Summary

  1. Dealer Creation

    • Admin calls POST /create with Dealer payload.
    • Helper validates dealer data, checks for duplicates, generates dealer code, assigns IDs and status.
    • Dealer record is inserted into dealermaster.
    • Addresses are validated and stored in dealeraddress with geolocation populated from zipcodedetails or Nominatim.
    • Contact Persons details are stored in Contact table.
    • Business Hours details are stored in DepartmentBusinessHours after proper validation.
    • A primary user is created for the dealer via Auth service.
    • A welcome and activation email notification is queued via notification service. Dealers and Contact Persons can activate their profile from the mail.
  2. Dealer Listing

    • Admin calls GET /list with DealerFilter query parameters.
    • Helper validates filter ranges, normalizes strings, and delegates to DAL.
    • DAL builds dynamic SQL for filters, global search, sorting, and pagination.
    • Dealer list and total count are returned; addresses are attached per dealer.
  3. Dealer Details

    • Authorized user calls GET /details/{dealerId}.
    • DAL reads dealer row and associated addresses, mapping enums from text arrays.
    • Helper returns a full Dealer object to the controller, which returns 200 or appropriate error.
  4. Dealer Updates

    • Authorized Admin or Dealer calls PUT /update/{dealerId} with updated Dealer payload.
    • Controller enforces that dealers can update only their own records using entity_id.
    • Helper validates data, checks duplicates, and loads current dealer and addresses.
    • Address operations (add/update/delete) are classified and validated (especially legal/billing constraints).
    • DAL updates dealermaster and dealeraddress in a transaction.
    • If primary email/phone changed, helper triggers primary user update.
  5. Address Suggestions

    • Admin or Dealer calls GET /address-suggestion:
      • Admin passes dealerId.
      • Dealer uses entity_id claim.
    • Helper fetches addresses and returns them as suggestions.
  6. Radius Update

    • Admin or Dealer calls PUT /update-radius with UpdateDealerRadiusRequest.
    • Controller resolves dealer ID from entity_id claim.
    • Helper validates radius and dealer existence, then DAL updates radius in dealermaster.

Example Use Case: Dealer Profile Update

  • Admin updates company name, website, and brand list for an existing dealer via PUT /update/{dealerId}.
  • Dealer updates their billing address radius while keeping legal address unchanged.
  • Admin later uses GET /list with filters on status and searchKeyword to confirm updated dealer data appears correctly.

Development Tasks & Estimates

NoTask NameEstimate (Hours)DependenciesNotes
1DealerController API implementation10NoneAll endpoints, validation, error handling (includes UpdateDealerRadius)
2DealerHelper business logic and integration7Task1Includes radius update logic
3Unit and integration tests for DealerController10Task1,2Includes tests for radius update API
4Swagger/OpenAPI documentation2Task1
Grand Total29 hours

Design Specifications

UI/UX Design

The dealer management feature includes several key user interfaces:

  1. Dealer Onboarding Page

    • Multi-step form for registering a new dealership
    • Basic company details (company name, owner name, email, phone, website)
    • Logo upload section
    • Contact person information (role, name, phone, email)
    • Business Hours for individual department
    • Dealership type selection (owned, pre-owned)
    • Brand association selection
    • Address entry with minimum two required
    • Final confirmation and submission
  2. Dealer List Page

    • List of all registered dealers
    • Search, sorting, and filtering options
    • Dealer table displaying essential dealership info
    • Quick actions such as edit/view
  3. Dealer Details View

    • Detailed profile of selected dealership
    • Contact person list
    • Registered brands overview
    • All registered addresses
    • Status indicators (active / disable)
  4. Dealer Profile Update Page

    • Edit basic and contact details
    • Add/remove brands
    • Add/update addresses
    • Update logo, website, etc.

Database Schema

1. dealermaster Table

  • Core columns used:
    • dealerId (primary identifier, uuid).
    • dealerCode (unique text code, pattern DL-00001, ...).
    • primaryemail
    • companyName
    • ownerName
    • phone
    • website
    • dealershipType (text array; mapped to DealershipType enum).
    • brand (text array; mapped to Brand enum).
    • status (text; mapped to Status enum).
    • dealerLogoUrl
    • createdAt
    • updatedAt
    • logUsername
    • logDts
    • radius (used by UpdateDealerRadius).

2. dealeraddress Table

  • Columns:
    • addressId
    • dealerId
    • addressType (text; mapped to AddressType enum).
    • addressline
    • state
    • zipcode
    • country
    • latitude
    • longitude
    • radius
    • isprimary
    • logUsername
    • logDts

3. zipcodedetails Table

  • Columns:

    • zipcode
    • lat
    • lng
  • Data Models:

    1. Dealer Related Models
      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? OwnerName { get; init; } // Changed from "ContactPersonName" to "OwnerName"
    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; set; } = new List<DealerAddress>();
    public List<ContactPerson>? ContactPersons { get; set; } = new List<ContactPerson>(); // Newly added for Adding Managers Details
    public List<DepartmentBusinessHours>? DepartmentBusinessHours { get; set; } = new List<DepartmentBusinessHours>(); // Newly added for Adding Department wise Business Hours
    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;
    }

    public record DealerAddress
    {
    public Guid AddressId { get; set; }
    public Guid DealerId { get; set; }
    public AddressType AddressType { get; init; }
    public string? AddressLine { get; init; }
    public string? State { get; init; }
    public string? Zipcode { get; init; }
    public string? Country { get; init; }
    public double? Latitude { get; set; }
    public double? Longitude { get; set; }
    public double? Radius { get; set; }
    public bool IsPrimary { get; set; }
    }

    public record DealerFilter
    {
    public string? DealerCode { get; set; }
    public string? CompanyName { get; set; }
    public string? OwnerName { 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; } = DealerSortBy.DealerCode;
    public SortOrder SortDirection { get; set; } = SortOrder.Descending;
    public int PageNumber { get; set; } = 1;
    public int RowsPerPage { get; set; } = 10;
    }

    public class UpdateDealerRadiusRequest
    {
    public required decimal Radius { get; set; }
    }

    public record DealerCreationResponse : CommonResponse
    {
    public string? DealerCode { get; init; }
    public Guid? DealerId { get; init; }
    }
  • API Interfaces:

    (Dealer endpoints implemented in DealerController with methods, parameters, and response types.)

EndpointMethodParametersResponseResponse Status Codes
/createPOSTBody: DealerCommonResponse / DealerCreationResponse201, 400, 401, 403, 404, 500
/listGETQuery: DealerFilterServerPaginatedData<Dealer>200, 400, 401, 403, 500
/details/{dealerId}GETRoute: Guid dealerIdDealer200, 401, 404, 500
/update/{dealerId}PUTRoute: Guid dealerId, Body: DealerCommonResponse200, 400, 401, 403, 404, 409, 500
/address-suggestionGETQuery: Guid? dealerId (admin only)List<DealerAddress>200, 400, 401, 403, 404, 500
/update-radiusPUTBody: UpdateDealerRadiusRequestCommonResponse200, 400, 401, 403, 404, 500

API Request & Response Examples

Below are JSON examples derived directly from the C# models and controller behavior.

1. Create Dealer

Endpoint: POST /create

Request Body (Dealer):

{
"dealerId": "00000000-0000-0000-0000-000000000000",
"dealerCode": null,
"primaryEmail": "contact@dealer.com",
"companyName": "Best Dealer Ltd",
"ownerName": "John Doe",
"phone": "+1-555-0100",
"website": "https://dealer.example.com",
"dealershipType": [ "New" ],
"brand": [ "Toyota", "Honda" ],
"status": "Active",
"dealerLogoUrl": "https://example.com/logo.png",
"addresses": [
{
"addressId": "00000000-0000-0000-0000-000000000001",
"dealerId": "00000000-0000-0000-0000-000000000000",
"addressType": "Legal",
"addressLine": "123 Legal St",
"state": "CA",
"zipcode": "90001",
"country": "USA",
"latitude": null,
"longitude": null,
"radius": 50.0,
"isPrimary": true
},
{
"addressId": "00000000-0000-0000-0000-000000000002",
"dealerId": "00000000-0000-0000-0000-000000000000",
"addressType": "Billing",
"addressLine": "456 Billing Ave",
"state": "CA",
"zipcode": "90002",
"country": "USA",
"latitude": null,
"longitude": null,
"radius": 25.0,
"isPrimary": false
}
],
"ContactPersons": [
{
"ContactId": "00000000-0000-0000-0000-000000002222",
"DealerId": "00000000-0000-0000-0000-0000000033333",
"FirstName": "ALex",
"LastName": "Net",
"Phone": "9988776651",
"Email": "gm@gmail.com",
"IsUser": false,
"Designation": "General Manager"
},
{
"ContactId": "00000000-0000-0000-0000-000000002222",
"DealerId": "00000000-0000-0000-0000-0000000033333",
"FirstName": "ALex",
"LastName": "Net",
"Phone": "9988776651",
"Email": "gm@gmail.com",
"IsUser": true,
"Designation": "Billing Manager"
}
],
"DepartmentBusinessHours": [
{
"DeptName": "Sales",
"DealerId": "00000000-0000-0000-0000-000000000000",
"MonFrom": 09:30:00,
"MonTo": 09:30:00,
"TueFrom": 09:30:00,
"TueTo": 09:30:00,
"WedFrom": 09:30:00,
"WedTo": 09:30:00,
"ThuFrom": 09:30:00,
"ThuTo": 09:30:00,
"FriFrom": 09:30:00,
"FriTo": 09:30:00,
"SatFrom": 09:30:00,
"SatTo": 09:30:00,
"SunFrom": 09:30:00,
"SunTo": 09:30:00,
},
{
"DeptName": "Parts",
"DealerId": "00000000-0000-0000-0000-000000000000",
"MonFrom": 09:30:00,
"MonTo": 09:30:00,
"TueFrom": 09:30:00,
"TueTo": 09:30:00,
"WedFrom": 09:30:00,
"WedTo": 09:30:00,
"ThuFrom": 09:30:00,
"ThuTo": 09:30:00,
"FriFrom": 09:30:00,
"FriTo": 09:30:00,
"SatFrom": 09:30:00,
"SatTo": 09:30:00,
"SunFrom": 09:30:00,
"SunTo": 09:30:00,
}
]
}

Success Response Body (DealerCreationResponse):

{
"status": 200,
"message": "Dealer created successfully",
"dealerCode": "DL-00001",
"dealerId": "11111111-1111-1111-1111-111111111111"
}

Error Response Body (CommonResponse example):

{
"status": 400,
"message": "Exactly one legal address and one billing address are required."
}

2. List Dealers

Endpoint:
GET /list?pageNumber=1&rowsPerPage=10&dealerCode=DL-00001&status=Active

Query Parameters: Map to DealerFilter.

Response Body (ServerPaginatedData<Dealer>):

{
"data": [
{
"dealerId": "11111111-1111-1111-1111-111111111111",
"dealerCode": "DL-00001",
"primaryEmail": "contact@dealer.com",
"companyName": "Best Dealer Ltd",
"ownerName": "John Doe",
"phone": "+1-555-0100",
"website": "https://dealer.example.com",
"dealershipType": [ "New" ],
"brand": [ "Toyota" ],
"status": "Active",
"dealerLogoUrl": "https://example.com/logo.png",
"createdAt": "2025-01-01T10:00:00Z",
"updatedAt": "2025-01-02T12:00:00Z",
"addresses": [
{
"addressId": "22222222-2222-2222-2222-222222222222",
"dealerId": "11111111-1111-1111-1111-111111111111",
"addressType": "Legal",
"addressLine": "123 Legal St",
"state": "CA",
"zipcode": "90001",
"country": "USA",
"latitude": 34.0522,
"longitude": -118.2437,
"radius": 50.0,
"isPrimary": true
}
],
"ContactPersons": [
{
"ContactId": "00000000-0000-0000-0000-000000002222",
"DealerId": "00000000-0000-0000-0000-0000000033333",
"FirstName": "Jhon",
"LastName": "Doe",
"Phone": "9988776651",
"Email": "gm@gmail.com",
"IsUser": false,
"Designation": "General Manager"
},
{
"ContactId": "00000000-0000-0000-0000-000000002222",
"DealerId": "00000000-0000-0000-0000-0000000033333",
"FirstName": "Alison",
"LastName": "Drew",
"Phone": "9988274651",
"Email": "gm2@gmail.com",
"IsUser": true,
"Designation": "Billing Manager"
}
],
"DepartmentBusinessHours": [
{
"DeptName": "Sales",
"DealerId": "00000000-0000-0000-0000-000000000000",
"MonFrom": 09:30:00,
"MonTo": 09:30:00,
"TueFrom": 09:30:00,
"TueTo": 09:30:00,
"WedFrom": 09:30:00,
"WedTo": 09:30:00,
"ThuFrom": 09:30:00,
"ThuTo": 09:30:00,
"FriFrom": 09:30:00,
"FriTo": 09:30:00,
"SatFrom": 09:30:00,
"SatTo": 09:30:00,
"SunFrom": 09:30:00,
"SunTo": 09:30:00,
},
{
"DeptName": "Parts",
"DealerId": "00000000-0000-0000-0000-000000000000",
"MonFrom": 09:30:00,
"MonTo": 09:30:00,
"TueFrom": 09:30:00,
"TueTo": 09:30:00,
"WedFrom": 09:30:00,
"WedTo": 09:30:00,
"ThuFrom": 09:30:00,
"ThuTo": 09:30:00,
"FriFrom": 09:30:00,
"FriTo": 09:30:00,
"SatFrom": 09:30:00,
"SatTo": 09:30:00,
"SunFrom": 09:30:00,
"SunTo": 09:30:00,
}
]
}
],
"totalNumber": 1,
"hasPreviousPage": false,
"hasNextPage": false,
"totalPages": 1,
"pageNumber": 1,
"rowsPerPage": 10
}

3. Get Dealer Details

Endpoint: GET /details/{dealerId}

Response Body (Dealer):

{
"dealerId": "11111111-1111-1111-1111-111111111111",
"dealerCode": "DL-00001",
"primaryEmail": "contact@dealer.com",
"companyName": "Best Dealer Ltd",
"ownerName": "John Doe",
"phone": "+1-555-0100",
"website": "https://dealer.example.com",
"dealershipType": [ "New" ],
"brand": [ "Toyota" ],
"status": "Active",
"dealerLogoUrl": "https://example.com/logo.png",
"createdAt": "2025-01-01T10:00:00Z",
"updatedAt": "2025-01-02T12:00:00Z",
"addresses": [
{
"addressId": "22222222-2222-2222-2222-222222222222",
"dealerId": "11111111-1111-1111-1111-111111111111",
"addressType": "Legal",
"addressLine": "123 Legal St",
"state": "CA",
"zipcode": "90001",
"country": "USA",
"latitude": 34.0522,
"longitude": -118.2437,
"radius": 50.0,
"isPrimary": true
}
]
}

4. Update Dealer

Endpoint: PUT /update/{dealerId}

Request Body (Dealer – partial update example):

{
"dealerId": "11111111-1111-1111-1111-111111111111",
"primaryEmail": "new-contact@dealer.com",
"companyName": "Best Dealer Ltd",
"phone": "+1-555-0200",
"website": "https://dealer-updated.example.com",
"addresses": [
{
"addressId": "22222222-2222-2222-2222-222222222222",
"dealerId": "11111111-1111-1111-1111-111111111111",
"addressType": "Legal",
"addressLine": "123 Legal St, Suite 200",
"state": "CA",
"zipcode": "90001",
"country": "USA",
"latitude": null,
"longitude": null,
"radius": 50.0,
"isPrimary": true
}
]
}

Success Response Body (CommonResponse):

{
"status": 200,
"message": "Dealer updated successfully"
}

5. Get Address Suggestions

Endpoint:
GET /address-suggestion?dealerId=11111111-1111-1111-1111-111111111111

Response Body (List<DealerAddress>):

[
{
"addressId": "22222222-2222-2222-2222-222222222222",
"dealerId": "11111111-1111-1111-1111-111111111111",
"addressType": "Legal",
"addressLine": "123 Legal St",
"state": "CA",
"zipcode": "90001",
"country": "USA",
"latitude": 34.0522,
"longitude": -118.2437,
"radius": 50.0,
"isPrimary": true
}
]

6. Update Dealer Radius

Endpoint: PUT /update-radius

Request Body (UpdateDealerRadiusRequest):

{
"radius": 150.5
}

Success Response Body (CommonResponse):

{
"status": 200,
"message": "Dealer radius updated successfully"
}

Important Notes for Frontend Developers

  1. Models: All request and response bodies are based directly on the C# models in ContractLibrary.Dealer and ContractLibrary.Common.

  2. GUID Format: All IDs are Guid in C# and serialized as standard GUID strings like "11111111-1111-1111-1111-111111111111".

  3. Pagination Structure: List endpoints use ServerPaginatedData<T> with the following JSON shape:

    {
    "data": [],
    "totalNumber": 0,
    "hasPreviousPage": false,
    "hasNextPage": false,
    "totalPages": 0,
    "pageNumber": 1,
    "rowsPerPage": 10
    }
  4. Address Constraints: The backend enforces:

    • Exactly one Legal and one Billing address per dealer.
    • Legal and billing addresses cannot be deleted or duplicated.

Data Relationships

  • dealermaster (1) ← (M) dealeraddress: One dealer can have multiple addresses.

Business Logic

Dealer Lifecycle

  1. Creation: Admin creates dealer with required data (Addresses, Contact Person details, Business Hours).
  2. Onboarding: Primary user is created and welcome notification is sent.
  3. Dealers and Managers can activate their profile from mail.
  4. Listing & Search: Admin lists dealers using filters and pagination.
  5. Update: Admin or dealer updates dealer profile and addresses with validation.
  6. Radius Management: Dealer radius is updated and stored on dealer record.

Address Rules

  • Exactly one legal and one billing address must exist for each dealer.
  • Optional additional addresses (e.g., Shipping, Branch) can be added or removed.

Authorization Rules

  • Admin-only operations:
    • Dealer creation.
    • Viewing full dealer list.
  • Dealer-specific restrictions:
    • Dealers can only update their own dealer record, enforced via entity_id claim.

Third-Party Integrations

  • RabbitMQ / MassTransit: Used for:
    • User creation/update requests to Auth services.
    • Batch notification publishing (BatchNotificationRequest).
  • Notification Service: Sends welcome and activation emails for new dealers.
  • Geolocation: Used by IGeolocationService to enrich addresses with latitude/longitude.

Workflow

Dealer Creation Workflow

  1. Admin calls POST /create with dealer, contact persons, business hours and address data.
  2. Service validates input and checks for duplicate email/phone.
  3. Unique dealer code is generated and dealer is saved.
  4. Addresses are validated and saved with geolocation.
  5. Contact Person Details are saved.
  6. Contact Person are also saved as user if IsUser field is true.
  7. Business Hours for each department is saved.
  8. Primary user is created.
  9. Welcome notification is published.

Dealer Update Workflow

  1. Admin or dealer calls PUT /update/{dealerId}.
  2. Service validates authorization (including entity_id for dealers).
  3. Dealer core data and addresses are validated.
  4. Dealer and address changes are applied in a transaction.
  5. If primary email/phone changed, user is updated.

Radius Update Workflow

  1. Dealer calls PUT /update-radius with new radius.
  2. Service validates user type and entity ID, and radius range.
  3. Dealer radius is updated in dealermaster.
  4. Audit fields are updated.

Testing & Quality Assurance

Unit Tests Exists for:

  • Dealer Controllers
  • Dealer Helpers

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

  1. Dealer creation fails when:
    • Duplicate email/phone exists.
    • Exactly one legal and billing address are not provided.
  2. Dealer listing returns correct pagination metadata and honors all filters.
  3. Dealer update enforces address and permission rules.
  4. Radius update is allowed only for valid users and within valid range.

Feature Complete When:

  1. ✅ Admins can create, update, and list dealers
  2. ✅ Dealers can update their own information
  3. ✅ Dealers can update their service radius (validated 1-1000 KM, dealer-only access)
  4. ✅ All endpoints enforce correct access control
  5. ✅ Pagination and filtering work as expected
  6. ✅ Address suggestions are accurate and secure
  7. ✅ All error cases return correct status and message
  8. ✅ Unit test coverage >85%
  9. ✅ All integration tests pass

Deployment Considerations

Database Migration

  • Tables dealermaster, dealeraddress, and zipcodedetails must exist with columns used by DealerDAL.
  • Any new columns used by this service (e.g., radius on dealermaster) must be added before deploying updated code.

Monitoring & Alerts

  • Monitor:
    • Dealer creation and update error logs (DealerHelper, DealerDAL).
    • Failed geolocation lookups and zipcode insert failures.
    • MassTransit publish failures for notifications.

Configuration Changes

  • Ensure database schema for dealers and addresses is up to date
  • Configure JWT authentication and required claims in IAMService
  • Update Swagger documentation

Security Considerations

Authorization

  • All DealerController endpoints require authenticated JWT tokens.
  • Scopes (Right.dealer_create, Right.dealer_view_all, Right.dealer_view, Right.dealer_update) are enforced via attributes and middleware.
  • Additional checks based on:
    • user_type claim (Admin vs Dealer).
    • entity_id claim (dealer-specific access control).

Data Protection

  • Dealer email and phone are central identifiers; duplicates are explicitly checked to avoid conflicts.
  • Audit fields (logUsername, logDts) are maintained on dealer and address records for traceability.
  • Zipcode geolocation is cached and reused, minimizing calls to external services.

Risks & Mitigations

RiskImpactLikelihoodMitigation Strategy
Incorrect address configuration (missing legal/billing)HighMediumStrict validation in helper methods and helpful error messages
Duplicate dealer email or phoneHighMediumDuplicate checks before insert/update in DealerHelper
Geolocation failures for zipcodesMediumMediumWarning logs and graceful degradation without blocking create/update
Auth or notification service failuresMediumLowLogging errors and isolating them from core DB transactions
Misconfigured JWT or scopesHighLowCentralized auth configuration in Program.cs and middleware
Unauthorized access to endpointsHighMediumStrict JWT and claim validation, logging, and auditing
Data inconsistency on updateMediumLowUse 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