Initialize fork and rebrand app to event_manager
CI / Server (push) Has been cancelled
Linters / Frappe Linter (push) Has been cancelled
Linters / Vulnerable Dependency Check (push) Has been cancelled
UI Tests / Playwright E2E Tests (push) Has been cancelled

This commit is contained in:
2026-05-11 09:56:57 +02:00
parent f82bb803ac
commit 786cbc724f
500 changed files with 41152 additions and 2 deletions
View File
@@ -0,0 +1,10 @@
// Copyright (c) 2025, BWH Studios and contributors
// For license information, please see license.txt
frappe.ui.form.on("Event Proposal", {
refresh(frm) {
if (!frm.is_new() && frm.doc.docstatus == 0) {
frm.set_intro("Pohodex Event Manager Event will be created on submission of this document", "yellow");
}
},
});
@@ -0,0 +1,215 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2025-12-10 17:17:09.702412",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"title",
"category",
"free_webinar",
"medium",
"column_break_bixo",
"status",
"naming_series",
"section_break_psfj",
"start_date",
"start_time",
"column_break_wfaw",
"end_date",
"end_time",
"section_break_zajc",
"short_description",
"column_break_yxbj",
"about",
"host_tab",
"host",
"section_break_xzvg",
"host_company",
"additional_notes",
"host_company_logo",
"section_break_kray",
"amended_from"
],
"fields": [
{
"default": "Received",
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "Received\nIn Review\nApproved\nEvent Created\nRejected"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "EPR-.###"
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "column_break_bixo",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_zajc",
"fieldtype": "Section Break"
},
{
"fieldname": "host_tab",
"fieldtype": "Tab Break",
"label": "Host"
},
{
"fieldname": "host_company",
"fieldtype": "Data",
"label": "Host Company"
},
{
"fieldname": "additional_notes",
"fieldtype": "Small Text",
"label": "Additional Notes"
},
{
"description": "Required for creating an event",
"fieldname": "host",
"fieldtype": "Link",
"label": "Host",
"options": "Event Host"
},
{
"fieldname": "section_break_kray",
"fieldtype": "Section Break"
},
{
"fieldname": "host_company_logo",
"fieldtype": "Attach Image",
"label": "Host Company Logo"
},
{
"fieldname": "section_break_psfj",
"fieldtype": "Section Break"
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date",
"reqd": 1
},
{
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date"
},
{
"fieldname": "column_break_wfaw",
"fieldtype": "Column Break"
},
{
"fieldname": "start_time",
"fieldtype": "Time",
"label": "Start Time"
},
{
"fieldname": "end_time",
"fieldtype": "Time",
"label": "End Time"
},
{
"fieldname": "about",
"fieldtype": "Text Editor",
"label": "About the event",
"reqd": 1
},
{
"fieldname": "column_break_yxbj",
"fieldtype": "Column Break"
},
{
"fieldname": "short_description",
"fieldtype": "Small Text",
"label": "Short Description"
},
{
"default": "Online",
"fieldname": "medium",
"fieldtype": "Select",
"label": "Medium",
"options": "Online\nIn Person",
"reqd": 1
},
{
"default": "0",
"depends_on": "eval:doc.event_category==\"Webinars\"",
"fieldname": "free_webinar",
"fieldtype": "Check",
"label": "Free Webinar?"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Event Proposal",
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"fieldname": "category",
"fieldtype": "Link",
"label": "Event Category",
"options": "Event Category",
"reqd": 1
},
{
"fieldname": "section_break_xzvg",
"fieldtype": "Section Break"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-12-11 11:36:40.618179",
"modified_by": "Administrator",
"module": "Proposals",
"name": "Event Proposal",
"naming_rule": "By \"Naming Series\" 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": [
{
"color": "Green",
"title": "Event Created"
},
{
"color": "Orange",
"title": "In Review"
}
]
}
@@ -0,0 +1,83 @@
# 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.mapper import get_mapped_doc
from frappe.utils.data import get_url_to_form, getdate, today
class EventProposal(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
about: DF.TextEditor
additional_notes: DF.SmallText | None
amended_from: DF.Link | None
category: DF.Link
end_date: DF.Date | None
end_time: DF.Time | None
free_webinar: DF.Check
host: DF.Link | None
host_company: DF.Data | None
host_company_logo: DF.AttachImage | None
medium: DF.Literal["Online", "In Person"]
naming_series: DF.Literal["EPR-.###"]
short_description: DF.SmallText | None
start_date: DF.Date
start_time: DF.Time | None
status: DF.Literal["Received", "In Review", "Approved", "Event Created", "Rejected"]
title: DF.Data
# end: auto-generated types
def validate(self):
self.validate_dates()
self.validate_times()
def validate_dates(self):
if getdate(self.start_date) < getdate(today()):
frappe.throw(_("Start Date cannot be in the past."))
if self.end_date and getdate(self.end_date) < getdate(self.start_date):
frappe.throw(_("End Date cannot be before Start Date."))
def validate_times(self):
if not self.start_time or not self.end_time:
return
same_day = not self.end_date or getdate(self.end_date) == getdate(self.start_date)
if same_day and self.end_time <= self.start_time:
frappe.throw(_("End Time must be after Start Time for same-day events."))
def before_submit(self):
if self.status not in ("Approved", "Rejected"):
frappe.throw(_("Only Approved or Rejected proposals can be submitted."))
self.create_event()
def create_event(self):
if self.status == "Rejected":
return
if not self.host:
frappe.throw(_("Please create or set a Host before submitting the proposal."))
buzz_event = get_mapped_doc(
"Event Proposal", self.name, {"Event Proposal": {"doctype": "Pohodex Event Manager Event"}}
)
buzz_event.proposal = self.name
buzz_event.insert()
self.status = "Event Created"
frappe.msgprint(
_("Pohodex Event Manager Event {0} created successfully.").format(
f'<a href="{get_url_to_form("Pohodex Event Manager Event", buzz_event.name)}">{buzz_event.title}</a>'
)
)
@@ -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 IntegrationTestEventProposal(IntegrationTestCase):
"""
Integration tests for EventProposal.
Use this class for testing interactions between multiple components.
"""
pass
@@ -0,0 +1,55 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-07-26 12:07:26.497133",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"first_name",
"last_name",
"column_break_ouof",
"email"
],
"fields": [
{
"fieldname": "first_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "First Name",
"reqd": 1
},
{
"fieldname": "last_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Last Name"
},
{
"fieldname": "column_break_ouof",
"fieldtype": "Column Break"
},
{
"fieldname": "email",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Email",
"options": "Email",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-07-26 12:12:56.836403",
"modified_by": "Administrator",
"module": "Proposals",
"name": "Proposal Speaker",
"owner": "Administrator",
"permissions": [],
"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 ProposalSpeaker(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
email: DF.Data
first_name: DF.Data
last_name: DF.Data | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass
@@ -0,0 +1,35 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-10-29 17:07:45.086186",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"file"
],
"fields": [
{
"fieldname": "file",
"fieldtype": "Attach",
"in_list_view": 1,
"label": "File",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-10-29 17:07:59.277650",
"modified_by": "Administrator",
"module": "Proposals",
"name": "Sponsorship Deck Item",
"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 SponsorshipDeckItem(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
file: DF.Attach
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass
@@ -0,0 +1,22 @@
// Copyright (c) 2025, BWH Studios and contributors
// For license information, please see license.txt
frappe.ui.form.on("Sponsorship Enquiry", {
refresh(frm) {
if (!frm.doc.__islocal) {
if (frm.doc.status === "Approval Pending") {
frm.add_custom_button(__("Approve"), () => {
frm.set_value("status", "Payment Pending");
frm.save();
});
}
frm.add_custom_button(__("Create Sponsor"), () => {
frm.call("create_sponsor").then(() => {
frappe.show_alert(__("Sponsor Created!"));
frm.refresh();
});
});
}
},
});
@@ -0,0 +1,137 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-07-26 12:13:43.344063",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"event",
"company_name",
"company_logo",
"website",
"column_break_fhgg",
"status",
"tier",
"country",
"phone",
"section_break_additional",
"additional_fields"
],
"fields": [
{
"default": "Approval Pending",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Approval Pending\nPayment Pending\nPaid\nWithdrawn"
},
{
"fieldname": "company_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Company Name",
"reqd": 1
},
{
"fieldname": "company_logo",
"fieldtype": "Attach Image",
"label": "Company Logo",
"reqd": 1
},
{
"fieldname": "column_break_fhgg",
"fieldtype": "Column Break"
},
{
"fieldname": "tier",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Tier",
"options": "Sponsorship Tier"
},
{
"fieldname": "event",
"fieldtype": "Link",
"label": "Event",
"options": "Pohodex Event Manager Event",
"reqd": 1
},
{
"fieldname": "website",
"fieldtype": "Data",
"label": "Website",
"options": "URL"
},
{
"fieldname": "country",
"fieldtype": "Link",
"label": "Country",
"options": "Country"
},
{
"fieldname": "phone",
"fieldtype": "Phone",
"label": "Phone"
},
{
"fieldname": "section_break_additional",
"fieldtype": "Section Break"
},
{
"fieldname": "additional_fields",
"fieldtype": "Table",
"label": "Additional Fields",
"options": "Additional Field"
}
],
"grid_page_length": 50,
"image_field": "company_logo",
"index_web_pages_for_search": 1,
"links": [
{
"link_doctype": "Event Sponsor",
"link_fieldname": "enquiry"
},
{
"link_doctype": "Event Payment",
"link_fieldname": "reference_docname"
}
],
"modified": "2025-10-28 17:00:34.475703",
"modified_by": "Administrator",
"module": "Proposals",
"name": "Sponsorship Enquiry",
"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",
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "company_name",
"track_seen": 1,
"track_views": 1
}
@@ -0,0 +1,137 @@
# Copyright (c) 2025, BWH Studios and contributors
# For license information, please see license.txt
import frappe
from frappe.email.doctype.email_template.email_template import get_email_template
from frappe.model.document import Document
from frappe.utils import get_url
from event_manager.payments import mark_payment_as_received
class SponsorshipEnquiry(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
event: DF.Link
phone: DF.Phone | None
status: DF.Literal["Approval Pending", "Payment Pending", "Paid", "Withdrawn"]
tier: DF.Link | None
website: DF.Data | None
# end: auto-generated types
def on_update(self):
if self.has_value_changed("status") and self.status == "Payment Pending":
try:
self.send_approval_notification()
except Exception:
frappe.log_error("Error sending Sponsorship Approval Notification")
def on_payment_authorized(self, payment_status: str):
if payment_status in ("Authorized", "Completed"):
mark_payment_as_received(self.doctype, self.name)
frappe.get_doc(
{
"doctype": "Event Sponsor",
"company_name": self.company_name,
"company_logo": self.company_logo,
"event": self.event,
"tier": self.tier,
"enquiry": self.name,
"website": self.website,
}
).insert(ignore_permissions=True)
self.db_set("status", "Paid")
@frappe.whitelist()
def create_sponsor(self):
frappe.only_for("Event Manager")
if not self.tier:
frappe.throw(frappe._("Please select a sponsorship tier!"))
frappe.get_doc(
{
"doctype": "Event Sponsor",
"company_name": self.company_name,
"company_logo": self.company_logo,
"event": self.event,
"tier": self.tier,
"enquiry": self.name,
"website": self.website,
"country": self.country,
}
).insert(ignore_permissions=True)
def after_insert(self):
try:
self.send_pitch_deck()
except Exception:
frappe.log_error("Error sending Sponsor Pitch Deck")
def send_pitch_deck(self, now=False):
event = frappe.get_cached_doc("Pohodex Event Manager Event", self.event)
settings = frappe.get_cached_doc("Pohodex Event Manager Settings")
# Check event-level toggle first, then fall back to global
if not event.auto_send_pitch_deck and not settings.auto_send_pitch_deck:
return
# Get template: event-level takes precedence, fall back to global
template_name = event.sponsor_deck_email_template or settings.default_sponsor_deck_email_template
if not template_name:
frappe.log_error("No sponsor deck email template configured", "Sponsorship Enquiry")
return
email_template = get_email_template(template_name, {"doc": self, "event": event})
subject = email_template.get("subject")
content = email_template.get("message")
# Get CC and Reply-To: event-level takes precedence
cc = event.sponsor_deck_cc or settings.default_sponsor_deck_cc
reply_to = event.sponsor_deck_reply_to or settings.default_sponsor_deck_reply_to
frappe.sendmail(
recipients=[self.owner],
subject=subject,
cc=cc,
reply_to=reply_to,
content=content,
reference_doctype=self.doctype,
reference_name=self.name,
now=now,
attachments=[{"file_url": attachment.file} for attachment in event.sponsor_deck_attachments],
)
def send_approval_notification(self):
event = frappe.get_cached_doc("Pohodex Event Manager Event", self.event)
host_name = event.host or "The Event Team"
dashboard_link = get_url(f"/dashboard/account/sponsorships/{self.name}")
subject = f"[Payment Pending] Your Sponsorship for {event.title} has been Approved!"
message = f"""
<p>Dear {self.company_name},</p>
<p>We are pleased to inform you that your sponsorship enquiry for <strong>{event.title}</strong> has been approved.</p>
<p>You can now proceed to select a sponsorship tier and complete the payment by visiting your dashboard <a href="{dashboard_link}">here</a>.</p>
<br>{host_name}</p>
"""
frappe.sendmail(
recipients=[self.owner],
subject=subject,
message=message,
reference_doctype=self.doctype,
reference_name=self.name,
)
@@ -0,0 +1,171 @@
# Copyright (c) 2025, BWH Studios and Contributors
# See license.txt
from unittest.mock import patch
import frappe
from frappe.tests import IntegrationTestCase
EXTRA_TEST_RECORD_DEPENDENCIES = []
IGNORE_TEST_RECORD_DEPENDENCIES = []
class TestSponsorshipEnquiryEmail(IntegrationTestCase):
"""Tests for Sponsorship Enquiry pitch deck email with template fallback logic."""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.test_event = frappe.get_doc("Pohodex Event Manager Event", {"route": "test-route"})
cls.test_event.auto_send_pitch_deck = False
cls.test_event.sponsor_deck_email_template = None
cls.test_event.sponsor_deck_cc = None
cls.test_event.sponsor_deck_reply_to = None
cls.test_event.save()
settings = frappe.get_doc("Pohodex Event Manager Settings")
settings.auto_send_pitch_deck = False
settings.default_sponsor_deck_email_template = None
settings.default_sponsor_deck_cc = None
settings.default_sponsor_deck_reply_to = None
settings.save()
def _create_enquiry(self):
return frappe.get_doc(
{
"doctype": "Sponsorship Enquiry",
"event": self.test_event.name,
"company_name": "Test Sponsor Co",
"company_logo": "/files/test-logo.png",
}
).insert()
def _create_template(self, name, subject_prefix):
if frappe.db.exists("Email Template", name):
frappe.delete_doc("Email Template", name, force=True)
return frappe.get_doc(
{
"doctype": "Email Template",
"name": name,
"subject": f"{subject_prefix} - {{{{ event.title }}}}",
"response": f"<p>{subject_prefix} content</p>",
}
).insert()
def tearDown(self):
for e in frappe.get_all("Sponsorship Enquiry", {"company_name": "Test Sponsor Co"}, pluck="name"):
frappe.delete_doc("Sponsorship Enquiry", e, force=True)
@classmethod
def tearDownClass(cls):
cls.test_event.auto_send_pitch_deck = False
cls.test_event.sponsor_deck_email_template = None
cls.test_event.sponsor_deck_cc = None
cls.test_event.sponsor_deck_reply_to = None
cls.test_event.save()
settings = frappe.get_doc("Pohodex Event Manager Settings")
settings.auto_send_pitch_deck = False
settings.default_sponsor_deck_email_template = None
settings.default_sponsor_deck_cc = None
settings.default_sponsor_deck_reply_to = None
settings.save()
super().tearDownClass()
@patch("frappe.sendmail")
def test_no_email_when_disabled(self, mock_sendmail):
self.test_event.auto_send_pitch_deck = False
self.test_event.save()
enquiry = self._create_enquiry()
enquiry.send_pitch_deck()
mock_sendmail.assert_not_called()
@patch("frappe.sendmail")
def test_uses_event_settings(self, mock_sendmail):
template = self._create_template("Event Sponsor Template", "EVENT")
try:
self.test_event.auto_send_pitch_deck = True
self.test_event.sponsor_deck_email_template = template.name
self.test_event.sponsor_deck_reply_to = "event@test.com"
self.test_event.sponsor_deck_cc = "event-cc@test.com"
self.test_event.save()
# after_insert hook triggers send_pitch_deck automatically
self._create_enquiry()
mock_sendmail.assert_called_once()
args = mock_sendmail.call_args[1]
self.assertIn("EVENT", args["subject"])
self.assertEqual(args["reply_to"], "event@test.com")
self.assertEqual(args["cc"], "event-cc@test.com")
finally:
self.test_event.auto_send_pitch_deck = False
self.test_event.sponsor_deck_email_template = None
self.test_event.sponsor_deck_reply_to = None
self.test_event.sponsor_deck_cc = None
self.test_event.save()
frappe.delete_doc("Email Template", template.name, force=True)
@patch("frappe.sendmail")
def test_falls_back_to_global_settings(self, mock_sendmail):
template = self._create_template("Global Sponsor Template", "GLOBAL")
try:
settings = frappe.get_doc("Pohodex Event Manager Settings")
settings.auto_send_pitch_deck = True
settings.default_sponsor_deck_email_template = template.name
settings.default_sponsor_deck_reply_to = "global@test.com"
settings.default_sponsor_deck_cc = "global-cc@test.com"
settings.save()
# after_insert hook triggers send_pitch_deck automatically
self._create_enquiry()
mock_sendmail.assert_called_once()
args = mock_sendmail.call_args[1]
self.assertIn("GLOBAL", args["subject"])
self.assertEqual(args["reply_to"], "global@test.com")
self.assertEqual(args["cc"], "global-cc@test.com")
finally:
settings.auto_send_pitch_deck = False
settings.default_sponsor_deck_email_template = None
settings.default_sponsor_deck_reply_to = None
settings.default_sponsor_deck_cc = None
settings.save()
frappe.delete_doc("Email Template", template.name, force=True)
@patch("frappe.sendmail")
def test_event_settings_take_precedence(self, mock_sendmail):
event_template = self._create_template("Event Template", "EVENT")
global_template = self._create_template("Global Template", "GLOBAL")
try:
self.test_event.auto_send_pitch_deck = True
self.test_event.sponsor_deck_email_template = event_template.name
self.test_event.sponsor_deck_reply_to = "event@test.com"
self.test_event.save()
settings = frappe.get_doc("Pohodex Event Manager Settings")
settings.auto_send_pitch_deck = True
settings.default_sponsor_deck_email_template = global_template.name
settings.default_sponsor_deck_reply_to = "global@test.com"
settings.save()
# after_insert hook triggers send_pitch_deck automatically
self._create_enquiry()
mock_sendmail.assert_called_once()
args = mock_sendmail.call_args[1]
self.assertIn("EVENT", args["subject"])
self.assertEqual(args["reply_to"], "event@test.com")
finally:
self.test_event.auto_send_pitch_deck = False
self.test_event.sponsor_deck_email_template = None
self.test_event.sponsor_deck_reply_to = None
self.test_event.save()
settings.auto_send_pitch_deck = False
settings.default_sponsor_deck_email_template = None
settings.default_sponsor_deck_reply_to = None
settings.save()
frappe.delete_doc("Email Template", event_template.name, force=True)
frappe.delete_doc("Email Template", global_template.name, force=True)
@@ -0,0 +1,21 @@
// Copyright (c) 2025, BWH Studios and contributors
// For license information, please see license.txt
frappe.ui.form.on("Talk Proposal", {
refresh(frm) {
if (frm.doc.status != "Accepted") {
const btn = frm.add_custom_button(__("Accept and Create Talk"), () => {
frm.call({
method: "create_talk",
doc: frm.doc,
btn,
}).then(({ message: talk }) => {
frm.set_value("status", "Accepted");
frm.save();
frm.refresh();
frappe.set_route("Form", "Event Talk", talk.name);
});
});
}
},
});
@@ -0,0 +1,160 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-07-26 11:59:34.416282",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"title",
"submitted_by",
"column_break_esac",
"status",
"event",
"section_break_yqfb",
"description",
"column_break_kemm",
"speakers",
"section_break_ouiw",
"phone",
"section_break_additional",
"additional_fields"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"reqd": 1
},
{
"fieldname": "submitted_by",
"fieldtype": "Link",
"label": "Submitted By",
"options": "User"
},
{
"fieldname": "column_break_esac",
"fieldtype": "Column Break"
},
{
"default": "Review Pending",
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Review Pending\nShortlisted\nAccepted\nRejected"
},
{
"fieldname": "event",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Event",
"options": "Pohodex Event Manager Event",
"reqd": 1
},
{
"fieldname": "section_break_yqfb",
"fieldtype": "Section Break"
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description"
},
{
"fieldname": "column_break_kemm",
"fieldtype": "Column Break"
},
{
"fieldname": "speakers",
"fieldtype": "Table",
"label": "Speakers",
"options": "Proposal Speaker",
"reqd": 1
},
{
"fieldname": "section_break_ouiw",
"fieldtype": "Section Break"
},
{
"fieldname": "phone",
"fieldtype": "Phone",
"label": "Phone"
},
{
"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": [
{
"link_doctype": "Event Talk",
"link_fieldname": "proposal"
}
],
"modified": "2026-03-23 17:28:23.254661",
"modified_by": "Administrator",
"module": "Proposals",
"name": "Talk Proposal",
"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",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [
{
"color": "Green",
"title": "Accepted"
}
],
"title_field": "title",
"track_changes": 1,
"track_seen": 1,
"track_views": 1
}
@@ -0,0 +1,60 @@
# Copyright (c) 2025, BWH Studios and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
class TalkProposal(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.proposals.doctype.proposal_speaker.proposal_speaker import ProposalSpeaker
from event_manager.ticketing.doctype.additional_field.additional_field import AdditionalField
additional_fields: DF.Table[AdditionalField]
description: DF.TextEditor | None
event: DF.Link
phone: DF.Phone | None
speakers: DF.Table[ProposalSpeaker]
status: DF.Literal["Review Pending", "Shortlisted", "Accepted", "Rejected"]
submitted_by: DF.Link | None
title: DF.Data
# end: auto-generated types
def validate(self):
if not self.submitted_by:
self.submitted_by = frappe.session.user
@frappe.whitelist()
def create_talk(self):
talk = get_mapped_doc("Talk Proposal", self.name, {"Talk Proposal": {"doctype": "Event Talk"}})
for speaker in self.speakers:
user = frappe.db.exists("User", speaker.email)
if not user:
user = (
frappe.get_doc(
{
"doctype": "User",
"first_name": speaker.first_name,
"last_name": speaker.last_name,
"email": speaker.email,
}
)
.insert()
.name
)
speaker_profile = frappe.db.exists("Speaker Profile", {"user": user})
if not speaker_profile:
speaker_profile = frappe.get_doc({"doctype": "Speaker Profile", "user": user}).insert().name
talk.append("speakers", {"speaker": speaker_profile})
return talk.save()
@@ -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 IntegrationTestTalkProposal(IntegrationTestCase):
"""
Integration tests for TalkProposal.
Use this class for testing interactions between multiple components.
"""
pass