fix: booking timeslots (#12195)

This commit is contained in:
Lauris Skraucis 2023-11-03 15:58:58 +01:00 committed by GitHub
parent 5348464972
commit fc716f5921
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 61 deletions

View File

@ -1,61 +0,0 @@
import type { DateRange } from "@calcom/lib/date-ranges";
import { intersect } from "@calcom/lib/date-ranges";
import { SchedulingType } from "@calcom/prisma/enums";
export const getAggregatedAvailability = (
userAvailability: { dateRanges: DateRange[]; user?: { isFixed?: boolean } }[],
schedulingType: SchedulingType | null
): DateRange[] => {
const fixedHosts = userAvailability.filter(
({ user }) => !schedulingType || schedulingType === SchedulingType.COLLECTIVE || user?.isFixed
);
const dateRangesToIntersect = fixedHosts.map((s) => s.dateRanges);
const unfixedHosts = userAvailability.filter(({ user }) => user?.isFixed !== true);
if (unfixedHosts.length) {
dateRangesToIntersect.push(unfixedHosts.flatMap((s) => s.dateRanges));
}
const availability = intersect(dateRangesToIntersect);
return mergeOverlappingDateRanges(availability);
};
function isSameDay(date1: Date, date2: Date) {
return (
date1.getUTCFullYear() === date2.getUTCFullYear() &&
date1.getUTCMonth() === date2.getUTCMonth() &&
date1.getUTCDate() === date2.getUTCDate()
);
}
function mergeOverlappingDateRanges(dateRanges: DateRange[]) {
dateRanges.sort((a, b) => a.start.valueOf() - b.start.valueOf());
const mergedDateRanges: DateRange[] = [];
let currentRange = dateRanges[0];
if (!currentRange) {
return [];
}
for (let i = 1; i < dateRanges.length; i++) {
const nextRange = dateRanges[i];
if (
isSameDay(currentRange.start.toDate(), nextRange.start.toDate()) &&
currentRange.end.valueOf() > nextRange.start.valueOf()
) {
currentRange = {
start: currentRange.start,
end: currentRange.end.valueOf() > nextRange.end.valueOf() ? currentRange.end : nextRange.end,
};
} else {
mergedDateRanges.push(currentRange);
currentRange = nextRange;
}
}
mergedDateRanges.push(currentRange);
return mergedDateRanges;
}

View File

@ -0,0 +1,36 @@
import type { DateRange } from "@calcom/lib/date-ranges";
export function mergeOverlappingDateRanges(dateRanges: DateRange[]) {
dateRanges.sort((a, b) => a.start.valueOf() - b.start.valueOf());
const mergedDateRanges: DateRange[] = [];
let currentRange = dateRanges[0];
if (!currentRange) {
return [];
}
for (let i = 1; i < dateRanges.length; i++) {
const nextRange = dateRanges[i];
if (isCurrentRangeOverlappingNext(currentRange, nextRange)) {
currentRange = {
start: currentRange.start,
end: currentRange.end.valueOf() > nextRange.end.valueOf() ? currentRange.end : nextRange.end,
};
} else {
mergedDateRanges.push(currentRange);
currentRange = nextRange;
}
}
mergedDateRanges.push(currentRange);
return mergedDateRanges;
}
function isCurrentRangeOverlappingNext(currentRange: DateRange, nextRange: DateRange): boolean {
return (
currentRange.start.valueOf() <= nextRange.start.valueOf() &&
currentRange.end.valueOf() > nextRange.start.valueOf()
);
}

View File

@ -0,0 +1,62 @@
import { describe, it, expect } from "vitest";
import dayjs from "@calcom/dayjs";
import type { DateRange } from "@calcom/lib/date-ranges";
import { mergeOverlappingDateRanges } from ".";
const november2 = "2023-11-02";
const november3 = "2023-11-03";
describe("mergeOverlappingDateRanges", () => {
it("should merge all ranges into one when one range includes all others", () => {
const dateRanges = [
createDateRange(`${november2}T23:00:00.000Z`, `${november3}T07:00:00.000Z`), // Includes all others
createDateRange(`${november2}T23:15:00.000Z`, `${november3}T00:00:00.000Z`),
createDateRange(`${november3}T00:15:00.000Z`, `${november3}T01:00:00.000Z`),
createDateRange(`${november3}T01:15:00.000Z`, `${november3}T02:00:00.000Z`),
];
const mergedRanges = mergeOverlappingDateRanges(dateRanges);
expect(mergedRanges).toHaveLength(1);
expect(mergedRanges[0].start.isSame(dayjs(dateRanges[0].start))).toBe(true);
expect(mergedRanges[0].end.isSame(dayjs(dateRanges[0].end))).toBe(true);
});
it("should merge only overlapping ranges over 2 days and leave non-overlapping ranges as is", () => {
const dateRanges = [
createDateRange(`${november2}T23:00:00.000Z`, `${november3}T07:00:00.000Z`),
createDateRange(`${november3}T05:00:00.000Z`, `${november3}T06:00:00.000Z`),
createDateRange(`${november3}T08:00:00.000Z`, `${november3}T10:00:00.000Z`), // This range should not be merged
];
const mergedRanges = mergeOverlappingDateRanges(dateRanges);
expect(mergedRanges).toHaveLength(2);
expect(mergedRanges[0].start.isSame(dayjs(dateRanges[0].start))).toBe(true);
expect(mergedRanges[0].end.isSame(dayjs(dateRanges[0].end))).toBe(true);
expect(mergedRanges[1].start.isSame(dayjs(dateRanges[2].start))).toBe(true);
expect(mergedRanges[1].end.isSame(dayjs(dateRanges[2].end))).toBe(true);
});
it("should merge ranges that overlap on the same day", () => {
const dateRanges = [
createDateRange(`${november2}T01:00:00.000Z`, `${november2}T04:00:00.000Z`),
createDateRange(`${november2}T02:00:00.000Z`, `${november2}T03:00:00.000Z`), // This overlaps with the first range
createDateRange(`${november2}T05:00:00.000Z`, `${november2}T06:00:00.000Z`), // This doesn't overlap with above
];
const mergedRanges = mergeOverlappingDateRanges(dateRanges);
expect(mergedRanges).toHaveLength(2);
expect(mergedRanges[0].start.isSame(dayjs(dateRanges[0].start))).toBe(true);
expect(mergedRanges[0].end.isSame(dayjs(dateRanges[0].end))).toBe(true);
expect(mergedRanges[1].start.isSame(dayjs(dateRanges[2].start))).toBe(true);
expect(mergedRanges[1].end.isSame(dayjs(dateRanges[2].end))).toBe(true);
});
});
function createDateRange(start: string, end: string): DateRange {
return {
start: dayjs(start),
end: dayjs(end),
};
}

View File

@ -0,0 +1,25 @@
import type { DateRange } from "@calcom/lib/date-ranges";
import { intersect } from "@calcom/lib/date-ranges";
import { SchedulingType } from "@calcom/prisma/enums";
import { mergeOverlappingDateRanges } from "./date-range-utils/mergeOverlappingDateRanges";
export const getAggregatedAvailability = (
userAvailability: { dateRanges: DateRange[]; user?: { isFixed?: boolean } }[],
schedulingType: SchedulingType | null
): DateRange[] => {
const fixedHosts = userAvailability.filter(
({ user }) => !schedulingType || schedulingType === SchedulingType.COLLECTIVE || user?.isFixed
);
const dateRangesToIntersect = fixedHosts.map((s) => s.dateRanges);
const unfixedHosts = userAvailability.filter(({ user }) => user?.isFixed !== true);
if (unfixedHosts.length) {
dateRangesToIntersect.push(unfixedHosts.flatMap((s) => s.dateRanges));
}
const availability = intersect(dateRangesToIntersect);
return mergeOverlappingDateRanges(availability);
};