diff --git a/controllers/scheduleController.js b/controllers/scheduleController.js index 4d43ee118a980c6ddf536eff25592f0cf734a66c..0f45d899cbe545b45cfe2e003a9e7ba707041c1b 100644 --- a/controllers/scheduleController.js +++ b/controllers/scheduleController.js @@ -23,21 +23,17 @@ class scheduleController { try { const userId = req.user.id; const scheduleRequestDTO = new ScheduleRequestDTO(req.body); - const validatedData = scheduleRequestDTO.validate('create'); // 'create' ���� 寃�利� + const validatedData = scheduleRequestDTO.validate('create'); - const { title, is_fixed, events } = validatedData; - - const schedules = await ScheduleService.createSchedules({ + const schedule = await ScheduleService.createSchedules({ userId, - title, - is_fixed, - events + ...validatedData }); return res.status(201).json({ success: true, data: { - schedules + schedule } }); } catch (error) { @@ -49,7 +45,7 @@ class scheduleController { } }); } - } + } /** * �ㅼ�以� �섏젙 @@ -57,31 +53,28 @@ class scheduleController { * Bulk update 吏��� * �붿껌 蹂몃Ц �덉떆: * { - * updates: [ - * { time_idx: 36, title: 'New Title', is_fixed: true }, - * { time_idx: 44, title: 'Another Title' }, - * // ... - * ] + * "originalTitle": "�뚭퀬由ъ쬁 �ㅽ꽣��", // 湲곗〈 �ㅼ�以꾩쓽 �쒕ぉ + * "title": "�뚭퀬由ъ쬁 �ㅽ꽣�� 2.0", // 蹂�寃쏀븷 �쒕ぉ (�쒕ぉ 蹂�寃� �덊븷嫄곕㈃ 湲곗〈 �쒕ぉ�� �l뼱�쇳븿 * -> title濡� �숈씪 �ㅼ�以꾩쓣 李얠븘��) + * "is_fixed": true, + * "time_indices": [36, 37, 38, 40] // 蹂�寃쏀븷 time_indices 諛곗뿴 * } */ async updateSchedules(req, res) { try { const userId = req.user.id; const scheduleRequestDTO = new ScheduleRequestDTO(req.body); - const validatedData = scheduleRequestDTO.validate('bulk_update'); // 'bulk_update' ���� 寃�利� - - const { updates } = validatedData; - - const updatedSchedules = await ScheduleService.updateSchedules(userId, updates); + const validatedData = scheduleRequestDTO.validate('bulk_update'); + const updatedSchedule = await ScheduleService.updateSchedules(userId, validatedData); + return res.status(200).json({ success: true, data: { - schedules: updatedSchedules + schedule: updatedSchedule } }); } catch (error) { - if (error.code === 'SCHEDULE_NOT_FOUND') { + if (error.message === 'Schedule not found') { return res.status(404).json({ success: false, error: { @@ -106,24 +99,21 @@ class scheduleController { * Bulk delete 吏��� * �붿껌 蹂몃Ц �덉떆: * { - * time_idxs: [36, 44, ...] + * "title": "�뚭퀬由ъ쬁 �ㅽ꽣��" * } */ async deleteSchedules(req, res) { try { const userId = req.user.id; const scheduleRequestDTO = new ScheduleRequestDTO(req.body); - const validatedData = scheduleRequestDTO.validate('bulk_delete'); // 'bulk_delete' ���� 寃�利� - - const { time_idxs } = validatedData; - - const result = await ScheduleService.deleteSchedules(userId, time_idxs); + const validatedData = scheduleRequestDTO.validate('bulk_delete'); + const result = await ScheduleService.deleteSchedules(userId, validatedData.title); return res.status(200).json({ success: true, data: { - message: 'Schedules successfully deleted', - deleted_time_idxs: result.deleted_time_idxs + message: 'Schedule successfully deleted', + deletedCount: result.deletedCount } }); } catch (error) { @@ -136,7 +126,6 @@ class scheduleController { }); } } - /** * �대떦 �ъ슜�� �꾩껜 �ㅼ�以� 議고쉶 * GET /api/schedule/all @@ -148,7 +137,9 @@ class scheduleController { return res.status(200).json({ success: true, - data: schedules + data: { + schedules + } }); } catch (error) { return res.status(500).json({ @@ -171,11 +162,15 @@ class scheduleController { const { time_idx } = req.params; const userId = req.user.id; - const schedule = await ScheduleService.getScheduleByTimeIdx(userId, parseInt(time_idx, 10)); + const scheduleRequestDTO = new ScheduleRequestDTO({ time_idx: parseInt(time_idx, 10) }); + const validatedData = scheduleRequestDTO.validate('get_by_time_idx'); + const schedule = await ScheduleService.getScheduleByTimeIdx(userId, validatedData.time_idx); return res.status(200).json({ success: true, - data: schedule + data: { + schedule + } }); } catch (error) { if (error.message === 'Schedule not found') { diff --git a/dtos/ScheduleRequestDTO.js b/dtos/ScheduleRequestDTO.js index 854bd3f124ebe293fe781924683ed0d9b0a4c168..218f9e040e224a1b7b00f144e510a26e640cb4e3 100644 --- a/dtos/ScheduleRequestDTO.js +++ b/dtos/ScheduleRequestDTO.js @@ -13,27 +13,28 @@ class ScheduleRequestDTO { schema = Joi.object({ title: Joi.string().min(1).max(255).required(), is_fixed: Joi.boolean().required(), - events: Joi.array().items( - Joi.object({ - time_idx: Joi.number().integer().min(0).max(671).required(), - }) - ).min(1).required() + time_indices: Joi.array() + .items(Joi.number().integer().min(0).max(671)) + .min(1) + .required() }); } else if (type === 'bulk_update') { schema = Joi.object({ - updates: Joi.array().items( - Joi.object({ - time_idx: Joi.number().integer().min(0).max(671).required(), - title: Joi.string().min(1).max(255).optional(), - is_fixed: Joi.boolean().optional(), - }) - ).min(1).required() + originalTitle: Joi.string().min(1).max(255).required(), + title: Joi.string().min(1).max(255).required(), + is_fixed: Joi.boolean().required(), + time_indices: Joi.array() + .items(Joi.number().integer().min(0).max(671)) + .min(1) + .required() }); } else if (type === 'bulk_delete') { schema = Joi.object({ - time_idxs: Joi.array().items( - Joi.number().integer().min(0).max(671).required() - ).min(1).required() + title: Joi.string().min(1).max(255).required() + }); + } else if (type === 'get_by_time_idx') { + schema = Joi.object({ + time_idx: Joi.number().integer().min(0).max(671).required() }); } diff --git a/dtos/ScheduleResponseDTO.js b/dtos/ScheduleResponseDTO.js index a75316e65e13999800b70b4e23e9c40203dccee8..75276216f17143fd685160c0a47e72dd8b61608e 100644 --- a/dtos/ScheduleResponseDTO.js +++ b/dtos/ScheduleResponseDTO.js @@ -1,14 +1,25 @@ // dtos/ScheduleResponseDTO.js class ScheduleResponseDTO { - constructor(schedule) { - this.id = schedule.id; - this.user_id = schedule.user_id; - this.title = schedule.title; - this.time_idx = schedule.time_idx; // �덈줈�� time_idx �꾨뱶 異붽� - this.is_fixed = schedule.is_fixed; - this.createdAt = schedule.createdAt; - this.updatedAt = schedule.updatedAt; + static groupSchedules(schedules) { + const grouped = schedules.reduce((acc, schedule) => { + const key = `${schedule.title}-${schedule.is_fixed}`; + if (!acc[key]) { + acc[key] = { + id: schedule.id, + user_id: schedule.user_id, + title: schedule.title, + is_fixed: schedule.is_fixed, + time_indices: [], + createdAt: schedule.createdAt, + updatedAt: schedule.updatedAt + }; + } + acc[key].time_indices.push(schedule.time_idx); + return acc; + }, {}); + + return Object.values(grouped); } } diff --git a/services/meetingService.js b/services/meetingService.js index 24b9e67f94799975c8826f442002783b51c8360b..746597dcf5123cc7802ae8087e763a028b3d34cc 100644 --- a/services/meetingService.js +++ b/services/meetingService.js @@ -118,20 +118,16 @@ class MeetingService { { transaction } ); - // �ㅼ�以� �앹꽦 (紐⑥엫 �쒓컙 踰붿쐞 �� 紐⑤뱺 time_idx�� ���� �앹꽦) - const events = []; - for (let idx = time_idx_start; idx <= time_idx_end; idx++) { - events.push({ time_idx: idx }); - } - await ScheduleService.createSchedules( - { - userId: created_by, - title: `踰덇컻 紐⑥엫: ${title}`, - is_fixed: false, - events: events, - }, - transaction + const time_indices = Array.from( + { length: time_idx_end - time_idx_start + 1 }, + (_, i) => time_idx_start + i ); + await ScheduleService.createSchedules({ + userId: created_by, + title: `踰덇컻 紐⑥엫: ${title}`, + is_fixed: false, + time_indices: time_indices, + }, transaction); // 移쒓뎄 珥덈� 濡쒖쭅 �몄텧 const invitedFriendIds = await this.sendInvites({ @@ -264,24 +260,17 @@ class MeetingService { { transaction } ); - // �ㅼ�以� �앹꽦 (紐⑥엫 �쒓컙 踰붿쐞 �� 紐⑤뱺 time_idx�� ���� �앹꽦) - const events = []; - for ( - let idx = meeting.time_idx_start; - idx <= meeting.time_idx_end; - idx++ - ) { - events.push({ time_idx: idx }); - } - await ScheduleService.createSchedules( - { - userId: userId, - title: `踰덇컻 紐⑥엫: ${meeting.title}`, - is_fixed: false, - events: events, - }, - transaction - ); + const time_indices = Array.from( + { length: meeting.time_idx_end - meeting.time_idx_start + 1 }, + (_, i) => meeting.time_idx_start + i + ); + + await ScheduleService.createSchedules({ + userId: userId, + title: `踰덇컻 紐⑥엫: ${meeting.title}`, + is_fixed: false, + time_indices: time_indices, + }, transaction); // 梨꾪똿諛� 李멸� (MongoDB) const user = await User.findOne({ diff --git a/services/modifyScheduleResponse.test.js b/services/modifyScheduleResponse.test.js new file mode 100644 index 0000000000000000000000000000000000000000..64ab78c260c61a9274df0f3f77fa5d459cc65934 --- /dev/null +++ b/services/modifyScheduleResponse.test.js @@ -0,0 +1,187 @@ +// test/scheduleService.test.js +const sequelize = require('../config/sequelize'); +const { Schedule, User, Meeting, MeetingParticipant, FcmToken } = require('../models'); +const ScheduleService = require('../services/scheduleService'); +const MeetingService = require('../services/meetingService'); +const ChatRooms = require('../schemas/chatRooms'); + +describe('Schedule Service and Meeting Integration Tests', () => { + beforeAll(async () => { + await sequelize.sync({ force: true }); + }); + + beforeEach(async () => { + await MeetingParticipant.destroy({ where: {} }); + await Meeting.destroy({ where: {} }); + await Schedule.destroy({ where: {} }); + await User.destroy({ where: {} }); + await FcmToken.destroy({ where: {} }); + + // �붾� �ъ슜�� �앹꽦 + await User.bulkCreate([ + { id: 1, name: 'Alice', email: 'alice@example.com' }, + { id: 2, name: 'Bob', email: 'bob@example.com' }, + { id: 3, name: 'Charlie', email: 'charlie@example.com' } + ]); + + // ChatRooms Mock �ㅼ젙 + jest.spyOn(ChatRooms.prototype, 'save').mockResolvedValue(undefined); + jest.spyOn(ChatRooms, 'findOne').mockResolvedValue({ + participants: [], + isOnline: new Map(), + lastReadAt: new Map(), + lastReadLogId: new Map(), + save: jest.fn().mockResolvedValue(true) + }); + }); + + afterAll(async () => { + await sequelize.close(); + }); + + describe('Schedule Service Tests', () => { + test('should create schedule with time_indices', async () => { + const scheduleData = { + userId: 1, + title: 'Test Schedule', + is_fixed: true, + time_indices: [36, 37, 38] + }; + + const schedule = await ScheduleService.createSchedules(scheduleData); + + expect(schedule).toBeDefined(); + expect(schedule.title).toBe('Test Schedule'); + expect(schedule.time_indices).toEqual([36, 37, 38]); + + const dbSchedules = await Schedule.findAll({ + where: { user_id: 1, title: 'Test Schedule' } + }); + expect(dbSchedules.length).toBe(3); + }); + + test('should update schedule with new time_indices', async () => { + await ScheduleService.createSchedules({ + userId: 1, + title: 'Original Schedule', + is_fixed: true, + time_indices: [36, 37, 38] + }); + + const updateData = { + originalTitle: 'Original Schedule', + title: 'Updated Schedule', + is_fixed: true, + time_indices: [36, 37, 38, 39] + }; + + const updatedSchedule = await ScheduleService.updateSchedules(1, updateData); + + expect(updatedSchedule.title).toBe('Updated Schedule'); + expect(updatedSchedule.time_indices).toEqual([36, 37, 38, 39]); + }); + + test('should delete schedule by title', async () => { + await ScheduleService.createSchedules({ + userId: 1, + title: 'Schedule to Delete', + is_fixed: true, + time_indices: [40, 41, 42] + }); + + const result = await ScheduleService.deleteSchedules(1, 'Schedule to Delete'); + expect(result.deletedCount).toBe(3); + + const remainingSchedules = await Schedule.findAll({ + where: { user_id: 1, title: 'Schedule to Delete' } + }); + expect(remainingSchedules.length).toBe(0); + }); + }); + + describe('Meeting Integration Tests', () => { + beforeEach(() => { + jest.spyOn(User, 'findOne').mockResolvedValue({ + id: 1, + name: 'Alice', + email: 'alice@example.com', + fcmTokenList: [] + }); + }); + + test('should create meeting with correct schedules', async () => { + const meetingData = { + title: 'Test Meeting', + time_idx_start: 50, + time_idx_end: 52, + created_by: 1, + type: 'OPEN', + max_num: 5 + }; + + const meeting = await MeetingService.createMeeting(meetingData); + + const creatorSchedules = await Schedule.findAll({ + where: { + user_id: 1, + title: `踰덇컻 紐⑥엫: ${meetingData.title}` + } + }); + + expect(creatorSchedules.length).toBe(3); + expect(creatorSchedules.map(s => s.time_idx).sort()).toEqual([50, 51, 52]); + }); + + test('should create correct schedules when joining meeting', async () => { + const meetingData = { + title: 'Join Test Meeting', + time_idx_start: 60, + time_idx_end: 62, + created_by: 1, + type: 'OPEN', + max_num: 5, + time_idx_deadline: 59 + }; + + const meeting = await MeetingService.createMeeting(meetingData); + jest.spyOn(MeetingService, 'getCurrentTimeIdx').mockReturnValue(58); + + await MeetingService.joinMeeting(meeting.meeting_id, 2); + + const participantSchedules = await Schedule.findAll({ + where: { + user_id: 2, + title: `踰덇컻 紐⑥엫: ${meetingData.title}` + } + }); + + expect(participantSchedules.length).toBe(3); + expect(participantSchedules.map(s => s.time_idx).sort()).toEqual([60, 61, 62]); + }); + + test('should handle schedule conflicts correctly', async () => { + await ScheduleService.createSchedules({ + userId: 2, + title: 'Existing Schedule', + is_fixed: true, + time_indices: [70, 71] + }); + + const meetingData = { + title: 'Conflict Test Meeting', + time_idx_start: 70, + time_idx_end: 72, + created_by: 1, + type: 'OPEN', + max_num: 5, + time_idx_deadline: 69 + }; + + const meeting = await MeetingService.createMeeting(meetingData); + + await expect( + MeetingService.joinMeeting(meeting.meeting_id, 2) + ).rejects.toThrow('�ㅼ�以꾩씠 寃뱀묩�덈떎'); + }); + }); +}); \ No newline at end of file diff --git a/services/scheduleService.js b/services/scheduleService.js index e664a4909abfbca48cbb533bcbcf67f77152382d..abfb66ef6230eb7e7e24bc811ddf733d3fdacf84 100644 --- a/services/scheduleService.js +++ b/services/scheduleService.js @@ -9,158 +9,222 @@ class ScheduleService { * �ㅼ�以� �앹꽦 (踰뚰겕) * @param {object} [transaction] - Sequelize �몃옖��뀡 媛앹껜 -> 誘명똿諛⑹뿉�� �곌린�꾪빐 �몃옖��뀡�� �섍꺼諛쏅뒗嫄� 異붽� */ - async createSchedules({ userId, title, is_fixed, events }, transaction = null) { - const scheduleDTOs = []; - - for (const event of events) { - const { time_idx } = event; - - // 以묐났 �ㅼ�以� 寃��� + async createSchedules({ userId, title, is_fixed, time_indices }, transaction = null) { + // 以묐났 寃��� + for (const time_idx of time_indices) { const overlap = await this.checkScheduleOverlap(userId, time_idx, transaction); if (overlap) { - throw new Error(`Schedule overlaps with existing schedule at time_idx ${time_idx}`); + throw new Error(`Schedule overlaps at time_idx ${time_idx}`); } - - const scheduleData = { - user_id: userId, - title, - time_idx, - is_fixed, - }; - - const schedule = await Schedule.create(scheduleData, { transaction }); - scheduleDTOs.push(new ScheduleResponseDTO(schedule)); } - return scheduleDTOs; - } - - /** - * �ㅼ�以� �섏젙 (踰뚰겕) - * @param {Array} updates - �섏젙�� �ㅼ�以� 諛곗뿴 - */ - async updateSchedules(userId, updates, transaction = null) { - const updatedSchedules = []; + const createdSchedules = await Promise.all( + time_indices.map(time_idx => + Schedule.create({ + user_id: userId, + title, + time_idx, + is_fixed + }, { transaction }) + ) + ); - for (const update of updates) { - const { time_idx, title, is_fixed } = update; + return { + id: createdSchedules[0].id, + user_id: userId, + title, + is_fixed, + time_indices, + createdAt: createdSchedules[0].createdAt, + updatedAt: createdSchedules[0].updatedAt + }; + } - const schedule = await Schedule.findOne({ - where: { user_id: userId, time_idx }, - transaction, + async getAllSchedules(userId) { + try { + const schedules = await Schedule.findAll({ + where: { user_id: userId }, + order: [['time_idx', 'ASC']] }); - if (!schedule) { - throw new Error(`Schedule not found at time_idx ${time_idx}`); - } + return ScheduleResponseDTO.groupSchedules(schedules); + } catch (error) { + throw new Error(`Failed to fetch schedules: ${error.message}`); + } + } + + async updateSchedules(userId, updates, transaction = null) { + const { originalTitle, title, is_fixed, time_indices } = updates; - const updatedData = {}; - if (title !== undefined) updatedData.title = title; - if (is_fixed !== undefined) updatedData.is_fixed = is_fixed; + // 湲곗〈 �ㅼ�以� 議고쉶 + const existingSchedules = await Schedule.findAll({ + where: { + user_id: userId, + title: originalTitle + }, + transaction + }); - const updatedSchedule = await schedule.update(updatedData, { transaction }); - updatedSchedules.push(new ScheduleResponseDTO(updatedSchedule)); + if (existingSchedules.length === 0) { + throw new Error('Schedule not found'); } - return updatedSchedules; - } + const existingTimeIndices = existingSchedules.map(s => s.time_idx); // 湲곗〈 �쒓컙�� + const toDelete = existingTimeIndices.filter(idx => !time_indices.includes(idx)); // ��젣�� �쒓컙�� + const toAdd = time_indices.filter(idx => !existingTimeIndices.includes(idx)); // 異붽��� �쒓컙�� + const t = transaction || await sequelize.transaction(); - /** - * �ㅼ�以� ��젣 (踰뚰겕) - * @param {number} userId - �ъ슜�� ID - * @param {Array<number>} time_idxs - ��젣�� �ㅼ�以꾩쓽 time_idx 諛곗뿴 - * @param {object} [transaction] - Sequelize �몃옖��뀡 媛앹껜 - */ - async deleteSchedules(userId, time_idxs, transaction = null) { - const deleted_time_idxs = []; + try { + // ��젣 + if (toDelete.length > 0) { + await Schedule.destroy({ + where: { + user_id: userId, + title: originalTitle, + time_idx: { + [Op.in]: toDelete + } + }, + transaction: t + }); + } - for (const time_idx of time_idxs) { - const deletedCount = await Schedule.destroy({ - where: { user_id: userId, time_idx }, - transaction, - }); + // �쒕ぉ, 怨좎젙/�좊룞 �낅뜲�댄듃 + await Schedule.update( + { + title, + is_fixed + }, + { + where: { + user_id: userId, + title: originalTitle + }, + transaction: t + } + ); + + // �덈줈�� time_indices 異붽� + if (toAdd.length > 0) { + await Promise.all( + toAdd.map(time_idx => + Schedule.create({ + user_id: userId, + title, + time_idx, + is_fixed + }, { transaction: t }) + ) + ); + } - if (deletedCount === 0) { - throw new Error(`Schedule not found at time_idx ${time_idx}`); + if (!transaction) { + await t.commit(); } - deleted_time_idxs.push(time_idx); + return { + id: existingSchedules[0].id, + user_id: userId, + title, + is_fixed, + time_indices, + createdAt: existingSchedules[0].createdAt, + updatedAt: new Date() + }; + + } catch (error) { + if (!transaction) { + await t.rollback(); + } + throw error; } + } + + async deleteSchedules(userId, title, transaction = null) { + const deletedSchedules = await Schedule.destroy({ + where: { + user_id: userId, + title + }, + transaction + }); - return { deleted_time_idxs }; + return { deletedCount: deletedSchedules }; } /** * �뱀젙 time_idx濡� �ㅼ�以� 議고쉶 */ async getScheduleByTimeIdx(userId, time_idx) { + // �대떦 time_idx�� �ㅼ�以� 李얘린 const schedule = await Schedule.findOne({ - where: { user_id: userId, time_idx }, + where: { user_id: userId, time_idx } }); if (!schedule) { throw new Error('Schedule not found'); } - return new ScheduleResponseDTO(schedule); + // 媛숈� �쒕ぉ�� 紐⑤뱺 �ㅼ�以� 李얘린 + const relatedSchedules = await Schedule.findAll({ + where: { + user_id: userId, + title: schedule.title, + is_fixed: schedule.is_fixed + }, + order: [['time_idx', 'ASC']] + }); + + return ScheduleResponseDTO.groupSchedules(relatedSchedules)[0]; } - /** - * 紐⑤뱺 �ㅼ�以� 議고쉶 - */ async getAllSchedules(userId) { try { const schedules = await Schedule.findAll({ where: { user_id: userId }, - order: [['time_idx', 'ASC']], + order: [['time_idx', 'ASC']] }); - return schedules.map((schedule) => new ScheduleResponseDTO(schedule)); + return ScheduleResponseDTO.groupSchedules(schedules); } catch (error) { throw new Error(`Failed to fetch schedules: ${error.message}`); } } - /** - * 以묐났 �ㅼ�以� 寃��� - */ async checkScheduleOverlap(userId, time_idx, transaction = null) { const overlappingSchedule = await Schedule.findOne({ where: { user_id: userId, time_idx }, - transaction, + transaction }); - return !!overlappingSchedule; } async checkScheduleOverlapByTime(userId, time_idx_start, time_idx_end, transaction = null) { - console.log( - `checkScheduleOverlapByTime �몄텧: userId=${userId}, time_idx_start=${time_idx_start}, time_idx_end=${time_idx_end}` - ); - const overlappingSchedule = await Schedule.findOne({ + const overlappingSchedules = await Schedule.findAll({ where: { user_id: userId, time_idx: { - [Op.between]: [time_idx_start, time_idx_end] + [Op.between]: [time_idx_start, time_idx_end] } }, - transaction, + transaction }); - console.log(`以묐났 �ㅼ�以�: ${JSON.stringify(overlappingSchedule)}`); - const result = !!overlappingSchedule; - console.log(`�ㅼ�以� 異⑸룎 寃곌낵: ${result}`); - return result; + + const groupedSchedules = ScheduleResponseDTO.groupSchedules(overlappingSchedules); + const result = groupedSchedules.length > 0; + + console.log(`checkScheduleOverlapByTime �몄텧: userId=${userId}, time_idx_start=${time_idx_start}, time_idx_end=${time_idx_end}`); + console.log(`以묐났 �ㅼ�以�: ${JSON.stringify(groupedSchedules)}`); + console.log(`�ㅼ�以� 異⑸룎 寃곌낵: ${result}`); + + return result; } - - /** - * 留뚮즺�� �ㅼ�以� ��젣 - */ async cleanExpiredSchedules() { try { const deletedCount = await Schedule.destroy({ - where: { is_fixed: false }, + where: { is_fixed: false } }); - //console.log(`Deleted ${deletedCount} flexible schedules.`); + return { deletedCount }; } catch (error) { console.error('Failed to clean expired schedules:', error); throw error;