fix: align booking limit timezone between availability and validation (#24846)
* fix: align booking limit timezone between availability and validation - Use eventType.schedule?.timeZone for booking limits in availability calculation - Previously used user's timezone causing day boundary mismatch - Add unit tests to verify timezone alignment - Fixes issue where slots remain available after reaching booking limit Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * Rename variable limitsTz to eventTimeZone * update * fix: correct timezone fallback to include user timezone without forcing UTC - Changed fallback chain to: schedule → event → user → undefined - Treats empty strings as missing timezones - Does not force UTC when all timezones are missing - Aligns with validation behavior when timezone is undefined - Fixes getSchedule.test.ts failures Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * Revert "fix: correct timezone fallback to include user timezone without forcing UTC" This reverts commit 721e0bdfd7b08004e68df6bdf5b97b9bd6cf3d5c. * test: remove complex unit tests with 'as any' casts - Removed Booking Limits Timezone Alignment tests that required heavy mocking - These tests relied on spying private methods and had many 'as any' casts - Timezone alignment is already covered by integration tests in getSchedule.test.ts - Kept BookingDateInPastError test which has no type issues Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * address review --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
anik@cal.com
Devin AI
parent
f1db27c0ad
commit
896f8dbd3d
@@ -2735,6 +2735,103 @@ describe("getSchedule", () => {
|
||||
expect(availableSlotsInTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(0); // 1 booking per day as limit
|
||||
});
|
||||
|
||||
test("booking limits use schedule timezone when set, falling back to user timezone", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
|
||||
|
||||
const scenarioData = {
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
length: 60,
|
||||
beforeEventBuffer: 0,
|
||||
afterEventBuffer: 0,
|
||||
bookingLimits: {
|
||||
PER_DAY: 1,
|
||||
},
|
||||
schedule: {
|
||||
id: 1,
|
||||
name: "Schedule with timezone",
|
||||
availability: [
|
||||
{
|
||||
userId: null,
|
||||
eventTypeId: null,
|
||||
days: [0, 1, 2, 3, 4, 5, 6],
|
||||
startTime: new Date("1970-01-01T00:00:00.000Z"),
|
||||
endTime: new Date("1970-01-01T23:59:59.999Z"),
|
||||
date: null,
|
||||
},
|
||||
],
|
||||
timeZone: Timezones["+5:30"],
|
||||
},
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
users: [
|
||||
{
|
||||
...TestData.users.example,
|
||||
id: 101,
|
||||
schedules: [
|
||||
{
|
||||
id: 2,
|
||||
name: "User schedule",
|
||||
availability: [
|
||||
{
|
||||
userId: null,
|
||||
eventTypeId: null,
|
||||
days: [0, 1, 2, 3, 4, 5, 6],
|
||||
startTime: new Date("1970-01-01T00:00:00.000Z"),
|
||||
endTime: new Date("1970-01-01T23:59:59.999Z"),
|
||||
date: null,
|
||||
},
|
||||
],
|
||||
timeZone: Timezones["+6:00"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
bookings: [
|
||||
{
|
||||
userId: 101,
|
||||
eventTypeId: 1,
|
||||
startTime: `${plus2DateString}T08:00:00.000Z`,
|
||||
endTime: `${plus2DateString}T09:00:00.000Z`,
|
||||
status: "ACCEPTED" as BookingStatus,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await createBookingScenario(scenarioData);
|
||||
|
||||
const availability = await availableSlotsService.getAvailableSlots({
|
||||
input: {
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T00:00:00.000Z`,
|
||||
endTime: `${plus3DateString}T23:59:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: false,
|
||||
orgSlug: null,
|
||||
},
|
||||
});
|
||||
|
||||
const availableSlotsInScheduleTz: dayjs.Dayjs[] = [];
|
||||
for (const date in availability.slots) {
|
||||
availability.slots[date].forEach((timeObj) => {
|
||||
availableSlotsInScheduleTz.push(dayjs(timeObj.time).tz(Timezones["+5:30"]));
|
||||
});
|
||||
}
|
||||
|
||||
expect(availableSlotsInScheduleTz.filter((slot) => slot.format().startsWith(plus2DateString)).length).toBe(
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
test("a slot counts as being busy when the eventType is requiresConfirmation and requiresConfirmationWillBlockSlot", async () => {
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
|
||||
|
||||
@@ -823,6 +823,7 @@ export class AvailableSlotsService {
|
||||
let busyTimesFromLimitsMap: Map<number, EventBusyDetails[]> | undefined = undefined;
|
||||
if (eventType && (bookingLimits || durationLimits)) {
|
||||
const usersForLimits = usersWithCredentials.map((user) => ({ id: user.id, email: user.email }));
|
||||
const eventTimeZone = eventType.schedule?.timeZone ?? usersWithCredentials[0]?.timeZone ?? "UTC";
|
||||
busyTimesFromLimitsMap = await this.getBusyTimesFromLimitsForUsers(
|
||||
usersForLimits,
|
||||
bookingLimits,
|
||||
@@ -831,7 +832,7 @@ export class AvailableSlotsService {
|
||||
endTime,
|
||||
typeof input.duration === "number" ? input.duration : undefined,
|
||||
eventType,
|
||||
usersWithCredentials[0]?.timeZone || "UTC",
|
||||
eventTimeZone,
|
||||
input.rescheduleUid || undefined
|
||||
);
|
||||
}
|
||||
@@ -845,6 +846,7 @@ export class AvailableSlotsService {
|
||||
let teamBookingLimitsMap: Map<number, EventBusyDetails[]> | undefined = undefined;
|
||||
if (teamForBookingLimits && teamBookingLimits) {
|
||||
const usersForTeamLimits = usersWithCredentials.map((user) => ({ id: user.id, email: user.email }));
|
||||
const eventTimeZone = eventType.schedule?.timeZone ?? usersWithCredentials[0]?.timeZone ?? "UTC";
|
||||
teamBookingLimitsMap = await this.getBusyTimesFromTeamLimitsForUsers(
|
||||
usersForTeamLimits,
|
||||
teamBookingLimits,
|
||||
@@ -852,7 +854,7 @@ export class AvailableSlotsService {
|
||||
endTime,
|
||||
teamForBookingLimits.id,
|
||||
teamForBookingLimits.includeManagedEventsInLimits,
|
||||
usersWithCredentials[0]?.timeZone || "UTC",
|
||||
eventTimeZone,
|
||||
input.rescheduleUid || undefined
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user