Initialize fork and rebrand app to event_manager
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
__version__ = "0.0.1"
|
||||
|
||||
import os
|
||||
|
||||
if os.environ.get("CI"):
|
||||
import frappe
|
||||
from frappe.tests.utils import toggle_test_mode
|
||||
|
||||
toggle_test_mode(True)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
import frappe
|
||||
from frappe.utils import cint, md_to_html
|
||||
from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
|
||||
def get_login_context(redirect_to: str | None = None):
|
||||
context = {
|
||||
"disable_signup": frappe.get_website_settings("disable_signup"),
|
||||
"disable_user_pass_login": frappe.get_system_settings("disable_user_pass_login"),
|
||||
"login_with_email_link": frappe.get_system_settings("login_with_email_link"),
|
||||
"login_banner": md_to_html(raw_banner)
|
||||
if (raw_banner := frappe.db.get_single_value("Pohodex Event Manager Settings", "login_banner"))
|
||||
else None,
|
||||
"provider_logins": [],
|
||||
}
|
||||
|
||||
if not redirect_to:
|
||||
redirect_to = frappe.utils.get_url("/dashboard")
|
||||
|
||||
social_login_keys = frappe.get_all(
|
||||
"Social Login Key",
|
||||
filters={"enable_social_login": 1},
|
||||
fields=["name", "provider_name", "icon", "client_id", "base_url"],
|
||||
)
|
||||
|
||||
for provider in social_login_keys:
|
||||
if provider.client_id and provider.base_url and get_oauth_keys(provider.name):
|
||||
context["provider_logins"].append(
|
||||
{
|
||||
"name": provider.name,
|
||||
"provider_name": provider.provider_name,
|
||||
"icon": provider.icon or "",
|
||||
"auth_url": get_oauth2_authorize_url(provider.name, redirect_to),
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
@@ -0,0 +1,344 @@
|
||||
from functools import lru_cache
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.geo.country_info import get_all as get_all_countries
|
||||
from frappe.model import DEFAULT_FIELDS, display_fieldtypes
|
||||
from frappe.utils import get_datetime, now_datetime, today
|
||||
from frappe.utils.data import cstr, sbool
|
||||
|
||||
LAYOUT_FIELDTYPES = set(display_fieldtypes)
|
||||
|
||||
EVENT_PROPOSAL_EXCLUDE_FIELDS = DEFAULT_FIELDS | {
|
||||
"naming_series",
|
||||
"amended_from",
|
||||
"host",
|
||||
"host_company",
|
||||
"host_company_logo",
|
||||
"additional_notes",
|
||||
"status",
|
||||
"submitted_by",
|
||||
}
|
||||
|
||||
STANDARD_EXCLUDE_FIELDS = DEFAULT_FIELDS | {
|
||||
"additional_fields",
|
||||
"event",
|
||||
"section_break_additional",
|
||||
"submitted_by",
|
||||
"status",
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
|
||||
def get_dial_codes() -> list:
|
||||
return _get_dial_codes()
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _get_dial_codes() -> list:
|
||||
data = get_all_countries()
|
||||
codes = []
|
||||
seen = set()
|
||||
for country in sorted(data):
|
||||
info = data[country]
|
||||
isd = info.get("isd", "")
|
||||
code = (info.get("code") or "").upper()
|
||||
if isd and isd not in seen:
|
||||
codes.append({"country": country, "code": code, "dial_code": isd})
|
||||
seen.add(isd)
|
||||
return codes
|
||||
|
||||
|
||||
def get_form_fields(doctype: str, exclude_fields: set) -> list:
|
||||
meta = frappe.get_meta(doctype)
|
||||
fields = []
|
||||
for df in meta.fields:
|
||||
if df.fieldname in exclude_fields:
|
||||
continue
|
||||
if df.fieldtype in LAYOUT_FIELDTYPES:
|
||||
continue
|
||||
if df.hidden:
|
||||
continue
|
||||
if df.read_only:
|
||||
continue
|
||||
default_value = df.default
|
||||
if default_value:
|
||||
if default_value == "Today":
|
||||
default_value = today()
|
||||
elif default_value == "Now":
|
||||
default_value = cstr(now_datetime())
|
||||
elif default_value.startswith("eval:") or default_value.startswith("%"):
|
||||
default_value = None
|
||||
|
||||
field_data = {
|
||||
"fieldname": df.fieldname,
|
||||
"fieldtype": df.fieldtype,
|
||||
"label": df.label or df.fieldname,
|
||||
"options": df.options,
|
||||
"reqd": df.reqd,
|
||||
"default": default_value,
|
||||
"description": df.description,
|
||||
}
|
||||
if df.fieldtype == "Link" and df.options:
|
||||
link_values = frappe.get_all(
|
||||
df.options,
|
||||
fields=["name"],
|
||||
limit_page_length=0,
|
||||
order_by="name asc",
|
||||
)
|
||||
field_data["link_options"] = [d.name for d in link_values]
|
||||
if df.fieldtype == "Table" and df.options:
|
||||
child_meta = frappe.get_meta(df.options)
|
||||
child_fields = []
|
||||
for child_df in child_meta.fields:
|
||||
if child_df.fieldtype in LAYOUT_FIELDTYPES:
|
||||
continue
|
||||
if child_df.hidden:
|
||||
continue
|
||||
child_fields.append(
|
||||
{
|
||||
"fieldname": child_df.fieldname,
|
||||
"fieldtype": child_df.fieldtype,
|
||||
"label": child_df.label or child_df.fieldname,
|
||||
"options": child_df.options,
|
||||
"reqd": child_df.reqd,
|
||||
}
|
||||
)
|
||||
field_data["child_fields"] = child_fields
|
||||
fields.append(field_data)
|
||||
return fields
|
||||
|
||||
|
||||
def validate_custom_form(event_route: str, form_route: str):
|
||||
event_name = frappe.get_cached_value("Pohodex Event Manager Event", {"route": event_route}, "name")
|
||||
if not event_name:
|
||||
frappe.throw(_("Event not found"), frappe.DoesNotExistError)
|
||||
event_doc = frappe.get_cached_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
if not event_doc.is_published:
|
||||
frappe.throw(_("Event not found"), frappe.DoesNotExistError)
|
||||
|
||||
form_row = None
|
||||
for row in event_doc.custom_forms:
|
||||
if row.route == form_route and row.publish:
|
||||
form_row = row
|
||||
break
|
||||
|
||||
if not form_row:
|
||||
frappe.throw(_("This form is not available for this event"), frappe.DoesNotExistError)
|
||||
|
||||
return event_doc, form_row
|
||||
|
||||
|
||||
def get_auto_set_fields(form_doctype: str):
|
||||
meta = frappe.get_meta(form_doctype)
|
||||
auto_set = {}
|
||||
for df in meta.fields:
|
||||
if df.fieldname == "event" and df.fieldtype == "Link":
|
||||
auto_set["event"] = "from_route"
|
||||
elif df.fieldname == "submitted_by" and df.fieldtype == "Link":
|
||||
auto_set["submitted_by"] = "session_user"
|
||||
return auto_set
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
|
||||
def get_custom_form_data(event_route: str, form_route: str) -> dict:
|
||||
event_doc, form_row = validate_custom_form(event_route, form_route)
|
||||
form_doctype = form_row.form_doctype
|
||||
allow_guest_submission = sbool(event_doc.allow_guest_booking)
|
||||
|
||||
if not allow_guest_submission and frappe.session.user == "Guest":
|
||||
frappe.throw(_("Please log in to submit this form"), frappe.AuthenticationError)
|
||||
|
||||
event_data = {
|
||||
"name": event_doc.name,
|
||||
"title": event_doc.title,
|
||||
"route": event_doc.route,
|
||||
"banner_image": event_doc.banner_image,
|
||||
"start_date": event_doc.start_date,
|
||||
"end_date": event_doc.end_date,
|
||||
"start_time": event_doc.start_time,
|
||||
"end_time": event_doc.end_time,
|
||||
"time_zone": event_doc.time_zone,
|
||||
"venue": event_doc.venue,
|
||||
"medium": event_doc.medium,
|
||||
"short_description": event_doc.short_description,
|
||||
}
|
||||
|
||||
closed = False
|
||||
if form_row.auto_close_at and get_datetime(form_row.auto_close_at) < now_datetime():
|
||||
closed = True
|
||||
|
||||
if closed:
|
||||
return {
|
||||
"form_fields": [],
|
||||
"custom_fields": [],
|
||||
"form_title": form_doctype,
|
||||
"event": event_data,
|
||||
"closed": True,
|
||||
"closed_title": form_row.closed_title or _("Submissions Closed"),
|
||||
"closed_message": form_row.closed_message or _("Submissions for this form have closed."),
|
||||
"success_title": "",
|
||||
"success_message": "",
|
||||
}
|
||||
|
||||
auto_set = get_auto_set_fields(form_doctype)
|
||||
exclude_fields = STANDARD_EXCLUDE_FIELDS | set(auto_set.keys())
|
||||
form_fields = get_form_fields(form_doctype, exclude_fields)
|
||||
|
||||
form_doctype_meta = frappe.get_meta(form_doctype)
|
||||
custom_fields = []
|
||||
if form_doctype_meta.has_field("additional_fields"):
|
||||
custom_fields = frappe.get_all(
|
||||
"Pohodex Event Manager Custom Field",
|
||||
filters={
|
||||
"event": event_doc.name,
|
||||
"applied_to": "Custom Form",
|
||||
"custom_form_doctype": form_doctype,
|
||||
"enabled": 1,
|
||||
},
|
||||
fields=[
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"mandatory",
|
||||
"placeholder",
|
||||
"default_value",
|
||||
"order",
|
||||
],
|
||||
order_by="order asc",
|
||||
)
|
||||
|
||||
return {
|
||||
"form_fields": form_fields,
|
||||
"custom_fields": custom_fields,
|
||||
"form_title": form_doctype_meta.name,
|
||||
"event": event_data,
|
||||
"closed": False,
|
||||
"closed_title": form_row.closed_title or _("Submissions Closed"),
|
||||
"closed_message": form_row.closed_message or _("Submissions for this form have closed."),
|
||||
"success_title": form_row.success_title or _("Thank you!"),
|
||||
"success_message": form_row.success_message or "",
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
|
||||
def submit_custom_form(
|
||||
event_route: str, form_route: str, data: dict | str, custom_fields_data: dict | str | None = None
|
||||
) -> None:
|
||||
event_doc, form_row = validate_custom_form(event_route, form_route)
|
||||
form_doctype = form_row.form_doctype
|
||||
|
||||
if not event_doc.allow_guest_booking and frappe.session.user == "Guest":
|
||||
frappe.throw(_("Please login to submit this form"), frappe.AuthenticationError)
|
||||
|
||||
if form_row.auto_close_at and get_datetime(form_row.auto_close_at) < now_datetime():
|
||||
frappe.throw(_("Submissions for this form have closed."))
|
||||
|
||||
data = frappe.parse_json(data) or {}
|
||||
custom_fields_data = frappe.parse_json(custom_fields_data) or {}
|
||||
|
||||
auto_set = get_auto_set_fields(form_doctype)
|
||||
exclude_fields = STANDARD_EXCLUDE_FIELDS | set(auto_set.keys())
|
||||
|
||||
doc_data = {"doctype": form_doctype}
|
||||
|
||||
for field, source in auto_set.items():
|
||||
if source == "from_route":
|
||||
doc_data[field] = event_doc.name
|
||||
elif source == "session_user":
|
||||
doc_data[field] = frappe.session.user
|
||||
|
||||
allowed_fieldnames = {f["fieldname"] for f in get_form_fields(form_doctype, exclude_fields)}
|
||||
for fieldname, value in data.items():
|
||||
if fieldname in allowed_fieldnames:
|
||||
doc_data[fieldname] = value
|
||||
|
||||
meta = frappe.get_meta(form_doctype)
|
||||
for df in meta.fields:
|
||||
if df.fieldtype == "Table" and df.fieldname not in exclude_fields:
|
||||
if df.fieldname in data and isinstance(data[df.fieldname], list):
|
||||
doc_data[df.fieldname] = data[df.fieldname]
|
||||
|
||||
doc = frappe.get_doc(doc_data)
|
||||
|
||||
if custom_fields_data and meta.has_field("additional_fields"):
|
||||
custom_field_definitions = frappe.get_all(
|
||||
"Pohodex Event Manager Custom Field",
|
||||
filters={
|
||||
"event": event_doc.name,
|
||||
"applied_to": "Custom Form",
|
||||
"custom_form_doctype": form_doctype,
|
||||
"enabled": 1,
|
||||
},
|
||||
fields=["fieldname", "label", "fieldtype"],
|
||||
)
|
||||
allowed_custom = {cf["fieldname"]: cf for cf in custom_field_definitions}
|
||||
|
||||
for fieldname, value in custom_fields_data.items():
|
||||
if fieldname in allowed_custom and value not in (None, ""):
|
||||
cf = allowed_custom[fieldname]
|
||||
doc.append(
|
||||
"additional_fields",
|
||||
{
|
||||
"label": cf["label"],
|
||||
"fieldname": fieldname,
|
||||
"fieldtype": cf["fieldtype"],
|
||||
"value": cstr(value),
|
||||
},
|
||||
)
|
||||
|
||||
doc.insert(ignore_permissions=True)
|
||||
|
||||
|
||||
def validate_event_proposal_settings():
|
||||
settings = frappe.get_cached_doc("Pohodex Event Manager Settings")
|
||||
if not settings.accept_event_proposals:
|
||||
frappe.throw(_("Event Proposals are not being accepted"), frappe.DoesNotExistError)
|
||||
|
||||
if not settings.allow_guest_event_proposals and frappe.session.user == "Guest":
|
||||
frappe.throw(_("Please log in to submit a proposal"), frappe.AuthenticationError)
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
|
||||
def get_event_proposal_form_data() -> dict:
|
||||
settings = validate_event_proposal_settings()
|
||||
form_fields = get_form_fields("Event Proposal", EVENT_PROPOSAL_EXCLUDE_FIELDS)
|
||||
|
||||
return {
|
||||
"form_fields": form_fields,
|
||||
"form_title": _("Event Proposal"),
|
||||
"banner_title": settings.event_proposal_banner_title or _("Propose an Event"),
|
||||
"success_title": settings.event_proposal_success_title or _("Thank you!"),
|
||||
"success_message": settings.event_proposal_success_message or "",
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
|
||||
def submit_event_proposal(data: dict | str) -> None:
|
||||
validate_event_proposal_settings()
|
||||
|
||||
data = frappe.parse_json(data) or {}
|
||||
|
||||
doc_data = {"doctype": "Event Proposal"}
|
||||
|
||||
if frappe.session.user != "Guest":
|
||||
doc_data["submitted_by"] = frappe.session.user
|
||||
|
||||
allowed_fieldnames = {
|
||||
f["fieldname"] for f in get_form_fields("Event Proposal", EVENT_PROPOSAL_EXCLUDE_FIELDS)
|
||||
}
|
||||
for fieldname, value in data.items():
|
||||
if fieldname in allowed_fieldnames:
|
||||
doc_data[fieldname] = value
|
||||
|
||||
meta = frappe.get_meta("Event Proposal")
|
||||
for df in meta.fields:
|
||||
if df.fieldtype == "Table" and df.fieldname not in EVENT_PROPOSAL_EXCLUDE_FIELDS:
|
||||
if df.fieldname in data and isinstance(data[df.fieldname], list):
|
||||
doc_data[df.fieldname] = data[df.fieldname]
|
||||
|
||||
frappe.get_doc(doc_data).insert(ignore_permissions=True)
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Pohodex Event Manager Custom Field", {
|
||||
refresh(frm) {
|
||||
frm.set_query("custom_form_doctype", function () {
|
||||
return {
|
||||
filters: {
|
||||
istable: 0,
|
||||
issingle: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-11-01 11:29:38.327158",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"event",
|
||||
"label",
|
||||
"fieldname",
|
||||
"mandatory",
|
||||
"placeholder",
|
||||
"default_value",
|
||||
"column_break_fpgn",
|
||||
"applied_to",
|
||||
"custom_form_doctype",
|
||||
"offline_payment_method",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"order"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fpgn",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"options": "Data\nCheck\nSmall Text\nPhone\nEmail\nSelect\nDate\nNumber\nMulti Select\nRating\nAttach\nAttach Image",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Options"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled?"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "mandatory",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mandatory?"
|
||||
},
|
||||
{
|
||||
"fieldname": "placeholder",
|
||||
"fieldtype": "Data",
|
||||
"label": "Placeholder"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "order",
|
||||
"fieldtype": "Int",
|
||||
"label": "Order",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "default_value",
|
||||
"fieldtype": "Data",
|
||||
"label": "Default Value"
|
||||
},
|
||||
{
|
||||
"default": "Booking",
|
||||
"fieldname": "applied_to",
|
||||
"fieldtype": "Select",
|
||||
"label": "Applied To",
|
||||
"options": "Booking\nTicket\nOffline Payment Form\nCustom Form"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.applied_to === 'Custom Form'",
|
||||
"fieldname": "custom_form_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Custom Form DocType",
|
||||
"mandatory_depends_on": "eval:doc.applied_to === 'Custom Form'",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.applied_to === 'Offline Payment Form'",
|
||||
"fieldname": "offline_payment_method",
|
||||
"fieldtype": "Link",
|
||||
"label": "Offline Payment Method",
|
||||
"mandatory_depends_on": "eval:doc.applied_to === 'Offline Payment Form'",
|
||||
"options": "Offline Payment Method"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-06 15:26:27.195623",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Pohodex Event Manager",
|
||||
"name": "Pohodex Event Manager Custom Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "label"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class BuzzCustomField(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
applied_to: DF.Literal["Booking", "Ticket", "Offline Payment Form"]
|
||||
default_value: DF.Data | None
|
||||
enabled: DF.Check
|
||||
event: DF.Link
|
||||
fieldname: DF.Data | None
|
||||
fieldtype: DF.Literal[
|
||||
"Data", "Check", "Small Text", "Phone", "Email", "Select", "Date", "Number", "Multi Select"
|
||||
]
|
||||
label: DF.Data
|
||||
mandatory: DF.Check
|
||||
options: DF.SmallText | None
|
||||
order: DF.Int
|
||||
placeholder: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
if not self.fieldname:
|
||||
self.fieldname = frappe.scrub(self.label)
|
||||
|
||||
def on_update(self):
|
||||
if self.applied_to == "Custom Form" and self.custom_form_doctype:
|
||||
self.create_additional_fields_if_missing()
|
||||
|
||||
def create_additional_fields_if_missing(self):
|
||||
meta = frappe.get_meta(self.custom_form_doctype)
|
||||
|
||||
if meta.has_field("additional_fields"):
|
||||
return
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Custom Field",
|
||||
"dt": self.custom_form_doctype,
|
||||
"fieldname": "section_break_additional",
|
||||
"label": "Additional Fields",
|
||||
"fieldtype": "Section Break",
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Custom Field",
|
||||
"dt": self.custom_form_doctype,
|
||||
"fieldname": "additional_fields",
|
||||
"label": "Additional Fields",
|
||||
"fieldtype": "Table",
|
||||
"options": "Additional Field",
|
||||
"insert_after": "section_break_additional",
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
frappe.msgprint(
|
||||
_("Added 'Additional Fields' table to {0}").format(self.custom_form_doctype),
|
||||
alert=True,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestBuzzCustomField(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for BuzzCustomField.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
frappe.ready(function () {
|
||||
// bind events here
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"accept_payment": 0,
|
||||
"allow_comments": 0,
|
||||
"allow_delete": 0,
|
||||
"allow_edit": 0,
|
||||
"allow_incomplete": 0,
|
||||
"allow_multiple": 1,
|
||||
"allow_print": 0,
|
||||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"anonymous": 0,
|
||||
"apply_document_permissions": 0,
|
||||
"button_label": "Submit",
|
||||
"condition_json": "[]",
|
||||
"creation": "2025-10-09 18:53:54.552643",
|
||||
"currency": "INR",
|
||||
"doc_type": "Sponsorship Enquiry",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"hide_footer": 1,
|
||||
"hide_navbar": 1,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 5,
|
||||
"modified": "2025-10-30 16:52:36.708858",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Pohodex Event Manager",
|
||||
"name": "apply-for-sponsorship",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "apply-for-sponsorship",
|
||||
"show_attachments": 0,
|
||||
"show_list": 1,
|
||||
"show_sidebar": 0,
|
||||
"success_url": "/dashboard/account/sponsorships",
|
||||
"title": "Apply for Sponsorship",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Event",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "company_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Company Name",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "company_logo",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 0,
|
||||
"label": "Company Logo",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "column_break_fhgg",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Website",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "URL",
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "country",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Country",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Country",
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Phone",
|
||||
"hidden": 0,
|
||||
"label": "Phone Number",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def get_context(context):
|
||||
# do your magic here
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
frappe.ready(function () {
|
||||
// bind events here
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"accept_payment": 0,
|
||||
"allow_comments": 0,
|
||||
"allow_delete": 1,
|
||||
"allow_edit": 0,
|
||||
"allow_incomplete": 0,
|
||||
"allow_multiple": 1,
|
||||
"allow_print": 0,
|
||||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"anonymous": 0,
|
||||
"apply_document_permissions": 0,
|
||||
"button_label": "Submit",
|
||||
"condition_json": "[]",
|
||||
"creation": "2025-10-09 18:57:22.187906",
|
||||
"currency": "INR",
|
||||
"doc_type": "Talk Proposal",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Form",
|
||||
"hide_footer": 1,
|
||||
"hide_navbar": 1,
|
||||
"idx": 0,
|
||||
"introduction_text": "<div class=\"ql-editor read-mode\"><p>Apply for giving a talk at the event.</p></div>",
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2025-10-28 17:42:31.607629",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Pohodex Event Manager",
|
||||
"name": "propose-a-talk",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "propose-a-talk",
|
||||
"show_attachments": 0,
|
||||
"show_list": 1,
|
||||
"show_sidebar": 0,
|
||||
"title": "Propose a Talk",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Title of your talk",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "column_break_esac",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Event",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "section_break_yqfb",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"label": "Description",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "speakers",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Speakers",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Proposal Speaker",
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Phone",
|
||||
"hidden": 0,
|
||||
"label": "Phone Number",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"precision": "",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def get_context(context):
|
||||
# do your magic here
|
||||
pass
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"app": "Pohodex Event Manager",
|
||||
"charts": [],
|
||||
"content": "[]",
|
||||
"creation": "2026-03-05 19:10:56.484378",
|
||||
"custom_blocks": [],
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"indicator_color": "green",
|
||||
"is_hidden": 0,
|
||||
"label": "Pohodex Event Manager",
|
||||
"link_type": "DocType",
|
||||
"links": [],
|
||||
"modified": "2026-03-05 19:24:18.378896",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Pohodex Event Manager",
|
||||
"name": "Pohodex Event Manager",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"roles": [],
|
||||
"sequence_id": 0.0,
|
||||
"shortcuts": [],
|
||||
"title": "Pohodex Event Manager",
|
||||
"type": "Workspace"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2026, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Pohodex Event Manager Campaign", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "prompt",
|
||||
"creation": "2026-01-23 14:27:04.947771",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"title",
|
||||
"event",
|
||||
"qr_code",
|
||||
"column_break_onhk",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"description": "Will be shown to the user",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_onhk",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "qr_code",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "QR Code",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled?"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"image_field": "qr_code",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-23 17:23:01.011762",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Pohodex Event Manager Marketing",
|
||||
"name": "Pohodex Event Manager Campaign",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright (c) 2026, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
from event_manager.utils import generate_qr_code_file, is_app_installed
|
||||
|
||||
|
||||
class BuzzCampaign(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
description: DF.MarkdownEditor
|
||||
enabled: DF.Check
|
||||
event: DF.Link | None
|
||||
qr_code: DF.AttachImage | None
|
||||
title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def before_save(self):
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
previous = self.get_doc_before_save()
|
||||
name_changed = previous and previous.name != self.name
|
||||
if not self.qr_code or name_changed:
|
||||
self.generate_qr_code()
|
||||
|
||||
def validate(self):
|
||||
self.validate_crm_installed()
|
||||
|
||||
def validate_crm_installed(self):
|
||||
if self.enabled and not is_app_installed("crm"):
|
||||
frappe.throw(_("Please install Frappe CRM to use campaigns feature"))
|
||||
|
||||
def generate_qr_code(self):
|
||||
register_url = f"{frappe.utils.get_url()}/dashboard/register-interest/{self.name}"
|
||||
self.qr_code = generate_qr_code_file(
|
||||
doc=self,
|
||||
data=register_url,
|
||||
field_name="qr_code",
|
||||
file_prefix="campaign-qr-code",
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2026, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestBuzzCampaign(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for BuzzCampaign.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"app": "event_manager",
|
||||
"bg_color": "blue",
|
||||
"creation": "2026-03-05 19:10:56.544723",
|
||||
"docstatus": 0,
|
||||
"doctype": "Desktop Icon",
|
||||
"hidden": 0,
|
||||
"icon_type": "Link",
|
||||
"idx": 0,
|
||||
"label": "Pohodex Event Manager",
|
||||
"link_to": "Pohodex Event Manager",
|
||||
"link_type": "Workspace Sidebar",
|
||||
"modified": "2026-03-05 19:32:36.529508",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Pohodex Event Manager",
|
||||
"owner": "Administrator",
|
||||
"restrict_removal": 0,
|
||||
"roles": [],
|
||||
"sidebar": "Pohodex Event Manager",
|
||||
"standard": 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on("Additional Event Page", {
|
||||
refresh(frm) {
|
||||
if (frm.doc.is_published) {
|
||||
frappe.db.get_value("Pohodex Event Manager Event", frm.doc.event, "route").then(({ message }) => {
|
||||
frm.add_web_link(`/events/${message.route}/${frm.doc.route}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-09-25 18:54:07.782051",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"route",
|
||||
"column_break_gagp",
|
||||
"event",
|
||||
"is_published",
|
||||
"section_break_pifr",
|
||||
"content"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Content",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"label": "Route"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gagp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_pifr",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_published",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Published?"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-01 16:45:14.211143",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Additional Event Page",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AdditionalEventPage(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
content: DF.TextEditor
|
||||
event: DF.Link
|
||||
is_published: DF.Check
|
||||
route: DF.Data | None
|
||||
title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_route()
|
||||
self.validate_duplicate()
|
||||
|
||||
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_duplicate(self):
|
||||
if not self.route:
|
||||
return
|
||||
|
||||
if frappe.db.exists(
|
||||
"Additional Event Page",
|
||||
{"route": self.route, "event": self.event, "name": ["!=", self.name]},
|
||||
):
|
||||
frappe.throw(
|
||||
frappe._("An Additional Event Page with the same route already exists for this event.")
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestAdditionalEventPage(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for AdditionalEventPage.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,527 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
const FIELD_LABELS = {
|
||||
category: __("Category"),
|
||||
host: __("Host"),
|
||||
banner_image: __("Banner Image"),
|
||||
short_description: __("Short Description"),
|
||||
about: __("About"),
|
||||
medium: __("Medium"),
|
||||
venue: __("Venue"),
|
||||
allow_guest_booking: __("Allow Guest Booking"),
|
||||
guest_verification_method: __("Guest Verification Method"),
|
||||
time_zone: __("Time Zone"),
|
||||
send_ticket_email: __("Send Ticket Email"),
|
||||
apply_tax: __("Tax Settings"),
|
||||
tax_label: __("Tax Label"),
|
||||
tax_percentage: __("Tax Percentage"),
|
||||
ticket_email_template: __("Ticket Email Template"),
|
||||
ticket_print_format: __("Ticket Print Format"),
|
||||
auto_send_pitch_deck: __("Auto Send Pitch Deck"),
|
||||
sponsor_deck_email_template: __("Sponsor Deck Email Template"),
|
||||
sponsor_deck_reply_to: __("Sponsor Deck Reply To"),
|
||||
sponsor_deck_cc: __("Sponsor Deck CC"),
|
||||
sponsor_deck_attachments: __("Sponsor Deck Attachments"),
|
||||
payment_gateways: __("Payment Gateways"),
|
||||
ticket_types: __("Ticket Types"),
|
||||
add_ons: __("Add-ons"),
|
||||
custom_fields: __("Custom Fields"),
|
||||
};
|
||||
|
||||
function get_field_label(field) {
|
||||
return FIELD_LABELS[field] || field;
|
||||
}
|
||||
|
||||
function render_save_template_field_group(fields, doc) {
|
||||
let html = "";
|
||||
for (let field of fields) {
|
||||
let value = doc[field];
|
||||
let has_value = value !== null && value !== undefined && value !== "" && value !== 0;
|
||||
if (Array.isArray(value)) {
|
||||
has_value = value.length > 0;
|
||||
}
|
||||
let label = get_field_label(field);
|
||||
|
||||
html += `
|
||||
<div class="col-md-6 mb-2">
|
||||
<label class="d-flex align-items-center">
|
||||
<input type="checkbox" class="template-option mr-2" data-option="${field}" ${
|
||||
has_value ? "checked" : "disabled"
|
||||
}>
|
||||
${label}
|
||||
${!has_value ? '<span class="text-muted ml-1">(' + __("Not set") + ")</span>" : ""}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function render_save_template_options(dialog, frm) {
|
||||
let html = "";
|
||||
let doc = frm.doc;
|
||||
|
||||
let buttons_html = `
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-default btn-xs select-all-btn">${__("Select All")}</button>
|
||||
<button class="btn btn-default btn-xs unselect-all-btn">${__("Unselect All")}</button>
|
||||
</div>
|
||||
`;
|
||||
dialog.get_field("select_buttons").$wrapper.html(buttons_html);
|
||||
|
||||
// Event Details
|
||||
html += '<div class="template-section mt-3">';
|
||||
html += `<h6 class="text-muted">${__("Event Details")}</h6>`;
|
||||
html += '<div class="row">';
|
||||
html += render_save_template_field_group(
|
||||
[
|
||||
"category",
|
||||
"host",
|
||||
"banner_image",
|
||||
"short_description",
|
||||
"about",
|
||||
"medium",
|
||||
"venue",
|
||||
"allow_guest_booking",
|
||||
"guest_verification_method",
|
||||
"time_zone",
|
||||
],
|
||||
doc
|
||||
);
|
||||
html += "</div></div>";
|
||||
|
||||
// Ticketing Settings
|
||||
html += '<div class="template-section mt-3">';
|
||||
html += `<h6 class="text-muted">${__("Ticketing Settings")}</h6>`;
|
||||
html += '<div class="row">';
|
||||
html += render_save_template_field_group(
|
||||
[
|
||||
"send_ticket_email",
|
||||
"apply_tax",
|
||||
"tax_label",
|
||||
"tax_percentage",
|
||||
"ticket_email_template",
|
||||
"ticket_print_format",
|
||||
],
|
||||
doc
|
||||
);
|
||||
html += "</div></div>";
|
||||
|
||||
// Sponsorship Settings
|
||||
html += '<div class="template-section mt-3">';
|
||||
html += `<h6 class="text-muted">${__("Sponsorship Settings")}</h6>`;
|
||||
html += '<div class="row">';
|
||||
html += render_save_template_field_group(
|
||||
[
|
||||
"auto_send_pitch_deck",
|
||||
"sponsor_deck_email_template",
|
||||
"sponsor_deck_reply_to",
|
||||
"sponsor_deck_cc",
|
||||
"sponsor_deck_attachments",
|
||||
],
|
||||
doc
|
||||
);
|
||||
html += "</div></div>";
|
||||
|
||||
// Related Documents
|
||||
html += '<div class="template-section mt-4" id="related-docs-section">';
|
||||
html += `<h6 class="text-muted">${__("Related Documents")}</h6>`;
|
||||
html += '<div class="row">';
|
||||
|
||||
let pg_count = doc.payment_gateways ? doc.payment_gateways.length : 0;
|
||||
html += `
|
||||
<div class="col-md-6 mb-2">
|
||||
<label class="d-flex align-items-center">
|
||||
<input type="checkbox" class="template-option mr-2" data-option="payment_gateways" ${
|
||||
pg_count > 0 ? "checked" : ""
|
||||
} ${pg_count === 0 ? "disabled" : ""}>
|
||||
${__("Payment Gateways")} ${
|
||||
pg_count > 0
|
||||
? `<span class="text-muted ml-1">(${pg_count})</span>`
|
||||
: '<span class="text-muted ml-1">(' + __("None") + ")</span>"
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
html += `
|
||||
<div class="col-md-6 mb-2" id="ticket-types-option">
|
||||
<span class="text-muted">${__("Loading...")}</span>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2" id="add-ons-option">
|
||||
<span class="text-muted">${__("Loading...")}</span>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2" id="custom-fields-option">
|
||||
<span class="text-muted">${__("Loading...")}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
html += "</div></div>";
|
||||
|
||||
dialog.get_field("field_options").$wrapper.html(html);
|
||||
|
||||
let $wrapper = dialog.get_field("field_options").$wrapper;
|
||||
|
||||
const linked_doctypes = [
|
||||
{
|
||||
id: "ticket-types-option",
|
||||
doctype: "Event Ticket Type",
|
||||
option: "ticket_types",
|
||||
label: __("Ticket Types"),
|
||||
},
|
||||
{
|
||||
id: "add-ons-option",
|
||||
doctype: "Ticket Add-on",
|
||||
option: "add_ons",
|
||||
label: __("Add-ons"),
|
||||
},
|
||||
{
|
||||
id: "custom-fields-option",
|
||||
doctype: "Pohodex Event Manager Custom Field",
|
||||
option: "custom_fields",
|
||||
label: __("Custom Fields"),
|
||||
},
|
||||
];
|
||||
|
||||
for (let item of linked_doctypes) {
|
||||
frappe.call({
|
||||
method: "frappe.client.get_count",
|
||||
args: { doctype: item.doctype, filters: { event: doc.name } },
|
||||
callback: function (r) {
|
||||
let count = r.message || 0;
|
||||
$wrapper.find(`#${item.id}`).html(`
|
||||
<label class="d-flex align-items-center">
|
||||
<input type="checkbox" class="template-option mr-2" data-option="${item.option}" ${
|
||||
count > 0 ? "checked" : ""
|
||||
} ${count === 0 ? "disabled" : ""}>
|
||||
${item.label} ${
|
||||
count > 0
|
||||
? `<span class="text-muted ml-1">(${count})</span>`
|
||||
: '<span class="text-muted ml-1">(' + __("None") + ")</span>"
|
||||
}
|
||||
</label>
|
||||
`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
dialog
|
||||
.get_field("select_buttons")
|
||||
.$wrapper.find(".select-all-btn")
|
||||
.on("click", function () {
|
||||
dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(".template-option:not(:disabled)")
|
||||
.prop("checked", true);
|
||||
});
|
||||
|
||||
dialog
|
||||
.get_field("select_buttons")
|
||||
.$wrapper.find(".unselect-all-btn")
|
||||
.on("click", function () {
|
||||
dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(".template-option")
|
||||
.prop("checked", false);
|
||||
});
|
||||
}
|
||||
|
||||
function show_save_as_template_dialog(frm) {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Save Event as Template"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "template_name",
|
||||
label: __("Template Name"),
|
||||
reqd: 1,
|
||||
default: frm.doc.title + " Template",
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
label: __("Select What to Include"),
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "select_buttons",
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "field_options",
|
||||
},
|
||||
],
|
||||
size: "large",
|
||||
primary_action_label: __("Save Template"),
|
||||
primary_action: function (values) {
|
||||
let options = {};
|
||||
dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(".template-option:checked")
|
||||
.each(function () {
|
||||
options[$(this).data("option")] = 1;
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
method: "event_manager.events.doctype.event_template.event_template.create_template_from_event",
|
||||
args: {
|
||||
event_name: frm.doc.name,
|
||||
template_name: values.template_name,
|
||||
options: JSON.stringify(options),
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __("Creating Template..."),
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
dialog.hide();
|
||||
frappe.show_alert({
|
||||
message: __("Template {0} created successfully", [r.message]),
|
||||
indicator: "green",
|
||||
});
|
||||
frappe.set_route("Form", "Event Template", r.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
render_save_template_options(dialog, frm);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
frappe.ui.form.on("Pohodex Event Manager Event Form", {
|
||||
copy_to_clipboard(frm, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
const url = `${window.location.origin}/dashboard/events/${frm.doc.route}/forms/${row.route}`;
|
||||
navigator.clipboard.writeText(url);
|
||||
frappe.show_alert({ message: __("Link copied!"), indicator: "green" });
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Pohodex Event Manager Event", {
|
||||
refresh(frm) {
|
||||
frm.fields_dict.time_zone.set_data(getZoomSupportedTimezones());
|
||||
|
||||
if (frm.doc.route && frm.doc.is_published) {
|
||||
frm.add_web_link(`/events/${frm.doc.route}`);
|
||||
}
|
||||
|
||||
if (frm.doc.route) {
|
||||
frm.add_web_link(`/dashboard/book-tickets/${frm.doc.route}`, "View Registration Page");
|
||||
}
|
||||
|
||||
if (!frm.is_new()) {
|
||||
frm.add_web_link(`/dashboard/check-in/${frm.doc.name}`, __("Open Check-in"));
|
||||
}
|
||||
|
||||
const button_label = frm.doc.is_published ? __("Unpublish") : __("Publish");
|
||||
frm.add_custom_button(button_label, () => {
|
||||
frm.set_value("is_published", !frm.doc.is_published);
|
||||
frm.save();
|
||||
});
|
||||
|
||||
frm.set_query("track", "schedule", (doc, cdt, cdn) => {
|
||||
return {
|
||||
filters: {
|
||||
event: doc.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("default_ticket_type", (doc) => {
|
||||
return {
|
||||
filters: {
|
||||
event: doc.name,
|
||||
is_published: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Save as Template button
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(
|
||||
__("Save as Template"),
|
||||
function () {
|
||||
show_save_as_template_dialog(frm);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
}
|
||||
|
||||
frm.trigger("add_zoom_custom_actions");
|
||||
},
|
||||
|
||||
add_zoom_custom_actions(frm) {
|
||||
const installed_apps = frappe.boot.app_data.map((app) => app.app_name);
|
||||
if (!installed_apps.includes("zoom_integration") || frm.doc.category != "Webinars") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frm.doc.zoom_webinar) {
|
||||
frm.add_custom_button(__("View Webinar on Zoom"), () => {
|
||||
window.open(`https://zoom.us/webinar/${frm.doc.zoom_webinar}`, "_blank");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = frm.add_custom_button(__("Create Webinar on Zoom"), () => {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "create_webinar_on_zoom",
|
||||
btn,
|
||||
freeze: true,
|
||||
}).then(({ message }) => {
|
||||
frm.layout.tabs.find((t) => t.label == "Zoom Integration").set_active();
|
||||
});
|
||||
});
|
||||
},
|
||||
category(frm) {
|
||||
if (!frm.is_new()) return;
|
||||
|
||||
if (frm.doc.category === "Webinars") {
|
||||
frm.set_value("attach_email_ticket", 0);
|
||||
} else {
|
||||
frm.set_value("attach_email_ticket", 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function getZoomSupportedTimezones() {
|
||||
return [
|
||||
"Pacific/Midway",
|
||||
"Pacific/Pago_Pago",
|
||||
"Pacific/Honolulu",
|
||||
"America/Anchorage",
|
||||
"America/Vancouver",
|
||||
"America/Los_Angeles",
|
||||
"America/Tijuana",
|
||||
"America/Edmonton",
|
||||
"America/Denver",
|
||||
"America/Phoenix",
|
||||
"America/Mazatlan",
|
||||
"America/Winnipeg",
|
||||
"America/Regina",
|
||||
"America/Chicago",
|
||||
"America/Mexico_City",
|
||||
"America/Guatemala",
|
||||
"America/El_Salvador",
|
||||
"America/Managua",
|
||||
"America/Costa_Rica",
|
||||
"America/Montreal",
|
||||
"America/New_York",
|
||||
"America/Indianapolis",
|
||||
"America/Panama",
|
||||
"America/Bogota",
|
||||
"America/Lima",
|
||||
"America/Halifax",
|
||||
"America/Puerto_Rico",
|
||||
"America/Caracas",
|
||||
"America/Santiago",
|
||||
"America/St_Johns",
|
||||
"America/Montevideo",
|
||||
"America/Araguaina",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Godthab",
|
||||
"America/Sao_Paulo",
|
||||
"Atlantic/Azores",
|
||||
"Canada/Atlantic",
|
||||
"Atlantic/Cape_Verde",
|
||||
"UTC",
|
||||
"Etc/Greenwich",
|
||||
"Europe/Belgrade",
|
||||
"CET",
|
||||
"Atlantic/Reykjavik",
|
||||
"Europe/Dublin",
|
||||
"Europe/London",
|
||||
"Europe/Lisbon",
|
||||
"Africa/Casablanca",
|
||||
"Africa/Nouakchott",
|
||||
"Europe/Oslo",
|
||||
"Europe/Copenhagen",
|
||||
"Europe/Brussels",
|
||||
"Europe/Berlin",
|
||||
"Europe/Helsinki",
|
||||
"Europe/Amsterdam",
|
||||
"Europe/Rome",
|
||||
"Europe/Stockholm",
|
||||
"Europe/Vienna",
|
||||
"Europe/Luxembourg",
|
||||
"Europe/Paris",
|
||||
"Europe/Zurich",
|
||||
"Europe/Madrid",
|
||||
"Africa/Bangui",
|
||||
"Africa/Algiers",
|
||||
"Africa/Tunis",
|
||||
"Africa/Harare",
|
||||
"Africa/Nairobi",
|
||||
"Europe/Warsaw",
|
||||
"Europe/Prague",
|
||||
"Europe/Budapest",
|
||||
"Europe/Sofia",
|
||||
"Europe/Istanbul",
|
||||
"Europe/Athens",
|
||||
"Europe/Bucharest",
|
||||
"Asia/Nicosia",
|
||||
"Asia/Beirut",
|
||||
"Asia/Damascus",
|
||||
"Asia/Jerusalem",
|
||||
"Asia/Amman",
|
||||
"Africa/Tripoli",
|
||||
"Africa/Cairo",
|
||||
"Africa/Johannesburg",
|
||||
"Europe/Moscow",
|
||||
"Asia/Baghdad",
|
||||
"Asia/Kuwait",
|
||||
"Asia/Riyadh",
|
||||
"Asia/Bahrain",
|
||||
"Asia/Qatar",
|
||||
"Asia/Aden",
|
||||
"Asia/Tehran",
|
||||
"Africa/Khartoum",
|
||||
"Africa/Djibouti",
|
||||
"Africa/Mogadishu",
|
||||
"Asia/Dubai",
|
||||
"Asia/Muscat",
|
||||
"Asia/Baku",
|
||||
"Asia/Kabul",
|
||||
"Asia/Yekaterinburg",
|
||||
"Asia/Tashkent",
|
||||
"Asia/Calcutta",
|
||||
"Asia/Kathmandu",
|
||||
"Asia/Novosibirsk",
|
||||
"Asia/Almaty",
|
||||
"Asia/Dacca",
|
||||
"Asia/Krasnoyarsk",
|
||||
"Asia/Dhaka",
|
||||
"Asia/Bangkok",
|
||||
"Asia/Saigon",
|
||||
"Asia/Jakarta",
|
||||
"Asia/Irkutsk",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Hong_Kong",
|
||||
"Asia/Taipei",
|
||||
"Asia/Kuala_Lumpur",
|
||||
"Asia/Singapore",
|
||||
"Australia/Perth",
|
||||
"Asia/Yakutsk",
|
||||
"Asia/Seoul",
|
||||
"Asia/Tokyo",
|
||||
"Australia/Darwin",
|
||||
"Australia/Adelaide",
|
||||
"Asia/Vladivostok",
|
||||
"Pacific/Port_Moresby",
|
||||
"Australia/Brisbane",
|
||||
"Australia/Sydney",
|
||||
"Australia/Hobart",
|
||||
"Asia/Magadan",
|
||||
"SST",
|
||||
"Pacific/Noumea",
|
||||
"Asia/Kamchatka",
|
||||
"Pacific/Fiji",
|
||||
"Pacific/Auckland",
|
||||
"Asia/Kolkata",
|
||||
"Europe/Kiev",
|
||||
"America/Tegucigalpa",
|
||||
"Pacific/Apia",
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,637 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2025-07-19 11:28:39.634220",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"category",
|
||||
"free_webinar",
|
||||
"medium",
|
||||
"column_break_cxqh",
|
||||
"banner_image",
|
||||
"host",
|
||||
"venue",
|
||||
"section_break_naqe",
|
||||
"start_date",
|
||||
"start_time",
|
||||
"time_zone",
|
||||
"column_break_cjby",
|
||||
"end_date",
|
||||
"end_time",
|
||||
"section_break_zukm",
|
||||
"short_description",
|
||||
"about",
|
||||
"tab_2_tab",
|
||||
"schedule",
|
||||
"website_tab",
|
||||
"is_published",
|
||||
"route",
|
||||
"default_ticket_type",
|
||||
"column_break_ndtl",
|
||||
"external_registration_page",
|
||||
"registration_url",
|
||||
"meta_image",
|
||||
"card_image",
|
||||
"section_break_rmtj",
|
||||
"allow_guest_booking",
|
||||
"column_break_lzcw",
|
||||
"guest_verification_method",
|
||||
"auto_closures_section",
|
||||
"registrations_close_at",
|
||||
"section_break_kwlt",
|
||||
"featured_speakers",
|
||||
"payments_tab",
|
||||
"section_break_owtc",
|
||||
"payment_gateways",
|
||||
"tax_settings_section",
|
||||
"apply_tax",
|
||||
"tax_inclusive",
|
||||
"column_break_gehk",
|
||||
"tax_label",
|
||||
"tax_percentage",
|
||||
"sponsorships_tab",
|
||||
"show_sponsorship_section",
|
||||
"automations_section",
|
||||
"auto_send_pitch_deck",
|
||||
"section_break_edar",
|
||||
"sponsor_deck_email_template",
|
||||
"sponsor_deck_reply_to",
|
||||
"column_break_awpd",
|
||||
"sponsor_deck_cc",
|
||||
"section_break_mieu",
|
||||
"sponsor_deck_attachments",
|
||||
"customisations_tab",
|
||||
"send_ticket_email",
|
||||
"section_break_uoke",
|
||||
"attach_calendar_invite",
|
||||
"ticket_email_template",
|
||||
"column_break_ukql",
|
||||
"attach_email_ticket",
|
||||
"ticket_print_format",
|
||||
"talks_section",
|
||||
"allow_editing_talks_after_acceptance",
|
||||
"custom_forms_tab",
|
||||
"forms_section",
|
||||
"custom_forms",
|
||||
"connections_tab",
|
||||
"proposal"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_cxqh",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Category",
|
||||
"options": "Event Category",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.medium!=\"Online\"",
|
||||
"fieldname": "venue",
|
||||
"fieldtype": "Link",
|
||||
"label": "Venue",
|
||||
"mandatory_depends_on": "eval:doc.medium!=\"Online\"",
|
||||
"options": "Event Venue"
|
||||
},
|
||||
{
|
||||
"default": "In Person",
|
||||
"fieldname": "medium",
|
||||
"fieldtype": "Select",
|
||||
"label": "Medium",
|
||||
"options": "In Person\nOnline"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_naqe",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_cjby",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "End Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_zukm",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "short_description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Short Description"
|
||||
},
|
||||
{
|
||||
"description": "Description of the event",
|
||||
"fieldname": "about",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "About"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Start Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "end_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "End Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "host",
|
||||
"fieldtype": "Link",
|
||||
"label": "Host",
|
||||
"options": "Event Host",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time_zone",
|
||||
"fieldtype": "Autocomplete",
|
||||
"label": "Time Zone"
|
||||
},
|
||||
{
|
||||
"fieldname": "banner_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Banner Image"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_published",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Published?"
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_2_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "schedule",
|
||||
"fieldtype": "Table",
|
||||
"label": "Schedule",
|
||||
"options": "Schedule Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payments"
|
||||
},
|
||||
{
|
||||
"description": "Used by Frappe Builder",
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"label": "Route",
|
||||
"no_copy": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Meta Image"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "external_registration_page",
|
||||
"fieldtype": "Check",
|
||||
"label": "External Registration Page?"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.external_registration_page==1",
|
||||
"fieldname": "registration_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Registration URL",
|
||||
"mandatory_depends_on": "eval:doc.external_registration_page==1"
|
||||
},
|
||||
{
|
||||
"fieldname": "website_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Website"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ndtl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.send_ticket_email && doc.attach_email_ticket;",
|
||||
"fieldname": "ticket_print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Ticket Print Format",
|
||||
"link_filters": "[[\"Print Format\",\"doc_type\",\"=\",\"Event Ticket\"]]",
|
||||
"options": "Print Format"
|
||||
},
|
||||
{
|
||||
"fieldname": "forms_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_forms",
|
||||
"fieldtype": "Table",
|
||||
"label": "Custom Forms",
|
||||
"options": "Pohodex Event Manager Event Form"
|
||||
},
|
||||
{
|
||||
"fieldname": "talks_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Talk Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "When enabled, speakers can edit their talk title and description after acceptance",
|
||||
"fieldname": "allow_editing_talks_after_acceptance",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Editing Talks After Acceptance"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.send_ticket_email;",
|
||||
"fieldname": "ticket_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Ticket Email Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "customisations_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Customisations"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ukql",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_kwlt",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "featured_speakers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Featured Speakers",
|
||||
"options": "Event Featured Speaker"
|
||||
},
|
||||
{
|
||||
"fieldname": "sponsorships_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Sponsorships"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Show or hide the sponsorship section on the event website",
|
||||
"fieldname": "show_sponsorship_section",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Sponsorship Section on Website"
|
||||
},
|
||||
{
|
||||
"fieldname": "automations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Automations"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "auto_send_pitch_deck",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Send Pitch Deck?"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_edar",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_awpd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_mieu",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"description": "Default template will be used if not set",
|
||||
"fieldname": "sponsor_deck_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Email Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_cc",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "CC"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_reply_to",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reply To"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_attachments",
|
||||
"fieldtype": "Table",
|
||||
"label": "Attachments",
|
||||
"options": "Sponsorship Deck Item"
|
||||
},
|
||||
{
|
||||
"description": "Will be selected by default in the booking form",
|
||||
"fieldname": "default_ticket_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Ticket Type",
|
||||
"options": "Event Ticket Type"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.category==\"Webinars\"",
|
||||
"fieldname": "free_webinar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Free Webinar?"
|
||||
},
|
||||
{
|
||||
"fieldname": "connections_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections"
|
||||
},
|
||||
{
|
||||
"fieldname": "proposal",
|
||||
"fieldtype": "Link",
|
||||
"label": "Proposal",
|
||||
"options": "Event Proposal",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_owtc",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_gateways",
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Gateways",
|
||||
"options": "Event Payment Gateway"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "apply_tax",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax on Bookings?"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_tax==1",
|
||||
"description": "When enabled, ticket prices are treated as tax-inclusive. Tax is back-calculated from the price instead of added on top.",
|
||||
"fieldname": "tax_inclusive",
|
||||
"fieldtype": "Check",
|
||||
"label": "Tax Inclusive"
|
||||
},
|
||||
{
|
||||
"default": "GST",
|
||||
"depends_on": "eval:doc.apply_tax==1",
|
||||
"description": "Label displayed to customers (e.g., GST, VAT, Sales Tax)",
|
||||
"fieldname": "tax_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tax Label",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax==1"
|
||||
},
|
||||
{
|
||||
"default": "18",
|
||||
"depends_on": "eval:doc.apply_tax==1",
|
||||
"description": "Tax rate to apply on bookings",
|
||||
"fieldname": "tax_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Tax Percentage",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax==1"
|
||||
},
|
||||
{
|
||||
"fieldname": "card_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Card Image"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.send_ticket_email",
|
||||
"fieldname": "attach_calendar_invite",
|
||||
"fieldtype": "Check",
|
||||
"label": "Attach Calendar Invite"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_guest_booking",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Guest Booking"
|
||||
},
|
||||
{
|
||||
"default": "None",
|
||||
"depends_on": "eval:doc.allow_guest_booking",
|
||||
"description": "How to verify guest identity before booking",
|
||||
"fieldname": "guest_verification_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Guest Verification Method",
|
||||
"options": "None\nEmail OTP\nPhone OTP"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.send_ticket_email;",
|
||||
"fieldname": "attach_email_ticket",
|
||||
"fieldtype": "Check",
|
||||
"label": "Attach Email Ticket"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_uoke",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "send_ticket_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Ticket Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_rmtj",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Guest Booking"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gehk",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lzcw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "auto_closures_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Auto Closures"
|
||||
},
|
||||
{
|
||||
"fieldname": "registrations_close_at",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Registrations Close At"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_forms_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Forms"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"image_field": "banner_image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
"group": "Sponsorship",
|
||||
"link_doctype": "Event Sponsor",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Sponsorship",
|
||||
"link_doctype": "Sponsorship Tier",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Ticketing",
|
||||
"link_doctype": "Event Ticket Type",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Ticketing",
|
||||
"link_doctype": "Ticket Add-on",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Ticketing",
|
||||
"link_doctype": "Event Booking",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Ticketing",
|
||||
"link_doctype": "Event Ticket",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Proposals",
|
||||
"link_doctype": "Sponsorship Enquiry",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Proposals",
|
||||
"link_doctype": "Talk Proposal",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "General",
|
||||
"link_doctype": "Event Track",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "General",
|
||||
"link_doctype": "Event Check In",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Ticketing",
|
||||
"link_doctype": "Pohodex Event Manager Coupon Code",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "General",
|
||||
"link_doctype": "Additional Event Page",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Ticketing",
|
||||
"link_doctype": "Offline Payment Method",
|
||||
"link_fieldname": "event"
|
||||
},
|
||||
{
|
||||
"group": "Customisations",
|
||||
"link_doctype": "Pohodex Event Manager Custom Field",
|
||||
"link_fieldname": "event"
|
||||
}
|
||||
],
|
||||
"modified": "2026-03-23 17:37:37.770911",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Pohodex Event Manager Event",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Pohodex Event Manager User",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "start_date",
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import append_number_if_name_exists
|
||||
from frappe.utils.data import get_time, time_diff_in_seconds
|
||||
|
||||
from event_manager.utils import only_if_app_installed
|
||||
|
||||
|
||||
class BuzzEvent(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from event_manager.events.doctype.buzz_event_form.buzz_event_form import BuzzEventForm
|
||||
from event_manager.events.doctype.event_featured_speaker.event_featured_speaker import EventFeaturedSpeaker
|
||||
from event_manager.events.doctype.event_payment_gateway.event_payment_gateway import EventPaymentGateway
|
||||
from event_manager.events.doctype.schedule_item.schedule_item import ScheduleItem
|
||||
from event_manager.proposals.doctype.sponsorship_deck_item.sponsorship_deck_item import SponsorshipDeckItem
|
||||
|
||||
about: DF.TextEditor | None
|
||||
allow_editing_talks_after_acceptance: DF.Check
|
||||
allow_guest_booking: DF.Check
|
||||
apply_tax: DF.Check
|
||||
attach_calendar_invite: DF.Check
|
||||
attach_email_ticket: DF.Check
|
||||
auto_send_pitch_deck: DF.Check
|
||||
banner_image: DF.AttachImage | None
|
||||
card_image: DF.AttachImage | None
|
||||
category: DF.Link
|
||||
custom_forms: DF.Table[BuzzEventForm]
|
||||
default_ticket_type: DF.Link | None
|
||||
end_date: DF.Date | None
|
||||
end_time: DF.Time
|
||||
external_registration_page: DF.Check
|
||||
featured_speakers: DF.Table[EventFeaturedSpeaker]
|
||||
free_webinar: DF.Check
|
||||
guest_verification_method: DF.Literal["None", "Email OTP", "Phone OTP"]
|
||||
host: DF.Link
|
||||
is_published: DF.Check
|
||||
medium: DF.Literal["In Person", "Online"]
|
||||
meta_image: DF.AttachImage | None
|
||||
name: DF.Int | None
|
||||
payment_gateways: DF.Table[EventPaymentGateway]
|
||||
proposal: DF.Link | None
|
||||
registration_url: DF.Data | None
|
||||
registrations_close_at: DF.Datetime | None
|
||||
route: DF.Data | None
|
||||
schedule: DF.Table[ScheduleItem]
|
||||
send_ticket_email: DF.Check
|
||||
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
|
||||
sponsor_deck_reply_to: DF.Data | None
|
||||
start_date: DF.Date
|
||||
start_time: DF.Time
|
||||
tax_inclusive: DF.Check
|
||||
tax_label: DF.Data | None
|
||||
tax_percentage: DF.Percent
|
||||
ticket_email_template: DF.Link | None
|
||||
ticket_print_format: DF.Link | None
|
||||
time_zone: DF.Autocomplete | None
|
||||
title: DF.Data
|
||||
venue: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_schedule()
|
||||
self.validate_route()
|
||||
self.validate_tax_settings()
|
||||
self.validate_guest_verification_config()
|
||||
|
||||
def validate_schedule(self):
|
||||
end_date = self.end_date or self.start_date
|
||||
for item in self.schedule:
|
||||
if item.date < self.start_date or item.date > end_date:
|
||||
frappe.throw(
|
||||
frappe._("<b>Schedule</b> row #{0}: <b>Date</b> must be within event dates").format(
|
||||
item.idx
|
||||
)
|
||||
)
|
||||
|
||||
if time_diff_in_seconds(item.end_time, item.start_time) <= 0:
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"<b>Schedule</b> row #{0}: <b>End Time</b> must be after <b>Start Time</b>"
|
||||
).format(item.idx)
|
||||
)
|
||||
|
||||
if (
|
||||
item.date == self.start_date
|
||||
and self.start_time
|
||||
and get_time(item.start_time) < get_time(self.start_time)
|
||||
):
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"<b>Schedule</b> row #{0}: <b>Start Time</b> cannot be before event start time"
|
||||
).format(item.idx)
|
||||
)
|
||||
|
||||
if item.date == end_date and self.end_time and get_time(item.end_time) > get_time(self.end_time):
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"<b>Schedule</b> row #{0}: <b>End Time</b> cannot be after event end time"
|
||||
).format(item.idx)
|
||||
)
|
||||
|
||||
def validate_dates(self):
|
||||
self.validate_from_to_dates("start_date", "end_date")
|
||||
if (
|
||||
(not self.end_date or self.start_date == self.end_date)
|
||||
and self.start_time
|
||||
and self.end_time
|
||||
and time_diff_in_seconds(self.end_time, self.start_time) <= 0
|
||||
):
|
||||
frappe.throw(frappe._("<b>End Time</b> must be after <b>Start Time</b>"))
|
||||
|
||||
def validate_tax_settings(self):
|
||||
"""Set default tax values when tax is enabled."""
|
||||
if self.apply_tax:
|
||||
if not self.tax_label:
|
||||
self.tax_label = "GST"
|
||||
if not self.tax_percentage:
|
||||
self.tax_percentage = 18
|
||||
|
||||
def validate_route(self):
|
||||
if self.is_published and not self.route:
|
||||
route = frappe.website.utils.cleanup_page_name(self.title).replace("_", "-")
|
||||
self.route = append_number_if_name_exists("Pohodex Event Manager Event", route, fieldname="route")
|
||||
|
||||
def validate_guest_verification_config(self):
|
||||
"""Ensure email/SMS is configured when OTP verification is enabled."""
|
||||
if frappe.in_test or not self.allow_guest_booking:
|
||||
return
|
||||
|
||||
if self.guest_verification_method == "Email OTP":
|
||||
has_email = frappe.db.exists("Email Account", {"default_outgoing": 1, "enable_outgoing": 1})
|
||||
if not has_email:
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"Please configure an outgoing Email Account before enabling Email OTP verification."
|
||||
),
|
||||
title=frappe._("Email Not Configured"),
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def after_insert(self):
|
||||
self.create_default_records()
|
||||
|
||||
def create_default_records(self):
|
||||
records = [
|
||||
{"doctype": "Sponsorship Tier", "title": "Normal"},
|
||||
{"doctype": "Event Ticket Type", "title": "Normal"},
|
||||
]
|
||||
for record in records:
|
||||
frappe.get_doc({**record, "event": self.name}).insert(ignore_permissions=True)
|
||||
|
||||
default_forms = [
|
||||
{"form_doctype": "Event Feedback", "route": "feedback"},
|
||||
{"form_doctype": "Talk Proposal", "route": "propose-talk"},
|
||||
{"form_doctype": "Sponsorship Enquiry", "route": "enquire-sponsorship"},
|
||||
]
|
||||
for form in default_forms:
|
||||
self.append("custom_forms", form)
|
||||
self.save(ignore_permissions=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
@only_if_app_installed("zoom_integration", raise_exception=True)
|
||||
def create_webinar_on_zoom(self):
|
||||
if not self.end_time:
|
||||
frappe.throw(frappe._("End time is needed for Zoom Webinar creation"))
|
||||
|
||||
zoom_webinar = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Zoom Webinar",
|
||||
"title": self.title,
|
||||
"date": self.start_date,
|
||||
"start_time": self.start_time,
|
||||
"duration": int(time_diff_in_seconds(self.end_time, self.start_time)),
|
||||
"timezone": self.time_zone,
|
||||
"template": frappe.get_cached_doc("Pohodex Event Manager Settings").default_webinar_template,
|
||||
}
|
||||
).insert()
|
||||
|
||||
self.db_set("zoom_webinar", zoom_webinar.name)
|
||||
|
||||
return zoom_webinar
|
||||
|
||||
def on_update(self):
|
||||
self.update_zoom_webinar()
|
||||
|
||||
@only_if_app_installed("zoom_integration")
|
||||
def update_zoom_webinar(self):
|
||||
if not self.zoom_webinar:
|
||||
return
|
||||
|
||||
if (
|
||||
self.has_value_changed("start_date")
|
||||
or self.has_value_changed("end_time")
|
||||
or self.has_value_changed("start_time")
|
||||
or self.has_value_changed("time_zone")
|
||||
):
|
||||
webinar = frappe.get_doc("Zoom Webinar", self.zoom_webinar)
|
||||
webinar.update(
|
||||
{
|
||||
"date": self.start_date,
|
||||
"start_time": self.start_time,
|
||||
"duration": int(time_diff_in_seconds(self.end_time, self.start_time)),
|
||||
"timezone": self.time_zone,
|
||||
}
|
||||
)
|
||||
webinar.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_from_template(template_name: str, options: str, additional_fields: str = "{}") -> str:
|
||||
"""
|
||||
Create a new Pohodex Event Manager Event from a template.
|
||||
|
||||
Args:
|
||||
template_name: Name of the Event Template
|
||||
options: JSON string of what to copy (e.g., {"category": 1, "ticket_types": 1, ...})
|
||||
additional_fields: JSON string of additional field values for mandatory fields not in template
|
||||
|
||||
Returns:
|
||||
New Pohodex Event Manager Event document name
|
||||
"""
|
||||
if not frappe.has_permission("Event Template", "read"):
|
||||
frappe.throw(_("You don't have permission to use templates"))
|
||||
|
||||
if not frappe.has_permission("Pohodex Event Manager Event", "create"):
|
||||
frappe.throw(_("You don't have permission to create events"))
|
||||
|
||||
template = frappe.get_doc("Event Template", template_name)
|
||||
options = frappe.parse_json(options)
|
||||
additional_fields = frappe.parse_json(additional_fields)
|
||||
|
||||
# Create new event with required fields
|
||||
event = frappe.new_doc("Pohodex Event Manager Event")
|
||||
event.title = f"New Event from {template.template_name}"
|
||||
event.start_date = frappe.utils.today()
|
||||
event.start_time = "09:00:00"
|
||||
event.end_time = "18:00:00"
|
||||
|
||||
# Apply additional fields first (these are mandatory fields provided by user)
|
||||
for field, value in additional_fields.items():
|
||||
if value:
|
||||
event.set(field, value)
|
||||
|
||||
# Field mapping for direct copy
|
||||
field_map = {
|
||||
"category": "category",
|
||||
"host": "host",
|
||||
"banner_image": "banner_image",
|
||||
"short_description": "short_description",
|
||||
"about": "about",
|
||||
"medium": "medium",
|
||||
"venue": "venue",
|
||||
"allow_guest_booking": "allow_guest_booking",
|
||||
"guest_verification_method": "guest_verification_method",
|
||||
"time_zone": "time_zone",
|
||||
"send_ticket_email": "send_ticket_email",
|
||||
"ticket_email_template": "ticket_email_template",
|
||||
"ticket_print_format": "ticket_print_format",
|
||||
"apply_tax": "apply_tax",
|
||||
"tax_inclusive": "tax_inclusive",
|
||||
"tax_label": "tax_label",
|
||||
"tax_percentage": "tax_percentage",
|
||||
"auto_send_pitch_deck": "auto_send_pitch_deck",
|
||||
"sponsor_deck_email_template": "sponsor_deck_email_template",
|
||||
"sponsor_deck_reply_to": "sponsor_deck_reply_to",
|
||||
"sponsor_deck_cc": "sponsor_deck_cc",
|
||||
}
|
||||
|
||||
for option_key, field_name in field_map.items():
|
||||
if options.get(option_key):
|
||||
event.set(field_name, template.get(field_name))
|
||||
|
||||
# Copy child tables
|
||||
if options.get("payment_gateways"):
|
||||
for pg in template.payment_gateways:
|
||||
event.append("payment_gateways", {"payment_gateway": pg.payment_gateway})
|
||||
|
||||
if options.get("sponsor_deck_attachments"):
|
||||
for attachment in template.sponsor_deck_attachments:
|
||||
event.append("sponsor_deck_attachments", {"file": attachment.file})
|
||||
|
||||
event.insert()
|
||||
|
||||
# Create linked documents (Ticket Types, Add-ons, Custom Fields)
|
||||
if options.get("ticket_types"):
|
||||
for tt in template.template_ticket_types:
|
||||
ticket_type = frappe.new_doc("Event Ticket Type")
|
||||
ticket_type.event = event.name
|
||||
ticket_type.title = tt.title
|
||||
ticket_type.price = tt.price
|
||||
ticket_type.currency = tt.currency
|
||||
ticket_type.is_published = tt.is_published
|
||||
ticket_type.max_tickets_available = tt.max_tickets_available
|
||||
ticket_type.auto_unpublish_after = tt.auto_unpublish_after
|
||||
ticket_type.insert()
|
||||
|
||||
if options.get("add_ons"):
|
||||
for addon in template.template_add_ons:
|
||||
add_on = frappe.new_doc("Ticket Add-on")
|
||||
add_on.event = event.name
|
||||
add_on.title = addon.title
|
||||
add_on.price = addon.price
|
||||
add_on.currency = addon.currency
|
||||
add_on.description = addon.description
|
||||
add_on.user_selects_option = addon.user_selects_option
|
||||
add_on.options = addon.options
|
||||
add_on.enabled = addon.enabled
|
||||
add_on.insert()
|
||||
|
||||
if options.get("custom_fields"):
|
||||
for cf in template.template_custom_fields:
|
||||
custom_field = frappe.new_doc("Pohodex Event Manager Custom Field")
|
||||
custom_field.event = event.name
|
||||
custom_field.label = cf.label
|
||||
custom_field.fieldname = cf.fieldname
|
||||
custom_field.fieldtype = cf.fieldtype
|
||||
custom_field.options = cf.options
|
||||
custom_field.applied_to = cf.applied_to
|
||||
custom_field.enabled = cf.enabled
|
||||
custom_field.mandatory = cf.mandatory
|
||||
custom_field.placeholder = cf.placeholder
|
||||
custom_field.default_value = cf.default_value
|
||||
custom_field.order = cf.order
|
||||
custom_field.insert()
|
||||
|
||||
return event.name
|
||||
@@ -0,0 +1,379 @@
|
||||
// Field groups for template options
|
||||
const TEMPLATE_FIELD_GROUPS = {
|
||||
event_details: {
|
||||
label: __("Event Details"),
|
||||
fields: [
|
||||
"category",
|
||||
"host",
|
||||
"banner_image",
|
||||
"short_description",
|
||||
"about",
|
||||
"medium",
|
||||
"venue",
|
||||
"allow_guest_booking",
|
||||
"guest_verification_method",
|
||||
"time_zone",
|
||||
],
|
||||
},
|
||||
ticketing_settings: {
|
||||
label: __("Ticketing Settings"),
|
||||
fields: [
|
||||
"send_ticket_email",
|
||||
"apply_tax",
|
||||
"tax_label",
|
||||
"tax_percentage",
|
||||
"ticket_email_template",
|
||||
"ticket_print_format",
|
||||
],
|
||||
},
|
||||
sponsorship_settings: {
|
||||
label: __("Sponsorship Settings"),
|
||||
fields: [
|
||||
"auto_send_pitch_deck",
|
||||
"sponsor_deck_email_template",
|
||||
"sponsor_deck_reply_to",
|
||||
"sponsor_deck_cc",
|
||||
"sponsor_deck_attachments",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const MANDATORY_FIELDS = ["category", "host"];
|
||||
|
||||
const FIELD_LABELS = {
|
||||
category: __("Category"),
|
||||
host: __("Host"),
|
||||
banner_image: __("Banner Image"),
|
||||
short_description: __("Short Description"),
|
||||
about: __("About"),
|
||||
medium: __("Medium"),
|
||||
venue: __("Venue"),
|
||||
allow_guest_booking: __("Allow Guest Booking"),
|
||||
guest_verification_method: __("Guest Verification Method"),
|
||||
time_zone: __("Time Zone"),
|
||||
send_ticket_email: __("Send Ticket Email"),
|
||||
apply_tax: __("Tax Settings"),
|
||||
tax_label: __("Tax Label"),
|
||||
tax_percentage: __("Tax Percentage"),
|
||||
ticket_email_template: __("Ticket Email Template"),
|
||||
ticket_print_format: __("Ticket Print Format"),
|
||||
auto_send_pitch_deck: __("Auto Send Pitch Deck"),
|
||||
sponsor_deck_email_template: __("Sponsor Deck Email Template"),
|
||||
sponsor_deck_reply_to: __("Sponsor Deck Reply To"),
|
||||
sponsor_deck_cc: __("Sponsor Deck CC"),
|
||||
sponsor_deck_attachments: __("Sponsor Deck Attachments"),
|
||||
payment_gateways: __("Payment Gateways"),
|
||||
ticket_types: __("Ticket Types"),
|
||||
add_ons: __("Add-ons"),
|
||||
custom_fields: __("Custom Fields"),
|
||||
};
|
||||
|
||||
function get_field_label(field) {
|
||||
return FIELD_LABELS[field] || field;
|
||||
}
|
||||
|
||||
function render_field_group(group_key, template) {
|
||||
let group = TEMPLATE_FIELD_GROUPS[group_key];
|
||||
let html = '<div class="template-section mt-3">';
|
||||
html += `<h6 class="text-muted">${group.label}</h6>`;
|
||||
html += '<div class="row">';
|
||||
|
||||
for (let field of group.fields) {
|
||||
let value = template[field];
|
||||
let has_value = value !== null && value !== undefined && value !== "" && value !== 0;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
has_value = value.length > 0;
|
||||
}
|
||||
|
||||
let label = get_field_label(field);
|
||||
|
||||
html += `
|
||||
<div class="col-md-6 mb-2">
|
||||
<label class="d-flex align-items-center">
|
||||
<input type="checkbox" class="template-option mr-2" data-option="${field}" ${
|
||||
has_value ? "checked" : "disabled"
|
||||
}>
|
||||
${label}
|
||||
${!has_value ? '<span class="text-muted ml-1">(' + __("Not set") + ")</span>" : ""}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += "</div></div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function render_related_documents(template) {
|
||||
let html = '<div class="template-section mt-4">';
|
||||
html += `<h6 class="text-muted">${__("Related Documents")}</h6>`;
|
||||
html += '<div class="row">';
|
||||
|
||||
const related_items = [
|
||||
{ key: "payment_gateways", label: __("Payment Gateways"), data_key: "payment_gateways" },
|
||||
{
|
||||
key: "ticket_types",
|
||||
label: __("Ticket Types"),
|
||||
data_key: "template_ticket_types",
|
||||
},
|
||||
{ key: "add_ons", label: __("Add-ons"), data_key: "template_add_ons" },
|
||||
{
|
||||
key: "custom_fields",
|
||||
label: __("Custom Fields"),
|
||||
data_key: "template_custom_fields",
|
||||
},
|
||||
];
|
||||
|
||||
for (let item of related_items) {
|
||||
let count = template[item.data_key] ? template[item.data_key].length : 0;
|
||||
html += `
|
||||
<div class="col-md-6 mb-2">
|
||||
<label class="d-flex align-items-center">
|
||||
<input type="checkbox" class="template-option mr-2" data-option="${item.key}" ${
|
||||
count > 0 ? "checked" : ""
|
||||
} ${count === 0 ? "disabled" : ""}>
|
||||
${item.label} ${
|
||||
count > 0
|
||||
? `<span class="text-muted ml-1">(${count})</span>`
|
||||
: '<span class="text-muted ml-1">(' + __("None") + ")</span>"
|
||||
}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += "</div></div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function update_mandatory_fields_visibility(dialog, template) {
|
||||
let missing_fields = [];
|
||||
|
||||
for (let field of MANDATORY_FIELDS) {
|
||||
let template_has_value = template[field] && template[field] !== "";
|
||||
let checkbox = dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(`.template-option[data-option="${field}"]`);
|
||||
let is_checked = checkbox.is(":checked");
|
||||
|
||||
if (!template_has_value || !is_checked) {
|
||||
missing_fields.push(field);
|
||||
dialog.get_field(field).df.hidden = 0;
|
||||
dialog.get_field(field).df.reqd = 1;
|
||||
dialog.get_field(field).refresh();
|
||||
} else {
|
||||
dialog.get_field(field).df.hidden = 1;
|
||||
dialog.get_field(field).df.reqd = 0;
|
||||
dialog.get_field(field).refresh();
|
||||
}
|
||||
}
|
||||
|
||||
if (missing_fields.length > 0) {
|
||||
dialog.get_field("missing_fields_section").df.hidden = 0;
|
||||
dialog.get_field("missing_fields_section").refresh();
|
||||
dialog
|
||||
.get_field("missing_fields_info")
|
||||
.$wrapper.html(
|
||||
`<p class="text-muted small">${__(
|
||||
"The following required fields are not set in the template or not selected. Please fill them in:"
|
||||
)}</p>`
|
||||
);
|
||||
} else {
|
||||
dialog.get_field("missing_fields_section").df.hidden = 1;
|
||||
dialog.get_field("missing_fields_section").refresh();
|
||||
dialog.get_field("missing_fields_info").$wrapper.html("");
|
||||
}
|
||||
}
|
||||
|
||||
function bind_select_buttons(dialog) {
|
||||
dialog
|
||||
.get_field("select_buttons")
|
||||
.$wrapper.find(".select-all-btn")
|
||||
.on("click", function () {
|
||||
dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(".template-option:not(:disabled)")
|
||||
.prop("checked", true);
|
||||
update_mandatory_fields_visibility(dialog, dialog.template_data);
|
||||
});
|
||||
|
||||
dialog
|
||||
.get_field("select_buttons")
|
||||
.$wrapper.find(".unselect-all-btn")
|
||||
.on("click", function () {
|
||||
dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(".template-option")
|
||||
.prop("checked", false);
|
||||
update_mandatory_fields_visibility(dialog, dialog.template_data);
|
||||
});
|
||||
}
|
||||
|
||||
function render_template_options(dialog, template) {
|
||||
let buttons_html = `
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-default btn-xs select-all-btn">${__("Select All")}</button>
|
||||
<button class="btn btn-default btn-xs unselect-all-btn">${__("Unselect All")}</button>
|
||||
</div>
|
||||
`;
|
||||
dialog.get_field("select_buttons").$wrapper.html(buttons_html);
|
||||
|
||||
let html = "";
|
||||
html += render_field_group("event_details", template);
|
||||
html += render_field_group("ticketing_settings", template);
|
||||
html += render_field_group("sponsorship_settings", template);
|
||||
html += render_related_documents(template);
|
||||
|
||||
dialog.get_field("field_options").$wrapper.html(html);
|
||||
dialog.template_data = template;
|
||||
|
||||
update_mandatory_fields_visibility(dialog, template);
|
||||
bind_select_buttons(dialog);
|
||||
|
||||
dialog.get_field("field_options").$wrapper.on("change", ".template-option", function () {
|
||||
update_mandatory_fields_visibility(dialog, dialog.template_data);
|
||||
});
|
||||
}
|
||||
|
||||
function on_template_selected(dialog) {
|
||||
let template_name = dialog.get_value("template");
|
||||
if (!template_name) {
|
||||
dialog.get_field("field_options").$wrapper.html("");
|
||||
dialog.get_field("select_buttons").$wrapper.html("");
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.get",
|
||||
args: {
|
||||
doctype: "Event Template",
|
||||
name: template_name,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
render_template_options(dialog, r.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function create_event_from_template(dialog, values) {
|
||||
let template_name = values.template;
|
||||
let options = {};
|
||||
|
||||
dialog
|
||||
.get_field("field_options")
|
||||
.$wrapper.find(".template-option:checked")
|
||||
.each(function () {
|
||||
options[$(this).data("option")] = 1;
|
||||
});
|
||||
|
||||
let additional_fields = {};
|
||||
for (let field of MANDATORY_FIELDS) {
|
||||
let field_obj = dialog.get_field(field);
|
||||
if (!field_obj.df.hidden && values[field]) {
|
||||
additional_fields[field] = values[field];
|
||||
}
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "event_manager.events.doctype.buzz_event.buzz_event.create_from_template",
|
||||
args: {
|
||||
template_name: template_name,
|
||||
options: JSON.stringify(options),
|
||||
additional_fields: JSON.stringify(additional_fields),
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __("Creating Event..."),
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
dialog.hide();
|
||||
frappe.show_alert({
|
||||
message: __("Event created successfully"),
|
||||
indicator: "green",
|
||||
});
|
||||
frappe.set_route("Form", "Pohodex Event Manager Event", r.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function show_create_from_template_dialog() {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Create Event from Template"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "template",
|
||||
label: __("Select Template"),
|
||||
options: "Event Template",
|
||||
reqd: 1,
|
||||
change: function () {
|
||||
on_template_selected(dialog);
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
fieldname: "missing_fields_section",
|
||||
label: __("Required Fields"),
|
||||
depends_on: "eval:doc.template",
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "missing_fields_info",
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "category",
|
||||
label: __("Category"),
|
||||
options: "Event Category",
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "host",
|
||||
label: __("Host"),
|
||||
options: "Event Host",
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
fieldname: "options_section",
|
||||
label: __("Select What to Copy"),
|
||||
depends_on: "eval:doc.template",
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "select_buttons",
|
||||
depends_on: "eval:doc.template",
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "field_options",
|
||||
depends_on: "eval:doc.template",
|
||||
},
|
||||
],
|
||||
size: "large",
|
||||
primary_action_label: __("Create Event"),
|
||||
primary_action: function (values) {
|
||||
create_event_from_template(dialog, values);
|
||||
},
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
frappe.listview_settings["Pohodex Event Manager Event"] = {
|
||||
onload: function (listview) {
|
||||
if (frappe.perm.has_perm("Event Template", 0, "read")) {
|
||||
listview.page.add_inner_button(__("Create from Template"), function () {
|
||||
show_create_from_template_dialog();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,731 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from event_manager.api import are_registrations_closed
|
||||
from event_manager.events.doctype.buzz_event.buzz_event import create_from_template
|
||||
from event_manager.events.doctype.event_template.event_template import create_template_from_event
|
||||
|
||||
|
||||
class TestBuzzEvent(FrappeTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.create_test_fixtures()
|
||||
|
||||
@classmethod
|
||||
def create_test_fixtures(cls):
|
||||
if not frappe.db.exists("Event Category", "Test Category"):
|
||||
frappe.get_doc({"doctype": "Event Category", "category_name": "Test Category"}).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Event Host", "Test Host"):
|
||||
frappe.get_doc({"doctype": "Event Host", "host_name": "Test Host"}).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
# ==================== Schedule Validation Tests ====================
|
||||
|
||||
def _make_event_with_schedule(self, schedule_overrides, **event_overrides):
|
||||
"""Helper to create a Pohodex Event Manager Event with a single schedule item for validation tests."""
|
||||
event_defaults = {
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Schedule Test Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": "2026-03-05",
|
||||
"end_date": "2026-03-06",
|
||||
"start_time": "9:00:00",
|
||||
"end_time": "18:00:00",
|
||||
}
|
||||
event_defaults.update(event_overrides)
|
||||
event = frappe.get_doc(event_defaults)
|
||||
|
||||
# Directly call validate_schedule instead of insert to avoid
|
||||
# needing linked Event Track records in the test database
|
||||
for row in schedule_overrides:
|
||||
event.append("schedule", row)
|
||||
return event
|
||||
|
||||
def test_schedule_start_time_after_event_start_is_valid(self):
|
||||
"""Schedule at 11:00 should be valid when event starts at 9:00 (regression: string comparison bug)"""
|
||||
event = self._make_event_with_schedule(
|
||||
[{"date": "2026-03-05", "start_time": "11:00:00", "end_time": "12:00:00"}]
|
||||
)
|
||||
# Should not raise
|
||||
event.validate_schedule()
|
||||
|
||||
def test_schedule_start_time_before_event_start_is_rejected(self):
|
||||
"""Schedule at 08:00 should be rejected when event starts at 9:00"""
|
||||
event = self._make_event_with_schedule(
|
||||
[{"date": "2026-03-05", "start_time": "08:00:00", "end_time": "08:30:00"}]
|
||||
)
|
||||
with self.assertRaises(frappe.exceptions.ValidationError):
|
||||
event.validate_schedule()
|
||||
|
||||
def test_schedule_end_time_after_event_end_is_rejected(self):
|
||||
"""Schedule ending at 19:00 should be rejected when event ends at 18:00"""
|
||||
event = self._make_event_with_schedule(
|
||||
[{"date": "2026-03-06", "start_time": "17:00:00", "end_time": "19:00:00"}]
|
||||
)
|
||||
with self.assertRaises(frappe.exceptions.ValidationError):
|
||||
event.validate_schedule()
|
||||
|
||||
def test_schedule_end_time_before_event_end_is_valid(self):
|
||||
"""Schedule ending at 16:30 should be valid when event ends at 18:00"""
|
||||
event = self._make_event_with_schedule(
|
||||
[{"date": "2026-03-06", "start_time": "16:00:00", "end_time": "16:30:00"}]
|
||||
)
|
||||
# Should not raise
|
||||
event.validate_schedule()
|
||||
|
||||
# ==================== Create from Template Tests ====================
|
||||
|
||||
def test_create_from_template_copies_direct_fields(self):
|
||||
"""Test that direct fields are copied from template to event"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Direct Fields Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"medium": "Online",
|
||||
"about": "About text",
|
||||
"short_description": "Short desc",
|
||||
"time_zone": "Asia/Kolkata",
|
||||
"allow_guest_booking": 1,
|
||||
"guest_verification_method": "Email OTP",
|
||||
"send_ticket_email": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": "GST",
|
||||
"tax_percentage": 18,
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"medium": 1,
|
||||
"about": 1,
|
||||
"short_description": 1,
|
||||
"time_zone": 1,
|
||||
"allow_guest_booking": 1,
|
||||
"guest_verification_method": 1,
|
||||
"send_ticket_email": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": 1,
|
||||
"tax_percentage": 1,
|
||||
}
|
||||
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
self.assertEqual(event.category, "Test Category")
|
||||
self.assertEqual(event.host, "Test Host")
|
||||
self.assertEqual(event.medium, "Online")
|
||||
self.assertEqual(event.about, "About text")
|
||||
self.assertEqual(event.short_description, "Short desc")
|
||||
self.assertEqual(event.time_zone, "Asia/Kolkata")
|
||||
self.assertEqual(event.allow_guest_booking, 1)
|
||||
self.assertEqual(event.guest_verification_method, "Email OTP")
|
||||
self.assertEqual(event.send_ticket_email, 1)
|
||||
self.assertEqual(event.apply_tax, 1)
|
||||
self.assertEqual(event.tax_label, "GST")
|
||||
self.assertEqual(event.tax_percentage, 18)
|
||||
|
||||
def test_create_from_template_respects_unselected_options(self):
|
||||
"""Test that unselected options are not copied"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Selective Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"medium": "In Person",
|
||||
"about": "Should not appear",
|
||||
"apply_tax": 1,
|
||||
"tax_percentage": 18,
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1, "medium": 0, "about": 0, "apply_tax": 0}
|
||||
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
self.assertEqual(event.category, "Test Category")
|
||||
self.assertEqual(event.host, "Test Host")
|
||||
self.assertFalse(event.about)
|
||||
self.assertFalse(event.apply_tax)
|
||||
|
||||
def test_create_from_template_additional_fields_override(self):
|
||||
"""Test that additional_fields override template values"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Override Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
# Don't copy category from template, provide via additional_fields
|
||||
options = {"host": 1}
|
||||
additional_fields = {"category": "Test Category"}
|
||||
|
||||
event_name = create_from_template(
|
||||
template.name, frappe.as_json(options), frappe.as_json(additional_fields)
|
||||
)
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
self.assertEqual(event.category, "Test Category")
|
||||
self.assertEqual(event.host, "Test Host")
|
||||
|
||||
def test_create_from_template_creates_ticket_types(self):
|
||||
"""Test that ticket types are created as linked documents"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Ticket Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_ticket_types": [
|
||||
{
|
||||
"title": "Early Bird",
|
||||
"price": 500,
|
||||
"currency": "INR",
|
||||
"is_published": 1,
|
||||
"max_tickets_available": 100,
|
||||
},
|
||||
{"title": "Regular", "price": 1000, "currency": "INR", "is_published": 1},
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1, "ticket_types": 1}
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
|
||||
ticket_types = frappe.get_all(
|
||||
"Event Ticket Type",
|
||||
filters={"event": event_name, "title": ["in", ["Early Bird", "Regular"]]},
|
||||
fields=["title", "price", "max_tickets_available"],
|
||||
order_by="price",
|
||||
)
|
||||
self.assertEqual(len(ticket_types), 2)
|
||||
self.assertEqual(ticket_types[0].title, "Early Bird")
|
||||
self.assertEqual(ticket_types[0].price, 500)
|
||||
self.assertEqual(ticket_types[0].max_tickets_available, 100)
|
||||
self.assertEqual(ticket_types[1].title, "Regular")
|
||||
self.assertEqual(ticket_types[1].price, 1000)
|
||||
|
||||
def test_create_from_template_creates_add_ons(self):
|
||||
"""Test that add-ons are created as linked documents"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "AddOn Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_add_ons": [
|
||||
{
|
||||
"title": "Workshop Access",
|
||||
"price": 2000,
|
||||
"currency": "INR",
|
||||
"enabled": 1,
|
||||
"user_selects_option": 1,
|
||||
"options": "Morning\nAfternoon",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1, "add_ons": 1}
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
|
||||
add_ons = frappe.get_all(
|
||||
"Ticket Add-on",
|
||||
filters={"event": event_name},
|
||||
fields=["title", "price", "user_selects_option", "options"],
|
||||
)
|
||||
self.assertEqual(len(add_ons), 1)
|
||||
self.assertEqual(add_ons[0].title, "Workshop Access")
|
||||
self.assertEqual(add_ons[0].price, 2000)
|
||||
self.assertEqual(add_ons[0].user_selects_option, 1)
|
||||
self.assertEqual(add_ons[0].options, "Morning\nAfternoon")
|
||||
|
||||
def test_create_from_template_creates_custom_fields(self):
|
||||
"""Test that custom fields are created as linked documents"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "CustomField Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_custom_fields": [
|
||||
{
|
||||
"label": "Company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Data",
|
||||
"applied_to": "Booking",
|
||||
"mandatory": 1,
|
||||
"enabled": 1,
|
||||
"placeholder": "Enter company name",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1, "custom_fields": 1}
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
|
||||
custom_fields = frappe.get_all(
|
||||
"Pohodex Event Manager Custom Field",
|
||||
filters={"event": event_name},
|
||||
fields=["label", "fieldtype", "mandatory", "placeholder"],
|
||||
)
|
||||
self.assertEqual(len(custom_fields), 1)
|
||||
self.assertEqual(custom_fields[0].label, "Company")
|
||||
self.assertEqual(custom_fields[0].fieldtype, "Data")
|
||||
self.assertEqual(custom_fields[0].mandatory, 1)
|
||||
self.assertEqual(custom_fields[0].placeholder, "Enter company name")
|
||||
|
||||
def test_create_from_template_skips_linked_docs_when_unselected(self):
|
||||
"""Test that linked docs are not created when options are 0"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Skip Linked Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_ticket_types": [
|
||||
{"title": "Skipped", "price": 100, "currency": "INR", "is_published": 1}
|
||||
],
|
||||
"template_add_ons": [
|
||||
{"title": "Skipped Addon", "price": 50, "currency": "INR", "enabled": 1}
|
||||
],
|
||||
"template_custom_fields": [
|
||||
{
|
||||
"label": "Skipped Field",
|
||||
"fieldname": "skipped",
|
||||
"fieldtype": "Data",
|
||||
"applied_to": "Booking",
|
||||
"enabled": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1, "ticket_types": 0, "add_ons": 0, "custom_fields": 0}
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
|
||||
self.assertEqual(
|
||||
len(frappe.get_all("Event Ticket Type", filters={"event": event_name, "title": "Skipped"})), 0
|
||||
)
|
||||
self.assertEqual(len(frappe.get_all("Ticket Add-on", filters={"event": event_name})), 0)
|
||||
self.assertEqual(len(frappe.get_all("Pohodex Event Manager Custom Field", filters={"event": event_name})), 0)
|
||||
|
||||
def test_create_from_template_sets_default_title_and_date(self):
|
||||
"""Test that event gets a default title and today's date"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Defaults Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1}
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
self.assertIn("Defaults Template", event.title)
|
||||
self.assertEqual(str(event.start_date), frappe.utils.today())
|
||||
|
||||
def test_create_from_template_copies_sponsorship_settings(self):
|
||||
"""Test that sponsorship settings are copied"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Sponsor Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"auto_send_pitch_deck": 1,
|
||||
"sponsor_deck_reply_to": "test@example.com",
|
||||
"sponsor_deck_cc": "cc@example.com",
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"auto_send_pitch_deck": 1,
|
||||
"sponsor_deck_reply_to": 1,
|
||||
"sponsor_deck_cc": 1,
|
||||
}
|
||||
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
self.assertEqual(event.auto_send_pitch_deck, 1)
|
||||
self.assertEqual(event.sponsor_deck_reply_to, "test@example.com")
|
||||
self.assertEqual(event.sponsor_deck_cc, "cc@example.com")
|
||||
|
||||
# ==================== Save as Template Tests ====================
|
||||
|
||||
def test_save_event_as_template_all_options(self):
|
||||
"""Test saving an event as template with all field options"""
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Full Save Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
"medium": "Online",
|
||||
"about": "Full event description",
|
||||
"apply_tax": 1,
|
||||
"tax_label": "GST",
|
||||
"tax_percentage": 18,
|
||||
}
|
||||
)
|
||||
event.insert()
|
||||
|
||||
# Create linked docs
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Ticket Type",
|
||||
"event": event.name,
|
||||
"title": "Gold",
|
||||
"price": 5000,
|
||||
"currency": "INR",
|
||||
"is_published": 1,
|
||||
}
|
||||
).insert()
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Ticket Add-on",
|
||||
"event": event.name,
|
||||
"title": "Parking",
|
||||
"price": 200,
|
||||
"currency": "INR",
|
||||
"enabled": 1,
|
||||
}
|
||||
).insert()
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Custom Field",
|
||||
"event": event.name,
|
||||
"label": "Designation",
|
||||
"fieldname": "designation",
|
||||
"fieldtype": "Data",
|
||||
"applied_to": "Booking",
|
||||
"enabled": 1,
|
||||
}
|
||||
).insert()
|
||||
|
||||
options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"medium": 1,
|
||||
"about": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": 1,
|
||||
"tax_percentage": 1,
|
||||
"ticket_types": 1,
|
||||
"add_ons": 1,
|
||||
"custom_fields": 1,
|
||||
}
|
||||
|
||||
template_name = create_template_from_event(
|
||||
str(event.name), "Full Save Template", frappe.as_json(options)
|
||||
)
|
||||
template = frappe.get_doc("Event Template", template_name)
|
||||
|
||||
self.assertEqual(template.category, "Test Category")
|
||||
self.assertEqual(template.host, "Test Host")
|
||||
self.assertEqual(template.medium, "Online")
|
||||
self.assertEqual(template.about, "Full event description")
|
||||
self.assertEqual(template.apply_tax, 1)
|
||||
self.assertEqual(template.tax_percentage, 18)
|
||||
|
||||
# Check linked docs (ticket types include default "Normal" created on event insert)
|
||||
gold_tickets = [t for t in template.template_ticket_types if t.title == "Gold"]
|
||||
self.assertEqual(len(gold_tickets), 1)
|
||||
self.assertEqual(gold_tickets[0].price, 5000)
|
||||
|
||||
self.assertEqual(len(template.template_add_ons), 1)
|
||||
self.assertEqual(template.template_add_ons[0].title, "Parking")
|
||||
|
||||
self.assertEqual(len(template.template_custom_fields), 1)
|
||||
self.assertEqual(template.template_custom_fields[0].label, "Designation")
|
||||
|
||||
def test_save_event_as_template_partial(self):
|
||||
"""Test saving event as template with partial options"""
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Partial Save Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
"medium": "In Person",
|
||||
"about": "Included",
|
||||
"apply_tax": 1,
|
||||
"tax_percentage": 18,
|
||||
}
|
||||
)
|
||||
event.insert()
|
||||
|
||||
options = {"category": 1, "about": 1, "medium": 0, "apply_tax": 0}
|
||||
|
||||
template_name = create_template_from_event(
|
||||
str(event.name), "Partial Save Template", frappe.as_json(options)
|
||||
)
|
||||
template = frappe.get_doc("Event Template", template_name)
|
||||
|
||||
self.assertEqual(template.category, "Test Category")
|
||||
self.assertEqual(template.about, "Included")
|
||||
self.assertFalse(template.host)
|
||||
self.assertFalse(template.apply_tax)
|
||||
|
||||
# ==================== Round Trip Test ====================
|
||||
|
||||
def test_round_trip_preserves_data(self):
|
||||
"""Test Event -> Template -> Event preserves all data"""
|
||||
original = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Round Trip Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
"medium": "Online",
|
||||
"about": "Round trip description",
|
||||
"apply_tax": 1,
|
||||
"tax_label": "Service Tax",
|
||||
"tax_percentage": 12,
|
||||
}
|
||||
)
|
||||
original.insert()
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Ticket Type",
|
||||
"event": original.name,
|
||||
"title": "Platinum",
|
||||
"price": 10000,
|
||||
"currency": "INR",
|
||||
"is_published": 1,
|
||||
"max_tickets_available": 25,
|
||||
}
|
||||
).insert()
|
||||
|
||||
# Event -> Template
|
||||
all_options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"medium": 1,
|
||||
"about": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": 1,
|
||||
"tax_percentage": 1,
|
||||
"ticket_types": 1,
|
||||
}
|
||||
template_name = create_template_from_event(
|
||||
str(original.name), "Round Trip Template", frappe.as_json(all_options)
|
||||
)
|
||||
|
||||
# Template -> New Event
|
||||
new_event_name = create_from_template(template_name, frappe.as_json(all_options))
|
||||
new_event = frappe.get_doc("Pohodex Event Manager Event", new_event_name)
|
||||
|
||||
self.assertEqual(new_event.category, original.category)
|
||||
self.assertEqual(new_event.host, original.host)
|
||||
self.assertEqual(new_event.medium, original.medium)
|
||||
self.assertEqual(new_event.about, original.about)
|
||||
self.assertEqual(new_event.tax_label, original.tax_label)
|
||||
self.assertEqual(new_event.tax_percentage, original.tax_percentage)
|
||||
|
||||
platinum_tickets = frappe.get_all(
|
||||
"Event Ticket Type",
|
||||
filters={"event": new_event_name, "title": "Platinum"},
|
||||
fields=["price", "max_tickets_available"],
|
||||
)
|
||||
self.assertEqual(len(platinum_tickets), 1)
|
||||
self.assertEqual(platinum_tickets[0].price, 10000)
|
||||
self.assertEqual(platinum_tickets[0].max_tickets_available, 25)
|
||||
|
||||
# ==================== Permission Tests ====================
|
||||
|
||||
def test_create_from_template_requires_template_read_permission(self):
|
||||
"""Test that creating from template requires read permission on Event Template"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Perm Test Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
# Create a user without Event Template read permission
|
||||
frappe.set_user("Guest")
|
||||
try:
|
||||
with self.assertRaises(frappe.exceptions.ValidationError):
|
||||
create_from_template(template.name, frappe.as_json({"category": 1, "host": 1}))
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_save_as_template_requires_create_permission(self):
|
||||
"""Test that saving as template requires create permission on Event Template"""
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Perm Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
}
|
||||
)
|
||||
event.insert()
|
||||
|
||||
frappe.set_user("Guest")
|
||||
try:
|
||||
with self.assertRaises(frappe.exceptions.ValidationError):
|
||||
create_template_from_event(str(event.name), "Perm Template", frappe.as_json({"category": 1}))
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
|
||||
class TestRegistrationsClosed(FrappeTestCase):
|
||||
"""Tests for the are_registrations_closed function with timezone handling."""
|
||||
|
||||
def _make_event(self, registrations_close_at=None, time_zone=None):
|
||||
"""Create a minimal event _dict for testing (no DB insert needed)."""
|
||||
return frappe._dict(
|
||||
registrations_close_at=registrations_close_at,
|
||||
time_zone=time_zone,
|
||||
)
|
||||
|
||||
def test_no_close_at_returns_false(self):
|
||||
"""When registrations_close_at is not set, registrations are open."""
|
||||
event = self._make_event()
|
||||
self.assertFalse(are_registrations_closed(event))
|
||||
|
||||
def test_future_close_at_returns_false(self):
|
||||
"""When close_at is in the future, registrations are open."""
|
||||
fake_now = datetime(2026, 6, 15, 10, 0, 0)
|
||||
event = self._make_event(
|
||||
registrations_close_at="2026-06-15 12:00:00", # 2 hours after fake_now
|
||||
time_zone="UTC",
|
||||
)
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_now):
|
||||
self.assertFalse(are_registrations_closed(event))
|
||||
|
||||
def test_past_close_at_returns_true(self):
|
||||
"""When close_at is in the past, registrations are closed."""
|
||||
fake_now = datetime(2026, 6, 15, 14, 0, 0)
|
||||
event = self._make_event(
|
||||
registrations_close_at="2026-06-15 12:00:00", # 2 hours before fake_now
|
||||
time_zone="UTC",
|
||||
)
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_now):
|
||||
self.assertTrue(are_registrations_closed(event))
|
||||
|
||||
def test_timezone_ahead_of_utc_closes_earlier(self):
|
||||
"""An event in Asia/Kolkata (UTC+5:30) should close before the same wall-clock time in UTC.
|
||||
|
||||
If it's 14:00 UTC, that's 19:30 IST.
|
||||
A close_at of 18:00 (naive, in event tz) is already past in IST but not in UTC.
|
||||
"""
|
||||
# Simulate 19:30 IST (= 14:00 UTC)
|
||||
fake_ist_now = datetime(2026, 6, 15, 19, 30, 0, tzinfo=timezone(timedelta(hours=5, minutes=30)))
|
||||
|
||||
event = self._make_event(
|
||||
registrations_close_at="2026-06-15 18:00:00", # 18:00 in event tz (IST)
|
||||
time_zone="Asia/Kolkata",
|
||||
)
|
||||
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_ist_now):
|
||||
# 19:30 IST > 18:00 IST → closed
|
||||
self.assertTrue(are_registrations_closed(event))
|
||||
|
||||
def test_timezone_behind_utc_stays_open_longer(self):
|
||||
"""An event in US/Pacific (UTC-7) should stay open longer than the same wall-clock in UTC.
|
||||
|
||||
If it's 23:00 UTC on June 15, that's 16:00 PDT on June 15.
|
||||
A close_at of 18:00 (naive, in event tz) is still in the future in PDT.
|
||||
"""
|
||||
# Simulate 16:00 PDT (= 23:00 UTC)
|
||||
fake_pdt_now = datetime(2026, 6, 15, 16, 0, 0, tzinfo=timezone(timedelta(hours=-7)))
|
||||
|
||||
event = self._make_event(
|
||||
registrations_close_at="2026-06-15 18:00:00", # 18:00 in event tz (PDT)
|
||||
time_zone="US/Pacific",
|
||||
)
|
||||
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_pdt_now):
|
||||
# 16:00 PDT < 18:00 PDT → still open
|
||||
self.assertFalse(are_registrations_closed(event))
|
||||
|
||||
def test_same_close_time_different_timezones(self):
|
||||
"""Same UTC instant, same close_at string — different result depending on event timezone.
|
||||
|
||||
At 2026-06-15 17:30 UTC:
|
||||
- Asia/Kolkata: 23:00 IST → 23:00 > 18:00 → closed
|
||||
- US/Pacific: 10:30 PDT → 10:30 < 18:00 → open
|
||||
"""
|
||||
close_at = "2026-06-15 18:00:00"
|
||||
|
||||
event_ist = self._make_event(registrations_close_at=close_at, time_zone="Asia/Kolkata")
|
||||
event_pdt = self._make_event(registrations_close_at=close_at, time_zone="US/Pacific")
|
||||
|
||||
# 17:30 UTC = 23:00 IST
|
||||
fake_ist_now = datetime(2026, 6, 15, 23, 0, 0, tzinfo=timezone(timedelta(hours=5, minutes=30)))
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_ist_now):
|
||||
self.assertTrue(are_registrations_closed(event_ist))
|
||||
|
||||
# 17:30 UTC = 10:30 PDT
|
||||
fake_pdt_now = datetime(2026, 6, 15, 10, 30, 0, tzinfo=timezone(timedelta(hours=-7)))
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_pdt_now):
|
||||
self.assertFalse(are_registrations_closed(event_pdt))
|
||||
|
||||
def test_falls_back_to_system_timezone_when_event_tz_not_set(self):
|
||||
"""When event has no time_zone, system timezone is used."""
|
||||
fake_now = datetime(2026, 6, 15, 14, 0, 0)
|
||||
event = self._make_event(
|
||||
registrations_close_at="2026-06-15 13:00:00", # 1 hour before fake_now
|
||||
time_zone=None,
|
||||
)
|
||||
with patch("event_manager.api.get_datetime_in_timezone", return_value=fake_now):
|
||||
self.assertTrue(are_registrations_closed(event))
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2026-03-20 00:00:00",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"form_doctype",
|
||||
"route",
|
||||
"copy_to_clipboard",
|
||||
"publish",
|
||||
"column_break_main",
|
||||
"auto_close_at",
|
||||
"section_break_success",
|
||||
"success_title",
|
||||
"success_message",
|
||||
"section_break_closed",
|
||||
"closed_title",
|
||||
"closed_message"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "form_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Route",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "publish",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Publish"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_main",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "auto_close_at",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Auto Close At"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_success",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Success"
|
||||
},
|
||||
{
|
||||
"fieldname": "success_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Success Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "success_message",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Success Message"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_closed",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Closed"
|
||||
},
|
||||
{
|
||||
"fieldname": "closed_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Closed Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "closed_message",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Closed Message"
|
||||
},
|
||||
{
|
||||
"fieldname": "copy_to_clipboard",
|
||||
"fieldtype": "Button",
|
||||
"in_list_view": 1,
|
||||
"label": "Copy link to clipboard"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-14 19:33:17.779162",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Pohodex Event Manager Event Form",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class BuzzEventForm(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
auto_close_at: DF.Datetime | None
|
||||
closed_message: DF.SmallText | None
|
||||
closed_title: DF.Data | None
|
||||
form_doctype: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
publish: DF.Check
|
||||
route: DF.Data
|
||||
success_message: DF.MarkdownEditor | None
|
||||
success_title: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_unique_route()
|
||||
|
||||
def validate_unique_route(self):
|
||||
for row in self.parentdoc.custom_forms:
|
||||
if row.name != self.name and row.route == self.route:
|
||||
frappe.throw(
|
||||
_("Duplicate route '{0}' in custom forms. Each form must have a unique route.").format(
|
||||
self.route
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Pohodex Event Manager Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,251 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-17 22:08:28.579289",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"general_section",
|
||||
"support_email",
|
||||
"ticketing_section",
|
||||
"allow_transfer_ticket_before_event_start_days",
|
||||
"column_break_ehsr",
|
||||
"allow_add_ons_change_before_event_start_days",
|
||||
"column_break_hagy",
|
||||
"allow_ticket_cancellation_request_before_event_start_days",
|
||||
"proposals_tab",
|
||||
"event_proposals_section",
|
||||
"accept_event_proposals",
|
||||
"event_proposal_banner_title",
|
||||
"column_break_lxjh",
|
||||
"allow_guest_event_proposals",
|
||||
"success_section",
|
||||
"event_proposal_success_title",
|
||||
"event_proposal_success_message",
|
||||
"login_tab",
|
||||
"login_banner_section",
|
||||
"login_banner",
|
||||
"communications_tab",
|
||||
"ticketing_emails_section",
|
||||
"default_ticket_email_template",
|
||||
"sponsorship_emails_section",
|
||||
"auto_send_pitch_deck",
|
||||
"sponsor_email_settings_section",
|
||||
"default_sponsor_deck_email_template",
|
||||
"default_sponsor_deck_reply_to",
|
||||
"column_break_sponsor",
|
||||
"default_sponsor_deck_cc",
|
||||
"section_break_vtep",
|
||||
"custom_fields_go_after_this"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "ticketing_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Ticketing"
|
||||
},
|
||||
{
|
||||
"default": "7",
|
||||
"fieldname": "allow_transfer_ticket_before_event_start_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Allow Transfer Ticket Before Event Start (Days)",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"default": "7",
|
||||
"fieldname": "allow_add_ons_change_before_event_start_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Allow Add Ons Change Before Event Start (Days)",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"default": "7",
|
||||
"fieldname": "allow_ticket_cancellation_request_before_event_start_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Allow Ticket Cancellation Request Before Event Start (Days)",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ehsr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hagy",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "general_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "General"
|
||||
},
|
||||
{
|
||||
"description": "Will be linked in emails, etc.",
|
||||
"fieldname": "support_email",
|
||||
"fieldtype": "Data",
|
||||
"label": "Support Email",
|
||||
"options": "Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "proposals_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Proposals"
|
||||
},
|
||||
{
|
||||
"fieldname": "event_proposals_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Event Proposals"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "accept_event_proposals",
|
||||
"fieldtype": "Check",
|
||||
"label": "Accept Event Proposals"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.accept_event_proposals",
|
||||
"fieldname": "allow_guest_event_proposals",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Guest Submission"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.accept_event_proposals",
|
||||
"fieldname": "event_proposal_banner_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Banner Title"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.accept_event_proposals",
|
||||
"fieldname": "event_proposal_success_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Success Title"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.accept_event_proposals",
|
||||
"fieldname": "event_proposal_success_message",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Success Message"
|
||||
},
|
||||
{
|
||||
"fieldname": "login_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Login"
|
||||
},
|
||||
{
|
||||
"fieldname": "login_banner_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Login Banner Config"
|
||||
},
|
||||
{
|
||||
"description": "Promotional message shown in the login/signup modal. Supports Markdown.",
|
||||
"fieldname": "login_banner",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Login Banner"
|
||||
},
|
||||
{
|
||||
"fieldname": "communications_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Communications"
|
||||
},
|
||||
{
|
||||
"fieldname": "ticketing_emails_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Ticketing"
|
||||
},
|
||||
{
|
||||
"description": "Default template for ticket confirmation emails. Can be overridden per event.",
|
||||
"fieldname": "default_ticket_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Ticket Email Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "sponsorship_emails_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Sponsorships"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "auto_send_pitch_deck",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Send Pitch Deck"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck",
|
||||
"fieldname": "sponsor_email_settings_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck",
|
||||
"description": "Default template for sponsorship pitch deck emails. Can be overridden per event.",
|
||||
"fieldname": "default_sponsor_deck_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Email Template",
|
||||
"mandatory_depends_on": "eval:doc.auto_send_pitch_deck",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck",
|
||||
"fieldname": "default_sponsor_deck_reply_to",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Default Reply To",
|
||||
"mandatory_depends_on": "eval:doc.auto_send_pitch_deck",
|
||||
"options": "Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_sponsor",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck",
|
||||
"fieldname": "default_sponsor_deck_cc",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Default CC"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_vtep",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_fields_go_after_this",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Custom Fields Go After This"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lxjh",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.accept_event_proposals",
|
||||
"fieldname": "success_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Success"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-29 09:46:29.455686",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Pohodex Event Manager Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class BuzzSettings(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
accept_event_proposals: DF.Check
|
||||
allow_add_ons_change_before_event_start_days: DF.Int
|
||||
allow_guest_event_proposals: DF.Check
|
||||
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 | None
|
||||
default_ticket_email_template: DF.Link | None
|
||||
event_proposal_banner_title: DF.Data | None
|
||||
event_proposal_success_message: DF.MarkdownEditor | None
|
||||
event_proposal_success_title: DF.Data | None
|
||||
login_banner: DF.MarkdownEditor | None
|
||||
support_email: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
"""Validate the settings."""
|
||||
self.validate_transfer_days()
|
||||
|
||||
def validate_transfer_days(self):
|
||||
"""Validate that transfer days is a reasonable value."""
|
||||
if self.allow_transfer_ticket_before_event_start_days is not None:
|
||||
if self.allow_transfer_ticket_before_event_start_days < 0:
|
||||
frappe.throw(_("Allow Transfer Ticket Before Event Start Days cannot be negative."))
|
||||
elif self.allow_transfer_ticket_before_event_start_days > 365:
|
||||
frappe.throw(_("Allow Transfer Ticket Before Event Start Days cannot be more than 365 days."))
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestBuzzSettings(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for BuzzSettings.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Event Category", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "prompt",
|
||||
"creation": "2025-07-19 11:30:07.885513",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"slug",
|
||||
"description",
|
||||
"column_break_mrmh",
|
||||
"banner_image",
|
||||
"meta_image",
|
||||
"icon_svg"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "icon_svg",
|
||||
"fieldtype": "Code",
|
||||
"label": "Icon SVG",
|
||||
"options": "HTML"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "banner_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Banner Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "slug",
|
||||
"fieldtype": "Data",
|
||||
"label": "Slug",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_mrmh",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Meta Image"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"image_field": "banner_image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Pohodex Event Manager Coupon Code",
|
||||
"link_fieldname": "event_category"
|
||||
}
|
||||
],
|
||||
"modified": "2026-05-08 12:38:57.139602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Category",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventCategory(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
banner_image: DF.AttachImage | None
|
||||
description: DF.SmallText | None
|
||||
enabled: DF.Check
|
||||
icon_svg: DF.Code | None
|
||||
meta_image: DF.AttachImage | None
|
||||
slug: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
if not self.slug:
|
||||
self.set_slug()
|
||||
|
||||
def set_slug(self):
|
||||
self.slug = frappe.website.utils.cleanup_page_name(self.name).replace("_", "-")
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventCategory(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventCategory.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Event Check In", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-07-29 20:00:04.578374",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"event",
|
||||
"date",
|
||||
"column_break_fxzb",
|
||||
"ticket",
|
||||
"section_break_tt1x",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_tt1x",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Event Check In",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "ticket.event",
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ticket",
|
||||
"fieldtype": "Link",
|
||||
"label": "Ticket",
|
||||
"options": "Event Ticket",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fxzb",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Date"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-18 16:51:30.061975",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Check In",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Frontdesk Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventCheckIn(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
amended_from: DF.Link | None
|
||||
date: DF.Date | None
|
||||
event: DF.Link
|
||||
ticket: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
def before_insert(self):
|
||||
if not self.date:
|
||||
self.date = frappe.utils.today()
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventCheckIn(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventCheckIn.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-09-25 19:04:14.697421",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"speaker"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "speaker",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Speaker",
|
||||
"options": "Speaker Profile",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-09-25 19:04:45.486029",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Featured Speaker",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventFeaturedSpeaker(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
speaker: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Event Feedback", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-14 14:10:08.833301",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"event",
|
||||
"how_can_we_improve",
|
||||
"section_break_additional",
|
||||
"additional_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "how_can_we_improve",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "How can we improve?"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_additional",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Additional Fields",
|
||||
"options": "Additional Field"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-14 14:11:07.484890",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Feedback",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"if_owner": 1,
|
||||
"read": 1,
|
||||
"role": "Pohodex Event Manager User"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventFeedback(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
event: DF.Link
|
||||
how_can_we_improve: DF.SmallText | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventFeedback(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventFeedback.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Event Host", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "prompt",
|
||||
"creation": "2025-07-19 11:36:30.869780",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"logo",
|
||||
"country",
|
||||
"social_media_links",
|
||||
"column_break_udkj",
|
||||
"by_line",
|
||||
"address",
|
||||
"section_break_hgnb",
|
||||
"about"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"fieldname": "logo",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Logo"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_udkj",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"fieldname": "by_line",
|
||||
"fieldtype": "Data",
|
||||
"label": "By Line"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hgnb",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "about",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "About"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"fieldname": "country",
|
||||
"fieldtype": "Link",
|
||||
"label": "Country",
|
||||
"options": "Country"
|
||||
},
|
||||
{
|
||||
"fieldname": "address",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "social_media_links",
|
||||
"fieldtype": "Table",
|
||||
"label": "Social Media Links",
|
||||
"options": "Social Media Link"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"image_field": "logo",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-26 12:24:58.729143",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Host",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventHost(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from event_manager.events.doctype.social_media_link.social_media_link import SocialMediaLink
|
||||
|
||||
about: DF.TextEditor | None
|
||||
address: DF.SmallText | None
|
||||
by_line: DF.Data | None
|
||||
country: DF.Link | None
|
||||
logo: DF.AttachImage | None
|
||||
social_media_links: DF.Table[SocialMediaLink]
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventHost(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventHost.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-12-22 14:26:23.528630",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_gateway"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "payment_gateway",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Gateway",
|
||||
"options": "Payment Gateway",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-22 14:27:25.962536",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Payment Gateway",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventPaymentGateway(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
payment_gateway: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Event Sponsor", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-07-19 12:48:18.436949",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company_name",
|
||||
"company_logo",
|
||||
"website",
|
||||
"column_break_ozou",
|
||||
"event",
|
||||
"tier",
|
||||
"country",
|
||||
"additional_section",
|
||||
"enquiry"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_ozou",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Company Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tier",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tier",
|
||||
"options": "Sponsorship Tier",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company_logo",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Company Logo",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Additional"
|
||||
},
|
||||
{
|
||||
"fieldname": "enquiry",
|
||||
"fieldtype": "Link",
|
||||
"label": "Enquiry",
|
||||
"options": "Sponsorship Enquiry"
|
||||
},
|
||||
{
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"label": "Website",
|
||||
"options": "URL"
|
||||
},
|
||||
{
|
||||
"fieldname": "country",
|
||||
"fieldtype": "Link",
|
||||
"label": "Country",
|
||||
"options": "Country"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"image_field": "company_logo",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-28 16:18:05.658346",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Sponsor",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "company_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventSponsor(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company_logo: DF.AttachImage
|
||||
company_name: DF.Data
|
||||
country: DF.Link | None
|
||||
enquiry: DF.Link | None
|
||||
event: DF.Link
|
||||
tier: DF.Link
|
||||
website: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
already_exists = frappe.db.exists(
|
||||
"Event Sponsor", {"event": self.event, "enquiry": self.enquiry, "name": ("!=", self.name)}
|
||||
)
|
||||
|
||||
if already_exists:
|
||||
frappe.throw(frappe._("Sponsor for this enquiry already exists!"))
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventSponsor(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventSponsor.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
def test_enquiry_to_sponsor_flow(self):
|
||||
test_event = frappe.get_doc("Pohodex Event Manager Event", {"route": "test-route"})
|
||||
test_sponsorship_tier = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Sponsorship Tier",
|
||||
"event": test_event.name,
|
||||
"title": "Super Platinum",
|
||||
"price": 1000,
|
||||
"currency": "INR",
|
||||
}
|
||||
).insert()
|
||||
|
||||
test_enquiry = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Sponsorship Enquiry",
|
||||
"event": test_event.name,
|
||||
"company_name": "Test Studios",
|
||||
"company_logo": "https://buildwithhussain.com/files/youtube2.png",
|
||||
"tier": test_sponsorship_tier.name,
|
||||
}
|
||||
).insert()
|
||||
|
||||
# "Payment Success trigger"
|
||||
test_enquiry.on_payment_authorized("Completed")
|
||||
self.assertEqual(test_enquiry.status, "Paid")
|
||||
|
||||
self.assertTrue(frappe.db.exists("Event Sponsor", {"enquiry": test_enquiry.name}))
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, BWH Studios and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Event Talk", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2025-07-19 12:15:38.420883",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"submitted_by",
|
||||
"column_break_npnb",
|
||||
"proposal",
|
||||
"event",
|
||||
"section_break_rnda",
|
||||
"speakers",
|
||||
"section_break_nrdk",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fetch_from": "proposal.title",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_rnda",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "speakers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Speakers",
|
||||
"options": "Talk Speaker"
|
||||
},
|
||||
{
|
||||
"fetch_from": "proposal.submitted_by",
|
||||
"fieldname": "submitted_by",
|
||||
"fieldtype": "Link",
|
||||
"label": "Submitted By",
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_npnb",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "proposal",
|
||||
"fieldtype": "Link",
|
||||
"label": "Proposal",
|
||||
"options": "Talk Proposal"
|
||||
},
|
||||
{
|
||||
"fetch_from": "proposal.event",
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Link",
|
||||
"label": "Event",
|
||||
"options": "Pohodex Event Manager Event",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_nrdk",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "proposal.description",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-14 12:38:51.583900",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Talk",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Pohodex Event Manager User",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2025, BWH Studios and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventTalk(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from event_manager.events.doctype.talk_speaker.talk_speaker import TalkSpeaker
|
||||
|
||||
description: DF.TextEditor | None
|
||||
event: DF.Link
|
||||
name: DF.Int | None
|
||||
proposal: DF.Link | None
|
||||
speakers: DF.Table[TalkSpeaker]
|
||||
submitted_by: DF.Link
|
||||
title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
if frappe.db.exists("Event Talk", {"proposal": self.proposal, "name": ["!=", self.name]}):
|
||||
frappe.throw("Talk already created for this proposal!")
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, BWH Studios and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestEventTalk(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for EventTalk.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
@@ -0,0 +1,327 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:template_name",
|
||||
"creation": "2025-12-31 12:00:00.000000",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"template_name",
|
||||
"category",
|
||||
"medium",
|
||||
"column_break_main",
|
||||
"banner_image",
|
||||
"host",
|
||||
"venue",
|
||||
"allow_guest_booking",
|
||||
"guest_verification_method",
|
||||
"section_break_details",
|
||||
"time_zone",
|
||||
"short_description",
|
||||
"about",
|
||||
"payments_tab",
|
||||
"section_break_payments",
|
||||
"payment_gateways",
|
||||
"tax_settings_section",
|
||||
"apply_tax",
|
||||
"tax_label",
|
||||
"tax_percentage",
|
||||
"sponsorships_tab",
|
||||
"automations_section",
|
||||
"auto_send_pitch_deck",
|
||||
"section_break_sponsor",
|
||||
"sponsor_deck_reply_to",
|
||||
"sponsor_deck_email_template",
|
||||
"column_break_sponsor",
|
||||
"sponsor_deck_cc",
|
||||
"section_break_attachments",
|
||||
"sponsor_deck_attachments",
|
||||
"customisations_tab",
|
||||
"send_ticket_email",
|
||||
"ticket_email_template",
|
||||
"column_break_custom",
|
||||
"ticket_print_format",
|
||||
"ticket_types_tab",
|
||||
"template_ticket_types",
|
||||
"add_ons_tab",
|
||||
"template_add_ons",
|
||||
"custom_fields_tab",
|
||||
"template_custom_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "template_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Template Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Category",
|
||||
"options": "Event Category"
|
||||
},
|
||||
{
|
||||
"default": "In Person",
|
||||
"fieldname": "medium",
|
||||
"fieldtype": "Select",
|
||||
"label": "Medium",
|
||||
"options": "In Person\nOnline"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_main",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "banner_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Banner Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "host",
|
||||
"fieldtype": "Link",
|
||||
"label": "Host",
|
||||
"options": "Event Host"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.medium!=\"Online\"",
|
||||
"fieldname": "venue",
|
||||
"fieldtype": "Link",
|
||||
"label": "Venue",
|
||||
"options": "Event Venue"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_guest_booking",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Guest Booking"
|
||||
},
|
||||
{
|
||||
"default": "Email OTP",
|
||||
"depends_on": "eval:doc.allow_guest_booking",
|
||||
"description": "How to verify guest identity before booking",
|
||||
"fieldname": "guest_verification_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Guest Verification Method",
|
||||
"options": "None\nEmail OTP\nPhone OTP"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_details",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "time_zone",
|
||||
"fieldtype": "Autocomplete",
|
||||
"label": "Time Zone"
|
||||
},
|
||||
{
|
||||
"fieldname": "short_description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Short Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "about",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "About"
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payments"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_payments",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_gateways",
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Gateways",
|
||||
"options": "Event Payment Gateway"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "apply_tax",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax on Bookings?"
|
||||
},
|
||||
{
|
||||
"default": "GST",
|
||||
"depends_on": "eval:doc.apply_tax==1",
|
||||
"fieldname": "tax_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tax Label"
|
||||
},
|
||||
{
|
||||
"default": "18",
|
||||
"depends_on": "eval:doc.apply_tax==1",
|
||||
"fieldname": "tax_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Tax Percentage"
|
||||
},
|
||||
{
|
||||
"fieldname": "sponsorships_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Sponsorships"
|
||||
},
|
||||
{
|
||||
"fieldname": "automations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Automations"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "auto_send_pitch_deck",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Send Pitch Deck?"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_sponsor",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_reply_to",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reply To"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Email Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_sponsor",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_cc",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "CC"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_attachments",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.auto_send_pitch_deck==true",
|
||||
"fieldname": "sponsor_deck_attachments",
|
||||
"fieldtype": "Table",
|
||||
"label": "Attachments",
|
||||
"options": "Sponsorship Deck Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "customisations_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Customisations"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "send_ticket_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Ticket Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "ticket_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Ticket Email Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_custom",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "ticket_print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Ticket Print Format",
|
||||
"link_filters": "[[\"Print Format\",\"doc_type\",\"=\",\"Event Ticket\"]]",
|
||||
"options": "Print Format"
|
||||
},
|
||||
{
|
||||
"fieldname": "ticket_types_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Ticket Types"
|
||||
},
|
||||
{
|
||||
"fieldname": "template_ticket_types",
|
||||
"fieldtype": "Table",
|
||||
"label": "Ticket Types",
|
||||
"options": "Event Template Ticket Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "add_ons_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Add-ons"
|
||||
},
|
||||
{
|
||||
"fieldname": "template_add_ons",
|
||||
"fieldtype": "Table",
|
||||
"label": "Add-ons",
|
||||
"options": "Event Template Add-on"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_fields_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Custom Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "template_custom_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Custom Fields",
|
||||
"options": "Event Template Custom Field"
|
||||
}
|
||||
],
|
||||
"image_field": "banner_image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-31 12:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Template",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Event Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "template_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventTemplate(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from event_manager.events.doctype.event_payment_gateway.event_payment_gateway import EventPaymentGateway
|
||||
from event_manager.events.doctype.event_template_add_on.event_template_add_on import EventTemplateAddOn
|
||||
from event_manager.events.doctype.event_template_custom_field.event_template_custom_field import (
|
||||
EventTemplateCustomField,
|
||||
)
|
||||
from event_manager.events.doctype.event_template_ticket_type.event_template_ticket_type import (
|
||||
EventTemplateTicketType,
|
||||
)
|
||||
from event_manager.proposals.doctype.sponsorship_deck_item.sponsorship_deck_item import SponsorshipDeckItem
|
||||
|
||||
about: DF.TextEditor | None
|
||||
apply_tax: DF.Check
|
||||
auto_send_pitch_deck: DF.Check
|
||||
banner_image: DF.AttachImage | None
|
||||
category: DF.Link | None
|
||||
host: DF.Link | None
|
||||
medium: DF.Literal["In Person", "Online"]
|
||||
payment_gateways: DF.Table[EventPaymentGateway]
|
||||
short_description: DF.SmallText | None
|
||||
sponsor_deck_attachments: DF.Table[SponsorshipDeckItem]
|
||||
sponsor_deck_cc: DF.SmallText | None
|
||||
sponsor_deck_email_template: DF.Link | None
|
||||
sponsor_deck_reply_to: DF.Data | None
|
||||
tax_label: DF.Data | None
|
||||
tax_percentage: DF.Percent
|
||||
template_add_ons: DF.Table[EventTemplateAddOn]
|
||||
template_custom_fields: DF.Table[EventTemplateCustomField]
|
||||
template_name: DF.Data
|
||||
template_ticket_types: DF.Table[EventTemplateTicketType]
|
||||
ticket_email_template: DF.Link | None
|
||||
ticket_print_format: DF.Link | None
|
||||
time_zone: DF.Autocomplete | None
|
||||
venue: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_template_from_event(event_name: str, template_name: str, options: str) -> str:
|
||||
"""
|
||||
Create an Event Template from an existing Pohodex Event Manager Event.
|
||||
|
||||
Args:
|
||||
event_name: Name of the source Pohodex Event Manager Event
|
||||
template_name: Name for the new template
|
||||
options: JSON string of what to include
|
||||
|
||||
Returns:
|
||||
New Event Template document name
|
||||
"""
|
||||
if not frappe.has_permission("Event Template", "create"):
|
||||
frappe.throw(_("You don't have permission to create templates"))
|
||||
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
options = frappe.parse_json(options)
|
||||
|
||||
template = frappe.new_doc("Event Template")
|
||||
template.template_name = template_name
|
||||
|
||||
# Field mapping for direct copy
|
||||
field_map = {
|
||||
"category": "category",
|
||||
"host": "host",
|
||||
"banner_image": "banner_image",
|
||||
"short_description": "short_description",
|
||||
"about": "about",
|
||||
"medium": "medium",
|
||||
"venue": "venue",
|
||||
"allow_guest_booking": "allow_guest_booking",
|
||||
"guest_verification_method": "guest_verification_method",
|
||||
"time_zone": "time_zone",
|
||||
"send_ticket_email": "send_ticket_email",
|
||||
"ticket_email_template": "ticket_email_template",
|
||||
"ticket_print_format": "ticket_print_format",
|
||||
"apply_tax": "apply_tax",
|
||||
"tax_label": "tax_label",
|
||||
"tax_percentage": "tax_percentage",
|
||||
"auto_send_pitch_deck": "auto_send_pitch_deck",
|
||||
"sponsor_deck_email_template": "sponsor_deck_email_template",
|
||||
"sponsor_deck_reply_to": "sponsor_deck_reply_to",
|
||||
"sponsor_deck_cc": "sponsor_deck_cc",
|
||||
}
|
||||
|
||||
for option_key, field_name in field_map.items():
|
||||
if options.get(option_key):
|
||||
template.set(field_name, event.get(field_name))
|
||||
|
||||
# Copy child tables from event
|
||||
if options.get("payment_gateways"):
|
||||
for pg in event.payment_gateways:
|
||||
template.append("payment_gateways", {"payment_gateway": pg.payment_gateway})
|
||||
|
||||
if options.get("sponsor_deck_attachments"):
|
||||
for attachment in event.sponsor_deck_attachments:
|
||||
template.append("sponsor_deck_attachments", {"file": attachment.file})
|
||||
|
||||
# Copy linked documents (Ticket Types, Add-ons, Custom Fields)
|
||||
if options.get("ticket_types"):
|
||||
ticket_types = frappe.get_all(
|
||||
"Event Ticket Type",
|
||||
filters={"event": event_name},
|
||||
fields=[
|
||||
"title",
|
||||
"price",
|
||||
"currency",
|
||||
"is_published",
|
||||
"max_tickets_available",
|
||||
"auto_unpublish_after",
|
||||
],
|
||||
)
|
||||
for tt in ticket_types:
|
||||
template.append(
|
||||
"template_ticket_types",
|
||||
{
|
||||
"title": tt.title,
|
||||
"price": tt.price,
|
||||
"currency": tt.currency,
|
||||
"is_published": tt.is_published,
|
||||
"max_tickets_available": tt.max_tickets_available,
|
||||
"auto_unpublish_after": tt.auto_unpublish_after,
|
||||
},
|
||||
)
|
||||
|
||||
if options.get("add_ons"):
|
||||
add_ons = frappe.get_all(
|
||||
"Ticket Add-on",
|
||||
filters={"event": event_name},
|
||||
fields=["title", "price", "currency", "description", "user_selects_option", "options", "enabled"],
|
||||
)
|
||||
for addon in add_ons:
|
||||
template.append(
|
||||
"template_add_ons",
|
||||
{
|
||||
"title": addon.title,
|
||||
"price": addon.price,
|
||||
"currency": addon.currency,
|
||||
"description": addon.description,
|
||||
"user_selects_option": addon.user_selects_option,
|
||||
"options": addon.options,
|
||||
"enabled": addon.enabled,
|
||||
},
|
||||
)
|
||||
|
||||
if options.get("custom_fields"):
|
||||
custom_fields = frappe.get_all(
|
||||
"Pohodex Event Manager Custom Field",
|
||||
filters={"event": event_name},
|
||||
fields=[
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"applied_to",
|
||||
"enabled",
|
||||
"mandatory",
|
||||
"placeholder",
|
||||
"default_value",
|
||||
"order",
|
||||
],
|
||||
)
|
||||
for cf in custom_fields:
|
||||
template.append(
|
||||
"template_custom_fields",
|
||||
{
|
||||
"label": cf.label,
|
||||
"fieldname": cf.fieldname,
|
||||
"fieldtype": cf.fieldtype,
|
||||
"options": cf.options,
|
||||
"applied_to": cf.applied_to,
|
||||
"enabled": cf.enabled,
|
||||
"mandatory": cf.mandatory,
|
||||
"placeholder": cf.placeholder,
|
||||
"default_value": cf.default_value,
|
||||
"order": cf.order,
|
||||
},
|
||||
)
|
||||
|
||||
template.insert()
|
||||
return template.name
|
||||
@@ -0,0 +1,505 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from event_manager.events.doctype.buzz_event.buzz_event import create_from_template
|
||||
from event_manager.events.doctype.event_template.event_template import create_template_from_event
|
||||
|
||||
|
||||
class TestEventTemplate(FrappeTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.create_test_fixtures()
|
||||
|
||||
@classmethod
|
||||
def create_test_fixtures(cls):
|
||||
"""Create required test data: Event Category, Host, etc."""
|
||||
# Create Event Category if not exists
|
||||
if not frappe.db.exists("Event Category", "Test Category"):
|
||||
frappe.get_doc({"doctype": "Event Category", "category_name": "Test Category"}).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
|
||||
# Create Event Host if not exists
|
||||
if not frappe.db.exists("Event Host", "Test Host"):
|
||||
frappe.get_doc({"doctype": "Event Host", "host_name": "Test Host"}).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up test data after each test"""
|
||||
frappe.db.rollback()
|
||||
|
||||
# ==================== Template Creation Tests ====================
|
||||
|
||||
def test_create_template_basic(self):
|
||||
"""Test creating a basic Event Template"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Test Webinar Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"medium": "Online",
|
||||
"about": "Test description",
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
self.assertEqual(template.template_name, "Test Webinar Template")
|
||||
self.assertEqual(template.category, "Test Category")
|
||||
self.assertEqual(template.medium, "Online")
|
||||
|
||||
def test_create_template_with_ticket_types(self):
|
||||
"""Test creating a template with ticket types"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Template with Tickets",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_ticket_types": [
|
||||
{
|
||||
"title": "Early Bird",
|
||||
"price": 100,
|
||||
"currency": "INR",
|
||||
"is_published": 1,
|
||||
"max_tickets_available": 50,
|
||||
},
|
||||
{"title": "Regular", "price": 200, "currency": "INR", "is_published": 1},
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
self.assertEqual(len(template.template_ticket_types), 2)
|
||||
self.assertEqual(template.template_ticket_types[0].title, "Early Bird")
|
||||
self.assertEqual(template.template_ticket_types[0].price, 100)
|
||||
|
||||
def test_create_template_with_add_ons(self):
|
||||
"""Test creating a template with add-ons"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Template with Add-ons",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_add_ons": [
|
||||
{"title": "T-Shirt", "price": 500, "currency": "INR", "enabled": 1},
|
||||
{
|
||||
"title": "Lunch",
|
||||
"price": 300,
|
||||
"currency": "INR",
|
||||
"user_selects_option": 1,
|
||||
"options": "Veg\nNon-Veg",
|
||||
"enabled": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
self.assertEqual(len(template.template_add_ons), 2)
|
||||
self.assertEqual(template.template_add_ons[1].user_selects_option, 1)
|
||||
|
||||
def test_create_template_with_custom_fields(self):
|
||||
"""Test creating a template with custom fields"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Template with Custom Fields",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_custom_fields": [
|
||||
{
|
||||
"label": "Company Name",
|
||||
"fieldname": "company_name",
|
||||
"fieldtype": "Data",
|
||||
"applied_to": "Booking",
|
||||
"mandatory": 1,
|
||||
"enabled": 1,
|
||||
},
|
||||
{
|
||||
"label": "Dietary Preference",
|
||||
"fieldname": "dietary_preference",
|
||||
"fieldtype": "Select",
|
||||
"options": "Veg\nNon-Veg\nVegan",
|
||||
"applied_to": "Ticket",
|
||||
"enabled": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
self.assertEqual(len(template.template_custom_fields), 2)
|
||||
self.assertEqual(template.template_custom_fields[0].mandatory, 1)
|
||||
|
||||
# ==================== Create Event from Template Tests ====================
|
||||
|
||||
def test_create_event_from_template_all_options(self):
|
||||
"""Test creating an event from template with all options selected"""
|
||||
# Create template
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Full Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"medium": "Online",
|
||||
"about": "Template about text",
|
||||
"apply_tax": 1,
|
||||
"tax_label": "GST",
|
||||
"tax_percentage": 18,
|
||||
"template_ticket_types": [
|
||||
{"title": "Standard", "price": 500, "currency": "INR", "is_published": 1}
|
||||
],
|
||||
"template_add_ons": [{"title": "Workshop", "price": 1000, "currency": "INR", "enabled": 1}],
|
||||
"template_custom_fields": [
|
||||
{
|
||||
"label": "Phone",
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Phone",
|
||||
"applied_to": "Booking",
|
||||
"enabled": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
# Create event from template with all options
|
||||
options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"medium": 1,
|
||||
"about": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": 1,
|
||||
"tax_percentage": 1,
|
||||
"ticket_types": 1,
|
||||
"add_ons": 1,
|
||||
"custom_fields": 1,
|
||||
}
|
||||
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
# Verify event fields
|
||||
self.assertEqual(event.category, "Test Category")
|
||||
self.assertEqual(event.host, "Test Host")
|
||||
self.assertEqual(event.medium, "Online")
|
||||
self.assertEqual(event.about, "Template about text")
|
||||
self.assertEqual(event.apply_tax, 1)
|
||||
self.assertEqual(event.tax_percentage, 18)
|
||||
|
||||
# Verify ticket types created (excluding default "Normal" ticket type)
|
||||
ticket_types = frappe.get_all(
|
||||
"Event Ticket Type", filters={"event": event_name, "title": "Standard"}, fields=["title", "price"]
|
||||
)
|
||||
self.assertEqual(len(ticket_types), 1)
|
||||
self.assertEqual(ticket_types[0].title, "Standard")
|
||||
|
||||
# Verify add-ons created
|
||||
add_ons = frappe.get_all("Ticket Add-on", filters={"event": event_name}, fields=["title", "price"])
|
||||
self.assertEqual(len(add_ons), 1)
|
||||
self.assertEqual(add_ons[0].title, "Workshop")
|
||||
|
||||
# Verify custom fields created
|
||||
custom_fields = frappe.get_all(
|
||||
"Pohodex Event Manager Custom Field", filters={"event": event_name}, fields=["label", "fieldtype"]
|
||||
)
|
||||
self.assertEqual(len(custom_fields), 1)
|
||||
self.assertEqual(custom_fields[0].fieldtype, "Phone")
|
||||
|
||||
def test_create_event_from_template_partial_options(self):
|
||||
"""Test creating an event with only some options selected"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Partial Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"medium": "In Person",
|
||||
"about": "Should not be copied",
|
||||
"template_ticket_types": [
|
||||
{"title": "VIP", "price": 2000, "currency": "INR", "is_published": 1}
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
# Copy category, host (required) and ticket types, but not medium/about
|
||||
options = {"category": 1, "host": 1, "medium": 0, "about": 0, "ticket_types": 1}
|
||||
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
|
||||
# Category should be copied
|
||||
self.assertEqual(event.category, "Test Category")
|
||||
|
||||
# Host should be copied (it's mandatory)
|
||||
self.assertEqual(event.host, "Test Host")
|
||||
|
||||
# About should NOT be copied
|
||||
self.assertFalse(event.about)
|
||||
|
||||
# Ticket types should be copied
|
||||
ticket_types = frappe.get_all("Event Ticket Type", filters={"event": event_name, "title": "VIP"})
|
||||
self.assertEqual(len(ticket_types), 1)
|
||||
|
||||
def test_create_event_from_template_no_linked_docs(self):
|
||||
"""Test creating an event without copying linked documents"""
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "No Linked Docs Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"template_ticket_types": [
|
||||
{"title": "General", "price": 100, "currency": "INR", "is_published": 1}
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
# Copy fields but not linked docs
|
||||
options = {"category": 1, "host": 1, "ticket_types": 0, "add_ons": 0, "custom_fields": 0}
|
||||
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
|
||||
# Event fields should be copied
|
||||
event = frappe.get_doc("Pohodex Event Manager Event", event_name)
|
||||
self.assertEqual(event.category, "Test Category")
|
||||
|
||||
# No "General" ticket type should be created (only default "Normal")
|
||||
ticket_types = frappe.get_all("Event Ticket Type", filters={"event": event_name, "title": "General"})
|
||||
self.assertEqual(len(ticket_types), 0)
|
||||
|
||||
# ==================== Save as Template Tests ====================
|
||||
|
||||
def test_save_event_as_template(self):
|
||||
"""Test saving an existing event as a template"""
|
||||
# Create an event with ticket types and add-ons
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Source Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
"medium": "Online",
|
||||
"about": "Event description",
|
||||
}
|
||||
)
|
||||
event.insert()
|
||||
|
||||
# Create ticket type for the event
|
||||
ticket_type = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Ticket Type",
|
||||
"event": event.name,
|
||||
"title": "Premium",
|
||||
"price": 1500,
|
||||
"currency": "INR",
|
||||
"is_published": 1,
|
||||
}
|
||||
)
|
||||
ticket_type.insert()
|
||||
|
||||
# Create add-on for the event
|
||||
add_on = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Ticket Add-on",
|
||||
"event": event.name,
|
||||
"title": "Swag Kit",
|
||||
"price": 500,
|
||||
"currency": "INR",
|
||||
"enabled": 1,
|
||||
}
|
||||
)
|
||||
add_on.insert()
|
||||
|
||||
# Save as template (convert event.name to string as it's an int autoname)
|
||||
options = {"category": 1, "host": 1, "medium": 1, "about": 1, "ticket_types": 1, "add_ons": 1}
|
||||
|
||||
template_name = create_template_from_event(
|
||||
str(event.name), "My Event Template", frappe.as_json(options)
|
||||
)
|
||||
template = frappe.get_doc("Event Template", template_name)
|
||||
|
||||
# Verify template fields
|
||||
self.assertEqual(template.template_name, "My Event Template")
|
||||
self.assertEqual(template.category, "Test Category")
|
||||
self.assertEqual(template.medium, "Online")
|
||||
|
||||
# Verify ticket types in template (excluding default "Normal")
|
||||
premium_tickets = [t for t in template.template_ticket_types if t.title == "Premium"]
|
||||
self.assertEqual(len(premium_tickets), 1)
|
||||
self.assertEqual(premium_tickets[0].price, 1500)
|
||||
|
||||
# Verify add-ons in template
|
||||
self.assertEqual(len(template.template_add_ons), 1)
|
||||
self.assertEqual(template.template_add_ons[0].title, "Swag Kit")
|
||||
|
||||
def test_save_event_as_template_partial(self):
|
||||
"""Test saving event as template with only some options"""
|
||||
event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Partial Source Event",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
"medium": "In Person",
|
||||
"about": "Should be copied",
|
||||
"apply_tax": 1,
|
||||
"tax_percentage": 18,
|
||||
}
|
||||
)
|
||||
event.insert()
|
||||
|
||||
# Only save category and about (convert event.name to string as it's an int autoname)
|
||||
options = {"category": 1, "host": 0, "medium": 0, "about": 1, "apply_tax": 0}
|
||||
|
||||
template_name = create_template_from_event(
|
||||
str(event.name), "Partial Template 2", frappe.as_json(options)
|
||||
)
|
||||
template = frappe.get_doc("Event Template", template_name)
|
||||
|
||||
self.assertEqual(template.category, "Test Category")
|
||||
self.assertEqual(template.about, "Should be copied")
|
||||
self.assertFalse(template.host)
|
||||
self.assertFalse(template.apply_tax)
|
||||
|
||||
# ==================== Round Trip Tests ====================
|
||||
|
||||
def test_round_trip_event_to_template_to_event(self):
|
||||
"""Test full round trip: Event -> Template -> New Event"""
|
||||
# Step 1: Create original event
|
||||
original_event = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Pohodex Event Manager Event",
|
||||
"title": "Original Conference",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
"start_date": frappe.utils.today(),
|
||||
"start_time": "09:00:00",
|
||||
"end_time": "18:00:00",
|
||||
"medium": "In Person",
|
||||
"about": "Annual conference description",
|
||||
"apply_tax": 1,
|
||||
"tax_label": "GST",
|
||||
"tax_percentage": 18,
|
||||
}
|
||||
)
|
||||
original_event.insert()
|
||||
|
||||
# Add ticket types
|
||||
for ticket_data in [
|
||||
{"title": "Early Bird", "price": 1000},
|
||||
{"title": "Regular", "price": 1500},
|
||||
{"title": "VIP", "price": 3000},
|
||||
]:
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Ticket Type",
|
||||
"event": original_event.name,
|
||||
"title": ticket_data["title"],
|
||||
"price": ticket_data["price"],
|
||||
"currency": "INR",
|
||||
"is_published": 1,
|
||||
}
|
||||
).insert()
|
||||
|
||||
# Step 2: Save as template (convert event.name to string as it's an int autoname)
|
||||
template_options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"medium": 1,
|
||||
"about": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": 1,
|
||||
"tax_percentage": 1,
|
||||
"ticket_types": 1,
|
||||
}
|
||||
template_name = create_template_from_event(
|
||||
str(original_event.name), "Conference Template", frappe.as_json(template_options)
|
||||
)
|
||||
|
||||
# Step 3: Create new event from template
|
||||
event_options = {
|
||||
"category": 1,
|
||||
"host": 1,
|
||||
"medium": 1,
|
||||
"about": 1,
|
||||
"apply_tax": 1,
|
||||
"tax_label": 1,
|
||||
"tax_percentage": 1,
|
||||
"ticket_types": 1,
|
||||
}
|
||||
new_event_name = create_from_template(template_name, frappe.as_json(event_options))
|
||||
new_event = frappe.get_doc("Pohodex Event Manager Event", new_event_name)
|
||||
|
||||
# Verify new event matches original
|
||||
self.assertEqual(new_event.category, original_event.category)
|
||||
self.assertEqual(new_event.host, original_event.host)
|
||||
self.assertEqual(new_event.medium, original_event.medium)
|
||||
self.assertEqual(new_event.about, original_event.about)
|
||||
self.assertEqual(new_event.tax_percentage, original_event.tax_percentage)
|
||||
|
||||
# Verify ticket types match (excluding default "Normal")
|
||||
new_ticket_types = frappe.get_all(
|
||||
"Event Ticket Type",
|
||||
filters={"event": new_event_name, "title": ["in", ["Early Bird", "Regular", "VIP"]]},
|
||||
fields=["title", "price"],
|
||||
order_by="price",
|
||||
)
|
||||
self.assertEqual(len(new_ticket_types), 3)
|
||||
self.assertEqual(new_ticket_types[0].title, "Early Bird")
|
||||
self.assertEqual(new_ticket_types[0].price, 1000)
|
||||
|
||||
# ==================== Edge Case Tests ====================
|
||||
|
||||
def test_create_event_empty_template(self):
|
||||
"""Test creating event from template with minimal data"""
|
||||
# Template with required fields for Pohodex Event Manager Event (category and host are mandatory)
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Event Template",
|
||||
"template_name": "Empty Template",
|
||||
"category": "Test Category",
|
||||
"host": "Test Host",
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
options = {"category": 1, "host": 1}
|
||||
event_name = create_from_template(template.name, frappe.as_json(options))
|
||||
|
||||
# Should create event without errors
|
||||
self.assertTrue(frappe.db.exists("Pohodex Event Manager Event", event_name))
|
||||
|
||||
def test_template_name_required(self):
|
||||
"""Test that template_name is required"""
|
||||
template = frappe.get_doc({"doctype": "Event Template", "category": "Test Category"})
|
||||
|
||||
# Template uses autoname: field:template_name, so it raises ValidationError not MandatoryError
|
||||
with self.assertRaises(frappe.exceptions.ValidationError):
|
||||
template.insert()
|
||||
|
||||
def test_duplicate_template_name(self):
|
||||
"""Test handling of duplicate template names"""
|
||||
frappe.get_doc({"doctype": "Event Template", "template_name": "Duplicate Name"}).insert()
|
||||
|
||||
duplicate = frappe.get_doc({"doctype": "Event Template", "template_name": "Duplicate Name"})
|
||||
|
||||
with self.assertRaises(frappe.exceptions.DuplicateEntryError):
|
||||
duplicate.insert()
|
||||
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-12-31 12:00:00.000000",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"title",
|
||||
"price",
|
||||
"description",
|
||||
"column_break_abcd",
|
||||
"currency",
|
||||
"user_selects_option",
|
||||
"options"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled?"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "price",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Price",
|
||||
"non_negative": 1,
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_abcd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "INR",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "user_selects_option",
|
||||
"fieldtype": "Check",
|
||||
"label": "User Selects Option?"
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Options",
|
||||
"mandatory_depends_on": "eval:doc.user_selects_option==true"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-31 12:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Template Add-on",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventTemplateAddon(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
currency: DF.Link | None
|
||||
description: DF.SmallText | None
|
||||
enabled: DF.Check
|
||||
options: DF.SmallText | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
price: DF.Currency
|
||||
title: DF.Data
|
||||
user_selects_option: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-12-31 12:00:00.000000",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"label",
|
||||
"fieldname",
|
||||
"mandatory",
|
||||
"placeholder",
|
||||
"default_value",
|
||||
"column_break_efgh",
|
||||
"applied_to",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"order"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled?"
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "mandatory",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mandatory?"
|
||||
},
|
||||
{
|
||||
"fieldname": "placeholder",
|
||||
"fieldtype": "Data",
|
||||
"label": "Placeholder"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_value",
|
||||
"fieldtype": "Data",
|
||||
"label": "Default Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_efgh",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Booking",
|
||||
"fieldname": "applied_to",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Applied To",
|
||||
"options": "Booking\nTicket"
|
||||
},
|
||||
{
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"options": "Data\nPhone\nEmail\nSelect\nDate\nNumber",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Options"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "order",
|
||||
"fieldtype": "Int",
|
||||
"label": "Order",
|
||||
"non_negative": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-31 12:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Template Custom Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class EventTemplateCustomField(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
applied_to: DF.Literal["Booking", "Ticket"]
|
||||
default_value: DF.Data | None
|
||||
enabled: DF.Check
|
||||
fieldname: DF.Data | None
|
||||
fieldtype: DF.Literal["Data", "Phone", "Email", "Select", "Date", "Number"]
|
||||
label: DF.Data
|
||||
mandatory: DF.Check
|
||||
options: DF.SmallText | None
|
||||
order: DF.Int
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
placeholder: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
# Copyright (c) 2025, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-12-31 12:00:00.000000",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"price",
|
||||
"currency",
|
||||
"column_break_xyzq",
|
||||
"is_published",
|
||||
"max_tickets_available",
|
||||
"auto_unpublish_after"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "price",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Price",
|
||||
"non_negative": 1,
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"default": "INR",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xyzq",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "is_published",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Published?"
|
||||
},
|
||||
{
|
||||
"fieldname": "max_tickets_available",
|
||||
"fieldtype": "Int",
|
||||
"label": "Max Tickets Available",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_published",
|
||||
"fieldname": "auto_unpublish_after",
|
||||
"fieldtype": "Date",
|
||||
"label": "Auto Unpublish After"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-31 12:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Events",
|
||||
"name": "Event Template Ticket Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user