import functools from collections.abc import Callable import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def is_app_installed(app_name: str) -> bool: """Check if a specified app is installed.""" return app_name in frappe.get_installed_apps() def only_if_app_installed(app_name: str, raise_exception: bool = False) -> Callable: """ Decorator to check if a specified app is installed before running the function. :param app_name: The name of the app to check for installation. :param raise_exception: If True, raises an exception if the app is not installed. If False, the function silently returns None. :return: The decorated function. """ def decorator(func: Callable) -> Callable: @functools.wraps(func) def wrapper(*args, **kwargs): installed_apps = frappe.get_installed_apps() if app_name not in installed_apps: if raise_exception: frappe.throw( frappe._("This feature requires the {0} app to be installed.").format(app_name) ) return None return func(*args, **kwargs) return wrapper return decorator def add_event_manager_user_role(doc, event=None): doc.add_roles("Pohodex Event Manager User") # https://github.com/resilient-tech/india-compliance/blob/f259e9d1408a1cbb85c91146df3b5baa72e5fafb/india_compliance/utils/custom_fields.py def make_custom_fields(custom_fields, module_name, *args, **kwargs): for _doctypes, fields in custom_fields.items(): if isinstance(fields, dict): fields = (fields,) for field in fields: field["module"] = module_name return create_custom_fields(custom_fields, *args, **kwargs) # https://github.com/resilient-tech/india-compliance/blob/f259e9d1408a1cbb85c91146df3b5baa72e5fafb/india_compliance/utils/custom_fields.py def get_custom_fields_creator(module_name): return functools.partial(make_custom_fields, module_name=module_name) # https://github.com/resilient-tech/india-compliance/blob/f259e9d1408a1cbb85c91146df3b5baa72e5fafb/india_compliance/utils/custom_fields.py#L54C1-L77C48 def delete_custom_fields(custom_fields): """ :param custom_fields: a dict like `{'Sales Invoice': [{fieldname: 'test', ...}]}` """ for doctypes, fields in custom_fields.items(): if isinstance(fields, dict): # only one field fields = [fields] if isinstance(doctypes, str): # only one doctype doctypes = (doctypes,) for doctype in doctypes: frappe.db.delete( "Custom Field", { "fieldname": ("in", [field["fieldname"] for field in fields]), "dt": doctype, }, ) frappe.clear_cache(doctype=doctype) def make_qr_image(data: str) -> bytes: """ Generate QR code image bytes from data string. :param data: The data to encode in the QR code :return: PNG image as bytes """ import io import qrcode from qrcode.image.styledpil import StyledPilImage from qrcode.image.styles.moduledrawers.pil import HorizontalBarsDrawer qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_H, box_size=10, border=4, ) qr.add_data(data) qr.make(fit=True) img = qr.make_image(image_factory=StyledPilImage, module_drawer=HorizontalBarsDrawer()) output = io.BytesIO() img.save(output, format="PNG") return output.getvalue() def generate_qr_code_file(doc, data: str, field_name: str = "qr_code", file_prefix: str = "qr-code") -> str: """ Generate QR code image and attach as File to a document. :param doc: The Frappe document to attach the QR code to :param data: The data to encode in the QR code :param field_name: The field name to attach the file to (default: "qr_code") :param file_prefix: Prefix for the file name (default: "qr-code") :return: The file URL of the created QR code image """ qr_data = make_qr_image(data) qr_code_file = frappe.get_doc( { "doctype": "File", "content": qr_data, "attached_to_doctype": doc.doctype, "attached_to_name": doc.name, "attached_to_field": field_name, "file_name": f"{file_prefix}-{doc.name}.png", } ).save(ignore_permissions=True) return qr_code_file.file_url def build_event_datetimes(event_doc): from datetime import datetime, timedelta from frappe.utils import get_time, getdate start_date = getdate(event_doc.start_date) start_time = get_time(event_doc.start_time) start_datetime = datetime.combine(start_date, start_time) end_date = getdate(event_doc.end_date) if event_doc.end_date else start_date if event_doc.end_time: end_time = get_time(event_doc.end_time) end_datetime = datetime.combine(end_date, end_time) else: end_datetime = start_datetime + timedelta(hours=1) return start_datetime, end_datetime def generate_ics_file(event_doc, attendee_email: str): from uuid import uuid4 from frappe.utils import now_datetime start_dt, end_dt = build_event_datetimes(event_doc) organizer_name = event_doc.host or event_doc.title organizer_email = frappe.db.get_value( "Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "email_id" ) venue_address = "" if event_doc.venue: venue_address = frappe.db.get_value("Event Venue", event_doc.venue, "address") or "" context = { "uid": uuid4(), "now": now_datetime().strftime("%Y%m%dT%H%M%S"), "timezone": event_doc.time_zone, "start": start_dt.strftime("%Y%m%dT%H%M%S"), "end": end_dt.strftime("%Y%m%dT%H%M%S"), "title": event_doc.title, "location": venue_address, "attendee_email": attendee_email, "description": f"Your ticket for {event_doc.title}", "organizer_name": organizer_name, "organizer_email": organizer_email, } return frappe.render_template("templates/ics/ics.jinja2", context, is_path=True)