refactor: remove manual attendance feature for Google Meet classes

Manual attendance marking is not required for Google Meet live classes.
This removes the ManualAttendance modal, the mark_manual_attendance API
endpoint, and associated tests.
This commit is contained in:
Vaibhav Rathore
2026-02-26 18:38:55 +05:30
parent 08b6a9d091
commit 8ea178fcad
4 changed files with 3 additions and 214 deletions
@@ -1,113 +0,0 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Mark Attendance - {0}').format(live_class?.title),
size: '2xl',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: () => saveAttendance(),
},
],
}"
>
<template #body-content>
<div v-if="enrollments.data?.length" class="divide-y">
<label
v-for="student in enrollments.data"
:key="student.member"
class="flex items-center space-x-3 py-2 cursor-pointer hover:bg-surface-gray-2 px-2 rounded"
>
<input
type="checkbox"
:value="student.member"
v-model="selectedMembers"
class="rounded border-outline-gray-3"
/>
<Avatar
:image="student.member_image"
:label="student.member_name"
size="lg"
/>
<div>
<div class="font-medium text-ink-gray-9">
{{ student.member_name }}
</div>
<div class="text-sm text-ink-gray-5">
{{ student.member }}
</div>
</div>
</label>
</div>
<div v-else class="text-sm text-ink-gray-5 italic py-4">
{{ __('No students enrolled in this batch.') }}
</div>
</template>
</Dialog>
</template>
<script setup>
import { Avatar, createListResource, createResource, Dialog, toast } from 'frappe-ui'
import { ref, onMounted } from 'vue'
const show = defineModel()
const emit = defineEmits(['saved'])
const props = defineProps({
live_class: {
type: Object,
required: true,
},
batch: {
type: String,
required: true,
},
})
const selectedMembers = ref([])
const enrollments = createListResource({
doctype: 'LMS Batch Enrollment',
filters: {
batch: props.batch,
},
fields: ['member', 'member_name', 'member_image'],
auto: true,
})
const existingAttendance = createListResource({
doctype: 'LMS Live Class Participant',
filters: {
live_class: props.live_class?.name,
},
fields: ['member'],
auto: true,
onSuccess(data) {
selectedMembers.value = data.map((d) => d.member)
},
})
const markAttendance = createResource({
url: 'lms.lms.doctype.lms_live_class.lms_live_class.mark_manual_attendance',
})
const saveAttendance = () => {
markAttendance.submit(
{
live_class: props.live_class?.name,
members: JSON.stringify(selectedMembers.value),
},
{
onSuccess() {
toast.success(__('Attendance saved successfully'))
emit('saved')
show.value = false
},
onError(err) {
toast.error(err.messages?.[0] || err)
},
}
)
}
</script>
@@ -35,7 +35,7 @@
v-for="cls in liveClasses.data"
class="flex flex-col border rounded-md h-full text-ink-gray-7 hover:border-outline-gray-3 p-3"
:class="{
'cursor-pointer': isAdmin() && (cls.attendees > 0 || cls.conferencing_provider === 'Google Meet'),
'cursor-pointer': isAdmin() && cls.attendees > 0,
}"
@click="
() => {
@@ -131,13 +131,6 @@
:live_class="attendanceFor"
/>
<ManualAttendance
v-if="showManualAttendance"
v-model="showManualAttendance"
:live_class="attendanceFor"
:batch="batch.data?.name"
@saved="liveClasses.reload()"
/>
</template>
<script setup>
import { createListResource, Button, Tooltip } from 'frappe-ui'
@@ -154,14 +147,12 @@ import { inject, ref } from 'vue'
import { formatTime } from '@/utils/'
import LiveClassModal from '@/components/Modals/LiveClassModal.vue'
import LiveClassAttendance from '@/components/Modals/LiveClassAttendance.vue'
import ManualAttendance from '@/components/Modals/ManualAttendance.vue'
const user = inject('$user')
const showLiveClassModal = ref(false)
const dayjs = inject('$dayjs')
const readOnlyMode = window.read_only_mode
const showAttendance = ref(false)
const showManualAttendance = ref(false)
const attendanceFor = ref(null)
const props = defineProps({
@@ -238,13 +229,9 @@ const hasClassEnded = (cls) => {
const openAttendanceModal = (cls) => {
if (!isAdmin()) return
if (cls.attendees <= 0) return
attendanceFor.value = cls
if (cls.conferencing_provider === 'Google Meet') {
showManualAttendance.value = true
} else {
if (cls.attendees <= 0) return
showAttendance.value = true
}
showAttendance.value = true
}
</script>
<style>
@@ -1,7 +1,6 @@
# Copyright (c) 2023, Frappe and contributors
# For license information, please see license.txt
import json
from datetime import timedelta
import frappe
@@ -304,36 +303,6 @@ def get_minutes(duration_in_seconds):
return 0
@frappe.whitelist()
def mark_manual_attendance(live_class, members):
if isinstance(members, str):
members = json.loads(members)
live_class_doc = frappe.get_doc("LMS Live Class", live_class)
start = get_datetime(f"{live_class_doc.date} {live_class_doc.time}")
end = start + timedelta(minutes=cint(live_class_doc.duration))
# Remove existing manual attendance records for this class
existing = frappe.get_all(
"LMS Live Class Participant",
{"live_class": live_class},
pluck="name",
)
for record in existing:
frappe.delete_doc("LMS Live Class Participant", record, ignore_permissions=True)
for member in members:
doc = frappe.new_doc("LMS Live Class Participant")
doc.live_class = live_class
doc.member = member
doc.joined_at = start
doc.left_at = end
doc.duration = cint(live_class_doc.duration)
doc.insert(ignore_permissions=True)
frappe.db.set_value("LMS Live Class", live_class, "attendees", len(members))
def has_permission(doc, ptype="read", user=None):
user = user or frappe.session.user
roles = frappe.get_roles(user)
@@ -1,7 +1,6 @@
# Copyright (c) 2023, Frappe and Contributors
# See license.txt
import json
from unittest.mock import MagicMock, patch
import frappe
@@ -297,59 +296,6 @@ class TestLMSLiveClass(BaseTestUtils):
# Reset
self.batch.reload()
def test_manual_attendance_marking(self):
"""Manual attendance marking should create participant records."""
from lms.lms.doctype.lms_live_class.lms_live_class import mark_manual_attendance
live_class = self._create_live_class()
members = [self.student1.email, self.student2.email]
mark_manual_attendance(live_class.name, json.dumps(members))
participants = frappe.get_all(
"LMS Live Class Participant",
{"live_class": live_class.name},
pluck="member",
)
for p in participants:
self.cleanup_items.append(("LMS Live Class Participant", frappe.db.get_value(
"LMS Live Class Participant", {"live_class": live_class.name, "member": p}
)))
self.assertEqual(len(participants), 2)
self.assertIn(self.student1.email, participants)
self.assertIn(self.student2.email, participants)
live_class.reload()
self.assertEqual(live_class.attendees, 2)
def test_manual_attendance_replaces_existing(self):
"""Re-marking attendance should replace previous records."""
from lms.lms.doctype.lms_live_class.lms_live_class import mark_manual_attendance
live_class = self._create_live_class()
# First marking with 2 students
mark_manual_attendance(live_class.name, json.dumps([self.student1.email, self.student2.email]))
# Second marking with only 1 student
mark_manual_attendance(live_class.name, json.dumps([self.student1.email]))
participants = frappe.get_all(
"LMS Live Class Participant",
{"live_class": live_class.name},
pluck="member",
)
for p in participants:
self.cleanup_items.append(("LMS Live Class Participant", frappe.db.get_value(
"LMS Live Class Participant", {"live_class": live_class.name, "member": p}
)))
self.assertEqual(len(participants), 1)
self.assertIn(self.student1.email, participants)
live_class.reload()
self.assertEqual(live_class.attendees, 1)
def test_update_attendance_skips_google_meet(self):
"""The Zoom attendance scheduler should skip Google Meet classes."""
from lms.lms.doctype.lms_live_class.lms_live_class import update_attendance