changed to typescript and added missed tables

This commit is contained in:
laxmanhalaki 2026-01-21 19:05:16 +05:30
parent 8984a314a7
commit 251a362717
148 changed files with 11531 additions and 2994 deletions

5292
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,14 @@
"name": "royal-enfield-onboarding-backend",
"version": "1.0.0",
"description": "Backend API for Royal Enfield Dealership Onboarding System",
"type": "module",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"migrate": "node scripts/migrate.js",
"start": "node dist/src/server.js",
"dev": "tsx watch src/server.ts",
"build": "tsc",
"type-check": "tsc --noEmit",
"migrate": "node dist/scripts/migrate.js",
"test": "jest",
"test:coverage": "jest --coverage",
"clear-logs": "rm -rf logs/*.log"
@ -20,27 +23,43 @@
"author": "Royal Enfield",
"license": "PROPRIETARY",
"dependencies": {
"express": "^4.18.2",
"sequelize": "^6.35.2",
"pg": "^8.11.3",
"pg-hstore": "^2.3.4",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3",
"bcryptjs": "^3.0.3",
"compression": "^1.8.1",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"express-validator": "^7.0.1",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.7",
"winston": "^3.11.0",
"dotenv": "^16.3.1",
"uuid": "^9.0.1",
"express-rate-limit": "^7.1.5",
"compression": "^1.7.4"
"dotenv": "^17.2.3",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"express-validator": "^7.3.1",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.3",
"multer": "^2.0.2",
"nodemailer": "^7.0.12",
"pg": "^8.17.2",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.7",
"uuid": "^13.0.0",
"winston": "^3.19.0"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/compression": "^1.8.1",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^25.0.9",
"@types/nodemailer": "^7.0.5",
"@types/pg": "^8.16.0",
"@types/supertest": "^6.0.3",
"@types/uuid": "^10.0.0",
"@types/validator": "^13.15.10",
"jest": "^30.2.0",
"nodemon": "^3.0.2",
"jest": "^29.7.0",
"supertest": "^6.3.3"
"supertest": "^7.2.2",
"ts-node": "^10.9.2",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=18.0.0",

41
scripts/migrate.ts Normal file
View File

@ -0,0 +1,41 @@
/**
* Database Migration Script
* Synchronizes all Sequelize models with the database
* This script will DROP all existing tables and recreate them.
*
* Run: npx tsx scripts/migrate.ts
*/
import 'dotenv/config';
import db from '../src/database/models/index.js';
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('----------------------------------------------------');
const modelNames = Object.keys(db).filter(k => k !== 'sequelize' && k !== 'Sequelize');
console.log(`Available Models (${modelNames.length}): ${modelNames.join(', ')}`);
console.log('----------------------------------------------------');
process.exit(0);
} catch (error: any) {
console.error('\n❌ Migration failed:', error.message);
if (error.stack) {
console.error('\nStack Trace:\n', error.stack);
}
process.exit(1);
}
}
runMigrations();

156
server.js
View File

@ -1,156 +0,0 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const path = require('path');
// Import database
const db = require('./src/database/models');
// Import routes (Modular Monolith Structure)
const authRoutes = require('./src/modules/auth/auth.routes');
const onboardingRoutes = require('./src/modules/onboarding/onboarding.routes');
const selfServiceRoutes = require('./src/modules/self-service/self-service.routes');
const masterRoutes = require('./src/modules/master/master.routes');
const settlementRoutes = require('./src/modules/settlement/settlement.routes');
const collaborationRoutes = require('./src/modules/collaboration/collaboration.routes');
// Import common middleware & utils
const errorHandler = require('./src/common/middleware/errorHandler');
const logger = require('./src/common/utils/logger');
// Initialize Express app
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api/', limiter);
// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Compression
app.use(compression());
// Static files (uploaded documents)
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// Request logging
app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.get('user-agent')
});
next();
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV
});
});
// API Routes (Modular)
app.use('/api/auth', authRoutes);
app.use('/api/onboarding', onboardingRoutes);
app.use('/api/self-service', selfServiceRoutes);
app.use('/api/master', masterRoutes);
app.use('/api/settlement', settlementRoutes);
app.use('/api/collaboration', collaborationRoutes);
// Backward Compatibility Aliases
app.use('/api/applications', onboardingRoutes);
app.use('/api/resignations', require('./src/modules/self-service/resignation.routes'));
app.use('/api/constitutional', (req, res, next) => {
// Map /api/constitutional to /api/self-service/constitutional
req.url = '/constitutional' + req.url;
next();
}, selfServiceRoutes);
app.use('/api/relocations', (req, res, next) => {
// Map /api/relocations to /api/self-service/relocation
req.url = '/relocation' + req.url;
next();
}, selfServiceRoutes);
app.use('/api/outlets', require('./src/modules/master/outlet.routes'));
app.use('/api/finance', settlementRoutes);
app.use('/api/worknotes', collaborationRoutes);
// 404 handler
app.use((req, res) => {
res.status(404).json({
success: false,
message: 'Route not found'
});
});
// Global error handler
app.use(errorHandler);
// Database connection and server start
const PORT = process.env.PORT || 5000;
const startServer = async () => {
try {
// Test database connection
await db.sequelize.authenticate();
logger.info('Database connection established successfully');
// Sync database (in development only)
if (process.env.NODE_ENV === 'development') {
await db.sequelize.sync({ alter: false });
logger.info('Database models synchronized');
}
// Start server
app.listen(PORT, () => {
logger.info(`🚀 Server running on port ${PORT}`);
logger.info(`📍 Environment: ${process.env.NODE_ENV}`);
logger.info(`🔗 API Base URL: http://localhost:${PORT}/api`);
});
} catch (error) {
logger.error('Unable to start server:', error);
process.exit(1);
}
};
// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
logger.error('Unhandled Promise Rejection:', err);
process.exit(1);
});
// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
logger.error('Uncaught Exception:', err);
process.exit(1);
});
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('SIGTERM signal received: closing HTTP server');
await db.sequelize.close();
process.exit(0);
});
startServer();
module.exports = app;

View File

@ -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
};

31
src/common/config/auth.ts Normal file
View File

@ -0,0 +1,31 @@
import jwt from 'jsonwebtoken';
import { TokenPayload } from '../../types/auth.types.js';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
const JWT_EXPIRE = process.env.JWT_EXPIRE || '7d';
// Generate JWT token
export const generateToken = (user: any): string => {
const payload: TokenPayload = {
userId: user.id,
email: user.email,
role: user.role,
region: user.region,
zone: user.zone
};
return jwt.sign(payload, JWT_SECRET, {
expiresIn: JWT_EXPIRE as any
});
};
// Verify JWT token
export const verifyToken = (token: string): TokenPayload => {
try {
return jwt.verify(token, JWT_SECRET) as TokenPayload;
} catch (error) {
throw new Error('Invalid or expired token');
}
};
export { JWT_SECRET };

View File

@ -1,5 +1,5 @@
// User Roles
const ROLES = {
export const ROLES = {
DD: 'DD',
DD_ZM: 'DD-ZM',
RBM: 'RBM',
@ -13,19 +13,19 @@ const ROLES = {
DD_AM: 'DD AM',
FINANCE: 'Finance',
DEALER: 'Dealer'
};
} as const;
// Regions
const REGIONS = {
export const REGIONS = {
EAST: 'East',
WEST: 'West',
NORTH: 'North',
SOUTH: 'South',
CENTRAL: 'Central'
};
} as const;
// Application Stages
const APPLICATION_STAGES = {
export const APPLICATION_STAGES = {
DD: 'DD',
DD_ZM: 'DD-ZM',
RBM: 'RBM',
@ -37,18 +37,18 @@ const APPLICATION_STAGES = {
FINANCE: 'Finance',
APPROVED: 'Approved',
REJECTED: 'Rejected'
};
} as const;
// Application Status
const APPLICATION_STATUS = {
export const APPLICATION_STATUS = {
PENDING: 'Pending',
IN_REVIEW: 'In Review',
APPROVED: 'Approved',
REJECTED: 'Rejected'
};
} as const;
// Resignation Stages
const RESIGNATION_STAGES = {
export const RESIGNATION_STAGES = {
ASM: 'ASM',
RBM: 'RBM',
ZBH: 'ZBH',
@ -59,99 +59,99 @@ const RESIGNATION_STAGES = {
FNF_INITIATED: 'F&F Initiated',
COMPLETED: 'Completed',
REJECTED: 'Rejected'
};
} as const;
// Resignation Types
const RESIGNATION_TYPES = {
export const RESIGNATION_TYPES = {
VOLUNTARY: 'Voluntary',
RETIREMENT: 'Retirement',
HEALTH_ISSUES: 'Health Issues',
BUSINESS_CLOSURE: 'Business Closure',
OTHER: 'Other'
};
} as const;
// Constitutional Change Types
const CONSTITUTIONAL_CHANGE_TYPES = {
export const CONSTITUTIONAL_CHANGE_TYPES = {
OWNERSHIP_TRANSFER: 'Ownership Transfer',
PARTNERSHIP_CHANGE: 'Partnership Change',
LLP_CONVERSION: 'LLP Conversion',
COMPANY_FORMATION: 'Company Formation',
DIRECTOR_CHANGE: 'Director Change'
};
} as const;
// Constitutional Change Stages
const CONSTITUTIONAL_STAGES = {
export 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'
};
} as const;
// Relocation Types
const RELOCATION_TYPES = {
export const RELOCATION_TYPES = {
WITHIN_CITY: 'Within City',
INTERCITY: 'Intercity',
INTERSTATE: 'Interstate'
};
} as const;
// Relocation Stages
const RELOCATION_STAGES = {
export 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'
};
} as const;
// Outlet Types
const OUTLET_TYPES = {
export const OUTLET_TYPES = {
DEALERSHIP: 'Dealership',
STUDIO: 'Studio'
};
} as const;
// Outlet Status
const OUTLET_STATUS = {
export const OUTLET_STATUS = {
ACTIVE: 'Active',
PENDING_RESIGNATION: 'Pending Resignation',
CLOSED: 'Closed'
};
} as const;
// Business Types
const BUSINESS_TYPES = {
export const BUSINESS_TYPES = {
DEALERSHIP: 'Dealership',
STUDIO: 'Studio'
};
} as const;
// Payment Types
const PAYMENT_TYPES = {
export const PAYMENT_TYPES = {
SECURITY_DEPOSIT: 'Security Deposit',
LICENSE_FEE: 'License Fee',
SETUP_FEE: 'Setup Fee',
OTHER: 'Other'
};
} as const;
// Payment Status
const PAYMENT_STATUS = {
export const PAYMENT_STATUS = {
PENDING: 'Pending',
PAID: 'Paid',
OVERDUE: 'Overdue',
WAIVED: 'Waived'
};
} as const;
// F&F Status
const FNF_STATUS = {
export const FNF_STATUS = {
INITIATED: 'Initiated',
DD_CLEARANCE: 'DD Clearance',
LEGAL_CLEARANCE: 'Legal Clearance',
FINANCE_APPROVAL: 'Finance Approval',
COMPLETED: 'Completed'
};
} as const;
// Audit Actions
const AUDIT_ACTIONS = {
export const AUDIT_ACTIONS = {
CREATED: 'CREATED',
UPDATED: 'UPDATED',
APPROVED: 'APPROVED',
@ -160,10 +160,10 @@ const AUDIT_ACTIONS = {
STAGE_CHANGED: 'STAGE_CHANGED',
DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED',
WORKNOTE_ADDED: 'WORKNOTE_ADDED'
};
} as const;
// Document Types
const DOCUMENT_TYPES = {
export const DOCUMENT_TYPES = {
GST_CERTIFICATE: 'GST Certificate',
PAN_CARD: 'PAN Card',
AADHAAR: 'Aadhaar',
@ -176,34 +176,12 @@ const DOCUMENT_TYPES = {
PROPERTY_DOCUMENTS: 'Property Documents',
BANK_STATEMENT: 'Bank Statement',
OTHER: 'Other'
};
} as const;
// Request Types
const REQUEST_TYPES = {
export 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
};
} as const;

View File

@ -1,12 +1,14 @@
require('dotenv').config();
import 'dotenv/config';
import { Options } from 'sequelize';
import { DbConfig } from '../../types/common.types.js';
module.exports = {
const config: DbConfig = {
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,
port: parseInt(process.env.DB_PORT || '5432'),
dialect: 'postgres',
logging: console.log,
pool: {
@ -21,7 +23,7 @@ module.exports = {
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,
port: parseInt(process.env.DB_PORT || '5432'),
dialect: 'postgres',
logging: false,
pool: {
@ -40,10 +42,12 @@ module.exports = {
test: {
username: process.env.DB_USER || 'laxman',
password: process.env.DB_PASSWORD || 'Admin@123',
database: process.env.DB_NAME + '_test' || 'royal_enfield_onboarding_test',
database: (process.env.DB_NAME || 'royal_enfield_onboarding') + '_test',
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
port: parseInt(process.env.DB_PORT || '5432'),
dialect: 'postgres',
logging: false
}
};
export default config;

View File

@ -1,100 +0,0 @@
const jwt = require('jsonwebtoken');
const db = require('../../database/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
};

View File

@ -0,0 +1,99 @@
import { Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import db from '../../database/models/index.js';
import logger from '../utils/logger.js';
import { AuthenticatedRequest, AuthRequest } from '../../types/express.types.js';
export const authenticate = async (req: AuthRequest, res: Response, next: NextFunction) => {
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 jwtSecret = process.env.JWT_SECRET || 'your-default-secret';
const decoded = jwt.verify(token, jwtSecret) as { id: string };
// 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: any) {
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'
});
}
};
export const optionalAuth = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const authHeader = req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return next();
}
const token = authHeader.replace('Bearer ', '');
const jwtSecret = process.env.JWT_SECRET || 'your-default-secret';
const decoded = jwt.verify(token, jwtSecret) as { id: string };
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();
}
};

View File

@ -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;

View File

@ -0,0 +1,78 @@
import { Request, Response, NextFunction } from 'express';
import logger from '../utils/logger.js';
import { AppError } from '../../types/common.types.js';
const errorHandler = (err: AppError, req: Request, res: Response, next: NextFunction) => {
// 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 })
});
};
export default errorHandler;

View File

@ -1,47 +0,0 @@
const { ROLES } = require('../config/constants');
const logger = require('../utils/logger');
/**
* Role-based access control middleware
* @param {Array<string>} 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
};

View File

@ -0,0 +1,46 @@
import { Request, Response, NextFunction } from 'express';
import { ROLES } from '../config/constants.js';
import logger from '../utils/logger.js';
import { AuthenticatedRequest } from '../../types/express.types.js';
/**
* Role-based access control middleware
* @param allowedRoles - Array of roles that can access the route
* @returns Express middleware function
*/
export const checkRole = (allowedRoles: string[]) => {
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
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'
});
}
};
};
export { ROLES };

View File

@ -0,0 +1,96 @@
import multer, { FileFilterCallback } from 'multer';
import path from 'path';
import fs from 'fs';
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from '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: Request, file: Express.Multer.File, cb: FileFilterCallback) => {
// 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'));
}
};
// 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
export const uploadSingle = upload.single('file');
// Multiple files upload
export const uploadMultiple = upload.array('files', 10); // Max 10 files
// Error handler for multer
export const handleUploadError = (err: any, req: Request, res: Response, next: NextFunction) => {
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();
};

View File

@ -1,67 +0,0 @@
const winston = require('winston');
const path = require('path');
const fs = require('fs');
// Create logs directory if it doesn't exist
const logsDir = path.join(__dirname, '../../../logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir);
}
// Define log format
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// Console format for development
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ level, message, timestamp, ...meta }) => {
return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;
})
);
// Create the logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
transports: [
// Write all logs to combined.log
new winston.transports.File({
filename: path.join(logsDir, 'combined.log'),
maxsize: 5242880, // 5MB
maxFiles: 5
}),
// Write error logs to error.log
new winston.transports.File({
filename: path.join(logsDir, 'error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
})
],
// Handle exceptions and rejections
exceptionHandlers: [
new winston.transports.File({
filename: path.join(logsDir, 'exceptions.log')
})
],
rejectionHandlers: [
new winston.transports.File({
filename: path.join(logsDir, 'rejections.log')
})
]
});
// Add console transport in development
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: consoleFormat
}));
}
module.exports = logger;

View File

@ -0,0 +1,71 @@
import winston from 'winston';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Create logs directory if it doesn't exist
const logsDir = path.join(process.cwd(), 'logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Define log format
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// Console format for development
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ level, message, timestamp, ...meta }) => {
return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;
})
);
// Create the logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
transports: [
// Write all logs to combined.log
new winston.transports.File({
filename: path.join(logsDir, 'combined.log'),
maxsize: 5242880, // 5MB
maxFiles: 5
}),
// Write error logs to error.log
new winston.transports.File({
filename: path.join(logsDir, 'error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
})
],
// Handle exceptions and rejections
exceptionHandlers: [
new winston.transports.File({
filename: path.join(logsDir, 'exceptions.log')
})
],
rejectionHandlers: [
new winston.transports.File({
filename: path.join(logsDir, 'rejections.log')
})
]
});
// Add console transport in development
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: consoleFormat
}));
}
export default logger;

View File

@ -0,0 +1,57 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface AiSummaryAttributes {
id: string;
applicationId: string;
summaryType: string;
summaryText: string;
sentimentScore: number | null;
recommendation: string | null;
}
export interface AiSummaryInstance extends Model<AiSummaryAttributes>, AiSummaryAttributes { }
export default (sequelize: Sequelize) => {
const AiSummary = sequelize.define<AiSummaryInstance>('AiSummary', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
summaryType: {
type: DataTypes.STRING,
allowNull: false
},
summaryText: {
type: DataTypes.TEXT,
allowNull: false
},
sentimentScore: {
type: DataTypes.DECIMAL(5, 2),
allowNull: true
},
recommendation: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'ai_summaries',
timestamps: true,
updatedAt: false,
createdAt: 'generatedAt'
});
(AiSummary as any).associate = (models: any) => {
AiSummary.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
};
return AiSummary;
};

View File

@ -1,124 +0,0 @@
const { APPLICATION_STAGES, APPLICATION_STATUS, BUSINESS_TYPES } = require('../../common/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
},
isShortlisted: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
ddLeadShortlisted: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
assignedTo: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
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.belongsTo(models.User, {
foreignKey: 'assignedTo',
as: 'assignee'
});
Application.hasMany(models.Document, {
foreignKey: 'requestId',
as: 'uploadedDocuments',
scope: { requestType: 'application' }
});
};
return Application;
};

View File

@ -0,0 +1,189 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { APPLICATION_STAGES, APPLICATION_STATUS, BUSINESS_TYPES } from '../../common/config/constants.js';
export interface ApplicationAttributes {
id: string;
applicationId: string;
opportunityId: string | null;
applicantName: string;
email: string;
phone: string;
businessType: string;
preferredLocation: string | null;
city: string | null;
state: string | null;
experienceYears: number | null;
investmentCapacity: string | null;
currentStage: string;
overallStatus: string;
progressPercentage: number;
isShortlisted: boolean;
ddLeadShortlisted: boolean;
assignedTo: string | null;
submittedBy: string | null;
zoneId: string | null;
regionId: string | null;
areaId: string | null;
documents: any[];
timeline: any[];
}
export interface ApplicationInstance extends Model<ApplicationAttributes>, ApplicationAttributes { }
export default (sequelize: Sequelize) => {
const Application = sequelize.define<ApplicationInstance>('Application', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
opportunityId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'opportunities',
key: 'id'
}
},
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: true
},
city: {
type: DataTypes.STRING,
allowNull: true
},
state: {
type: DataTypes.STRING,
allowNull: true
},
experienceYears: {
type: DataTypes.INTEGER,
allowNull: true
},
investmentCapacity: {
type: DataTypes.STRING,
allowNull: true
},
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
},
isShortlisted: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
ddLeadShortlisted: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
assignedTo: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
submittedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
zoneId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'zones',
key: 'id'
}
},
regionId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'regions',
key: 'id'
}
},
areaId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'areas',
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'] },
{ fields: ['opportunityId'] }
]
});
(Application as any).associate = (models: any) => {
Application.belongsTo(models.User, { foreignKey: 'submittedBy', as: 'submitter' });
Application.belongsTo(models.User, { foreignKey: 'assignedTo', as: 'assignee' });
Application.belongsTo(models.Opportunity, { foreignKey: 'opportunityId', as: 'opportunity' });
Application.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
Application.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
Application.belongsTo(models.Area, { foreignKey: 'areaId', as: 'area' });
Application.hasMany(models.ApplicationStatusHistory, { foreignKey: 'applicationId', as: 'statusHistory' });
Application.hasMany(models.ApplicationProgress, { foreignKey: 'applicationId', as: 'progressTracking' });
Application.hasMany(models.Document, {
foreignKey: 'requestId',
as: 'uploadedDocuments',
scope: { requestType: 'application' }
});
};
return Application;
};

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ApplicationProgressAttributes {
id: string;
applicationId: string;
stageName: string;
stageOrder: number;
status: string;
completionPercentage: number;
stageStartedAt: Date | null;
stageCompletedAt: Date | null;
}
export interface ApplicationProgressInstance extends Model<ApplicationProgressAttributes>, ApplicationProgressAttributes { }
export default (sequelize: Sequelize) => {
const ApplicationProgress = sequelize.define<ApplicationProgressInstance>('ApplicationProgress', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
stageName: {
type: DataTypes.STRING,
allowNull: false
},
stageOrder: {
type: DataTypes.INTEGER,
allowNull: false
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
completionPercentage: {
type: DataTypes.INTEGER,
defaultValue: 0
},
stageStartedAt: {
type: DataTypes.DATE,
allowNull: true
},
stageCompletedAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
tableName: 'application_progress',
timestamps: true
});
(ApplicationProgress as any).associate = (models: any) => {
ApplicationProgress.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
};
return ApplicationProgress;
};

View File

@ -0,0 +1,61 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ApplicationStatusHistoryAttributes {
id: string;
applicationId: string;
previousStatus: string | null;
newStatus: string;
changedBy: string | null;
changeReason: string | null;
}
export interface ApplicationStatusHistoryInstance extends Model<ApplicationStatusHistoryAttributes>, ApplicationStatusHistoryAttributes { }
export default (sequelize: Sequelize) => {
const ApplicationStatusHistory = sequelize.define<ApplicationStatusHistoryInstance>('ApplicationStatusHistory', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
previousStatus: {
type: DataTypes.STRING,
allowNull: true
},
newStatus: {
type: DataTypes.STRING,
allowNull: false
},
changedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
changeReason: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'application_status_history',
timestamps: true,
updatedAt: false
});
(ApplicationStatusHistory as any).associate = (models: any) => {
ApplicationStatusHistory.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
ApplicationStatusHistory.belongsTo(models.User, { foreignKey: 'changedBy', as: 'changer' });
};
return ApplicationStatusHistory;
};

View File

@ -0,0 +1,81 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface AreaAttributes {
id: string;
regionId: string;
districtId: string;
areaCode: string;
areaName: string;
city: string | null;
pincode: string | null;
isActive: boolean;
}
export interface AreaInstance extends Model<AreaAttributes>, AreaAttributes { }
export default (sequelize: Sequelize) => {
const Area = sequelize.define<AreaInstance>('Area', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
regionId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'regions',
key: 'id'
}
},
districtId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'districts',
key: 'id'
}
},
areaCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
areaName: {
type: DataTypes.STRING,
allowNull: false
},
city: {
type: DataTypes.STRING,
allowNull: true
},
pincode: {
type: DataTypes.STRING,
allowNull: true
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'areas',
timestamps: true
});
(Area as any).associate = (models: any) => {
Area.belongsTo(models.Region, {
foreignKey: 'regionId',
as: 'region'
});
Area.belongsTo(models.District, {
foreignKey: 'districtId',
as: 'district'
});
Area.hasMany(models.Application, {
foreignKey: 'areaId',
as: 'applications'
});
};
return Area;
};

View File

@ -0,0 +1,67 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface AreaManagerAttributes {
id: string;
areaId: string;
userId: string;
managerType: string;
isActive: boolean;
assignedAt: Date;
}
export interface AreaManagerInstance extends Model<AreaManagerAttributes>, AreaManagerAttributes { }
export default (sequelize: Sequelize) => {
const AreaManager = sequelize.define<AreaManagerInstance>('AreaManager', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
areaId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'areas',
key: 'id'
}
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
managerType: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
assignedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'area_managers',
timestamps: true,
updatedAt: false
});
(AreaManager as any).associate = (models: any) => {
AreaManager.belongsTo(models.Area, {
foreignKey: 'areaId',
as: 'area'
});
AreaManager.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
};
return AreaManager;
};

View File

@ -1,7 +1,22 @@
const { AUDIT_ACTIONS } = require('../../common/config/constants');
import { Model, DataTypes, Sequelize } from 'sequelize';
import { AUDIT_ACTIONS } from '../../common/config/constants.js';
module.exports = (sequelize, DataTypes) => {
const AuditLog = sequelize.define('AuditLog', {
export interface AuditLogAttributes {
id: string;
userId: string | null;
action: typeof AUDIT_ACTIONS[keyof typeof AUDIT_ACTIONS];
entityType: string;
entityId: string;
oldData: any | null;
newData: any | null;
ipAddress: string | null;
userAgent: string | null;
}
export interface AuditLogInstance extends Model<AuditLogAttributes>, AuditLogAttributes { }
export default (sequelize: Sequelize) => {
const AuditLog = sequelize.define<AuditLogInstance>('AuditLog', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -16,7 +31,7 @@ module.exports = (sequelize, DataTypes) => {
}
},
action: {
type: DataTypes.ENUM(Object.values(AUDIT_ACTIONS)),
type: DataTypes.ENUM(...Object.values(AUDIT_ACTIONS)),
allowNull: false
},
entityType: {
@ -54,7 +69,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
AuditLog.associate = (models) => {
(AuditLog as any).associate = (models: any) => {
AuditLog.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'

View File

@ -1,7 +1,24 @@
const { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } = require('../../common/config/constants');
import { Model, DataTypes, Sequelize } from 'sequelize';
import { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } from '../../common/config/constants.js';
module.exports = (sequelize, DataTypes) => {
const ConstitutionalChange = sequelize.define('ConstitutionalChange', {
export interface ConstitutionalChangeAttributes {
id: string;
requestId: string;
outletId: string;
dealerId: string;
changeType: typeof CONSTITUTIONAL_CHANGE_TYPES[keyof typeof CONSTITUTIONAL_CHANGE_TYPES];
description: string;
currentStage: typeof CONSTITUTIONAL_STAGES[keyof typeof CONSTITUTIONAL_STAGES];
status: string;
progressPercentage: number;
documents: any[];
timeline: any[];
}
export interface ConstitutionalChangeInstance extends Model<ConstitutionalChangeAttributes>, ConstitutionalChangeAttributes { }
export default (sequelize: Sequelize) => {
const ConstitutionalChange = sequelize.define<ConstitutionalChangeInstance>('ConstitutionalChange', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -29,7 +46,7 @@ module.exports = (sequelize, DataTypes) => {
}
},
changeType: {
type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_CHANGE_TYPES)),
type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_CHANGE_TYPES)),
allowNull: false
},
description: {
@ -37,7 +54,7 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false
},
currentStage: {
type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_STAGES)),
type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_STAGES)),
defaultValue: CONSTITUTIONAL_STAGES.DD_ADMIN_REVIEW
},
status: {
@ -67,7 +84,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
ConstitutionalChange.associate = (models) => {
(ConstitutionalChange as any).associate = (models: any) => {
ConstitutionalChange.belongsTo(models.Outlet, {
foreignKey: 'outletId',
as: 'outlet'

View File

@ -0,0 +1,89 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface DealerAttributes {
id: string;
applicationId: string;
dealerCodeId: string | null;
legalName: string;
businessName: string;
constitutionType: string;
registeredAddress: string | null;
gstNumber: string | null;
panNumber: string | null;
status: string;
onboardedAt: Date | null;
}
export interface DealerInstance extends Model<DealerAttributes>, DealerAttributes { }
export default (sequelize: Sequelize) => {
const Dealer = sequelize.define<DealerInstance>('Dealer', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
dealerCodeId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'dealer_codes',
key: 'id'
}
},
legalName: {
type: DataTypes.STRING,
allowNull: false
},
businessName: {
type: DataTypes.STRING,
allowNull: false
},
constitutionType: {
type: DataTypes.STRING,
allowNull: false
},
registeredAddress: {
type: DataTypes.TEXT,
allowNull: true
},
gstNumber: {
type: DataTypes.STRING,
allowNull: true
},
panNumber: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'active'
},
onboardedAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
tableName: 'dealers',
timestamps: true
});
(Dealer as any).associate = (models: any) => {
Dealer.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
Dealer.belongsTo(models.DealerCode, { foreignKey: 'dealerCodeId', as: 'dealerCode' });
Dealer.hasMany(models.Document, { foreignKey: 'dealerId', as: 'documents' });
Dealer.hasMany(models.Resignation, { foreignKey: 'dealerId', as: 'resignations' });
Dealer.hasMany(models.TerminationRequest, { foreignKey: 'dealerId', as: 'terminationRequests' });
};
return Dealer;
};

View File

@ -0,0 +1,53 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface DealerCodeAttribute {
id: string;
dealerCode: string;
status: string;
generatedAt: Date;
generatedBy: string | null;
}
export interface DealerCodeInstance extends Model<DealerCodeAttribute>, DealerCodeAttribute { }
export default (sequelize: Sequelize) => {
const DealerCode = sequelize.define<DealerCodeInstance>('DealerCode', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
dealerCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
status: {
type: DataTypes.STRING,
defaultValue: 'active'
},
generatedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
generatedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'dealer_codes',
timestamps: true,
updatedAt: false
});
(DealerCode as any).associate = (models: any) => {
DealerCode.belongsTo(models.User, { foreignKey: 'generatedBy', as: 'generator' });
DealerCode.hasOne(models.Dealer, { foreignKey: 'dealerCodeId', as: 'dealer' });
};
return DealerCode;
};

View File

@ -0,0 +1,53 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface DistrictAttributes {
id: string;
stateId: string;
districtName: string;
isActive: boolean;
}
export interface DistrictInstance extends Model<DistrictAttributes>, DistrictAttributes { }
export default (sequelize: Sequelize) => {
const District = sequelize.define<DistrictInstance>('District', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
stateId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'states',
key: 'id'
}
},
districtName: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'districts',
timestamps: true,
updatedAt: false
});
(District as any).associate = (models: any) => {
District.belongsTo(models.State, {
foreignKey: 'stateId',
as: 'state'
});
District.hasMany(models.Area, {
foreignKey: 'districtId',
as: 'areas'
});
};
return District;
};

View File

@ -0,0 +1,67 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface DistrictManagerAttributes {
id: string;
districtId: string;
userId: string;
managerType: string;
isActive: boolean;
assignedAt: Date;
}
export interface DistrictManagerInstance extends Model<DistrictManagerAttributes>, DistrictManagerAttributes { }
export default (sequelize: Sequelize) => {
const DistrictManager = sequelize.define<DistrictManagerInstance>('DistrictManager', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
districtId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'districts',
key: 'id'
}
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
managerType: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
assignedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'district_managers',
timestamps: true,
updatedAt: false
});
(DistrictManager as any).associate = (models: any) => {
DistrictManager.belongsTo(models.District, {
foreignKey: 'districtId',
as: 'district'
});
DistrictManager.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
};
return DistrictManager;
};

View File

@ -1,64 +0,0 @@
const { REQUEST_TYPES, DOCUMENT_TYPES } = require('../../common/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;
};

View File

@ -0,0 +1,104 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { REQUEST_TYPES, DOCUMENT_TYPES } from '../../common/config/constants.js';
export interface DocumentAttributes {
id: string;
applicationId: string | null;
dealerId: string | null;
requestId: string | null; // Compatibility
requestType: string | null; // Compatibility
documentType: string;
fileName: string;
filePath: string;
fileSize: number | null;
mimeType: string | null;
status: string;
uploadedBy: string | null;
}
export interface DocumentInstance extends Model<DocumentAttributes>, DocumentAttributes { }
export default (sequelize: Sequelize) => {
const Document = sequelize.define<DocumentInstance>('Document', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'applications',
key: 'id'
}
},
dealerId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'dealers',
key: 'id'
}
},
requestId: {
type: DataTypes.UUID,
allowNull: true
},
requestType: {
type: DataTypes.STRING,
allowNull: true
},
documentType: {
type: DataTypes.STRING,
allowNull: false
},
fileName: {
type: DataTypes.STRING,
allowNull: false
},
filePath: {
type: DataTypes.STRING,
allowNull: false
},
fileSize: {
type: DataTypes.INTEGER,
allowNull: true
},
mimeType: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'active'
},
uploadedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'documents',
timestamps: true,
indexes: [
{ fields: ['applicationId'] },
{ fields: ['dealerId'] },
{ fields: ['requestId'] }
]
});
(Document as any).associate = (models: any) => {
Document.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' });
Document.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
Document.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' });
Document.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions' });
Document.hasMany(models.WorkNoteAttachment, { foreignKey: 'documentId', as: 'workNoteAttachments' });
};
return Document;
};

View File

@ -0,0 +1,56 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface DocumentVersionAttributes {
id: string;
documentId: string;
versionNumber: number;
filePath: string;
uploadedBy: string | null;
}
export interface DocumentVersionInstance extends Model<DocumentVersionAttributes>, DocumentVersionAttributes { }
export default (sequelize: Sequelize) => {
const DocumentVersion = sequelize.define<DocumentVersionInstance>('DocumentVersion', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
documentId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'documents',
key: 'id'
}
},
versionNumber: {
type: DataTypes.INTEGER,
allowNull: false
},
filePath: {
type: DataTypes.STRING,
allowNull: false
},
uploadedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'document_versions',
timestamps: true,
updatedAt: false
});
(DocumentVersion as any).associate = (models: any) => {
DocumentVersion.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' });
DocumentVersion.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' });
};
return DocumentVersion;
};

View File

@ -0,0 +1,53 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface EmailTemplateAttributes {
id: string;
templateCode: string;
description: string;
subject: string;
body: string;
placeholders: any; // JSON array of strings
isActive: boolean;
}
export interface EmailTemplateInstance extends Model<EmailTemplateAttributes>, EmailTemplateAttributes { }
export default (sequelize: Sequelize) => {
const EmailTemplate = sequelize.define<EmailTemplateInstance>('EmailTemplate', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
templateCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
description: {
type: DataTypes.STRING,
allowNull: false
},
subject: {
type: DataTypes.STRING,
allowNull: false
},
body: {
type: DataTypes.TEXT,
allowNull: false
},
placeholders: {
type: DataTypes.JSON,
defaultValue: []
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'email_templates',
timestamps: true
});
return EmailTemplate;
};

View File

@ -0,0 +1,62 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface EorChecklistAttributes {
id: string;
applicationId: string;
auditorId: string | null;
auditDate: Date | null;
status: string;
overallComments: string | null;
}
export interface EorChecklistInstance extends Model<EorChecklistAttributes>, EorChecklistAttributes { }
export default (sequelize: Sequelize) => {
const EorChecklist = sequelize.define<EorChecklistInstance>('EorChecklist', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
auditorId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
auditDate: {
type: DataTypes.DATE,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
overallComments: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'eor_checklists',
timestamps: true
});
(EorChecklist as any).associate = (models: any) => {
EorChecklist.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
EorChecklist.belongsTo(models.User, { foreignKey: 'auditorId', as: 'auditor' });
EorChecklist.hasMany(models.EorChecklistItem, { foreignKey: 'checklistId', as: 'items' });
};
return EorChecklist;
};

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface EorChecklistItemAttributes {
id: string;
checklistId: string;
itemType: string;
description: string;
isCompliant: boolean;
remarks: string | null;
proofDocumentId: string | null;
}
export interface EorChecklistItemInstance extends Model<EorChecklistItemAttributes>, EorChecklistItemAttributes { }
export default (sequelize: Sequelize) => {
const EorChecklistItem = sequelize.define<EorChecklistItemInstance>('EorChecklistItem', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
checklistId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'eor_checklists',
key: 'id'
}
},
itemType: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false
},
isCompliant: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
},
proofDocumentId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'documents',
key: 'id'
}
}
}, {
tableName: 'eor_checklist_items',
timestamps: true
});
(EorChecklistItem as any).associate = (models: any) => {
EorChecklistItem.belongsTo(models.EorChecklist, { foreignKey: 'checklistId', as: 'checklist' });
EorChecklistItem.belongsTo(models.Document, { foreignKey: 'proofDocumentId', as: 'proofDocument' });
};
return EorChecklistItem;
};

View File

@ -0,0 +1,76 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ExitFeedbackAttributes {
id: string;
resignationId: string | null;
terminationRequestId: string | null;
feedbackType: string;
ratings: any; // JSON
comments: string | null;
submittedAt: Date;
submittedBy: string | null;
}
export interface ExitFeedbackInstance extends Model<ExitFeedbackAttributes>, ExitFeedbackAttributes { }
export default (sequelize: Sequelize) => {
const ExitFeedback = sequelize.define<ExitFeedbackInstance>('ExitFeedback', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
resignationId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'resignations',
key: 'id'
}
},
terminationRequestId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'termination_requests',
key: 'id'
}
},
feedbackType: {
type: DataTypes.STRING,
allowNull: false
},
ratings: {
type: DataTypes.JSON,
defaultValue: {}
},
comments: {
type: DataTypes.TEXT,
allowNull: true
},
submittedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
submittedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'exit_feedbacks',
timestamps: true,
updatedAt: false
});
(ExitFeedback as any).associate = (models: any) => {
ExitFeedback.belongsTo(models.Resignation, { foreignKey: 'resignationId', as: 'resignation' });
ExitFeedback.belongsTo(models.TerminationRequest, { foreignKey: 'terminationRequestId', as: 'terminationRequest' });
ExitFeedback.belongsTo(models.User, { foreignKey: 'submittedBy', as: 'submitter' });
};
return ExitFeedback;
};

View File

@ -0,0 +1,51 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface FddAssignmentAttributes {
id: string;
applicationId: string;
assignedToAgency: string | null;
status: string;
}
export interface FddAssignmentInstance extends Model<FddAssignmentAttributes>, FddAssignmentAttributes { }
export default (sequelize: Sequelize) => {
const FddAssignment = sequelize.define<FddAssignmentInstance>('FddAssignment', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
assignedToAgency: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
}
}, {
tableName: 'fdd_assignments',
timestamps: true
});
(FddAssignment as any).associate = (models: any) => {
FddAssignment.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
FddAssignment.belongsTo(models.User, { foreignKey: 'assignedToAgency', as: 'agency' });
FddAssignment.hasMany(models.FddReport, { foreignKey: 'assignmentId', as: 'reports' });
};
return FddAssignment;
};

View File

@ -0,0 +1,70 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface FddReportAttributes {
id: string;
assignmentId: string;
reportDocumentId: string | null;
findings: string | null;
recommendation: string | null;
verifiedAt: Date | null;
verifiedBy: string | null;
}
export interface FddReportInstance extends Model<FddReportAttributes>, FddReportAttributes { }
export default (sequelize: Sequelize) => {
const FddReport = sequelize.define<FddReportInstance>('FddReport', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
assignmentId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'fdd_assignments',
key: 'id'
}
},
reportDocumentId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'documents',
key: 'id'
}
},
findings: {
type: DataTypes.TEXT,
allowNull: true
},
recommendation: {
type: DataTypes.STRING,
allowNull: true
},
verifiedAt: {
type: DataTypes.DATE,
allowNull: true
},
verifiedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'fdd_reports',
timestamps: true
});
(FddReport as any).associate = (models: any) => {
FddReport.belongsTo(models.FddAssignment, { foreignKey: 'assignmentId', as: 'assignment' });
FddReport.belongsTo(models.Document, { foreignKey: 'reportDocumentId', as: 'reportDocument' });
FddReport.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' });
};
return FddReport;
};

View File

@ -1,7 +1,23 @@
const { PAYMENT_TYPES, PAYMENT_STATUS } = require('../../common/config/constants');
import { Model, DataTypes, Sequelize } from 'sequelize';
import { PAYMENT_TYPES, PAYMENT_STATUS } from '../../common/config/constants.js';
module.exports = (sequelize, DataTypes) => {
const FinancePayment = sequelize.define('FinancePayment', {
export interface FinancePaymentAttributes {
id: string;
applicationId: string;
paymentType: typeof PAYMENT_TYPES[keyof typeof PAYMENT_TYPES];
amount: number;
paymentStatus: typeof PAYMENT_STATUS[keyof typeof PAYMENT_STATUS];
transactionId: string | null;
paymentDate: Date | null;
verifiedBy: string | null;
verificationDate: Date | null;
remarks: string | null;
}
export interface FinancePaymentInstance extends Model<FinancePaymentAttributes>, FinancePaymentAttributes { }
export default (sequelize: Sequelize) => {
const FinancePayment = sequelize.define<FinancePaymentInstance>('FinancePayment', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -16,7 +32,7 @@ module.exports = (sequelize, DataTypes) => {
}
},
paymentType: {
type: DataTypes.ENUM(Object.values(PAYMENT_TYPES)),
type: DataTypes.ENUM(...Object.values(PAYMENT_TYPES)),
allowNull: false
},
amount: {
@ -24,7 +40,7 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false
},
paymentStatus: {
type: DataTypes.ENUM(Object.values(PAYMENT_STATUS)),
type: DataTypes.ENUM(...Object.values(PAYMENT_STATUS)),
defaultValue: PAYMENT_STATUS.PENDING
},
transactionId: {
@ -60,7 +76,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
FinancePayment.associate = (models) => {
(FinancePayment as any).associate = (models: any) => {
FinancePayment.belongsTo(models.Application, {
foreignKey: 'applicationId',
as: 'application'

View File

@ -1,72 +0,0 @@
const { FNF_STATUS } = require('../../common/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;
};

103
src/database/models/FnF.ts Normal file
View File

@ -0,0 +1,103 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { FNF_STATUS } from '../../common/config/constants.js';
export interface FnFAttributes {
id: string;
resignationId: string | null;
terminationRequestId: string | null;
outletId: string | null;
dealerId: string | null; // For direct dealer level F&F
status: string;
totalReceivables: number;
totalPayables: number;
netAmount: number;
settlementDate: Date | null;
clearanceDocuments: any[];
}
export interface FnFInstance extends Model<FnFAttributes>, FnFAttributes { }
export default (sequelize: Sequelize) => {
const FnF = sequelize.define<FnFInstance>('FnF', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
resignationId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'resignations',
key: 'id'
}
},
terminationRequestId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'termination_requests',
key: 'id'
}
},
outletId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'outlets',
key: 'id'
}
},
dealerId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'dealers',
key: 'id'
}
},
status: {
type: DataTypes.STRING,
defaultValue: '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: ['terminationRequestId'] },
{ fields: ['outletId'] },
{ fields: ['status'] }
]
});
(FnF as any).associate = (models: any) => {
FnF.belongsTo(models.Resignation, { foreignKey: 'resignationId', as: 'resignation' });
FnF.belongsTo(models.TerminationRequest, { foreignKey: 'terminationRequestId', as: 'terminationRequest' });
FnF.belongsTo(models.Outlet, { foreignKey: 'outletId', as: 'outlet' });
FnF.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' });
FnF.hasMany(models.FnFLineItem, { foreignKey: 'fnfId', as: 'lineItems' });
};
return FnF;
};

View File

@ -1,5 +1,19 @@
module.exports = (sequelize, DataTypes) => {
const FnFLineItem = sequelize.define('FnFLineItem', {
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface FnFLineItemAttributes {
id: string;
fnfId: string;
itemType: 'Payable' | 'Receivable' | 'Deduction';
description: string;
department: string;
amount: number;
addedBy: string | null;
}
export interface FnFLineItemInstance extends Model<FnFLineItemAttributes>, FnFLineItemAttributes { }
export default (sequelize: Sequelize) => {
const FnFLineItem = sequelize.define<FnFLineItemInstance>('FnFLineItem', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -48,7 +62,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
FnFLineItem.associate = (models) => {
(FnFLineItem as any).associate = (models: any) => {
FnFLineItem.belongsTo(models.FnF, {
foreignKey: 'fnfId',
as: 'settlement'

View File

@ -0,0 +1,62 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface InterviewAttributes {
id: string;
applicationId: string;
level: number;
scheduleDate: Date | null;
interviewType: string;
linkOrLocation: string | null;
status: string;
}
export interface InterviewInstance extends Model<InterviewAttributes>, InterviewAttributes { }
export default (sequelize: Sequelize) => {
const Interview = sequelize.define<InterviewInstance>('Interview', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
level: {
type: DataTypes.INTEGER,
allowNull: false
},
scheduleDate: {
type: DataTypes.DATE,
allowNull: true
},
interviewType: {
type: DataTypes.STRING,
allowNull: false
},
linkOrLocation: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'scheduled'
}
}, {
tableName: 'interviews',
timestamps: true
});
(Interview as any).associate = (models: any) => {
Interview.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
Interview.hasMany(models.InterviewParticipant, { foreignKey: 'interviewId', as: 'participants' });
Interview.hasMany(models.InterviewEvaluation, { foreignKey: 'interviewId', as: 'evaluations' });
};
return Interview;
};

View File

@ -0,0 +1,62 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface InterviewEvaluationAttributes {
id: string;
interviewId: string;
evaluatorId: string;
ktMatrixScore: number | null;
qualitativeFeedback: string | null;
recommendation: string | null;
}
export interface InterviewEvaluationInstance extends Model<InterviewEvaluationAttributes>, InterviewEvaluationAttributes { }
export default (sequelize: Sequelize) => {
const InterviewEvaluation = sequelize.define<InterviewEvaluationInstance>('InterviewEvaluation', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
interviewId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'interviews',
key: 'id'
}
},
evaluatorId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
ktMatrixScore: {
type: DataTypes.DECIMAL(10, 2),
allowNull: true
},
qualitativeFeedback: {
type: DataTypes.TEXT,
allowNull: true
},
recommendation: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'interview_evaluations',
timestamps: true
});
(InterviewEvaluation as any).associate = (models: any) => {
InterviewEvaluation.belongsTo(models.Interview, { foreignKey: 'interviewId', as: 'interview' });
InterviewEvaluation.belongsTo(models.User, { foreignKey: 'evaluatorId', as: 'evaluator' });
InterviewEvaluation.hasMany(models.KTMatrixScore, { foreignKey: 'evaluationId', as: 'criterionScores' });
InterviewEvaluation.hasMany(models.InterviewFeedback, { foreignKey: 'evaluationId', as: 'feedbackDetails' });
};
return InterviewEvaluation;
};

View File

@ -0,0 +1,46 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface InterviewFeedbackAttributes {
id: string;
evaluationId: string;
feedbackType: string;
comments: string;
}
export interface InterviewFeedbackInstance extends Model<InterviewFeedbackAttributes>, InterviewFeedbackAttributes { }
export default (sequelize: Sequelize) => {
const InterviewFeedback = sequelize.define<InterviewFeedbackInstance>('InterviewFeedback', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
evaluationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'interview_evaluations',
key: 'id'
}
},
feedbackType: {
type: DataTypes.STRING,
allowNull: false
},
comments: {
type: DataTypes.TEXT,
allowNull: false
}
}, {
tableName: 'interview_feedback',
timestamps: true,
updatedAt: false
});
(InterviewFeedback as any).associate = (models: any) => {
InterviewFeedback.belongsTo(models.InterviewEvaluation, { foreignKey: 'evaluationId', as: 'evaluation' });
};
return InterviewFeedback;
};

View File

@ -0,0 +1,51 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface InterviewParticipantAttributes {
id: string;
interviewId: string;
userId: string;
roleInPanel: string | null;
}
export interface InterviewParticipantInstance extends Model<InterviewParticipantAttributes>, InterviewParticipantAttributes { }
export default (sequelize: Sequelize) => {
const InterviewParticipant = sequelize.define<InterviewParticipantInstance>('InterviewParticipant', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
interviewId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'interviews',
key: 'id'
}
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
roleInPanel: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'interview_participants',
timestamps: true,
updatedAt: false
});
(InterviewParticipant as any).associate = (models: any) => {
InterviewParticipant.belongsTo(models.Interview, { foreignKey: 'interviewId', as: 'interview' });
InterviewParticipant.belongsTo(models.User, { foreignKey: 'userId', as: 'user' });
};
return InterviewParticipant;
};

View File

@ -0,0 +1,61 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface KTMatrixScoreAttributes {
id: string;
evaluationId: string;
criterionName: string;
score: number;
maxScore: number;
weightage: number;
weightedScore: number;
}
export interface KTMatrixScoreInstance extends Model<KTMatrixScoreAttributes>, KTMatrixScoreAttributes { }
export default (sequelize: Sequelize) => {
const KTMatrixScore = sequelize.define<KTMatrixScoreInstance>('KTMatrixScore', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
evaluationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'interview_evaluations',
key: 'id'
}
},
criterionName: {
type: DataTypes.STRING,
allowNull: false
},
score: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
maxScore: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
weightage: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
weightedScore: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
}
}, {
tableName: 'kt_matrix_scores',
timestamps: true,
updatedAt: false
});
(KTMatrixScore as any).associate = (models: any) => {
KTMatrixScore.belongsTo(models.InterviewEvaluation, { foreignKey: 'evaluationId', as: 'evaluation' });
};
return KTMatrixScore;
};

View File

@ -0,0 +1,56 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoaAcknowledgementAttributes {
id: string;
loaDocId: string;
applicantId: string;
acknowledgedAt: Date;
remarks: string | null;
}
export interface LoaAcknowledgementInstance extends Model<LoaAcknowledgementAttributes>, LoaAcknowledgementAttributes { }
export default (sequelize: Sequelize) => {
const LoaAcknowledgement = sequelize.define<LoaAcknowledgementInstance>('LoaAcknowledgement', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
loaDocId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'loa_documents_generated',
key: 'id'
}
},
applicantId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
acknowledgedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'loa_acknowledgements',
timestamps: true,
updatedAt: false
});
(LoaAcknowledgement as any).associate = (models: any) => {
LoaAcknowledgement.belongsTo(models.LoaDocumentGenerated, { foreignKey: 'loaDocId', as: 'loaDocument' });
LoaAcknowledgement.belongsTo(models.User, { foreignKey: 'applicantId', as: 'applicant' });
};
return LoaAcknowledgement;
};

View File

@ -0,0 +1,66 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoaApprovalAttributes {
id: string;
requestId: string;
level: number;
approverRole: string;
approverId: string | null;
action: string;
remarks: string | null;
}
export interface LoaApprovalInstance extends Model<LoaApprovalAttributes>, LoaApprovalAttributes { }
export default (sequelize: Sequelize) => {
const LoaApproval = sequelize.define<LoaApprovalInstance>('LoaApproval', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
requestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'loa_requests',
key: 'id'
}
},
level: {
type: DataTypes.INTEGER,
allowNull: false
},
approverRole: {
type: DataTypes.STRING,
allowNull: false
},
approverId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'loa_approvals',
timestamps: true,
updatedAt: false
});
(LoaApproval as any).associate = (models: any) => {
LoaApproval.belongsTo(models.LoaRequest, { foreignKey: 'requestId', as: 'request' });
LoaApproval.belongsTo(models.User, { foreignKey: 'approverId', as: 'approver' });
};
return LoaApproval;
};

View File

@ -0,0 +1,57 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoaDocumentGeneratedAttributes {
id: string;
requestId: string;
documentId: string;
version: string;
generatedAt: Date;
}
export interface LoaDocumentGeneratedInstance extends Model<LoaDocumentGeneratedAttributes>, LoaDocumentGeneratedAttributes { }
export default (sequelize: Sequelize) => {
const LoaDocumentGenerated = sequelize.define<LoaDocumentGeneratedInstance>('LoaDocumentGenerated', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
requestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'loa_requests',
key: 'id'
}
},
documentId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'documents',
key: 'id'
}
},
version: {
type: DataTypes.STRING,
allowNull: false
},
generatedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'loa_documents_generated',
timestamps: true,
updatedAt: false
});
(LoaDocumentGenerated as any).associate = (models: any) => {
LoaDocumentGenerated.belongsTo(models.LoaRequest, { foreignKey: 'requestId', as: 'request' });
LoaDocumentGenerated.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' });
LoaDocumentGenerated.hasMany(models.LoaAcknowledgement, { foreignKey: 'loaDocId', as: 'acknowledgements' });
};
return LoaDocumentGenerated;
};

View File

@ -0,0 +1,68 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoaRequestAttributes {
id: string;
applicationId: string;
status: string;
requestedBy: string | null;
approvedAt: Date | null;
approvedBy: string | null;
}
export interface LoaRequestInstance extends Model<LoaRequestAttributes>, LoaRequestAttributes { }
export default (sequelize: Sequelize) => {
const LoaRequest = sequelize.define<LoaRequestInstance>('LoaRequest', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
requestedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
approvedAt: {
type: DataTypes.DATE,
allowNull: true
},
approvedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'loa_requests',
timestamps: true
});
(LoaRequest as any).associate = (models: any) => {
LoaRequest.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
LoaRequest.belongsTo(models.User, { foreignKey: 'requestedBy', as: 'requester' });
LoaRequest.belongsTo(models.User, { foreignKey: 'approvedBy', as: 'approver' });
LoaRequest.hasMany(models.LoaApproval, { foreignKey: 'requestId', as: 'approvals' });
LoaRequest.hasMany(models.LoaDocumentGenerated, { foreignKey: 'requestId', as: 'generatedDocuments' });
};
return LoaRequest;
};

View File

@ -0,0 +1,56 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoiAcknowledgementAttributes {
id: string;
loiDocId: string;
applicantId: string;
acknowledgedAt: Date;
remarks: string | null;
}
export interface LoiAcknowledgementInstance extends Model<LoiAcknowledgementAttributes>, LoiAcknowledgementAttributes { }
export default (sequelize: Sequelize) => {
const LoiAcknowledgement = sequelize.define<LoiAcknowledgementInstance>('LoiAcknowledgement', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
loiDocId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'loi_documents_generated',
key: 'id'
}
},
applicantId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users', // Applicant is a user
key: 'id'
}
},
acknowledgedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'loi_acknowledgements',
timestamps: true,
updatedAt: false
});
(LoiAcknowledgement as any).associate = (models: any) => {
LoiAcknowledgement.belongsTo(models.LoiDocumentGenerated, { foreignKey: 'loiDocId', as: 'loiDocument' });
LoiAcknowledgement.belongsTo(models.User, { foreignKey: 'applicantId', as: 'applicant' });
};
return LoiAcknowledgement;
};

View File

@ -0,0 +1,66 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoiApprovalAttributes {
id: string;
requestId: string;
level: number;
approverRole: string;
approverId: string | null;
action: string;
remarks: string | null;
}
export interface LoiApprovalInstance extends Model<LoiApprovalAttributes>, LoiApprovalAttributes { }
export default (sequelize: Sequelize) => {
const LoiApproval = sequelize.define<LoiApprovalInstance>('LoiApproval', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
requestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'loi_requests',
key: 'id'
}
},
level: {
type: DataTypes.INTEGER,
allowNull: false
},
approverRole: {
type: DataTypes.STRING,
allowNull: false
},
approverId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'loi_approvals',
timestamps: true,
updatedAt: false
});
(LoiApproval as any).associate = (models: any) => {
LoiApproval.belongsTo(models.LoiRequest, { foreignKey: 'requestId', as: 'request' });
LoiApproval.belongsTo(models.User, { foreignKey: 'approverId', as: 'approver' });
};
return LoiApproval;
};

View File

@ -0,0 +1,57 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoiDocumentGeneratedAttributes {
id: string;
requestId: string;
documentId: string;
version: string;
generatedAt: Date;
}
export interface LoiDocumentGeneratedInstance extends Model<LoiDocumentGeneratedAttributes>, LoiDocumentGeneratedAttributes { }
export default (sequelize: Sequelize) => {
const LoiDocumentGenerated = sequelize.define<LoiDocumentGeneratedInstance>('LoiDocumentGenerated', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
requestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'loi_requests',
key: 'id'
}
},
documentId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'documents',
key: 'id'
}
},
version: {
type: DataTypes.STRING,
allowNull: false
},
generatedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'loi_documents_generated',
timestamps: true,
updatedAt: false
});
(LoiDocumentGenerated as any).associate = (models: any) => {
LoiDocumentGenerated.belongsTo(models.LoiRequest, { foreignKey: 'requestId', as: 'request' });
LoiDocumentGenerated.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' });
LoiDocumentGenerated.hasMany(models.LoiAcknowledgement, { foreignKey: 'loiDocId', as: 'acknowledgements' });
};
return LoiDocumentGenerated;
};

View File

@ -0,0 +1,68 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface LoiRequestAttributes {
id: string;
applicationId: string;
status: string;
requestedBy: string | null;
approvedAt: Date | null;
approvedBy: string | null;
}
export interface LoiRequestInstance extends Model<LoiRequestAttributes>, LoiRequestAttributes { }
export default (sequelize: Sequelize) => {
const LoiRequest = sequelize.define<LoiRequestInstance>('LoiRequest', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
requestedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
approvedAt: {
type: DataTypes.DATE,
allowNull: true
},
approvedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'loi_requests',
timestamps: true
});
(LoiRequest as any).associate = (models: any) => {
LoiRequest.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
LoiRequest.belongsTo(models.User, { foreignKey: 'requestedBy', as: 'requester' });
LoiRequest.belongsTo(models.User, { foreignKey: 'approvedBy', as: 'approver' });
LoiRequest.hasMany(models.LoiApproval, { foreignKey: 'requestId', as: 'approvals' });
LoiRequest.hasMany(models.LoiDocumentGenerated, { foreignKey: 'requestId', as: 'generatedDocuments' });
};
return LoiRequest;
};

View File

@ -1,5 +1,19 @@
module.exports = (sequelize, DataTypes) => {
const Notification = sequelize.define('Notification', {
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface NotificationAttributes {
id: string;
userId: string;
title: string;
message: string;
type: string;
link: string | null;
isRead: boolean;
}
export interface NotificationInstance extends Model<NotificationAttributes>, NotificationAttributes { }
export default (sequelize: Sequelize) => {
const Notification = sequelize.define<NotificationInstance>('Notification', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -42,7 +56,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
Notification.associate = (models) => {
(Notification as any).associate = (models: any) => {
Notification.belongsTo(models.User, {
foreignKey: 'userId',
as: 'recipient'

View File

@ -0,0 +1,116 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface OpportunityAttributes {
id: string;
zoneId: string;
regionId: string;
stateId: string | null;
districtId: string | null;
city: string;
opportunityType: string;
capacity: string;
priority: string;
openFrom: Date | null;
openTo: Date | null;
status: string;
notes: string | null;
createdBy: string | null;
}
export interface OpportunityInstance extends Model<OpportunityAttributes>, OpportunityAttributes { }
export default (sequelize: Sequelize) => {
const Opportunity = sequelize.define<OpportunityInstance>('Opportunity', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
zoneId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'zones',
key: 'id'
}
},
regionId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'regions',
key: 'id'
}
},
stateId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'states',
key: 'id'
}
},
districtId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'districts',
key: 'id'
}
},
city: {
type: DataTypes.STRING,
allowNull: false
},
opportunityType: {
type: DataTypes.STRING,
allowNull: false
},
capacity: {
type: DataTypes.STRING,
allowNull: false
},
priority: {
type: DataTypes.STRING,
allowNull: false
},
openFrom: {
type: DataTypes.DATE,
allowNull: true
},
openTo: {
type: DataTypes.DATE,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'active'
},
notes: {
type: DataTypes.TEXT,
allowNull: true
},
createdBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'opportunities',
timestamps: true
});
(Opportunity as any).associate = (models: any) => {
Opportunity.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
Opportunity.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
Opportunity.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' });
Opportunity.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' });
Opportunity.belongsTo(models.User, { foreignKey: 'createdBy', as: 'creator' });
Opportunity.hasMany(models.Application, { foreignKey: 'opportunityId', as: 'applications' });
};
return Opportunity;
};

View File

@ -1,104 +0,0 @@
const { OUTLET_TYPES, OUTLET_STATUS, REGIONS } = require('../../common/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;
};

View File

@ -0,0 +1,125 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { OUTLET_TYPES, OUTLET_STATUS, REGIONS } from '../../common/config/constants.js';
export interface OutletAttributes {
id: string;
code: string;
name: string;
type: typeof OUTLET_TYPES[keyof typeof OUTLET_TYPES];
address: string;
city: string;
state: string;
pincode: string;
latitude: number | null;
longitude: number | null;
status: typeof OUTLET_STATUS[keyof typeof OUTLET_STATUS];
establishedDate: string;
dealerId: string;
region: typeof REGIONS[keyof typeof REGIONS];
zone: string;
}
export interface OutletInstance extends Model<OutletAttributes>, OutletAttributes { }
export default (sequelize: Sequelize) => {
const Outlet = sequelize.define<OutletInstance>('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 as any).associate = (models: any) => {
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;
};

View File

@ -0,0 +1,61 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface PermissionAttributes {
id: string;
permissionCode: string;
permissionName: string;
module: string;
permissionType: string;
action: string;
description: string | null;
}
export interface PermissionInstance extends Model<PermissionAttributes>, PermissionAttributes { }
export default (sequelize: Sequelize) => {
const Permission = sequelize.define<PermissionInstance>('Permission', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
permissionCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
permissionName: {
type: DataTypes.STRING,
allowNull: false
},
module: {
type: DataTypes.STRING,
allowNull: false
},
permissionType: {
type: DataTypes.STRING,
allowNull: false
},
action: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'permissions',
timestamps: true,
updatedAt: false
});
(Permission as any).associate = (models: any) => {
Permission.hasMany(models.RolePermission, {
foreignKey: 'permissionId',
as: 'rolePermissions'
});
};
return Permission;
};

View File

@ -0,0 +1,37 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface QuestionnaireAttributes {
id: string;
version: string;
isActive: boolean;
}
export interface QuestionnaireInstance extends Model<QuestionnaireAttributes>, QuestionnaireAttributes { }
export default (sequelize: Sequelize) => {
const Questionnaire = sequelize.define<QuestionnaireInstance>('Questionnaire', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
version: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'questionnaires',
timestamps: true
});
(Questionnaire as any).associate = (models: any) => {
Questionnaire.hasMany(models.QuestionnaireQuestion, { foreignKey: 'questionnaireId', as: 'questions' });
Questionnaire.hasMany(models.QuestionnaireResponse, { foreignKey: 'questionnaireId', as: 'responses' });
};
return Questionnaire;
};

View File

@ -0,0 +1,62 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface QuestionnaireQuestionAttributes {
id: string;
questionnaireId: string;
sectionName: string;
questionText: string;
inputType: string;
options: any;
isMandatory: boolean;
}
export interface QuestionnaireQuestionInstance extends Model<QuestionnaireQuestionAttributes>, QuestionnaireQuestionAttributes { }
export default (sequelize: Sequelize) => {
const QuestionnaireQuestion = sequelize.define<QuestionnaireQuestionInstance>('QuestionnaireQuestion', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
questionnaireId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'questionnaires',
key: 'id'
}
},
sectionName: {
type: DataTypes.STRING,
allowNull: false
},
questionText: {
type: DataTypes.TEXT,
allowNull: false
},
inputType: {
type: DataTypes.STRING,
allowNull: false
},
options: {
type: DataTypes.JSON,
allowNull: true
},
isMandatory: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'questionnaire_questions',
timestamps: true,
updatedAt: false
});
(QuestionnaireQuestion as any).associate = (models: any) => {
QuestionnaireQuestion.belongsTo(models.Questionnaire, { foreignKey: 'questionnaireId', as: 'questionnaire' });
QuestionnaireQuestion.hasMany(models.QuestionnaireResponse, { foreignKey: 'questionId', as: 'responses' });
};
return QuestionnaireQuestion;
};

View File

@ -0,0 +1,66 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface QuestionnaireResponseAttributes {
id: string;
applicationId: string;
questionnaireId: string;
questionId: string;
responseValue: string | null;
attachmentUrl: string | null;
}
export interface QuestionnaireResponseInstance extends Model<QuestionnaireResponseAttributes>, QuestionnaireResponseAttributes { }
export default (sequelize: Sequelize) => {
const QuestionnaireResponse = sequelize.define<QuestionnaireResponseInstance>('QuestionnaireResponse', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
questionnaireId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'questionnaires',
key: 'id'
}
},
questionId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'questionnaire_questions',
key: 'id'
}
},
responseValue: {
type: DataTypes.TEXT,
allowNull: true
},
attachmentUrl: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'questionnaire_responses',
timestamps: true,
updatedAt: false
});
(QuestionnaireResponse as any).associate = (models: any) => {
QuestionnaireResponse.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
QuestionnaireResponse.belongsTo(models.Questionnaire, { foreignKey: 'questionnaireId', as: 'questionnaire' });
QuestionnaireResponse.belongsTo(models.QuestionnaireQuestion, { foreignKey: 'questionId', as: 'question' });
};
return QuestionnaireResponse;
};

View File

@ -0,0 +1,56 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface QuestionnaireScoreAttributes {
id: string;
applicationId: string;
sectionName: string;
score: number;
weightage: number;
weightedScore: number;
}
export interface QuestionnaireScoreInstance extends Model<QuestionnaireScoreAttributes>, QuestionnaireScoreAttributes { }
export default (sequelize: Sequelize) => {
const QuestionnaireScore = sequelize.define<QuestionnaireScoreInstance>('QuestionnaireScore', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
sectionName: {
type: DataTypes.STRING,
allowNull: false
},
score: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
weightage: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
weightedScore: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
}
}, {
tableName: 'questionnaire_scores',
timestamps: true,
updatedAt: false
});
(QuestionnaireScore as any).associate = (models: any) => {
QuestionnaireScore.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
};
return QuestionnaireScore;
};

View File

@ -1,44 +0,0 @@
const { REGIONS } = require('../../common/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;
};

View File

@ -0,0 +1,84 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface RegionAttributes {
id: string;
zoneId: string;
stateId: string | null;
regionCode: string;
regionName: string;
description: string | null;
isActive: boolean;
}
export interface RegionInstance extends Model<RegionAttributes>, RegionAttributes { }
export default (sequelize: Sequelize) => {
const Region = sequelize.define<RegionInstance>('Region', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
zoneId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'zones',
key: 'id'
}
},
stateId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'states',
key: 'id'
}
},
regionCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
regionName: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'regions',
timestamps: true
});
(Region as any).associate = (models: any) => {
Region.belongsTo(models.Zone, {
foreignKey: 'zoneId',
as: 'zone'
});
Region.belongsTo(models.State, {
foreignKey: 'stateId',
as: 'state'
});
Region.hasMany(models.Area, {
foreignKey: 'regionId',
as: 'areas'
});
Region.hasMany(models.RegionManager, {
foreignKey: 'regionId',
as: 'managers'
});
Region.hasMany(models.Application, {
foreignKey: 'regionId',
as: 'applications'
});
};
return Region;
};

View File

@ -0,0 +1,67 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface RegionManagerAttributes {
id: string;
regionId: string;
userId: string;
managerType: string;
isActive: boolean;
assignedAt: Date;
}
export interface RegionManagerInstance extends Model<RegionManagerAttributes>, RegionManagerAttributes { }
export default (sequelize: Sequelize) => {
const RegionManager = sequelize.define<RegionManagerInstance>('RegionManager', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
regionId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'regions',
key: 'id'
}
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
managerType: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
assignedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'region_managers',
timestamps: true,
updatedAt: false
});
(RegionManager as any).associate = (models: any) => {
RegionManager.belongsTo(models.Region, {
foreignKey: 'regionId',
as: 'region'
});
RegionManager.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
};
return RegionManager;
};

View File

@ -1,7 +1,27 @@
const { RELOCATION_TYPES, RELOCATION_STAGES } = require('../../common/config/constants');
import { Model, DataTypes, Sequelize } from 'sequelize';
import { RELOCATION_TYPES, RELOCATION_STAGES } from '../../common/config/constants.js';
module.exports = (sequelize, DataTypes) => {
const RelocationRequest = sequelize.define('RelocationRequest', {
export interface RelocationRequestAttributes {
id: string;
requestId: string;
outletId: string;
dealerId: string;
relocationType: typeof RELOCATION_TYPES[keyof typeof RELOCATION_TYPES];
newAddress: string;
newCity: string;
newState: string;
reason: string;
currentStage: typeof RELOCATION_STAGES[keyof typeof RELOCATION_STAGES];
status: string;
progressPercentage: number;
documents: any[];
timeline: any[];
}
export interface RelocationRequestInstance extends Model<RelocationRequestAttributes>, RelocationRequestAttributes { }
export default (sequelize: Sequelize) => {
const RelocationRequest = sequelize.define<RelocationRequestInstance>('RelocationRequest', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -29,7 +49,7 @@ module.exports = (sequelize, DataTypes) => {
}
},
relocationType: {
type: DataTypes.ENUM(Object.values(RELOCATION_TYPES)),
type: DataTypes.ENUM(...Object.values(RELOCATION_TYPES)),
allowNull: false
},
newAddress: {
@ -49,7 +69,7 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false
},
currentStage: {
type: DataTypes.ENUM(Object.values(RELOCATION_STAGES)),
type: DataTypes.ENUM(...Object.values(RELOCATION_STAGES)),
defaultValue: RELOCATION_STAGES.DD_ADMIN_REVIEW
},
status: {
@ -79,7 +99,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
RelocationRequest.associate = (models) => {
(RelocationRequest as any).associate = (models: any) => {
RelocationRequest.belongsTo(models.Outlet, {
foreignKey: 'outletId',
as: 'outlet'

View File

@ -1,110 +0,0 @@
const { RESIGNATION_TYPES, RESIGNATION_STAGES } = require('../../common/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;
};

View File

@ -0,0 +1,132 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { RESIGNATION_TYPES, RESIGNATION_STAGES } from '../../common/config/constants.js';
export interface ResignationAttributes {
id: string;
resignationId: string;
outletId: string;
dealerId: string;
resignationType: typeof RESIGNATION_TYPES[keyof typeof RESIGNATION_TYPES];
lastOperationalDateSales: string;
lastOperationalDateServices: string;
reason: string;
additionalInfo: string | null;
currentStage: typeof RESIGNATION_STAGES[keyof typeof RESIGNATION_STAGES];
status: string;
progressPercentage: number;
submittedOn: Date;
documents: any[];
timeline: any[];
rejectionReason: string | null;
}
export interface ResignationInstance extends Model<ResignationAttributes>, ResignationAttributes { }
export default (sequelize: Sequelize) => {
const Resignation = sequelize.define<ResignationInstance>('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 as any).associate = (models: any) => {
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;
};

View File

@ -0,0 +1,59 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface RoleAttributes {
id: string;
roleCode: string;
roleName: string;
description: string | null;
category: string | null;
isActive: boolean;
}
export interface RoleInstance extends Model<RoleAttributes>, RoleAttributes { }
export default (sequelize: Sequelize) => {
const Role = sequelize.define<RoleInstance>('Role', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
roleCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
roleName: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
category: {
type: DataTypes.STRING,
allowNull: true
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'roles',
timestamps: true
});
(Role as any).associate = (models: any) => {
Role.hasMany(models.UserRole, {
foreignKey: 'roleId',
as: 'userRoles'
});
Role.hasMany(models.RolePermission, {
foreignKey: 'roleId',
as: 'permissions'
});
};
return Role;
};

View File

@ -0,0 +1,77 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
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>, RolePermissionAttributes { }
export default (sequelize: Sequelize) => {
const RolePermission = sequelize.define<RolePermissionInstance>('RolePermission', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
roleId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'roles',
key: 'id'
}
},
permissionId: {
type: DataTypes.UUID,
allowNull: false,
references: {
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',
timestamps: true,
updatedAt: false
});
(RolePermission as any).associate = (models: any) => {
RolePermission.belongsTo(models.Role, {
foreignKey: 'roleId',
as: 'role'
});
RolePermission.belongsTo(models.Permission, {
foreignKey: 'permissionId',
as: 'permission'
});
};
return RolePermission;
};

View File

@ -0,0 +1,56 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface SLABreachAttributes {
id: string;
trackingId: string;
breachedAt: Date;
notifiedTo: string | null; // Email or User ID
status: string; // Open, Acknowledged, Resolved
actionTaken: string | null;
}
export interface SLABreachInstance extends Model<SLABreachAttributes>, SLABreachAttributes { }
export default (sequelize: Sequelize) => {
const SLABreach = sequelize.define<SLABreachInstance>('SLABreach', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
trackingId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'sla_tracking',
key: 'id'
}
},
breachedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
notifiedTo: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'Open'
},
actionTaken: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'sla_breaches',
timestamps: true,
updatedAt: false
});
(SLABreach as any).associate = (models: any) => {
SLABreach.belongsTo(models.SLATracking, { foreignKey: 'trackingId', as: 'slaTracking' });
};
return SLABreach;
};

View File

@ -1,5 +1,18 @@
module.exports = (sequelize, DataTypes) => {
const SLAConfiguration = sequelize.define('SLAConfiguration', {
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface SLAConfigurationAttributes {
id: string;
activityName: string;
ownerRole: string;
tatHours: number;
tatUnit: 'hours' | 'days';
isActive: boolean;
}
export interface SLAConfigurationInstance extends Model<SLAConfigurationAttributes>, SLAConfigurationAttributes { }
export default (sequelize: Sequelize) => {
const SLAConfiguration = sequelize.define<SLAConfigurationInstance>('SLAConfiguration', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -34,7 +47,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
SLAConfiguration.associate = (models) => {
(SLAConfiguration as any).associate = (models: any) => {
SLAConfiguration.hasMany(models.SLAReminder, {
foreignKey: 'slaConfigId',
as: 'reminders'

View File

@ -1,5 +1,18 @@
module.exports = (sequelize, DataTypes) => {
const SLAEscalationConfig = sequelize.define('SLAEscalationConfig', {
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface SLAEscalationConfigAttributes {
id: string;
slaConfigId: string;
level: number;
timeValue: number;
timeUnit: 'hours' | 'days';
notifyEmail: string;
}
export interface SLAEscalationConfigInstance extends Model<SLAEscalationConfigAttributes>, SLAEscalationConfigAttributes { }
export default (sequelize: Sequelize) => {
const SLAEscalationConfig = sequelize.define<SLAEscalationConfigInstance>('SLAEscalationConfig', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -38,7 +51,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
SLAEscalationConfig.associate = (models) => {
(SLAEscalationConfig as any).associate = (models: any) => {
SLAEscalationConfig.belongsTo(models.SLAConfiguration, {
foreignKey: 'slaConfigId',
as: 'slaConfig'

View File

@ -1,5 +1,17 @@
module.exports = (sequelize, DataTypes) => {
const SLAReminder = sequelize.define('SLAReminder', {
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface SLAReminderAttributes {
id: string;
slaConfigId: string;
timeValue: number;
timeUnit: 'hours' | 'days';
isEnabled: boolean;
}
export interface SLAReminderInstance extends Model<SLAReminderAttributes>, SLAReminderAttributes { }
export default (sequelize: Sequelize) => {
const SLAReminder = sequelize.define<SLAReminderInstance>('SLAReminder', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -33,7 +45,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
SLAReminder.associate = (models) => {
(SLAReminder as any).associate = (models: any) => {
SLAReminder.belongsTo(models.SLAConfiguration, {
foreignKey: 'slaConfigId',
as: 'slaConfig'

View File

@ -0,0 +1,76 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface SLATrackingAttributes {
id: string;
applicationId: string | null;
entityType: string;
entityId: string;
stageName: string;
startTime: Date;
endTime: Date | null;
duration: number | null; // minutes or hours
isBreached: boolean;
isActive: boolean;
}
export interface SLATrackingInstance extends Model<SLATrackingAttributes>, SLATrackingAttributes { }
export default (sequelize: Sequelize) => {
const SLATracking = sequelize.define<SLATrackingInstance>('SLATracking', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'applications',
key: 'id'
}
},
entityType: {
type: DataTypes.STRING,
allowNull: false
},
entityId: {
type: DataTypes.UUID,
allowNull: false
},
stageName: {
type: DataTypes.STRING,
allowNull: false
},
startTime: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
endTime: {
type: DataTypes.DATE,
allowNull: true
},
duration: {
type: DataTypes.INTEGER,
allowNull: true
},
isBreached: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'sla_tracking',
timestamps: true
});
(SLATracking as any).associate = (models: any) => {
SLATracking.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
SLATracking.hasMany(models.SLABreach, { foreignKey: 'trackingId', as: 'breaches' });
};
return SLATracking;
};

View File

@ -0,0 +1,75 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface SecurityDepositAttributes {
id: string;
applicationId: string;
amount: number;
paymentReference: string | null;
proofDocumentId: string | null;
status: string;
verifiedAt: Date | null;
verifiedBy: string | null;
}
export interface SecurityDepositInstance extends Model<SecurityDepositAttributes>, SecurityDepositAttributes { }
export default (sequelize: Sequelize) => {
const SecurityDeposit = sequelize.define<SecurityDepositInstance>('SecurityDeposit', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'applications',
key: 'id'
}
},
amount: {
type: DataTypes.DECIMAL(15, 2),
allowNull: false
},
paymentReference: {
type: DataTypes.STRING,
allowNull: true
},
proofDocumentId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'documents',
key: 'id'
}
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
verifiedAt: {
type: DataTypes.DATE,
allowNull: true
},
verifiedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'security_deposits',
timestamps: true
});
(SecurityDeposit as any).associate = (models: any) => {
SecurityDeposit.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
SecurityDeposit.belongsTo(models.Document, { foreignKey: 'proofDocumentId', as: 'proofDocument' });
SecurityDeposit.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' });
};
return SecurityDeposit;
};

View File

@ -0,0 +1,58 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface StateAttributes {
id: string;
stateName: string;
zoneId: string;
isActive: boolean;
}
export interface StateInstance extends Model<StateAttributes>, StateAttributes { }
export default (sequelize: Sequelize) => {
const State = sequelize.define<StateInstance>('State', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
stateName: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
zoneId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'zones',
key: 'id'
}
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'states',
timestamps: true,
updatedAt: false
});
(State as any).associate = (models: any) => {
State.belongsTo(models.Zone, {
foreignKey: 'zoneId',
as: 'zone'
});
State.hasMany(models.District, {
foreignKey: 'stateId',
as: 'districts'
});
State.hasMany(models.Region, {
foreignKey: 'stateId',
as: 'regions'
});
};
return State;
};

View File

@ -0,0 +1,71 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface TerminationRequestAttributes {
id: string;
dealerId: string;
category: string;
reason: string;
proposedLwd: Date;
status: string;
initiatedBy: string;
comments: string | null;
}
export interface TerminationRequestInstance extends Model<TerminationRequestAttributes>, TerminationRequestAttributes { }
export default (sequelize: Sequelize) => {
const TerminationRequest = sequelize.define<TerminationRequestInstance>('TerminationRequest', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
dealerId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'dealers',
key: 'id'
}
},
category: {
type: DataTypes.STRING,
allowNull: false
},
reason: {
type: DataTypes.TEXT,
allowNull: false
},
proposedLwd: {
type: DataTypes.DATEONLY,
allowNull: false
},
status: {
type: DataTypes.STRING,
defaultValue: 'pending'
},
initiatedBy: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
comments: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'termination_requests',
timestamps: true
});
(TerminationRequest as any).associate = (models: any) => {
TerminationRequest.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' });
TerminationRequest.belongsTo(models.User, { foreignKey: 'initiatedBy', as: 'initiator' });
TerminationRequest.hasOne(models.FnF, { foreignKey: 'terminationRequestId', as: 'fnfSettlement' });
};
return TerminationRequest;
};

View File

@ -1,89 +0,0 @@
const { ROLES, REGIONS } = require('../../common/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;
};

160
src/database/models/User.ts Normal file
View File

@ -0,0 +1,160 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { ROLES, REGIONS } from '../../common/config/constants.js';
export interface UserAttributes {
id: string;
employeeId: string | null;
email: string;
password?: string;
fullName: string;
mobileNumber: string | null;
department: string | null;
designation: string | null;
roleCode: string | null;
zoneId: string | null;
regionId: string | null;
stateId: string | null;
districtId: string | null;
areaId: string | null;
dealerId: string | null;
isActive: boolean;
isExternal: boolean;
ssoProvider: string | null;
status: string;
lastLogin: Date | null;
}
export interface UserInstance extends Model<UserAttributes>, UserAttributes { }
export default (sequelize: Sequelize) => {
const User = sequelize.define<UserInstance>('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
employeeId: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: true // SSO might not need passwords
},
fullName: {
type: DataTypes.STRING,
allowNull: false
},
mobileNumber: {
type: DataTypes.STRING,
allowNull: true
},
department: {
type: DataTypes.STRING,
allowNull: true
},
designation: {
type: DataTypes.STRING,
allowNull: true
},
roleCode: {
type: DataTypes.STRING,
allowNull: true
},
zoneId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'zones',
key: 'id'
}
},
regionId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'regions',
key: 'id'
}
},
stateId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'states',
key: 'id'
}
},
districtId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'districts',
key: 'id'
}
},
areaId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'areas',
key: 'id'
}
},
dealerId: {
type: DataTypes.UUID, // Link to Dealer entity if applicable
allowNull: true
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
isExternal: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
ssoProvider: {
type: DataTypes.STRING,
allowNull: true
},
status: {
type: DataTypes.STRING,
defaultValue: 'active'
},
lastLogin: {
type: DataTypes.DATE,
allowNull: true
}
}, {
tableName: 'users',
timestamps: true,
indexes: [
{ fields: ['email'] },
{ fields: ['employeeId'] },
{ fields: ['roleCode'] }
]
});
(User as any).associate = (models: any) => {
User.hasMany(models.UserRole, { foreignKey: 'userId', as: 'userRoles' });
User.hasMany(models.UserRole, { foreignKey: 'assignedBy', as: 'assignedRoles' });
User.belongsTo(models.Zone, { foreignKey: 'zoneId', as: 'zone' });
User.belongsTo(models.Region, { foreignKey: 'regionId', as: 'region' });
User.belongsTo(models.State, { foreignKey: 'stateId', as: 'state' });
User.belongsTo(models.District, { foreignKey: 'districtId', as: 'district' });
User.belongsTo(models.Area, { foreignKey: 'areaId', as: 'area' });
User.hasMany(models.AuditLog, { foreignKey: 'userId', as: 'auditLogs' });
};
return User;
};

View File

@ -0,0 +1,109 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface UserRoleAttributes {
id: string;
userId: string;
roleId: string;
zoneId: string | null;
regionId: string | null;
areaId: string | null;
assignedAt: Date;
assignedBy: string | null;
}
export interface UserRoleInstance extends Model<UserRoleAttributes>, UserRoleAttributes { }
export default (sequelize: Sequelize) => {
const UserRole = sequelize.define<UserRoleInstance>('UserRole', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
roleId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'roles',
key: 'id'
}
},
zoneId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'zones',
key: 'id'
}
},
regionId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'regions',
key: 'id'
}
},
areaId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'areas',
key: 'id'
}
},
assignedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
assignedBy: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'user_roles',
timestamps: true,
updatedAt: false
});
(UserRole as any).associate = (models: any) => {
UserRole.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
UserRole.belongsTo(models.Role, {
foreignKey: 'roleId',
as: 'role'
});
UserRole.belongsTo(models.Zone, {
foreignKey: 'zoneId',
as: 'zone'
});
UserRole.belongsTo(models.Region, {
foreignKey: 'regionId',
as: 'region'
});
UserRole.belongsTo(models.Area, {
foreignKey: 'areaId',
as: 'area'
});
UserRole.belongsTo(models.User, {
foreignKey: 'assignedBy',
as: 'assigner'
});
};
return UserRole;
};

View File

@ -0,0 +1,46 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface WorkNoteAttachmentAttributes {
id: string;
noteId: string;
documentId: string;
}
export interface WorkNoteAttachmentInstance extends Model<WorkNoteAttachmentAttributes>, WorkNoteAttachmentAttributes { }
export default (sequelize: Sequelize) => {
const WorkNoteAttachment = sequelize.define<WorkNoteAttachmentInstance>('WorkNoteAttachment', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
noteId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'worknotes',
key: 'id'
}
},
documentId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'documents',
key: 'id'
}
}
}, {
tableName: 'work_note_attachments',
timestamps: true,
updatedAt: false
});
(WorkNoteAttachment as any).associate = (models: any) => {
WorkNoteAttachment.belongsTo(models.Worknote, { foreignKey: 'noteId', as: 'workNote' });
WorkNoteAttachment.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' });
};
return WorkNoteAttachment;
};

View File

@ -0,0 +1,41 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface WorkNoteTagAttributes {
id: string;
noteId: string;
tagName: string;
}
export interface WorkNoteTagInstance extends Model<WorkNoteTagAttributes>, WorkNoteTagAttributes { }
export default (sequelize: Sequelize) => {
const WorkNoteTag = sequelize.define<WorkNoteTagInstance>('WorkNoteTag', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
noteId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'worknotes',
key: 'id'
}
},
tagName: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'work_note_tags',
timestamps: true,
updatedAt: false
});
(WorkNoteTag as any).associate = (models: any) => {
WorkNoteTag.belongsTo(models.Worknote, { foreignKey: 'noteId', as: 'workNote' });
};
return WorkNoteTag;
};

View File

@ -1,5 +1,19 @@
module.exports = (sequelize, DataTypes) => {
const WorkflowStageConfig = sequelize.define('WorkflowStageConfig', {
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface WorkflowStageConfigAttributes {
id: string;
stageName: string;
stageOrder: number;
colorCode: string | null;
isParallel: boolean;
defaultEvaluators: any[];
isActive: boolean;
}
export interface WorkflowStageConfigInstance extends Model<WorkflowStageConfigAttributes>, WorkflowStageConfigAttributes { }
export default (sequelize: Sequelize) => {
const WorkflowStageConfig = sequelize.define<WorkflowStageConfigInstance>('WorkflowStageConfig', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
@ -39,7 +53,7 @@ module.exports = (sequelize, DataTypes) => {
]
});
WorkflowStageConfig.associate = (models) => {
(WorkflowStageConfig as any).associate = (models: any) => {
// Future associations with applications or tasks
};

View File

@ -1,52 +0,0 @@
const { REQUEST_TYPES } = require('../../common/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;
};

View File

@ -0,0 +1,78 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface WorknoteAttributes {
id: string;
applicationId: string | null;
requestId: string | null; // Compatibility
requestType: string | null; // Compatibility
userId: string;
noteText: string;
noteType: string;
status: string;
}
export interface WorknoteInstance extends Model<WorknoteAttributes>, WorknoteAttributes { }
export default (sequelize: Sequelize) => {
const Worknote = sequelize.define<WorknoteInstance>('Worknote', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicationId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'applications',
key: 'id'
}
},
requestId: {
type: DataTypes.UUID,
allowNull: true
},
requestType: {
type: DataTypes.STRING,
allowNull: true
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
noteText: {
type: DataTypes.TEXT,
allowNull: false
},
noteType: {
type: DataTypes.STRING,
defaultValue: 'general'
},
status: {
type: DataTypes.STRING,
defaultValue: 'active'
}
}, {
tableName: 'worknotes',
timestamps: true,
indexes: [
{ fields: ['applicationId'] },
{ fields: ['requestId'] },
{ fields: ['userId'] }
]
});
(Worknote as any).associate = (models: any) => {
Worknote.belongsTo(models.User, { foreignKey: 'userId', as: 'author' });
Worknote.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' });
Worknote.hasMany(models.WorkNoteTag, { foreignKey: 'noteId', as: 'tags' });
Worknote.hasMany(models.WorkNoteAttachment, { foreignKey: 'noteId', as: 'attachments' });
};
return Worknote;
};

View File

@ -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;
};

View File

@ -0,0 +1,75 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ZoneAttributes {
id: string;
zoneCode: string;
zoneName: string;
description: string | null;
isActive: boolean;
zonalBusinessHeadId: string | null;
}
export interface ZoneInstance extends Model<ZoneAttributes>, ZoneAttributes { }
export default (sequelize: Sequelize) => {
const Zone = sequelize.define<ZoneInstance>('Zone', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
zoneCode: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
zoneName: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
zonalBusinessHeadId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
}
}, {
tableName: 'zones',
timestamps: true
});
(Zone as any).associate = (models: any) => {
Zone.belongsTo(models.User, {
foreignKey: 'zonalBusinessHeadId',
as: 'zonalBusinessHead'
});
Zone.hasMany(models.Region, {
foreignKey: 'zoneId',
as: 'regions'
});
Zone.hasMany(models.State, {
foreignKey: 'zoneId',
as: 'states'
});
Zone.hasMany(models.ZoneManager, {
foreignKey: 'zoneId',
as: 'managers'
});
Zone.hasMany(models.Application, {
foreignKey: 'zoneId',
as: 'applications'
});
};
return Zone;
};

View File

@ -0,0 +1,67 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ZoneManagerAttributes {
id: string;
zoneId: string;
userId: string;
managerType: string;
isActive: boolean;
assignedAt: Date;
}
export interface ZoneManagerInstance extends Model<ZoneManagerAttributes>, ZoneManagerAttributes { }
export default (sequelize: Sequelize) => {
const ZoneManager = sequelize.define<ZoneManagerInstance>('ZoneManager', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
zoneId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'zones',
key: 'id'
}
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
},
managerType: {
type: DataTypes.STRING,
allowNull: false
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
assignedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'zone_managers',
timestamps: true,
updatedAt: false
});
(ZoneManager as any).associate = (models: any) => {
ZoneManager.belongsTo(models.Zone, {
foreignKey: 'zoneId',
as: 'zone'
});
ZoneManager.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
};
return ZoneManager;
};

View File

@ -1,55 +0,0 @@
const { Sequelize } = require('sequelize');
const config = require('../../common/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.FnFLineItem = require('./FnFLineItem')(sequelize, Sequelize.DataTypes);
db.Region = require('./Region')(sequelize, Sequelize.DataTypes);
db.Zone = require('./Zone')(sequelize, Sequelize.DataTypes);
db.SLAConfiguration = require('./SLAConfiguration')(sequelize, Sequelize.DataTypes);
db.SLAReminder = require('./SLAReminder')(sequelize, Sequelize.DataTypes);
db.SLAEscalationConfig = require('./SLAEscalationConfig')(sequelize, Sequelize.DataTypes);
db.WorkflowStageConfig = require('./WorkflowStageConfig')(sequelize, Sequelize.DataTypes);
db.Notification = require('./Notification')(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;

View File

@ -0,0 +1,203 @@
import { Sequelize } from 'sequelize';
import config from '../../common/config/database.js';
// Import individual model factories
import createUser from './User.js';
import createApplication from './Application.js';
import createResignation from './Resignation.js';
import createConstitutionalChange from './ConstitutionalChange.js';
import createRelocationRequest from './RelocationRequest.js';
import createOutlet from './Outlet.js';
import createWorknote from './Worknote.js';
import createDocument from './Document.js';
import createAuditLog from './AuditLog.js';
import createFinancePayment from './FinancePayment.js';
import createFnF from './FnF.js';
import createFnFLineItem from './FnFLineItem.js';
import createRegion from './Region.js';
import createZone from './Zone.js';
import createSLAConfiguration from './SLAConfiguration.js';
import createSLAReminder from './SLAReminder.js';
import createSLAEscalationConfig from './SLAEscalationConfig.js';
import createWorkflowStageConfig from './WorkflowStageConfig.js';
import createNotification from './Notification.js';
// Batch 1: Organizational Hierarchy & User Management
import createRole from './Role.js';
import createPermission from './Permission.js';
import createRolePermission from './RolePermission.js';
import createState from './State.js';
import createDistrict from './District.js';
import createArea from './Area.js';
import createUserRole from './UserRole.js';
import createZoneManager from './ZoneManager.js';
import createRegionManager from './RegionManager.js';
import createAreaManager from './AreaManager.js';
import createDistrictManager from './DistrictManager.js';
// Batch 2: Opportunity & Application Framework
import createOpportunity from './Opportunity.js';
import createApplicationStatusHistory from './ApplicationStatusHistory.js';
import createApplicationProgress from './ApplicationProgress.js';
// Batch 3: Questionnaire & Interview Systems
import createQuestionnaire from './Questionnaire.js';
import createQuestionnaireQuestion from './QuestionnaireQuestion.js';
import createQuestionnaireResponse from './QuestionnaireResponse.js';
import createQuestionnaireScore from './QuestionnaireScore.js';
import createInterview from './Interview.js';
import createInterviewParticipant from './InterviewParticipant.js';
import createInterviewEvaluation from './InterviewEvaluation.js';
import createKTMatrixScore from './KTMatrixScore.js';
import createInterviewFeedback from './InterviewFeedback.js';
import createAiSummary from './AiSummary.js';
// Batch 4: Dealer Entity, Documents & Work Notes
import createDealer from './Dealer.js';
import createDealerCode from './DealerCode.js';
import createDocumentVersion from './DocumentVersion.js';
import createWorkNoteTag from './WorkNoteTag.js';
import createWorkNoteAttachment from './WorkNoteAttachment.js';
// Batch 5: FDD, LOI, LOA, EOR & Security Deposit
import createFddAssignment from './FddAssignment.js';
import createFddReport from './FddReport.js';
import createLoiRequest from './LoiRequest.js';
import createLoiApproval from './LoiApproval.js';
import createLoiDocumentGenerated from './LoiDocumentGenerated.js';
import createLoiAcknowledgement from './LoiAcknowledgement.js';
import createSecurityDeposit from './SecurityDeposit.js';
import createLoaRequest from './LoaRequest.js';
import createLoaApproval from './LoaApproval.js';
import createLoaDocumentGenerated from './LoaDocumentGenerated.js';
import createLoaAcknowledgement from './LoaAcknowledgement.js';
import createEorChecklist from './EorChecklist.js';
import createEorChecklistItem from './EorChecklistItem.js';
// Batch 6: Offboarding & F&F Settlement
import createTerminationRequest from './TerminationRequest.js';
import createExitFeedback from './ExitFeedback.js';
// Batch 7: Notifications, Logs & Templates
import createEmailTemplate from './EmailTemplate.js';
// Batch 8: SLA & TAT Tracking
import createSLATracking from './SLATracking.js';
import createSLABreach from './SLABreach.js';
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 as number,
dialect: dbConfig.dialect as any,
logging: dbConfig.logging,
pool: dbConfig.pool,
dialectOptions: dbConfig.dialectOptions
}
);
const db: any = {};
// Initialize models
db.User = createUser(sequelize);
db.Application = createApplication(sequelize);
db.Resignation = createResignation(sequelize);
db.ConstitutionalChange = createConstitutionalChange(sequelize);
db.RelocationRequest = createRelocationRequest(sequelize);
db.Outlet = createOutlet(sequelize);
db.Worknote = createWorknote(sequelize);
db.Document = createDocument(sequelize);
db.AuditLog = createAuditLog(sequelize);
db.FinancePayment = createFinancePayment(sequelize);
db.FnF = createFnF(sequelize);
db.FnFLineItem = createFnFLineItem(sequelize);
db.Region = createRegion(sequelize);
db.Zone = createZone(sequelize);
db.SLAConfiguration = createSLAConfiguration(sequelize);
db.SLAReminder = createSLAReminder(sequelize);
db.SLAEscalationConfig = createSLAEscalationConfig(sequelize);
db.WorkflowStageConfig = createWorkflowStageConfig(sequelize);
db.Notification = createNotification(sequelize);
// Batch 1: Organizational Hierarchy & User Management
db.Role = createRole(sequelize);
db.Permission = createPermission(sequelize);
db.RolePermission = createRolePermission(sequelize);
db.State = createState(sequelize);
db.District = createDistrict(sequelize);
db.Area = createArea(sequelize);
db.UserRole = createUserRole(sequelize);
db.ZoneManager = createZoneManager(sequelize);
db.RegionManager = createRegionManager(sequelize);
db.AreaManager = createAreaManager(sequelize);
db.DistrictManager = createDistrictManager(sequelize);
// Batch 2: Opportunity & Application Framework
db.Opportunity = createOpportunity(sequelize);
db.ApplicationStatusHistory = createApplicationStatusHistory(sequelize);
db.ApplicationProgress = createApplicationProgress(sequelize);
// Batch 3: Questionnaire & Interview Systems
db.Questionnaire = createQuestionnaire(sequelize);
db.QuestionnaireQuestion = createQuestionnaireQuestion(sequelize);
db.QuestionnaireResponse = createQuestionnaireResponse(sequelize);
db.QuestionnaireScore = createQuestionnaireScore(sequelize);
db.Interview = createInterview(sequelize);
db.InterviewParticipant = createInterviewParticipant(sequelize);
db.InterviewEvaluation = createInterviewEvaluation(sequelize);
db.KTMatrixScore = createKTMatrixScore(sequelize);
db.InterviewFeedback = createInterviewFeedback(sequelize);
db.AiSummary = createAiSummary(sequelize);
// Batch 4: Dealer Entity, Documents & Work Notes
db.Dealer = createDealer(sequelize);
db.DealerCode = createDealerCode(sequelize);
db.DocumentVersion = createDocumentVersion(sequelize);
db.WorkNoteTag = createWorkNoteTag(sequelize);
db.WorkNoteAttachment = createWorkNoteAttachment(sequelize);
// Batch 5: FDD, LOI, LOA, EOR & Security Deposit
db.FddAssignment = createFddAssignment(sequelize);
db.FddReport = createFddReport(sequelize);
db.LoiRequest = createLoiRequest(sequelize);
db.LoiApproval = createLoiApproval(sequelize);
db.LoiDocumentGenerated = createLoiDocumentGenerated(sequelize);
db.LoiAcknowledgement = createLoiAcknowledgement(sequelize);
db.SecurityDeposit = createSecurityDeposit(sequelize);
db.LoaRequest = createLoaRequest(sequelize);
db.LoaApproval = createLoaApproval(sequelize);
db.LoaDocumentGenerated = createLoaDocumentGenerated(sequelize);
db.LoaAcknowledgement = createLoaAcknowledgement(sequelize);
db.EorChecklist = createEorChecklist(sequelize);
db.EorChecklistItem = createEorChecklistItem(sequelize);
// Batch 6: Offboarding & F&F Settlement
db.TerminationRequest = createTerminationRequest(sequelize);
db.ExitFeedback = createExitFeedback(sequelize);
// Batch 7: Notifications, Logs & Templates
db.EmailTemplate = createEmailTemplate(sequelize);
// Batch 8: SLA & TAT Tracking
db.SLATracking = createSLATracking(sequelize);
db.SLABreach = createSLABreach(sequelize);
// Define associations
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
export default db;
export { sequelize, Sequelize };

View File

@ -0,0 +1,169 @@
import { Request, Response } from 'express';
import db from '../../database/models/index.js';
const { Role, Permission, RolePermission, User, DealerCode, AuditLog } = db;
import { AUDIT_ACTIONS } from '../../common/config/constants.js';
import { AuthRequest } from '../../types/express.types.js';
// --- Roles Management ---
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'] }
}],
order: [['roleName', 'ASC']]
});
res.json({ success: true, data: roles });
} catch (error) {
console.error('Get roles error:', error);
res.status(500).json({ success: false, message: 'Error fetching roles' });
}
};
export const createRole = async (req: AuthRequest, res: Response) => {
try {
const { roleCode, roleName, description, permissions } = req.body; // permissions: [{ permissionId, actions: { canCreate... } }]
const role = await Role.create({ roleCode, roleName, description });
if (permissions && permissions.length > 0) {
for (const p of permissions) {
await RolePermission.create({
roleId: role.id,
permissionId: p.permissionId,
...p.actions
});
}
}
await AuditLog.create({
userId: req.user?.id, // Optional chaining as user might be undefined if auth middleware fails or not strict
action: AUDIT_ACTIONS.CREATED,
entityType: 'role',
entityId: role.id,
newData: req.body
});
res.status(201).json({ success: true, data: role, message: 'Role created successfully' });
} catch (error) {
console.error('Create role error:', error);
res.status(500).json({ success: false, message: 'Error creating role' });
}
};
export const updateRole = async (req: AuthRequest, res: Response) => {
try {
const { id } = req.params;
const { roleName, description, permissions, 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
await RolePermission.destroy({ where: { roleId: id } });
for (const p of permissions) {
await RolePermission.create({
roleId: id,
permissionId: p.permissionId,
...p.actions
});
}
}
await AuditLog.create({
userId: req.user?.id,
action: AUDIT_ACTIONS.UPDATED,
entityType: 'role',
entityId: id,
newData: req.body
});
res.json({ success: true, message: 'Role updated successfully' });
} catch (error) {
console.error('Update role error:', error);
res.status(500).json({ success: false, message: 'Error updating role' });
}
};
// --- Permissions Management ---
export const getPermissions = async (req: Request, res: Response) => {
try {
const permissions = await Permission.findAll({ order: [['module', 'ASC']] });
res.json({ success: true, data: permissions });
} catch (error) {
console.error('Get permissions error:', error);
res.status(500).json({ success: false, message: 'Error fetching permissions' });
}
};
// --- User Management (Admin) ---
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
order: [['createdAt', 'DESC']]
});
res.json({ success: true, data: users });
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ success: false, message: 'Error fetching users' });
}
};
export const updateUserStatus = async (req: AuthRequest, res: Response) => {
try {
const { id } = req.params;
const { status, isActive } = req.body;
const user = await User.findByPk(id);
if (!user) return res.status(404).json({ success: false, message: 'User not found' });
await user.update({ status, isActive });
await AuditLog.create({
userId: req.user?.id,
action: AUDIT_ACTIONS.UPDATED,
entityType: 'user',
entityId: id,
newData: { status, isActive }
});
res.json({ success: true, message: 'User status updated' });
} catch (error) {
console.error('Update user status error:', error);
res.status(500).json({ success: false, message: 'Error updating user status' });
}
};
// --- Dealer Codes ---
export const generateDealerCode = async (req: AuthRequest, res: Response) => {
try {
const { regionId, stateId, channel } = req.body;
// Logic to generate unique code based on format (e.g., RE-[Region]-[State]-[Seq])
// This is a placeholder for the actual business logic
const timestamp = Date.now().toString().slice(-6);
const code = `DLR-${timestamp}`;
const dealerCode = await DealerCode.create({
code,
isUsed: false,
generatedBy: req.user?.id
});
res.status(201).json({ success: true, data: dealerCode });
} catch (error) {
console.error('Generate dealer code error:', error);
res.status(500).json({ success: false, message: 'Error generating dealer code' });
}
};

View File

@ -0,0 +1,27 @@
import express from 'express';
const router = express.Router();
import * as adminController from './admin.controller.js';
import { authenticate } from '../../common/middleware/auth.js';
import { checkRole } from '../../common/middleware/roleCheck.js';
import { ROLES } from '../../common/config/constants.js';
// All admin routes require authentication and typically Admin roles
// For now, allowing all for dev, but should be: checkRole([ROLES.SUPER_ADMIN])
router.use(authenticate as any);
// Roles
router.get('/roles', adminController.getRoles);
router.post('/roles', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.createRole);
router.put('/roles/:id', checkRole([ROLES.SUPER_ADMIN]) as any, adminController.updateRole);
// Permissions
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);
// Dealer Codes
router.post('/dealer-codes/generate', checkRole([ROLES.SUPER_ADMIN, ROLES.DD_ADMIN]) as any, adminController.generateDealerCode);
export default router;

View File

@ -0,0 +1,160 @@
import { Request, Response } from 'express';
import db from '../../database/models/index.js';
const {
Questionnaire, QuestionnaireQuestion, QuestionnaireResponse, QuestionnaireScore,
Interview, InterviewEvaluation, InterviewParticipant, AiSummary
} = db;
import { AuthRequest } from '../../types/express.types.js';
import { Op } from 'sequelize';
// --- Questionnaires ---
export const getQuestionnaire = async (req: Request, res: Response) => {
try {
const { version } = req.query;
const where: any = { isActive: true };
if (version) where.version = version;
const questionnaire = await Questionnaire.findOne({
where,
include: [{ model: QuestionnaireQuestion, as: 'questions' }],
order: [['createdAt', 'DESC']] // GET latest if no version
});
res.json({ success: true, data: questionnaire });
} catch (error) {
console.error('Get questionnaire error:', error);
res.status(500).json({ success: false, message: 'Error fetching questionnaire' });
}
};
export const submitQuestionnaireResponse = async (req: AuthRequest, res: Response) => {
try {
const { applicationId, questionnaireId, responses } = req.body; // responses: [{ questionId, responseValue, attachmentUrl }]
// Calculate score logic (Placeholder)
let calculatedScore = 0;
let totalWeight = 0;
for (const resp of responses) {
await QuestionnaireResponse.create({
applicationId,
questionnaireId,
questionId: resp.questionId,
responseValue: resp.responseValue,
attachmentUrl: resp.attachmentUrl
});
// Add scoring logic here based on question type/weight
}
// Create Score Record
await QuestionnaireScore.create({
applicationId,
questionnaireId,
score: calculatedScore,
maxScore: 100, // Placeholder
status: 'Completed'
});
res.status(201).json({ success: true, message: 'Responses submitted successfully' });
} catch (error) {
console.error('Submit response error:', error);
res.status(500).json({ success: false, message: 'Error submitting responses' });
}
};
// --- Interviews ---
export const scheduleInterview = async (req: AuthRequest, res: Response) => {
try {
const { applicationId, level, scheduledAt, type, location, participants } = req.body; // participants: [userId]
const interview = await Interview.create({
applicationId,
level,
scheduledAt,
type,
location,
status: 'Scheduled',
scheduledBy: req.user?.id
});
if (participants && participants.length > 0) {
for (const userId of participants) {
await InterviewParticipant.create({
interviewId: interview.id,
userId,
role: 'Panelist'
});
}
}
res.status(201).json({ success: true, message: 'Interview scheduled successfully', data: interview });
} catch (error) {
console.error('Schedule interview error:', error);
res.status(500).json({ success: false, message: 'Error scheduling interview' });
}
};
export const updateInterview = async (req: AuthRequest, res: Response) => {
try {
const { id } = req.params;
const { status, scheduledAt, outcome } = req.body;
const interview = await Interview.findByPk(id);
if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });
await interview.update({ status, scheduledAt, outcome });
res.json({ success: true, message: 'Interview updated successfully' });
} catch (error) {
console.error('Update interview error:', error);
res.status(500).json({ success: false, message: 'Error updating interview' });
}
};
export const submitEvaluation = async (req: AuthRequest, res: Response) => {
try {
const { id } = req.params; // Interview ID
const { ktScore, feedback, recommendation, status } = req.body;
const interview = await Interview.findByPk(id);
if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });
const evaluation = await InterviewEvaluation.create({
interviewId: id,
evaluatorId: req.user?.id,
ktScore,
feedback,
recommendation
});
// Auto update interview status if completed
if (status === 'Completed') {
await interview.update({ status: 'Completed', outcome: recommendation });
}
res.status(201).json({ success: true, message: 'Evaluation submitted successfully', data: evaluation });
} catch (error) {
console.error('Submit evaluation error:', error);
res.status(500).json({ success: false, message: 'Error submitting evaluation' });
}
};
// --- AI Summary ---
export const getAiSummary = async (req: Request, res: Response) => {
try {
const { applicationId } = req.params;
const summary = await AiSummary.findOne({
where: { applicationId },
order: [['createdAt', 'DESC']]
});
res.json({ success: true, data: summary });
} catch (error) {
console.error('Get AI summary error:', error);
res.status(500).json({ success: false, message: 'Error fetching AI summary' });
}
};

View File

@ -0,0 +1,20 @@
import express from 'express';
const router = express.Router();
import * as assessmentController from './assessment.controller.js';
import { authenticate } from '../../common/middleware/auth.js';
router.use(authenticate as any);
// Questionnaires
router.get('/questionnaire', assessmentController.getQuestionnaire);
router.post('/questionnaire/response', assessmentController.submitQuestionnaireResponse);
// Interviews
router.post('/interviews', assessmentController.scheduleInterview);
router.put('/interviews/:id', assessmentController.updateInterview);
router.post('/interviews/:id/evaluation', assessmentController.submitEvaluation);
// AI Summary
router.get('/ai-summary/:applicationId', assessmentController.getAiSummary);
export default router;

View File

@ -1,10 +1,13 @@
const bcrypt = require('bcryptjs');
const { User, AuditLog } = require('../../database/models');
const { generateToken } = require('../../common/config/auth');
const { AUDIT_ACTIONS } = require('../../common/config/constants');
import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import db from '../../database/models/index.js';
const { User, AuditLog } = db;
import { generateToken } from '../../common/config/auth.js';
import { AUDIT_ACTIONS } from '../../common/config/constants.js';
import { AuthRequest } from '../../types/express.types.js';
// Register new user
exports.register = async (req, res) => {
export const register = async (req: Request, res: Response) => {
try {
const { email, password, fullName, role, phone, region, zone } = req.body;
@ -36,7 +39,8 @@ exports.register = async (req, res) => {
role,
phone,
region,
zone
zone,
status: 'active'
});
// Log audit
@ -62,7 +66,7 @@ exports.register = async (req, res) => {
};
// Login
exports.login = async (req, res) => {
export const login = async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
@ -93,7 +97,7 @@ exports.login = async (req, res) => {
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.password);
const isValidPassword = await bcrypt.compare(password, user.password!);
if (!isValidPassword) {
return res.status(401).json({
success: false,
@ -110,7 +114,7 @@ exports.login = async (req, res) => {
// Log audit
await AuditLog.create({
userId: user.id,
action: 'user_login',
action: 'user_login' as any,
entityType: 'user',
entityId: user.id
});
@ -137,8 +141,12 @@ exports.login = async (req, res) => {
};
// Get profile
exports.getProfile = async (req, res) => {
export const getProfile = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) {
return res.status(401).json({ success: false, message: 'Unauthorized' });
}
const user = await User.findByPk(req.user.id, {
attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt']
});
@ -173,8 +181,12 @@ exports.getProfile = async (req, res) => {
};
// Update profile
exports.updateProfile = async (req, res) => {
export const updateProfile = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) {
return res.status(401).json({ success: false, message: 'Unauthorized' });
}
const { fullName, phone } = req.body;
const user = await User.findByPk(req.user.id);
@ -212,8 +224,12 @@ exports.updateProfile = async (req, res) => {
};
// Change password
exports.changePassword = async (req, res) => {
export const changePassword = async (req: AuthRequest, res: Response) => {
try {
if (!req.user) {
return res.status(401).json({ success: false, message: 'Unauthorized' });
}
const { currentPassword, newPassword } = req.body;
if (!currentPassword || !newPassword) {
@ -233,7 +249,7 @@ exports.changePassword = async (req, res) => {
}
// Verify current password
const isValid = await bcrypt.compare(currentPassword, user.password);
const isValid = await bcrypt.compare(currentPassword, user.password!);
if (!isValid) {
return res.status(401).json({
success: false,
@ -250,7 +266,7 @@ exports.changePassword = async (req, res) => {
// Log audit
await AuditLog.create({
userId: req.user.id,
action: 'password_changed',
action: 'password_changed' as any,
entityType: 'user',
entityId: req.user.id
});

View File

@ -1,15 +0,0 @@
const express = require('express');
const router = express.Router();
const authController = require('./auth.controller');
const { authenticate } = require('../../common/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;

Some files were not shown because too many files have changed in this diff Show More