From f54501793c778669d5f113d63e981b1244117d9e Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Tue, 27 Jan 2026 19:08:24 +0530 Subject: [PATCH] seeded data for the state districts andseeded default zones create login api with username password later oka will be added --- config/auth.js | 34 -- config/constants.js | 209 ----------- config/database.js | 49 --- config/email.js | 12 - controllers/applicationController.js | 135 ------- controllers/authController.js | 269 ------------- controllers/constitutionalController.js | 183 --------- controllers/financeController.js | 99 ----- controllers/masterController.js | 121 ------ controllers/outletController.js | 184 --------- controllers/relocationController.js | 238 ------------ controllers/resignationController.js | 417 --------------------- controllers/worknoteController.js | 84 ----- docs/dealer_onboard_backend_schema.mermaid | 18 +- middleware/auth.js | 100 ----- middleware/errorHandler.js | 76 ---- middleware/roleCheck.js | 47 --- middleware/upload.js | 101 ----- models/Application.js | 104 ----- models/AuditLog.js | 65 ---- models/ConstitutionalChange.js | 87 ----- models/Document.js | 64 ---- models/FinancePayment.js | 75 ---- models/FnF.js | 72 ---- models/Outlet.js | 104 ----- models/Region.js | 44 --- models/RelocationRequest.js | 99 ----- models/Resignation.js | 110 ------ models/User.js | 89 ----- models/Worknote.js | 52 --- models/Zone.js | 49 --- models/index.js | 49 --- package.json | 3 +- routes/applications.js | 15 - routes/auth.js | 15 - routes/constitutional.js | 24 -- routes/finance.js | 16 - routes/master.js | 20 - routes/outlets.js | 21 -- routes/relocation.js | 24 -- routes/resignations.js | 62 --- routes/upload.js | 65 ---- routes/worknotes.js | 18 - scripts/migrate.js | 40 -- scripts/seed-geo.ts | 27 ++ scripts/seed-permissions.ts | 310 +++++++++++++++ scripts/seed-roles.ts | 38 ++ scripts/seed-users.ts | 52 +++ scripts/seed.js | 160 -------- seeders/20240127-seed-geo-data.js | 314 ++++++++++++++++ services/auditService.js | 111 ------ src/common/config/auth.ts | 6 +- src/common/config/constants.ts | 1 + src/common/config/email.ts | 25 ++ src/common/config/permissions.ts | 50 +++ src/common/middleware/auth.ts | 11 +- src/common/middleware/roleCheck.ts | 6 +- src/database/models/Area.ts | 26 ++ src/database/models/District.ts | 29 +- src/database/models/Permission.ts | 12 + src/database/models/Role.ts | 11 + src/database/models/RolePermission.ts | 25 -- src/database/models/State.ts | 3 +- src/database/models/User.ts | 5 + src/modules/admin/admin.controller.ts | 111 +++++- src/modules/admin/admin.routes.ts | 1 + src/modules/auth/auth.controller.ts | 38 +- src/modules/auth/auth.routes.ts | 1 + src/modules/master/master.controller.ts | 44 ++- src/types/express.types.ts | 5 +- tsconfig.json | 3 +- 71 files changed, 1068 insertions(+), 4119 deletions(-) delete mode 100644 config/auth.js delete mode 100644 config/constants.js delete mode 100644 config/database.js delete mode 100644 config/email.js delete mode 100644 controllers/applicationController.js delete mode 100644 controllers/authController.js delete mode 100644 controllers/constitutionalController.js delete mode 100644 controllers/financeController.js delete mode 100644 controllers/masterController.js delete mode 100644 controllers/outletController.js delete mode 100644 controllers/relocationController.js delete mode 100644 controllers/resignationController.js delete mode 100644 controllers/worknoteController.js delete mode 100644 middleware/auth.js delete mode 100644 middleware/errorHandler.js delete mode 100644 middleware/roleCheck.js delete mode 100644 middleware/upload.js delete mode 100644 models/Application.js delete mode 100644 models/AuditLog.js delete mode 100644 models/ConstitutionalChange.js delete mode 100644 models/Document.js delete mode 100644 models/FinancePayment.js delete mode 100644 models/FnF.js delete mode 100644 models/Outlet.js delete mode 100644 models/Region.js delete mode 100644 models/RelocationRequest.js delete mode 100644 models/Resignation.js delete mode 100644 models/User.js delete mode 100644 models/Worknote.js delete mode 100644 models/Zone.js delete mode 100644 models/index.js delete mode 100644 routes/applications.js delete mode 100644 routes/auth.js delete mode 100644 routes/constitutional.js delete mode 100644 routes/finance.js delete mode 100644 routes/master.js delete mode 100644 routes/outlets.js delete mode 100644 routes/relocation.js delete mode 100644 routes/resignations.js delete mode 100644 routes/upload.js delete mode 100644 routes/worknotes.js delete mode 100644 scripts/migrate.js create mode 100644 scripts/seed-geo.ts create mode 100644 scripts/seed-permissions.ts create mode 100644 scripts/seed-roles.ts create mode 100644 scripts/seed-users.ts delete mode 100644 scripts/seed.js create mode 100644 seeders/20240127-seed-geo-data.js delete mode 100644 services/auditService.js create mode 100644 src/common/config/email.ts create mode 100644 src/common/config/permissions.ts diff --git a/config/auth.js b/config/auth.js deleted file mode 100644 index c402910..0000000 --- a/config/auth.js +++ /dev/null @@ -1,34 +0,0 @@ -const jwt = require('jsonwebtoken'); - -const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; -const JWT_EXPIRE = process.env.JWT_EXPIRE || '7d'; - -// Generate JWT token -const generateToken = (user) => { - const payload = { - userId: user.id, - email: user.email, - role: user.role, - region: user.region, - zone: user.zone - }; - - return jwt.sign(payload, JWT_SECRET, { - expiresIn: JWT_EXPIRE - }); -}; - -// Verify JWT token -const verifyToken = (token) => { - try { - return jwt.verify(token, JWT_SECRET); - } catch (error) { - throw new Error('Invalid or expired token'); - } -}; - -module.exports = { - generateToken, - verifyToken, - JWT_SECRET -}; diff --git a/config/constants.js b/config/constants.js deleted file mode 100644 index a8227b1..0000000 --- a/config/constants.js +++ /dev/null @@ -1,209 +0,0 @@ -// User Roles -const ROLES = { - DD: 'DD', - DD_ZM: 'DD-ZM', - RBM: 'RBM', - ZBH: 'ZBH', - DD_LEAD: 'DD Lead', - DD_HEAD: 'DD Head', - NBH: 'NBH', - DD_ADMIN: 'DD Admin', - LEGAL_ADMIN: 'Legal Admin', - SUPER_ADMIN: 'Super Admin', - DD_AM: 'DD AM', - FINANCE: 'Finance', - DEALER: 'Dealer' -}; - -// Regions -const REGIONS = { - EAST: 'East', - WEST: 'West', - NORTH: 'North', - SOUTH: 'South', - CENTRAL: 'Central' -}; - -// Application Stages -const APPLICATION_STAGES = { - DD: 'DD', - DD_ZM: 'DD-ZM', - RBM: 'RBM', - ZBH: 'ZBH', - DD_LEAD: 'DD Lead', - DD_HEAD: 'DD Head', - NBH: 'NBH', - LEGAL: 'Legal', - FINANCE: 'Finance', - APPROVED: 'Approved', - REJECTED: 'Rejected' -}; - -// Application Status -const APPLICATION_STATUS = { - PENDING: 'Pending', - IN_REVIEW: 'In Review', - APPROVED: 'Approved', - REJECTED: 'Rejected' -}; - -// Resignation Stages -const RESIGNATION_STAGES = { - ASM: 'ASM', - RBM: 'RBM', - ZBH: 'ZBH', - NBH: 'NBH', - DD_ADMIN: 'DD Admin', - LEGAL: 'Legal', - FINANCE: 'Finance', - FNF_INITIATED: 'F&F Initiated', - COMPLETED: 'Completed', - REJECTED: 'Rejected' -}; - -// Resignation Types -const RESIGNATION_TYPES = { - VOLUNTARY: 'Voluntary', - RETIREMENT: 'Retirement', - HEALTH_ISSUES: 'Health Issues', - BUSINESS_CLOSURE: 'Business Closure', - OTHER: 'Other' -}; - -// Constitutional Change Types -const CONSTITUTIONAL_CHANGE_TYPES = { - OWNERSHIP_TRANSFER: 'Ownership Transfer', - PARTNERSHIP_CHANGE: 'Partnership Change', - LLP_CONVERSION: 'LLP Conversion', - COMPANY_FORMATION: 'Company Formation', - DIRECTOR_CHANGE: 'Director Change' -}; - -// Constitutional Change Stages -const CONSTITUTIONAL_STAGES = { - DD_ADMIN_REVIEW: 'DD Admin Review', - LEGAL_REVIEW: 'Legal Review', - NBH_APPROVAL: 'NBH Approval', - FINANCE_CLEARANCE: 'Finance Clearance', - COMPLETED: 'Completed', - REJECTED: 'Rejected' -}; - -// Relocation Types -const RELOCATION_TYPES = { - WITHIN_CITY: 'Within City', - INTERCITY: 'Intercity', - INTERSTATE: 'Interstate' -}; - -// Relocation Stages -const RELOCATION_STAGES = { - DD_ADMIN_REVIEW: 'DD Admin Review', - RBM_REVIEW: 'RBM Review', - NBH_APPROVAL: 'NBH Approval', - LEGAL_CLEARANCE: 'Legal Clearance', - COMPLETED: 'Completed', - REJECTED: 'Rejected' -}; - -// Outlet Types -const OUTLET_TYPES = { - DEALERSHIP: 'Dealership', - STUDIO: 'Studio' -}; - -// Outlet Status -const OUTLET_STATUS = { - ACTIVE: 'Active', - PENDING_RESIGNATION: 'Pending Resignation', - CLOSED: 'Closed' -}; - -// Business Types -const BUSINESS_TYPES = { - DEALERSHIP: 'Dealership', - STUDIO: 'Studio' -}; - -// Payment Types -const PAYMENT_TYPES = { - SECURITY_DEPOSIT: 'Security Deposit', - LICENSE_FEE: 'License Fee', - SETUP_FEE: 'Setup Fee', - OTHER: 'Other' -}; - -// Payment Status -const PAYMENT_STATUS = { - PENDING: 'Pending', - PAID: 'Paid', - OVERDUE: 'Overdue', - WAIVED: 'Waived' -}; - -// F&F Status -const FNF_STATUS = { - INITIATED: 'Initiated', - DD_CLEARANCE: 'DD Clearance', - LEGAL_CLEARANCE: 'Legal Clearance', - FINANCE_APPROVAL: 'Finance Approval', - COMPLETED: 'Completed' -}; - -// Audit Actions -const AUDIT_ACTIONS = { - CREATED: 'CREATED', - UPDATED: 'UPDATED', - APPROVED: 'APPROVED', - REJECTED: 'REJECTED', - DELETED: 'DELETED', - STAGE_CHANGED: 'STAGE_CHANGED', - DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED', - WORKNOTE_ADDED: 'WORKNOTE_ADDED' -}; - -// Document Types -const DOCUMENT_TYPES = { - GST_CERTIFICATE: 'GST Certificate', - PAN_CARD: 'PAN Card', - AADHAAR: 'Aadhaar', - PARTNERSHIP_DEED: 'Partnership Deed', - LLP_AGREEMENT: 'LLP Agreement', - INCORPORATION_CERTIFICATE: 'Certificate of Incorporation', - MOA: 'MOA', - AOA: 'AOA', - BOARD_RESOLUTION: 'Board Resolution', - PROPERTY_DOCUMENTS: 'Property Documents', - BANK_STATEMENT: 'Bank Statement', - OTHER: 'Other' -}; - -// Request Types -const REQUEST_TYPES = { - APPLICATION: 'application', - RESIGNATION: 'resignation', - CONSTITUTIONAL: 'constitutional', - RELOCATION: 'relocation' -}; - -module.exports = { - ROLES, - REGIONS, - APPLICATION_STAGES, - APPLICATION_STATUS, - RESIGNATION_STAGES, - RESIGNATION_TYPES, - CONSTITUTIONAL_CHANGE_TYPES, - CONSTITUTIONAL_STAGES, - RELOCATION_TYPES, - RELOCATION_STAGES, - OUTLET_TYPES, - OUTLET_STATUS, - BUSINESS_TYPES, - PAYMENT_TYPES, - PAYMENT_STATUS, - FNF_STATUS, - AUDIT_ACTIONS, - DOCUMENT_TYPES, - REQUEST_TYPES -}; diff --git a/config/database.js b/config/database.js deleted file mode 100644 index 8b9fd3b..0000000 --- a/config/database.js +++ /dev/null @@ -1,49 +0,0 @@ -require('dotenv').config(); - -module.exports = { - development: { - username: process.env.DB_USER || 'laxman', - password: process.env.DB_PASSWORD || 'Admin@123', - database: process.env.DB_NAME || 'royal_enfield_onboarding', - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, - dialect: 'postgres', - logging: console.log, - pool: { - max: 5, - min: 0, - acquire: 30000, - idle: 10000 - } - }, - production: { - username: process.env.DB_USER || 'laxman', - password: process.env.DB_PASSWORD || 'Admin@123', - database: process.env.DB_NAME || 'royal_enfield_onboarding', - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, - dialect: 'postgres', - logging: false, - pool: { - max: 20, - min: 5, - acquire: 60000, - idle: 10000 - }, - dialectOptions: { - ssl: process.env.DB_SSL === 'true' ? { - require: true, - rejectUnauthorized: false - } : false - } - }, - test: { - username: process.env.DB_USER || 'laxman', - password: process.env.DB_PASSWORD || 'Admin@123', - database: process.env.DB_NAME + '_test' || 'royal_enfield_onboarding_test', - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, - dialect: 'postgres', - logging: false - } -}; diff --git a/config/email.js b/config/email.js deleted file mode 100644 index 4f241ce..0000000 --- a/config/email.js +++ /dev/null @@ -1,12 +0,0 @@ -require('dotenv').config(); - -module.exports = { - host: process.env.EMAIL_HOST || 'smtp.gmail.com', - port: parseInt(process.env.EMAIL_PORT) || 587, - secure: process.env.EMAIL_SECURE === 'true', - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASSWORD - }, - from: process.env.EMAIL_FROM || 'Royal Enfield ' -}; diff --git a/controllers/applicationController.js b/controllers/applicationController.js deleted file mode 100644 index 56e8567..0000000 --- a/controllers/applicationController.js +++ /dev/null @@ -1,135 +0,0 @@ -const { Application } = require('../models'); -const { v4: uuidv4 } = require('uuid'); - -exports.submitApplication = async (req, res) => { - try { - const { - applicantName, email, phone, businessType, locationType, - preferredLocation, city, state, experienceYears, investmentCapacity - } = req.body; - - const applicationId = `APP-${new Date().getFullYear()}-${uuidv4().substring(0, 6).toUpperCase()}`; - - const application = await Application.create({ - applicationId, - applicantName, - email, - phone, - businessType, - preferredLocation, - city, - state, - experienceYears, - investmentCapacity, - currentStage: 'DD', - overallStatus: 'Pending', - progressPercentage: 0 - }); - - res.status(201).json({ - success: true, - message: 'Application submitted successfully', - applicationId: application.applicationId - }); - } catch (error) { - console.error('Submit application error:', error); - res.status(500).json({ success: false, message: 'Error submitting application' }); - } -}; - -exports.getApplications = async (req, res) => { - try { - const applications = await Application.findAll({ - order: [['createdAt', 'DESC']] - }); - - res.json({ success: true, applications }); - } catch (error) { - console.error('Get applications error:', error); - res.status(500).json({ success: false, message: 'Error fetching applications' }); - } -}; - -exports.getApplicationById = async (req, res) => { - try { - const { id } = req.params; - - const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } - }); - - if (!application) { - return res.status(404).json({ success: false, message: 'Application not found' }); - } - - res.json({ success: true, application }); - } catch (error) { - console.error('Get application error:', error); - res.status(500).json({ success: false, message: 'Error fetching application' }); - } -}; - -exports.takeAction = async (req, res) => { - try { - const { id } = req.params; - const { action, comments, rating } = req.body; - - const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } - }); - - if (!application) { - return res.status(404).json({ success: false, message: 'Application not found' }); - } - - await application.update({ - overallStatus: action, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Action taken successfully' }); - } catch (error) { - console.error('Take action error:', error); - res.status(500).json({ success: false, message: 'Error processing action' }); - } -}; - -exports.uploadDocuments = async (req, res) => { - try { - const { id } = req.params; - const { documents } = req.body; - - const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } - }); - - if (!application) { - return res.status(404).json({ success: false, message: 'Application not found' }); - } - - await application.update({ - documents: documents, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Documents uploaded successfully' }); - } catch (error) { - console.error('Upload documents error:', error); - res.status(500).json({ success: false, message: 'Error uploading documents' }); - } -}; diff --git a/controllers/authController.js b/controllers/authController.js deleted file mode 100644 index e8f3a19..0000000 --- a/controllers/authController.js +++ /dev/null @@ -1,269 +0,0 @@ -const bcrypt = require('bcryptjs'); -const { User, AuditLog } = require('../models'); -const { generateToken } = require('../config/auth'); -const { AUDIT_ACTIONS } = require('../config/constants'); - -// Register new user -exports.register = async (req, res) => { - try { - const { email, password, fullName, role, phone, region, zone } = req.body; - - // Validate input - if (!email || !password || !fullName || !role) { - return res.status(400).json({ - success: false, - message: 'Email, password, full name, and role are required' - }); - } - - // Check if user already exists - const existingUser = await User.findOne({ where: { email } }); - if (existingUser) { - return res.status(400).json({ - success: false, - message: 'User with this email already exists' - }); - } - - // Hash password - const hashedPassword = await bcrypt.hash(password, 10); - - // Insert user - const user = await User.create({ - email, - password: hashedPassword, - name: fullName, - role, - phone, - region, - zone - }); - - // Log audit - await AuditLog.create({ - userId: user.id, - action: AUDIT_ACTIONS.CREATED, - entityType: 'user', - entityId: user.id - }); - - res.status(201).json({ - success: true, - message: 'User registered successfully', - userId: user.id - }); - } catch (error) { - console.error('Register error:', error); - res.status(500).json({ - success: false, - message: 'Error registering user' - }); - } -}; - -// Login -exports.login = async (req, res) => { - try { - const { email, password } = req.body; - - // Validate input - if (!email || !password) { - return res.status(400).json({ - success: false, - message: 'Email and password are required' - }); - } - - // Get user - const user = await User.findOne({ where: { email } }); - - if (!user) { - return res.status(401).json({ - success: false, - message: 'Invalid email or password' - }); - } - - // Check if account is active - if (user.status !== 'active') { - return res.status(403).json({ - success: false, - message: 'Account is deactivated' - }); - } - - // Verify password - const isValidPassword = await bcrypt.compare(password, user.password); - if (!isValidPassword) { - return res.status(401).json({ - success: false, - message: 'Invalid email or password' - }); - } - - // Update last login - await user.update({ lastLogin: new Date() }); - - // Generate token - const token = generateToken(user); - - // Log audit - await AuditLog.create({ - userId: user.id, - action: 'user_login', - entityType: 'user', - entityId: user.id - }); - - res.json({ - success: true, - token, - user: { - id: user.id, - email: user.email, - fullName: user.name, - role: user.role, - region: user.region, - zone: user.zone - } - }); - } catch (error) { - console.error('Login error:', error); - res.status(500).json({ - success: false, - message: 'Error during login' - }); - } -}; - -// Get profile -exports.getProfile = async (req, res) => { - try { - const user = await User.findByPk(req.user.id, { - attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt'] - }); - - if (!user) { - return res.status(404).json({ - success: false, - message: 'User not found' - }); - } - - res.json({ - success: true, - user: { - id: user.id, - email: user.email, - fullName: user.name, - role: user.role, - region: user.region, - zone: user.zone, - phone: user.phone, - createdAt: user.createdAt - } - }); - } catch (error) { - console.error('Get profile error:', error); - res.status(500).json({ - success: false, - message: 'Error fetching profile' - }); - } -}; - -// Update profile -exports.updateProfile = async (req, res) => { - try { - const { fullName, phone } = req.body; - - const user = await User.findByPk(req.user.id); - if (!user) { - return res.status(404).json({ - success: false, - message: 'User not found' - }); - } - - await user.update({ - name: fullName || user.name, - phone: phone || user.phone - }); - - // Log audit - await AuditLog.create({ - userId: req.user.id, - action: AUDIT_ACTIONS.UPDATED, - entityType: 'user', - entityId: req.user.id - }); - - res.json({ - success: true, - message: 'Profile updated successfully' - }); - } catch (error) { - console.error('Update profile error:', error); - res.status(500).json({ - success: false, - message: 'Error updating profile' - }); - } -}; - -// Change password -exports.changePassword = async (req, res) => { - try { - const { currentPassword, newPassword } = req.body; - - if (!currentPassword || !newPassword) { - return res.status(400).json({ - success: false, - message: 'Current password and new password are required' - }); - } - - // Get current user - const user = await User.findByPk(req.user.id); - if (!user) { - return res.status(404).json({ - success: false, - message: 'User not found' - }); - } - - // Verify current password - const isValid = await bcrypt.compare(currentPassword, user.password); - if (!isValid) { - return res.status(401).json({ - success: false, - message: 'Current password is incorrect' - }); - } - - // Hash new password - const hashedPassword = await bcrypt.hash(newPassword, 10); - - // Update password - await user.update({ password: hashedPassword }); - - // Log audit - await AuditLog.create({ - userId: req.user.id, - action: 'password_changed', - entityType: 'user', - entityId: req.user.id - }); - - res.json({ - success: true, - message: 'Password changed successfully' - }); - } catch (error) { - console.error('Change password error:', error); - res.status(500).json({ - success: false, - message: 'Error changing password' - }); - } -}; diff --git a/controllers/constitutionalController.js b/controllers/constitutionalController.js deleted file mode 100644 index 2c15e7b..0000000 --- a/controllers/constitutionalController.js +++ /dev/null @@ -1,183 +0,0 @@ -const { ConstitutionalChange, Outlet, User, Worknote } = require('../models'); -const { v4: uuidv4 } = require('uuid'); -const { Op } = require('sequelize'); // Required for Op.or - -exports.submitRequest = async (req, res) => { - try { - const { - outletId, changeType, currentConstitution, proposedConstitution, - reason, effectiveDate, newEntityDetails - } = req.body; - - const requestId = `CC-${Date.now()}-${uuidv4().substring(0, 4).toUpperCase()}`; - - const request = await ConstitutionalChange.create({ - requestId, - outletId, - dealerId: req.user.id, - changeType, - description: reason, - currentConstitution, - proposedConstitution, - effectiveDate, - newEntityDetails: JSON.stringify(newEntityDetails), // Store as JSON string - currentStage: 'RBM Review', - status: 'Pending', - progressPercentage: 0, - timeline: [{ - stage: 'Submitted', - timestamp: new Date(), - user: req.user.full_name, // Assuming req.user.full_name is available - action: 'Request submitted' - }] - }); - - res.status(201).json({ - success: true, - message: 'Constitutional change request submitted successfully', - requestId: request.requestId - }); - } catch (error) { - console.error('Submit constitutional change error:', error); - res.status(500).json({ success: false, message: 'Error submitting request' }); - } -}; - -exports.getRequests = async (req, res) => { - try { - const where = {}; - if (req.user.role === 'Dealer') { - where.dealerId = req.user.id; - } - - const requests = await ConstitutionalChange.findAll({ - where, - include: [ - { - model: Outlet, - as: 'outlet', - attributes: ['code', 'name'] - }, - { - model: User, - as: 'dealer', - attributes: ['full_name'] // Changed from 'name' to 'full_name' based on original code - } - ], - order: [['createdAt', 'DESC']] - }); - - res.json({ success: true, requests }); - } catch (error) { - console.error('Get constitutional changes error:', error); - res.status(500).json({ success: false, message: 'Error fetching requests' }); - } -}; - -exports.getRequestById = async (req, res) => { - try { - const { id } = req.params; - - const request = await ConstitutionalChange.findOne({ - where: { - [Op.or]: [ - { id }, - { requestId: id } - ] - }, - include: [ - { - model: Outlet, - as: 'outlet' - }, - { - model: User, - as: 'dealer', - attributes: ['full_name', 'email'] // Changed from 'name' to 'full_name' - }, - { - model: Worknote, - as: 'worknotes' // Assuming Worknote model is associated as 'worknotes' - } - ] - }); - - if (!request) { - return res.status(404).json({ success: false, message: 'Request not found' }); - } - - res.json({ success: true, request }); - } catch (error) { - console.error('Get constitutional change details error:', error); - res.status(500).json({ success: false, message: 'Error fetching details' }); - } -}; - -exports.takeAction = async (req, res) => { - try { - const { id } = req.params; - const { action, comments } = req.body; - - const request = await ConstitutionalChange.findOne({ - where: { - [Op.or]: [ - { id }, - { requestId: id } - ] - } - }); - - if (!request) { - return res.status(404).json({ success: false, message: 'Request not found' }); - } - - const timeline = [...request.timeline, { - stage: 'Review', - timestamp: new Date(), - user: req.user.full_name, // Assuming req.user.full_name is available - action, - remarks: comments - }]; - - await request.update({ - status: action, // Assuming action directly maps to status (e.g., 'Approved', 'Rejected') - timeline, - updatedAt: new Date() - }); - - res.json({ success: true, message: `Request ${action.toLowerCase()} successfully` }); - } catch (error) { - console.error('Take action error:', error); - res.status(500).json({ success: false, message: 'Error processing action' }); - } -}; - -exports.uploadDocuments = async (req, res) => { - try { - const { id } = req.params; - const { documents } = req.body; - - const request = await ConstitutionalChange.findOne({ - where: { - [Op.or]: [ - { id }, - { requestId: id } - ] - } - }); - - if (!request) { - return res.status(404).json({ success: false, message: 'Request not found' }); - } - - await request.update({ - documents: documents, // Assuming documents is an array or object that can be stored directly - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Documents uploaded successfully' }); - } catch (error) { - console.error('Upload documents error:', error); - res.status(500).json({ success: false, message: 'Error uploading documents' }); - } -}; diff --git a/controllers/financeController.js b/controllers/financeController.js deleted file mode 100644 index ed260e8..0000000 --- a/controllers/financeController.js +++ /dev/null @@ -1,99 +0,0 @@ -const { FinancePayment, FnF, Application, Resignation, User, Outlet } = require('../models'); - -exports.getOnboardingPayments = async (req, res) => { - try { - const payments = await FinancePayment.findAll({ - include: [{ - model: Application, - as: 'application', - attributes: ['applicantName', 'applicationId'] - }], - order: [['createdAt', 'ASC']] - }); - - res.json({ success: true, payments }); - } catch (error) { - console.error('Get onboarding payments error:', error); - res.status(500).json({ success: false, message: 'Error fetching payments' }); - } -}; - -exports.getFnFSettlements = async (req, res) => { - try { - const settlements = await FnF.findAll({ - include: [ - { - model: Resignation, - as: 'resignation', - attributes: ['resignationId'] - }, - { - model: Outlet, // Need to ensure Outlet is imported or associated - as: 'outlet', - include: [{ - model: User, - as: 'dealer', - attributes: ['name'] - }] - } - ], - order: [['createdAt', 'DESC']] - }); - - res.json({ success: true, settlements }); - } catch (error) { - console.error('Get F&F settlements error:', error); - res.status(500).json({ success: false, message: 'Error fetching settlements' }); - } -}; - -exports.updatePayment = async (req, res) => { - try { - const { id } = req.params; - const { paidDate, amount, paymentMode, transactionReference, status } = req.body; - - const payment = await FinancePayment.findByPk(id); - if (!payment) { - return res.status(404).json({ success: false, message: 'Payment not found' }); - } - - await payment.update({ - paymentDate: paidDate || payment.paymentDate, - amount: amount || payment.amount, - transactionId: transactionReference || payment.transactionId, - paymentStatus: status || payment.paymentStatus, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Payment updated successfully' }); - } catch (error) { - console.error('Update payment error:', error); - res.status(500).json({ success: false, message: 'Error updating payment' }); - } -}; - -exports.updateFnF = async (req, res) => { - try { - const { id } = req.params; - const { - inventoryClearance, sparesClearance, accountsClearance, legalClearance, - finalSettlementAmount, status - } = req.body; - - const fnf = await FnF.findByPk(id); - if (!fnf) { - return res.status(404).json({ success: false, message: 'F&F settlement not found' }); - } - - await fnf.update({ - status: status || fnf.status, - netAmount: finalSettlementAmount || fnf.netAmount, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'F&F settlement updated successfully' }); - } catch (error) { - console.error('Update F&F error:', error); - res.status(500).json({ success: false, message: 'Error updating F&F settlement' }); - } -}; diff --git a/controllers/masterController.js b/controllers/masterController.js deleted file mode 100644 index 5e85814..0000000 --- a/controllers/masterController.js +++ /dev/null @@ -1,121 +0,0 @@ -const { Region, Zone } = require('../models'); - -exports.getRegions = async (req, res) => { - try { - const regions = await Region.findAll({ - order: [['name', 'ASC']] - }); - - res.json({ success: true, regions }); - } catch (error) { - console.error('Get regions error:', error); - res.status(500).json({ success: false, message: 'Error fetching regions' }); - } -}; - -exports.createRegion = async (req, res) => { - try { - const { regionName } = req.body; - - if (!regionName) { - return res.status(400).json({ success: false, message: 'Region name is required' }); - } - - await Region.create({ name: regionName }); - - res.status(201).json({ success: true, message: 'Region created successfully' }); - } catch (error) { - console.error('Create region error:', error); - res.status(500).json({ success: false, message: 'Error creating region' }); - } -}; - -exports.updateRegion = async (req, res) => { - try { - const { id } = req.params; - const { regionName, isActive } = req.body; - - const region = await Region.findByPk(id); - if (!region) { - return res.status(404).json({ success: false, message: 'Region not found' }); - } - - await region.update({ - name: regionName || region.name, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Region updated successfully' }); - } catch (error) { - console.error('Update region error:', error); - res.status(500).json({ success: false, message: 'Error updating region' }); - } -}; - -exports.getZones = async (req, res) => { - try { - const { regionId } = req.query; - - const where = {}; - if (regionId) { - where.regionId = regionId; - } - - const zones = await Zone.findAll({ - where, - include: [{ - model: Region, - as: 'region', - attributes: ['name'] - }], - order: [['name', 'ASC']] - }); - - res.json({ success: true, zones }); - } catch (error) { - console.error('Get zones error:', error); - res.status(500).json({ success: false, message: 'Error fetching zones' }); - } -}; - -exports.createZone = async (req, res) => { - try { - const { regionId, zoneName, zoneCode } = req.body; - - if (!regionId || !zoneName) { - return res.status(400).json({ success: false, message: 'Region ID and zone name are required' }); - } - - await Zone.create({ - regionId, - name: zoneName - }); - - res.status(201).json({ success: true, message: 'Zone created successfully' }); - } catch (error) { - console.error('Create zone error:', error); - res.status(500).json({ success: false, message: 'Error creating zone' }); - } -}; - -exports.updateZone = async (req, res) => { - try { - const { id } = req.params; - const { zoneName, zoneCode, isActive } = req.body; - - const zone = await Zone.findByPk(id); - if (!zone) { - return res.status(404).json({ success: false, message: 'Zone not found' }); - } - - await zone.update({ - name: zoneName || zone.name, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Zone updated successfully' }); - } catch (error) { - console.error('Update zone error:', error); - res.status(500).json({ success: false, message: 'Error updating zone' }); - } -}; diff --git a/controllers/outletController.js b/controllers/outletController.js deleted file mode 100644 index 88f80cd..0000000 --- a/controllers/outletController.js +++ /dev/null @@ -1,184 +0,0 @@ -const { Outlet, User, Resignation } = require('../models'); - -// Get all outlets for logged-in dealer -exports.getOutlets = async (req, res) => { - try { - const where = {}; - if (req.user.role === 'Dealer') { - where.dealerId = req.user.id; - } - - const outlets = await Outlet.findAll({ - where, - include: [ - { - model: User, - as: 'dealer', - attributes: ['name', 'email'] - }, - { - model: Resignation, - as: 'resignations', - required: false, - where: { - status: { [Op.notIn]: ['Completed', 'Rejected'] } - } - } - ], - order: [['createdAt', 'DESC']] - }); - - res.json({ - success: true, - outlets - }); - } catch (error) { - console.error('Get outlets error:', error); - res.status(500).json({ - success: false, - message: 'Error fetching outlets' - }); - } -}; - -// Get specific outlet details -exports.getOutletById = async (req, res) => { - try { - const { id } = req.params; - - const outlet = await Outlet.findByPk(id, { - include: [{ - model: User, - as: 'dealer', - attributes: ['name', 'email', 'phone'] - }] - }); - - if (!outlet) { - return res.status(404).json({ - success: false, - message: 'Outlet not found' - }); - } - - // Check if dealer can access this outlet - if (req.user.role === 'Dealer' && outlet.dealerId !== req.user.id) { - return res.status(403).json({ - success: false, - message: 'Access denied' - }); - } - - res.json({ - success: true, - outlet - }); - } catch (error) { - console.error('Get outlet error:', error); - res.status(500).json({ - success: false, - message: 'Error fetching outlet' - }); - } -}; - -// Create new outlet (admin only) -exports.createOutlet = async (req, res) => { - try { - const { - dealerId, - code, - name, - type, - address, - city, - state, - pincode, - establishedDate, - latitude, - longitude - } = req.body; - - // Validate required fields - if (!dealerId || !code || !name || !type || !address || !city || !state) { - return res.status(400).json({ - success: false, - message: 'Missing required fields' - }); - } - - // Check if code already exists - const existing = await Outlet.findOne({ where: { code } }); - if (existing) { - return res.status(400).json({ - success: false, - message: 'Outlet code already exists' - }); - } - - const outlet = await Outlet.create({ - dealerId, - code, - name, - type, - address, - city, - state, - pincode, - establishedDate, - latitude, - longitude - }); - - res.status(201).json({ - success: true, - message: 'Outlet created successfully', - outletId: outlet.id - }); - } catch (error) { - console.error('Create outlet error:', error); - res.status(500).json({ - success: false, - message: 'Error creating outlet' - }); - } -}; - -// Update outlet -exports.updateOutlet = async (req, res) => { - try { - const { id } = req.params; - const { name, address, city, state, pincode, status, latitude, longitude } = req.body; - - const outlet = await Outlet.findByPk(id); - if (!outlet) { - return res.status(404).json({ - success: false, - message: 'Outlet not found' - }); - } - - await outlet.update({ - name: name || outlet.name, - address: address || outlet.address, - city: city || outlet.city, - state: state || outlet.state, - pincode: pincode || outlet.pincode, - status: status || outlet.status, - latitude: latitude || outlet.latitude, - longitude: longitude || outlet.longitude, - updatedAt: new Date() - }); - - res.json({ - success: true, - message: 'Outlet updated successfully' - }); - } catch (error) { - console.error('Update outlet error:', error); - res.status(500).json({ - success: false, - message: 'Error updating outlet' - }); - } -}; diff --git a/controllers/relocationController.js b/controllers/relocationController.js deleted file mode 100644 index ee23194..0000000 --- a/controllers/relocationController.js +++ /dev/null @@ -1,238 +0,0 @@ -const { RelocationRequest, Outlet, User, Worknote } = require('../models'); -const { Op } = require('sequelize'); -const { v4: uuidv4 } = require('uuid'); - -exports.submitRequest = async (req, res) => { - try { - const { - outletId, relocationType, currentAddress, currentCity, currentState, - currentLatitude, currentLongitude, proposedAddress, proposedCity, - proposedState, proposedLatitude, proposedLongitude, reason, proposedDate - } = req.body; - - const requestId = `REL-${Date.now()}-${uuidv4().substring(0, 4).toUpperCase()}`; - - const request = await RelocationRequest.create({ - requestId, - outletId, - dealerId: req.user.id, - relocationType, - currentAddress, - currentCity, - currentState, - currentLatitude, - currentLongitude, - proposedAddress, - proposedCity, - proposedState, - proposedLatitude, - proposedLongitude, - reason, - proposedDate, - currentStage: 'RBM Review', - status: 'Pending', - progressPercentage: 0, - timeline: [{ - stage: 'Submitted', - timestamp: new Date(), - user: req.user.full_name, // Assuming req.user.full_name is available - action: 'Request submitted' - }] - }); - - res.status(201).json({ - success: true, - message: 'Relocation request submitted successfully', - requestId: request.requestId - }); - } catch (error) { - console.error('Submit relocation error:', error); - res.status(500).json({ success: false, message: 'Error submitting request' }); - } -}; - -exports.getRequests = async (req, res) => { - try { - const where = {}; - if (req.user.role === 'Dealer') { - where.dealerId = req.user.id; - } - - const requests = await RelocationRequest.findAll({ - where, - include: [ - { - model: Outlet, - as: 'outlet', - attributes: ['code', 'name'] - }, - { - model: User, - as: 'dealer', - attributes: ['full_name'] // Changed 'name' to 'full_name' based on original query - } - ], - order: [['createdAt', 'DESC']] - }); - - res.json({ success: true, requests }); - } catch (error) { - console.error('Get relocation requests error:', error); - res.status(500).json({ success: false, message: 'Error fetching requests' }); - } -}; - -exports.getRequestById = async (req, res) => { - try { - const { id } = req.params; - - const request = await RelocationRequest.findOne({ - where: { - [Op.or]: [ - { id }, - { requestId: id } - ] - }, - include: [ - { - model: Outlet, - as: 'outlet' - }, - { - model: User, - as: 'dealer', - attributes: ['full_name', 'email'] // Changed 'name' to 'full_name' - }, - { - model: Worknote, - as: 'worknotes', // Assuming Worknote model is for workflow/comments - include: [{ - model: User, - as: 'actionedBy', // Assuming Worknote has an association to User for actioned_by - attributes: ['full_name'] - }], - order: [['createdAt', 'ASC']] - } - ] - }); - - if (!request) { - return res.status(404).json({ success: false, message: 'Request not found' }); - } - - // Calculate distance between current and proposed location (retained from original) - if (request.currentLatitude && request.proposedLatitude) { - const distance = calculateDistance( - request.currentLatitude, request.currentLongitude, - request.proposedLatitude, request.proposedLongitude - ); - request.dataValues.distance = `${distance.toFixed(2)} km`; // Add to dataValues for response - } - - res.json({ success: true, request }); - } catch (error) { - console.error('Get relocation details error:', error); - res.status(500).json({ success: false, message: 'Error fetching details' }); - } -}; - -exports.takeAction = async (req, res) => { - try { - const { id } = req.params; - const { action, comments } = req.body; - - const request = await RelocationRequest.findOne({ - where: { - [Op.or]: [ - { id }, - { requestId: id } - ] - } - }); - - if (!request) { - return res.status(404).json({ success: false, message: 'Request not found' }); - } - - // Update status and current_stage based on action - let newStatus = request.status; - let newCurrentStage = request.currentStage; - - if (action === 'Approved') { - newStatus = 'Approved'; - // Assuming next stage logic would be here, e.g., 'Final Approval' - } else if (action === 'Rejected') { - newStatus = 'Rejected'; - } else if (action === 'Forwarded to RBM') { - newCurrentStage = 'RBM Review'; - } else if (action === 'Forwarded to ZBM') { - newCurrentStage = 'ZBM Review'; - } else if (action === 'Forwarded to HO') { - newCurrentStage = 'HO Review'; - } - - // Create a worknote entry - await Worknote.create({ - requestId: request.id, - stage: newCurrentStage, // Or the specific stage where action was taken - action: action, - comments: comments, - actionedBy: req.user.id, - actionedAt: new Date() - }); - - // Update the request status and current stage - await request.update({ - status: newStatus, - currentStage: newCurrentStage, - updatedAt: new Date() - }); - - res.json({ success: true, message: `Request ${action.toLowerCase()} successfully` }); - } catch (error) { - console.error('Take action error:', error); - res.status(500).json({ success: false, message: 'Error processing action' }); - } -}; - -exports.uploadDocuments = async (req, res) => { - try { - const { id } = req.params; - const { documents } = req.body; - - const request = await RelocationRequest.findOne({ - where: { - [Op.or]: [ - { id }, - { requestId: id } - ] - } - }); - - if (!request) { - return res.status(404).json({ success: false, message: 'Request not found' }); - } - - await request.update({ - documents: documents, - updatedAt: new Date() - }); - - res.json({ success: true, message: 'Documents uploaded successfully' }); - } catch (error) { - console.error('Upload documents error:', error); - res.status(500).json({ success: false, message: 'Error uploading documents' }); - } -}; - -// Helper function to calculate distance between two coordinates -function calculateDistance(lat1, lon1, lat2, lon2) { - const R = 6371; // Radius of Earth in km - const dLat = (lat2 - lat1) * Math.PI / 180; - const dLon = (lon2 - lon1) * Math.PI / 180; - const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLon / 2) * Math.sin(dLon / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; -} diff --git a/controllers/resignationController.js b/controllers/resignationController.js deleted file mode 100644 index 307c88f..0000000 --- a/controllers/resignationController.js +++ /dev/null @@ -1,417 +0,0 @@ -const db = require('../models'); -const logger = require('../utils/logger'); -const { RESIGNATION_STAGES, AUDIT_ACTIONS, ROLES } = require('../config/constants'); -const { Op } = require('sequelize'); - -// Generate unique resignation ID -const generateResignationId = async () => { - const count = await db.Resignation.count(); - return `RES-${String(count + 1).padStart(3, '0')}`; -}; - -// Calculate progress percentage based on stage -const calculateProgress = (stage) => { - const stageProgress = { - [RESIGNATION_STAGES.ASM]: 15, - [RESIGNATION_STAGES.RBM]: 30, - [RESIGNATION_STAGES.ZBH]: 45, - [RESIGNATION_STAGES.NBH]: 60, - [RESIGNATION_STAGES.DD_ADMIN]: 70, - [RESIGNATION_STAGES.LEGAL]: 80, - [RESIGNATION_STAGES.FINANCE]: 90, - [RESIGNATION_STAGES.FNF_INITIATED]: 95, - [RESIGNATION_STAGES.COMPLETED]: 100, - [RESIGNATION_STAGES.REJECTED]: 0 - }; - return stageProgress[stage] || 0; -}; - -// Create resignation request (Dealer only) -exports.createResignation = async (req, res, next) => { - const transaction = await db.sequelize.transaction(); - - try { - const { outletId, resignationType, lastOperationalDateSales, lastOperationalDateServices, reason, additionalInfo } = req.body; - const dealerId = req.user.id; - - // Verify outlet belongs to dealer - const outlet = await db.Outlet.findOne({ - where: { id: outletId, dealerId } - }); - - if (!outlet) { - await transaction.rollback(); - return res.status(404).json({ - success: false, - message: 'Outlet not found or does not belong to you' - }); - } - - // Check if outlet already has active resignation - const existingResignation = await db.Resignation.findOne({ - where: { - outletId, - status: { [Op.notIn]: ['Completed', 'Rejected'] } - } - }); - - if (existingResignation) { - await transaction.rollback(); - return res.status(400).json({ - success: false, - message: 'This outlet already has an active resignation request' - }); - } - - // Generate resignation ID - const resignationId = await generateResignationId(); - - // Create resignation - const resignation = await db.Resignation.create({ - resignationId, - outletId, - dealerId, - resignationType, - lastOperationalDateSales, - lastOperationalDateServices, - reason, - additionalInfo, - currentStage: RESIGNATION_STAGES.ASM, - status: 'ASM Review', - progressPercentage: 15, - timeline: [{ - stage: 'Submitted', - timestamp: new Date(), - user: req.user.name, - action: 'Resignation request submitted' - }] - }, { transaction }); - - // Update outlet status - await outlet.update({ - status: 'Pending Resignation' - }, { transaction }); - - // Create audit log - await db.AuditLog.create({ - userId: req.user.id, - userName: req.user.name, - userRole: req.user.role, - action: AUDIT_ACTIONS.CREATED, - entityType: 'resignation', - entityId: resignation.id, - changes: { created: resignation.toJSON() } - }, { transaction }); - - await transaction.commit(); - - logger.info(`Resignation request created: ${resignationId} by dealer: ${req.user.email}`); - - // TODO: Send email notification to ASM/DD Admin - - res.status(201).json({ - success: true, - message: 'Resignation request submitted successfully', - resignationId: resignation.resignationId, - resignation: resignation.toJSON() - }); - - } catch (error) { - await transaction.rollback(); - logger.error('Error creating resignation:', error); - next(error); - } -}; - -// Get resignations list (role-based filtering) -exports.getResignations = async (req, res, next) => { - try { - const { status, region, zone, page = 1, limit = 10 } = req.query; - const offset = (page - 1) * limit; - - // Build where clause based on user role - let where = {}; - - if (req.user.role === ROLES.DEALER) { - // Dealers see only their resignations - where.dealerId = req.user.id; - } else if (req.user.region && ![ROLES.NBH, ROLES.DD_HEAD, ROLES.DD_LEAD, ROLES.SUPER_ADMIN].includes(req.user.role)) { - // Regional users see resignations in their region - where['$outlet.region$'] = req.user.region; - } - - if (status) { - where.status = status; - } - - // Get resignations - const { count, rows: resignations } = await db.Resignation.findAndCountAll({ - where, - include: [ - { - model: db.Outlet, - as: 'outlet', - attributes: ['id', 'code', 'name', 'type', 'city', 'state', 'region', 'zone'] - }, - { - model: db.User, - as: 'dealer', - attributes: ['id', 'name', 'email', 'phone'] - } - ], - order: [['submittedOn', 'DESC']], - limit: parseInt(limit), - offset: parseInt(offset) - }); - - res.json({ - success: true, - resignations, - pagination: { - total: count, - page: parseInt(page), - pages: Math.ceil(count / limit), - limit: parseInt(limit) - } - }); - - } catch (error) { - logger.error('Error fetching resignations:', error); - next(error); - } -}; - -// Get resignation details -exports.getResignationById = async (req, res, next) => { - try { - const { id } = req.params; - - const resignation = await db.Resignation.findOne({ - where: { id }, - include: [ - { - model: db.Outlet, - as: 'outlet', - include: [ - { - model: db.User, - as: 'dealer', - attributes: ['id', 'name', 'email', 'phone'] - } - ] - }, - { - model: db.Worknote, - as: 'worknotes', - order: [['timestamp', 'DESC']] - } - ] - }); - - if (!resignation) { - return res.status(404).json({ - success: false, - message: 'Resignation not found' - }); - } - - // Check access permissions - if (req.user.role === ROLES.DEALER && resignation.dealerId !== req.user.id) { - return res.status(403).json({ - success: false, - message: 'Access denied' - }); - } - - res.json({ - success: true, - resignation - }); - - } catch (error) { - logger.error('Error fetching resignation details:', error); - next(error); - } -}; - -// Approve resignation (move to next stage) -exports.approveResignation = async (req, res, next) => { - const transaction = await db.sequelize.transaction(); - - try { - const { id } = req.params; - const { remarks } = req.body; - - const resignation = await db.Resignation.findByPk(id, { - include: [{ model: db.Outlet, as: 'outlet' }] - }); - - if (!resignation) { - await transaction.rollback(); - return res.status(404).json({ - success: false, - message: 'Resignation not found' - }); - } - - // Determine next stage based on current stage - const stageFlow = { - [RESIGNATION_STAGES.ASM]: RESIGNATION_STAGES.RBM, - [RESIGNATION_STAGES.RBM]: RESIGNATION_STAGES.ZBH, - [RESIGNATION_STAGES.ZBH]: RESIGNATION_STAGES.NBH, - [RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.DD_ADMIN, - [RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.LEGAL, - [RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.FINANCE, - [RESIGNATION_STAGES.FINANCE]: RESIGNATION_STAGES.FNF_INITIATED, - [RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.COMPLETED - }; - - const nextStage = stageFlow[resignation.currentStage]; - - if (!nextStage) { - await transaction.rollback(); - return res.status(400).json({ - success: false, - message: 'Cannot approve from current stage' - }); - } - - // Update resignation - const timeline = [...resignation.timeline, { - stage: nextStage, - timestamp: new Date(), - user: req.user.name, - action: 'Approved', - remarks - }]; - - await resignation.update({ - currentStage: nextStage, - status: nextStage === RESIGNATION_STAGES.COMPLETED ? 'Completed' : `${nextStage} Review`, - progressPercentage: calculateProgress(nextStage), - timeline - }, { transaction }); - - // If completed, update outlet status - if (nextStage === RESIGNATION_STAGES.COMPLETED) { - await resignation.outlet.update({ - status: 'Closed' - }, { transaction }); - } - - // Create audit log - await db.AuditLog.create({ - userId: req.user.id, - userName: req.user.name, - userRole: req.user.role, - action: AUDIT_ACTIONS.APPROVED, - entityType: 'resignation', - entityId: resignation.id, - changes: { - from: resignation.currentStage, - to: nextStage, - remarks - } - }, { transaction }); - - await transaction.commit(); - - logger.info(`Resignation ${resignation.resignationId} approved to ${nextStage} by ${req.user.email}`); - - // TODO: Send email notification to next approver - - res.json({ - success: true, - message: 'Resignation approved successfully', - nextStage, - resignation - }); - - } catch (error) { - await transaction.rollback(); - logger.error('Error approving resignation:', error); - next(error); - } -}; - -// Reject resignation -exports.rejectResignation = async (req, res, next) => { - const transaction = await db.sequelize.transaction(); - - try { - const { id } = req.params; - const { reason } = req.body; - - if (!reason) { - await transaction.rollback(); - return res.status(400).json({ - success: false, - message: 'Rejection reason is required' - }); - } - - const resignation = await db.Resignation.findByPk(id, { - include: [{ model: db.Outlet, as: 'outlet' }] - }); - - if (!resignation) { - await transaction.rollback(); - return res.status(404).json({ - success: false, - message: 'Resignation not found' - }); - } - - // Update resignation - const timeline = [...resignation.timeline, { - stage: 'Rejected', - timestamp: new Date(), - user: req.user.name, - action: 'Rejected', - reason - }]; - - await resignation.update({ - currentStage: RESIGNATION_STAGES.REJECTED, - status: 'Rejected', - progressPercentage: 0, - rejectionReason: reason, - timeline - }, { transaction }); - - // Update outlet status back to Active - await resignation.outlet.update({ - status: 'Active' - }, { transaction }); - - // Create audit log - await db.AuditLog.create({ - userId: req.user.id, - userName: req.user.name, - userRole: req.user.role, - action: AUDIT_ACTIONS.REJECTED, - entityType: 'resignation', - entityId: resignation.id, - changes: { reason } - }, { transaction }); - - await transaction.commit(); - - logger.info(`Resignation ${resignation.resignationId} rejected by ${req.user.email}`); - - // TODO: Send email notification to dealer - - res.json({ - success: true, - message: 'Resignation rejected', - resignation - }); - - } catch (error) { - await transaction.rollback(); - logger.error('Error rejecting resignation:', error); - next(error); - } -}; - -module.exports = exports; diff --git a/controllers/worknoteController.js b/controllers/worknoteController.js deleted file mode 100644 index 9ce8eb3..0000000 --- a/controllers/worknoteController.js +++ /dev/null @@ -1,84 +0,0 @@ -const { Worknote, User } = require('../models'); - -exports.addWorknote = async (req, res) => { - try { - const { requestId, requestType, message, isInternal } = req.body; - - if (!requestId || !requestType || !message) { - return res.status(400).json({ - success: false, - message: 'Request ID, type, and message are required' - }); - } - - await Worknote.create({ - requestId, - requestType, - userId: req.user.id, - content: message, - isInternal: isInternal || false - }); - - res.status(201).json({ - success: true, - message: 'Worknote added successfully' - }); - } catch (error) { - console.error('Add worknote error:', error); - res.status(500).json({ success: false, message: 'Error adding worknote' }); - } -}; - -exports.getWorknotes = async (req, res) => { - try { - const { requestId } = req.params; - const { requestType } = req.query; - - const worknotes = await Worknote.findAll({ - where: { - requestId, - requestType - }, - include: [{ - model: User, - as: 'author', - attributes: ['name', 'role'] - }], - order: [['createdAt', 'DESC']] - }); - - res.json({ - success: true, - worknotes - }); - } catch (error) { - console.error('Get worknotes error:', error); - res.status(500).json({ success: false, message: 'Error fetching worknotes' }); - } -}; - -exports.deleteWorknote = async (req, res) => { - try { - const { id } = req.params; - - const worknote = await Worknote.findByPk(id); - if (!worknote) { - return res.status(404).json({ success: false, message: 'Worknote not found' }); - } - - // Only allow user who created it or admin to delete - if (worknote.userId !== req.user.id && req.user.role !== 'Super Admin') { - return res.status(403).json({ success: false, message: 'Access denied' }); - } - - await worknote.destroy(); - - res.json({ - success: true, - message: 'Worknote deleted successfully' - }); - } catch (error) { - console.error('Delete worknote error:', error); - res.status(500).json({ success: false, message: 'Error deleting worknote' }); - } -}; diff --git a/docs/dealer_onboard_backend_schema.mermaid b/docs/dealer_onboard_backend_schema.mermaid index 6b6f4eb..7efe3d4 100644 --- a/docs/dealer_onboard_backend_schema.mermaid +++ b/docs/dealer_onboard_backend_schema.mermaid @@ -17,6 +17,7 @@ erDiagram string department string designation uuid role_code FK + string role_code UK uuid zone_id FK uuid region_id FK uuid state_id FK @@ -48,7 +49,7 @@ erDiagram string permission_code UK string permission_name string module - string permission_type + string permission_category string action string description timestamp created_at @@ -58,11 +59,6 @@ erDiagram uuid role_permission_id PK uuid role_id FK uuid permission_id FK - boolean can_view - boolean can_create - boolean can_edit - boolean can_delete - boolean can_approve timestamp created_at } @@ -115,6 +111,8 @@ erDiagram DISTRICTS { uuid district_id PK uuid state_id FK + uuid zone_id FK + uuid region_id FK string district_name boolean is_active timestamp created_at @@ -122,7 +120,9 @@ erDiagram AREAS { uuid area_id PK + uuid zone_id FK uuid region_id FK + uuid state_id FK uuid district_id FK string area_code UK string area_name @@ -1067,15 +1067,19 @@ erDiagram %% RELATIONSHIPS %% ============================================ USERS ||--o{ USER_ROLES : "has" - ROLES ||--o{ USER_ROLES : "assigned_to" + ROLES ||--o{ USERS : "assigned_to" ROLES ||--o{ ROLE_PERMISSIONS : "has" PERMISSIONS ||--o{ ROLE_PERMISSIONS : "granted_in" ZONES ||--o{ STATES : "contains" STATES ||--o{ DISTRICTS : "contains" + ZONES ||--o{ DISTRICTS : "contains" + REGIONS ||--o{ DISTRICTS : "contains" ZONES ||--o{ REGIONS : "contains" STATES ||--o{ REGIONS : "contains" + ZONES ||--o{ AREAS : "contains" REGIONS ||--o{ AREAS : "contains" + STATES ||--o{ AREAS : "contains" DISTRICTS ||--o{ AREAS : "contains" ZONES ||--o{ ZONE_MANAGERS : "managed_by" diff --git a/middleware/auth.js b/middleware/auth.js deleted file mode 100644 index c68c23c..0000000 --- a/middleware/auth.js +++ /dev/null @@ -1,100 +0,0 @@ -const jwt = require('jsonwebtoken'); -const db = require('../models'); -const logger = require('../utils/logger'); - -const authenticate = async (req, res, next) => { - try { - // Get token from header - const authHeader = req.header('Authorization'); - - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return res.status(401).json({ - success: false, - message: 'Access denied. No token provided.' - }); - } - - const token = authHeader.replace('Bearer ', ''); - - // Verify token - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - // Find user - const user = await db.User.findByPk(decoded.id, { - attributes: { exclude: ['password'] } - }); - - if (!user) { - return res.status(401).json({ - success: false, - message: 'Invalid token. User not found.' - }); - } - - if (user.status !== 'active') { - return res.status(401).json({ - success: false, - message: 'User account is inactive.' - }); - } - - // Attach user to request - req.user = user; - req.token = token; - - next(); - } catch (error) { - logger.error('Authentication error:', error); - - if (error.name === 'TokenExpiredError') { - return res.status(401).json({ - success: false, - message: 'Token expired' - }); - } - - if (error.name === 'JsonWebTokenError') { - return res.status(401).json({ - success: false, - message: 'Invalid token' - }); - } - - res.status(500).json({ - success: false, - message: 'Authentication failed' - }); - } -}; - -const optionalAuth = async (req, res, next) => { - try { - const authHeader = req.header('Authorization'); - - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return next(); - } - - const token = authHeader.replace('Bearer ', ''); - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - const user = await db.User.findByPk(decoded.id, { - attributes: { exclude: ['password'] } - }); - - if (user && user.status === 'active') { - req.user = user; - req.token = token; - } - - next(); - } catch (error) { - // If token is invalid/expired, just proceed without user - next(); - } -}; - -module.exports = { - authenticate, - optionalAuth -}; diff --git a/middleware/errorHandler.js b/middleware/errorHandler.js deleted file mode 100644 index 6f54581..0000000 --- a/middleware/errorHandler.js +++ /dev/null @@ -1,76 +0,0 @@ -const logger = require('../utils/logger'); - -const errorHandler = (err, req, res, next) => { - // Log error - logger.error('Error occurred:', { - message: err.message, - stack: err.stack, - path: req.path, - method: req.method, - ip: req.ip - }); - - // Sequelize validation errors - if (err.name === 'SequelizeValidationError') { - const errors = err.errors.map(e => ({ - field: e.path, - message: e.message - })); - - return res.status(400).json({ - success: false, - message: 'Validation error', - errors - }); - } - - // Sequelize unique constraint errors - if (err.name === 'SequelizeUniqueConstraintError') { - return res.status(409).json({ - success: false, - message: 'Resource already exists', - field: err.errors[0]?.path - }); - } - - // JWT errors - if (err.name === 'JsonWebTokenError') { - return res.status(401).json({ - success: false, - message: 'Invalid token' - }); - } - - if (err.name === 'TokenExpiredError') { - return res.status(401).json({ - success: false, - message: 'Token expired' - }); - } - - // Multer file upload errors - if (err.name === 'MulterError') { - if (err.code === 'LIMIT_FILE_SIZE') { - return res.status(413).json({ - success: false, - message: 'File too large' - }); - } - return res.status(400).json({ - success: false, - message: err.message - }); - } - - // Default error - const statusCode = err.statusCode || 500; - const message = err.message || 'Internal server error'; - - res.status(statusCode).json({ - success: false, - message, - ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) - }); -}; - -module.exports = errorHandler; diff --git a/middleware/roleCheck.js b/middleware/roleCheck.js deleted file mode 100644 index af26545..0000000 --- a/middleware/roleCheck.js +++ /dev/null @@ -1,47 +0,0 @@ -const { ROLES } = require('../config/constants'); -const logger = require('../utils/logger'); - -/** - * Role-based access control middleware - * @param {Array} allowedRoles - Array of roles that can access the route - * @returns {Function} Express middleware function - */ -const checkRole = (allowedRoles) => { - return (req, res, next) => { - try { - // Check if user is authenticated - if (!req.user) { - return res.status(401).json({ - success: false, - message: 'Authentication required' - }); - } - - // Check if user role is in allowed roles - if (!allowedRoles.includes(req.user.role)) { - logger.warn(`Access denied for user ${req.user.email} (${req.user.role}) to route ${req.path}`); - - return res.status(403).json({ - success: false, - message: 'Access denied. Insufficient permissions.', - requiredRoles: allowedRoles, - yourRole: req.user.role - }); - } - - // User has required role, proceed - next(); - } catch (error) { - logger.error('Role check error:', error); - res.status(500).json({ - success: false, - message: 'Authorization check failed' - }); - } - }; -}; - -module.exports = { - checkRole, - ROLES -}; diff --git a/middleware/upload.js b/middleware/upload.js deleted file mode 100644 index 1d2237d..0000000 --- a/middleware/upload.js +++ /dev/null @@ -1,101 +0,0 @@ -const multer = require('multer'); -const path = require('path'); -const fs = require('fs'); -const { v4: uuidv4 } = require('uuid'); - -// Create uploads directory if it doesn't exist -const uploadDir = process.env.UPLOAD_DIR || './uploads'; -const documentsDir = path.join(uploadDir, 'documents'); -const profilesDir = path.join(uploadDir, 'profiles'); -const tempDir = path.join(uploadDir, 'temp'); - -[uploadDir, documentsDir, profilesDir, tempDir].forEach(dir => { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } -}); - -// Storage configuration -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - let folder = documentsDir; - - if (req.body.uploadType === 'profile') { - folder = profilesDir; - } else if (req.body.uploadType === 'temp') { - folder = tempDir; - } - - cb(null, folder); - }, - filename: (req, file, cb) => { - const uniqueId = uuidv4(); - const ext = path.extname(file.originalname); - const filename = `${uniqueId}${ext}`; - cb(null, filename); - } -}); - -// File filter -const fileFilter = (req, file, cb) => { - // Allowed file types - const allowedTypes = [ - 'application/pdf', - 'image/jpeg', - 'image/jpg', - 'image/png', - 'application/msword', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.ms-excel', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - ]; - - if (allowedTypes.includes(file.mimetype)) { - cb(null, true); - } else { - cb(new Error('Invalid file type. Only PDF, JPG, PNG, DOC, DOCX, XLS, XLSX allowed'), false); - } -}; - -// Multer upload configuration -const upload = multer({ - storage: storage, - limits: { - fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024 // 10MB default - }, - fileFilter: fileFilter -}); - -// Single file upload -const uploadSingle = upload.single('file'); - -// Multiple files upload -const uploadMultiple = upload.array('files', 10); // Max 10 files - -// Error handler for multer -const handleUploadError = (err, req, res, next) => { - if (err instanceof multer.MulterError) { - if (err.code === 'LIMIT_FILE_SIZE') { - return res.status(400).json({ - success: false, - message: 'File too large. Maximum size is 10MB' - }); - } - return res.status(400).json({ - success: false, - message: `Upload error: ${err.message}` - }); - } else if (err) { - return res.status(400).json({ - success: false, - message: err.message - }); - } - next(); -}; - -module.exports = { - uploadSingle, - uploadMultiple, - handleUploadError -}; diff --git a/models/Application.js b/models/Application.js deleted file mode 100644 index 64ad663..0000000 --- a/models/Application.js +++ /dev/null @@ -1,104 +0,0 @@ -const { APPLICATION_STAGES, APPLICATION_STATUS, BUSINESS_TYPES } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Application = sequelize.define('Application', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - applicationId: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - applicantName: { - type: DataTypes.STRING, - allowNull: false - }, - email: { - type: DataTypes.STRING, - allowNull: false, - validate: { isEmail: true } - }, - phone: { - type: DataTypes.STRING, - allowNull: false - }, - businessType: { - type: DataTypes.ENUM(Object.values(BUSINESS_TYPES)), - allowNull: false - }, - preferredLocation: { - type: DataTypes.STRING, - allowNull: false - }, - city: { - type: DataTypes.STRING, - allowNull: false - }, - state: { - type: DataTypes.STRING, - allowNull: false - }, - experienceYears: { - type: DataTypes.INTEGER, - allowNull: false - }, - investmentCapacity: { - type: DataTypes.STRING, - allowNull: false - }, - currentStage: { - type: DataTypes.ENUM(Object.values(APPLICATION_STAGES)), - defaultValue: APPLICATION_STAGES.DD - }, - overallStatus: { - type: DataTypes.ENUM(Object.values(APPLICATION_STATUS)), - defaultValue: APPLICATION_STATUS.PENDING - }, - progressPercentage: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - submittedBy: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - }, - documents: { - type: DataTypes.JSON, - defaultValue: [] - }, - timeline: { - type: DataTypes.JSON, - defaultValue: [] - } - }, { - tableName: 'applications', - timestamps: true, - indexes: [ - { fields: ['applicationId'] }, - { fields: ['email'] }, - { fields: ['currentStage'] }, - { fields: ['overallStatus'] } - ] - }); - - Application.associate = (models) => { - Application.belongsTo(models.User, { - foreignKey: 'submittedBy', - as: 'submitter' - }); - Application.hasMany(models.Document, { - foreignKey: 'requestId', - as: 'uploadedDocuments', - scope: { requestType: 'application' } - }); - }; - - return Application; -}; diff --git a/models/AuditLog.js b/models/AuditLog.js deleted file mode 100644 index 1245e08..0000000 --- a/models/AuditLog.js +++ /dev/null @@ -1,65 +0,0 @@ -const { AUDIT_ACTIONS } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const AuditLog = sequelize.define('AuditLog', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - userId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - }, - action: { - type: DataTypes.ENUM(Object.values(AUDIT_ACTIONS)), - allowNull: false - }, - entityType: { - type: DataTypes.STRING, - allowNull: false - }, - entityId: { - type: DataTypes.UUID, - allowNull: false - }, - oldData: { - type: DataTypes.JSON, - allowNull: true - }, - newData: { - type: DataTypes.JSON, - allowNull: true - }, - ipAddress: { - type: DataTypes.STRING, - allowNull: true - }, - userAgent: { - type: DataTypes.STRING, - allowNull: true - } - }, { - tableName: 'audit_logs', - timestamps: true, - indexes: [ - { fields: ['userId'] }, - { fields: ['action'] }, - { fields: ['entityType'] }, - { fields: ['entityId'] } - ] - }); - - AuditLog.associate = (models) => { - AuditLog.belongsTo(models.User, { - foreignKey: 'userId', - as: 'user' - }); - }; - - return AuditLog; -}; diff --git a/models/ConstitutionalChange.js b/models/ConstitutionalChange.js deleted file mode 100644 index 88a5538..0000000 --- a/models/ConstitutionalChange.js +++ /dev/null @@ -1,87 +0,0 @@ -const { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const ConstitutionalChange = sequelize.define('ConstitutionalChange', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - requestId: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - outletId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'outlets', - key: 'id' - } - }, - dealerId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - changeType: { - type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_CHANGE_TYPES)), - allowNull: false - }, - description: { - type: DataTypes.TEXT, - allowNull: false - }, - currentStage: { - type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_STAGES)), - defaultValue: CONSTITUTIONAL_STAGES.DD_ADMIN_REVIEW - }, - status: { - type: DataTypes.STRING, - defaultValue: 'Pending' - }, - progressPercentage: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - documents: { - type: DataTypes.JSON, - defaultValue: [] - }, - timeline: { - type: DataTypes.JSON, - defaultValue: [] - } - }, { - tableName: 'constitutional_changes', - timestamps: true, - indexes: [ - { fields: ['requestId'] }, - { fields: ['outletId'] }, - { fields: ['dealerId'] }, - { fields: ['currentStage'] } - ] - }); - - ConstitutionalChange.associate = (models) => { - ConstitutionalChange.belongsTo(models.Outlet, { - foreignKey: 'outletId', - as: 'outlet' - }); - ConstitutionalChange.belongsTo(models.User, { - foreignKey: 'dealerId', - as: 'dealer' - }); - ConstitutionalChange.hasMany(models.Worknote, { - foreignKey: 'requestId', - as: 'worknotes', - scope: { requestType: 'constitutional' } - }); - }; - - return ConstitutionalChange; -}; diff --git a/models/Document.js b/models/Document.js deleted file mode 100644 index 31f7683..0000000 --- a/models/Document.js +++ /dev/null @@ -1,64 +0,0 @@ -const { REQUEST_TYPES, DOCUMENT_TYPES } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Document = sequelize.define('Document', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - requestId: { - type: DataTypes.UUID, - allowNull: false - }, - requestType: { - type: DataTypes.ENUM(Object.values(REQUEST_TYPES)), - allowNull: false - }, - documentType: { - type: DataTypes.ENUM(Object.values(DOCUMENT_TYPES)), - allowNull: false - }, - fileName: { - type: DataTypes.STRING, - allowNull: false - }, - fileUrl: { - type: DataTypes.STRING, - allowNull: false - }, - fileSize: { - type: DataTypes.INTEGER, - allowNull: true - }, - uploadedBy: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - status: { - type: DataTypes.STRING, - defaultValue: 'Active' - } - }, { - tableName: 'documents', - timestamps: true, - indexes: [ - { fields: ['requestId'] }, - { fields: ['requestType'] }, - { fields: ['documentType'] } - ] - }); - - Document.associate = (models) => { - Document.belongsTo(models.User, { - foreignKey: 'uploadedBy', - as: 'uploader' - }); - }; - - return Document; -}; diff --git a/models/FinancePayment.js b/models/FinancePayment.js deleted file mode 100644 index 2ab1ec6..0000000 --- a/models/FinancePayment.js +++ /dev/null @@ -1,75 +0,0 @@ -const { PAYMENT_TYPES, PAYMENT_STATUS } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const FinancePayment = sequelize.define('FinancePayment', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - applicationId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'applications', - key: 'id' - } - }, - paymentType: { - type: DataTypes.ENUM(Object.values(PAYMENT_TYPES)), - allowNull: false - }, - amount: { - type: DataTypes.DECIMAL(15, 2), - allowNull: false - }, - paymentStatus: { - type: DataTypes.ENUM(Object.values(PAYMENT_STATUS)), - defaultValue: PAYMENT_STATUS.PENDING - }, - transactionId: { - type: DataTypes.STRING, - allowNull: true - }, - paymentDate: { - type: DataTypes.DATE, - allowNull: true - }, - verifiedBy: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - }, - verificationDate: { - type: DataTypes.DATE, - allowNull: true - }, - remarks: { - type: DataTypes.TEXT, - allowNull: true - } - }, { - tableName: 'finance_payments', - timestamps: true, - indexes: [ - { fields: ['applicationId'] }, - { fields: ['paymentStatus'] } - ] - }); - - FinancePayment.associate = (models) => { - FinancePayment.belongsTo(models.Application, { - foreignKey: 'applicationId', - as: 'application' - }); - FinancePayment.belongsTo(models.User, { - foreignKey: 'verifiedBy', - as: 'verifier' - }); - }; - - return FinancePayment; -}; diff --git a/models/FnF.js b/models/FnF.js deleted file mode 100644 index 03bf4d9..0000000 --- a/models/FnF.js +++ /dev/null @@ -1,72 +0,0 @@ -const { FNF_STATUS } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const FnF = sequelize.define('FnF', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - resignationId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'resignations', - key: 'id' - } - }, - outletId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'outlets', - key: 'id' - } - }, - status: { - type: DataTypes.ENUM(Object.values(FNF_STATUS)), - defaultValue: FNF_STATUS.INITIATED - }, - totalReceivables: { - type: DataTypes.DECIMAL(15, 2), - defaultValue: 0 - }, - totalPayables: { - type: DataTypes.DECIMAL(15, 2), - defaultValue: 0 - }, - netAmount: { - type: DataTypes.DECIMAL(15, 2), - defaultValue: 0 - }, - settlementDate: { - type: DataTypes.DATE, - allowNull: true - }, - clearanceDocuments: { - type: DataTypes.JSON, - defaultValue: [] - } - }, { - tableName: 'fnf_settlements', - timestamps: true, - indexes: [ - { fields: ['resignationId'] }, - { fields: ['outletId'] }, - { fields: ['status'] } - ] - }); - - FnF.associate = (models) => { - FnF.belongsTo(models.Resignation, { - foreignKey: 'resignationId', - as: 'resignation' - }); - FnF.belongsTo(models.Outlet, { - foreignKey: 'outletId', - as: 'outlet' - }); - }; - - return FnF; -}; diff --git a/models/Outlet.js b/models/Outlet.js deleted file mode 100644 index 6691a94..0000000 --- a/models/Outlet.js +++ /dev/null @@ -1,104 +0,0 @@ -const { OUTLET_TYPES, OUTLET_STATUS, REGIONS } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Outlet = sequelize.define('Outlet', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - code: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - name: { - type: DataTypes.STRING, - allowNull: false - }, - type: { - type: DataTypes.ENUM(Object.values(OUTLET_TYPES)), - allowNull: false - }, - address: { - type: DataTypes.TEXT, - allowNull: false - }, - city: { - type: DataTypes.STRING, - allowNull: false - }, - state: { - type: DataTypes.STRING, - allowNull: false - }, - pincode: { - type: DataTypes.STRING, - allowNull: false - }, - latitude: { - type: DataTypes.DECIMAL(10, 8), - allowNull: true - }, - longitude: { - type: DataTypes.DECIMAL(11, 8), - allowNull: true - }, - status: { - type: DataTypes.ENUM(Object.values(OUTLET_STATUS)), - defaultValue: OUTLET_STATUS.ACTIVE - }, - establishedDate: { - type: DataTypes.DATEONLY, - allowNull: false - }, - dealerId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - region: { - type: DataTypes.ENUM(Object.values(REGIONS)), - allowNull: false - }, - zone: { - type: DataTypes.STRING, - allowNull: false - } - }, { - tableName: 'outlets', - timestamps: true, - indexes: [ - { fields: ['code'] }, - { fields: ['dealerId'] }, - { fields: ['type'] }, - { fields: ['status'] }, - { fields: ['region'] }, - { fields: ['zone'] } - ] - }); - - Outlet.associate = (models) => { - Outlet.belongsTo(models.User, { - foreignKey: 'dealerId', - as: 'dealer' - }); - Outlet.hasMany(models.Resignation, { - foreignKey: 'outletId', - as: 'resignations' - }); - Outlet.hasMany(models.ConstitutionalChange, { - foreignKey: 'outletId', - as: 'constitutionalChanges' - }); - Outlet.hasMany(models.RelocationRequest, { - foreignKey: 'outletId', - as: 'relocationRequests' - }); - }; - - return Outlet; -}; diff --git a/models/Region.js b/models/Region.js deleted file mode 100644 index 7d6a644..0000000 --- a/models/Region.js +++ /dev/null @@ -1,44 +0,0 @@ -const { REGIONS } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Region = sequelize.define('Region', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.ENUM(Object.values(REGIONS)), - unique: true, - allowNull: false - }, - description: { - type: DataTypes.TEXT, - allowNull: true - }, - regionalManagerId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - } - }, { - tableName: 'regions', - timestamps: true - }); - - Region.associate = (models) => { - Region.belongsTo(models.User, { - foreignKey: 'regionalManagerId', - as: 'regionalManager' - }); - Region.hasMany(models.Zone, { - foreignKey: 'regionId', - as: 'zones' - }); - }; - - return Region; -}; diff --git a/models/RelocationRequest.js b/models/RelocationRequest.js deleted file mode 100644 index 903b687..0000000 --- a/models/RelocationRequest.js +++ /dev/null @@ -1,99 +0,0 @@ -const { RELOCATION_TYPES, RELOCATION_STAGES } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const RelocationRequest = sequelize.define('RelocationRequest', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - requestId: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - outletId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'outlets', - key: 'id' - } - }, - dealerId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - relocationType: { - type: DataTypes.ENUM(Object.values(RELOCATION_TYPES)), - allowNull: false - }, - newAddress: { - type: DataTypes.TEXT, - allowNull: false - }, - newCity: { - type: DataTypes.STRING, - allowNull: false - }, - newState: { - type: DataTypes.STRING, - allowNull: false - }, - reason: { - type: DataTypes.TEXT, - allowNull: false - }, - currentStage: { - type: DataTypes.ENUM(Object.values(RELOCATION_STAGES)), - defaultValue: RELOCATION_STAGES.DD_ADMIN_REVIEW - }, - status: { - type: DataTypes.STRING, - defaultValue: 'Pending' - }, - progressPercentage: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - documents: { - type: DataTypes.JSON, - defaultValue: [] - }, - timeline: { - type: DataTypes.JSON, - defaultValue: [] - } - }, { - tableName: 'relocation_requests', - timestamps: true, - indexes: [ - { fields: ['requestId'] }, - { fields: ['outletId'] }, - { fields: ['dealerId'] }, - { fields: ['currentStage'] } - ] - }); - - RelocationRequest.associate = (models) => { - RelocationRequest.belongsTo(models.Outlet, { - foreignKey: 'outletId', - as: 'outlet' - }); - RelocationRequest.belongsTo(models.User, { - foreignKey: 'dealerId', - as: 'dealer' - }); - RelocationRequest.hasMany(models.Worknote, { - foreignKey: 'requestId', - as: 'worknotes', - scope: { requestType: 'relocation' } - }); - }; - - return RelocationRequest; -}; diff --git a/models/Resignation.js b/models/Resignation.js deleted file mode 100644 index b1af1b5..0000000 --- a/models/Resignation.js +++ /dev/null @@ -1,110 +0,0 @@ -const { RESIGNATION_TYPES, RESIGNATION_STAGES } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Resignation = sequelize.define('Resignation', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - resignationId: { - type: DataTypes.STRING, - unique: true, - allowNull: false - }, - outletId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'outlets', - key: 'id' - } - }, - dealerId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - resignationType: { - type: DataTypes.ENUM(Object.values(RESIGNATION_TYPES)), - allowNull: false - }, - lastOperationalDateSales: { - type: DataTypes.DATEONLY, - allowNull: false - }, - lastOperationalDateServices: { - type: DataTypes.DATEONLY, - allowNull: false - }, - reason: { - type: DataTypes.TEXT, - allowNull: false - }, - additionalInfo: { - type: DataTypes.TEXT, - allowNull: true - }, - currentStage: { - type: DataTypes.ENUM(Object.values(RESIGNATION_STAGES)), - defaultValue: RESIGNATION_STAGES.ASM - }, - status: { - type: DataTypes.STRING, - defaultValue: 'Pending' - }, - progressPercentage: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - submittedOn: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW - }, - documents: { - type: DataTypes.JSON, - defaultValue: [] - }, - timeline: { - type: DataTypes.JSON, - defaultValue: [] - }, - rejectionReason: { - type: DataTypes.TEXT, - allowNull: true - } - }, { - tableName: 'resignations', - timestamps: true, - indexes: [ - { fields: ['resignationId'] }, - { fields: ['outletId'] }, - { fields: ['dealerId'] }, - { fields: ['currentStage'] }, - { fields: ['status'] } - ] - }); - - Resignation.associate = (models) => { - Resignation.belongsTo(models.Outlet, { - foreignKey: 'outletId', - as: 'outlet' - }); - Resignation.belongsTo(models.User, { - foreignKey: 'dealerId', - as: 'dealer' - }); - Resignation.hasMany(models.Worknote, { - foreignKey: 'requestId', - as: 'worknotes', - scope: { - requestType: 'resignation' - } - }); - }; - - return Resignation; -}; diff --git a/models/User.js b/models/User.js deleted file mode 100644 index 769572e..0000000 --- a/models/User.js +++ /dev/null @@ -1,89 +0,0 @@ -const { ROLES, REGIONS } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const User = sequelize.define('User', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - email: { - type: DataTypes.STRING, - unique: true, - allowNull: false, - validate: { - isEmail: true - } - }, - password: { - type: DataTypes.STRING, - allowNull: false - }, - name: { - type: DataTypes.STRING, - allowNull: false - }, - role: { - type: DataTypes.ENUM(Object.values(ROLES)), - allowNull: false - }, - region: { - type: DataTypes.ENUM(Object.values(REGIONS)), - allowNull: true - }, - zone: { - type: DataTypes.STRING, - allowNull: true - }, - phone: { - type: DataTypes.STRING, - allowNull: true - }, - status: { - type: DataTypes.ENUM('active', 'inactive'), - defaultValue: 'active' - }, - lastLogin: { - type: DataTypes.DATE, - allowNull: true - } - }, { - tableName: 'users', - timestamps: true, - indexes: [ - { fields: ['email'] }, - { fields: ['role'] }, - { fields: ['region'] }, - { fields: ['zone'] } - ] - }); - - User.associate = (models) => { - User.hasMany(models.Application, { - foreignKey: 'submittedBy', - as: 'applications' - }); - User.hasMany(models.Outlet, { - foreignKey: 'dealerId', - as: 'outlets' - }); - User.hasMany(models.Resignation, { - foreignKey: 'dealerId', - as: 'resignations' - }); - User.hasMany(models.ConstitutionalChange, { - foreignKey: 'dealerId', - as: 'constitutionalChanges' - }); - User.hasMany(models.RelocationRequest, { - foreignKey: 'dealerId', - as: 'relocationRequests' - }); - User.hasMany(models.AuditLog, { - foreignKey: 'userId', - as: 'auditLogs' - }); - }; - - return User; -}; diff --git a/models/Worknote.js b/models/Worknote.js deleted file mode 100644 index 67c6711..0000000 --- a/models/Worknote.js +++ /dev/null @@ -1,52 +0,0 @@ -const { REQUEST_TYPES } = require('../config/constants'); - -module.exports = (sequelize, DataTypes) => { - const Worknote = sequelize.define('Worknote', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - requestId: { - type: DataTypes.UUID, - allowNull: false - }, - requestType: { - type: DataTypes.ENUM(Object.values(REQUEST_TYPES)), - allowNull: false - }, - userId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'users', - key: 'id' - } - }, - content: { - type: DataTypes.TEXT, - allowNull: false - }, - isInternal: { - type: DataTypes.BOOLEAN, - defaultValue: true - } - }, { - tableName: 'worknotes', - timestamps: true, - indexes: [ - { fields: ['requestId'] }, - { fields: ['requestType'] }, - { fields: ['userId'] } - ] - }); - - Worknote.associate = (models) => { - Worknote.belongsTo(models.User, { - foreignKey: 'userId', - as: 'author' - }); - }; - - return Worknote; -}; diff --git a/models/Zone.js b/models/Zone.js deleted file mode 100644 index a780694..0000000 --- a/models/Zone.js +++ /dev/null @@ -1,49 +0,0 @@ -module.exports = (sequelize, DataTypes) => { - const Zone = sequelize.define('Zone', { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: false - }, - regionId: { - type: DataTypes.UUID, - allowNull: false, - references: { - model: 'regions', - key: 'id' - } - }, - zonalManagerId: { - type: DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id' - } - } - }, { - tableName: 'zones', - timestamps: true, - indexes: [ - { fields: ['regionId'] }, - { unique: true, fields: ['name', 'regionId'] } - ] - }); - - Zone.associate = (models) => { - Zone.belongsTo(models.Region, { - foreignKey: 'regionId', - as: 'region' - }); - Zone.belongsTo(models.User, { - foreignKey: 'zonalManagerId', - as: 'zonalManager' - }); - }; - - return Zone; -}; diff --git a/models/index.js b/models/index.js deleted file mode 100644 index 946d34f..0000000 --- a/models/index.js +++ /dev/null @@ -1,49 +0,0 @@ -const { Sequelize } = require('sequelize'); -const config = require('../config/database'); - -const env = process.env.NODE_ENV || 'development'; -const dbConfig = config[env]; - -// Initialize Sequelize -const sequelize = new Sequelize( - dbConfig.database, - dbConfig.username, - dbConfig.password, - { - host: dbConfig.host, - port: dbConfig.port, - dialect: dbConfig.dialect, - logging: dbConfig.logging, - pool: dbConfig.pool, - dialectOptions: dbConfig.dialectOptions - } -); - -const db = {}; - -// Import models -db.User = require('./User')(sequelize, Sequelize.DataTypes); -db.Application = require('./Application')(sequelize, Sequelize.DataTypes); -db.Resignation = require('./Resignation')(sequelize, Sequelize.DataTypes); -db.ConstitutionalChange = require('./ConstitutionalChange')(sequelize, Sequelize.DataTypes); -db.RelocationRequest = require('./RelocationRequest')(sequelize, Sequelize.DataTypes); -db.Outlet = require('./Outlet')(sequelize, Sequelize.DataTypes); -db.Worknote = require('./Worknote')(sequelize, Sequelize.DataTypes); -db.Document = require('./Document')(sequelize, Sequelize.DataTypes); -db.AuditLog = require('./AuditLog')(sequelize, Sequelize.DataTypes); -db.FinancePayment = require('./FinancePayment')(sequelize, Sequelize.DataTypes); -db.FnF = require('./FnF')(sequelize, Sequelize.DataTypes); -db.Region = require('./Region')(sequelize, Sequelize.DataTypes); -db.Zone = require('./Zone')(sequelize, Sequelize.DataTypes); - -// Define associations -Object.keys(db).forEach(modelName => { - if (db[modelName].associate) { - db[modelName].associate(db); - } -}); - -db.sequelize = sequelize; -db.Sequelize = Sequelize; - -module.exports = db; diff --git a/package.json b/package.json index 0261d48..233fa94 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "dev": "tsx watch src/server.ts", "build": "tsc", "type-check": "tsc --noEmit", - "migrate": "node dist/scripts/migrate.js", + "migrate": "tsx scripts/migrate.ts", + "seed": "tsx scripts/seed-geo.ts", "test": "jest", "test:coverage": "jest --coverage", "clear-logs": "rm -rf logs/*.log" diff --git a/routes/applications.js b/routes/applications.js deleted file mode 100644 index f310694..0000000 --- a/routes/applications.js +++ /dev/null @@ -1,15 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const applicationController = require('../controllers/applicationController'); -const { authenticate, optionalAuth } = require('../middleware/auth'); - -// Public route - submit application -router.post('/', applicationController.submitApplication); - -// Protected routes -router.get('/', authenticate, applicationController.getApplications); -router.get('/:id', authenticate, applicationController.getApplicationById); -router.put('/:id/action', authenticate, applicationController.takeAction); -router.post('/:id/documents', authenticate, applicationController.uploadDocuments); - -module.exports = router; diff --git a/routes/auth.js b/routes/auth.js deleted file mode 100644 index abc2737..0000000 --- a/routes/auth.js +++ /dev/null @@ -1,15 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const authController = require('../controllers/authController'); -const { authenticate } = require('../middleware/auth'); - -// Public routes -router.post('/register', authController.register); -router.post('/login', authController.login); - -// Protected routes -router.get('/profile', authenticate, authController.getProfile); -router.put('/profile', authenticate, authController.updateProfile); -router.post('/change-password', authenticate, authController.changePassword); - -module.exports = router; diff --git a/routes/constitutional.js b/routes/constitutional.js deleted file mode 100644 index f5800e5..0000000 --- a/routes/constitutional.js +++ /dev/null @@ -1,24 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const constitutionalController = require('../controllers/constitutionalController'); -const { authenticate } = require('../middleware/auth'); - -// All routes require authentication -router.use(authenticate); - -// Submit constitutional change request -router.post('/', constitutionalController.submitRequest); - -// Get constitutional change requests -router.get('/', constitutionalController.getRequests); - -// Get specific request details -router.get('/:id', constitutionalController.getRequestById); - -// Take action on request -router.put('/:id/action', constitutionalController.takeAction); - -// Upload documents -router.post('/:id/documents', constitutionalController.uploadDocuments); - -module.exports = router; diff --git a/routes/finance.js b/routes/finance.js deleted file mode 100644 index a0a8ece..0000000 --- a/routes/finance.js +++ /dev/null @@ -1,16 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const financeController = require('../controllers/financeController'); -const { authenticate } = require('../middleware/auth'); -const { checkRole, ROLES } = require('../middleware/roleCheck'); - -// All routes require authentication -router.use(authenticate); - -// Finance user only routes -router.get('/onboarding', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), financeController.getOnboardingPayments); -router.get('/fnf', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), financeController.getFnFSettlements); -router.put('/payments/:id', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), financeController.updatePayment); -router.put('/fnf/:id', checkRole([ROLES.FINANCE, ROLES.SUPER_ADMIN]), financeController.updateFnF); - -module.exports = router; diff --git a/routes/master.js b/routes/master.js deleted file mode 100644 index c5d3d12..0000000 --- a/routes/master.js +++ /dev/null @@ -1,20 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const masterController = require('../controllers/masterController'); -const { authenticate } = require('../middleware/auth'); -const { checkRole, ROLES } = require('../middleware/roleCheck'); - -// All routes require authentication -router.use(authenticate); - -// Regions -router.get('/regions', masterController.getRegions); -router.post('/regions', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]), masterController.createRegion); -router.put('/regions/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), masterController.updateRegion); - -// Zones -router.get('/zones', masterController.getZones); -router.post('/zones', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN, ROLES.DD_LEAD]), masterController.createZone); -router.put('/zones/:id', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]), masterController.updateZone); - -module.exports = router; diff --git a/routes/outlets.js b/routes/outlets.js deleted file mode 100644 index a4722f9..0000000 --- a/routes/outlets.js +++ /dev/null @@ -1,21 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const outletController = require('../controllers/outletController'); -const { authenticate } = require('../middleware/auth'); - -// All routes require authentication -router.use(authenticate); - -// Get all outlets for logged-in dealer -router.get('/', outletController.getOutlets); - -// Get specific outlet details -router.get('/:id', outletController.getOutletById); - -// Create new outlet (admin only) -router.post('/', outletController.createOutlet); - -// Update outlet -router.put('/:id', outletController.updateOutlet); - -module.exports = router; diff --git a/routes/relocation.js b/routes/relocation.js deleted file mode 100644 index d920cb1..0000000 --- a/routes/relocation.js +++ /dev/null @@ -1,24 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const relocationController = require('../controllers/relocationController'); -const { authenticate } = require('../middleware/auth'); - -// All routes require authentication -router.use(authenticate); - -// Submit relocation request -router.post('/', relocationController.submitRequest); - -// Get relocation requests -router.get('/', relocationController.getRequests); - -// Get specific request details -router.get('/:id', relocationController.getRequestById); - -// Take action on request -router.put('/:id/action', relocationController.takeAction); - -// Upload documents -router.post('/:id/documents', relocationController.uploadDocuments); - -module.exports = router; diff --git a/routes/resignations.js b/routes/resignations.js deleted file mode 100644 index e2cf48f..0000000 --- a/routes/resignations.js +++ /dev/null @@ -1,62 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const resignationController = require('../controllers/resignationController'); -const { authenticate } = require('../middleware/auth'); -const { checkRole } = require('../middleware/roleCheck'); -const { ROLES } = require('../config/constants'); - -// Create resignation (Dealer only) -router.post( - '/create', - authenticate, - checkRole([ROLES.DEALER]), - resignationController.createResignation -); - -// Get resignations list (role-based filtering) -router.get( - '/list', - authenticate, - resignationController.getResignations -); - -// Get resignation by ID -router.get( - '/:id', - authenticate, - resignationController.getResignationById -); - -// Approve resignation (specific roles only) -router.post( - '/:id/approve', - authenticate, - checkRole([ - ROLES.RBM, - ROLES.ZBH, - ROLES.NBH, - ROLES.DD_ADMIN, - ROLES.LEGAL_ADMIN, - ROLES.FINANCE, - ROLES.SUPER_ADMIN - ]), - resignationController.approveResignation -); - -// Reject resignation (specific roles only) -router.post( - '/:id/reject', - authenticate, - checkRole([ - ROLES.RBM, - ROLES.ZBH, - ROLES.NBH, - ROLES.DD_ADMIN, - ROLES.LEGAL_ADMIN, - ROLES.FINANCE, - ROLES.SUPER_ADMIN - ]), - resignationController.rejectResignation -); - -module.exports = router; diff --git a/routes/upload.js b/routes/upload.js deleted file mode 100644 index 7e62df0..0000000 --- a/routes/upload.js +++ /dev/null @@ -1,65 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { authenticate } = require('../middleware/auth'); -const { uploadSingle, uploadMultiple, handleUploadError } = require('../middleware/upload'); - -// All routes require authentication -router.use(authenticate); - -// Single file upload -router.post('/document', (req, res, next) => { - uploadSingle(req, res, (err) => { - if (err) { - return handleUploadError(err, req, res, next); - } - - if (!req.file) { - return res.status(400).json({ - success: false, - message: 'No file uploaded' - }); - } - - res.json({ - success: true, - message: 'File uploaded successfully', - file: { - filename: req.file.filename, - originalName: req.file.originalname, - size: req.file.size, - url: `/uploads/${req.body.uploadType || 'documents'}/${req.file.filename}` - } - }); - }); -}); - -// Multiple files upload -router.post('/documents', (req, res, next) => { - uploadMultiple(req, res, (err) => { - if (err) { - return handleUploadError(err, req, res, next); - } - - if (!req.files || req.files.length === 0) { - return res.status(400).json({ - success: false, - message: 'No files uploaded' - }); - } - - const files = req.files.map(file => ({ - filename: file.filename, - originalName: file.originalname, - size: file.size, - url: `/uploads/${req.body.uploadType || 'documents'}/${file.filename}` - })); - - res.json({ - success: true, - message: 'Files uploaded successfully', - files - }); - }); -}); - -module.exports = router; diff --git a/routes/worknotes.js b/routes/worknotes.js deleted file mode 100644 index 22cab85..0000000 --- a/routes/worknotes.js +++ /dev/null @@ -1,18 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const worknoteController = require('../controllers/worknoteController'); -const { authenticate } = require('../middleware/auth'); - -// All routes require authentication -router.use(authenticate); - -// Add worknote to a request -router.post('/', worknoteController.addWorknote); - -// Get worknotes for a request -router.get('/:requestId', worknoteController.getWorknotes); - -// Delete worknote (admin only) -router.delete('/:id', worknoteController.deleteWorknote); - -module.exports = router; diff --git a/scripts/migrate.js b/scripts/migrate.js deleted file mode 100644 index 5e06d71..0000000 --- a/scripts/migrate.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Database Migration Script - * Synchronizes all Sequelize models with the database - * This script will DROP all existing tables and recreate them. - * - * Run: node scripts/migrate.js - */ - -require('dotenv').config(); -const db = require('../src/database/models'); - -async function runMigrations() { - console.log('🔄 Starting database synchronization (Fresh Startup)...\n'); - console.log('⚠️ WARNING: This will drop all existing tables in the database.\n'); - - try { - // Authenticate with the database - await db.sequelize.authenticate(); - console.log('📡 Connected to the database successfully.'); - - // Synchronize models (force: true drops existing tables) - // This ensures that the schema exactly matches the Sequelize models - await db.sequelize.sync({ force: true }); - - console.log('\n✅ All tables created and synchronized successfully!'); - console.log('----------------------------------------------------'); - console.log(`Available Models: ${Object.keys(db).filter(k => k !== 'sequelize' && k !== 'Sequelize').join(', ')}`); - console.log('----------------------------------------------------'); - - process.exit(0); - } catch (error) { - console.error('\n❌ Migration failed:', error.message); - if (error.stack) { - console.error('\nStack Trace:\n', error.stack); - } - process.exit(1); - } -} - -runMigrations(); diff --git a/scripts/seed-geo.ts b/scripts/seed-geo.ts new file mode 100644 index 0000000..c6f712d --- /dev/null +++ b/scripts/seed-geo.ts @@ -0,0 +1,27 @@ + +import { createRequire } from 'module'; +import db from '../src/database/models/index.js'; + +const require = createRequire(import.meta.url); +const geoSeeder = require('../seeders/20240127-seed-geo-data.js'); + +async function seed() { + console.log('🌱 Starting Geo Data Seeding...'); + try { + await db.sequelize.authenticate(); + console.log('Connection established.'); + + const queryInterface = db.sequelize.getQueryInterface(); + + console.log('Executing seeder...'); + await geoSeeder.up(queryInterface, db.Sequelize); + + console.log('✅ Seeding completed successfully.'); + process.exit(0); + } catch (error) { + console.error('❌ Seeding failed:', error); + process.exit(1); + } +} + +seed(); diff --git a/scripts/seed-permissions.ts b/scripts/seed-permissions.ts new file mode 100644 index 0000000..2ccf67c --- /dev/null +++ b/scripts/seed-permissions.ts @@ -0,0 +1,310 @@ +import 'dotenv/config'; +import db from '../src/database/models/index.js'; +import { PERMISSIONS, PERMISSION_CATEGORIES } from '../src/common/config/permissions.js'; + +const { Permission } = db; + +const permissionsToSeed = [ + // Action Permissions + { + code: PERMISSIONS.ACTION_APPROVE, + name: 'Approve Applications', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'APPROVE', + description: 'Ability to approve applications' + }, + { + code: PERMISSIONS.ACTION_REJECT, + name: 'Reject Applications', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'REJECT', + description: 'Ability to reject applications' + }, + { + code: PERMISSIONS.ACTION_UPLOAD_DOCS, + name: 'Upload Documents', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'UPLOAD_DOCS', + description: 'Ability to upload documents' + }, + { + code: PERMISSIONS.ACTION_REQUEST_CHANGES, + name: 'Request Changes', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'REQUEST_CHANGES', + description: 'Ability to request changes' + }, + { + code: PERMISSIONS.ACTION_FORWARD, + name: 'Forward to Others', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'FORWARD', + description: 'Ability to forward applications' + }, + { + code: PERMISSIONS.ACTION_REASSIGN, + name: 'Reassign Applications', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'REASSIGN', + description: 'Ability to reassign applications' + }, + { + code: PERMISSIONS.ACTION_SCHEDULE_INTERVIEW, + name: 'Schedule Interviews', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'SCHEDULE_INTERVIEW', + description: 'Ability to schedule interviews' + }, + { + code: PERMISSIONS.ACTION_ADD_COMMENTS, + name: 'Add Comments/Notes', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'ADD_COMMENTS', + description: 'Ability to add comments' + }, + { + code: PERMISSIONS.ACTION_RANK_APPLICANTS, + name: 'Rank Applicants', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'RANK_APPLICANTS', + description: 'Ability to rank applicants' + }, + { + code: PERMISSIONS.ACTION_FINAL_APPROVAL, + name: 'Final Approval', + category: PERMISSION_CATEGORIES.ACTION, + module: 'APPLICATIONS', + type: 'ACTION', + action: 'FINAL_APPROVAL', + description: 'Ability to give final approval' + }, + + // View Permissions + { + code: PERMISSIONS.VIEW_DETAILS, + name: 'Application Details', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_DETAILS', + description: 'View basic application details' + }, + { + code: PERMISSIONS.VIEW_FINANCIAL, + name: 'Financial Information', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_FINANCIAL', + description: 'View financial information' + }, + { + code: PERMISSIONS.VIEW_DISCUSSIONS, + name: 'Discussion Notes', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_DISCUSSIONS', + description: 'View internal discussion notes' + }, + { + code: PERMISSIONS.VIEW_PROGRESS, + name: 'Progress Tracking', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_PROGRESS', + description: 'View application progress' + }, + { + code: PERMISSIONS.VIEW_AUDIT, + name: 'Audit Logs', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_AUDIT', + description: 'View application audit logs' + }, + { + code: PERMISSIONS.VIEW_DOCUMENTS, + name: 'All Documents', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_DOCUMENTS', + description: 'View all documents' + }, + { + code: PERMISSIONS.VIEW_PERSONAL, + name: 'Personal Information', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_PERSONAL', + description: 'View applicant personal info' + }, + { + code: PERMISSIONS.VIEW_BUSINESS, + name: 'Business Details', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_BUSINESS', + description: 'View applicant business details' + }, + { + code: PERMISSIONS.VIEW_REPORTS, + name: 'Reports & Analytics', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_REPORTS', + description: 'View application reports' + }, + { + code: PERMISSIONS.VIEW_HISTORY, + name: 'Application History', + category: PERMISSION_CATEGORIES.VIEW, + module: 'APPLICATIONS', + type: 'VIEW', + action: 'VIEW_HISTORY', + description: 'View status history' + }, + + // Stage Permissions + { + code: PERMISSIONS.STAGE_INITIAL_REVIEW, + name: 'Initial Review', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Initial Review stage' + }, + { + code: PERMISSIONS.STAGE_FIELD_VERIFICATION, + name: 'Field Verification', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Field Verification stage' + }, + { + code: PERMISSIONS.STAGE_LEVEL1_INTERVIEW, + name: 'Level 1 Interview', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Level 1 Interview stage' + }, + { + code: PERMISSIONS.STAGE_LEVEL2_INTERVIEW, + name: 'Level 2 Interview', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Level 2 Interview stage' + }, + { + code: PERMISSIONS.STAGE_RANKING, + name: 'Ranking & Selection', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Ranking & Selection stage' + }, + { + code: PERMISSIONS.STAGE_LEGAL_REVIEW, + name: 'Legal Review', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Legal Review stage' + }, + { + code: PERMISSIONS.STAGE_FINANCIAL_REVIEW, + name: 'Financial Review', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Financial Review stage' + }, + { + code: PERMISSIONS.STAGE_FINAL_APPROVAL, + name: 'Final Approval', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Final Approval stage' + }, + { + code: PERMISSIONS.STAGE_PAYMENT, + name: 'Payment Verification', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Payment Verification stage' + }, + { + code: PERMISSIONS.STAGE_ONBOARDING, + name: 'Onboarding', + category: PERMISSION_CATEGORIES.STAGE, + module: 'WORKFLOW', + type: 'STAGE_ACCESS', + action: 'ACCESS', + description: 'Access to Onboarding stage' + } +]; + +async function seedPermissions() { + console.log('🌱 Seeding permissions...'); + try { + await db.sequelize.authenticate(); + + for (const p of permissionsToSeed) { + await Permission.upsert({ + permissionCode: p.code, + permissionName: p.name, + permissionCategory: p.category, + module: p.module, + permissionType: p.type, + action: p.action, + description: p.description + }); + } + + console.log('✅ Permissions seeded successfully!'); + process.exit(0); + } catch (error) { + console.error('❌ Seeding failed:', error); + process.exit(1); + } +} + +seedPermissions(); diff --git a/scripts/seed-roles.ts b/scripts/seed-roles.ts new file mode 100644 index 0000000..ccc2ec5 --- /dev/null +++ b/scripts/seed-roles.ts @@ -0,0 +1,38 @@ +import 'dotenv/config'; +import db from '../src/database/models/index.js'; +import { ROLES } from '../src/common/config/constants.js'; + +const { Role } = db; + +const rolesToSeed = [ + { roleCode: ROLES.SUPER_ADMIN, roleName: 'Super Admin', category: 'ADMIN', description: 'Full system access' }, + { roleCode: ROLES.DD_ADMIN, roleName: 'DD Admin', category: 'ADMIN', description: 'Dealer Development Admin' }, + { roleCode: ROLES.DD_HEAD, roleName: 'DD Head', category: 'LEVEL_3', description: 'DD Head' }, + { roleCode: ROLES.DD_LEAD, roleName: 'DD Lead', category: 'LEVEL_2', description: 'DD Lead' }, + { roleCode: ROLES.DD_ZM, roleName: 'DD-ZM', category: 'LEVEL_1', description: 'DD Zone Manager' }, + { roleCode: ROLES.ZBH, roleName: 'ZBH', category: 'ZONAL', description: 'Zonal Business Head' }, + { roleCode: ROLES.RBM, roleName: 'RBM', category: 'REGIONAL', description: 'Regional Business Manager' }, + { roleCode: ROLES.FINANCE, roleName: 'Finance', category: 'DEPARTMENT', description: 'Finance Department' }, + { roleCode: ROLES.LEGAL_ADMIN, roleName: 'Legal Admin', category: 'DEPARTMENT', description: 'Legal Department' }, + { roleCode: ROLES.NBH, roleName: 'NBH', category: 'NATIONAL', description: 'National Business Head' }, + { roleCode: ROLES.DEALER, roleName: 'Dealer', category: 'EXTERNAL', description: 'Dealer Principal' } +]; + +async function seedRoles() { + console.log('🌱 Seeding roles...'); + try { + await db.sequelize.authenticate(); + + for (const r of rolesToSeed) { + await Role.upsert(r); + } + + console.log('✅ Roles seeded successfully!'); + process.exit(0); + } catch (error) { + console.error('❌ Seeding failed:', error); + process.exit(1); + } +} + +seedRoles(); diff --git a/scripts/seed-users.ts b/scripts/seed-users.ts new file mode 100644 index 0000000..5fd3b59 --- /dev/null +++ b/scripts/seed-users.ts @@ -0,0 +1,52 @@ +import 'dotenv/config'; +import bcrypt from 'bcryptjs'; +import db from '../src/database/models/index.js'; +import { ROLES } from '../src/common/config/constants.js'; + +const { User } = db; + +async function seedUsers() { + console.log('🌱 Seeding users...'); + try { + await db.sequelize.authenticate(); + + const hashedPassword = await bcrypt.hash('Admin@123', 10); + + const usersToSeed = [ + { + email: 'admin@royalenfield.com', + fullName: 'Super Admin', + password: hashedPassword, + roleCode: ROLES.SUPER_ADMIN, + status: 'active' + }, + { + email: 'zm@royalenfield.com', + fullName: 'Zone Manager', + password: hashedPassword, + roleCode: ROLES.DD_ZM, + status: 'active' + }, + { + email: 'dealer@example.com', + fullName: 'Amit Sharma', + password: hashedPassword, + roleCode: ROLES.DEALER, + status: 'active', + isExternal: true + } + ]; + + for (const u of usersToSeed) { + await User.upsert(u); + } + + console.log('✅ Users seeded successfully!'); + process.exit(0); + } catch (error) { + console.error('❌ Seeding failed:', error); + process.exit(1); + } +} + +seedUsers(); diff --git a/scripts/seed.js b/scripts/seed.js deleted file mode 100644 index 8949fd5..0000000 --- a/scripts/seed.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Database Seeding Script - * Adds initial test data to the database - * - * Run: node scripts/seed.js - */ - -require('dotenv').config(); -const bcrypt = require('bcryptjs'); -const { query } = require('../config/database'); - -async function seedDatabase() { - console.log('🌱 Starting database seeding...\n'); - - try { - // 1. Seed regions - console.log('Adding regions...'); - const regions = ['East', 'West', 'North', 'South', 'Central']; - for (const region of regions) { - await query( - `INSERT INTO master_regions (region_name) VALUES ($1) ON CONFLICT (region_name) DO NOTHING`, - [region] - ); - } - console.log('✅ Regions added\n'); - - // 2. Seed zones - console.log('Adding zones...'); - const zones = [ - { region: 'West', name: 'Mumbai Zone', code: 'MUM-01' }, - { region: 'West', name: 'Pune Zone', code: 'PUN-01' }, - { region: 'North', name: 'Delhi Zone', code: 'DEL-01' }, - { region: 'South', name: 'Bangalore Zone', code: 'BLR-01' }, - { region: 'East', name: 'Kolkata Zone', code: 'KOL-01' }, - ]; - - for (const zone of zones) { - const regionResult = await query('SELECT id FROM master_regions WHERE region_name = $1', [zone.region]); - if (regionResult.rows.length > 0) { - await query( - `INSERT INTO master_zones (region_id, zone_name, zone_code) - VALUES ($1, $2, $3) ON CONFLICT (zone_code) DO NOTHING`, - [regionResult.rows[0].id, zone.name, zone.code] - ); - } - } - console.log('✅ Zones added\n'); - - // 3. Seed users - console.log('Adding users...'); - const hashedPassword = await bcrypt.hash('Password@123', 10); - - const users = [ - { email: 'admin@royalenfield.com', name: 'Super Admin', role: 'Super Admin', region: null, zone: null }, - { email: 'ddlead@royalenfield.com', name: 'DD Lead', role: 'DD Lead', region: 'West', zone: null }, - { email: 'ddhead@royalenfield.com', name: 'DD Head', role: 'DD Head', region: 'West', zone: null }, - { email: 'nbh@royalenfield.com', name: 'NBH', role: 'NBH', region: null, zone: null }, - { email: 'finance@royalenfield.com', name: 'Finance Admin', role: 'Finance', region: null, zone: null }, - { email: 'legal@royalenfield.com', name: 'Legal Admin', role: 'Legal Admin', region: null, zone: null }, - { email: 'dd@royalenfield.com', name: 'DD Mumbai', role: 'DD', region: 'West', zone: 'Mumbai Zone' }, - { email: 'rbm@royalenfield.com', name: 'RBM West', role: 'RBM', region: 'West', zone: 'Mumbai Zone' }, - { email: 'zbh@royalenfield.com', name: 'ZBH Mumbai', role: 'ZBH', region: 'West', zone: 'Mumbai Zone' }, - { email: 'dealer@example.com', name: 'Amit Sharma', role: 'Dealer', region: 'West', zone: 'Mumbai Zone' }, - ]; - - for (const user of users) { - const result = await query( - `INSERT INTO users (email, password, full_name, role, region, zone, phone) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (email) DO NOTHING - RETURNING id`, - [user.email, hashedPassword, user.name, user.role, user.region, user.zone, '+91-9876543210'] - ); - - if (result.rows.length > 0) { - console.log(` Added: ${user.email} (${user.role})`); - } - } - console.log('✅ Users added\n'); - - // 4. Seed outlets for dealer - console.log('Adding outlets...'); - const dealerResult = await query('SELECT id FROM users WHERE email = $1', ['dealer@example.com']); - - if (dealerResult.rows.length > 0) { - const dealerId = dealerResult.rows[0].id; - - const outlets = [ - { - code: 'DL-MH-001', - name: 'Royal Enfield Mumbai', - type: 'Dealership', - address: 'Plot No. 45, Linking Road, Bandra West', - city: 'Mumbai', - state: 'Maharashtra', - lat: 19.0596, - lon: 72.8295 - }, - { - code: 'ST-MH-002', - name: 'Royal Enfield Andheri Studio', - type: 'Studio', - address: 'Shop 12, Phoenix Market City, Kurla', - city: 'Mumbai', - state: 'Maharashtra', - lat: 19.0822, - lon: 72.8912 - }, - { - code: 'DL-MH-003', - name: 'Royal Enfield Thane Dealership', - type: 'Dealership', - address: 'Eastern Express Highway, Thane West', - city: 'Thane', - state: 'Maharashtra', - lat: 19.2183, - lon: 72.9781 - }, - { - code: 'ST-MH-004', - name: 'Royal Enfield Pune Studio', - type: 'Studio', - address: 'FC Road, Deccan Gymkhana', - city: 'Pune', - state: 'Maharashtra', - lat: 18.5204, - lon: 73.8567 - } - ]; - - for (const outlet of outlets) { - await query( - `INSERT INTO outlets - (dealer_id, code, name, type, address, city, state, status, established_date, latitude, longitude) - VALUES ($1, $2, $3, $4, $5, $6, $7, 'Active', '2020-01-15', $8, $9) - ON CONFLICT (code) DO NOTHING`, - [dealerId, outlet.code, outlet.name, outlet.type, outlet.address, outlet.city, outlet.state, outlet.lat, outlet.lon] - ); - } - console.log('✅ Outlets added\n'); - } - - console.log('✅ Database seeding completed successfully!'); - console.log('\n📝 Test Credentials:'); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - console.log('Email: admin@royalenfield.com'); - console.log('Email: dealer@example.com'); - console.log('Email: finance@royalenfield.com'); - console.log('Email: ddlead@royalenfield.com'); - console.log('\nPassword (all users): Password@123'); - console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); - - process.exit(0); - } catch (error) { - console.error('❌ Seeding failed:', error); - process.exit(1); - } -} - -seedDatabase(); diff --git a/seeders/20240127-seed-geo-data.js b/seeders/20240127-seed-geo-data.js new file mode 100644 index 0000000..3f0944d --- /dev/null +++ b/seeders/20240127-seed-geo-data.js @@ -0,0 +1,314 @@ +'use strict'; +const { v4: uuidv4 } = require('uuid'); + +// Zone Definitions +const ZONES_DATA = [ + { code: 'NZ', name: 'North Zone', description: 'Northern India including NCR', states: ['JAMMU & KASHMIR', 'LADAKH', 'HIMACHAL PRADESH', 'PUNJAB', 'CHANDIGARH', 'UTTARAKHAND', 'HARYANA', 'DELHI', 'RAJASTHAN', 'UTTAR PRADESH'] }, + { code: 'SZ', name: 'South Zone', description: 'Southern India', states: ['KARNATAKA', 'TELANGANA', 'ANDHRA PRADESH', 'TAMIL NADU', 'KERALA', 'PUDUCHERRY', 'LAKSHADWEEP', 'ANDAMAN & NICOBAR'] }, + { code: 'EZ', name: 'East Zone', description: 'Eastern and North-Eastern India', states: ['BIHAR', 'JHARKHAND', 'ODISHA', 'WEST BENGAL', 'SIKKIM', 'ASSAM', 'MEGHALAYA', 'ARUNACHAL PRADESH', 'NAGALAND', 'MANIPUR', 'MIZORAM', 'TRIPURA'] }, + { code: 'WZ', name: 'West Zone', description: 'Western India', states: ['GUJARAT', 'MAHARASHTRA', 'GOA', 'DADRA & NAGAR HAVELI', 'DAMAN & DIU'] }, + { code: 'CZ', name: 'Central Zone', description: 'Central India', states: ['MADHYA PRADESH', 'CHHATTISGARH'] } +]; + +// Raw State Data +const STATES_DATA = [ + { id: 1, name: 'ANDHRA PRADESH', code: 'AP', country_id: 105 }, + { id: 2, name: 'ASSAM', code: 'AS', country_id: 105 }, + { id: 3, name: 'ARUNACHAL PRADESH', code: 'AR', country_id: 105 }, + { id: 4, name: 'BIHAR', code: 'BR', country_id: 105 }, + { id: 5, name: 'GUJARAT', code: 'GJ', country_id: 105 }, + { id: 6, name: 'HARYANA', code: 'HR', country_id: 105 }, + { id: 7, name: 'HIMACHAL PRADESH', code: 'HP', country_id: 105 }, + { id: 8, name: 'JAMMU & KASHMIR', code: 'JK', country_id: 105 }, + { id: 9, name: 'KARNATAKA', code: 'KA', country_id: 105 }, + { id: 10, name: 'KERALA', code: 'KL', country_id: 105 }, + { id: 11, name: 'MADHYA PRADESH', code: 'MP', country_id: 105 }, + { id: 12, name: 'MAHARASHTRA', code: 'MH', country_id: 105 }, + { id: 13, name: 'MANIPUR', code: 'MN', country_id: 105 }, + { id: 14, name: 'MEGHALAYA', code: 'ML', country_id: 105 }, + { id: 15, name: 'MIZORAM', code: 'MZ', country_id: 105 }, + { id: 16, name: 'NAGALAND', code: 'NL', country_id: 105 }, + { id: 17, name: 'ODISHA', code: 'OD', country_id: 105 }, + { id: 18, name: 'PUNJAB', code: 'PB', country_id: 105 }, + { id: 19, name: 'RAJASTHAN', code: 'RJ', country_id: 105 }, + { id: 20, name: 'SIKKIM', code: 'SK', country_id: 105 }, + { id: 21, name: 'TAMIL NADU', code: 'TN', country_id: 105 }, + { id: 22, name: 'TRIPURA', code: 'TR', country_id: 105 }, + { id: 23, name: 'UTTAR PRADESH', code: 'UP', country_id: 105 }, + { id: 24, name: 'WEST BENGAL', code: 'WB', country_id: 105 }, + { id: 25, name: 'DELHI', code: 'DL', country_id: 105 }, + { id: 26, name: 'GOA', code: 'GA', country_id: 105 }, + { id: 27, name: 'PUDUCHERRY', code: 'PY', country_id: 105 }, + { id: 28, name: 'LAKSHADWEEP', code: 'LD', country_id: 105 }, + { id: 29, name: 'DAMAN & DIU', code: 'DD', country_id: 105 }, + { id: 30, name: 'DADRA & NAGAR HAVELI', code: 'DN', country_id: 105 }, + { id: 31, name: 'CHANDIGARH', code: 'CH', country_id: 105 }, + { id: 32, name: 'ANDAMAN & NICOBAR', code: 'AN', country_id: 105 }, + { id: 33, name: 'UTTARAKHAND', code: 'UK', country_id: 105 }, + { id: 34, name: 'JHARKHAND', code: 'JH', country_id: 105 }, + { id: 35, name: 'CHHATTISGARH', code: 'CG', country_id: 105 }, + { id: 36, name: 'TELANGANA', code: 'TG', country_id: 105 }, + { id: 37, name: 'LADAKH', code: 'LA', country_id: 105 } +]; + +// Raw City Data +const CITIES_DATA = [ + // Bihar (ID 4) + { name: 'Araria', state_id: 4 }, { name: 'Arwal', state_id: 4 }, { name: 'Aurangabad', state_id: 4 }, { name: 'Banka', state_id: 4 }, { name: 'Begusarai', state_id: 4 }, { name: 'Bhagalpur', state_id: 4 }, { name: 'Bhojpur', state_id: 4 }, + { name: 'Buxar', state_id: 4 }, { name: 'Darbhanga', state_id: 4 }, { name: 'East Champaran', state_id: 4 }, { name: 'Gaya', state_id: 4 }, { name: 'Gopalganj', state_id: 4 }, { name: 'Jamui', state_id: 4 }, { name: 'Jehanabad', state_id: 4 }, + { name: 'Kaimur', state_id: 4 }, { name: 'Katihar', state_id: 4 }, { name: 'Khagaria', state_id: 4 }, { name: 'Kishanganj', state_id: 4 }, { name: 'Lakhisarai', state_id: 4 }, { name: 'Madhepura', state_id: 4 }, { name: 'Madhubani', state_id: 4 }, + { name: 'Munger', state_id: 4 }, { name: 'Muzaffarpur', state_id: 4 }, { name: 'Nalanda', state_id: 4 }, { name: 'Nawada', state_id: 4 }, { name: 'Patna', state_id: 4 }, { name: 'Purnia', state_id: 4 }, { name: 'Rohtas', state_id: 4 }, + { name: 'Saharsa', state_id: 4 }, { name: 'Samastipur', state_id: 4 }, { name: 'Saran', state_id: 4 }, { name: 'Sheikhpura', state_id: 4 }, { name: 'Sheohar', state_id: 4 }, { name: 'Sitamarhi', state_id: 4 }, { name: 'Siwan', state_id: 4 }, + { name: 'Supaul', state_id: 4 }, { name: 'Vaishali', state_id: 4 }, { name: 'West Champaran', state_id: 4 }, + // Chhattisgarh (ID 35) + { name: 'Balod', state_id: 35 }, { name: 'Baloda Bazar', state_id: 35 }, { name: 'Balrampur', state_id: 35 }, { name: 'Bastar', state_id: 35 }, { name: 'Bemetara', state_id: 35 }, { name: 'Bijapur', state_id: 35 }, { name: 'Bilaspur', state_id: 35 }, + { name: 'Dantewada', state_id: 35 }, { name: 'Dhamtari', state_id: 35 }, { name: 'Durg', state_id: 35 }, { name: 'Gariaband', state_id: 35 }, { name: 'Gaurela-Pendra-Marwahi', state_id: 35 }, { name: 'Janjgir-Champa', state_id: 35 }, { name: 'Jashpur', state_id: 35 }, + { name: 'Kabirdham', state_id: 35 }, { name: 'Kanker', state_id: 35 }, { name: 'Khairagarh-Chhuikhadan-Gandai', state_id: 35 }, { name: 'Kondagaon', state_id: 35 }, { name: 'Korba', state_id: 35 }, { name: 'Koriya', state_id: 35 }, { name: 'Mahasamund', state_id: 35 }, + { name: 'Manendragarh-Chirmiri-Bharatpur', state_id: 35 }, { name: 'Mohla-Manpur-Ambagarh Chowki', state_id: 35 }, { name: 'Mungeli', state_id: 35 }, { name: 'Narayanpur', state_id: 35 }, { name: 'Raigarh', state_id: 35 }, { name: 'Raipur', state_id: 35 }, + { name: 'Rajnandgaon', state_id: 35 }, { name: 'Sakti', state_id: 35 }, { name: 'Sarangarh-Bilaigarh', state_id: 35 }, { name: 'Sukma', state_id: 35 }, { name: 'Surajpur', state_id: 35 }, { name: 'Surguja', state_id: 35 }, + { name: 'Diu', state_id: 29 }, { name: 'Daman', state_id: 29 }, { name: 'Central Delhi', state_id: 25 }, { name: 'East Delhi', state_id: 25 }, + { name: 'New Delhi', state_id: 25 }, { name: 'North Delhi', state_id: 25 }, { name: 'North East Delhi', state_id: 25 }, { name: 'North West Delhi', state_id: 25 }, { name: 'South Delhi', state_id: 25 }, { name: 'South West Delhi', state_id: 25 }, + { name: 'West Delhi', state_id: 25 }, { name: 'North Goa', state_id: 26 }, { name: 'South Goa', state_id: 26 }, + // Gujarat (ID 5) + { name: 'Ahmedabad', state_id: 5 }, { name: 'Amreli', state_id: 5 }, { name: 'Anand', state_id: 5 }, { name: 'Aravalli', state_id: 5 }, { name: 'Banaskantha', state_id: 5 }, { name: 'Bharuch', state_id: 5 }, { name: 'Bhavnagar', state_id: 5 }, + { name: 'Botad', state_id: 5 }, { name: 'Chhota Udaipur', state_id: 5 }, { name: 'Dahod', state_id: 5 }, { name: 'Dang', state_id: 5 }, { name: 'Devbhoomi Dwarka', state_id: 5 }, { name: 'Gandhinagar', state_id: 5 }, { name: 'Gir Somnath', state_id: 5 }, + { name: 'Jamnagar', state_id: 5 }, { name: 'Junagadh', state_id: 5 }, { name: 'Kheda', state_id: 5 }, { name: 'Kutch', state_id: 5 }, { name: 'Mahisagar', state_id: 5 }, { name: 'Mehsana', state_id: 5 }, { name: 'Morbi', state_id: 5 }, + { name: 'Narmada', state_id: 5 }, { name: 'Navsari', state_id: 5 }, { name: 'Panchmahal', state_id: 5 }, { name: 'Patan', state_id: 5 }, { name: 'Porbandar', state_id: 5 }, { name: 'Rajkot', state_id: 5 }, { name: 'Sabarkantha', state_id: 5 }, + { name: 'Surat', state_id: 5 }, { name: 'Surendranagar', state_id: 5 }, { name: 'Tapi', state_id: 5 }, { name: 'Vadodara', state_id: 5 }, { name: 'Valsad', state_id: 5 }, + // Haryana (ID 6) + { name: 'Ambala', state_id: 6 }, { name: 'Bhiwani', state_id: 6 }, { name: 'Charkhi Dadri', state_id: 6 }, { name: 'Faridabad', state_id: 6 }, { name: 'Fatehabad', state_id: 6 }, { name: 'Gurugram', state_id: 6 }, { name: 'Hisar', state_id: 6 }, + { name: 'Jhajjar', state_id: 6 }, { name: 'Jind', state_id: 6 }, { name: 'Kaithal', state_id: 6 }, { name: 'Karnal', state_id: 6 }, { name: 'Kurukshetra', state_id: 6 }, { name: 'Mahendragarh', state_id: 6 }, { name: 'Nuh', state_id: 6 }, + { name: 'Palwal', state_id: 6 }, { name: 'Panchkula', state_id: 6 }, { name: 'Panipat', state_id: 6 }, { name: 'Rewari', state_id: 6 }, { name: 'Rohtak', state_id: 6 }, { name: 'Sirsa', state_id: 6 }, { name: 'Sonipat', state_id: 6 }, + { name: 'Yamunanagar', state_id: 6 }, + { name: 'Bilaspur', state_id: 7 }, { name: 'Chamba', state_id: 7 }, { name: 'Hamirpur', state_id: 7 }, { name: 'Kangra', state_id: 7 }, { name: 'Kinnaur', state_id: 7 }, { name: 'Kulu', state_id: 7 }, { name: 'Lahaul and Spiti', state_id: 7 }, + { name: 'Mandi', state_id: 7 }, { name: 'Shimla', state_id: 7 }, { name: 'Sirmaur', state_id: 7 }, { name: 'Solan', state_id: 7 }, { name: 'Una', state_id: 7 }, { name: 'Anantnag', state_id: 8 }, { name: 'Badgam', state_id: 8 }, + { name: 'Bandipore', state_id: 8 }, { name: 'Baramula', state_id: 8 }, { name: 'Doda', state_id: 8 }, { name: 'Jammu', state_id: 8 }, { name: 'Kargil', state_id: 37 }, { name: 'Kathua', state_id: 8 }, { name: 'Kupwara', state_id: 8 }, + { name: 'Leh', state_id: 37 }, { name: 'Poonch', state_id: 8 }, { name: 'Pulwama', state_id: 8 }, { name: 'Rajauri', state_id: 8 }, { name: 'Srinagar', state_id: 8 }, { name: 'Samba', state_id: 8 }, { name: 'Udhampur', state_id: 8 }, + { name: 'Bokaro', state_id: 34 }, { name: 'Chatra', state_id: 34 }, { name: 'Deoghar', state_id: 34 }, { name: 'Dhanbad', state_id: 34 }, { name: 'Dumka', state_id: 34 }, { name: 'Purba Singhbhum', state_id: 34 }, { name: 'Garhwa', state_id: 34 }, + { name: 'Giridih', state_id: 34 }, { name: 'Godda', state_id: 34 }, { name: 'Gumla', state_id: 34 }, { name: 'Hazaribagh', state_id: 34 }, { name: 'Koderma', state_id: 34 }, { name: 'Lohardaga', state_id: 34 }, { name: 'Pakur', state_id: 34 }, + { name: 'Palamu', state_id: 34 }, { name: 'Ranchi', state_id: 34 }, { name: 'Sahibganj', state_id: 34 }, { name: 'Seraikela and Kharsawan', state_id: 34 }, { name: 'Pashchim Singhbhum', state_id: 34 }, { name: 'Ramgarh', state_id: 34 }, + { name: 'Bidar', state_id: 9 }, { name: 'Belgaum', state_id: 9 }, { name: 'Bijapur', state_id: 9 }, { name: 'Bagalkot', state_id: 9 }, { name: 'Bellary', state_id: 9 }, { name: 'Bangalore Rural District', state_id: 9 }, + { name: 'Bangalore Urban District', state_id: 9 }, { name: 'Chamarajnagar', state_id: 9 }, { name: 'Chikmagalur', state_id: 9 }, { name: 'Chitradurga', state_id: 9 }, { name: 'Davanagere', state_id: 9 }, { name: 'Dharwad', state_id: 9 }, + { name: 'Dakshina Kannada', state_id: 9 }, { name: 'Gadag', state_id: 9 }, { name: 'Gulbarga', state_id: 9 }, { name: 'Hassan', state_id: 9 }, { name: 'Haveri District', state_id: 9 }, { name: 'Kodagu', state_id: 9 }, { name: 'Kolar', state_id: 9 }, + { name: 'Koppal', state_id: 9 }, { name: 'Mandya', state_id: 9 }, { name: 'Mysore', state_id: 9 }, { name: 'Raichur', state_id: 9 }, { name: 'Shimoga', state_id: 9 }, { name: 'Tumkur', state_id: 9 }, { name: 'Udupi', state_id: 9 }, + { name: 'Uttara Kannada', state_id: 9 }, { name: 'Ramanagara', state_id: 9 }, { name: 'Chikballapur', state_id: 9 }, { name: 'Yadagiri', state_id: 9 }, { name: 'Vijayanagara', state_id: 9 }, + { name: 'Alappuzha', state_id: 10 }, { name: 'Ernakulam', state_id: 10 }, + { name: 'Idukki', state_id: 10 }, { name: 'Kollam', state_id: 10 }, { name: 'Kannur', state_id: 10 }, { name: 'Kasaragod', state_id: 10 }, { name: 'Kottayam', state_id: 10 }, { name: 'Kozhikode', state_id: 10 }, { name: 'Malappuram', state_id: 10 }, + { name: 'Thrissur', state_id: 10 }, { name: 'Thiruvananthapuram', state_id: 10 }, { name: 'Wayanad', state_id: 10 }, + // Madhya Pradesh (ID 11) + { name: 'Agar Malwa', state_id: 11 }, { name: 'Alirajpur', state_id: 11 }, { name: 'Anuppur', state_id: 11 }, { name: 'Ashok Nagar', state_id: 11 }, { name: 'Balaghat', state_id: 11 }, { name: 'Barwani', state_id: 11 }, { name: 'Betul', state_id: 11 }, + { name: 'Bhind', state_id: 11 }, { name: 'Bhopal', state_id: 11 }, { name: 'Burhanpur', state_id: 11 }, { name: 'Chhatarpur', state_id: 11 }, { name: 'Chhindwara', state_id: 11 }, { name: 'Damoh', state_id: 11 }, { name: 'Datia', state_id: 11 }, + { name: 'Dewas', state_id: 11 }, { name: 'Dhar', state_id: 11 }, { name: 'Dindori', state_id: 11 }, { name: 'Guna', state_id: 11 }, { name: 'Gwalior', state_id: 11 }, { name: 'Harda', state_id: 11 }, { name: 'Hoshangabad', state_id: 11 }, // Narmadapuram + { name: 'Indore', state_id: 11 }, { name: 'Jabalpur', state_id: 11 }, { name: 'Jhabua', state_id: 11 }, { name: 'Katni', state_id: 11 }, { name: 'Khandwa', state_id: 11 }, { name: 'Khargone', state_id: 11 }, { name: 'Maihar', state_id: 11 }, + { name: 'Mandla', state_id: 11 }, { name: 'Mandsaur', state_id: 11 }, { name: 'Mauganj', state_id: 11 }, { name: 'Morena', state_id: 11 }, { name: 'Narsinghpur', state_id: 11 }, { name: 'Neemuch', state_id: 11 }, { name: 'Niwari', state_id: 11 }, + { name: 'Pandhurna', state_id: 11 }, { name: 'Panna', state_id: 11 }, { name: 'Raisen', state_id: 11 }, { name: 'Rajgarh', state_id: 11 }, { name: 'Ratlam', state_id: 11 }, { name: 'Rewa', state_id: 11 }, { name: 'Sagar', state_id: 11 }, + { name: 'Satna', state_id: 11 }, { name: 'Sehore', state_id: 11 }, { name: 'Seoni', state_id: 11 }, { name: 'Shahdol', state_id: 11 }, { name: 'Shajapur', state_id: 11 }, { name: 'Sheopur', state_id: 11 }, { name: 'Shivpuri', state_id: 11 }, + { name: 'Sidhi', state_id: 11 }, { name: 'Singrauli', state_id: 11 }, { name: 'Tikamgarh', state_id: 11 }, { name: 'Ujjain', state_id: 11 }, { name: 'Umaria', state_id: 11 }, { name: 'Vidisha', state_id: 11 }, + { name: 'Ahmednagar', state_id: 12 }, { name: 'Akola', state_id: 12 }, { name: 'Amrawati', state_id: 12 }, { name: 'Aurangabad', state_id: 12 }, { name: 'Bhandara', state_id: 12 }, { name: 'Beed', state_id: 12 }, { name: 'Buldhana', state_id: 12 }, + { name: 'Chandrapur', state_id: 12 }, { name: 'Dhule', state_id: 12 }, { name: 'Gadchiroli', state_id: 12 }, { name: 'Gondiya', state_id: 12 }, { name: 'Hingoli', state_id: 12 }, { name: 'Jalgaon', state_id: 12 }, { name: 'Jalna', state_id: 12 }, + { name: 'Kolhapur', state_id: 12 }, { name: 'Latur', state_id: 12 }, { name: 'Mumbai City', state_id: 12 }, { name: 'Mumbai suburban', state_id: 12 }, { name: 'Nandurbar', state_id: 12 }, { name: 'Nanded', state_id: 12 }, { name: 'Nagpur', state_id: 12 }, + { name: 'Nashik', state_id: 12 }, { name: 'Osmanabad', state_id: 12 }, { name: 'Parbhani', state_id: 12 }, { name: 'Pune', state_id: 12 }, { name: 'Raigad', state_id: 12 }, { name: 'Ratnagiri', state_id: 12 }, { name: 'Sindhudurg', state_id: 12 }, + { name: 'Sangli', state_id: 12 }, { name: 'Solapur', state_id: 12 }, { name: 'Satara', state_id: 12 }, { name: 'Thane', state_id: 12 }, { name: 'Wardha', state_id: 12 }, { name: 'Washim', state_id: 12 }, { name: 'Yavatmal', state_id: 12 }, + // Manipur (ID 13) + { name: 'Bishnupur', state_id: 13 }, { name: 'Chandel', state_id: 13 }, { name: 'Churachandpur', state_id: 13 }, { name: 'Imphal East', state_id: 13 }, { name: 'Imphal West', state_id: 13 }, { name: 'Jiribam', state_id: 13 }, { name: 'Kakching', state_id: 13 }, + { name: 'Kamjong', state_id: 13 }, { name: 'Kangpokpi', state_id: 13 }, { name: 'Noney', state_id: 13 }, { name: 'Pherzawl', state_id: 13 }, { name: 'Senapati', state_id: 13 }, { name: 'Tamenglong', state_id: 13 }, { name: 'Tengnoupal', state_id: 13 }, + { name: 'Thoubal', state_id: 13 }, { name: 'Ukhrul', state_id: 13 }, + // Meghalaya (ID 14) + { name: 'East Garo Hills', state_id: 14 }, { name: 'East Jaintia Hills', state_id: 14 }, { name: 'East Khasi Hills', state_id: 14 }, { name: 'Eastern West Khasi Hills', state_id: 14 }, { name: 'North Garo Hills', state_id: 14 }, { name: 'Ri-Bhoi', state_id: 14 }, + { name: 'South Garo Hills', state_id: 14 }, { name: 'South West Garo Hills', state_id: 14 }, { name: 'South West Khasi Hills', state_id: 14 }, { name: 'West Garo Hills', state_id: 14 }, { name: 'West Jaintia Hills', state_id: 14 }, { name: 'West Khasi Hills', state_id: 14 }, + // Mizoram (ID 15) + { name: 'Aizawl', state_id: 15 }, { name: 'Champhai', state_id: 15 }, { name: 'Hnahthial', state_id: 15 }, { name: 'Khawzawl', state_id: 15 }, { name: 'Kolasib', state_id: 15 }, { name: 'Lawngtlai', state_id: 15 }, { name: 'Lunglei', state_id: 15 }, + { name: 'Mamit', state_id: 15 }, { name: 'Saiha', state_id: 15 }, { name: 'Saitual', state_id: 15 }, { name: 'Serchhip', state_id: 15 }, + // Nagaland (ID 16) + { name: 'Chumoukedima', state_id: 16 }, { name: 'Dimapur', state_id: 16 }, { name: 'Kiphire', state_id: 16 }, { name: 'Kohima', state_id: 16 }, { name: 'Longleng', state_id: 16 }, { name: 'Mokokchung', state_id: 16 }, { name: 'Mon', state_id: 16 }, + { name: 'Niuland', state_id: 16 }, { name: 'Noklak', state_id: 16 }, { name: 'Peren', state_id: 16 }, { name: 'Phek', state_id: 16 }, { name: 'Shamator', state_id: 16 }, { name: 'Tseminyu', state_id: 16 }, { name: 'Tuensang', state_id: 16 }, + { name: 'Wokha', state_id: 16 }, { name: 'Zunheboto', state_id: 16 }, + { name: 'Angul', state_id: 17 }, { name: 'Boudh', state_id: 17 }, { name: 'Bhadrak', state_id: 17 }, { name: 'Bolangir', state_id: 17 }, { name: 'Bargarh', state_id: 17 }, { name: 'Baleswar', state_id: 17 }, { name: 'Cuttack', state_id: 17 }, + { name: 'Debagarh', state_id: 17 }, { name: 'Dhenkanal', state_id: 17 }, { name: 'Ganjam', state_id: 17 }, { name: 'Gajapati', state_id: 17 }, { name: 'Jharsuguda', state_id: 17 }, { name: 'Jajapur', state_id: 17 }, { name: 'Jagatsinghpur', state_id: 17 }, + { name: 'Khordha', state_id: 17 }, { name: 'Kendujhar', state_id: 17 }, { name: 'Kalahandi', state_id: 17 }, { name: 'Kandhamal', state_id: 17 }, { name: 'Koraput', state_id: 17 }, { name: 'Kendrapara', state_id: 17 }, { name: 'Malkangiri', state_id: 17 }, + { name: 'Mayurbhanj', state_id: 17 }, { name: 'Nabarangpur', state_id: 17 }, { name: 'Nuapada', state_id: 17 }, { name: 'Nayagarh', state_id: 17 }, { name: 'Puri', state_id: 17 }, { name: 'Rayagada', state_id: 17 }, { name: 'Sambalpur', state_id: 17 }, + { name: 'Subarnapur', state_id: 17 }, { name: 'Sundargarh', state_id: 17 }, { name: 'Karaikal', state_id: 27 }, { name: 'Mahe', state_id: 27 }, { name: 'Puducherry', state_id: 27 }, { name: 'Yanam', state_id: 27 }, + // Punjab (ID 18) + { name: 'Amritsar', state_id: 18 }, { name: 'Barnala', state_id: 18 }, { name: 'Bathinda', state_id: 18 }, { name: 'Faridkot', state_id: 18 }, { name: 'Fatehgarh Sahib', state_id: 18 }, { name: 'Fazilka', state_id: 18 }, { name: 'Ferozepur', state_id: 18 }, + { name: 'Gurdaspur', state_id: 18 }, { name: 'Hoshiarpur', state_id: 18 }, { name: 'Jalandhar', state_id: 18 }, { name: 'Kapurthala', state_id: 18 }, { name: 'Ludhiana', state_id: 18 }, { name: 'Malerkotla', state_id: 18 }, { name: 'Mansa', state_id: 18 }, + { name: 'Moga', state_id: 18 }, { name: 'Mohali', state_id: 18 }, { name: 'Muktsar', state_id: 18 }, { name: 'Nawanshahr', state_id: 18 }, { name: 'Pathankot', state_id: 18 }, { name: 'Patiala', state_id: 18 }, { name: 'Rupnagar', state_id: 18 }, + { name: 'Sangrur', state_id: 18 }, { name: 'Tarn Taran', state_id: 18 }, + // Rajasthan (ID 19) + { name: 'Ajmer', state_id: 19 }, { name: 'Alwar', state_id: 19 }, { name: 'Balotra', state_id: 19 }, { name: 'Banswara', state_id: 19 }, { name: 'Baran', state_id: 19 }, { name: 'Barmer', state_id: 19 }, { name: 'Beawar', state_id: 19 }, + { name: 'Bharatpur', state_id: 19 }, { name: 'Bhilwara', state_id: 19 }, { name: 'Bikaner', state_id: 19 }, { name: 'Bundi', state_id: 19 }, { name: 'Chittorgarh', state_id: 19 }, { name: 'Churu', state_id: 19 }, { name: 'Dausa', state_id: 19 }, + { name: 'Deeg', state_id: 19 }, { name: 'Didwana-Kuchaman', state_id: 19 }, { name: 'Dholpur', state_id: 19 }, { name: 'Dungarpur', state_id: 19 }, { name: 'Hanumangarh', state_id: 19 }, { name: 'Jaipur', state_id: 19 }, { name: 'Jaisalmer', state_id: 19 }, + { name: 'Jalore', state_id: 19 }, { name: 'Jhalawar', state_id: 19 }, { name: 'Jhunjhunu', state_id: 19 }, { name: 'Jodhpur', state_id: 19 }, { name: 'Karauli', state_id: 19 }, { name: 'Khairthal-Tijara', state_id: 19 }, { name: 'Kota', state_id: 19 }, + { name: 'Kotputli-Behror', state_id: 19 }, { name: 'Nagaur', state_id: 19 }, { name: 'Pali', state_id: 19 }, { name: 'Phalodi', state_id: 19 }, { name: 'Pratapgarh', state_id: 19 }, { name: 'Rajsamand', state_id: 19 }, { name: 'Salumbar', state_id: 19 }, + { name: 'Sawai Madhopur', state_id: 19 }, { name: 'Sikar', state_id: 19 }, { name: 'Sirohi', state_id: 19 }, { name: 'Sri Ganganagar', state_id: 19 }, { name: 'Tonk', state_id: 19 }, { name: 'Udaipur', state_id: 19 }, + // Sikkim (ID 20) + { name: 'Gangtok', state_id: 20 }, { name: 'Gyalshing', state_id: 20 }, { name: 'Mangan', state_id: 20 }, { name: 'Namchi', state_id: 20 }, { name: 'Pakyong', state_id: 20 }, { name: 'Soreng', state_id: 20 }, + // Tamil Nadu (ID 21) + { name: 'Ariyalur', state_id: 21 }, { name: 'Chengalpattu', state_id: 21 }, { name: 'Chennai', state_id: 21 }, { name: 'Coimbatore', state_id: 21 }, { name: 'Cuddalore', state_id: 21 }, { name: 'Dharmapuri', state_id: 21 }, { name: 'Dindigul', state_id: 21 }, + { name: 'Erode', state_id: 21 }, { name: 'Kallakurichi', state_id: 21 }, { name: 'Kanchipuram', state_id: 21 }, { name: 'Kanyakumari', state_id: 21 }, { name: 'Karur', state_id: 21 }, { name: 'Krishnagiri', state_id: 21 }, { name: 'Madurai', state_id: 21 }, + { name: 'Mayiladuthurai', state_id: 21 }, { name: 'Nagapattinam', state_id: 21 }, { name: 'Namakkal', state_id: 21 }, { name: 'Nilgiris', state_id: 21 }, { name: 'Perambalur', state_id: 21 }, { name: 'Pudukkottai', state_id: 21 }, { name: 'Ramanathapuram', state_id: 21 }, + { name: 'Ranipet', state_id: 21 }, { name: 'Salem', state_id: 21 }, { name: 'Sivagangai', state_id: 21 }, { name: 'Tenkasi', state_id: 21 }, { name: 'Thanjavur', state_id: 21 }, { name: 'Theni', state_id: 21 }, { name: 'Thoothukudi', state_id: 21 }, + { name: 'Tiruchirappalli', state_id: 21 }, { name: 'Tirunelveli', state_id: 21 }, { name: 'Tirupattur', state_id: 21 }, { name: 'Tiruppur', state_id: 21 }, { name: 'Tiruvallur', state_id: 21 }, { name: 'Tiruvannamalai', state_id: 21 }, + { name: 'Tiruvarur', state_id: 21 }, { name: 'Vellore', state_id: 21 }, { name: 'Viluppuram', state_id: 21 }, { name: 'Virudhunagar', state_id: 21 }, + { name: 'Bageshwar', state_id: 33 }, { name: 'Chamoli', state_id: 33 }, { name: 'Champawat', state_id: 33 }, { name: 'Dehradun', state_id: 33 }, { name: 'Haridwar', state_id: 33 }, { name: 'Nainital', state_id: 33 }, { name: 'Pauri Garhwal', state_id: 33 }, + { name: 'Pithoragharh', state_id: 33 }, { name: 'Rudraprayag', state_id: 33 }, { name: 'Tehri Garhwal', state_id: 33 }, { name: 'Udham Singh Nagar', state_id: 33 }, { name: 'Uttarkashi', state_id: 33 }, { name: 'Agra', state_id: 23 }, + // Uttar Pradesh (ID 23) + { name: 'Agra', state_id: 23 }, { name: 'Aligarh', state_id: 23 }, { name: 'Ambedkar Nagar', state_id: 23 }, { name: 'Amethi', state_id: 23 }, { name: 'Amroha', state_id: 23 }, { name: 'Auraiya', state_id: 23 }, { name: 'Ayodhya', state_id: 23 }, + { name: 'Azamgarh', state_id: 23 }, { name: 'Badaun', state_id: 23 }, { name: 'Baghpat', state_id: 23 }, { name: 'Bahraich', state_id: 23 }, { name: 'Ballia', state_id: 23 }, { name: 'Balrampur', state_id: 23 }, { name: 'Banda', state_id: 23 }, + { name: 'Barabanki', state_id: 23 }, { name: 'Bareilly', state_id: 23 }, { name: 'Basti', state_id: 23 }, { name: 'Bhadohi', state_id: 23 }, { name: 'Bijnor', state_id: 23 }, { name: 'Bulandshahr', state_id: 23 }, { name: 'Chandauli', state_id: 23 }, + { name: 'Chitrakoot', state_id: 23 }, { name: 'Deoria', state_id: 23 }, { name: 'Etah', state_id: 23 }, { name: 'Etawah', state_id: 23 }, { name: 'Farrukhabad', state_id: 23 }, { name: 'Fatehpur', state_id: 23 }, { name: 'Firozabad', state_id: 23 }, + { name: 'Gautam Buddha Nagar', state_id: 23 }, { name: 'Ghaziabad', state_id: 23 }, { name: 'Ghazipur', state_id: 23 }, { name: 'Gonda', state_id: 23 }, { name: 'Gorakhpur', state_id: 23 }, { name: 'Hamirpur', state_id: 23 }, { name: 'Hapur', state_id: 23 }, + { name: 'Hardoi', state_id: 23 }, { name: 'Hathras', state_id: 23 }, { name: 'Jalaun', state_id: 23 }, { name: 'Jaunpur', state_id: 23 }, { name: 'Jhansi', state_id: 23 }, { name: 'Kannauj', state_id: 23 }, { name: 'Kanpur Dehat', state_id: 23 }, + { name: 'Kanpur Nagar', state_id: 23 }, { name: 'Kasganj', state_id: 23 }, { name: 'Kaushambi', state_id: 23 }, { name: 'Kushinagar', state_id: 23 }, { name: 'Lakhimpur Kheri', state_id: 23 }, { name: 'Lalitpur', state_id: 23 }, { name: 'Lucknow', state_id: 23 }, + { name: 'Maharajganj', state_id: 23 }, { name: 'Mahoba', state_id: 23 }, { name: 'Mainpuri', state_id: 23 }, { name: 'Mathura', state_id: 23 }, { name: 'Mau', state_id: 23 }, { name: 'Meerut', state_id: 23 }, { name: 'Mirzapur', state_id: 23 }, + { name: 'Moradabad', state_id: 23 }, { name: 'Muzaffarnagar', state_id: 23 }, { name: 'Pilibhit', state_id: 23 }, { name: 'Pratapgarh', state_id: 23 }, { name: 'Prayagraj', state_id: 23 }, { name: 'Rae Bareli', state_id: 23 }, { name: 'Rampur', state_id: 23 }, + { name: 'Saharanpur', state_id: 23 }, { name: 'Sambhal', state_id: 23 }, { name: 'Sant Kabir Nagar', state_id: 23 }, { name: 'Shahjahanpur', state_id: 23 }, { name: 'Shamli', state_id: 23 }, { name: 'Shravasti', state_id: 23 }, { name: 'Siddharthnagar', state_id: 23 }, + { name: 'Sitapur', state_id: 23 }, { name: 'Sonbhadra', state_id: 23 }, { name: 'Sultanpur', state_id: 23 }, { name: 'Unnao', state_id: 23 }, { name: 'Varanasi', state_id: 23 }, + // Andhra Pradesh (ID 1) + { name: 'Anantapur', state_id: 1 }, { name: 'Chittoor', state_id: 1 }, { name: 'East Godavari', state_id: 1 }, { name: 'Guntur', state_id: 1 }, { name: 'Krishna', state_id: 1 }, + { name: 'Kurnool', state_id: 1 }, { name: 'Nellore', state_id: 1 }, { name: 'Prakasam', state_id: 1 }, { name: 'Srikakulam', state_id: 1 }, { name: 'Visakhapatnam', state_id: 1 }, + { name: 'Vizianagaram', state_id: 1 }, { name: 'West Godavari', state_id: 1 }, { name: 'YSR Kadapa', state_id: 1 }, + // New AP Districts + { name: 'Parvathipuram Manyam', state_id: 1 }, { name: 'Alluri Sitharama Raju', state_id: 1 }, { name: 'Anakapalli', state_id: 1 }, { name: 'Kakinada', state_id: 1 }, { name: 'Konaseema', state_id: 1 }, + { name: 'Eluru', state_id: 1 }, { name: 'NTR', state_id: 1 }, { name: 'Bapatla', state_id: 1 }, { name: 'Palnadu', state_id: 1 }, { name: 'Nandyal', state_id: 1 }, + { name: 'Sri Sathya Sai', state_id: 1 }, { name: 'Annamayya', state_id: 1 }, { name: 'Tirupati', state_id: 1 }, + // Telangana (ID 36) + { name: 'Adilabad', state_id: 36 }, { name: 'Bhadradri Kothagudem', state_id: 36 }, { name: 'Hyderabad', state_id: 36 }, { name: 'Jagtial', state_id: 36 }, { name: 'Jangaon', state_id: 36 }, + { name: 'Jayashankar Bhupalpally', state_id: 36 }, { name: 'Jogulamba Gadwal', state_id: 36 }, { name: 'Kamareddy', state_id: 36 }, { name: 'Karimnagar', state_id: 36 }, { name: 'Khammam', state_id: 36 }, + { name: 'Komaram Bheem Asifabad', state_id: 36 }, { name: 'Mahabubabad', state_id: 36 }, { name: 'Mahbubnagar', state_id: 36 }, { name: 'Mancherial', state_id: 36 }, { name: 'Medak', state_id: 36 }, + { name: 'Medchal-Malkajgiri', state_id: 36 }, { name: 'Nagarkurnool', state_id: 36 }, { name: 'Nalgonda', state_id: 36 }, { name: 'Nirmal', state_id: 36 }, { name: 'Nizamabad', state_id: 36 }, + { name: 'Peddapalli', state_id: 36 }, { name: 'Rajanna Sircilla', state_id: 36 }, { name: 'Rangareddy', state_id: 36 }, { name: 'Sangareddy', state_id: 36 }, { name: 'Siddipet', state_id: 36 }, + { name: 'Suryapet', state_id: 36 }, { name: 'Vikarabad', state_id: 36 }, { name: 'Wanaparthy', state_id: 36 }, { name: 'Warangal (Rural)', state_id: 36 }, { name: 'Warangal (Urban)', state_id: 36 }, + { name: 'Yadadri Bhuvanagiri', state_id: 36 }, { name: 'Mulugu', state_id: 36 }, { name: 'Narayanpet', state_id: 36 }, + // West Bengal (ID 24) + { name: 'Alipurduar', state_id: 24 }, { name: 'Bankura', state_id: 24 }, { name: 'Birbhum', state_id: 24 }, { name: 'Cooch Behar', state_id: 24 }, { name: 'Dakshin Dinajpur', state_id: 24 }, { name: 'Darjeeling', state_id: 24 }, { name: 'Hooghly', state_id: 24 }, + { name: 'Howrah', state_id: 24 }, { name: 'Jalpaiguri', state_id: 24 }, { name: 'Jhargram', state_id: 24 }, { name: 'Kalimpong', state_id: 24 }, { name: 'Kolkata', state_id: 24 }, { name: 'Malda', state_id: 24 }, { name: 'Murshidabad', state_id: 24 }, + { name: 'Nadia', state_id: 24 }, { name: 'North 24 Parganas', state_id: 24 }, { name: 'Paschim Bardhaman', state_id: 24 }, { name: 'Paschim Medinipur', state_id: 24 }, { name: 'Purba Bardhaman', state_id: 24 }, { name: 'Purba Medinipur', state_id: 24 }, + { name: 'Purulia', state_id: 24 }, { name: 'South 24 Parganas', state_id: 24 }, { name: 'Uttar Dinajpur', state_id: 24 }, + // Arunachal Pradesh (ID 3) + { name: 'Anjaw', state_id: 3 }, { name: 'Changlang', state_id: 3 }, { name: 'Dibang Valley', state_id: 3 }, { name: 'East Kameng', state_id: 3 }, { name: 'East Siang', state_id: 3 }, { name: 'Kamle', state_id: 3 }, { name: 'Kra Daadi', state_id: 3 }, + { name: 'Kurung Kumey', state_id: 3 }, { name: 'Lepa Rada', state_id: 3 }, { name: 'Lohit', state_id: 3 }, { name: 'Longding', state_id: 3 }, { name: 'Lower Dibang Valley', state_id: 3 }, { name: 'Lower Siang', state_id: 3 }, { name: 'Lower Subansiri', state_id: 3 }, + { name: 'Namsai', state_id: 3 }, { name: 'Pakke Kessang', state_id: 3 }, { name: 'Papum Pare', state_id: 3 }, { name: 'Shi Yomi', state_id: 3 }, { name: 'Siang', state_id: 3 }, { name: 'Tawang', state_id: 3 }, { name: 'Tirap', state_id: 3 }, + { name: 'Upper Siang', state_id: 3 }, { name: 'Upper Subansiri', state_id: 3 }, { name: 'West Kameng', state_id: 3 }, { name: 'West Siang', state_id: 3 }, { name: 'Itanagar Capital Complex', state_id: 3 }, { name: 'Bichom', state_id: 3 }, { name: 'Keyi Panyor', state_id: 3 }, + // Assam (ID 2) + { name: 'Baksa', state_id: 2 }, { name: 'Barpeta', state_id: 2 }, { name: 'Bongaigaon', state_id: 2 }, { name: 'Cachar', state_id: 2 }, { name: 'Charaideo', state_id: 2 }, { name: 'Chirang', state_id: 2 }, { name: 'Darrang', state_id: 2 }, + { name: 'Dhemaji', state_id: 2 }, { name: 'Dhubri', state_id: 2 }, { name: 'Dibrugarh', state_id: 2 }, { name: 'Dima Hasao', state_id: 2 }, { name: 'Goalpara', state_id: 2 }, { name: 'Golaghat', state_id: 2 }, { name: 'Hailakandi', state_id: 2 }, + { name: 'Jorhat', state_id: 2 }, { name: 'Kamrup', state_id: 2 }, { name: 'Kamrup Metropolitan', state_id: 2 }, { name: 'Karbi Anglong', state_id: 2 }, { name: 'Karimganj', state_id: 2 }, { name: 'Kokrajhar', state_id: 2 }, { name: 'Lakhimpur', state_id: 2 }, + { name: 'Majuli', state_id: 2 }, { name: 'Morigaon', state_id: 2 }, { name: 'Nagaon', state_id: 2 }, { name: 'Nalbari', state_id: 2 }, { name: 'Sivasagar', state_id: 2 }, { name: 'Sonitpur', state_id: 2 }, { name: 'South Salmara-Mankachar', state_id: 2 }, + { name: 'Tinsukia', state_id: 2 }, { name: 'Udalguri', state_id: 2 }, { name: 'West Karbi Anglong', state_id: 2 }, + // Tripura (ID 22) + { name: 'Dhalai', state_id: 22 }, { name: 'Gomati', state_id: 22 }, { name: 'Khowai', state_id: 22 }, { name: 'North Tripura', state_id: 22 }, { name: 'Sepahijala', state_id: 22 }, { name: 'South Tripura', state_id: 22 }, { name: 'Unakoti', state_id: 22 }, + { name: 'West Tripura', state_id: 22 } +]; + +module.exports = { + up: async (queryInterface, Sequelize) => { + try { + console.log('Starting migration...'); + + // 1. Create Zones and maintain a map for state lookup + const zoneMap = new Map(); // Name -> UUID + const zoneInserts = ZONES_DATA.map(zone => { + const id = uuidv4(); + zoneMap.set(zone.name, id); + return { + id: id, + zoneCode: zone.code, + zoneName: zone.name, + description: zone.description, + isActive: true, + createdAt: new Date(), + updatedAt: new Date() + }; + }); + + // Insert Zones + if (zoneInserts.length > 0) { + await queryInterface.bulkInsert('zones', zoneInserts); + console.log(`Inserted ${zoneInserts.length} zones`); + } + + // 2. Prepare States with Zone IDs + const stateMap = new Map(); // Legacy ID -> UUID (for city lookup) + const stateInserts = STATES_DATA.map(state => { + const id = uuidv4(); + // Store both ID and ZoneID for lookup + stateMap.set(state.id, { id: id, zoneId: null }); // Will update zoneId below + + // Find which Zone this state belongs to + let zoneId = null; + for (const z of ZONES_DATA) { + if (z.states.includes(state.name)) { + zoneId = zoneMap.get(z.name); + // Update the map with the found zoneId + stateMap.get(state.id).zoneId = zoneId; + break; + } + } + + if (!zoneId) { + console.warn(`Warning: State ${state.name} not mapped to any Zone`); + } + + return { + id: id, + stateName: state.name, + zoneId: zoneId, + isActive: true, + createdAt: new Date(), + updatedAt: new Date() + // code and country_id not present in State.ts (based on my viewing) + // Let's re-verify State.ts. + // State.ts: stateName, zoneId, isActive. NO code, NO country_id. + }; + }); + + // Insert States + if (stateInserts.length > 0) { + await queryInterface.bulkInsert('states', stateInserts); + console.log(`Inserted ${stateInserts.length} states`); + } + + // 3. Prepare Districts (Cities) using State Map + const districtInserts = CITIES_DATA.map(city => { + const stateData = stateMap.get(city.state_id); + if (!stateData) { + console.warn(`Warning: City ${city.name} refers to unknown State ID ${city.state_id}`); + return null; + } + const { id: stateUuid, zoneId } = stateData; + + return { + id: uuidv4(), + districtName: city.name, + districtName: city.name, + stateId: stateUuid, + zoneId: zoneId, + isActive: true, + createdAt: new Date(), + updatedAt: new Date() + }; + }).filter(d => d !== null); + + // Insert Districts + // Bulk insert in chunks to avoid packet size issues if list is huge (it is ~380 items which is fine, but good practice) + if (districtInserts.length > 0) { + await queryInterface.bulkInsert('districts', districtInserts); + console.log(`Inserted ${districtInserts.length} districts`); + } + + console.log('Migration completed successfully.'); + + } catch (error) { + console.error('Migration failed:', error); + throw error; + } + }, + + down: async (queryInterface, Sequelize) => { + // Delete in reverse order of foreign key dependency + // Delete in reverse order of foreign key dependency + await queryInterface.bulkDelete('districts', null, {}); + await queryInterface.bulkDelete('states', null, {}); + await queryInterface.bulkDelete('zones', null, {}); + } +}; diff --git a/services/auditService.js b/services/auditService.js deleted file mode 100644 index b196b72..0000000 --- a/services/auditService.js +++ /dev/null @@ -1,111 +0,0 @@ -const { query } = require('../config/database'); - -/** - * Log audit trail for all important actions - */ -const logAudit = async ({ userId, action, entityType, entityId, oldValue = null, newValue = null, ipAddress = null, userAgent = null }) => { - try { - await query( - `INSERT INTO audit_logs - (user_id, action, entity_type, entity_id, old_value, new_value, ip_address, user_agent) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, - [ - userId, - action, - entityType, - entityId, - oldValue ? JSON.stringify(oldValue) : null, - newValue ? JSON.stringify(newValue) : null, - ipAddress, - userAgent - ] - ); - - console.log(`Audit logged: ${action} by user ${userId}`); - } catch (error) { - console.error('Error logging audit:', error); - // Don't throw error - audit logging should not break the main flow - } -}; - -/** - * Get audit logs for an entity - */ -const getAuditLogs = async (entityType, entityId) => { - try { - const result = await query( - `SELECT al.*, u.full_name as user_name, u.email as user_email - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - WHERE al.entity_type = $1 AND al.entity_id = $2 - ORDER BY al.created_at DESC`, - [entityType, entityId] - ); - - return result.rows; - } catch (error) { - console.error('Error fetching audit logs:', error); - return []; - } -}; - -/** - * Get all audit logs with filters - */ -const getAllAuditLogs = async ({ userId, action, entityType, startDate, endDate, limit = 100 }) => { - try { - let queryText = ` - SELECT al.*, u.full_name as user_name, u.email as user_email - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id - WHERE 1=1 - `; - const params = []; - let paramCount = 1; - - if (userId) { - queryText += ` AND al.user_id = $${paramCount}`; - params.push(userId); - paramCount++; - } - - if (action) { - queryText += ` AND al.action = $${paramCount}`; - params.push(action); - paramCount++; - } - - if (entityType) { - queryText += ` AND al.entity_type = $${paramCount}`; - params.push(entityType); - paramCount++; - } - - if (startDate) { - queryText += ` AND al.created_at >= $${paramCount}`; - params.push(startDate); - paramCount++; - } - - if (endDate) { - queryText += ` AND al.created_at <= $${paramCount}`; - params.push(endDate); - paramCount++; - } - - queryText += ` ORDER BY al.created_at DESC LIMIT $${paramCount}`; - params.push(limit); - - const result = await query(queryText, params); - return result.rows; - } catch (error) { - console.error('Error fetching all audit logs:', error); - return []; - } -}; - -module.exports = { - logAudit, - getAuditLogs, - getAllAuditLogs -}; diff --git a/src/common/config/auth.ts b/src/common/config/auth.ts index bcbc1ac..e6096e1 100644 --- a/src/common/config/auth.ts +++ b/src/common/config/auth.ts @@ -9,9 +9,9 @@ export const generateToken = (user: any): string => { const payload: TokenPayload = { userId: user.id, email: user.email, - role: user.role, - region: user.region, - zone: user.zone + role: user.roleCode, + region: user.regionId, + zone: user.zoneId }; return jwt.sign(payload, JWT_SECRET, { diff --git a/src/common/config/constants.ts b/src/common/config/constants.ts index 9de482c..6dc62d5 100644 --- a/src/common/config/constants.ts +++ b/src/common/config/constants.ts @@ -157,6 +157,7 @@ export const AUDIT_ACTIONS = { APPROVED: 'APPROVED', REJECTED: 'REJECTED', DELETED: 'DELETED', + LOGIN: 'LOGIN', STAGE_CHANGED: 'STAGE_CHANGED', DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED', WORKNOTE_ADDED: 'WORKNOTE_ADDED' diff --git a/src/common/config/email.ts b/src/common/config/email.ts new file mode 100644 index 0000000..9da8d9e --- /dev/null +++ b/src/common/config/email.ts @@ -0,0 +1,25 @@ +import 'dotenv/config'; + +export interface EmailConfig { + host: string; + port: number; + secure: boolean; + auth: { + user: string | undefined; + pass: string | undefined; + }; + from: string; +} + +const config: EmailConfig = { + host: process.env.EMAIL_HOST || 'smtp.gmail.com', + port: parseInt(process.env.EMAIL_PORT || '587'), + secure: process.env.EMAIL_SECURE === 'true', + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASSWORD + }, + from: process.env.EMAIL_FROM || 'Royal Enfield ' +}; + +export default config; diff --git a/src/common/config/permissions.ts b/src/common/config/permissions.ts new file mode 100644 index 0000000..e63f8d0 --- /dev/null +++ b/src/common/config/permissions.ts @@ -0,0 +1,50 @@ +/** + * Granular Permission Constants + * Categorized by Action, View/Access, and Application Stage + */ + +export const PERMISSION_CATEGORIES = { + ACTION: 'ACTION', + VIEW: 'VIEW', + STAGE: 'STAGE' +} as const; + +export const PERMISSIONS = { + // Action Permissions + ACTION_APPROVE: 'action:approve', + ACTION_REJECT: 'action:reject', + ACTION_UPLOAD_DOCS: 'action:upload_docs', + ACTION_REQUEST_CHANGES: 'action:request_changes', + ACTION_FORWARD: 'action:forward', + ACTION_REASSIGN: 'action:reassign', + ACTION_SCHEDULE_INTERVIEW: 'action:schedule_interview', + ACTION_ADD_COMMENTS: 'action:add_comments', + ACTION_RANK_APPLICANTS: 'action:rank_applicants', + ACTION_FINAL_APPROVAL: 'action:final_approval', + + // View/Access Permissions + VIEW_DETAILS: 'view:view_details', + VIEW_FINANCIAL: 'view:view_financial', + VIEW_DISCUSSIONS: 'view:view_discussions', + VIEW_PROGRESS: 'view:view_progress', + VIEW_AUDIT: 'view:view_audit', + VIEW_DOCUMENTS: 'view:view_documents', + VIEW_PERSONAL: 'view:view_personal', + VIEW_BUSINESS: 'view:view_business', + VIEW_REPORTS: 'view:view_reports', + VIEW_HISTORY: 'view:view_history', + + // Application Stage Access + STAGE_INITIAL_REVIEW: 'stage:initial_review', + STAGE_FIELD_VERIFICATION: 'stage:field_verification', + STAGE_LEVEL1_INTERVIEW: 'stage:level1_interview', + STAGE_LEVEL2_INTERVIEW: 'stage:level2_interview', + STAGE_RANKING: 'stage:ranking', + STAGE_LEGAL_REVIEW: 'stage:legal_review', + STAGE_FINANCIAL_REVIEW: 'stage:financial_review', + STAGE_FINAL_APPROVAL: 'stage:final_approval', + STAGE_PAYMENT: 'stage:payment', + STAGE_ONBOARDING: 'stage:onboarding' +} as const; + +export type PermissionCode = typeof PERMISSIONS[keyof typeof PERMISSIONS]; diff --git a/src/common/middleware/auth.ts b/src/common/middleware/auth.ts index f375560..04b21c8 100644 --- a/src/common/middleware/auth.ts +++ b/src/common/middleware/auth.ts @@ -2,6 +2,7 @@ import { Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import db from '../../database/models/index.js'; import logger from '../utils/logger.js'; +import { JWT_SECRET } from '../config/auth.js'; import { AuthenticatedRequest, AuthRequest } from '../../types/express.types.js'; export const authenticate = async (req: AuthRequest, res: Response, next: NextFunction) => { @@ -19,11 +20,10 @@ export const authenticate = async (req: AuthRequest, res: Response, next: NextFu const token = authHeader.replace('Bearer ', ''); // Verify token - const jwtSecret = process.env.JWT_SECRET || 'your-default-secret'; - const decoded = jwt.verify(token, jwtSecret) as { id: string }; + const decoded = jwt.verify(token, JWT_SECRET) as { userId: string }; // Find user - const user = await db.User.findByPk(decoded.id, { + const user = await db.User.findByPk(decoded.userId, { attributes: { exclude: ['password'] } }); @@ -79,10 +79,9 @@ export const optionalAuth = async (req: AuthRequest, res: Response, next: NextFu } const token = authHeader.replace('Bearer ', ''); - const jwtSecret = process.env.JWT_SECRET || 'your-default-secret'; - const decoded = jwt.verify(token, jwtSecret) as { id: string }; + const decoded = jwt.verify(token, JWT_SECRET) as { userId: string }; - const user = await db.User.findByPk(decoded.id, { + const user = await db.User.findByPk(decoded.userId, { attributes: { exclude: ['password'] } }); diff --git a/src/common/middleware/roleCheck.ts b/src/common/middleware/roleCheck.ts index 1b68ade..f4da70d 100644 --- a/src/common/middleware/roleCheck.ts +++ b/src/common/middleware/roleCheck.ts @@ -20,14 +20,14 @@ export const checkRole = (allowedRoles: string[]) => { } // Check if user role is in allowed roles - if (!allowedRoles.includes(req.user.role)) { - logger.warn(`Access denied for user ${req.user.email} (${req.user.role}) to route ${req.path}`); + if (!allowedRoles.includes(req.user.roleCode)) { + logger.warn(`Access denied for user ${req.user.email} (${req.user.roleCode}) to route ${req.path}`); return res.status(403).json({ success: false, message: 'Access denied. Insufficient permissions.', requiredRoles: allowedRoles, - yourRole: req.user.role + yourRole: req.user.roleCode }); } diff --git a/src/database/models/Area.ts b/src/database/models/Area.ts index 603f905..8f6acfa 100644 --- a/src/database/models/Area.ts +++ b/src/database/models/Area.ts @@ -3,6 +3,8 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface AreaAttributes { id: string; regionId: string; + stateId: string; + zoneId: string; districtId: string; areaCode: string; areaName: string; @@ -28,6 +30,22 @@ export default (sequelize: Sequelize) => { key: 'id' } }, + stateId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'states', + key: 'id' + } + }, + zoneId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'zones', + key: 'id' + } + }, districtId: { type: DataTypes.UUID, allowNull: false, @@ -67,6 +85,14 @@ export default (sequelize: Sequelize) => { foreignKey: 'regionId', as: 'region' }); + Area.belongsTo(models.State, { + foreignKey: 'stateId', + as: 'state' + }); + Area.belongsTo(models.Zone, { + foreignKey: 'zoneId', + as: 'zone' + }); Area.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' diff --git a/src/database/models/District.ts b/src/database/models/District.ts index 56f3169..3a5946f 100644 --- a/src/database/models/District.ts +++ b/src/database/models/District.ts @@ -3,6 +3,8 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface DistrictAttributes { id: string; stateId: string; + zoneId: string; + regionId: string; districtName: string; isActive: boolean; } @@ -24,6 +26,22 @@ export default (sequelize: Sequelize) => { key: 'id' } }, + zoneId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'zones', + key: 'id' + } + }, + regionId: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'regions', + key: 'id' + } + }, districtName: { type: DataTypes.STRING, allowNull: false @@ -34,8 +52,7 @@ export default (sequelize: Sequelize) => { } }, { tableName: 'districts', - timestamps: true, - updatedAt: false + timestamps: true }); (District as any).associate = (models: any) => { @@ -43,6 +60,14 @@ export default (sequelize: Sequelize) => { foreignKey: 'stateId', as: 'state' }); + District.belongsTo(models.Zone, { + foreignKey: 'zoneId', + as: 'zone' + }); + District.belongsTo(models.Region, { + foreignKey: 'regionId', + as: 'region' + }); District.hasMany(models.Area, { foreignKey: 'districtId', as: 'areas' diff --git a/src/database/models/Permission.ts b/src/database/models/Permission.ts index c98805a..f67e03d 100644 --- a/src/database/models/Permission.ts +++ b/src/database/models/Permission.ts @@ -4,6 +4,7 @@ export interface PermissionAttributes { id: string; permissionCode: string; permissionName: string; + permissionCategory: string; module: string; permissionType: string; action: string; @@ -28,6 +29,11 @@ export default (sequelize: Sequelize) => { type: DataTypes.STRING, allowNull: false }, + permissionCategory: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: 'GENERAL' + }, module: { type: DataTypes.STRING, allowNull: false @@ -55,6 +61,12 @@ export default (sequelize: Sequelize) => { foreignKey: 'permissionId', as: 'rolePermissions' }); + Permission.belongsToMany(models.Role, { + through: models.RolePermission, + foreignKey: 'permissionId', + otherKey: 'roleId', + as: 'roles' + }); }; return Permission; diff --git a/src/database/models/Role.ts b/src/database/models/Role.ts index 89b2299..ff91f32 100644 --- a/src/database/models/Role.ts +++ b/src/database/models/Role.ts @@ -45,12 +45,23 @@ export default (sequelize: Sequelize) => { }); (Role as any).associate = (models: any) => { + Role.hasMany(models.User, { + foreignKey: 'roleCode', + sourceKey: 'roleCode', + as: 'users' + }); Role.hasMany(models.UserRole, { foreignKey: 'roleId', as: 'userRoles' }); Role.hasMany(models.RolePermission, { foreignKey: 'roleId', + as: 'rolePermissions' + }); + Role.belongsToMany(models.Permission, { + through: models.RolePermission, + foreignKey: 'roleId', + otherKey: 'permissionId', as: 'permissions' }); }; diff --git a/src/database/models/RolePermission.ts b/src/database/models/RolePermission.ts index 9532132..8bdfcfb 100644 --- a/src/database/models/RolePermission.ts +++ b/src/database/models/RolePermission.ts @@ -4,11 +4,6 @@ export interface RolePermissionAttributes { id: string; roleId: string; permissionId: string; - canView: boolean; - canCreate: boolean; - canEdit: boolean; - canDelete: boolean; - canApprove: boolean; } export interface RolePermissionInstance extends Model, RolePermissionAttributes { } @@ -35,26 +30,6 @@ export default (sequelize: Sequelize) => { model: 'permissions', key: 'id' } - }, - canView: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - canCreate: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - canEdit: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - canDelete: { - type: DataTypes.BOOLEAN, - defaultValue: false - }, - canApprove: { - type: DataTypes.BOOLEAN, - defaultValue: false } }, { tableName: 'role_permissions', diff --git a/src/database/models/State.ts b/src/database/models/State.ts index 092367e..1b1efed 100644 --- a/src/database/models/State.ts +++ b/src/database/models/State.ts @@ -35,8 +35,7 @@ export default (sequelize: Sequelize) => { } }, { tableName: 'states', - timestamps: true, - updatedAt: false + timestamps: true }); (State as any).associate = (models: any) => { diff --git a/src/database/models/User.ts b/src/database/models/User.ts index 5b6ffcf..4edfa2f 100644 --- a/src/database/models/User.ts +++ b/src/database/models/User.ts @@ -145,6 +145,11 @@ export default (sequelize: Sequelize) => { }); (User as any).associate = (models: any) => { + User.belongsTo(models.Role, { + foreignKey: 'roleCode', + targetKey: 'roleCode', + as: 'role' + }); User.hasMany(models.UserRole, { foreignKey: 'userId', as: 'userRoles' }); User.hasMany(models.UserRole, { foreignKey: 'assignedBy', as: 'assignedRoles' }); User.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' }); diff --git a/src/modules/admin/admin.controller.ts b/src/modules/admin/admin.controller.ts index efdc27f..f9c9d81 100644 --- a/src/modules/admin/admin.controller.ts +++ b/src/modules/admin/admin.controller.ts @@ -9,14 +9,28 @@ import { AuthRequest } from '../../types/express.types.js'; export const getRoles = async (req: Request, res: Response) => { try { const roles = await Role.findAll({ - include: [{ - model: Permission, - as: 'permissions', - through: { attributes: ['canCreate', 'canRead', 'canUpdate', 'canDelete', 'canApprove'] } - }], + include: [ + { + model: Permission, + as: 'permissions', + through: { attributes: [] } + }, + { + model: User, + as: 'users', + attributes: ['id'] + } + ], order: [['roleName', 'ASC']] }); - res.json({ success: true, data: roles }); + + // Map to include userCount + const result = roles.map((r: any) => ({ + ...r.toJSON(), + userCount: r.users?.length || 0 + })); + + res.json({ success: true, data: result }); } catch (error) { console.error('Get roles error:', error); res.status(500).json({ success: false, message: 'Error fetching roles' }); @@ -25,22 +39,21 @@ export const getRoles = async (req: Request, res: Response) => { export const createRole = async (req: AuthRequest, res: Response) => { try { - const { roleCode, roleName, description, permissions } = req.body; // permissions: [{ permissionId, actions: { canCreate... } }] + const { roleCode, roleName, description, permissionIds } = req.body; // permissionIds: string[] const role = await Role.create({ roleCode, roleName, description }); - if (permissions && permissions.length > 0) { - for (const p of permissions) { + if (permissionIds && permissionIds.length > 0) { + for (const pid of permissionIds) { await RolePermission.create({ roleId: role.id, - permissionId: p.permissionId, - ...p.actions + permissionId: pid }); } } await AuditLog.create({ - userId: req.user?.id, // Optional chaining as user might be undefined if auth middleware fails or not strict + userId: req.user?.id, action: AUDIT_ACTIONS.CREATED, entityType: 'role', entityId: role.id, @@ -57,21 +70,20 @@ export const createRole = async (req: AuthRequest, res: Response) => { export const updateRole = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; - const { roleName, description, permissions, isActive } = req.body; + const { roleName, description, permissionIds, isActive } = req.body; const role = await Role.findByPk(id); if (!role) return res.status(404).json({ success: false, message: 'Role not found' }); await role.update({ roleName, description, isActive }); - if (permissions) { - // Simplistic: Remove all and re-add (or smart update). for MVP redo all is fine or use bulkCreate with updateOnDuplicate + if (permissionIds) { + // Remove existing permissions and re-add new ones await RolePermission.destroy({ where: { roleId: id } }); - for (const p of permissions) { + for (const pid of permissionIds) { await RolePermission.create({ roleId: id, - permissionId: p.permissionId, - ...p.actions + permissionId: pid }); } } @@ -109,7 +121,22 @@ export const getAllUsers = async (req: Request, res: Response) => { try { const users = await User.findAll({ attributes: { exclude: ['password'] }, - include: ['roleDetails', 'zoneDetails', 'regionDetails', 'areaDetails'], // Assuming associations are named like this or similar + include: [ + { + model: Role, + as: 'role', + include: [ + { + model: Permission, + as: 'permissions', + through: { attributes: [] } + } + ] + }, + { model: db.Zone, as: 'zone' }, + { model: db.Region, as: 'region' }, + { model: db.Area, as: 'area' } + ], order: [['createdAt', 'DESC']] }); res.json({ success: true, data: users }); @@ -144,6 +171,52 @@ export const updateUserStatus = async (req: AuthRequest, res: Response) => { } }; +export const updateUser = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { + fullName, email, roleCode, status, isActive, employeeId, + mobileNumber, department, designation, + zoneId, regionId, stateId, districtId, areaId + } = req.body; + + const user = await User.findByPk(id); + if (!user) return res.status(404).json({ success: false, message: 'User not found' }); + + const oldData = user.toJSON(); + await user.update({ + fullName: fullName || user.fullName, + email: email || user.email, + roleCode: roleCode || user.roleCode, + status: status || user.status, + isActive: isActive !== undefined ? isActive : user.isActive, + employeeId: employeeId || user.employeeId, + mobileNumber: mobileNumber || user.mobileNumber, + department: department || user.department, + designation: designation || user.designation, + zoneId: zoneId !== undefined ? zoneId : user.zoneId, + regionId: regionId !== undefined ? regionId : user.regionId, + stateId: stateId !== undefined ? stateId : user.stateId, + districtId: districtId !== undefined ? districtId : user.districtId, + areaId: areaId !== undefined ? areaId : user.areaId + }); + + await AuditLog.create({ + userId: req.user?.id, + action: AUDIT_ACTIONS.UPDATED, + entityType: 'user', + entityId: id, + oldData, + newData: req.body + }); + + res.json({ success: true, message: 'User updated successfully', data: user }); + } catch (error) { + console.error('Update user error:', error); + res.status(500).json({ success: false, message: 'Error updating user' }); + } +}; + // --- Dealer Codes --- export const generateDealerCode = async (req: AuthRequest, res: Response) => { diff --git a/src/modules/admin/admin.routes.ts b/src/modules/admin/admin.routes.ts index 92095a5..43dd63c 100644 --- a/src/modules/admin/admin.routes.ts +++ b/src/modules/admin/admin.routes.ts @@ -20,6 +20,7 @@ router.get('/permissions', adminController.getPermissions); // Users (Admin View) router.get('/users', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, adminController.getAllUsers); router.patch('/users/:id/status', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.updateUserStatus); +router.put('/users/:id', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.updateUser); // Dealer Codes router.post('/dealer-codes/generate', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, adminController.generateDealerCode); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index ae10e9b..8359793 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -35,11 +35,11 @@ export const register = async (req: Request, res: Response) => { const user = await User.create({ email, password: hashedPassword, - name: fullName, - role, - phone, - region, - zone, + fullName, + roleCode: role, + mobileNumber: phone, + regionId: region, + zoneId: zone, status: 'active' }); @@ -114,7 +114,7 @@ export const login = async (req: Request, res: Response) => { // Log audit await AuditLog.create({ userId: user.id, - action: 'user_login' as any, + action: AUDIT_ACTIONS.LOGIN as any, entityType: 'user', entityId: user.id }); @@ -125,10 +125,10 @@ export const login = async (req: Request, res: Response) => { user: { id: user.id, email: user.email, - fullName: user.name, - role: user.role, - region: user.region, - zone: user.zone + fullName: user.fullName, + role: user.roleCode, + region: user.regionId, + zone: user.zoneId } }); } catch (error) { @@ -148,7 +148,7 @@ export const getProfile = async (req: AuthRequest, res: Response) => { } const user = await User.findByPk(req.user.id, { - attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt'] + attributes: ['id', 'email', 'fullName', 'roleCode', 'regionId', 'zoneId', 'mobileNumber', 'createdAt'] }); if (!user) { @@ -163,12 +163,12 @@ export const getProfile = async (req: AuthRequest, res: Response) => { user: { id: user.id, email: user.email, - fullName: user.name, - role: user.role, - region: user.region, - zone: user.zone, - phone: user.phone, - createdAt: user.createdAt + fullName: user.fullName, + role: user.roleCode, + region: user.regionId, + zone: user.zoneId, + phone: user.mobileNumber, + createdAt: (user as any).createdAt } }); } catch (error) { @@ -198,8 +198,8 @@ export const updateProfile = async (req: AuthRequest, res: Response) => { } await user.update({ - name: fullName || user.name, - phone: phone || user.phone + fullName: fullName || user.fullName, + mobileNumber: phone || user.mobileNumber }); // Log audit diff --git a/src/modules/auth/auth.routes.ts b/src/modules/auth/auth.routes.ts index ac3235c..222a8c0 100644 --- a/src/modules/auth/auth.routes.ts +++ b/src/modules/auth/auth.routes.ts @@ -8,6 +8,7 @@ router.post('/register', authController.register); router.post('/login', authController.login); // Protected routes +router.get('/me', authenticate as any, authController.getProfile); router.get('/profile', authenticate as any, authController.getProfile); router.put('/profile', authenticate as any, authController.updateProfile); router.post('/change-password', authenticate as any, authController.changePassword); diff --git a/src/modules/master/master.controller.ts b/src/modules/master/master.controller.ts index 187d9f3..754f9d8 100644 --- a/src/modules/master/master.controller.ts +++ b/src/modules/master/master.controller.ts @@ -1,15 +1,15 @@ import { Request, Response } from 'express'; import db from '../../database/models/index.js'; -const { Region, Zone, State, District, Area } = db; +const { Region, Zone, State, District, Area, User } = db; // --- Regions --- export const getRegions = async (req: Request, res: Response) => { try { const regions = await Region.findAll({ - order: [['name', 'ASC']] + order: [['regionName', 'ASC']] }); - res.json({ success: true, regions }); + res.json({ success: true, data: regions }); } catch (error) { console.error('Get regions error:', error); res.status(500).json({ success: false, message: 'Error fetching regions' }); @@ -24,7 +24,7 @@ export const createRegion = async (req: Request, res: Response) => { return res.status(400).json({ success: false, message: 'Region name is required' }); } - const region = await Region.create({ name: regionName }); + const region = await Region.create({ regionName }); res.status(201).json({ success: true, message: 'Region created successfully', data: region }); } catch (error) { @@ -44,7 +44,7 @@ export const updateRegion = async (req: Request, res: Response) => { } await region.update({ - name: regionName || region.name, + regionName: regionName || (region as any).regionName, updatedAt: new Date() }); @@ -67,15 +67,27 @@ export const getZones = async (req: Request, res: Response) => { const zones = await Zone.findAll({ where, - include: [{ - model: Region, - as: 'region', - attributes: ['name'] - }], - order: [['name', 'ASC']] + include: [ + { + model: Region, + as: 'regions', + attributes: ['regionName'] + }, + { + model: User, + as: 'zonalBusinessHead', + attributes: ['fullName', 'email', 'mobileNumber'] + }, + { + model: State, + as: 'states', + attributes: ['stateName'] + } + ], + order: [['zoneName', 'ASC']] }); - res.json({ success: true, zones }); + res.json({ success: true, data: zones }); } catch (error) { console.error('Get zones error:', error); res.status(500).json({ success: false, message: 'Error fetching zones' }); @@ -91,8 +103,8 @@ export const createZone = async (req: Request, res: Response) => { } const zone = await Zone.create({ - regionId, - name: zoneName + regionId, // Wait, Zone Model doesn't have regionId. It's the other way around? + zoneName }); res.status(201).json({ success: true, message: 'Zone created successfully', data: zone }); @@ -113,7 +125,7 @@ export const updateZone = async (req: Request, res: Response) => { } await zone.update({ - name: zoneName || zone.name, + zoneName: zoneName || (zone as any).zoneName, updatedAt: new Date() }); @@ -133,7 +145,7 @@ export const getStates = async (req: Request, res: Response) => { const states = await State.findAll({ where, - include: [{ model: Zone, as: 'zone', attributes: ['name'] }], + include: [{ model: Zone, as: 'zone', attributes: ['zoneName'] }], order: [['stateName', 'ASC']] }); res.json({ success: true, states }); diff --git a/src/types/express.types.ts b/src/types/express.types.ts index 5c127b8..5e89fc8 100644 --- a/src/types/express.types.ts +++ b/src/types/express.types.ts @@ -5,8 +5,9 @@ export interface AuthenticatedRequest extends Request { user?: { id: string; email: string; - name: string; - role: typeof ROLES[keyof typeof ROLES]; + fullName: string; + roleCode: string; + role?: typeof ROLES[keyof typeof ROLES]; }; } diff --git a/tsconfig.json b/tsconfig.json index deb2ded..f7c1704 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,6 @@ "exclude": [ "node_modules", "dist", - "tests", - "scripts" + "tests" ] } \ No newline at end of file