diff --git a/buzz/api.py b/buzz/api.py index 65c7a3ec..9dfd603b 100644 --- a/buzz/api.py +++ b/buzz/api.py @@ -120,7 +120,7 @@ def can_request_cancellation(event_id: str | int) -> dict: def get_event_booking_data(event_route: str) -> dict: data = frappe._dict() event_doc = frappe.get_cached_doc("Buzz Event", {"route": event_route}) - + data.booking_allowed = event_doc.is_booking_open # Ticket Types available_ticket_types = [] published_ticket_types = frappe.db.get_all( diff --git a/buzz/events/doctype/buzz_event/buzz_event.json b/buzz/events/doctype/buzz_event/buzz_event.json index 51cbdb14..655825ff 100644 --- a/buzz/events/doctype/buzz_event/buzz_event.json +++ b/buzz/events/doctype/buzz_event/buzz_event.json @@ -21,6 +21,7 @@ "column_break_cjby", "end_date", "end_time", + "booking_closes_on", "section_break_zukm", "short_description", "about", @@ -391,6 +392,11 @@ "fieldname": "card_image", "fieldtype": "Attach Image", "label": "Card Image" + }, + { + "fieldname": "booking_closes_on", + "fieldtype": "Date", + "label": "Booking Closes On" } ], "grid_page_length": 50, @@ -463,7 +469,7 @@ "link_fieldname": "event" } ], - "modified": "2026-01-14 11:13:04.386770", + "modified": "2026-01-21 20:36:44.002422", "modified_by": "Administrator", "module": "Events", "name": "Buzz Event", diff --git a/buzz/events/doctype/buzz_event/buzz_event.py b/buzz/events/doctype/buzz_event/buzz_event.py index 409ab811..cd324410 100644 --- a/buzz/events/doctype/buzz_event/buzz_event.py +++ b/buzz/events/doctype/buzz_event/buzz_event.py @@ -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) diff --git a/buzz/events/doctype/buzz_settings/buzz_settings.json b/buzz/events/doctype/buzz_settings/buzz_settings.json index 83cdc042..c06e7bf9 100644 --- a/buzz/events/doctype/buzz_settings/buzz_settings.json +++ b/buzz/events/doctype/buzz_settings/buzz_settings.json @@ -13,6 +13,8 @@ "allow_add_ons_change_before_event_start_days", "column_break_hagy", "allow_ticket_cancellation_request_before_event_start_days", + "column_break_hyfc", + "allow_booking_before_event_start_days", "communications_tab", "ticketing_emails_section", "default_ticket_email_template", @@ -142,13 +144,25 @@ "fieldname": "custom_fields_go_after_this", "fieldtype": "HTML", "label": "Custom Fields Go After This" + }, + { + "fieldname": "column_break_hyfc", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Stop accepting bookings X days before event starts. Set to 0 to allow booking until event start date. Can be overridden per event.", + "fieldname": "allow_booking_before_event_start_days", + "fieldtype": "Int", + "label": "Allow Booking Before Event Start (Days)", + "non_negative": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-13 22:34:24.493305", + "modified": "2026-01-21 21:02:08.339821", "modified_by": "Administrator", "module": "Events", "name": "Buzz Settings", diff --git a/buzz/events/doctype/buzz_settings/buzz_settings.py b/buzz/events/doctype/buzz_settings/buzz_settings.py index d5031a0b..abad7578 100644 --- a/buzz/events/doctype/buzz_settings/buzz_settings.py +++ b/buzz/events/doctype/buzz_settings/buzz_settings.py @@ -16,12 +16,13 @@ class BuzzSettings(Document): from frappe.types import DF allow_add_ons_change_before_event_start_days: DF.Int + allow_booking_before_event_start_days: DF.Int allow_ticket_cancellation_request_before_event_start_days: DF.Int allow_transfer_ticket_before_event_start_days: DF.Int auto_send_pitch_deck: DF.Check default_sponsor_deck_cc: DF.SmallText | None default_sponsor_deck_email_template: DF.Link | None - default_sponsor_deck_reply_to: DF.Data + default_sponsor_deck_reply_to: DF.Data | None default_ticket_email_template: DF.Link | None support_email: DF.Data | None # end: auto-generated types diff --git a/buzz/ticketing/doctype/event_booking/event_booking.py b/buzz/ticketing/doctype/event_booking/event_booking.py index 9afbc69e..3c97944d 100644 --- a/buzz/ticketing/doctype/event_booking/event_booking.py +++ b/buzz/ticketing/doctype/event_booking/event_booking.py @@ -40,6 +40,7 @@ class EventBooking(Document): # end: auto-generated types def validate(self): + self.validate_booking_open() self.validate_ticket_availability() 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)) diff --git a/dashboard/src/pages/BookTickets.vue b/dashboard/src/pages/BookTickets.vue index 3f9dcd27..89b34f20 100644 --- a/dashboard/src/pages/BookTickets.vue +++ b/dashboard/src/pages/BookTickets.vue @@ -3,7 +3,21 @@
+ {{ __("Bookings are no longer available for this event.") }} +
+