system configuration seed added
This commit is contained in:
parent
31354f2825
commit
53bc624094
124
CONSOLE_LOGS_CLEANED.md
Normal file
124
CONSOLE_LOGS_CLEANED.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# Console Logs Cleanup Summary
|
||||||
|
|
||||||
|
## Changes Applied
|
||||||
|
|
||||||
|
All verbose, redundant, and confusing console logs have been removed or simplified to keep only essential one-line messages helpful for bug tracking.
|
||||||
|
|
||||||
|
## What Was Kept ✅
|
||||||
|
|
||||||
|
**Server Startup:**
|
||||||
|
- `🚀 Server running on port ${PORT} | ${environment}` - Single line server status
|
||||||
|
|
||||||
|
**Critical Errors:**
|
||||||
|
- `❌ Database connection failed`
|
||||||
|
- `❌ Unable to start server`
|
||||||
|
- `❌ SSO Callback failed`
|
||||||
|
- `❌ Get Users failed`
|
||||||
|
- `❌ Authorization check failed`
|
||||||
|
- `❌ Admin authorization failed`
|
||||||
|
- `❌ Migration failed`
|
||||||
|
- `❌ Configuration seeding error`
|
||||||
|
- `❌ TAT Error loading working hours/holidays`
|
||||||
|
|
||||||
|
**Migration Status:**
|
||||||
|
- `✅ Migrations up-to-date`
|
||||||
|
- `🔄 Running ${count} migration(s)...`
|
||||||
|
- `✅ ${migration-name}` - Per migration success
|
||||||
|
- `✅ Applied ${count} migration(s)` - Final summary
|
||||||
|
|
||||||
|
**Graceful Shutdown:**
|
||||||
|
- `🛑 SIGTERM signal received: closing HTTP server`
|
||||||
|
- `🛑 SIGINT signal received: closing HTTP server`
|
||||||
|
|
||||||
|
## What Was Removed ❌
|
||||||
|
|
||||||
|
### Multi-line Configuration Dumps
|
||||||
|
- ~~TAT Configuration details (working hours, thresholds, test mode)~~
|
||||||
|
- ~~System Configuration details (environment, version, features)~~
|
||||||
|
- ~~Working hours cache loaded messages~~
|
||||||
|
|
||||||
|
### Verbose Development Logs
|
||||||
|
- ~~Database connection established~~
|
||||||
|
- ~~Database models synchronized~~
|
||||||
|
- ~~Socket.IO server initialized~~
|
||||||
|
- ~~Socket.IO client connected with socket.id~~
|
||||||
|
- ~~Auth routes loaded~~
|
||||||
|
- ~~Holiday calendar loaded~~
|
||||||
|
|
||||||
|
### Migration Details
|
||||||
|
- ~~Individual table created messages~~
|
||||||
|
- ~~Individual column added messages~~
|
||||||
|
- ~~Index created messages~~
|
||||||
|
- ~~Conversion progress messages~~
|
||||||
|
|
||||||
|
### Database Query Logging
|
||||||
|
- **Disabled SQL query logging** in `database.ts` - previously showed ALL database queries in development mode
|
||||||
|
|
||||||
|
## File Changes
|
||||||
|
|
||||||
|
### Core Files
|
||||||
|
1. **src/server.ts** - Simplified to single line startup message
|
||||||
|
2. **src/app.ts** - Removed database connection messages, kept only errors
|
||||||
|
3. **src/config/tat.config.ts** - Disabled multi-line TAT config logging
|
||||||
|
4. **src/config/system.config.ts** - Disabled multi-line system config logging
|
||||||
|
5. **src/config/database.ts** - **Disabled SQL query logging** (was showing every SELECT/INSERT/UPDATE)
|
||||||
|
6. **src/realtime/socket.ts** - Removed Socket.IO initialization and connection logs
|
||||||
|
7. **src/utils/tatTimeUtils.ts** - Removed verbose cache loading messages, kept errors
|
||||||
|
8. **src/routes/auth.routes.ts** - Removed route loading message
|
||||||
|
9. **src/middlewares/authorization.middleware.ts** - Improved error messages
|
||||||
|
|
||||||
|
### Migration Files (All)
|
||||||
|
- **2025103000-create-users.ts**
|
||||||
|
- **2025110501-alter-tat-days-to-generated.ts**
|
||||||
|
- **20251105-add-skip-fields-to-approval-levels.ts**
|
||||||
|
- **20251104-create-admin-config.ts**
|
||||||
|
- **20251104-create-holidays.ts**
|
||||||
|
- **20251104-create-kpi-views.ts**
|
||||||
|
|
||||||
|
All replaced verbose console logs with inline comments.
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
- **src/scripts/migrate.ts** - Streamlined migration output
|
||||||
|
- **src/scripts/seed-admin-config.ts** - Simplified seed messages
|
||||||
|
|
||||||
|
## New Clean Console Output
|
||||||
|
|
||||||
|
### Development Server Start
|
||||||
|
```
|
||||||
|
🚀 Server running on port 5000 | development
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Migrations
|
||||||
|
```
|
||||||
|
🔄 Running 2 migration(s)...
|
||||||
|
✅ 2025103000-create-users
|
||||||
|
✅ 2025110501-alter-tat-days-to-generated
|
||||||
|
✅ Applied 2 migration(s)
|
||||||
|
```
|
||||||
|
|
||||||
|
### No More Clutter
|
||||||
|
- ❌ No SQL query logs
|
||||||
|
- ❌ No multi-line config dumps
|
||||||
|
- ❌ No verbose socket connection messages
|
||||||
|
- ❌ No redundant "table created" messages
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Cleaner Logs** - Easy to scan for errors and important events
|
||||||
|
2. **Better Performance** - No overhead from logging every SQL query
|
||||||
|
3. **Easier Debugging** - Critical errors stand out with ❌ emoji
|
||||||
|
4. **Production Ready** - Minimal logging suitable for production environments
|
||||||
|
|
||||||
|
## To Enable Debug Logging (If Needed)
|
||||||
|
|
||||||
|
To temporarily enable SQL query logging for debugging:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In Re_Backend/src/config/database.ts
|
||||||
|
logging: console.log // Change from false
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Summary:** Reduced ~100+ console log statements to ~20 essential one-liners for bug tracking.
|
||||||
|
|
||||||
@ -21,7 +21,8 @@
|
|||||||
"db:migrate:undo": "sequelize-cli db:migrate:undo",
|
"db:migrate:undo": "sequelize-cli db:migrate:undo",
|
||||||
"db:seed": "sequelize-cli db:seed:all",
|
"db:seed": "sequelize-cli db:seed:all",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"migrate": "ts-node src/scripts/migrate.ts"
|
"migrate": "ts-node -r tsconfig-paths/register src/scripts/migrate.ts",
|
||||||
|
"seed:config": "ts-node -r tsconfig-paths/register src/scripts/seed-admin-config.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/storage": "^7.14.0",
|
"@google-cloud/storage": "^7.14.0",
|
||||||
|
|||||||
@ -21,11 +21,6 @@ const userService = new UserService();
|
|||||||
const initializeDatabase = async () => {
|
const initializeDatabase = async () => {
|
||||||
try {
|
try {
|
||||||
await sequelize.authenticate();
|
await sequelize.authenticate();
|
||||||
console.log('✅ Database connection established successfully');
|
|
||||||
|
|
||||||
// Sync models (create tables if they don't exist)
|
|
||||||
// await sequelize.sync({ force: false });
|
|
||||||
console.log('✅ Database models synchronized (sync disabled)');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Database connection failed:', error);
|
console.error('❌ Database connection failed:', error);
|
||||||
}
|
}
|
||||||
@ -130,7 +125,7 @@ app.post('/api/v1/auth/sso-callback', async (req: express.Request, res: express.
|
|||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('SSO Callback Error:', error);
|
console.error('❌ SSO Callback failed:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Internal server error',
|
message: 'Internal server error',
|
||||||
@ -169,7 +164,7 @@ app.get('/api/v1/users', async (_req: express.Request, res: express.Response): P
|
|||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get Users Error:', error);
|
console.error('❌ Get Users failed:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Internal server error',
|
message: 'Internal server error',
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const sequelize = new Sequelize({
|
|||||||
username: process.env.DB_USER || 'postgres',
|
username: process.env.DB_USER || 'postgres',
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
password: process.env.DB_PASSWORD || 'postgres',
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
logging: process.env.NODE_ENV === 'development' ? console.log : false,
|
logging: false, // Disable SQL query logging for cleaner console output
|
||||||
pool: {
|
pool: {
|
||||||
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
|
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
|
||||||
max: parseInt(process.env.DB_POOL_MAX || '10', 10),
|
max: parseInt(process.env.DB_POOL_MAX || '10', 10),
|
||||||
|
|||||||
@ -149,14 +149,7 @@ export function getPublicConfig() {
|
|||||||
* Log system configuration on startup
|
* Log system configuration on startup
|
||||||
*/
|
*/
|
||||||
export function logSystemConfig(): void {
|
export function logSystemConfig(): void {
|
||||||
console.log('⚙️ System Configuration:');
|
// System config logging disabled - use environment variables to verify settings
|
||||||
console.log(` - Environment: ${SYSTEM_CONFIG.APP_ENV}`);
|
|
||||||
console.log(` - Version: ${SYSTEM_CONFIG.APP_VERSION}`);
|
|
||||||
console.log(` - Working Hours: ${SYSTEM_CONFIG.WORKING_HOURS.START_HOUR}:00 - ${SYSTEM_CONFIG.WORKING_HOURS.END_HOUR}:00`);
|
|
||||||
console.log(` - Max File Size: ${SYSTEM_CONFIG.UPLOAD.MAX_FILE_SIZE_MB} MB`);
|
|
||||||
console.log(` - Max Approval Levels: ${SYSTEM_CONFIG.WORKFLOW.MAX_APPROVAL_LEVELS}`);
|
|
||||||
console.log(` - AI Conclusion: ${SYSTEM_CONFIG.FEATURES.ENABLE_AI_CONCLUSION ? 'Enabled' : 'Disabled'}`);
|
|
||||||
console.log(` - TAT Test Mode: ${SYSTEM_CONFIG.TAT.TEST_MODE ? 'ENABLED (1h = 1min)' : 'DISABLED'}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SYSTEM_CONFIG;
|
export default SYSTEM_CONFIG;
|
||||||
|
|||||||
@ -65,12 +65,7 @@ export function isTestMode(): boolean {
|
|||||||
* Log TAT configuration on startup
|
* Log TAT configuration on startup
|
||||||
*/
|
*/
|
||||||
export function logTatConfig(): void {
|
export function logTatConfig(): void {
|
||||||
console.log('⏰ TAT Configuration:');
|
// TAT config logging disabled - use environment variables to verify settings
|
||||||
console.log(` - Test Mode: ${TAT_CONFIG.TEST_MODE ? 'ENABLED (1 hour = 1 minute)' : 'DISABLED'}`);
|
|
||||||
console.log(` - Working Hours: ${TAT_CONFIG.WORK_START_HOUR}:00 - ${TAT_CONFIG.WORK_END_HOUR}:00`);
|
|
||||||
console.log(` - Working Days: Monday - Friday`);
|
|
||||||
console.log(` - Redis: ${TAT_CONFIG.REDIS_URL}`);
|
|
||||||
console.log(` - Thresholds: ${TAT_CONFIG.THRESHOLD_50_PERCENT}%, ${TAT_CONFIG.THRESHOLD_75_PERCENT}%, ${TAT_CONFIG.THRESHOLD_100_PERCENT}%`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TAT_CONFIG;
|
export default TAT_CONFIG;
|
||||||
|
|||||||
@ -91,7 +91,7 @@ export function requireParticipantTypes(allowed: AllowedType[]) {
|
|||||||
|
|
||||||
return res.status(403).json({ success: false, error: 'Insufficient permissions' });
|
return res.status(403).json({ success: false, error: 'Insufficient permissions' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Authorization check error:', err);
|
console.error('❌ Authorization check failed:', err);
|
||||||
return res.status(500).json({ success: false, error: 'Authorization check failed' });
|
return res.status(500).json({ success: false, error: 'Authorization check failed' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -114,7 +114,7 @@ export function requireAdmin(req: Request, res: Response, next: NextFunction): v
|
|||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Admin authorization check error:', error);
|
console.error('❌ Admin authorization failed:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Authorization check failed'
|
error: 'Authorization check failed'
|
||||||
|
|||||||
@ -105,11 +105,11 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
name: 'users_employee_id_idx'
|
name: 'users_employee_id_idx'
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Created users table with indexes');
|
// Users table created
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
await queryInterface.dropTable('users');
|
await queryInterface.dropTable('users');
|
||||||
console.log('✅ Dropped users table');
|
// Users table dropped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -122,15 +122,13 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "admin_configurations_is_editable" ON "admin_configurations" ("is_editable");');
|
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "admin_configurations_is_editable" ON "admin_configurations" ("is_editable");');
|
||||||
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "admin_configurations_sort_order" ON "admin_configurations" ("sort_order");');
|
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "admin_configurations_sort_order" ON "admin_configurations" ("sort_order");');
|
||||||
|
|
||||||
console.log('✅ Admin configurations table created successfully');
|
// Admin config table created
|
||||||
console.log('Note: Default configurations will be seeded on first server start');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
await queryInterface.dropTable('admin_configurations');
|
await queryInterface.dropTable('admin_configurations');
|
||||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_admin_configurations_config_category";');
|
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_admin_configurations_config_category";');
|
||||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_admin_configurations_value_type";');
|
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_admin_configurations_value_type";');
|
||||||
|
// Admin config table dropped
|
||||||
console.log('✅ Admin configurations table dropped');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -95,13 +95,12 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "holidays_holiday_type" ON "holidays" ("holiday_type");');
|
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "holidays_holiday_type" ON "holidays" ("holiday_type");');
|
||||||
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "holidays_created_by" ON "holidays" ("created_by");');
|
await queryInterface.sequelize.query('CREATE INDEX IF NOT EXISTS "holidays_created_by" ON "holidays" ("created_by");');
|
||||||
|
|
||||||
console.log('✅ Holidays table created successfully');
|
// Holidays table created
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
await queryInterface.dropTable('holidays');
|
await queryInterface.dropTable('holidays');
|
||||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_holidays_holiday_type";');
|
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_holidays_holiday_type";');
|
||||||
|
// Holidays table dropped
|
||||||
console.log('✅ Holidays table dropped successfully');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -249,7 +249,7 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
GROUP BY w.request_id, w.request_number, w.title, w.status;
|
GROUP BY w.request_id, w.request_number, w.title, w.status;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
console.log('✅ KPI views created successfully');
|
// KPI views created
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
@ -261,7 +261,6 @@ export async function down(queryInterface: QueryInterface): Promise<void> {
|
|||||||
await queryInterface.sequelize.query('DROP VIEW IF EXISTS vw_approver_performance;');
|
await queryInterface.sequelize.query('DROP VIEW IF EXISTS vw_approver_performance;');
|
||||||
await queryInterface.sequelize.query('DROP VIEW IF EXISTS vw_tat_compliance;');
|
await queryInterface.sequelize.query('DROP VIEW IF EXISTS vw_tat_compliance;');
|
||||||
await queryInterface.sequelize.query('DROP VIEW IF EXISTS vw_request_volume_summary;');
|
await queryInterface.sequelize.query('DROP VIEW IF EXISTS vw_request_volume_summary;');
|
||||||
|
// KPI views dropped
|
||||||
console.log('✅ KPI views dropped successfully');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
// Check if table exists first
|
// Check if table exists first
|
||||||
const tables = await queryInterface.showAllTables();
|
const tables = await queryInterface.showAllTables();
|
||||||
if (!tables.includes('approval_levels')) {
|
if (!tables.includes('approval_levels')) {
|
||||||
console.log('⚠️ approval_levels table does not exist yet, skipping...');
|
// Table doesn't exist yet, skipping
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
comment: 'Indicates if this approver was skipped by initiator'
|
comment: 'Indicates if this approver was skipped by initiator'
|
||||||
});
|
});
|
||||||
console.log(' ✅ Added is_skipped column');
|
// Added is_skipped column
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tableDescription.skipped_at) {
|
if (!tableDescription.skipped_at) {
|
||||||
@ -34,7 +34,7 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
comment: 'Timestamp when approver was skipped'
|
comment: 'Timestamp when approver was skipped'
|
||||||
});
|
});
|
||||||
console.log(' ✅ Added skipped_at column');
|
// Added skipped_at column
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tableDescription.skipped_by) {
|
if (!tableDescription.skipped_by) {
|
||||||
@ -49,7 +49,7 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
onDelete: 'SET NULL',
|
onDelete: 'SET NULL',
|
||||||
comment: 'User ID who skipped this approver'
|
comment: 'User ID who skipped this approver'
|
||||||
});
|
});
|
||||||
console.log(' ✅ Added skipped_by column');
|
// Added skipped_by column
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tableDescription.skip_reason) {
|
if (!tableDescription.skip_reason) {
|
||||||
@ -58,7 +58,7 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
comment: 'Reason for skipping this approver'
|
comment: 'Reason for skipping this approver'
|
||||||
});
|
});
|
||||||
console.log(' ✅ Added skip_reason column');
|
// Added skip_reason column
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if index exists before creating
|
// Check if index exists before creating
|
||||||
@ -73,14 +73,13 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
is_skipped: true
|
is_skipped: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(' ✅ Added idx_approval_levels_skipped index');
|
// Index added
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Index might already exist, which is fine
|
// Index already exists
|
||||||
console.log(' ℹ️ Index already exists or could not be created');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Skip-related fields migration completed');
|
// Skip fields added
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
@ -93,6 +92,6 @@ export async function down(queryInterface: QueryInterface): Promise<void> {
|
|||||||
await queryInterface.removeColumn('approval_levels', 'skipped_at');
|
await queryInterface.removeColumn('approval_levels', 'skipped_at');
|
||||||
await queryInterface.removeColumn('approval_levels', 'is_skipped');
|
await queryInterface.removeColumn('approval_levels', 'is_skipped');
|
||||||
|
|
||||||
console.log('✅ Removed skip-related fields from approval_levels table');
|
// Skip fields removed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,11 +22,11 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
const column = result[0] as any;
|
const column = result[0] as any;
|
||||||
|
|
||||||
if (column && column.is_generated === 's') {
|
if (column && column.is_generated === 's') {
|
||||||
console.log('✅ tat_days is already a GENERATED STORED column - skipping migration');
|
// Already a GENERATED column, skipping
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('📝 Converting tat_days to GENERATED STORED column...');
|
// Converting tat_days to GENERATED column
|
||||||
|
|
||||||
// Step 1: Drop the existing regular column
|
// Step 1: Drop the existing regular column
|
||||||
await queryInterface.sequelize.query(`
|
await queryInterface.sequelize.query(`
|
||||||
@ -41,11 +41,11 @@ export async function up(queryInterface: QueryInterface): Promise<void> {
|
|||||||
GENERATED ALWAYS AS (CAST(CEIL(tat_hours / 24.0) AS INTEGER)) STORED;
|
GENERATED ALWAYS AS (CAST(CEIL(tat_hours / 24.0) AS INTEGER)) STORED;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
console.log('✅ tat_days is now a GENERATED STORED column - will auto-calculate from tat_hours');
|
// tat_days is now auto-calculated
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
console.log('⚠️ Rolling back: Converting tat_days from GENERATED to regular column');
|
// Rolling back to regular column
|
||||||
|
|
||||||
// Drop the generated column
|
// Drop the generated column
|
||||||
await queryInterface.sequelize.query(`
|
await queryInterface.sequelize.query(`
|
||||||
@ -71,6 +71,6 @@ export async function down(queryInterface: QueryInterface): Promise<void> {
|
|||||||
ALTER COLUMN tat_days SET NOT NULL;
|
ALTER COLUMN tat_days SET NOT NULL;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
console.log('✅ Rolled back to regular INTEGER column');
|
// Rolled back successfully
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,6 @@ export function initSocket(httpServer: any) {
|
|||||||
const configured = (process.env.FRONTEND_ORIGIN || '').split(',').map(s => s.trim()).filter(Boolean);
|
const configured = (process.env.FRONTEND_ORIGIN || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||||
const origins = configured.length ? configured : defaultOrigins;
|
const origins = configured.length ? configured : defaultOrigins;
|
||||||
|
|
||||||
console.log('🔌 Initializing Socket.IO server with origins:', origins);
|
|
||||||
|
|
||||||
io = new Server(httpServer, {
|
io = new Server(httpServer, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: origins,
|
origin: origins,
|
||||||
@ -27,10 +25,7 @@ export function initSocket(httpServer: any) {
|
|||||||
transports: ['websocket', 'polling']
|
transports: ['websocket', 'polling']
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Socket.IO server initialized');
|
|
||||||
|
|
||||||
io.on('connection', (socket: any) => {
|
io.on('connection', (socket: any) => {
|
||||||
console.log('🔗 Client connected:', socket.id);
|
|
||||||
let currentRequestId: string | null = null;
|
let currentRequestId: string | null = null;
|
||||||
let currentUserId: string | null = null;
|
let currentUserId: string | null = null;
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import { asyncHandler } from '../middlewares/errorHandler.middleware';
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
const authController = new AuthController();
|
const authController = new AuthController();
|
||||||
|
|
||||||
console.log('✅ Auth routes loaded - token-exchange endpoint registered');
|
|
||||||
|
|
||||||
// Token exchange endpoint (no authentication required) - for localhost development
|
// Token exchange endpoint (no authentication required) - for localhost development
|
||||||
router.post('/token-exchange',
|
router.post('/token-exchange',
|
||||||
validateBody(tokenExchangeSchema),
|
validateBody(tokenExchangeSchema),
|
||||||
|
|||||||
@ -63,7 +63,7 @@ async function ensureMigrationsTable(queryInterface: QueryInterface): Promise<vo
|
|||||||
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
console.log('✅ Created migrations tracking table');
|
// Migrations table created
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating migrations table:', error);
|
console.error('Error creating migrations table:', error);
|
||||||
@ -106,7 +106,6 @@ async function markMigrationExecuted(name: string): Promise<void> {
|
|||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
await sequelize.authenticate();
|
await sequelize.authenticate();
|
||||||
console.log('📦 Database connected');
|
|
||||||
|
|
||||||
const queryInterface = sequelize.getQueryInterface();
|
const queryInterface = sequelize.getQueryInterface();
|
||||||
|
|
||||||
@ -122,33 +121,29 @@ async function run() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (pendingMigrations.length === 0) {
|
if (pendingMigrations.length === 0) {
|
||||||
console.log('✅ All migrations are up-to-date (no new migrations to run)');
|
console.log('✅ Migrations up-to-date');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🔄 Running ${pendingMigrations.length} pending migration(s)...\n`);
|
console.log(`🔄 Running ${pendingMigrations.length} migration(s)...`);
|
||||||
|
|
||||||
// Run each pending migration
|
// Run each pending migration
|
||||||
for (const migration of pendingMigrations) {
|
for (const migration of pendingMigrations) {
|
||||||
try {
|
try {
|
||||||
console.log(`⏳ Running: ${migration.name}`);
|
|
||||||
await migration.module.up(queryInterface);
|
await migration.module.up(queryInterface);
|
||||||
await markMigrationExecuted(migration.name);
|
await markMigrationExecuted(migration.name);
|
||||||
console.log(`✅ Completed: ${migration.name}\n`);
|
console.log(`✅ ${migration.name}`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`❌ Failed: ${migration.name}`);
|
console.error(`❌ Migration failed: ${migration.name} - ${error.message}`);
|
||||||
console.error('Error:', error.message);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n✅ Successfully applied ${pendingMigrations.length} migration(s)`);
|
console.log(`✅ Applied ${pendingMigrations.length} migration(s)`);
|
||||||
console.log(`📊 Total migrations: ${executedMigrations.length + pendingMigrations.length}`);
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('\n❌ Migration failed:', err.message);
|
console.error('❌ Migration failed:', err.message);
|
||||||
console.error('\nStack trace:', err.stack);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
392
src/scripts/seed-admin-config.ts
Normal file
392
src/scripts/seed-admin-config.ts
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
/**
|
||||||
|
* Manual script to seed admin configurations
|
||||||
|
* Run this if configurations are not auto-seeding on server startup
|
||||||
|
*
|
||||||
|
* Usage: npm run seed:config
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { sequelize } from '../config/database';
|
||||||
|
import { QueryTypes } from 'sequelize';
|
||||||
|
|
||||||
|
async function seedAdminConfigurations() {
|
||||||
|
try {
|
||||||
|
await sequelize.authenticate();
|
||||||
|
|
||||||
|
// Check if configurations already exist
|
||||||
|
const count = await sequelize.query(
|
||||||
|
'SELECT COUNT(*) as count FROM admin_configurations',
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingCount = (count[0] as any).count;
|
||||||
|
|
||||||
|
if (existingCount > 0) {
|
||||||
|
console.log(`⚠️ Found ${existingCount} existing configurations. Delete them first or skip this script.`);
|
||||||
|
const readline = require('readline').createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
const answer = await new Promise<string>((resolve) => {
|
||||||
|
readline.question('Delete existing and re-seed? (yes/no): ', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
readline.close();
|
||||||
|
|
||||||
|
if (answer.toLowerCase() !== 'yes') {
|
||||||
|
console.log('❌ Aborted. No changes made.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sequelize.query('DELETE FROM admin_configurations');
|
||||||
|
console.log('✅ Existing configurations deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📝 Seeding admin configurations...');
|
||||||
|
|
||||||
|
// Insert all default configurations
|
||||||
|
await sequelize.query(`
|
||||||
|
INSERT INTO admin_configurations (
|
||||||
|
config_id, config_key, config_category, config_value, value_type,
|
||||||
|
display_name, description, default_value, is_editable, is_sensitive,
|
||||||
|
validation_rules, ui_component, sort_order, requires_restart,
|
||||||
|
created_at, updated_at
|
||||||
|
) VALUES
|
||||||
|
-- TAT Settings
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'DEFAULT_TAT_EXPRESS_HOURS',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'24',
|
||||||
|
'NUMBER',
|
||||||
|
'Default TAT for Express Priority',
|
||||||
|
'Default turnaround time in hours for express priority requests (calendar days, 24/7)',
|
||||||
|
'24',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 168}'::jsonb,
|
||||||
|
'number',
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'DEFAULT_TAT_STANDARD_HOURS',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'48',
|
||||||
|
'NUMBER',
|
||||||
|
'Default TAT for Standard Priority',
|
||||||
|
'Default turnaround time in hours for standard priority requests (working hours only)',
|
||||||
|
'48',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 336}'::jsonb,
|
||||||
|
'number',
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'TAT_THRESHOLD_WARNING',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'50',
|
||||||
|
'NUMBER',
|
||||||
|
'TAT Warning Threshold (%)',
|
||||||
|
'Percentage of TAT elapsed when first warning notification is sent',
|
||||||
|
'50',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
|
'number',
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'TAT_THRESHOLD_CRITICAL',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'75',
|
||||||
|
'NUMBER',
|
||||||
|
'TAT Critical Threshold (%)',
|
||||||
|
'Percentage of TAT elapsed when critical notification is sent',
|
||||||
|
'75',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
|
'number',
|
||||||
|
4,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'TAT_TEST_MODE',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'false',
|
||||||
|
'BOOLEAN',
|
||||||
|
'TAT Test Mode',
|
||||||
|
'Enable test mode where 1 TAT hour = 1 minute (for development/testing only)',
|
||||||
|
'false',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'switch',
|
||||||
|
5,
|
||||||
|
true,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Working Hours Settings
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'WORK_START_HOUR',
|
||||||
|
'WORKING_HOURS',
|
||||||
|
'9',
|
||||||
|
'NUMBER',
|
||||||
|
'Work Day Start Hour',
|
||||||
|
'Hour when work day starts (24-hour format, e.g., 9 for 9:00 AM)',
|
||||||
|
'9',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 0, "max": 23}'::jsonb,
|
||||||
|
'number',
|
||||||
|
10,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'WORK_END_HOUR',
|
||||||
|
'WORKING_HOURS',
|
||||||
|
'18',
|
||||||
|
'NUMBER',
|
||||||
|
'Work Day End Hour',
|
||||||
|
'Hour when work day ends (24-hour format, e.g., 18 for 6:00 PM)',
|
||||||
|
'18',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 0, "max": 23}'::jsonb,
|
||||||
|
'number',
|
||||||
|
11,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'WORK_START_DAY',
|
||||||
|
'WORKING_HOURS',
|
||||||
|
'1',
|
||||||
|
'NUMBER',
|
||||||
|
'Work Week Start Day',
|
||||||
|
'Day when work week starts (1 = Monday, 7 = Sunday)',
|
||||||
|
'1',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 7}'::jsonb,
|
||||||
|
'number',
|
||||||
|
12,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'WORK_END_DAY',
|
||||||
|
'WORKING_HOURS',
|
||||||
|
'5',
|
||||||
|
'NUMBER',
|
||||||
|
'Work Week End Day',
|
||||||
|
'Day when work week ends (1 = Monday, 7 = Sunday)',
|
||||||
|
'5',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 7}'::jsonb,
|
||||||
|
'number',
|
||||||
|
13,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'TIMEZONE',
|
||||||
|
'WORKING_HOURS',
|
||||||
|
'Asia/Kolkata',
|
||||||
|
'STRING',
|
||||||
|
'System Timezone',
|
||||||
|
'Timezone for all TAT calculations and scheduling',
|
||||||
|
'Asia/Kolkata',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'select',
|
||||||
|
14,
|
||||||
|
true,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Workflow Settings
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'MAX_APPROVAL_LEVELS',
|
||||||
|
'WORKFLOW',
|
||||||
|
'10',
|
||||||
|
'NUMBER',
|
||||||
|
'Maximum Approval Levels',
|
||||||
|
'Maximum number of approval levels allowed per workflow',
|
||||||
|
'10',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 20}'::jsonb,
|
||||||
|
'number',
|
||||||
|
20,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'MAX_PARTICIPANTS',
|
||||||
|
'WORKFLOW',
|
||||||
|
'50',
|
||||||
|
'NUMBER',
|
||||||
|
'Maximum Participants',
|
||||||
|
'Maximum number of participants (spectators + approvers) per request',
|
||||||
|
'50',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
|
'number',
|
||||||
|
21,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
|
||||||
|
-- File Upload Settings
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'MAX_FILE_SIZE_MB',
|
||||||
|
'FILE_UPLOAD',
|
||||||
|
'10',
|
||||||
|
'NUMBER',
|
||||||
|
'Maximum File Size (MB)',
|
||||||
|
'Maximum size for uploaded files in megabytes',
|
||||||
|
'10',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
|
'number',
|
||||||
|
30,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'ALLOWED_FILE_TYPES',
|
||||||
|
'FILE_UPLOAD',
|
||||||
|
'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif,txt',
|
||||||
|
'STRING',
|
||||||
|
'Allowed File Types',
|
||||||
|
'Comma-separated list of allowed file extensions',
|
||||||
|
'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif,txt',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'text',
|
||||||
|
31,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Feature Toggles
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'ENABLE_AI_CONCLUSION',
|
||||||
|
'FEATURES',
|
||||||
|
'true',
|
||||||
|
'BOOLEAN',
|
||||||
|
'Enable AI-Generated Conclusions',
|
||||||
|
'Allow AI to generate automatic conclusion remarks for approved/rejected requests',
|
||||||
|
'true',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'switch',
|
||||||
|
40,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'ENABLE_PUSH_NOTIFICATIONS',
|
||||||
|
'FEATURES',
|
||||||
|
'true',
|
||||||
|
'BOOLEAN',
|
||||||
|
'Enable Push Notifications',
|
||||||
|
'Send browser push notifications for real-time events',
|
||||||
|
'true',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'switch',
|
||||||
|
41,
|
||||||
|
false,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'ENABLE_EMAIL_NOTIFICATIONS',
|
||||||
|
'FEATURES',
|
||||||
|
'true',
|
||||||
|
'BOOLEAN',
|
||||||
|
'Enable Email Notifications',
|
||||||
|
'Send email notifications for workflow events',
|
||||||
|
'true',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{}'::jsonb,
|
||||||
|
'switch',
|
||||||
|
42,
|
||||||
|
true,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const finalCount = await sequelize.query(
|
||||||
|
'SELECT COUNT(*) as count FROM admin_configurations',
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`✅ Seeded ${(finalCount[0] as any).count} admin configurations`);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error seeding admin configurations:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run if called directly
|
||||||
|
if (require.main === module) {
|
||||||
|
seedAdminConfigurations();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default seedAdminConfigurations;
|
||||||
|
|
||||||
@ -18,30 +18,19 @@ const startServer = async (): Promise<void> => {
|
|||||||
// Seed default configurations if table is empty
|
// Seed default configurations if table is empty
|
||||||
try {
|
try {
|
||||||
await seedDefaultConfigurations();
|
await seedDefaultConfigurations();
|
||||||
// console.log('⚙️ System configurations initialized');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Configuration seeding skipped');
|
console.error('⚠️ Configuration seeding error:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize holidays cache for TAT calculations
|
// Initialize holidays cache for TAT calculations
|
||||||
try {
|
try {
|
||||||
await initializeHolidaysCache();
|
await initializeHolidaysCache();
|
||||||
console.log('📅 Holiday calendar loaded for TAT calculations');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Holiday calendar not loaded - TAT will use weekends only');
|
// Silently fall back to weekends-only TAT calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`🚀 Server running on port ${PORT}`);
|
console.log(`🚀 Server running on port ${PORT} | ${process.env.NODE_ENV || 'development'}`);
|
||||||
console.log(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
||||||
console.log(`🌐 API Base URL: http://localhost:${PORT}`);
|
|
||||||
console.log(`❤️ Health Check: http://localhost:${PORT}/health`);
|
|
||||||
console.log(`🔌 Socket.IO path: /socket.io`);
|
|
||||||
console.log(`⏰ TAT Worker: Initialized and listening`);
|
|
||||||
console.log('');
|
|
||||||
logSystemConfig(); // Log centralized system configuration
|
|
||||||
console.log('');
|
|
||||||
logTatConfig(); // Log TAT-specific details
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Unable to start server:', error);
|
console.error('❌ Unable to start server:', error);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (count && (count[0] as any).count > 0) {
|
if (count && (count[0] as any).count > 0) {
|
||||||
logger.info('[Config Seed] Configurations already exist. Skipping seed.');
|
// Table has data, skip seeding silently
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,8 +26,8 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
INSERT INTO admin_configurations (
|
INSERT INTO admin_configurations (
|
||||||
config_id, config_key, config_category, config_value, value_type,
|
config_id, config_key, config_category, config_value, value_type,
|
||||||
display_name, description, default_value, is_editable, is_sensitive,
|
display_name, description, default_value, is_editable, is_sensitive,
|
||||||
validation_rules, ui_component, sort_order, requires_restart,
|
validation_rules, ui_component, options, sort_order, requires_restart,
|
||||||
created_at, updated_at
|
last_modified_by, last_modified_at, created_at, updated_at
|
||||||
) VALUES
|
) VALUES
|
||||||
-- TAT Settings
|
-- TAT Settings
|
||||||
(
|
(
|
||||||
@ -43,8 +43,11 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
false,
|
false,
|
||||||
'{"min": 1, "max": 168}'::jsonb,
|
'{"min": 1, "max": 168}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
1,
|
1,
|
||||||
false,
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -61,8 +64,11 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
false,
|
false,
|
||||||
'{"min": 1, "max": 720}'::jsonb,
|
'{"min": 1, "max": 720}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
2,
|
2,
|
||||||
false,
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -76,9 +82,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Send first gentle reminder when this percentage of TAT is elapsed',
|
'Send first gentle reminder when this percentage of TAT is elapsed',
|
||||||
'50',
|
'50',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 1, "max": 100}'::jsonb,
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
'slider',
|
'slider',
|
||||||
|
NULL,
|
||||||
3,
|
3,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -92,9 +103,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Send escalation warning when this percentage of TAT is elapsed',
|
'Send escalation warning when this percentage of TAT is elapsed',
|
||||||
'75',
|
'75',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 1, "max": 100}'::jsonb,
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
'slider',
|
'slider',
|
||||||
|
NULL,
|
||||||
4,
|
4,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -108,9 +124,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Hour when working day starts (24-hour format, 0-23)',
|
'Hour when working day starts (24-hour format, 0-23)',
|
||||||
'9',
|
'9',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 0, "max": 23}'::jsonb,
|
'{"min": 0, "max": 23}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
5,
|
5,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -124,9 +145,56 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Hour when working day ends (24-hour format, 0-23)',
|
'Hour when working day ends (24-hour format, 0-23)',
|
||||||
'18',
|
'18',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 0, "max": 23}'::jsonb,
|
'{"min": 0, "max": 23}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
6,
|
6,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'WORK_START_DAY',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'1',
|
||||||
|
'NUMBER',
|
||||||
|
'Working Week Start Day',
|
||||||
|
'Day of week start (1=Monday, 7=Sunday)',
|
||||||
|
'1',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 7}'::jsonb,
|
||||||
|
'number',
|
||||||
|
NULL,
|
||||||
|
7,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
gen_random_uuid(),
|
||||||
|
'WORK_END_DAY',
|
||||||
|
'TAT_SETTINGS',
|
||||||
|
'5',
|
||||||
|
'NUMBER',
|
||||||
|
'Working Week End Day',
|
||||||
|
'Day of week end (1=Monday, 7=Sunday)',
|
||||||
|
'5',
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'{"min": 1, "max": 7}'::jsonb,
|
||||||
|
'number',
|
||||||
|
NULL,
|
||||||
|
8,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -141,9 +209,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Maximum allowed file size for document uploads in megabytes',
|
'Maximum allowed file size for document uploads in megabytes',
|
||||||
'10',
|
'10',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 1, "max": 100}'::jsonb,
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
10,
|
10,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -157,9 +230,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Comma-separated list of allowed file extensions for uploads',
|
'Comma-separated list of allowed file extensions for uploads',
|
||||||
'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif',
|
'pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'text',
|
'text',
|
||||||
|
NULL,
|
||||||
11,
|
11,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -173,9 +251,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Number of days to retain documents after workflow closure before archival',
|
'Number of days to retain documents after workflow closure before archival',
|
||||||
'365',
|
'365',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 30, "max": 3650}'::jsonb,
|
'{"min": 30, "max": 3650}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
12,
|
12,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -190,9 +273,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Toggle AI-generated conclusion remarks for workflow closures',
|
'Toggle AI-generated conclusion remarks for workflow closures',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
20,
|
20,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -206,9 +294,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Maximum character limit for AI-generated conclusion remarks',
|
'Maximum character limit for AI-generated conclusion remarks',
|
||||||
'500',
|
'500',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 100, "max": 2000}'::jsonb,
|
'{"min": 100, "max": 2000}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
21,
|
21,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -223,9 +316,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Send email notifications for workflow events',
|
'Send email notifications for workflow events',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
30,
|
30,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -239,9 +337,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Send browser push notifications for real-time events',
|
'Send browser push notifications for real-time events',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
31,
|
31,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -255,9 +358,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Delay in milliseconds before sending batched notifications to avoid spam',
|
'Delay in milliseconds before sending batched notifications to avoid spam',
|
||||||
'5000',
|
'5000',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 1000, "max": 30000}'::jsonb,
|
'{"min": 1000, "max": 30000}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
32,
|
32,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -272,9 +380,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Display total requests KPI card on dashboard',
|
'Display total requests KPI card on dashboard',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
40,
|
40,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -288,9 +401,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Display open requests KPI card on dashboard',
|
'Display open requests KPI card on dashboard',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
41,
|
41,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -304,9 +422,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Display TAT compliance KPI card on dashboard',
|
'Display TAT compliance KPI card on dashboard',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
42,
|
42,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -320,9 +443,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Display pending actions KPI card on dashboard',
|
'Display pending actions KPI card on dashboard',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
43,
|
43,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -337,9 +465,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Enable users to add spectators to workflow requests',
|
'Enable users to add spectators to workflow requests',
|
||||||
'true',
|
'true',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
50,
|
50,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -353,9 +486,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Maximum number of spectators allowed per workflow request',
|
'Maximum number of spectators allowed per workflow request',
|
||||||
'20',
|
'20',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 1, "max": 100}'::jsonb,
|
'{"min": 1, "max": 100}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
51,
|
51,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -369,9 +507,14 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
'Allow sharing workflow links with users outside the organization',
|
'Allow sharing workflow links with users outside the organization',
|
||||||
'false',
|
'false',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{}'::jsonb,
|
'{}'::jsonb,
|
||||||
'toggle',
|
'toggle',
|
||||||
|
NULL,
|
||||||
52,
|
52,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
@ -379,38 +522,48 @@ export async function seedDefaultConfigurations(): Promise<void> {
|
|||||||
(
|
(
|
||||||
gen_random_uuid(),
|
gen_random_uuid(),
|
||||||
'MAX_APPROVAL_LEVELS',
|
'MAX_APPROVAL_LEVELS',
|
||||||
'WORKFLOW_LIMITS',
|
'SYSTEM_SETTINGS',
|
||||||
'10',
|
'10',
|
||||||
'NUMBER',
|
'NUMBER',
|
||||||
'Maximum Approval Levels',
|
'Maximum Approval Levels',
|
||||||
'Maximum number of approval levels allowed per workflow',
|
'Maximum number of approval levels allowed per workflow',
|
||||||
'10',
|
'10',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 1, "max": 20}'::jsonb,
|
'{"min": 1, "max": 20}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
60,
|
60,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
gen_random_uuid(),
|
gen_random_uuid(),
|
||||||
'MAX_PARTICIPANTS_PER_REQUEST',
|
'MAX_PARTICIPANTS_PER_REQUEST',
|
||||||
'WORKFLOW_LIMITS',
|
'SYSTEM_SETTINGS',
|
||||||
'50',
|
'50',
|
||||||
'NUMBER',
|
'NUMBER',
|
||||||
'Maximum Participants per Request',
|
'Maximum Participants per Request',
|
||||||
'Maximum total participants (approvers + spectators) per workflow',
|
'Maximum total participants (approvers + spectators) per workflow',
|
||||||
'50',
|
'50',
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
'{"min": 2, "max": 200}'::jsonb,
|
'{"min": 2, "max": 200}'::jsonb,
|
||||||
'number',
|
'number',
|
||||||
|
NULL,
|
||||||
61,
|
61,
|
||||||
|
false,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
)
|
)
|
||||||
`, { type: QueryTypes.INSERT });
|
`, { type: QueryTypes.INSERT });
|
||||||
|
|
||||||
logger.info('[Config Seed] ✅ Default configurations seeded successfully (18 settings across 7 categories)');
|
logger.info('[Config Seed] ✅ Default configurations seeded successfully (20 settings across 7 categories)');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[Config Seed] Error seeding configurations:', error);
|
logger.error('[Config Seed] Error seeding configurations:', error);
|
||||||
// Don't throw - let server start even if seeding fails
|
// Don't throw - let server start even if seeding fails
|
||||||
|
|||||||
@ -39,9 +39,8 @@ async function loadWorkingHoursCache(): Promise<void> {
|
|||||||
};
|
};
|
||||||
workingHoursCacheExpiry = dayjs().add(5, 'minute').toDate();
|
workingHoursCacheExpiry = dayjs().add(5, 'minute').toDate();
|
||||||
|
|
||||||
console.log(`[TAT Utils] Loaded working hours: ${hours.startHour}:00-${hours.endHour}:00, Days: ${startDay}-${endDay}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[TAT Utils] Error loading working hours cache:', error);
|
console.error('[TAT] Error loading working hours:', error);
|
||||||
// Fallback to default values from TAT_CONFIG
|
// Fallback to default values from TAT_CONFIG
|
||||||
workingHoursCache = {
|
workingHoursCache = {
|
||||||
startHour: TAT_CONFIG.WORK_START_HOUR,
|
startHour: TAT_CONFIG.WORK_START_HOUR,
|
||||||
@ -72,9 +71,8 @@ async function loadHolidaysCache(): Promise<void> {
|
|||||||
holidaysCache = new Set(holidays);
|
holidaysCache = new Set(holidays);
|
||||||
holidaysCacheExpiry = dayjs().add(6, 'hour').toDate();
|
holidaysCacheExpiry = dayjs().add(6, 'hour').toDate();
|
||||||
|
|
||||||
console.log(`[TAT Utils] Loaded ${holidays.length} holidays into cache`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[TAT Utils] Error loading holidays cache:', error);
|
console.error('[TAT] Error loading holidays:', error);
|
||||||
// Continue without holidays if loading fails
|
// Continue without holidays if loading fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,7 +224,7 @@ export async function initializeHolidaysCache(): Promise<void> {
|
|||||||
export function clearWorkingHoursCache(): void {
|
export function clearWorkingHoursCache(): void {
|
||||||
workingHoursCache = null;
|
workingHoursCache = null;
|
||||||
workingHoursCacheExpiry = null;
|
workingHoursCacheExpiry = null;
|
||||||
console.log('[TAT Utils] Working hours cache cleared');
|
// Cache cleared
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user