From 9a7c77c57b28732c7b06c8ae6e052168358b1231 Mon Sep 17 00:00:00 2001 From: Vaibhav Rathore Date: Tue, 24 Feb 2026 23:25:27 +0530 Subject: [PATCH 01/17] feat: Google Meet integration for Live Classes Add Google Meet as an alternative conferencing provider for Live Classes in Frappe LMS, alongside the existing Zoom integration. Leverages Frappe's built-in Google Calendar sync to generate Meet links. Changes: - New DocType: LMS Google Meet Settings (account_name, member, calendar) - Schema changes to LMS Batch (conferencing_provider, google_meet_account) - Schema changes to LMS Live Class (conferencing_provider, google_meet_account) - Participant calendar invites via Google Calendar API - Event update/reschedule sync (on_update hook) - Event cancellation/deletion sync (on_trash hook) - Async Meet link handling with user-facing fallback message - Frontend empty link guard ("Meet link generating...") - Batch validation for conferencing provider configuration - Manual attendance marking for Google Meet classes - Admin UI for managing Google Meet accounts in LMS Settings - Unit and integration tests Upstream Issue: frappe/lms#2027 --- .../Modals/GoogleMeetAccountModal.vue | 195 +++++++++ .../src/components/Modals/LiveClassModal.vue | 25 +- .../components/Modals/ManualAttendance.vue | 113 ++++++ .../Settings/GoogleMeetSettings.vue | 202 ++++++++++ frontend/src/components/Settings/Settings.vue | 8 + frontend/src/pages/Batches/BatchForm.vue | 31 ++ .../pages/Batches/components/LiveClass.vue | 50 ++- lms/lms/doctype/lms_batch/lms_batch.json | 16 + lms/lms/doctype/lms_batch/lms_batch.py | 54 +++ .../lms_google_meet_settings/__init__.py | 0 .../lms_google_meet_settings.js | 8 + .../lms_google_meet_settings.json | 123 ++++++ .../lms_google_meet_settings.py | 9 + .../test_lms_google_meet_settings.py | 88 +++++ .../lms_live_class/lms_live_class.json | 23 +- .../doctype/lms_live_class/lms_live_class.py | 179 ++++++++- .../lms_live_class/test_lms_live_class.py | 370 +++++++++++++++++- 17 files changed, 1468 insertions(+), 26 deletions(-) create mode 100644 frontend/src/components/Modals/GoogleMeetAccountModal.vue create mode 100644 frontend/src/components/Modals/ManualAttendance.vue create mode 100644 frontend/src/components/Settings/GoogleMeetSettings.vue create mode 100644 lms/lms/doctype/lms_google_meet_settings/__init__.py create mode 100644 lms/lms/doctype/lms_google_meet_settings/lms_google_meet_settings.js create mode 100644 lms/lms/doctype/lms_google_meet_settings/lms_google_meet_settings.json create mode 100644 lms/lms/doctype/lms_google_meet_settings/lms_google_meet_settings.py create mode 100644 lms/lms/doctype/lms_google_meet_settings/test_lms_google_meet_settings.py diff --git a/frontend/src/components/Modals/GoogleMeetAccountModal.vue b/frontend/src/components/Modals/GoogleMeetAccountModal.vue new file mode 100644 index 00000000..59b9522a --- /dev/null +++ b/frontend/src/components/Modals/GoogleMeetAccountModal.vue @@ -0,0 +1,195 @@ + + diff --git a/frontend/src/components/Modals/LiveClassModal.vue b/frontend/src/components/Modals/LiveClassModal.vue index 43796d4e..ac2ee3fe 100644 --- a/frontend/src/components/Modals/LiveClassModal.vue +++ b/frontend/src/components/Modals/LiveClassModal.vue @@ -67,6 +67,7 @@ /> { - return createLiveClass.submit(liveClass, { + const resource = + props.conferencingProvider === 'Google Meet' + ? createGoogleMeetLiveClass + : createLiveClass + return resource.submit(liveClass, { validate() { validateFormFields() }, diff --git a/frontend/src/components/Modals/ManualAttendance.vue b/frontend/src/components/Modals/ManualAttendance.vue new file mode 100644 index 00000000..963bb0bf --- /dev/null +++ b/frontend/src/components/Modals/ManualAttendance.vue @@ -0,0 +1,113 @@ + + diff --git a/frontend/src/components/Settings/GoogleMeetSettings.vue b/frontend/src/components/Settings/GoogleMeetSettings.vue new file mode 100644 index 00000000..2cb738a2 --- /dev/null +++ b/frontend/src/components/Settings/GoogleMeetSettings.vue @@ -0,0 +1,202 @@ + + diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue index 0365ed02..b3e718a5 100644 --- a/frontend/src/components/Settings/Settings.vue +++ b/frontend/src/components/Settings/Settings.vue @@ -76,6 +76,7 @@ import PaymentGateways from '@/components/Settings/PaymentGateways.vue' import Coupons from '@/components/Settings/Coupons/Coupons.vue' import Transactions from '@/components/Settings/Transactions/Transactions.vue' import ZoomSettings from '@/components/Settings/ZoomSettings.vue' +import GoogleMeetSettings from '@/components/Settings/GoogleMeetSettings.vue' import Badges from '@/components/Settings/Badges.vue' const show = defineModel() @@ -296,6 +297,13 @@ const tabsStructure = computed(() => { icon: 'Video', template: markRaw(ZoomSettings), }, + { + label: 'Google Meet Accounts', + description: + 'Manage Google Meet accounts to conduct live classes from batches', + icon: 'Video', + template: markRaw(GoogleMeetSettings), + }, { label: 'Badges', description: diff --git a/frontend/src/pages/Batches/BatchForm.vue b/frontend/src/pages/Batches/BatchForm.vue index 4b1b5b9b..9e838a49 100644 --- a/frontend/src/pages/Batches/BatchForm.vue +++ b/frontend/src/pages/Batches/BatchForm.vue @@ -166,7 +166,14 @@ />
+ + { }) } +const conferencingOptions = computed(() => { + return [ + { + label: 'Zoom', + value: 'Zoom', + }, + { + label: 'Google Meet', + value: 'Google Meet', + }, + ] +}) + const mediumOptions = computed(() => { return [ { diff --git a/frontend/src/pages/Batches/components/LiveClass.vue b/frontend/src/pages/Batches/components/LiveClass.vue index 0d3fdb42..690fbf58 100644 --- a/frontend/src/pages/Batches/components/LiveClass.vue +++ b/frontend/src/pages/Batches/components/LiveClass.vue @@ -1,14 +1,14 @@