-
Notifications
You must be signed in to change notification settings - Fork 12.6k
fix: add minimize gaps between bookings option to event types #28797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ import { | |
| type TeamBillingInput, | ||
| TeamBillingPublishResponseStatus, | ||
| } from "./ITeamBillingService"; | ||
| import { MembershipRole } from "@calcom/prisma/enums"; | ||
| import { CreditService } from "../../credit-service" | ||
|
|
||
| const log = logger.getSubLogger({ prefix: ["TeamBilling"] }); | ||
|
|
||
|
|
@@ -133,6 +135,25 @@ export class TeamBillingService implements ITeamBillingService { | |
| } | ||
| async downgrade() { | ||
| try { | ||
| const creditService = new CreditService(); | ||
|
|
||
| const owner = await prisma.membership.findFirst({ | ||
| where : { | ||
| teamId : this.team.id, | ||
| role : MembershipRole.OWNER | ||
| }, | ||
| }); | ||
|
|
||
| if(!owner) { | ||
| throw new Error(`No owner found for team ${this.team.id}`); | ||
| } | ||
|
|
||
| await creditService.moveCreditsFromTeamToUser({ | ||
| teamId : this.team.id, | ||
| userId : owner.userId, | ||
| }); | ||
|
Comment on lines
+138
to
+154
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the downgrade writes in one transaction.
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| const { mergeMetadata } = getMetadataHelpers(teamPaymentMetadataSchema, this.team.metadata); | ||
| const metadata = mergeMetadata({ | ||
| paymentId: undefined, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -171,6 +171,7 @@ export type FormValues = { | |
| bookingLimits?: IntervalLimit; | ||
| onlyShowFirstAvailableSlot: boolean; | ||
| showOptimizedSlots: boolean; | ||
| minimizeGaps?: boolean | null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add This file now carries the flag in Suggested fix onlyShowFirstAvailableSlot?: boolean;
showOptimizedSlots?: boolean | null;
+ minimizeGaps?: boolean | null;
disableCancelling?: boolean | null;
disableRescheduling?: boolean | null;🤖 Prompt for AI Agents |
||
| children: ChildrenEventType[]; | ||
| hosts: Host[]; | ||
| hostGroups: { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,8 @@ export type GetSlots = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| offsetStart?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datesOutOfOffice?: IOutOfOfficeData; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showOptimizedSlots?: boolean | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minimizeGaps? : boolean | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| existingBookings? : {startTime : Dayjs; endTime : Dayjs}[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datesOutOfOfficeTimeZone?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type TimeFrame = { userIds?: number[]; startTime: number; endTime: number }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -77,6 +79,8 @@ function buildSlotsWithDateRanges({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| offsetStart, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datesOutOfOffice, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showOptimizedSlots, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minimizeGaps, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| existingBookings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datesOutOfOfficeTimeZone, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dateRanges: DateRange[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,6 +91,8 @@ function buildSlotsWithDateRanges({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| offsetStart?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datesOutOfOffice?: IOutOfOfficeData; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showOptimizedSlots?: boolean | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minimizeGaps? : boolean | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| existingBookings? : {startTime : Dayjs; endTime : Dayjs}[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| datesOutOfOfficeTimeZone?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // keep the old safeguards in; may be needed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -226,6 +232,24 @@ function buildSlotsWithDateRanges({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if(minimizeGaps && existingBookings && existingBookings.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const validStartTimes = new Set<string>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for(const booking of existingBookings) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validStartTimes.add(booking.endTime.toISOString()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validStartTimes.add( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| booking.startTime.subtract(eventLength, "minutes").toISOString() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const filtered = Array.from(slots.values().filter((slot) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validStartTimes.has(slot.time.toISOString()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return filtered.length > 0 ? filtered : Array.from(slots.values()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+235
to
+251
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '235,251p' packages/features/schedules/lib/slots.ts
rg -n '\.values\(\)\.filter\(' packages/features/schedules/lib/slots.tsRepository: calcom/cal.com Length of output: 664 🏁 Script executed: sed -n '256,285p' packages/features/schedules/lib/slots.tsRepository: calcom/cal.com Length of output: 657 🏁 Script executed: rg -A 15 'const buildSlotsWithDateRanges = \(' packages/features/schedules/lib/slots.ts | head -30Repository: calcom/cal.com Length of output: 40 🏁 Script executed: rg -n "buildSlotsWithDateRanges" packages/features/schedules/lib/slots.ts | head -20Repository: calcom/cal.com Length of output: 136 🏁 Script executed: sed -n '73,120p' packages/features/schedules/lib/slots.tsRepository: calcom/cal.com Length of output: 1431 This filter branch won't work as written, and the feature is disconnected from the public API. Two critical issues:
The fallback Suggested fix for issue 1 if (minimizeGaps && existingBookings && existingBookings.length > 0) {
const validStartTimes = new Set<string>();
for (const booking of existingBookings) {
validStartTimes.add(booking.endTime.toISOString());
validStartTimes.add(booking.startTime.subtract(eventLength, "minutes").toISOString());
}
- const filtered = Array.from(slots.values().filter((slot) =>
- validStartTimes.has(slot.time.toISOString())
- ))
-
- return filtered.length > 0 ? filtered : Array.from(slots.values());
+ const filtered = Array.from(slots.values()).filter((slot) =>
+ validStartTimes.has(slot.time.toISOString())
+ );
+
+ return filtered;
}Fix issue 2 by adding 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Array.from(slots.values()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent double-transferring the same balance.
This captures
teamCreditBalance.additionalCreditsbefore the source balance is cleared, so two concurrent downgrade calls can both read the same amount and both increment the owner's balance. Please make the debit atomic on the source row first, then apply the increment to the user balance.🤖 Prompt for AI Agents