feat: Setting for rescheduling past bookings (#18358)
* feat: Setting for rescheduling past bookings * fix stuff * rename column * add e2e test * fix column name * fix typo --------- Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
co-authored by
Anik Dhabal Babu
Peer Richelsen
CarinaWolli
parent
39891ee308
commit
438a73b0cd
@@ -216,7 +216,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
];
|
||||
|
||||
const editBookingActions: ActionType[] = [
|
||||
...(isBookingInPast
|
||||
...(isBookingInPast && !booking.eventType.allowReschedulingPastBookings
|
||||
? []
|
||||
: [
|
||||
{
|
||||
|
||||
@@ -37,6 +37,7 @@ export const getEventTypesFromDB = async (id: number) => {
|
||||
price: true,
|
||||
currency: true,
|
||||
bookingFields: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
disableGuests: true,
|
||||
timeZone: true,
|
||||
profile: {
|
||||
|
||||
@@ -57,6 +57,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
},
|
||||
},
|
||||
slug: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
team: {
|
||||
select: {
|
||||
parentId: true,
|
||||
@@ -131,7 +132,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
});
|
||||
|
||||
const isBookingInPast = booking.endTime && new Date(booking.endTime) < new Date();
|
||||
if (isBookingInPast) {
|
||||
if (isBookingInPast && !eventType.allowReschedulingPastBookings) {
|
||||
const destinationUrlSearchParams = new URLSearchParams();
|
||||
const responses = bookingSeat ? getSafe<string>(bookingSeat.data, ["responses"]) : booking.responses;
|
||||
const name = getSafe<string>(responses, ["name"]);
|
||||
|
||||
@@ -730,22 +730,23 @@ export default function Success(props: PageProps) {
|
||||
</span>
|
||||
|
||||
<>
|
||||
{!props.recurringBookings && !isBookingInPast && (
|
||||
<span className="text-default inline">
|
||||
<span className="underline" data-testid="reschedule-link">
|
||||
<Link
|
||||
href={`/reschedule/${seatReferenceUid || bookingInfo?.uid}${
|
||||
currentUserEmail
|
||||
? `?rescheduledBy=${encodeURIComponent(currentUserEmail)}`
|
||||
: ""
|
||||
}`}
|
||||
legacyBehavior>
|
||||
{t("reschedule")}
|
||||
</Link>
|
||||
{!props.recurringBookings &&
|
||||
(!isBookingInPast || eventType.allowReschedulingPastBookings) && (
|
||||
<span className="text-default inline">
|
||||
<span className="underline" data-testid="reschedule-link">
|
||||
<Link
|
||||
href={`/reschedule/${seatReferenceUid || bookingInfo?.uid}${
|
||||
currentUserEmail
|
||||
? `?rescheduledBy=${encodeURIComponent(currentUserEmail)}`
|
||||
: ""
|
||||
}`}
|
||||
legacyBehavior>
|
||||
{t("reschedule")}
|
||||
</Link>
|
||||
</span>
|
||||
<span className="mx-2">{t("or_lowercase")}</span>
|
||||
</span>
|
||||
<span className="mx-2">{t("or_lowercase")}</span>
|
||||
</span>
|
||||
)}
|
||||
)}
|
||||
|
||||
<button
|
||||
data-testid="cancel"
|
||||
|
||||
@@ -50,6 +50,53 @@ test.describe("Reschedule Tests", async () => {
|
||||
await booking.delete();
|
||||
});
|
||||
|
||||
test("Should not show reschedule and request reschedule option if booking in past and disallowed", async ({
|
||||
page,
|
||||
users,
|
||||
bookings,
|
||||
}) => {
|
||||
const user = await users.create();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id!, {
|
||||
status: BookingStatus.ACCEPTED,
|
||||
startTime: dayjs().subtract(2, "day").toDate(),
|
||||
endTime: dayjs().subtract(2, "day").add(30, "minutes").toDate(),
|
||||
});
|
||||
|
||||
await prisma.eventType.update({
|
||||
where: {
|
||||
id: user.eventTypes[0].id,
|
||||
},
|
||||
data: {
|
||||
allowReschedulingPastBookings: true,
|
||||
},
|
||||
});
|
||||
|
||||
await user.apiLogin();
|
||||
await page.goto("/bookings/past");
|
||||
|
||||
await page.locator('[data-testid="edit_booking"]').nth(0).click();
|
||||
|
||||
await expect(page.locator('[data-testid="reschedule"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="reschedule_request"]')).toBeVisible();
|
||||
|
||||
await prisma.eventType.update({
|
||||
where: {
|
||||
id: user.eventTypes[0].id,
|
||||
},
|
||||
data: {
|
||||
allowReschedulingPastBookings: false,
|
||||
},
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
|
||||
await page.locator('[data-testid="edit_booking"]').nth(0).click();
|
||||
|
||||
await expect(page.locator('[data-testid="reschedule"]')).toBeHidden();
|
||||
await expect(page.locator('[data-testid="reschedule_request"]')).toBeHidden();
|
||||
});
|
||||
|
||||
test("Should display former time when rescheduling availability", async ({ page, users, bookings }) => {
|
||||
const user = await users.create();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
@@ -2907,6 +2907,8 @@
|
||||
"attribute_weight_enabled_description": "By enabling weights, it would be possible to assign higher priority to certain attributes per user. The higher the weight, the higher the priority.",
|
||||
"routed": "Routed",
|
||||
"reassigned": "Reassigned",
|
||||
"allow_rescheduling_past_events": "Allow rescheduling past events",
|
||||
"allow_rescheduling_past_events_description": "Enabling this option allows for past events to be rescheduled",
|
||||
"rerouted": "Rerouted",
|
||||
"salesforce_assigned": "Salesforce assignment",
|
||||
"router_position": "Router Position",
|
||||
|
||||
@@ -476,6 +476,7 @@ export const EventAdvancedTab = ({
|
||||
const eventTypeColorLocked = shouldLockDisableProps("eventTypeColor");
|
||||
const lockTimeZoneToggleOnBookingPageLocked = shouldLockDisableProps("lockTimeZoneToggleOnBookingPage");
|
||||
const multiplePrivateLinksLocked = shouldLockDisableProps("multiplePrivateLinks");
|
||||
const reschedulingPastBookingsLocked = shouldLockDisableProps("allowReschedulingPastBookings");
|
||||
const { isLocked, ...eventNameLocked } = shouldLockDisableProps("eventName");
|
||||
|
||||
if (isManagedEventType) {
|
||||
@@ -893,6 +894,21 @@ export const EventAdvancedTab = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="allowReschedulingPastBookings"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SettingsToggle
|
||||
labelClassName={classNames("text-sm")}
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName={classNames("border-subtle rounded-lg border py-6 px-4 sm:px-6")}
|
||||
title={t("allow_rescheduling_past_events")}
|
||||
{...reschedulingPastBookingsLocked}
|
||||
description={t("allow_rescheduling_past_events_description")}
|
||||
checked={value}
|
||||
onCheckedChange={(e) => onChange(e)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="eventTypeColor"
|
||||
render={() => (
|
||||
|
||||
@@ -88,6 +88,7 @@ const commons = {
|
||||
seatsShowAttendees: null,
|
||||
seatsShowAvailabilityCount: null,
|
||||
onlyShowFirstAvailableSlot: false,
|
||||
allowReschedulingPastBookings: false,
|
||||
id: 0,
|
||||
hideCalendarNotes: false,
|
||||
hideCalendarEventDetails: false,
|
||||
|
||||
@@ -35,6 +35,7 @@ export const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||
afterEventBuffer: true,
|
||||
seatsPerTimeSlot: true,
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
scheduleId: true,
|
||||
|
||||
@@ -501,6 +501,7 @@ export class EventTypeRepository {
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
durationLimits: true,
|
||||
assignAllTeamMembers: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
assignRRMembersUsingSegment: true,
|
||||
rrSegmentQueryValue: true,
|
||||
isRRWeightsEnabled: true,
|
||||
@@ -784,6 +785,7 @@ export class EventTypeRepository {
|
||||
periodStartDate: true,
|
||||
periodEndDate: true,
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
periodCountCalendarDays: true,
|
||||
rescheduleWithSameRoundRobinHost: true,
|
||||
periodDays: true,
|
||||
|
||||
@@ -146,6 +146,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
|
||||
rrSegmentQueryValue: null,
|
||||
autoTranslateDescriptionEnabled: false,
|
||||
useEventLevelSelectedCalendars: false,
|
||||
allowReschedulingPastBookings: false,
|
||||
...eventType,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -90,6 +90,7 @@ export const useEventTypeForm = ({
|
||||
requiresConfirmationForFreeEmail: eventType.requiresConfirmationForFreeEmail,
|
||||
slotInterval: eventType.slotInterval,
|
||||
minimumBookingNotice: eventType.minimumBookingNotice,
|
||||
allowReschedulingPastBookings: eventType.allowReschedulingPastBookings,
|
||||
metadata: eventType.metadata,
|
||||
hosts: eventType.hosts.sort((a, b) => sortHosts(a, b, eventType.isRRWeightsEnabled)),
|
||||
successRedirectUrl: eventType.successRedirectUrl || "",
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "allowReschedulingPastBookings" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -166,6 +166,7 @@ model EventType {
|
||||
fieldTranslations EventTypeTranslation[]
|
||||
maxLeadThreshold Int?
|
||||
selectedCalendars SelectedCalendar[]
|
||||
allowReschedulingPastBookings Boolean @default(false)
|
||||
|
||||
/// @zod.custom(imports.eventTypeColor)
|
||||
eventTypeColor Json?
|
||||
|
||||
@@ -748,6 +748,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
|
||||
assignAllTeamMembers: true,
|
||||
isRRWeightsEnabled: true,
|
||||
eventTypeColor: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
rescheduleWithSameRoundRobinHost: true,
|
||||
maxLeadThreshold: true,
|
||||
};
|
||||
|
||||
@@ -236,6 +236,7 @@ export async function getBookings({
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
eventTypeColor: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
schedulingType: true,
|
||||
length: true,
|
||||
team: {
|
||||
|
||||
@@ -716,6 +716,7 @@ async function getExistingBookings(
|
||||
seatsPerTimeSlot: true,
|
||||
requiresConfirmationWillBlockSlot: true,
|
||||
requiresConfirmation: true,
|
||||
allowReschedulingPastBookings: true,
|
||||
},
|
||||
},
|
||||
...(!!eventType?.seatsPerTimeSlot && {
|
||||
|
||||
Reference in New Issue
Block a user