feat: multiple zoom accounts
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col border hover:border-outline-gray-4 rounded-md p-4 h-full"
|
class="flex flex-col border hover:border-outline-gray-3 rounded-md p-4 h-full"
|
||||||
style="min-height: 150px"
|
style="min-height: 150px"
|
||||||
>
|
>
|
||||||
<div class="text-lg leading-5 font-semibold mb-2 text-ink-gray-9">
|
<div class="text-lg leading-5 font-semibold mb-2 text-ink-gray-9">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col border rounded-md p-3 h-full hover:border-outline-gray-4"
|
class="flex flex-col border rounded-md p-3 h-full hover:border-outline-gray-3"
|
||||||
>
|
>
|
||||||
<div class="flex space-x-4 mb-4">
|
<div class="flex space-x-4 mb-4">
|
||||||
<div class="flex flex-col space-y-2 flex-1">
|
<div class="flex flex-col space-y-2 flex-1">
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div
|
||||||
|
v-if="hasPermission() && !props.zoomAccount"
|
||||||
|
class="flex items-center space-x-2 mb-5 bg-surface-amber-1 py-1 px-2 rounded-md text-ink-amber-3"
|
||||||
|
>
|
||||||
|
<AlertCircle class="size-4 stroke-1.5" />
|
||||||
|
<span>
|
||||||
|
{{ __('Please add a zoom account to the batch to create live classes.') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<div class="text-lg font-semibold text-ink-gray-9">
|
<div class="text-lg font-semibold text-ink-gray-9">
|
||||||
{{ __('Live Class') }}
|
{{ __('Live Class') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -12,10 +22,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="liveClasses.data?.length" class="grid grid-cols-2 gap-5">
|
<div v-if="liveClasses.data?.length" class="grid grid-cols-2 gap-5 mt-5">
|
||||||
<div
|
<div
|
||||||
v-for="cls in liveClasses.data"
|
v-for="cls in liveClasses.data"
|
||||||
class="flex flex-col border rounded-md h-full text-ink-gray-7 p-3"
|
class="flex flex-col border rounded-md h-full text-ink-gray-7 hover:border-outline-gray-3 p-3"
|
||||||
>
|
>
|
||||||
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
|
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
|
||||||
{{ cls.title }}
|
{{ cls.title }}
|
||||||
@@ -44,7 +54,8 @@
|
|||||||
v-if="user.data?.is_moderator || user.data?.is_evaluator"
|
v-if="user.data?.is_moderator || user.data?.is_evaluator"
|
||||||
:href="cls.start_url"
|
:href="cls.start_url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="w-1/2 cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
|
class="cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-ink-gray-8 bg-surface-gray-2 hover:bg-surface-gray-3 active:bg-surface-gray-4 focus-visible:ring focus-visible:ring-outline-gray-3 h-7 text-base px-2 rounded"
|
||||||
|
:class="cls.join_url ? 'w-full' : 'w-1/2'"
|
||||||
>
|
>
|
||||||
<Monitor class="h-4 w-4 stroke-1.5" />
|
<Monitor class="h-4 w-4 stroke-1.5" />
|
||||||
{{ __('Start') }}
|
{{ __('Start') }}
|
||||||
@@ -67,21 +78,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-sm italic text-ink-gray-5">
|
<div v-else class="text-sm italic text-ink-gray-5 mt-2">
|
||||||
{{ __('No live classes scheduled') }}
|
{{ __('No live classes scheduled') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LiveClassModal
|
<LiveClassModal
|
||||||
:batch="props.batch"
|
:batch="props.batch"
|
||||||
|
:zoomAccount="props.zoomAccount"
|
||||||
v-model="showLiveClassModal"
|
v-model="showLiveClassModal"
|
||||||
v-model:reloadLiveClasses="liveClasses"
|
v-model:reloadLiveClasses="liveClasses"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { createListResource, Button } from 'frappe-ui'
|
import { createListResource, Button } from 'frappe-ui'
|
||||||
import { Plus, Clock, Calendar, Video, Monitor, Info } from 'lucide-vue-next'
|
import {
|
||||||
import { inject } from 'vue'
|
Plus,
|
||||||
|
Clock,
|
||||||
|
Calendar,
|
||||||
|
Video,
|
||||||
|
Monitor,
|
||||||
|
Info,
|
||||||
|
AlertCircle,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import { inject, ref } from 'vue'
|
||||||
import LiveClassModal from '@/components/Modals/LiveClassModal.vue'
|
import LiveClassModal from '@/components/Modals/LiveClassModal.vue'
|
||||||
import { ref } from 'vue'
|
|
||||||
import { formatTime } from '@/utils/'
|
import { formatTime } from '@/utils/'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
@@ -94,6 +114,7 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
zoomAccount: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
const liveClasses = createListResource({
|
const liveClasses = createListResource({
|
||||||
@@ -120,6 +141,11 @@ const openLiveClassModal = () => {
|
|||||||
|
|
||||||
const canCreateClass = () => {
|
const canCreateClass = () => {
|
||||||
if (readOnlyMode) return false
|
if (readOnlyMode) return false
|
||||||
|
if (!props.zoomAccount) return false
|
||||||
|
return hasPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPermission = () => {
|
||||||
return user.data?.is_moderator || user.data?.is_evaluator
|
return user.data?.is_moderator || user.data?.is_evaluator
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{
|
{
|
||||||
label: 'Submit',
|
label: 'Submit',
|
||||||
variant: 'solid',
|
variant: 'solid',
|
||||||
onClick: (close) => submitLiveClass(close),
|
onClick: ({ close }) => submitLiveClass(close),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}"
|
}"
|
||||||
@@ -107,7 +107,11 @@ const dayjs = inject('$dayjs')
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batch: {
|
batch: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
required: true,
|
||||||
|
},
|
||||||
|
zoomAccount: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -159,6 +163,7 @@ const createLiveClass = createResource({
|
|||||||
return {
|
return {
|
||||||
doctype: 'LMS Live Class',
|
doctype: 'LMS Live Class',
|
||||||
batch_name: values.batch,
|
batch_name: values.batch,
|
||||||
|
zoom_account: props.zoomAccount,
|
||||||
...values,
|
...values,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -167,39 +172,11 @@ const createLiveClass = createResource({
|
|||||||
const submitLiveClass = (close) => {
|
const submitLiveClass = (close) => {
|
||||||
return createLiveClass.submit(liveClass, {
|
return createLiveClass.submit(liveClass, {
|
||||||
validate() {
|
validate() {
|
||||||
if (!liveClass.title) {
|
validateFormFields()
|
||||||
return __('Please enter a title.')
|
|
||||||
}
|
|
||||||
if (!liveClass.date) {
|
|
||||||
return __('Please select a date.')
|
|
||||||
}
|
|
||||||
if (!liveClass.time) {
|
|
||||||
return __('Please select a time.')
|
|
||||||
}
|
|
||||||
if (!liveClass.timezone) {
|
|
||||||
return __('Please select a timezone.')
|
|
||||||
}
|
|
||||||
if (!valideTime()) {
|
|
||||||
return __('Please enter a valid time in the format HH:mm.')
|
|
||||||
}
|
|
||||||
const liveClassDateTime = dayjs(`${liveClass.date}T${liveClass.time}`).tz(
|
|
||||||
liveClass.timezone,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
liveClassDateTime.isSameOrBefore(
|
|
||||||
dayjs().tz(liveClass.timezone, false),
|
|
||||||
'minute'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return __('Please select a future date and time.')
|
|
||||||
}
|
|
||||||
if (!liveClass.duration) {
|
|
||||||
return __('Please select a duration.')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
liveClasses.value.reload()
|
liveClasses.value.reload()
|
||||||
|
refreshForm()
|
||||||
close()
|
close()
|
||||||
},
|
},
|
||||||
onError(err) {
|
onError(err) {
|
||||||
@@ -208,6 +185,39 @@ const submitLiveClass = (close) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateFormFields = () => {
|
||||||
|
if (!liveClass.title) {
|
||||||
|
return __('Please enter a title.')
|
||||||
|
}
|
||||||
|
if (!liveClass.date) {
|
||||||
|
return __('Please select a date.')
|
||||||
|
}
|
||||||
|
if (!liveClass.time) {
|
||||||
|
return __('Please select a time.')
|
||||||
|
}
|
||||||
|
if (!liveClass.timezone) {
|
||||||
|
return __('Please select a timezone.')
|
||||||
|
}
|
||||||
|
if (!valideTime()) {
|
||||||
|
return __('Please enter a valid time in the format HH:mm.')
|
||||||
|
}
|
||||||
|
const liveClassDateTime = dayjs(`${liveClass.date}T${liveClass.time}`).tz(
|
||||||
|
liveClass.timezone,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
liveClassDateTime.isSameOrBefore(
|
||||||
|
dayjs().tz(liveClass.timezone, false),
|
||||||
|
'minute'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return __('Please select a future date and time.')
|
||||||
|
}
|
||||||
|
if (!liveClass.duration) {
|
||||||
|
return __('Please select a duration.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const valideTime = () => {
|
const valideTime = () => {
|
||||||
let time = liveClass.time.split(':')
|
let time = liveClass.time.split(':')
|
||||||
if (time.length != 2) {
|
if (time.length != 2) {
|
||||||
@@ -221,4 +231,14 @@ const valideTime = () => {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refreshForm = () => {
|
||||||
|
liveClass.title = ''
|
||||||
|
liveClass.description = ''
|
||||||
|
liveClass.date = ''
|
||||||
|
liveClass.time = ''
|
||||||
|
liveClass.duration = ''
|
||||||
|
liveClass.timezone = getUserTimezone()
|
||||||
|
liveClass.auto_recording = 'No Recording'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -70,7 +70,10 @@
|
|||||||
<BatchStudents :batch="batch" />
|
<BatchStudents :batch="batch" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab.label == 'Classes'">
|
<div v-else-if="tab.label == 'Classes'">
|
||||||
<LiveClass :batch="batch.data.name" />
|
<LiveClass
|
||||||
|
:batch="batch.data.name"
|
||||||
|
:zoomAccount="batch.data.zoom_account"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab.label == 'Assessments'">
|
<div v-else-if="tab.label == 'Assessments'">
|
||||||
<Assessments :batch="batch.data.name" />
|
<Assessments :batch="batch.data.name" />
|
||||||
|
|||||||
@@ -159,6 +159,11 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
doctype="LMS Zoom Settings"
|
||||||
|
:label="__('Zoom Account')"
|
||||||
|
v-model="batch.zoom_account"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<FormControl
|
<FormControl
|
||||||
@@ -327,6 +332,7 @@ const batch = reactive({
|
|||||||
paid_batch: false,
|
paid_batch: false,
|
||||||
currency: '',
|
currency: '',
|
||||||
amount: 0,
|
amount: 0,
|
||||||
|
zoom_account: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const instructors = ref([])
|
const instructors = ref([])
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"description",
|
"description",
|
||||||
"column_break_hlqw",
|
"column_break_hlqw",
|
||||||
"instructors",
|
"instructors",
|
||||||
|
"zoom_account",
|
||||||
"section_break_rgfj",
|
"section_break_rgfj",
|
||||||
"medium",
|
"medium",
|
||||||
"category",
|
"category",
|
||||||
@@ -354,6 +355,12 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_cssv",
|
"fieldname": "section_break_cssv",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zoom_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Zoom Account",
|
||||||
|
"options": "LMS Zoom Settings"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -372,7 +379,7 @@
|
|||||||
"link_fieldname": "batch_name"
|
"link_fieldname": "batch_name"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-05-21 13:30:28.904260",
|
"modified": "2025-05-26 15:30:55.083507",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch",
|
"name": "LMS Batch",
|
||||||
|
|||||||
@@ -146,7 +146,15 @@ class LMSBatch(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_live_class(
|
def create_live_class(
|
||||||
batch_name, title, duration, date, time, timezone, auto_recording, description=None
|
batch_name,
|
||||||
|
zoom_account,
|
||||||
|
title,
|
||||||
|
duration,
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
timezone,
|
||||||
|
auto_recording,
|
||||||
|
description=None,
|
||||||
):
|
):
|
||||||
frappe.only_for("Moderator")
|
frappe.only_for("Moderator")
|
||||||
payload = {
|
payload = {
|
||||||
@@ -161,7 +169,7 @@ def create_live_class(
|
|||||||
"timezone": timezone,
|
"timezone": timezone,
|
||||||
}
|
}
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": "Bearer " + authenticate(),
|
"Authorization": "Bearer " + authenticate(zoom_account),
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
}
|
}
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
@@ -183,6 +191,7 @@ def create_live_class(
|
|||||||
"password": data.get("password"),
|
"password": data.get("password"),
|
||||||
"description": description,
|
"description": description,
|
||||||
"auto_recording": auto_recording,
|
"auto_recording": auto_recording,
|
||||||
|
"zoom_account": zoom_account,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
class_details = frappe.get_doc(payload)
|
class_details = frappe.get_doc(payload)
|
||||||
@@ -194,10 +203,10 @@ def create_live_class(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def authenticate():
|
def authenticate(zoom_account):
|
||||||
zoom = frappe.get_single("Zoom Settings")
|
zoom = frappe.get_doc("LMS Zoom Settings", zoom_account)
|
||||||
if not zoom.enable:
|
if not zoom.enabled:
|
||||||
frappe.throw(_("Please enable Zoom Settings to use this feature."))
|
frappe.throw(_("Please enable the zoom account to use this feature."))
|
||||||
|
|
||||||
authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}"
|
authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}"
|
||||||
|
|
||||||
|
|||||||
@@ -9,21 +9,22 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
"host",
|
"host",
|
||||||
|
"zoom_account",
|
||||||
"batch_name",
|
"batch_name",
|
||||||
"event",
|
|
||||||
"column_break_astv",
|
"column_break_astv",
|
||||||
"description",
|
|
||||||
"section_break_glxh",
|
|
||||||
"date",
|
"date",
|
||||||
"duration",
|
|
||||||
"column_break_spvt",
|
|
||||||
"time",
|
"time",
|
||||||
|
"duration",
|
||||||
"timezone",
|
"timezone",
|
||||||
"section_break_yrpq",
|
"section_break_glxh",
|
||||||
|
"description",
|
||||||
|
"column_break_spvt",
|
||||||
|
"event",
|
||||||
"password",
|
"password",
|
||||||
|
"auto_recording",
|
||||||
|
"section_break_yrpq",
|
||||||
"start_url",
|
"start_url",
|
||||||
"column_break_yokr",
|
"column_break_yokr",
|
||||||
"auto_recording",
|
|
||||||
"join_url"
|
"join_url"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -73,8 +74,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_glxh",
|
"fieldname": "section_break_glxh",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Date and Time"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_spvt",
|
"fieldname": "column_break_spvt",
|
||||||
@@ -130,13 +130,21 @@
|
|||||||
"label": "Event",
|
"label": "Event",
|
||||||
"options": "Event",
|
"options": "Event",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zoom_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Zoom Account",
|
||||||
|
"options": "LMS Zoom Settings",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-11-11 18:59:26.396111",
|
"modified": "2025-05-26 13:23:38.209187",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Live Class",
|
"name": "LMS Live Class",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
@@ -175,6 +183,7 @@
|
|||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"show_title_field_in_link": 1,
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
|||||||
0
lms/lms/doctype/lms_zoom_settings/__init__.py
Normal file
0
lms/lms/doctype/lms_zoom_settings/__init__.py
Normal file
8
lms/lms/doctype/lms_zoom_settings/lms_zoom_settings.js
Normal file
8
lms/lms/doctype/lms_zoom_settings/lms_zoom_settings.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2025, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Zoom Settings", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
103
lms/lms/doctype/lms_zoom_settings/lms_zoom_settings.json
Normal file
103
lms/lms/doctype/lms_zoom_settings/lms_zoom_settings.json
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:account_name",
|
||||||
|
"creation": "2025-05-26 13:04:18.285735",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enabled",
|
||||||
|
"section_break_xfow",
|
||||||
|
"account_name",
|
||||||
|
"member",
|
||||||
|
"member_name",
|
||||||
|
"column_break_fxxg",
|
||||||
|
"account_id",
|
||||||
|
"client_id",
|
||||||
|
"client_secret"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Account ID",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "client_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Client ID",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "client_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"label": "Client Secret",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "member",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Member",
|
||||||
|
"options": "User",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "member.full_name",
|
||||||
|
"fieldname": "member_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Member Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_xfow",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Account Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_fxxg",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2025-05-26 13:21:38.227043",
|
||||||
|
"modified_by": "sayali@frappe.io",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Zoom Settings",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "creation",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
lms/lms/doctype/lms_zoom_settings/lms_zoom_settings.py
Normal file
9
lms/lms/doctype/lms_zoom_settings/lms_zoom_settings.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2025, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSZoomSettings(Document):
|
||||||
|
pass
|
||||||
30
lms/lms/doctype/lms_zoom_settings/test_lms_zoom_settings.py
Normal file
30
lms/lms/doctype/lms_zoom_settings/test_lms_zoom_settings.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2025, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
|
|
||||||
|
# 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 UnitTestLMSZoomSettings(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for LMSZoomSettings.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IntegrationTestLMSZoomSettings(IntegrationTestCase):
|
||||||
|
"""
|
||||||
|
Integration tests for LMSZoomSettings.
|
||||||
|
Use this class for testing interactions between multiple components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
@@ -1391,6 +1391,7 @@ def get_batch_details(batch):
|
|||||||
"certification",
|
"certification",
|
||||||
"timezone",
|
"timezone",
|
||||||
"category",
|
"category",
|
||||||
|
"zoom_account",
|
||||||
],
|
],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -104,3 +104,6 @@ lms.patches.v2_0.delete_unused_custom_fields
|
|||||||
lms.patches.v2_0.update_certificate_request_status
|
lms.patches.v2_0.update_certificate_request_status
|
||||||
lms.patches.v2_0.update_job_city_and_country
|
lms.patches.v2_0.update_job_city_and_country
|
||||||
lms.patches.v2_0.update_course_evaluator_data
|
lms.patches.v2_0.update_course_evaluator_data
|
||||||
|
lms.patches.v2_0.move_zoom_settings #20-05-2025
|
||||||
|
lms.patches.v2_0.link_zoom_account_to_live_class
|
||||||
|
lms.patches.v2_0.link_zoom_account_to_batch
|
||||||
11
lms/patches/v2_0/link_zoom_account_to_batch.py
Normal file
11
lms/patches/v2_0/link_zoom_account_to_batch.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
live_classes = frappe.get_all("LMS Live Class", ["name", "batch_name"])
|
||||||
|
zoom_account = frappe.get_all("LMS Zoom Settings", pluck="name")
|
||||||
|
zoom_account = zoom_account[0] if zoom_account else None
|
||||||
|
|
||||||
|
if zoom_account:
|
||||||
|
for live_class in live_classes:
|
||||||
|
frappe.db.set_value("LMS Batch", live_class.batch_name, "zoom_account", zoom_account)
|
||||||
16
lms/patches/v2_0/link_zoom_account_to_live_class.py
Normal file
16
lms/patches/v2_0/link_zoom_account_to_live_class.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
live_classes = frappe.get_all("LMS Live Class", pluck="name")
|
||||||
|
zoom_account = frappe.get_all("LMS Zoom Settings", pluck="name")
|
||||||
|
zoom_account = zoom_account[0] if zoom_account else None
|
||||||
|
|
||||||
|
if zoom_account:
|
||||||
|
for live_class in live_classes:
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Live Class",
|
||||||
|
live_class,
|
||||||
|
"zoom_account",
|
||||||
|
zoom_account,
|
||||||
|
)
|
||||||
27
lms/patches/v2_0/move_zoom_settings.py
Normal file
27
lms/patches/v2_0/move_zoom_settings.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
create_settings()
|
||||||
|
|
||||||
|
|
||||||
|
def create_settings():
|
||||||
|
current_settings = frappe.get_single("Zoom Settings")
|
||||||
|
member = current_settings.owner
|
||||||
|
member_name = frappe.get_value("User", member, "full_name")
|
||||||
|
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Zoom Settings",
|
||||||
|
{
|
||||||
|
"account_name": member_name,
|
||||||
|
},
|
||||||
|
):
|
||||||
|
new_settings = frappe.new_doc("LMS Zoom Settings")
|
||||||
|
new_settings.enabled = current_settings.enable
|
||||||
|
new_settings.account_name = member_name
|
||||||
|
new_settings.member = member
|
||||||
|
new_settings.member_name = member_name
|
||||||
|
new_settings.account_id = current_settings.account_id
|
||||||
|
new_settings.client_id = current_settings.client_id
|
||||||
|
new_settings.client_secret = current_settings.client_secret
|
||||||
|
new_settings.insert()
|
||||||
Reference in New Issue
Block a user