fix: booking timeslots (#12195)
This commit is contained in:
parent
5348464972
commit
fc716f5921
|
@ -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;
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
);
|
||||||
|
}
|
|
@ -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),
|
||||||
|
};
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user