222 lines
5.6 KiB
TypeScript
222 lines
5.6 KiB
TypeScript
import { Holiday, HolidayType } from '@models/Holiday';
|
|
import { Op } from 'sequelize';
|
|
import logger from '@utils/logger';
|
|
import dayjs from 'dayjs';
|
|
|
|
export class HolidayService {
|
|
/**
|
|
* Get all holidays within a date range
|
|
*/
|
|
async getHolidaysInRange(startDate: Date | string, endDate: Date | string): Promise<string[]> {
|
|
try {
|
|
const holidays = await Holiday.findAll({
|
|
where: {
|
|
holidayDate: {
|
|
[Op.between]: [dayjs(startDate).format('YYYY-MM-DD'), dayjs(endDate).format('YYYY-MM-DD')]
|
|
},
|
|
isActive: true
|
|
},
|
|
attributes: ['holidayDate'],
|
|
raw: true
|
|
});
|
|
|
|
return holidays.map((h: any) => h.holidayDate || h.holiday_date);
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error fetching holidays:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a specific date is a holiday
|
|
*/
|
|
async isHoliday(date: Date | string): Promise<boolean> {
|
|
try {
|
|
const dateStr = dayjs(date).format('YYYY-MM-DD');
|
|
const holiday = await Holiday.findOne({
|
|
where: {
|
|
holidayDate: dateStr,
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
return !!holiday;
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error checking holiday:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a date is a working day (not weekend or holiday)
|
|
*/
|
|
async isWorkingDay(date: Date | string): Promise<boolean> {
|
|
const day = dayjs(date);
|
|
const dayOfWeek = day.day(); // 0 = Sunday, 6 = Saturday
|
|
|
|
// Check if weekend
|
|
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
return false;
|
|
}
|
|
|
|
// Check if holiday
|
|
const isHol = await this.isHoliday(date);
|
|
return !isHol;
|
|
}
|
|
|
|
/**
|
|
* Add a new holiday
|
|
*/
|
|
async createHoliday(holidayData: {
|
|
holidayDate: string;
|
|
holidayName: string;
|
|
description?: string;
|
|
holidayType?: HolidayType;
|
|
isRecurring?: boolean;
|
|
recurrenceRule?: string;
|
|
appliesToDepartments?: string[];
|
|
appliesToLocations?: string[];
|
|
createdBy: string;
|
|
}): Promise<Holiday> {
|
|
try {
|
|
const holiday = await Holiday.create({
|
|
...holidayData,
|
|
isActive: true
|
|
} as any);
|
|
|
|
logger.info(`[Holiday Service] Holiday created: ${holidayData.holidayName} on ${holidayData.holidayDate}`);
|
|
return holiday;
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error creating holiday:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update a holiday
|
|
*/
|
|
async updateHoliday(holidayId: string, updates: any, updatedBy: string): Promise<Holiday | null> {
|
|
try {
|
|
const holiday = await Holiday.findByPk(holidayId);
|
|
if (!holiday) {
|
|
throw new Error('Holiday not found');
|
|
}
|
|
|
|
await holiday.update({
|
|
...updates,
|
|
updatedBy,
|
|
updatedAt: new Date()
|
|
});
|
|
|
|
logger.info(`[Holiday Service] Holiday updated: ${holidayId}`);
|
|
return holiday;
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error updating holiday:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete (deactivate) a holiday
|
|
*/
|
|
async deleteHoliday(holidayId: string): Promise<boolean> {
|
|
try {
|
|
await Holiday.update(
|
|
{ isActive: false },
|
|
{ where: { holidayId } }
|
|
);
|
|
|
|
logger.info(`[Holiday Service] Holiday deactivated: ${holidayId}`);
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error deleting holiday:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all active holidays
|
|
*/
|
|
async getAllActiveHolidays(year?: number): Promise<Holiday[]> {
|
|
try {
|
|
const whereClause: any = { isActive: true };
|
|
|
|
if (year) {
|
|
const startDate = `${year}-01-01`;
|
|
const endDate = `${year}-12-31`;
|
|
whereClause.holidayDate = {
|
|
[Op.between]: [startDate, endDate]
|
|
};
|
|
}
|
|
|
|
const holidays = await Holiday.findAll({
|
|
where: whereClause,
|
|
order: [['holidayDate', 'ASC']]
|
|
});
|
|
|
|
return holidays;
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error fetching holidays:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get holidays by year for calendar view
|
|
*/
|
|
async getHolidayCalendar(year: number): Promise<any[]> {
|
|
try {
|
|
const startDate = `${year}-01-01`;
|
|
const endDate = `${year}-12-31`;
|
|
|
|
const holidays = await Holiday.findAll({
|
|
where: {
|
|
holidayDate: {
|
|
[Op.between]: [startDate, endDate]
|
|
},
|
|
isActive: true
|
|
},
|
|
order: [['holidayDate', 'ASC']]
|
|
});
|
|
|
|
return holidays.map((h: any) => ({
|
|
date: h.holidayDate || h.holiday_date,
|
|
name: h.holidayName || h.holiday_name,
|
|
description: h.description,
|
|
type: h.holidayType || h.holiday_type,
|
|
isRecurring: h.isRecurring || h.is_recurring
|
|
}));
|
|
} catch (error) {
|
|
logger.error('[Holiday Service] Error fetching holiday calendar:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import multiple holidays (bulk upload)
|
|
*/
|
|
async bulkImportHolidays(holidays: any[], createdBy: string): Promise<{ success: number; failed: number }> {
|
|
let success = 0;
|
|
let failed = 0;
|
|
|
|
for (const holiday of holidays) {
|
|
try {
|
|
await this.createHoliday({
|
|
...holiday,
|
|
createdBy
|
|
});
|
|
success++;
|
|
} catch (error) {
|
|
failed++;
|
|
logger.error(`[Holiday Service] Failed to import holiday: ${holiday.holidayName}`, error);
|
|
}
|
|
}
|
|
|
|
logger.info(`[Holiday Service] Bulk import complete: ${success} success, ${failed} failed`);
|
|
return { success, failed };
|
|
}
|
|
}
|
|
|
|
export const holidayService = new HolidayService();
|
|
|