mirror of
https://github.com/frappe/lms.git
synced 2026-05-02 13:39:31 +03:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user