Dealer Onboard
Author(s)
- Yeasa Mondal
Last Updated Date
2025-10-27
SRS References
Version History
| Version | Date | Changes | Author |
|---|---|---|---|
| 1.0 | 2025-10-27 | Initial draft | Yeasa Mondal |
Feature Overview
Objective:
Implement an automated dealer onboarding system for the MuddVision platform that runs as a daily background job. The system queries Salesforce for all dealers with muddVisionPlatform = true, validates their external service integrations (Workfront, Authenticom, Facebook Ads, etc.), and imports dealer records into the local database. This ensures the MuddVision platform maintains an up-to-date dealer registry synchronized with Salesforce without manual intervention.
Scope:
- Daily scheduled background job using .NET IHostedService (BackgroundService)
- MassTransit-based communication with Salesforce to fetch dealer records
- Automated data synchronization (Upsert) using
clientIdas the unique key. New dealers are inserted, and existing dealers are updated. - Asynchronous verification of external service IDs (Workfront, Authenticom, Facebook Ads)
- Transactional dealer and address record creation with primary address support
- Per-service verification flags and timestamps for partial import support
- Comprehensive logging, metrics, and alerting for job execution
Dependencies:
- Salesforce: Source of dealer data; dealers with
muddVisionPlatform = true - MassTransit: Message-based communication with Salesforce consumer
- External Verification Services: Workfront, Authenticom, Facebook Ads (APIs pending)
- Dealer Management Service: Hosts the background job
- Database: DealerMaster table with external ID fields, Address table with primary address FK
Implementation Details
Background Job Architecture
The Dealer Onboard feature is implemented as a scheduled background job DailySalesforceOnboardJob within the Dealer Management Service using .NET IHostedService (BackgroundService pattern).
Job Configuration:
- Schedule: Once daily (configurable via cron expression) ⏳ Pending: exact time and timezone
- Batch Processing: Configurable batch size for processing dealers ⏳ Pending: batch size and concurrency limits
- Retry Policy: Exponential backoff for transient failures
- Execution Mode: Dry-run and production modes supported
Job Execution Flow
Step 1: Fetch Dealers from Salesforce
The job uses MassTransit to request all dealers from Salesforce where muddVisionPlatform = true.
MassTransit Request Contract:
public class GetSalesforceDealersRequest
{
public string Filter { get; set; } // SOQL filter: "muddVisionPlatform = true"
public int? BatchSize { get; set; } // Optional batch size
}
public class GetSalesforceDealersResponse
{
public MuddStatusCode StatusCode { get; set; } // MuddStatusCode
public List<AccountDto> Dealers { get; set; }
public string Message { get; set; }
}
Step 2: Data Synchronization (Upsert Strategy)
The job performs an "Upsert" operation (Update or Insert) to synchronize dealer data from Salesforce with the local database. This ensures that new dealers are added and existing dealers are updated with the latest information.
Synchronization Strategy:
- Query for Existing Dealer: For each dealer from Salesforce, the system first queries the
DealerMastertable byclientId. - Update or Insert:
- If a dealer with the matching
clientIdis found, the existing record is updated with the new data from Salesforce. - If no matching dealer is found, a new record is inserted into the
DealerMastertable.
- If a dealer with the matching
Benefits of This Approach:
- Data Consistency: Keeps the local database synchronized with Salesforce.
- No Duplicates: Prevents the creation of duplicate records by using
clientIdas a unique identifier. - Efficiency: A single, clear workflow handles both new and existing dealers.
Synchronization Rule: Use clientId (muddId) as the unique key for all Upsert operations.
Step 3: Transactional Import
Create dealer and address records atomically in a database transaction:
- Insert dealer record into
DealerMastertable - Insert address records into
Addresstable - Set
PrimaryAddressIdFK inDealerMasterpointing to primary address - Commit transaction
Transaction Rules:
- All inserts must succeed or rollback entirely
- Primary address must be identified and linked
- Set initial verification flags to
false(unverified)
Step 4: Asynchronous Verification (Fire-and-forget)
After successful import, trigger asynchronous verification of external service IDs:
Verification Process:
- Call verification endpoints for each external service (Workfront, Authenticom, Facebook Ads, etc.)
- Update verification flags and timestamps in
DealerMastertable - Log verification results (success/failure)
- Continue processing even if verification fails (partial import allowed)
Verification Result Model:
:::warning[Verification APIs Pending]
External verification endpoints are not yet implemented. The job will record unverified status and retry verification when APIs become available.
:::
---
**Upsert Handling Flow:**
For each dealer from Salesforce: │ ├─► Call: UpsertDealerAsync(dealerDto) │ │ │ ├─► Query database for existing dealer by clientId │ │ │ │ │ ├─► Found? │ │ │ ├─► Yes → Update existing record │ │ │ │ Log: "Successfully updated ClientId" │ │ │ │ Count as "updated" │ │ │ │ │ │ │ └─► No → Insert new record │ │ │ │ │ │ │ ├─► Success? │ │ │ │ ├─► Yes → Log: "Successfully imported ClientId" │ │ │ │ │ Count as "imported" │ │ │ │ │ │ │ │ │ └─► No → Log: "Failed to import ClientId: Error" │ │ │ │ Count as "failed" │ │ │ │ │ │ │ └─► Continue to next dealer │ │ │ │ │ └─► Continue to next dealer
### **MassTransit Communication Pattern**
┌──────────────────────────┐
│ DailySalesforceOnboardJob│
│ (IHostedService) │
└────────┬─────────────────┘
│
│ 1. Publish GetSalesforceDealersRequest
│ (Filter: muddVisionPlatform = true)
▼
┌──────────────────────────┐
│ MassTransit Bus │
└────────┬─────────────────┘
│
│ 2. Route to Salesforce consumer
▼
┌──────────────────────────┐
│ Salesforce Consumer │
│ - Query SF via SOQL │
│ - Map to DTOs │
│ - Return dealer list │
└────────┬─────────────────┘
│
│ 3. Response: List<AccountDto>
▼
┌──────────────────────────┐
│ DailySalesforceOnboardJob│
│ - Query for existing dealer│
│ - Update or Insert (Upsert)│
│ - Count & log results │
│ - Async verification │
└────── ────────────────────┘
---
## **Database Schema**
### **DealerMaster Table**
Primary table for storing dealer information with external service IDs and verification flags.
| Column | Type | Constraints | Description |
|-------------------------|--------------|------------------------------|--------------------------------------------|
| id | VARCHAR(50) | PRIMARY KEY, AUTO-GENERATED | Unique dealer identifier |
| name | VARCHAR(255) | NOT NULL | Dealer name |
| email | VARCHAR(255) | NOT NULL, UNIQUE | Dealer email address |
| phone | VARCHAR(50) | NOT NULL | Contact phone number |
| address | VARCHAR(50) | NOT NULL | address |
| billingAddress | jsonB | | address in json with city/state/etc |
| clientid | VARCHAR(100) | NOT NULL, UNIQUE | Salesforce client ID (muddId) |
| dvdId | VARCHAR(100) | | Authenticom dealer ID |
| accountOwnerId | VARCHAR(100) | | Salesforce account owner ID |
| workfrontId | VARCHAR(100) | | Workfront integration ID |
| facebookAdId | VARCHAR(100) | | Facebook Ads account ID |
| make | VARCHAR(100) | | Vehicle make |
| status | VARCHAR(50) | NOT NULL | Dealer account status (Active/Inactive) |
| source | VARCHAR(50) | NOT NULL, DEFAULT 'Salesforce' | Source of dealer record |
| lastVerifiedAt | DATETIME | NULL | Last verification attempt timestamp |
| importedAt | DATETIME | NOT NULL, DEFAULT NOW() | Import timestamp from Salesforce |
| createdAt | DATETIME | NOT NULL, DEFAULT NOW() | Record creation timestamp |
| updatedAt | DATETIME | NOT NULL, DEFAULT NOW() | Last update timestamp |
**Indexes:**
- PRIMARY KEY on `Id`
- UNIQUE INDEX on `clientId`
- UNIQUE INDEX on `email`
- INDEX on `status` (for filtering active dealers)
- INDEX on `importedAt` (for monitoring recent imports)
- INDEX on `isWorkfrontVerified, isAuthenticomVerified, isFacebookVerified` (for reporting unverified dealers)
---
## **Workflow**
The end-to-end workflow describes how different components interact throughout the dealer onboarding process:
### **5. Data Flow Summary**
Salesforce (Source) ↓ SOQL Query (muddVisionPlatform = true) MassTransit Bus ↓ GetSalesforceDealersResponse DailySalesforceOnboardJob ↓ Query for existing dealer by clientId Database (DealerMaster + Address tables) ↓ Found? Yes -> Update Record ↓ Found? No -> Insert Record ↓ Success: Fire-and-Forget External Verification Services (Workfront, Authenticom, Facebook) ↓ Verification Results Database (Update verification flags)
---
## **Development Tasks & Estimates**
| **No** | **Task Name** | **Estimate (Hours)** | **Dependencies** | **Notes** |
|--------|--------------------------------------------------------------|----------------------|------------------------|-------------------------------------------------------------|
| 1 | Build Salesforce consumer for account retrieval (MassTransit)| 4 hours | None | API endpoint with filter `muddVisionPlatform = true`, SOQL query implementation |
| 2 | Database configuration - Create `DealerMaster` tables | 6 hours | None | DDL scripts with UNIQUE constraints, indexes, FK relationships |
| 3 | Implement `DailySalesforceOnboardJob` (IHostedService) | 6 hours | Task 1, Task 2 | Background job with timer, MassTransit client, exception handling |
| 4 | Build dealer import logic with Upsert functionality | 4 hours | Task 2, Task 3 | `UpsertDealerAsync()`, transaction management, update/insert logic |
| 5 | Implement external service verification (Workfront) | - | Task 4 | API client, `VerifyWorkfrontAsync()`, flag update logic |
| 6 | Implement external service verification (Authenticom) | - | Task 4 | API client, `VerifyAuthenticomAsync()`, flag update logic |
| 7 | Implement external service verification (Facebook Ads) | - | Task 4 | API client, `VerifyFacebookAsync()`, flag update logic |
| 8 | Add comprehensive logging, metrics, and alerting | - | Task 3 | Structured logging, Prometheus metrics, alert thresholds |
| 9 | Unit testing (all components) | 8 hours | Tasks 1-7 | Mock external services, test duplicate handling, transaction rollback |
| 10 | Integration testing (end-to-end job execution) | 8 hours | Tasks 1-7 | Test with live Salesforce sandbox, verify data integrity |
| 12 | **Total** | **36 hours** | - | Approximately 2 weeks for 1 developer |
---
## **Process Flow Diagram**
### **Daily Background Job Flow**
┌────────────────────────────────┐
│ Scheduler (IHostedService) │
│ DailySalesforceOnboardJob │
│ Runs once daily at 2 AM UTC │
└────────────┬───────────────────┘
│
│ 1. Timer triggers job execution
▼
┌────────────────────────────────┐
│ Publish MassTransit Request │
│ GetSalesforceDealersRequest │
│ Filter: muddVisionPlatform │
│ = true │
└────────────┬───────────────────┘
│
│ 2. Route via MassTransit Bus
▼
┌────────────────────────────────┐
│ Salesforce Consumer Service │
│ - Execute SOQL query │
│ - Fetch dealer records │
│ - Map to DTOs │
└────────────┬── ─────────────────┘
│
│ 3. Response: List<AccountDto>
▼
┌────────────────────────────────┐
│ DailySalesforceOnboardJob │
│ Process each dealer │
└────────────┬───────────────────┘
│
│ For each dealer:
▼
┌────────────────────────────────┐
│ Step 1: Upsert Dealer │
│ Call UpsertDealerAsync() │
└────────────┬───────────────────┘
│
│ Query by clientId
▼
┌────────────────────────────────┐
│ Dealer Found in DB? │
└────────────┬───────────────────┘
│
├─► Yes ──┐
│ ▼
│ ┌────────────────────────────┐
│ │ Update Existing Record │
│ │ Count as "updated" │
│ │ Log: "Updated ClientId" │
│ └────────────┬───────────────┘
│ │
└─► No ──────────┤
▼
┌────────────────────────────────┐
│ Insert New Record │
└────────┬───────────────────────┘
│
├─► Success ──┐
│ ▼
│ ┌────────────────────────────┐
│ │ Count as "imported" │
│ │ Log: "Imported ClientId" │
│ └────────────┬───────────────┘
│ │
└─► Failure ──────────┐
▼
┌────────────────────────────┐
│ Count as "failed" │
│ Log: "Import failed" │
└────────────┬───────────────┘
│
│ All paths continue to next dealer
▼
┌────────────────────────────────┐
│ Step 2: Async Verification │
│ (Only if imported) │
│ - Workfront.VerifyById() │
│ - Authenticom.VerifyById() │
│ - Facebook.VerifyById() │
└────────────┬───────────────────┘
│
│ Success
▼
┌────────────────────────────────┐
│ Step 3: Fire-and-Forget │
│ Async Verification │
│ (Do not block import) │
└────────────┬───────────────────┘
│
│ Parallel calls
▼
┌────────────────────────────────┐
│ External Service Verification│
│ - Workfront.VerifyById() │
│ - Authenticom.VerifyById() │
│ - Facebook.VerifyById() │
└────────────┬───────────────────┘
│
│ Collect results
▼
┌────────────────────────────────┐
│ Update Verification Flags │
│ - isWorkfrontVerified │
│ - isAuthenticomVerified │
│ - isFacebookVerified │
│ - lastVerifiedAt │
└────────────┬───────────────────┘
│
▼
┌────────────────────────────────┐
│ Log verification results │
│ Continue to next dealer │
└────────────────────────────────┘
After all dealers processed: │ ▼ ┌────────────────────────────────┐ │ Job Completion Summary │ │ - Log metrics: │ │ • Imported count │ │ • Updated count │ │ • Failed count │ │ • Duration │ │ - Update health metrics │ │ - Send alerts if failures │ └────────────────────────────────┘
### **Error Handling Flow**
┌────────────────────────┐ │ Process Dealer │ └───────┬────────────────┘ │ ├─ Salesforce Error ──→ Log error, retry after backoff │ ├─ Deduplication Check Error ──→ Log warning, skip dealer │ ├─ Transaction Failure ──→ Rollback, log error, increment failed counter │ └─ Verification Failure ──→ Log warning, set flags to false, continue (partial import allowed)