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",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API for Royal Enfield Dealership Onboarding System",
|
||||
"type": "module",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"migrate": "node scripts/migrate.js",
|
||||
"start": "node dist/src/server.js",
|
||||
"dev": "tsx watch src/server.ts",
|
||||
"build": "tsc",
|
||||
"type-check": "tsc --noEmit",
|
||||
"migrate": "node dist/scripts/migrate.js",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage",
|
||||
"clear-logs": "rm -rf logs/*.log"
|
||||
@ -20,27 +23,43 @@
|
||||
"author": "Royal Enfield",
|
||||
"license": "PROPRIETARY",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"sequelize": "^6.35.2",
|
||||
"pg": "^8.11.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"compression": "^1.8.1",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^7.1.0",
|
||||
"express-validator": "^7.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.7",
|
||||
"winston": "^3.11.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"uuid": "^9.0.1",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"compression": "^1.7.4"
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"express-validator": "^7.3.1",
|
||||
"helmet": "^8.1.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"multer": "^2.0.2",
|
||||
"nodemailer": "^7.0.12",
|
||||
"pg": "^8.17.2",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.7",
|
||||
"uuid": "^13.0.0",
|
||||
"winston": "^3.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/compression": "^1.8.1",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^25.0.9",
|
||||
"@types/nodemailer": "^7.0.5",
|
||||
"@types/pg": "^8.16.0",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/validator": "^13.15.10",
|
||||
"jest": "^30.2.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"jest": "^29.7.0",
|
||||
"supertest": "^6.3.3"
|
||||
"supertest": "^7.2.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
|
||||
41
scripts/migrate.ts
Normal file
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
|
||||
const ROLES = {
|
||||
export const ROLES = {
|
||||
DD: 'DD',
|
||||
DD_ZM: 'DD-ZM',
|
||||
RBM: 'RBM',
|
||||
@ -13,19 +13,19 @@ const ROLES = {
|
||||
DD_AM: 'DD AM',
|
||||
FINANCE: 'Finance',
|
||||
DEALER: 'Dealer'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Regions
|
||||
const REGIONS = {
|
||||
export const REGIONS = {
|
||||
EAST: 'East',
|
||||
WEST: 'West',
|
||||
NORTH: 'North',
|
||||
SOUTH: 'South',
|
||||
CENTRAL: 'Central'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Application Stages
|
||||
const APPLICATION_STAGES = {
|
||||
export const APPLICATION_STAGES = {
|
||||
DD: 'DD',
|
||||
DD_ZM: 'DD-ZM',
|
||||
RBM: 'RBM',
|
||||
@ -37,18 +37,18 @@ const APPLICATION_STAGES = {
|
||||
FINANCE: 'Finance',
|
||||
APPROVED: 'Approved',
|
||||
REJECTED: 'Rejected'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Application Status
|
||||
const APPLICATION_STATUS = {
|
||||
export const APPLICATION_STATUS = {
|
||||
PENDING: 'Pending',
|
||||
IN_REVIEW: 'In Review',
|
||||
APPROVED: 'Approved',
|
||||
REJECTED: 'Rejected'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Resignation Stages
|
||||
const RESIGNATION_STAGES = {
|
||||
export const RESIGNATION_STAGES = {
|
||||
ASM: 'ASM',
|
||||
RBM: 'RBM',
|
||||
ZBH: 'ZBH',
|
||||
@ -59,99 +59,99 @@ const RESIGNATION_STAGES = {
|
||||
FNF_INITIATED: 'F&F Initiated',
|
||||
COMPLETED: 'Completed',
|
||||
REJECTED: 'Rejected'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Resignation Types
|
||||
const RESIGNATION_TYPES = {
|
||||
export const RESIGNATION_TYPES = {
|
||||
VOLUNTARY: 'Voluntary',
|
||||
RETIREMENT: 'Retirement',
|
||||
HEALTH_ISSUES: 'Health Issues',
|
||||
BUSINESS_CLOSURE: 'Business Closure',
|
||||
OTHER: 'Other'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Constitutional Change Types
|
||||
const CONSTITUTIONAL_CHANGE_TYPES = {
|
||||
export const CONSTITUTIONAL_CHANGE_TYPES = {
|
||||
OWNERSHIP_TRANSFER: 'Ownership Transfer',
|
||||
PARTNERSHIP_CHANGE: 'Partnership Change',
|
||||
LLP_CONVERSION: 'LLP Conversion',
|
||||
COMPANY_FORMATION: 'Company Formation',
|
||||
DIRECTOR_CHANGE: 'Director Change'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Constitutional Change Stages
|
||||
const CONSTITUTIONAL_STAGES = {
|
||||
export const CONSTITUTIONAL_STAGES = {
|
||||
DD_ADMIN_REVIEW: 'DD Admin Review',
|
||||
LEGAL_REVIEW: 'Legal Review',
|
||||
NBH_APPROVAL: 'NBH Approval',
|
||||
FINANCE_CLEARANCE: 'Finance Clearance',
|
||||
COMPLETED: 'Completed',
|
||||
REJECTED: 'Rejected'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Relocation Types
|
||||
const RELOCATION_TYPES = {
|
||||
export const RELOCATION_TYPES = {
|
||||
WITHIN_CITY: 'Within City',
|
||||
INTERCITY: 'Intercity',
|
||||
INTERSTATE: 'Interstate'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Relocation Stages
|
||||
const RELOCATION_STAGES = {
|
||||
export const RELOCATION_STAGES = {
|
||||
DD_ADMIN_REVIEW: 'DD Admin Review',
|
||||
RBM_REVIEW: 'RBM Review',
|
||||
NBH_APPROVAL: 'NBH Approval',
|
||||
LEGAL_CLEARANCE: 'Legal Clearance',
|
||||
COMPLETED: 'Completed',
|
||||
REJECTED: 'Rejected'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Outlet Types
|
||||
const OUTLET_TYPES = {
|
||||
export const OUTLET_TYPES = {
|
||||
DEALERSHIP: 'Dealership',
|
||||
STUDIO: 'Studio'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Outlet Status
|
||||
const OUTLET_STATUS = {
|
||||
export const OUTLET_STATUS = {
|
||||
ACTIVE: 'Active',
|
||||
PENDING_RESIGNATION: 'Pending Resignation',
|
||||
CLOSED: 'Closed'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Business Types
|
||||
const BUSINESS_TYPES = {
|
||||
export const BUSINESS_TYPES = {
|
||||
DEALERSHIP: 'Dealership',
|
||||
STUDIO: 'Studio'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Payment Types
|
||||
const PAYMENT_TYPES = {
|
||||
export const PAYMENT_TYPES = {
|
||||
SECURITY_DEPOSIT: 'Security Deposit',
|
||||
LICENSE_FEE: 'License Fee',
|
||||
SETUP_FEE: 'Setup Fee',
|
||||
OTHER: 'Other'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Payment Status
|
||||
const PAYMENT_STATUS = {
|
||||
export const PAYMENT_STATUS = {
|
||||
PENDING: 'Pending',
|
||||
PAID: 'Paid',
|
||||
OVERDUE: 'Overdue',
|
||||
WAIVED: 'Waived'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// F&F Status
|
||||
const FNF_STATUS = {
|
||||
export const FNF_STATUS = {
|
||||
INITIATED: 'Initiated',
|
||||
DD_CLEARANCE: 'DD Clearance',
|
||||
LEGAL_CLEARANCE: 'Legal Clearance',
|
||||
FINANCE_APPROVAL: 'Finance Approval',
|
||||
COMPLETED: 'Completed'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Audit Actions
|
||||
const AUDIT_ACTIONS = {
|
||||
export const AUDIT_ACTIONS = {
|
||||
CREATED: 'CREATED',
|
||||
UPDATED: 'UPDATED',
|
||||
APPROVED: 'APPROVED',
|
||||
@ -160,10 +160,10 @@ const AUDIT_ACTIONS = {
|
||||
STAGE_CHANGED: 'STAGE_CHANGED',
|
||||
DOCUMENT_UPLOADED: 'DOCUMENT_UPLOADED',
|
||||
WORKNOTE_ADDED: 'WORKNOTE_ADDED'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Document Types
|
||||
const DOCUMENT_TYPES = {
|
||||
export const DOCUMENT_TYPES = {
|
||||
GST_CERTIFICATE: 'GST Certificate',
|
||||
PAN_CARD: 'PAN Card',
|
||||
AADHAAR: 'Aadhaar',
|
||||
@ -176,34 +176,12 @@ const DOCUMENT_TYPES = {
|
||||
PROPERTY_DOCUMENTS: 'Property Documents',
|
||||
BANK_STATEMENT: 'Bank Statement',
|
||||
OTHER: 'Other'
|
||||
};
|
||||
} as const;
|
||||
|
||||
// Request Types
|
||||
const REQUEST_TYPES = {
|
||||
export const REQUEST_TYPES = {
|
||||
APPLICATION: 'application',
|
||||
RESIGNATION: 'resignation',
|
||||
CONSTITUTIONAL: 'constitutional',
|
||||
RELOCATION: 'relocation'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
ROLES,
|
||||
REGIONS,
|
||||
APPLICATION_STAGES,
|
||||
APPLICATION_STATUS,
|
||||
RESIGNATION_STAGES,
|
||||
RESIGNATION_TYPES,
|
||||
CONSTITUTIONAL_CHANGE_TYPES,
|
||||
CONSTITUTIONAL_STAGES,
|
||||
RELOCATION_TYPES,
|
||||
RELOCATION_STAGES,
|
||||
OUTLET_TYPES,
|
||||
OUTLET_STATUS,
|
||||
BUSINESS_TYPES,
|
||||
PAYMENT_TYPES,
|
||||
PAYMENT_STATUS,
|
||||
FNF_STATUS,
|
||||
AUDIT_ACTIONS,
|
||||
DOCUMENT_TYPES,
|
||||
REQUEST_TYPES
|
||||
};
|
||||
} as const;
|
||||
@ -1,12 +1,14 @@
|
||||
require('dotenv').config();
|
||||
import 'dotenv/config';
|
||||
import { Options } from 'sequelize';
|
||||
import { DbConfig } from '../../types/common.types.js';
|
||||
|
||||
module.exports = {
|
||||
const config: DbConfig = {
|
||||
development: {
|
||||
username: process.env.DB_USER || 'laxman',
|
||||
password: process.env.DB_PASSWORD || 'Admin@123',
|
||||
database: process.env.DB_NAME || 'royal_enfield_onboarding',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 5432,
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
dialect: 'postgres',
|
||||
logging: console.log,
|
||||
pool: {
|
||||
@ -21,7 +23,7 @@ module.exports = {
|
||||
password: process.env.DB_PASSWORD || 'Admin@123',
|
||||
database: process.env.DB_NAME || 'royal_enfield_onboarding',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 5432,
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
dialect: 'postgres',
|
||||
logging: false,
|
||||
pool: {
|
||||
@ -40,10 +42,12 @@ module.exports = {
|
||||
test: {
|
||||
username: process.env.DB_USER || 'laxman',
|
||||
password: process.env.DB_PASSWORD || 'Admin@123',
|
||||
database: process.env.DB_NAME + '_test' || 'royal_enfield_onboarding_test',
|
||||
database: (process.env.DB_NAME || 'royal_enfield_onboarding') + '_test',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 5432,
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
dialect: 'postgres',
|
||||
logging: false
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
@ -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) => {
|
||||
const AuditLog = sequelize.define('AuditLog', {
|
||||
export interface AuditLogAttributes {
|
||||
id: string;
|
||||
userId: string | null;
|
||||
action: typeof AUDIT_ACTIONS[keyof typeof AUDIT_ACTIONS];
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
oldData: any | null;
|
||||
newData: any | null;
|
||||
ipAddress: string | null;
|
||||
userAgent: string | null;
|
||||
}
|
||||
|
||||
export interface AuditLogInstance extends Model<AuditLogAttributes>, AuditLogAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const AuditLog = sequelize.define<AuditLogInstance>('AuditLog', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -16,7 +31,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
}
|
||||
},
|
||||
action: {
|
||||
type: DataTypes.ENUM(Object.values(AUDIT_ACTIONS)),
|
||||
type: DataTypes.ENUM(...Object.values(AUDIT_ACTIONS)),
|
||||
allowNull: false
|
||||
},
|
||||
entityType: {
|
||||
@ -54,7 +69,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
AuditLog.associate = (models) => {
|
||||
(AuditLog as any).associate = (models: any) => {
|
||||
AuditLog.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'user'
|
||||
@ -1,7 +1,24 @@
|
||||
const { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } = require('../../common/config/constants');
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
import { CONSTITUTIONAL_CHANGE_TYPES, CONSTITUTIONAL_STAGES } from '../../common/config/constants.js';
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const ConstitutionalChange = sequelize.define('ConstitutionalChange', {
|
||||
export interface ConstitutionalChangeAttributes {
|
||||
id: string;
|
||||
requestId: string;
|
||||
outletId: string;
|
||||
dealerId: string;
|
||||
changeType: typeof CONSTITUTIONAL_CHANGE_TYPES[keyof typeof CONSTITUTIONAL_CHANGE_TYPES];
|
||||
description: string;
|
||||
currentStage: typeof CONSTITUTIONAL_STAGES[keyof typeof CONSTITUTIONAL_STAGES];
|
||||
status: string;
|
||||
progressPercentage: number;
|
||||
documents: any[];
|
||||
timeline: any[];
|
||||
}
|
||||
|
||||
export interface ConstitutionalChangeInstance extends Model<ConstitutionalChangeAttributes>, ConstitutionalChangeAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const ConstitutionalChange = sequelize.define<ConstitutionalChangeInstance>('ConstitutionalChange', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -29,7 +46,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
}
|
||||
},
|
||||
changeType: {
|
||||
type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_CHANGE_TYPES)),
|
||||
type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_CHANGE_TYPES)),
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
@ -37,7 +54,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
allowNull: false
|
||||
},
|
||||
currentStage: {
|
||||
type: DataTypes.ENUM(Object.values(CONSTITUTIONAL_STAGES)),
|
||||
type: DataTypes.ENUM(...Object.values(CONSTITUTIONAL_STAGES)),
|
||||
defaultValue: CONSTITUTIONAL_STAGES.DD_ADMIN_REVIEW
|
||||
},
|
||||
status: {
|
||||
@ -67,7 +84,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
ConstitutionalChange.associate = (models) => {
|
||||
(ConstitutionalChange as any).associate = (models: any) => {
|
||||
ConstitutionalChange.belongsTo(models.Outlet, {
|
||||
foreignKey: 'outletId',
|
||||
as: 'outlet'
|
||||
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) => {
|
||||
const FinancePayment = sequelize.define('FinancePayment', {
|
||||
export interface FinancePaymentAttributes {
|
||||
id: string;
|
||||
applicationId: string;
|
||||
paymentType: typeof PAYMENT_TYPES[keyof typeof PAYMENT_TYPES];
|
||||
amount: number;
|
||||
paymentStatus: typeof PAYMENT_STATUS[keyof typeof PAYMENT_STATUS];
|
||||
transactionId: string | null;
|
||||
paymentDate: Date | null;
|
||||
verifiedBy: string | null;
|
||||
verificationDate: Date | null;
|
||||
remarks: string | null;
|
||||
}
|
||||
|
||||
export interface FinancePaymentInstance extends Model<FinancePaymentAttributes>, FinancePaymentAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const FinancePayment = sequelize.define<FinancePaymentInstance>('FinancePayment', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -16,7 +32,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
}
|
||||
},
|
||||
paymentType: {
|
||||
type: DataTypes.ENUM(Object.values(PAYMENT_TYPES)),
|
||||
type: DataTypes.ENUM(...Object.values(PAYMENT_TYPES)),
|
||||
allowNull: false
|
||||
},
|
||||
amount: {
|
||||
@ -24,7 +40,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
allowNull: false
|
||||
},
|
||||
paymentStatus: {
|
||||
type: DataTypes.ENUM(Object.values(PAYMENT_STATUS)),
|
||||
type: DataTypes.ENUM(...Object.values(PAYMENT_STATUS)),
|
||||
defaultValue: PAYMENT_STATUS.PENDING
|
||||
},
|
||||
transactionId: {
|
||||
@ -60,7 +76,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
FinancePayment.associate = (models) => {
|
||||
(FinancePayment as any).associate = (models: any) => {
|
||||
FinancePayment.belongsTo(models.Application, {
|
||||
foreignKey: 'applicationId',
|
||||
as: 'application'
|
||||
@ -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) => {
|
||||
const FnFLineItem = sequelize.define('FnFLineItem', {
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface FnFLineItemAttributes {
|
||||
id: string;
|
||||
fnfId: string;
|
||||
itemType: 'Payable' | 'Receivable' | 'Deduction';
|
||||
description: string;
|
||||
department: string;
|
||||
amount: number;
|
||||
addedBy: string | null;
|
||||
}
|
||||
|
||||
export interface FnFLineItemInstance extends Model<FnFLineItemAttributes>, FnFLineItemAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const FnFLineItem = sequelize.define<FnFLineItemInstance>('FnFLineItem', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -48,7 +62,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
FnFLineItem.associate = (models) => {
|
||||
(FnFLineItem as any).associate = (models: any) => {
|
||||
FnFLineItem.belongsTo(models.FnF, {
|
||||
foreignKey: 'fnfId',
|
||||
as: 'settlement'
|
||||
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) => {
|
||||
const Notification = sequelize.define('Notification', {
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface NotificationAttributes {
|
||||
id: string;
|
||||
userId: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: string;
|
||||
link: string | null;
|
||||
isRead: boolean;
|
||||
}
|
||||
|
||||
export interface NotificationInstance extends Model<NotificationAttributes>, NotificationAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const Notification = sequelize.define<NotificationInstance>('Notification', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -42,7 +56,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
Notification.associate = (models) => {
|
||||
(Notification as any).associate = (models: any) => {
|
||||
Notification.belongsTo(models.User, {
|
||||
foreignKey: 'userId',
|
||||
as: 'recipient'
|
||||
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) => {
|
||||
const RelocationRequest = sequelize.define('RelocationRequest', {
|
||||
export interface RelocationRequestAttributes {
|
||||
id: string;
|
||||
requestId: string;
|
||||
outletId: string;
|
||||
dealerId: string;
|
||||
relocationType: typeof RELOCATION_TYPES[keyof typeof RELOCATION_TYPES];
|
||||
newAddress: string;
|
||||
newCity: string;
|
||||
newState: string;
|
||||
reason: string;
|
||||
currentStage: typeof RELOCATION_STAGES[keyof typeof RELOCATION_STAGES];
|
||||
status: string;
|
||||
progressPercentage: number;
|
||||
documents: any[];
|
||||
timeline: any[];
|
||||
}
|
||||
|
||||
export interface RelocationRequestInstance extends Model<RelocationRequestAttributes>, RelocationRequestAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const RelocationRequest = sequelize.define<RelocationRequestInstance>('RelocationRequest', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -29,7 +49,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
}
|
||||
},
|
||||
relocationType: {
|
||||
type: DataTypes.ENUM(Object.values(RELOCATION_TYPES)),
|
||||
type: DataTypes.ENUM(...Object.values(RELOCATION_TYPES)),
|
||||
allowNull: false
|
||||
},
|
||||
newAddress: {
|
||||
@ -49,7 +69,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
allowNull: false
|
||||
},
|
||||
currentStage: {
|
||||
type: DataTypes.ENUM(Object.values(RELOCATION_STAGES)),
|
||||
type: DataTypes.ENUM(...Object.values(RELOCATION_STAGES)),
|
||||
defaultValue: RELOCATION_STAGES.DD_ADMIN_REVIEW
|
||||
},
|
||||
status: {
|
||||
@ -79,7 +99,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
RelocationRequest.associate = (models) => {
|
||||
(RelocationRequest as any).associate = (models: any) => {
|
||||
RelocationRequest.belongsTo(models.Outlet, {
|
||||
foreignKey: 'outletId',
|
||||
as: 'outlet'
|
||||
@ -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) => {
|
||||
const SLAConfiguration = sequelize.define('SLAConfiguration', {
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface SLAConfigurationAttributes {
|
||||
id: string;
|
||||
activityName: string;
|
||||
ownerRole: string;
|
||||
tatHours: number;
|
||||
tatUnit: 'hours' | 'days';
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface SLAConfigurationInstance extends Model<SLAConfigurationAttributes>, SLAConfigurationAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const SLAConfiguration = sequelize.define<SLAConfigurationInstance>('SLAConfiguration', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -34,7 +47,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
SLAConfiguration.associate = (models) => {
|
||||
(SLAConfiguration as any).associate = (models: any) => {
|
||||
SLAConfiguration.hasMany(models.SLAReminder, {
|
||||
foreignKey: 'slaConfigId',
|
||||
as: 'reminders'
|
||||
@ -1,5 +1,18 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const SLAEscalationConfig = sequelize.define('SLAEscalationConfig', {
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface SLAEscalationConfigAttributes {
|
||||
id: string;
|
||||
slaConfigId: string;
|
||||
level: number;
|
||||
timeValue: number;
|
||||
timeUnit: 'hours' | 'days';
|
||||
notifyEmail: string;
|
||||
}
|
||||
|
||||
export interface SLAEscalationConfigInstance extends Model<SLAEscalationConfigAttributes>, SLAEscalationConfigAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const SLAEscalationConfig = sequelize.define<SLAEscalationConfigInstance>('SLAEscalationConfig', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -38,7 +51,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
SLAEscalationConfig.associate = (models) => {
|
||||
(SLAEscalationConfig as any).associate = (models: any) => {
|
||||
SLAEscalationConfig.belongsTo(models.SLAConfiguration, {
|
||||
foreignKey: 'slaConfigId',
|
||||
as: 'slaConfig'
|
||||
@ -1,5 +1,17 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const SLAReminder = sequelize.define('SLAReminder', {
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface SLAReminderAttributes {
|
||||
id: string;
|
||||
slaConfigId: string;
|
||||
timeValue: number;
|
||||
timeUnit: 'hours' | 'days';
|
||||
isEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface SLAReminderInstance extends Model<SLAReminderAttributes>, SLAReminderAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const SLAReminder = sequelize.define<SLAReminderInstance>('SLAReminder', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -33,7 +45,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
SLAReminder.associate = (models) => {
|
||||
(SLAReminder as any).associate = (models: any) => {
|
||||
SLAReminder.belongsTo(models.SLAConfiguration, {
|
||||
foreignKey: 'slaConfigId',
|
||||
as: 'slaConfig'
|
||||
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) => {
|
||||
const WorkflowStageConfig = sequelize.define('WorkflowStageConfig', {
|
||||
import { Model, DataTypes, Sequelize } from 'sequelize';
|
||||
|
||||
export interface WorkflowStageConfigAttributes {
|
||||
id: string;
|
||||
stageName: string;
|
||||
stageOrder: number;
|
||||
colorCode: string | null;
|
||||
isParallel: boolean;
|
||||
defaultEvaluators: any[];
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface WorkflowStageConfigInstance extends Model<WorkflowStageConfigAttributes>, WorkflowStageConfigAttributes { }
|
||||
|
||||
export default (sequelize: Sequelize) => {
|
||||
const WorkflowStageConfig = sequelize.define<WorkflowStageConfigInstance>('WorkflowStageConfig', {
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
@ -39,7 +53,7 @@ module.exports = (sequelize, DataTypes) => {
|
||||
]
|
||||
});
|
||||
|
||||
WorkflowStageConfig.associate = (models) => {
|
||||
(WorkflowStageConfig as any).associate = (models: any) => {
|
||||
// Future associations with applications or tasks
|
||||
};
|
||||
|
||||
@ -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');
|
||||
const { User, AuditLog } = require('../../database/models');
|
||||
const { generateToken } = require('../../common/config/auth');
|
||||
const { AUDIT_ACTIONS } = require('../../common/config/constants');
|
||||
import { Request, Response } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import db from '../../database/models/index.js';
|
||||
const { User, AuditLog } = db;
|
||||
import { generateToken } from '../../common/config/auth.js';
|
||||
import { AUDIT_ACTIONS } from '../../common/config/constants.js';
|
||||
import { AuthRequest } from '../../types/express.types.js';
|
||||
|
||||
// Register new user
|
||||
exports.register = async (req, res) => {
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { email, password, fullName, role, phone, region, zone } = req.body;
|
||||
|
||||
@ -36,7 +39,8 @@ exports.register = async (req, res) => {
|
||||
role,
|
||||
phone,
|
||||
region,
|
||||
zone
|
||||
zone,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
// Log audit
|
||||
@ -62,7 +66,7 @@ exports.register = async (req, res) => {
|
||||
};
|
||||
|
||||
// Login
|
||||
exports.login = async (req, res) => {
|
||||
export const login = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
@ -93,7 +97,7 @@ exports.login = async (req, res) => {
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
||||
const isValidPassword = await bcrypt.compare(password, user.password!);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
@ -110,7 +114,7 @@ exports.login = async (req, res) => {
|
||||
// Log audit
|
||||
await AuditLog.create({
|
||||
userId: user.id,
|
||||
action: 'user_login',
|
||||
action: 'user_login' as any,
|
||||
entityType: 'user',
|
||||
entityId: user.id
|
||||
});
|
||||
@ -137,8 +141,12 @@ exports.login = async (req, res) => {
|
||||
};
|
||||
|
||||
// Get profile
|
||||
exports.getProfile = async (req, res) => {
|
||||
export const getProfile = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: ['id', 'email', 'name', 'role', 'region', 'zone', 'phone', 'createdAt']
|
||||
});
|
||||
@ -173,8 +181,12 @@ exports.getProfile = async (req, res) => {
|
||||
};
|
||||
|
||||
// Update profile
|
||||
exports.updateProfile = async (req, res) => {
|
||||
export const updateProfile = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const { fullName, phone } = req.body;
|
||||
|
||||
const user = await User.findByPk(req.user.id);
|
||||
@ -212,8 +224,12 @@ exports.updateProfile = async (req, res) => {
|
||||
};
|
||||
|
||||
// Change password
|
||||
exports.changePassword = async (req, res) => {
|
||||
export const changePassword = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ success: false, message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
@ -233,7 +249,7 @@ exports.changePassword = async (req, res) => {
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValid = await bcrypt.compare(currentPassword, user.password);
|
||||
const isValid = await bcrypt.compare(currentPassword, user.password!);
|
||||
if (!isValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
@ -250,7 +266,7 @@ exports.changePassword = async (req, res) => {
|
||||
// Log audit
|
||||
await AuditLog.create({
|
||||
userId: req.user.id,
|
||||
action: 'password_changed',
|
||||
action: 'password_changed' as any,
|
||||
entityType: 'user',
|
||||
entityId: req.user.id
|
||||
});
|
||||
@ -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