From 855cfc5c38d963837c790736a0d47b3309acb134 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Wed, 24 Dec 2025 20:41:22 +0530 Subject: [PATCH] dealer seed added and api created based on dealer table --- package.json | 1 + src/controllers/dealer.controller.ts | 47 ++++- src/routes/dealer.routes.ts | 7 + src/scripts/seed-test-dealer.ts | 179 +++++++++++++++++++ src/services/dealer.service.ts | 254 ++++++++++++++++++--------- 5 files changed, 398 insertions(+), 90 deletions(-) create mode 100644 src/scripts/seed-test-dealer.ts diff --git a/package.json b/package.json index a16f04c..42309bd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "setup": "ts-node -r tsconfig-paths/register src/scripts/auto-setup.ts", "migrate": "ts-node -r tsconfig-paths/register src/scripts/migrate.ts", "seed:config": "ts-node -r tsconfig-paths/register src/scripts/seed-admin-config.ts", + "seed:test-dealer": "ts-node -r tsconfig-paths/register src/scripts/seed-test-dealer.ts", "cleanup:dealer-claims": "ts-node -r tsconfig-paths/register src/scripts/cleanup-dealer-claims.ts" }, "dependencies": { diff --git a/src/controllers/dealer.controller.ts b/src/controllers/dealer.controller.ts index 992e31d..b4a1bb4 100644 --- a/src/controllers/dealer.controller.ts +++ b/src/controllers/dealer.controller.ts @@ -7,11 +7,15 @@ import logger from '../utils/logger'; export class DealerController { /** * Get all dealers - * GET /api/v1/dealers + * GET /api/v1/dealers?q=searchTerm&limit=10 (optional search and limit) */ async getAllDealers(req: Request, res: Response): Promise { try { - const dealers = await dealerService.getAllDealers(); + const searchTerm = req.query.q as string | undefined; + const limitParam = req.query.limit as string | undefined; + // Parse limit, default to 10, max 100 + const limit = limitParam ? Math.min(Math.max(1, parseInt(limitParam, 10)), 100) : 10; + const dealers = await dealerService.getAllDealers(searchTerm, limit); return ResponseHandler.success(res, dealers, 'Dealers fetched successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; @@ -64,17 +68,19 @@ export class DealerController { /** * Search dealers - * GET /api/v1/dealers/search?q=searchTerm + * GET /api/v1/dealers/search?q=searchTerm&limit=10 */ async searchDealers(req: Request, res: Response): Promise { try { - const { q } = req.query; + const { q, limit: limitParam } = req.query; if (!q || typeof q !== 'string') { return ResponseHandler.error(res, 'Search term is required', 400); } - const dealers = await dealerService.searchDealers(q); + // Parse limit, default to 10, max 100 + const limit = limitParam ? Math.min(Math.max(1, parseInt(limitParam as string, 10)), 100) : 10; + const dealers = await dealerService.searchDealers(q, limit); return ResponseHandler.success(res, dealers, 'Dealers searched successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; @@ -82,5 +88,36 @@ export class DealerController { return ResponseHandler.error(res, 'Failed to search dealers', 500, errorMessage); } } + + /** + * Verify dealer is logged in + * GET /api/v1/dealers/verify/:dealerCode + * Returns dealer info with isLoggedIn flag + */ + async verifyDealerLogin(req: Request, res: Response): Promise { + try { + const { dealerCode } = req.params; + const dealer = await dealerService.getDealerByCode(dealerCode); + + if (!dealer) { + return ResponseHandler.error(res, 'Dealer not found', 404); + } + + if (!dealer.isLoggedIn) { + return ResponseHandler.error( + res, + 'Dealer not logged in to the system', + 400, + `The dealer with code ${dealerCode} (${dealer.email}) has not logged in to the system. Please ask them to log in first.` + ); + } + + return ResponseHandler.success(res, dealer, 'Dealer verified and logged in'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + logger.error('[DealerController] Error verifying dealer login:', error); + return ResponseHandler.error(res, 'Failed to verify dealer', 500, errorMessage); + } + } } diff --git a/src/routes/dealer.routes.ts b/src/routes/dealer.routes.ts index 580c350..ee5e114 100644 --- a/src/routes/dealer.routes.ts +++ b/src/routes/dealer.routes.ts @@ -34,5 +34,12 @@ router.get('/code/:dealerCode', authenticateToken, asyncHandler(dealerController */ router.get('/email/:email', authenticateToken, asyncHandler(dealerController.getDealerByEmail.bind(dealerController))); +/** + * @route GET /api/v1/dealers/verify/:dealerCode + * @desc Verify dealer is logged in to the system + * @access Private + */ +router.get('/verify/:dealerCode', authenticateToken, asyncHandler(dealerController.verifyDealerLogin.bind(dealerController))); + export default router; diff --git a/src/scripts/seed-test-dealer.ts b/src/scripts/seed-test-dealer.ts new file mode 100644 index 0000000..a253a8a --- /dev/null +++ b/src/scripts/seed-test-dealer.ts @@ -0,0 +1,179 @@ +/** + * Seed Test Dealer + * Creates a test dealer record in the dealers table for testing purposes + * + * This creates a dealer with: + * - domain_id: testreflow@example.com + * - dealer_principal_email_id: testreflow@example.com + * - dealer_principal_name: TEST REFLOW + * - Random codes for sales_code, gear_code, and dlrcode to avoid conflicts + */ + +import { sequelize } from '../config/database'; +import { Dealer } from '../models/Dealer'; +import { Op } from 'sequelize'; +import logger from '../utils/logger'; + +/** + * Generate a random 4-digit code + * Checks for existing codes to avoid conflicts + */ +async function generateUniqueCode( + field: 'salesCode' | 'serviceCode' | 'gearCode' | 'gmaCode' | 'dlrcode', + existingCodes: Set +): Promise { + let attempts = 0; + const maxAttempts = 100; + + while (attempts < maxAttempts) { + // Generate random 4-digit number (1000-9999) + const randomCode = String(Math.floor(1000 + Math.random() * 9000)); + + // Check if code already exists in database + const existing = await Dealer.findOne({ + where: { + [field]: randomCode + } + }); + + // Also check if we've already generated this code in this run + if (!existing && !existingCodes.has(randomCode)) { + existingCodes.add(randomCode); + return randomCode; + } + + attempts++; + } + + // Fallback: use timestamp-based code if random generation fails + const timestampCode = String(Date.now()).slice(-4); + logger.warn(`[Seed Test Dealer] Using timestamp-based code for ${field}: ${timestampCode}`); + return timestampCode; +} + +async function seedTestDealer(): Promise { + try { + logger.info('[Seed Test Dealer] Starting test dealer seeding...'); + + // Check if test dealer already exists + const existingDealer = await Dealer.findOne({ + where: { + [Op.or]: [ + { domainId: 'testreflow@example.com' }, + { dealerPrincipalEmailId: 'testreflow@example.com' } + ] + } + }); + + // Generate unique codes + const existingCodes = new Set(); + const salesCode = await generateUniqueCode('salesCode', existingCodes); + const serviceCode = await generateUniqueCode('serviceCode', existingCodes); + const gearCode = await generateUniqueCode('gearCode', existingCodes); + const gmaCode = await generateUniqueCode('gmaCode', existingCodes); + const dlrcode = await generateUniqueCode('dlrcode', existingCodes); + + const dealerData = { + salesCode, + serviceCode, + gearCode, + gmaCode, + region: 'TEST', + dealership: 'TEST REFLOW DEALERSHIP', + state: 'Test State', + district: 'Test District', + city: 'Test City', + location: 'Test Location', + cityCategoryPst: 'A', + layoutFormat: 'A', + tierCityCategory: 'Tier 1 City', + onBoardingCharges: null, + date: new Date().toISOString().split('T')[0], // Current date in YYYY-MM-DD format + singleFormatMonthYear: (() => { + const now = new Date(); + const month = now.toLocaleDateString('en-US', { month: 'short' }); + const year = now.getFullYear(); + return `${month}-${year}`; // e.g., "Dec-2025" (format: "MMM-YYYY") + })(), + domainId: 'testreflow@example.com', + replacement: null, + terminationResignationStatus: null, + dateOfTerminationResignation: null, + lastDateOfOperations: null, + oldCodes: null, + branchDetails: null, + dealerPrincipalName: 'TEST REFLOW', + dealerPrincipalEmailId: 'testreflow@example.com', + dpContactNumber: null, + dpContacts: null, + showroomAddress: null, + showroomPincode: null, + workshopAddress: null, + workshopPincode: null, + locationDistrict: null, + stateWorkshop: null, + noOfStudios: 0, + websiteUpdate: 'Yes', + gst: null, + pan: null, + firmType: 'Test Firm', + propManagingPartnersDirectors: 'TEST REFLOW', + totalPropPartnersDirectors: 'TEST REFLOW', + docsFolderLink: null, + workshopGmaCodes: null, + existingNew: 'New', + dlrcode, + isActive: true + }; + + if (existingDealer) { + logger.info('[Seed Test Dealer] Test dealer already exists, updating...'); + + // Update existing dealer + await existingDealer.update(dealerData); + + logger.info(`[Seed Test Dealer] ✅ Updated test dealer: ${existingDealer.dealerId}`); + logger.info(`[Seed Test Dealer] - Domain ID: ${dealerData.domainId}`); + logger.info(`[Seed Test Dealer] - Dealer Principal: ${dealerData.dealerPrincipalName}`); + logger.info(`[Seed Test Dealer] - Sales Code: ${dealerData.salesCode}`); + logger.info(`[Seed Test Dealer] - Gear Code: ${dealerData.gearCode}`); + logger.info(`[Seed Test Dealer] - Dealer Code (dlrcode): ${dealerData.dlrcode}`); + } else { + // Create new dealer + const newDealer = await Dealer.create(dealerData); + + logger.info(`[Seed Test Dealer] ✅ Created test dealer: ${newDealer.dealerId}`); + logger.info(`[Seed Test Dealer] - Domain ID: ${dealerData.domainId}`); + logger.info(`[Seed Test Dealer] - Dealer Principal: ${dealerData.dealerPrincipalName}`); + logger.info(`[Seed Test Dealer] - Sales Code: ${dealerData.salesCode}`); + logger.info(`[Seed Test Dealer] - Gear Code: ${dealerData.gearCode}`); + logger.info(`[Seed Test Dealer] - Dealer Code (dlrcode): ${dealerData.dlrcode}`); + } + + logger.info('[Seed Test Dealer] ✅ Test dealer seeding completed successfully'); + } catch (error) { + logger.error('[Seed Test Dealer] ❌ Error seeding test dealer:', error); + throw error; + } +} + +// Run if called directly +if (require.main === module) { + sequelize + .authenticate() + .then(() => { + logger.info('[Seed Test Dealer] Database connection established'); + return seedTestDealer(); + }) + .then(() => { + logger.info('[Seed Test Dealer] Seeding completed'); + process.exit(0); + }) + .catch((error) => { + logger.error('[Seed Test Dealer] Seeding failed:', error); + process.exit(1); + }); +} + +export { seedTestDealer }; + diff --git a/src/services/dealer.service.ts b/src/services/dealer.service.ts index d047611..b72314a 100644 --- a/src/services/dealer.service.ts +++ b/src/services/dealer.service.ts @@ -1,55 +1,111 @@ /** * Dealer Service * Handles dealer-related operations for claim management + * Fetches from dealers table and checks if dealer is logged in (domain_id exists in users table) */ import { User } from '../models/User'; +import { Dealer } from '../models/Dealer'; import { Op } from 'sequelize'; import logger from '../utils/logger'; export interface DealerInfo { - userId: string; - email: string; - dealerCode: string; - dealerName: string; - displayName: string; - phone?: string; + dealerId: string; + userId?: string | null; // User ID if dealer is logged in + email: string; // domain_id from dealers table + dealerCode: string; // dlrcode from dealers table + dealerName: string; // dealership from dealers table + displayName: string; // dealer_principal_name from dealers table + phone?: string; // dp_contact_number from dealers table department?: string; designation?: string; + isLoggedIn: boolean; // Whether dealer's domain_id exists in users table + salesCode?: string | null; + serviceCode?: string | null; + gearCode?: string | null; + gmaCode?: string | null; + region?: string | null; + state?: string | null; + district?: string | null; + city?: string | null; + dealerPrincipalName?: string | null; + dealerPrincipalEmailId?: string | null; } /** - * Get all dealers (users with designation = 'dealer') - * Note: If employeeId is empty, generates a dummy dealerCode like "RE-MH-001" in the response - * The database employeeId remains unchanged - this is only for the API response + * Get all dealers from dealers table + * Checks if dealer is logged in by matching domain_id with users.email + * @param searchTerm - Optional search term to filter dealers + * @param limit - Maximum number of records to return (default: 10) */ -export async function getAllDealers(): Promise { +export async function getAllDealers(searchTerm?: string, limit: number = 10): Promise { try { - const dealers = await User.findAll({ - where: { - designation: { [Op.iLike]: 'dealer' } as any, - isActive: true, - }, - order: [['displayName', 'ASC']], + // Build where clause for search + const whereClause: any = { + isActive: true, + }; + + if (searchTerm && searchTerm.trim()) { + whereClause[Op.or] = [ + { dealership: { [Op.iLike]: `%${searchTerm}%` } as any }, + { dealerPrincipalName: { [Op.iLike]: `%${searchTerm}%` } as any }, + { domainId: { [Op.iLike]: `%${searchTerm}%` } as any }, + { dlrcode: { [Op.iLike]: `%${searchTerm}%` } as any }, + { salesCode: { [Op.iLike]: `%${searchTerm}%` } as any }, + { gearCode: { [Op.iLike]: `%${searchTerm}%` } as any }, + ]; + } + + const dealers = await Dealer.findAll({ + where: whereClause, + order: [['dealership', 'ASC']], + limit: limit, // Always limit results to specified limit (default 10) }); - return dealers.map((dealer, index) => { - // Generate dummy dealerCode in response if employeeId is empty - // Format: RE-MH-XXX where XXX is a zero-padded index (001, 002, etc.) - // This is only for the API response - database employeeId is not modified - const dealerCode = dealer.employeeId && dealer.employeeId.trim() !== '' - ? dealer.employeeId - : `RE-MH-${String(index + 1).padStart(3, '0')}`; + // Get all domain_ids to check which dealers are logged in + const domainIds = dealers + .map((d) => d.domainId) + .filter((id): id is string => id !== null && id !== undefined); + + // Check which domain_ids exist in users table + const loggedInUsers = await User.findAll({ + where: { + email: { [Op.in]: domainIds } as any, + isActive: true, + }, + attributes: ['userId', 'email', 'displayName', 'phone', 'department', 'designation'], + }); + + // Create a map of email -> user for quick lookup + const userMap = new Map(loggedInUsers.map((u) => [u.email.toLowerCase(), u])); + + // Map dealers to DealerInfo with login status + return dealers.map((dealer) => { + const domainId = dealer.domainId?.toLowerCase() || ''; + const user = domainId ? userMap.get(domainId) : null; + const isLoggedIn = !!user; return { - userId: dealer.userId, - email: dealer.email, - dealerCode: dealerCode, - dealerName: dealer.displayName || dealer.email, - displayName: dealer.displayName || dealer.email, - phone: dealer.phone || undefined, - department: dealer.department || undefined, - designation: dealer.designation || undefined, + dealerId: dealer.dealerId, + userId: user?.userId || null, + email: dealer.domainId || '', + dealerCode: dealer.dlrcode || '', + dealerName: dealer.dealership || dealer.dealerPrincipalName || '', + displayName: dealer.dealerPrincipalName || dealer.dealership || '', + phone: dealer.dpContactNumber || undefined, + department: user?.department || undefined, + designation: user?.designation || undefined, + isLoggedIn, + salesCode: dealer.salesCode || null, + serviceCode: dealer.serviceCode || null, + gearCode: dealer.gearCode || null, + gmaCode: dealer.gmaCode || null, + region: dealer.region || null, + state: dealer.state || null, + district: dealer.district || null, + city: dealer.city || null, + dealerPrincipalName: dealer.dealerPrincipalName || null, + dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null, }; }); } catch (error) { @@ -59,14 +115,14 @@ export async function getAllDealers(): Promise { } /** - * Get dealer by code + * Get dealer by code (dlrcode) + * Checks if dealer is logged in by matching domain_id with users.email */ export async function getDealerByCode(dealerCode: string): Promise { try { - const dealer = await User.findOne({ + const dealer = await Dealer.findOne({ where: { - employeeId: dealerCode, - designation: { [Op.iLike]: 'dealer' } as any, + dlrcode: dealerCode, isActive: true, }, }); @@ -75,15 +131,41 @@ export async function getDealerByCode(dealerCode: string): Promise { try { - const dealer = await User.findOne({ + const dealer = await Dealer.findOne({ where: { - email: email.toLowerCase(), - designation: { [Op.iLike]: 'dealer' } as any, + domainId: { [Op.iLike]: email.toLowerCase() } as any, isActive: true, }, }); @@ -108,15 +190,41 @@ export async function getDealerByEmail(email: string): Promise { +export async function searchDealers(searchTerm: string, limit: number = 10): Promise { try { - const dealers = await User.findAll({ - where: { - [Op.and]: [ - { designation: { [Op.iLike]: 'dealer' } as any }, - { - [Op.or]: [ - { displayName: { [Op.iLike]: `%${searchTerm}%` } as any }, - { email: { [Op.iLike]: `%${searchTerm}%` } as any }, - { employeeId: { [Op.iLike]: `%${searchTerm}%` } as any }, - ], - }, - { isActive: true }, - ], - }, - order: [['displayName', 'ASC']], - limit: 50, // Limit results - }); - - return dealers.map((dealer) => ({ - userId: dealer.userId, - email: dealer.email, - dealerCode: dealer.employeeId || '', - dealerName: dealer.displayName || dealer.email, - displayName: dealer.displayName || dealer.email, - phone: dealer.phone || undefined, - department: dealer.department || undefined, - designation: dealer.designation || undefined, - })); + return await getAllDealers(searchTerm, limit); } catch (error) { logger.error('[DealerService] Error searching dealers:', error); throw error;