From 625c260e40137374f762107def8a2872bc890fb9 Mon Sep 17 00:00:00 2001 From: Abhijeet Singh Date: Wed, 26 Nov 2025 01:44:05 +0530 Subject: [PATCH] fix: OOO days not correctly blocked (#25259) * fix: OOO check in buildSlotsWithDateRanges * added testcase --------- Co-authored-by: chauhan_s Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> --- packages/features/schedules/lib/slots.test.ts | 56 +++++++++++++++++++ packages/features/schedules/lib/slots.ts | 5 +- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/packages/features/schedules/lib/slots.test.ts b/packages/features/schedules/lib/slots.test.ts index 35a32a6a1c..1b6c5d56ca 100644 --- a/packages/features/schedules/lib/slots.test.ts +++ b/packages/features/schedules/lib/slots.test.ts @@ -840,4 +840,60 @@ describe("Tests the date-range slot logic with showOptimizedSlots", () => { vi.useRealTimers(); }); + + it("should mark slots as away when OOO is on next day and availability extends past midnight", async () => { + // This test reproduces the bug where: + // - Day 1 has availability from 00:00 to 23:59 + // - Day 2 is marked as OOO + // - Slots generated after midnight are not marked as away + + vi.setSystemTime(dayjs.utc("2025-11-17T00:00:00Z").toDate()); + + const day1Start = dayjs.utc("2025-11-17T00:00:00Z"); + const day2End = dayjs.utc("2025-11-18T23:59:59Z"); + + // OOO data for Day 2 (2025-11-18) + const datesOutOfOffice = { + "2025-11-18": { + fromUser: { id: 1, displayName: "Test User" }, + toUser: null, + reason: "Out of office", + emoji: "🏖️", + }, + }; + + const slots = getSlots({ + inviteeDate: day1Start, + frequency: 15, + minimumBookingNotice: 0, + eventLength: 15, + dateRanges: [ + { + start: day1Start, + end: day2End, + }, + ], + datesOutOfOffice, + }); + + // Filter slots by day + const day1Slots = slots.filter((slot) => slot.time.format("YYYY-MM-DD") === "2025-11-17"); + const day2Slots = slots.filter((slot) => slot.time.format("YYYY-MM-DD") === "2025-11-18"); + + // Day 1 slots should NOT be marked as away + day1Slots.forEach((slot) => { + expect(slot.away).toBeUndefined(); + }); + + expect(day2Slots.length).toBeGreaterThan(0); + + // Day 2 slots should be marked as away + day2Slots.forEach((slot) => { + expect(slot.away).toBe(true); + expect(slot.reason).toBe("Out of office"); + expect(slot.emoji).toBe("🏖️"); + }); + + vi.useRealTimers(); + }); }); diff --git a/packages/features/schedules/lib/slots.ts b/packages/features/schedules/lib/slots.ts index 22c1c1c7df..3dc027aafc 100644 --- a/packages/features/schedules/lib/slots.ts +++ b/packages/features/schedules/lib/slots.ts @@ -122,7 +122,6 @@ function buildSlotsWithDateRanges({ const slotBoundaries = new Map(); orderedDateRanges.forEach((range) => { - const dateYYYYMMDD = range.start.format("YYYY-MM-DD"); let slotStartTime = range.start.utc().isAfter(startTimeWithMinNotice) ? range.start @@ -182,8 +181,8 @@ function buildSlotsWithDateRanges({ } slotBoundaries.set(slotStartTime.valueOf(), true); - - const dateOutOfOfficeExists = datesOutOfOffice?.[dateYYYYMMDD]; + const slotDateYYYYMMDD = slotStartTime.format("YYYY-MM-DD"); + const dateOutOfOfficeExists = datesOutOfOffice?.[slotDateYYYYMMDD]; let slotData: { time: Dayjs; userIds?: number[];