-
Notifications
You must be signed in to change notification settings - Fork 32
fix: prevent booking after event start date #135
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
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 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||||||||||||||
|
|
||||||||||||||||||
| import frappe | ||||||||||||||||||
| from frappe.model.document import Document | ||||||||||||||||||
| from frappe.utils import add_days, date_diff, getdate, today | ||||||||||||||||||
| from frappe.utils.data import time_diff_in_seconds | ||||||||||||||||||
|
|
||||||||||||||||||
| from buzz.utils import only_if_app_installed | ||||||||||||||||||
|
|
@@ -23,9 +24,11 @@ class BuzzEvent(Document): | |||||||||||||||||
| from buzz.proposals.doctype.sponsorship_deck_item.sponsorship_deck_item import SponsorshipDeckItem | ||||||||||||||||||
|
|
||||||||||||||||||
| about: DF.TextEditor | None | ||||||||||||||||||
| allow_editing_talks_after_acceptance: DF.Check | ||||||||||||||||||
| apply_tax: DF.Check | ||||||||||||||||||
| auto_send_pitch_deck: DF.Check | ||||||||||||||||||
| banner_image: DF.AttachImage | None | ||||||||||||||||||
| booking_closes_on: DF.Date | None | ||||||||||||||||||
| card_image: DF.AttachImage | None | ||||||||||||||||||
| category: DF.Link | ||||||||||||||||||
| default_ticket_type: DF.Link | None | ||||||||||||||||||
|
|
@@ -45,6 +48,7 @@ class BuzzEvent(Document): | |||||||||||||||||
| route: DF.Data | None | ||||||||||||||||||
| schedule: DF.Table[ScheduleItem] | ||||||||||||||||||
| short_description: DF.SmallText | None | ||||||||||||||||||
| show_sponsorship_section: DF.Check | ||||||||||||||||||
| sponsor_deck_attachments: DF.Table[SponsorshipDeckItem] | ||||||||||||||||||
| sponsor_deck_cc: DF.SmallText | None | ||||||||||||||||||
| sponsor_deck_email_template: DF.Link | None | ||||||||||||||||||
|
|
@@ -65,6 +69,7 @@ def validate(self): | |||||||||||||||||
| self.validate_schedule() | ||||||||||||||||||
| self.validate_route() | ||||||||||||||||||
| self.validate_tax_settings() | ||||||||||||||||||
| self.validate_booking_closes_on() | ||||||||||||||||||
|
|
||||||||||||||||||
| def validate_schedule(self): | ||||||||||||||||||
| end_date = self.end_date or self.start_date | ||||||||||||||||||
|
|
@@ -119,6 +124,13 @@ def validate_route(self): | |||||||||||||||||
| if self.is_published and not self.route: | ||||||||||||||||||
| self.route = frappe.website.utils.cleanup_page_name(self.title).replace("_", "-") | ||||||||||||||||||
|
|
||||||||||||||||||
| def validate_booking_closes_on(self): | ||||||||||||||||||
| if not self.booking_closes_on: | ||||||||||||||||||
| return | ||||||||||||||||||
| end_date = self.end_date or self.start_date | ||||||||||||||||||
| if date_diff(self.booking_closes_on, end_date) > 0: | ||||||||||||||||||
| frappe.throw(frappe._("Booking Closes On cannot be after the event end date")) | ||||||||||||||||||
|
|
||||||||||||||||||
| @frappe.whitelist() | ||||||||||||||||||
| def after_insert(self): | ||||||||||||||||||
| self.create_default_records() | ||||||||||||||||||
|
|
@@ -175,3 +187,18 @@ def update_zoom_webinar(self): | |||||||||||||||||
| } | ||||||||||||||||||
| ) | ||||||||||||||||||
| webinar.save() | ||||||||||||||||||
|
|
||||||||||||||||||
| @property | ||||||||||||||||||
| def is_booking_open(self): | ||||||||||||||||||
| """Returns True if booking is currently open for this event.""" | ||||||||||||||||||
| current_date = getdate(today()) | ||||||||||||||||||
|
|
||||||||||||||||||
| # Check custom booking close date first (per-event override) | ||||||||||||||||||
| if self.booking_closes_on: | ||||||||||||||||||
| return current_date <= getdate(self.booking_closes_on) | ||||||||||||||||||
|
|
||||||||||||||||||
| # Fall back to global setting | ||||||||||||||||||
| settings = frappe.get_cached_doc("Buzz Settings") | ||||||||||||||||||
| cutoff_days = settings.allow_booking_before_event_start_days or 0 | ||||||||||||||||||
| cutoff_date = add_days(self.start_date, -cutoff_days) | ||||||||||||||||||
| return current_date <= getdate(cutoff_date) | ||||||||||||||||||
|
Comment on lines
+201
to
+204
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. Guard against negative A negative value would extend booking after the event start, breaking the intended rule. Consider clamping to ✅ Suggested defensive clamp- cutoff_days = settings.allow_booking_before_event_start_days or 0
+ cutoff_days = max(int(settings.allow_booking_before_event_start_days or 0), 0)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,7 @@ class EventBooking(Document): | |
| # end: auto-generated types | ||
|
|
||
| def validate(self): | ||
| self.validate_booking_open() | ||
| self.validate_ticket_availability() | ||
|
Comment on lines
42
to
44
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.
🐛 Fix- if not event.is_booking_open:
+ if not event.is_booking_open():
frappe.throw(frappe._("Bookings are closed for {0}").format(event.title))Also applies to: 293-297 🤖 Prompt for AI Agents |
||
| self.fetch_amounts_from_ticket_types() | ||
| self.set_currency() | ||
|
|
@@ -288,3 +289,9 @@ def apply_coupon_if_applicable(self): | |
| frappe.throw(_("No attendees with eligible ticket type for this coupon")) | ||
|
|
||
| self.total_amount = self.net_amount - self.discount_amount | ||
|
|
||
| def validate_booking_open(self): | ||
| """Check if booking is open for this event.""" | ||
| event = frappe.get_cached_doc("Buzz Event", self.event) | ||
| if not event.is_booking_open: | ||
| frappe.throw(frappe._("Bookings are closed for {0}").format(event.title)) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,21 @@ | |||||||||||||||||
| <div class="w-8"> | ||||||||||||||||||
| <Spinner v-if="eventBookingResource.loading" /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <div> | ||||||||||||||||||
| <!-- Booking closed message --> | ||||||||||||||||||
| <div | ||||||||||||||||||
| v-if="!eventBookingResource.loading && !eventBookingData.bookingAllowed" | ||||||||||||||||||
| class="flex flex-col items-center justify-center py-16" | ||||||||||||||||||
| > | ||||||||||||||||||
| <LucideCalendarX2 class="w-16 h-16 text-gray-400 mb-4" /> | ||||||||||||||||||
| <h2 class="text-xl font-semibold text-gray-700 mb-2"> | ||||||||||||||||||
| {{ __("Bookings Closed") }} | ||||||||||||||||||
| </h2> | ||||||||||||||||||
| <p class="text-gray-500 text-center"> | ||||||||||||||||||
| {{ __("Bookings are no longer available for this event.") }} | ||||||||||||||||||
| </p> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <!-- Booking form --> | ||||||||||||||||||
| <div v-else> | ||||||||||||||||||
| <BookingForm | ||||||||||||||||||
| v-if="eventBookingData.availableAddOns && eventBookingData.availableTicketTypes" | ||||||||||||||||||
| :availableAddOns="eventBookingData.availableAddOns" | ||||||||||||||||||
|
|
@@ -22,6 +36,7 @@ | |||||||||||||||||
| import { reactive } from "vue"; | ||||||||||||||||||
| import BookingForm from "../components/BookingForm.vue"; | ||||||||||||||||||
| import { Spinner, createResource } from "frappe-ui"; | ||||||||||||||||||
| import LucideCalendarX2 from "~icons/lucide/calendar-x-2"; | ||||||||||||||||||
|
|
||||||||||||||||||
| const eventBookingData = reactive({ | ||||||||||||||||||
| availableAddOns: null, | ||||||||||||||||||
|
|
@@ -30,6 +45,7 @@ const eventBookingData = reactive({ | |||||||||||||||||
| eventDetails: null, | ||||||||||||||||||
| customFields: null, | ||||||||||||||||||
| paymentGateways: [], | ||||||||||||||||||
| bookingAllowed: true, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| const props = defineProps({ | ||||||||||||||||||
|
|
@@ -56,6 +72,7 @@ const eventBookingResource = createResource({ | |||||||||||||||||
| eventBookingData.eventDetails = data.event_details || {}; | ||||||||||||||||||
| eventBookingData.customFields = data.custom_fields || []; | ||||||||||||||||||
| eventBookingData.paymentGateways = data.payment_gateways || []; | ||||||||||||||||||
| eventBookingData.bookingAllowed = data.booking_allowed; | ||||||||||||||||||
| }, | ||||||||||||||||||
|
Comment on lines
73
to
76
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. Guard against missing If the API omits the field or returns null, the UI flips to “Bookings Closed.” A nullish fallback keeps the default behavior intact. 🐛 Suggested fix- eventBookingData.bookingAllowed = data.booking_allowed;
+ eventBookingData.bookingAllowed = data.booking_allowed ?? true;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| onError: (error) => { | ||||||||||||||||||
| if (error.message.includes("DoesNotExistError")) { | ||||||||||||||||||
|
|
||||||||||||||||||
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.
Call
is_booking_open()instead of passing the method object.event_doc.is_booking_openis a method on Buzz Event; assigning it directly will either serialize incorrectly or always truthy. This prevents proper booking closure and can break JSON serialization.🐛 Fix
📝 Committable suggestion
🤖 Prompt for AI Agents