changed to typescript and added missed tables
This commit is contained in:
parent
8984a314a7
commit
251a362717
5292
package-lock.json
generated
5292
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@ -2,11 +2,14 @@
|
|||||||
"name": "royal-enfield-onboarding-backend",
|
"name": "royal-enfield-onboarding-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Backend API for Royal Enfield Dealership Onboarding System",
|
"description": "Backend API for Royal Enfield Dealership Onboarding System",
|
||||||
|
"type": "module",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node dist/src/server.js",
|
||||||
"dev": "nodemon server.js",
|
"dev": "tsx watch src/server.ts",
|
||||||
"migrate": "node scripts/migrate.js",
|
"build": "tsc",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"migrate": "node dist/scripts/migrate.js",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
"clear-logs": "rm -rf logs/*.log"
|
"clear-logs": "rm -rf logs/*.log"
|
||||||
@ -20,27 +23,43 @@
|
|||||||
"author": "Royal Enfield",
|
"author": "Royal Enfield",
|
||||||
"license": "PROPRIETARY",
|
"license": "PROPRIETARY",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"bcryptjs": "^3.0.3",
|
||||||
"sequelize": "^6.35.2",
|
"compression": "^1.8.1",
|
||||||
"pg": "^8.11.3",
|
|
||||||
"pg-hstore": "^2.3.4",
|
|
||||||
"jsonwebtoken": "^9.0.2",
|
|
||||||
"bcryptjs": "^2.4.3",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"helmet": "^7.1.0",
|
"dotenv": "^17.2.3",
|
||||||
"express-validator": "^7.0.1",
|
"express": "^5.2.1",
|
||||||
"multer": "^1.4.5-lts.1",
|
"express-rate-limit": "^8.2.1",
|
||||||
"nodemailer": "^6.9.7",
|
"express-validator": "^7.3.1",
|
||||||
"winston": "^3.11.0",
|
"helmet": "^8.1.0",
|
||||||
"dotenv": "^16.3.1",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"uuid": "^9.0.1",
|
"multer": "^2.0.2",
|
||||||
"express-rate-limit": "^7.1.5",
|
"nodemailer": "^7.0.12",
|
||||||
"compression": "^1.7.4"
|
"pg": "^8.17.2",
|
||||||
|
"pg-hstore": "^2.3.4",
|
||||||
|
"sequelize": "^6.37.7",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
|
"winston": "^3.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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",
|
"nodemon": "^3.0.2",
|
||||||
"jest": "^29.7.0",
|
"supertest": "^7.2.2",
|
||||||
"supertest": "^6.3.3"
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0",
|
"node": ">=18.0.0",
|
||||||
|
|||||||
41
scripts/migrate.ts
Normal file
41
scripts/migrate.ts
Normal 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
156
server.js
@ -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;
|
|
||||||
|
|
||||||
@ -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
31
src/common/config/auth.ts
Normal 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 };
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// User Roles
|
// User Roles
|
||||||
const ROLES = {
|
export const ROLES = {
|
||||||
DD: 'DD',
|
DD: 'DD',
|
||||||
DD_ZM: 'DD-ZM',
|
DD_ZM: 'DD-ZM',
|
||||||
RBM: 'RBM',
|
RBM: 'RBM',
|
||||||
@ -13,19 +13,19 @@ const ROLES = {
|
|||||||
DD_AM: 'DD AM',
|
DD_AM: 'DD AM',
|
||||||
FINANCE: 'Finance',
|
FINANCE: 'Finance',
|
||||||
DEALER: 'Dealer'
|
DEALER: 'Dealer'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Regions
|
// Regions
|
||||||
const REGIONS = {
|
export const REGIONS = {
|
||||||
EAST: 'East',
|
EAST: 'East',
|
||||||
WEST: 'West',
|
WEST: 'West',
|
||||||
NORTH: 'North',
|
NORTH: 'North',
|
||||||
SOUTH: 'South',
|
SOUTH: 'South',
|
||||||
CENTRAL: 'Central'
|
CENTRAL: 'Central'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Application Stages
|
// Application Stages
|
||||||
const APPLICATION_STAGES = {
|
export const APPLICATION_STAGES = {
|
||||||
DD: 'DD',
|
DD: 'DD',
|
||||||
DD_ZM: 'DD-ZM',
|
DD_ZM: 'DD-ZM',
|
||||||
RBM: 'RBM',
|
RBM: 'RBM',
|
||||||
@ -37,18 +37,18 @@ const APPLICATION_STAGES = {
|
|||||||
FINANCE: 'Finance',
|
FINANCE: 'Finance',
|
||||||
APPROVED: 'Approved',
|
APPROVED: 'Approved',
|
||||||
REJECTED: 'Rejected'
|
REJECTED: 'Rejected'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Application Status
|
// Application Status
|
||||||
const APPLICATION_STATUS = {
|
export const APPLICATION_STATUS = {
|
||||||
PENDING: 'Pending',
|
PENDING: 'Pending',
|
||||||
IN_REVIEW: 'In Review',
|
IN_REVIEW: 'In Review',
|
||||||
APPROVED: 'Approved',
|
APPROVED: 'Approved',
|
||||||
REJECTED: 'Rejected'
|
REJECTED: 'Rejected'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Resignation Stages
|
// Resignation Stages
|
||||||
const RESIGNATION_STAGES = {
|
export const RESIGNATION_STAGES = {
|
||||||
ASM: 'ASM',
|
ASM: 'ASM',
|
||||||
RBM: 'RBM',
|
RBM: 'RBM',
|
||||||
ZBH: 'ZBH',
|
ZBH: 'ZBH',
|
||||||
@ -59,99 +59,99 @@ const RESIGNATION_STAGES = {
|
|||||||
FNF_INITIATED: 'F&F Initiated',
|
FNF_INITIATED: 'F&F Initiated',
|
||||||
COMPLETED: 'Completed',
|
COMPLETED: 'Completed',
|
||||||
REJECTED: 'Rejected'
|
REJECTED: 'Rejected'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Resignation Types
|
// Resignation Types
|
||||||
const RESIGNATION_TYPES = {
|
export const RESIGNATION_TYPES = {
|
||||||
VOLUNTARY: 'Voluntary',
|
VOLUNTARY: 'Voluntary',
|
||||||
RETIREMENT: 'Retirement',
|
RETIREMENT: 'Retirement',
|
||||||
HEALTH_ISSUES: 'Health Issues',
|
HEALTH_ISSUES: 'Health Issues',
|
||||||
BUSINESS_CLOSURE: 'Business Closure',
|
BUSINESS_CLOSURE: 'Business Closure',
|
||||||
OTHER: 'Other'
|
OTHER: 'Other'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Constitutional Change Types
|
// Constitutional Change Types
|
||||||
const CONSTITUTIONAL_CHANGE_TYPES = {
|
export const CONSTITUTIONAL_CHANGE_TYPES = {
|
||||||
OWNERSHIP_TRANSFER: 'Ownership Transfer',
|
OWNERSHIP_TRANSFER: 'Ownership Transfer',
|
||||||
PARTNERSHIP_CHANGE: 'Partnership Change',
|
PARTNERSHIP_CHANGE: 'Partnership Change',
|
||||||
LLP_CONVERSION: 'LLP Conversion',
|
LLP_CONVERSION: 'LLP Conversion',
|
||||||
COMPANY_FORMATION: 'Company Formation',
|
COMPANY_FORMATION: 'Company Formation',
|
||||||
DIRECTOR_CHANGE: 'Director Change'
|
DIRECTOR_CHANGE: 'Director Change'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Constitutional Change Stages
|
// Constitutional Change Stages
|
||||||
const CONSTITUTIONAL_STAGES = {
|
export const CONSTITUTIONAL_STAGES = {
|
||||||
DD_ADMIN_REVIEW: 'DD Admin Review',
|
DD_ADMIN_REVIEW: 'DD Admin Review',
|
||||||
LEGAL_REVIEW: 'Legal Review',
|
LEGAL_REVIEW: 'Legal Review',
|
||||||
NBH_APPROVAL: 'NBH Approval',
|
NBH_APPROVAL: 'NBH Approval',
|
||||||
FINANCE_CLEARANCE: 'Finance Clearance',
|
FINANCE_CLEARANCE: 'Finance Clearance',
|
||||||
COMPLETED: 'Completed',
|
COMPLETED: 'Completed',
|
||||||
REJECTED: 'Rejected'
|
REJECTED: 'Rejected'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Relocation Types
|
// Relocation Types
|
||||||
const RELOCATION_TYPES = {
|
export const RELOCATION_TYPES = {
|
||||||
WITHIN_CITY: 'Within City',
|
WITHIN_CITY: 'Within City',
|
||||||
INTERCITY: 'Intercity',
|
INTERCITY: 'Intercity',
|
||||||
INTERSTATE: 'Interstate'
|
INTERSTATE: 'Interstate'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Relocation Stages
|
// Relocation Stages
|
||||||
const RELOCATION_STAGES = {
|
export const RELOCATION_STAGES = {
|
||||||
DD_ADMIN_REVIEW: 'DD Admin Review',
|
DD_ADMIN_REVIEW: 'DD Admin Review',
|
||||||
RBM_REVIEW: 'RBM Review',
|
RBM_REVIEW: 'RBM Review',
|
||||||
NBH_APPROVAL: 'NBH Approval',
|
NBH_APPROVAL: 'NBH Approval',
|
||||||
LEGAL_CLEARANCE: 'Legal Clearance',
|
LEGAL_CLEARANCE: 'Legal Clearance',
|
||||||
COMPLETED: 'Completed',
|
COMPLETED: 'Completed',
|
||||||
REJECTED: 'Rejected'
|
REJECTED: 'Rejected'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Outlet Types
|
// Outlet Types
|
||||||
const OUTLET_TYPES = {
|
export const OUTLET_TYPES = {
|
||||||
DEALERSHIP: 'Dealership',
|
DEALERSHIP: 'Dealership',
|
||||||
STUDIO: 'Studio'
|
STUDIO: 'Studio'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Outlet Status
|
// Outlet Status
|
||||||
const OUTLET_STATUS = {
|
export const OUTLET_STATUS = {
|
||||||
ACTIVE: 'Active',
|
ACTIVE: 'Active',
|
||||||
PENDING_RESIGNATION: 'Pending Resignation',
|
PENDING_RESIGNATION: 'Pending Resignation',
|
||||||
CLOSED: 'Closed'
|
CLOSED: 'Closed'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Business Types
|
// Business Types
|
||||||
const BUSINESS_TYPES = {
|
export const BUSINESS_TYPES = {
|
||||||
DEALERSHIP: 'Dealership',
|
DEALERSHIP: 'Dealership',
|
||||||
STUDIO: 'Studio'
|
STUDIO: 'Studio'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Payment Types
|
// Payment Types
|
||||||
const PAYMENT_TYPES = {
|
export const PAYMENT_TYPES = {
|
||||||
SECURITY_DEPOSIT: 'Security Deposit',
|
SECURITY_DEPOSIT: 'Security Deposit',
|
||||||
LICENSE_FEE: 'License Fee',
|
LICENSE_FEE: 'License Fee',
|
||||||
SETUP_FEE: 'Setup Fee',
|
SETUP_FEE: 'Setup Fee',
|
||||||
OTHER: 'Other'
|
OTHER: 'Other'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Payment Status
|
// Payment Status
|
||||||
const PAYMENT_STATUS = {
|
export const PAYMENT_STATUS = {
|
||||||
PENDING: 'Pending',
|
PENDING: 'Pending',
|
||||||
PAID: 'Paid',
|
PAID: 'Paid',
|
||||||
OVERDUE: 'Overdue',
|
OVERDUE: 'Overdue',
|
||||||
WAIVED: 'Waived'
|
WAIVED: 'Waived'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// F&F Status
|
// F&F Status
|
||||||
const FNF_STATUS = {
|
export const FNF_STATUS = {
|
||||||
INITIATED: 'Initiated',
|
INITIATED: 'Initiated',
|
||||||
DD_CLEARANCE: 'DD Clearance',
|
DD_CLEARANCE: 'DD Clearance',
|
||||||
LEGAL_CLEARANCE: 'Legal Clearance',
|
LEGAL_CLEARANCE: 'Legal Clearance',
|
||||||
FINANCE_APPROVAL: 'Finance Approval',
|
FINANCE_APPROVAL: 'Finance Approval',
|
||||||
COMPLETED: 'Completed'
|
COMPLETED: 'Completed'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Audit Actions
|
// Audit Actions
|
||||||
const AUDIT_ACTIONS = {
|
export const AUDIT_ACTIONS = {
|
||||||
CREATED: 'CREATED',
|
CREATED: 'CREATED',
|
||||||
UPDATED: 'UPDATED',
|
UPDATED: 'UPDATED',
|
||||||
APPROVED: 'APPROVED',
|
APPROVED: 'APPROVED',
|
||||||
@ -160,10 +160,10 @@ const AUDIT_ACTIONS = {
|
|||||||
STAGE_CHANGED: 'STAGE_CHANGED',
|
STAGE_CHANGED: 'STAGE_CHANGED',
|
||||||
DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED',
|
DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED',
|
||||||
WORKNOTE_ADDED: 'WORKNOTE_ADDED'
|
WORKNOTE_ADDED: 'WORKNOTE_ADDED'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Document Types
|
// Document Types
|
||||||
const DOCUMENT_TYPES = {
|
export const DOCUMENT_TYPES = {
|
||||||
GST_CERTIFICATE: 'GST Certificate',
|
GST_CERTIFICATE: 'GST Certificate',
|
||||||
PAN_CARD: 'PAN Card',
|
PAN_CARD: 'PAN Card',
|
||||||
AADHAAR: 'Aadhaar',
|
AADHAAR: 'Aadhaar',
|
||||||
@ -176,34 +176,12 @@ const DOCUMENT_TYPES = {
|
|||||||
PROPERTY_DOCUMENTS: 'Property Documents',
|
PROPERTY_DOCUMENTS: 'Property Documents',
|
||||||
BANK_STATEMENT: 'Bank Statement',
|
BANK_STATEMENT: 'Bank Statement',
|
||||||
OTHER: 'Other'
|
OTHER: 'Other'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Request Types
|
// Request Types
|
||||||
const REQUEST_TYPES = {
|
export const REQUEST_TYPES = {
|
||||||
APPLICATION: 'application',
|
APPLICATION: 'application',
|
||||||
RESIGNATION: 'resignation',
|
RESIGNATION: 'resignation',
|
||||||
CONSTITUTIONAL: 'constitutional',
|
CONSTITUTIONAL: 'constitutional',
|
||||||
RELOCATION: 'relocation'
|
RELOCATION: 'relocation'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
@ -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: {
|
development: {
|
||||||
username: process.env.DB_USER || 'laxman',
|
username: process.env.DB_USER || 'laxman',
|
||||||
password: process.env.DB_PASSWORD || 'Admin@123',
|
password: process.env.DB_PASSWORD || 'Admin@123',
|
||||||
database: process.env.DB_NAME || 'royal_enfield_onboarding',
|
database: process.env.DB_NAME || 'royal_enfield_onboarding',
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: process.env.DB_PORT || 5432,
|
port: parseInt(process.env.DB_PORT || '5432'),
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
logging: console.log,
|
logging: console.log,
|
||||||
pool: {
|
pool: {
|
||||||
@ -21,7 +23,7 @@ module.exports = {
|
|||||||
password: process.env.DB_PASSWORD || 'Admin@123',
|
password: process.env.DB_PASSWORD || 'Admin@123',
|
||||||
database: process.env.DB_NAME || 'royal_enfield_onboarding',
|
database: process.env.DB_NAME || 'royal_enfield_onboarding',
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: process.env.DB_PORT || 5432,
|
port: parseInt(process.env.DB_PORT || '5432'),
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
logging: false,
|
logging: false,
|
||||||
pool: {
|
pool: {
|
||||||
@ -40,10 +42,12 @@ module.exports = {
|
|||||||
test: {
|
test: {
|
||||||
username: process.env.DB_USER || 'laxman',
|
username: process.env.DB_USER || 'laxman',
|
||||||
password: process.env.DB_PASSWORD || 'Admin@123',
|
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',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: process.env.DB_PORT || 5432,
|
port: parseInt(process.env.DB_PORT || '5432'),
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
logging: false
|
logging: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
99
src/common/middleware/auth.ts
Normal file
99
src/common/middleware/auth.ts
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
78
src/common/middleware/errorHandler.ts
Normal file
78
src/common/middleware/errorHandler.ts
Normal 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;
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
46
src/common/middleware/roleCheck.ts
Normal file
46
src/common/middleware/roleCheck.ts
Normal 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 };
|
||||||
96
src/common/middleware/upload.ts
Normal file
96
src/common/middleware/upload.ts
Normal 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();
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
71
src/common/utils/logger.ts
Normal file
71
src/common/utils/logger.ts
Normal 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;
|
||||||
57
src/database/models/AiSummary.ts
Normal file
57
src/database/models/AiSummary.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
189
src/database/models/Application.ts
Normal file
189
src/database/models/Application.ts
Normal 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;
|
||||||
|
};
|
||||||
65
src/database/models/ApplicationProgress.ts
Normal file
65
src/database/models/ApplicationProgress.ts
Normal 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;
|
||||||
|
};
|
||||||
61
src/database/models/ApplicationStatusHistory.ts
Normal file
61
src/database/models/ApplicationStatusHistory.ts
Normal 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;
|
||||||
|
};
|
||||||
81
src/database/models/Area.ts
Normal file
81
src/database/models/Area.ts
Normal 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;
|
||||||
|
};
|
||||||
67
src/database/models/AreaManager.ts
Normal file
67
src/database/models/AreaManager.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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) => {
|
export interface AuditLogAttributes {
|
||||||
const AuditLog = sequelize.define('AuditLog', {
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -16,7 +31,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: DataTypes.ENUM(Object.values(AUDIT_ACTIONS)),
|
type: DataTypes.ENUM(...Object.values(AUDIT_ACTIONS)),
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
entityType: {
|
entityType: {
|
||||||
@ -54,7 +69,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
AuditLog.associate = (models) => {
|
(AuditLog as any).associate = (models: any) => {
|
||||||
AuditLog.belongsTo(models.User, {
|
AuditLog.belongsTo(models.User, {
|
||||||
foreignKey: 'userId',
|
foreignKey: 'userId',
|
||||||
as: 'user'
|
as: 'user'
|
||||||
@ -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) => {
|
export interface ConstitutionalChangeAttributes {
|
||||||
const ConstitutionalChange = sequelize.define('ConstitutionalChange', {
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -29,7 +46,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeType: {
|
changeType: {
|
||||||
type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_CHANGE_TYPES)),
|
type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_CHANGE_TYPES)),
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
@ -37,7 +54,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
currentStage: {
|
currentStage: {
|
||||||
type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_STAGES)),
|
type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_STAGES)),
|
||||||
defaultValue: CONSTITUTIONAL_STAGES.DD_ADMIN_REVIEW
|
defaultValue: CONSTITUTIONAL_STAGES.DD_ADMIN_REVIEW
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
@ -67,7 +84,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
ConstitutionalChange.associate = (models) => {
|
(ConstitutionalChange as any).associate = (models: any) => {
|
||||||
ConstitutionalChange.belongsTo(models.Outlet, {
|
ConstitutionalChange.belongsTo(models.Outlet, {
|
||||||
foreignKey: 'outletId',
|
foreignKey: 'outletId',
|
||||||
as: 'outlet'
|
as: 'outlet'
|
||||||
89
src/database/models/Dealer.ts
Normal file
89
src/database/models/Dealer.ts
Normal 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;
|
||||||
|
};
|
||||||
53
src/database/models/DealerCode.ts
Normal file
53
src/database/models/DealerCode.ts
Normal 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;
|
||||||
|
};
|
||||||
53
src/database/models/District.ts
Normal file
53
src/database/models/District.ts
Normal 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;
|
||||||
|
};
|
||||||
67
src/database/models/DistrictManager.ts
Normal file
67
src/database/models/DistrictManager.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
104
src/database/models/Document.ts
Normal file
104
src/database/models/Document.ts
Normal 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;
|
||||||
|
};
|
||||||
56
src/database/models/DocumentVersion.ts
Normal file
56
src/database/models/DocumentVersion.ts
Normal 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;
|
||||||
|
};
|
||||||
53
src/database/models/EmailTemplate.ts
Normal file
53
src/database/models/EmailTemplate.ts
Normal 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;
|
||||||
|
};
|
||||||
62
src/database/models/EorChecklist.ts
Normal file
62
src/database/models/EorChecklist.ts
Normal 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;
|
||||||
|
};
|
||||||
65
src/database/models/EorChecklistItem.ts
Normal file
65
src/database/models/EorChecklistItem.ts
Normal 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;
|
||||||
|
};
|
||||||
76
src/database/models/ExitFeedback.ts
Normal file
76
src/database/models/ExitFeedback.ts
Normal 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;
|
||||||
|
};
|
||||||
51
src/database/models/FddAssignment.ts
Normal file
51
src/database/models/FddAssignment.ts
Normal 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;
|
||||||
|
};
|
||||||
70
src/database/models/FddReport.ts
Normal file
70
src/database/models/FddReport.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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) => {
|
export interface FinancePaymentAttributes {
|
||||||
const FinancePayment = sequelize.define('FinancePayment', {
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -16,7 +32,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
paymentType: {
|
paymentType: {
|
||||||
type: DataTypes.ENUM(Object.values(PAYMENT_TYPES)),
|
type: DataTypes.ENUM(...Object.values(PAYMENT_TYPES)),
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
@ -24,7 +40,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
paymentStatus: {
|
paymentStatus: {
|
||||||
type: DataTypes.ENUM(Object.values(PAYMENT_STATUS)),
|
type: DataTypes.ENUM(...Object.values(PAYMENT_STATUS)),
|
||||||
defaultValue: PAYMENT_STATUS.PENDING
|
defaultValue: PAYMENT_STATUS.PENDING
|
||||||
},
|
},
|
||||||
transactionId: {
|
transactionId: {
|
||||||
@ -60,7 +76,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
FinancePayment.associate = (models) => {
|
(FinancePayment as any).associate = (models: any) => {
|
||||||
FinancePayment.belongsTo(models.Application, {
|
FinancePayment.belongsTo(models.Application, {
|
||||||
foreignKey: 'applicationId',
|
foreignKey: 'applicationId',
|
||||||
as: 'application'
|
as: 'application'
|
||||||
@ -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
103
src/database/models/FnF.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -1,5 +1,19 @@
|
|||||||
module.exports = (sequelize, DataTypes) => {
|
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||||
const FnFLineItem = sequelize.define('FnFLineItem', {
|
|
||||||
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -48,7 +62,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
FnFLineItem.associate = (models) => {
|
(FnFLineItem as any).associate = (models: any) => {
|
||||||
FnFLineItem.belongsTo(models.FnF, {
|
FnFLineItem.belongsTo(models.FnF, {
|
||||||
foreignKey: 'fnfId',
|
foreignKey: 'fnfId',
|
||||||
as: 'settlement'
|
as: 'settlement'
|
||||||
62
src/database/models/Interview.ts
Normal file
62
src/database/models/Interview.ts
Normal 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;
|
||||||
|
};
|
||||||
62
src/database/models/InterviewEvaluation.ts
Normal file
62
src/database/models/InterviewEvaluation.ts
Normal 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;
|
||||||
|
};
|
||||||
46
src/database/models/InterviewFeedback.ts
Normal file
46
src/database/models/InterviewFeedback.ts
Normal 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;
|
||||||
|
};
|
||||||
51
src/database/models/InterviewParticipant.ts
Normal file
51
src/database/models/InterviewParticipant.ts
Normal 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;
|
||||||
|
};
|
||||||
61
src/database/models/KTMatrixScore.ts
Normal file
61
src/database/models/KTMatrixScore.ts
Normal 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;
|
||||||
|
};
|
||||||
56
src/database/models/LoaAcknowledgement.ts
Normal file
56
src/database/models/LoaAcknowledgement.ts
Normal 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;
|
||||||
|
};
|
||||||
66
src/database/models/LoaApproval.ts
Normal file
66
src/database/models/LoaApproval.ts
Normal 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;
|
||||||
|
};
|
||||||
57
src/database/models/LoaDocumentGenerated.ts
Normal file
57
src/database/models/LoaDocumentGenerated.ts
Normal 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;
|
||||||
|
};
|
||||||
68
src/database/models/LoaRequest.ts
Normal file
68
src/database/models/LoaRequest.ts
Normal 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;
|
||||||
|
};
|
||||||
56
src/database/models/LoiAcknowledgement.ts
Normal file
56
src/database/models/LoiAcknowledgement.ts
Normal 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;
|
||||||
|
};
|
||||||
66
src/database/models/LoiApproval.ts
Normal file
66
src/database/models/LoiApproval.ts
Normal 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;
|
||||||
|
};
|
||||||
57
src/database/models/LoiDocumentGenerated.ts
Normal file
57
src/database/models/LoiDocumentGenerated.ts
Normal 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;
|
||||||
|
};
|
||||||
68
src/database/models/LoiRequest.ts
Normal file
68
src/database/models/LoiRequest.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -1,5 +1,19 @@
|
|||||||
module.exports = (sequelize, DataTypes) => {
|
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||||
const Notification = sequelize.define('Notification', {
|
|
||||||
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -42,7 +56,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
Notification.associate = (models) => {
|
(Notification as any).associate = (models: any) => {
|
||||||
Notification.belongsTo(models.User, {
|
Notification.belongsTo(models.User, {
|
||||||
foreignKey: 'userId',
|
foreignKey: 'userId',
|
||||||
as: 'recipient'
|
as: 'recipient'
|
||||||
116
src/database/models/Opportunity.ts
Normal file
116
src/database/models/Opportunity.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
125
src/database/models/Outlet.ts
Normal file
125
src/database/models/Outlet.ts
Normal 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;
|
||||||
|
};
|
||||||
61
src/database/models/Permission.ts
Normal file
61
src/database/models/Permission.ts
Normal 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;
|
||||||
|
};
|
||||||
37
src/database/models/Questionnaire.ts
Normal file
37
src/database/models/Questionnaire.ts
Normal 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;
|
||||||
|
};
|
||||||
62
src/database/models/QuestionnaireQuestion.ts
Normal file
62
src/database/models/QuestionnaireQuestion.ts
Normal 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;
|
||||||
|
};
|
||||||
66
src/database/models/QuestionnaireResponse.ts
Normal file
66
src/database/models/QuestionnaireResponse.ts
Normal 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;
|
||||||
|
};
|
||||||
56
src/database/models/QuestionnaireScore.ts
Normal file
56
src/database/models/QuestionnaireScore.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
84
src/database/models/Region.ts
Normal file
84
src/database/models/Region.ts
Normal 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;
|
||||||
|
};
|
||||||
67
src/database/models/RegionManager.ts
Normal file
67
src/database/models/RegionManager.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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) => {
|
export interface RelocationRequestAttributes {
|
||||||
const RelocationRequest = sequelize.define('RelocationRequest', {
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -29,7 +49,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
relocationType: {
|
relocationType: {
|
||||||
type: DataTypes.ENUM(Object.values(RELOCATION_TYPES)),
|
type: DataTypes.ENUM(...Object.values(RELOCATION_TYPES)),
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
newAddress: {
|
newAddress: {
|
||||||
@ -49,7 +69,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
currentStage: {
|
currentStage: {
|
||||||
type: DataTypes.ENUM(Object.values(RELOCATION_STAGES)),
|
type: DataTypes.ENUM(...Object.values(RELOCATION_STAGES)),
|
||||||
defaultValue: RELOCATION_STAGES.DD_ADMIN_REVIEW
|
defaultValue: RELOCATION_STAGES.DD_ADMIN_REVIEW
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
@ -79,7 +99,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
RelocationRequest.associate = (models) => {
|
(RelocationRequest as any).associate = (models: any) => {
|
||||||
RelocationRequest.belongsTo(models.Outlet, {
|
RelocationRequest.belongsTo(models.Outlet, {
|
||||||
foreignKey: 'outletId',
|
foreignKey: 'outletId',
|
||||||
as: 'outlet'
|
as: 'outlet'
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
132
src/database/models/Resignation.ts
Normal file
132
src/database/models/Resignation.ts
Normal 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;
|
||||||
|
};
|
||||||
59
src/database/models/Role.ts
Normal file
59
src/database/models/Role.ts
Normal 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;
|
||||||
|
};
|
||||||
77
src/database/models/RolePermission.ts
Normal file
77
src/database/models/RolePermission.ts
Normal 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;
|
||||||
|
};
|
||||||
56
src/database/models/SLABreach.ts
Normal file
56
src/database/models/SLABreach.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -1,5 +1,18 @@
|
|||||||
module.exports = (sequelize, DataTypes) => {
|
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||||
const SLAConfiguration = sequelize.define('SLAConfiguration', {
|
|
||||||
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -34,7 +47,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
SLAConfiguration.associate = (models) => {
|
(SLAConfiguration as any).associate = (models: any) => {
|
||||||
SLAConfiguration.hasMany(models.SLAReminder, {
|
SLAConfiguration.hasMany(models.SLAReminder, {
|
||||||
foreignKey: 'slaConfigId',
|
foreignKey: 'slaConfigId',
|
||||||
as: 'reminders'
|
as: 'reminders'
|
||||||
@ -1,5 +1,18 @@
|
|||||||
module.exports = (sequelize, DataTypes) => {
|
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||||
const SLAEscalationConfig = sequelize.define('SLAEscalationConfig', {
|
|
||||||
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -38,7 +51,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
SLAEscalationConfig.associate = (models) => {
|
(SLAEscalationConfig as any).associate = (models: any) => {
|
||||||
SLAEscalationConfig.belongsTo(models.SLAConfiguration, {
|
SLAEscalationConfig.belongsTo(models.SLAConfiguration, {
|
||||||
foreignKey: 'slaConfigId',
|
foreignKey: 'slaConfigId',
|
||||||
as: 'slaConfig'
|
as: 'slaConfig'
|
||||||
@ -1,5 +1,17 @@
|
|||||||
module.exports = (sequelize, DataTypes) => {
|
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||||
const SLAReminder = sequelize.define('SLAReminder', {
|
|
||||||
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
@ -33,7 +45,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
SLAReminder.associate = (models) => {
|
(SLAReminder as any).associate = (models: any) => {
|
||||||
SLAReminder.belongsTo(models.SLAConfiguration, {
|
SLAReminder.belongsTo(models.SLAConfiguration, {
|
||||||
foreignKey: 'slaConfigId',
|
foreignKey: 'slaConfigId',
|
||||||
as: 'slaConfig'
|
as: 'slaConfig'
|
||||||
76
src/database/models/SLATracking.ts
Normal file
76
src/database/models/SLATracking.ts
Normal 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;
|
||||||
|
};
|
||||||
75
src/database/models/SecurityDeposit.ts
Normal file
75
src/database/models/SecurityDeposit.ts
Normal 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;
|
||||||
|
};
|
||||||
58
src/database/models/State.ts
Normal file
58
src/database/models/State.ts
Normal 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;
|
||||||
|
};
|
||||||
71
src/database/models/TerminationRequest.ts
Normal file
71
src/database/models/TerminationRequest.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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
160
src/database/models/User.ts
Normal 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;
|
||||||
|
};
|
||||||
109
src/database/models/UserRole.ts
Normal file
109
src/database/models/UserRole.ts
Normal 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;
|
||||||
|
};
|
||||||
46
src/database/models/WorkNoteAttachment.ts
Normal file
46
src/database/models/WorkNoteAttachment.ts
Normal 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;
|
||||||
|
};
|
||||||
41
src/database/models/WorkNoteTag.ts
Normal file
41
src/database/models/WorkNoteTag.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -1,5 +1,19 @@
|
|||||||
module.exports = (sequelize, DataTypes) => {
|
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||||
const WorkflowStageConfig = sequelize.define('WorkflowStageConfig', {
|
|
||||||
|
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: {
|
id: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
defaultValue: DataTypes.UUIDV4,
|
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
|
// Future associations with applications or tasks
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
78
src/database/models/Worknote.ts
Normal file
78
src/database/models/Worknote.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
75
src/database/models/Zone.ts
Normal file
75
src/database/models/Zone.ts
Normal 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;
|
||||||
|
};
|
||||||
67
src/database/models/ZoneManager.ts
Normal file
67
src/database/models/ZoneManager.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
203
src/database/models/index.ts
Normal file
203
src/database/models/index.ts
Normal 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 };
|
||||||
169
src/modules/admin/admin.controller.ts
Normal file
169
src/modules/admin/admin.controller.ts
Normal 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' });
|
||||||
|
}
|
||||||
|
};
|
||||||
27
src/modules/admin/admin.routes.ts
Normal file
27
src/modules/admin/admin.routes.ts
Normal 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;
|
||||||
160
src/modules/assessment/assessment.controller.ts
Normal file
160
src/modules/assessment/assessment.controller.ts
Normal 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' });
|
||||||
|
}
|
||||||
|
};
|
||||||
20
src/modules/assessment/assessment.routes.ts
Normal file
20
src/modules/assessment/assessment.routes.ts
Normal 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;
|
||||||
@ -1,10 +1,13 @@
|
|||||||
const bcrypt = require('bcryptjs');
|
import { Request, Response } from 'express';
|
||||||
const { User, AuditLog } = require('../../database/models');
|
import bcrypt from 'bcryptjs';
|
||||||
const { generateToken } = require('../../common/config/auth');
|
import db from '../../database/models/index.js';
|
||||||
const { AUDIT_ACTIONS } = require('../../common/config/constants');
|
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
|
// Register new user
|
||||||
exports.register = async (req, res) => {
|
export const register = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { email, password, fullName, role, phone, region, zone } = req.body;
|
const { email, password, fullName, role, phone, region, zone } = req.body;
|
||||||
|
|
||||||
@ -36,7 +39,8 @@ exports.register = async (req, res) => {
|
|||||||
role,
|
role,
|
||||||
phone,
|
phone,
|
||||||
region,
|
region,
|
||||||
zone
|
zone,
|
||||||
|
status: 'active'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log audit
|
// Log audit
|
||||||
@ -62,7 +66,7 @@ exports.register = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
exports.login = async (req, res) => {
|
export const login = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.body;
|
const { email, password } = req.body;
|
||||||
|
|
||||||
@ -93,7 +97,7 @@ exports.login = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify password
|
// Verify password
|
||||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
const isValidPassword = await bcrypt.compare(password, user.password!);
|
||||||
if (!isValidPassword) {
|
if (!isValidPassword) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
@ -110,7 +114,7 @@ exports.login = async (req, res) => {
|
|||||||
// Log audit
|
// Log audit
|
||||||
await AuditLog.create({
|
await AuditLog.create({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
action: 'user_login',
|
action: 'user_login' as any,
|
||||||
entityType: 'user',
|
entityType: 'user',
|
||||||
entityId: user.id
|
entityId: user.id
|
||||||
});
|
});
|
||||||
@ -137,8 +141,12 @@ exports.login = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get profile
|
// Get profile
|
||||||
exports.getProfile = async (req, res) => {
|
export const getProfile = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
const user = await User.findByPk(req.user.id, {
|
const user = await User.findByPk(req.user.id, {
|
||||||
attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt']
|
attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt']
|
||||||
});
|
});
|
||||||
@ -173,8 +181,12 @@ exports.getProfile = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Update profile
|
// Update profile
|
||||||
exports.updateProfile = async (req, res) => {
|
export const updateProfile = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
const { fullName, phone } = req.body;
|
const { fullName, phone } = req.body;
|
||||||
|
|
||||||
const user = await User.findByPk(req.user.id);
|
const user = await User.findByPk(req.user.id);
|
||||||
@ -212,8 +224,12 @@ exports.updateProfile = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Change password
|
// Change password
|
||||||
exports.changePassword = async (req, res) => {
|
export const changePassword = async (req: AuthRequest, res: Response) => {
|
||||||
try {
|
try {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
const { currentPassword, newPassword } = req.body;
|
const { currentPassword, newPassword } = req.body;
|
||||||
|
|
||||||
if (!currentPassword || !newPassword) {
|
if (!currentPassword || !newPassword) {
|
||||||
@ -233,7 +249,7 @@ exports.changePassword = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify current password
|
// Verify current password
|
||||||
const isValid = await bcrypt.compare(currentPassword, user.password);
|
const isValid = await bcrypt.compare(currentPassword, user.password!);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
@ -250,7 +266,7 @@ exports.changePassword = async (req, res) => {
|
|||||||
// Log audit
|
// Log audit
|
||||||
await AuditLog.create({
|
await AuditLog.create({
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
action: 'password_changed',
|
action: 'password_changed' as any,
|
||||||
entityType: 'user',
|
entityType: 'user',
|
||||||
entityId: req.user.id
|
entityId: req.user.id
|
||||||
});
|
});
|
||||||
@ -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
Loading…
Reference in New Issue
Block a user