429 lines
11 KiB
TypeScript
429 lines
11 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import { Holiday, HolidayType } from '@models/Holiday';
|
|
import { holidayService } from '@services/holiday.service';
|
|
import { sequelize } from '@config/database';
|
|
import { QueryTypes } from 'sequelize';
|
|
import logger from '@utils/logger';
|
|
import { initializeHolidaysCache, clearWorkingHoursCache } from '@utils/tatTimeUtils';
|
|
import { clearConfigCache } from '@services/configReader.service';
|
|
|
|
/**
|
|
* Get all holidays (with optional year filter)
|
|
*/
|
|
export const getAllHolidays = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { year } = req.query;
|
|
const yearNum = year ? parseInt(year as string) : undefined;
|
|
|
|
const holidays = await holidayService.getAllActiveHolidays(yearNum);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: holidays,
|
|
count: holidays.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching holidays:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch holidays'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get holiday calendar for a specific year
|
|
*/
|
|
export const getHolidayCalendar = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { year } = req.params;
|
|
const yearNum = parseInt(year);
|
|
|
|
if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid year'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const calendar = await holidayService.getHolidayCalendar(yearNum);
|
|
|
|
res.json({
|
|
success: true,
|
|
year: yearNum,
|
|
holidays: calendar,
|
|
count: calendar.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching holiday calendar:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch holiday calendar'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a new holiday
|
|
*/
|
|
export const createHoliday = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const {
|
|
holidayDate,
|
|
holidayName,
|
|
description,
|
|
holidayType,
|
|
isRecurring,
|
|
recurrenceRule,
|
|
appliesToDepartments,
|
|
appliesToLocations
|
|
} = req.body;
|
|
|
|
// Validate required fields
|
|
if (!holidayDate || !holidayName) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Holiday date and name are required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const holiday = await holidayService.createHoliday({
|
|
holidayDate,
|
|
holidayName,
|
|
description,
|
|
holidayType: holidayType || HolidayType.ORGANIZATIONAL,
|
|
isRecurring: isRecurring || false,
|
|
recurrenceRule,
|
|
appliesToDepartments,
|
|
appliesToLocations,
|
|
createdBy: userId
|
|
});
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Holiday created successfully',
|
|
data: holiday
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error creating holiday:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to create holiday'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update a holiday
|
|
*/
|
|
export const updateHoliday = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { holidayId } = req.params;
|
|
const updates = req.body;
|
|
|
|
const holiday = await holidayService.updateHoliday(holidayId, updates, userId);
|
|
|
|
if (!holiday) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Holiday not found'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Holiday updated successfully',
|
|
data: holiday
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error updating holiday:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to update holiday'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delete (deactivate) a holiday
|
|
*/
|
|
export const deleteHoliday = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { holidayId } = req.params;
|
|
|
|
await holidayService.deleteHoliday(holidayId);
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Holiday deleted successfully'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error deleting holiday:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to delete holiday'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Bulk import holidays from CSV/JSON
|
|
*/
|
|
export const bulkImportHolidays = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { holidays } = req.body;
|
|
|
|
if (!Array.isArray(holidays) || holidays.length === 0) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Holidays array is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const result = await holidayService.bulkImportHolidays(holidays, userId);
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Imported ${result.success} holidays, ${result.failed} failed`,
|
|
data: result
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error bulk importing holidays:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to import holidays'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get all admin configurations
|
|
*/
|
|
export const getAllConfigurations = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { category } = req.query;
|
|
|
|
let whereClause = '';
|
|
if (category) {
|
|
whereClause = `WHERE config_category = '${category}'`;
|
|
}
|
|
|
|
const rawConfigurations = await sequelize.query(`
|
|
SELECT
|
|
config_id,
|
|
config_key,
|
|
config_category,
|
|
config_value,
|
|
value_type,
|
|
display_name,
|
|
description,
|
|
default_value,
|
|
is_editable,
|
|
is_sensitive,
|
|
validation_rules,
|
|
ui_component,
|
|
options,
|
|
sort_order,
|
|
requires_restart,
|
|
last_modified_at,
|
|
last_modified_by
|
|
FROM admin_configurations
|
|
${whereClause}
|
|
ORDER BY config_category, sort_order
|
|
`, { type: QueryTypes.SELECT });
|
|
|
|
// Map snake_case to camelCase for frontend
|
|
const configurations = (rawConfigurations as any[]).map((config: any) => ({
|
|
configId: config.config_id,
|
|
configKey: config.config_key,
|
|
configCategory: config.config_category,
|
|
configValue: config.config_value,
|
|
valueType: config.value_type,
|
|
displayName: config.display_name,
|
|
description: config.description,
|
|
defaultValue: config.default_value,
|
|
isEditable: config.is_editable,
|
|
isSensitive: config.is_sensitive || false,
|
|
validationRules: config.validation_rules,
|
|
uiComponent: config.ui_component,
|
|
options: config.options,
|
|
sortOrder: config.sort_order,
|
|
requiresRestart: config.requires_restart || false,
|
|
lastModifiedAt: config.last_modified_at,
|
|
lastModifiedBy: config.last_modified_by
|
|
}));
|
|
|
|
res.json({
|
|
success: true,
|
|
data: configurations,
|
|
count: configurations.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching configurations:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch configurations'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update a configuration
|
|
*/
|
|
export const updateConfiguration = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { configKey } = req.params;
|
|
const { configValue } = req.body;
|
|
|
|
if (configValue === undefined) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Config value is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Update configuration
|
|
const result = await sequelize.query(`
|
|
UPDATE admin_configurations
|
|
SET
|
|
config_value = :configValue,
|
|
last_modified_by = :userId,
|
|
last_modified_at = NOW(),
|
|
updated_at = NOW()
|
|
WHERE config_key = :configKey
|
|
AND is_editable = true
|
|
RETURNING *
|
|
`, {
|
|
replacements: { configValue, userId, configKey },
|
|
type: QueryTypes.UPDATE
|
|
});
|
|
|
|
if (!result || (result[1] as any) === 0) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Configuration not found or not editable'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Clear config cache so new values are used immediately
|
|
clearConfigCache();
|
|
|
|
// If working hours config was updated, also clear working hours cache
|
|
const workingHoursKeys = ['WORK_START_HOUR', 'WORK_END_HOUR', 'WORK_START_DAY', 'WORK_END_DAY'];
|
|
if (workingHoursKeys.includes(configKey)) {
|
|
await clearWorkingHoursCache();
|
|
logger.info(`[Admin] Working hours configuration '${configKey}' updated - cache cleared and reloaded`);
|
|
} else {
|
|
logger.info(`[Admin] Configuration '${configKey}' updated and cache cleared`);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Configuration updated successfully'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error updating configuration:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to update configuration'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset configuration to default value
|
|
*/
|
|
export const resetConfiguration = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { configKey } = req.params;
|
|
|
|
await sequelize.query(`
|
|
UPDATE admin_configurations
|
|
SET config_value = default_value,
|
|
updated_at = NOW()
|
|
WHERE config_key = :configKey
|
|
`, {
|
|
replacements: { configKey },
|
|
type: QueryTypes.UPDATE
|
|
});
|
|
|
|
// Clear config cache so reset values are used immediately
|
|
clearConfigCache();
|
|
|
|
// If working hours config was reset, also clear working hours cache
|
|
const workingHoursKeys = ['WORK_START_HOUR', 'WORK_END_HOUR', 'WORK_START_DAY', 'WORK_END_DAY'];
|
|
if (workingHoursKeys.includes(configKey)) {
|
|
await clearWorkingHoursCache();
|
|
logger.info(`[Admin] Working hours configuration '${configKey}' reset to default - cache cleared and reloaded`);
|
|
} else {
|
|
logger.info(`[Admin] Configuration '${configKey}' reset to default and cache cleared`);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Configuration reset to default'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error resetting configuration:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to reset configuration'
|
|
});
|
|
}
|
|
};
|
|
|