* fix: Translating Error and Messages * Update erpnext/controllers/item_variant.py Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com> * Update erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py Co-Authored-By: Shivam Mishra <scmmishra@users.noreply.github.com>
		
			
				
	
	
		
			375 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| # Copyright (c) 2015, Frappe Technologies and contributors
 | |
| 
 | |
| from __future__ import unicode_literals, division
 | |
| import frappe
 | |
| from frappe import _
 | |
| 
 | |
| class OverlapError(frappe.ValidationError): pass
 | |
| 
 | |
| def validate_overlap_for(doc, doctype, fieldname, value=None):
 | |
| 	"""Checks overlap for specified field.
 | |
| 
 | |
| 	:param fieldname: Checks Overlap for this field
 | |
| 	"""
 | |
| 
 | |
| 	existing = get_overlap_for(doc, doctype, fieldname, value)
 | |
| 	if existing:
 | |
| 		frappe.throw(_("This {0} conflicts with {1} for {2} {3}").format(doc.doctype, existing.name,
 | |
| 			doc.meta.get_label(fieldname) if not value else fieldname , value or doc.get(fieldname)), OverlapError)
 | |
| 
 | |
| def get_overlap_for(doc, doctype, fieldname, value=None):
 | |
| 	"""Returns overlaping document for specified field.
 | |
| 
 | |
| 	:param fieldname: Checks Overlap for this field
 | |
| 	"""
 | |
| 
 | |
| 	existing = frappe.db.sql("""select name, from_time, to_time from `tab{0}`
 | |
| 		where `{1}`=%(val)s and schedule_date = %(schedule_date)s and
 | |
| 		(
 | |
| 			(from_time > %(from_time)s and from_time < %(to_time)s) or
 | |
| 			(to_time > %(from_time)s and to_time < %(to_time)s) or
 | |
| 			(%(from_time)s > from_time and %(from_time)s < to_time) or
 | |
| 			(%(from_time)s = from_time and %(to_time)s = to_time))
 | |
| 		and name!=%(name)s and docstatus!=2""".format(doctype, fieldname),
 | |
| 		{
 | |
| 			"schedule_date": doc.schedule_date,
 | |
| 			"val": value or doc.get(fieldname),
 | |
| 			"from_time": doc.from_time,
 | |
| 			"to_time": doc.to_time,
 | |
| 			"name": doc.name or "No Name"
 | |
| 		}, as_dict=True)
 | |
| 
 | |
| 	return existing[0] if existing else None
 | |
| 
 | |
| 
 | |
| def validate_duplicate_student(students):
 | |
| 	unique_students= []
 | |
| 	for stud in students:
 | |
| 		if stud.student in unique_students:
 | |
| 			frappe.throw(_("Student {0} - {1} appears Multiple times in row {2} & {3}")
 | |
| 				.format(stud.student, stud.student_name, unique_students.index(stud.student)+1, stud.idx))
 | |
| 		else:
 | |
| 			unique_students.append(stud.student)
 | |
| 
 | |
| 		return None
 | |
| 
 | |
| # LMS Utils
 | |
| def get_current_student():
 | |
| 	"""Returns current student from frappe.session.user
 | |
| 
 | |
| 	Returns:
 | |
| 		object: Student Document
 | |
| 	"""
 | |
| 	email = frappe.session.user
 | |
| 	if email in ('Administrator', 'Guest'):
 | |
| 		return None
 | |
| 	try:
 | |
| 		student_id = frappe.get_all("Student", {"student_email_id": email}, ["name"])[0].name
 | |
| 		return frappe.get_doc("Student", student_id)
 | |
| 	except (IndexError, frappe.DoesNotExistError):
 | |
| 		return None
 | |
| 
 | |
| def get_portal_programs():
 | |
| 	"""Returns a list of all program to be displayed on the portal
 | |
| 	Programs are returned based on the following logic
 | |
| 		is_published and (student_is_enrolled or student_can_self_enroll)
 | |
| 
 | |
| 	Returns:
 | |
| 		list of dictionary: List of all programs and to be displayed on the portal along with access rights
 | |
| 	"""
 | |
| 	published_programs = frappe.get_all("Program", filters={"is_published": True})
 | |
| 	if not published_programs:
 | |
| 		return None
 | |
| 
 | |
| 	program_list = [frappe.get_doc("Program", program) for program in published_programs]
 | |
| 	portal_programs = [{'program': program, 'has_access': allowed_program_access(program.name)} for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll]
 | |
| 
 | |
| 	return portal_programs
 | |
| 
 | |
| def allowed_program_access(program, student=None):
 | |
| 	"""Returns enrollment status for current student
 | |
| 
 | |
| 	Args:
 | |
| 		program (string): Name of the program
 | |
| 		student (object): instance of Student document
 | |
| 
 | |
| 	Returns:
 | |
| 		bool: Is current user enrolled or not
 | |
| 	"""
 | |
| 	if has_super_access():
 | |
| 		return True
 | |
| 	if not student:
 | |
| 		student = get_current_student()
 | |
| 	if student and get_enrollment('program', program, student.name):
 | |
| 		return True
 | |
| 	else:
 | |
| 		return False
 | |
| 
 | |
| def get_enrollment(master, document, student):
 | |
| 	"""Gets enrollment for course or program
 | |
| 
 | |
| 	Args:
 | |
| 		master (string): can either be program or course
 | |
| 		document (string): program or course name
 | |
| 		student (string): Student ID
 | |
| 
 | |
| 	Returns:
 | |
| 		string: Enrollment Name if exists else returns empty string
 | |
| 	"""
 | |
| 	if master == 'program':
 | |
| 		enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document, 'docstatus': 1})
 | |
| 	if master == 'course':
 | |
| 		enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document})
 | |
| 
 | |
| 	if enrollments:
 | |
| 		return enrollments[0].name
 | |
| 	else:
 | |
| 		return None
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def enroll_in_program(program_name, student=None):
 | |
| 	"""Enroll student in program
 | |
| 
 | |
| 	Args:
 | |
| 		program_name (string): Name of the program to be enrolled into
 | |
| 		student (string, optional): name of student who has to be enrolled, if not
 | |
| 			provided, a student will be created from the current user
 | |
| 
 | |
| 	Returns:
 | |
| 		string: name of the program enrollment document
 | |
| 	"""
 | |
| 	if has_super_access():
 | |
| 		return
 | |
| 
 | |
| 	if not student == None:
 | |
| 		student = frappe.get_doc("Student", student)
 | |
| 	else:
 | |
| 		# Check if self enrollment in allowed
 | |
| 		program = frappe.get_doc('Program', program_name)
 | |
| 		if not program.allow_self_enroll:
 | |
| 			return frappe.throw(_("You are not allowed to enroll for this course"))
 | |
| 
 | |
| 		student = get_current_student()
 | |
| 		if not student:
 | |
| 			student = create_student_from_current_user()
 | |
| 
 | |
| 	# Check if student is already enrolled in program
 | |
| 	enrollment = get_enrollment('program', program_name, student.name)
 | |
| 	if enrollment:
 | |
| 		return enrollment
 | |
| 
 | |
| 	# Check if self enrollment in allowed
 | |
| 	program = frappe.get_doc('Program', program_name)
 | |
| 	if not program.allow_self_enroll:
 | |
| 		return frappe.throw(_("You are not allowed to enroll for this course"))
 | |
| 
 | |
| 	# Enroll in program
 | |
| 	program_enrollment = student.enroll_in_program(program_name)
 | |
| 	return program_enrollment.name
 | |
| 
 | |
| def has_super_access():
 | |
| 	"""Check if user has a role that allows full access to LMS
 | |
| 
 | |
| 	Returns:
 | |
| 		bool: true if user has access to all lms content
 | |
| 	"""
 | |
| 	current_user = frappe.get_doc('User', frappe.session.user)
 | |
| 	roles = set([role.role for role in current_user.roles])
 | |
| 	return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'})
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def add_activity(course, content_type, content, program):
 | |
| 	if has_super_access():
 | |
| 		return None
 | |
| 
 | |
| 	student = get_current_student()
 | |
| 	if not student:
 | |
| 		return frappe.throw(_("Student with email {0} does not exist".format(frappe.session.user)), frappe.DoesNotExistError)
 | |
| 
 | |
| 	enrollment = get_or_create_course_enrollment(course, program)
 | |
| 	if content_type == 'Quiz':
 | |
| 		return
 | |
| 	else:
 | |
| 		return enrollment.add_activity(content_type, content)
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def evaluate_quiz(quiz_response, quiz_name, course, program):
 | |
| 	import json
 | |
| 
 | |
| 	student = get_current_student()
 | |
| 
 | |
| 	quiz_response = json.loads(quiz_response)
 | |
| 	quiz = frappe.get_doc("Quiz", quiz_name)
 | |
| 	result, score, status = quiz.evaluate(quiz_response, quiz_name)
 | |
| 
 | |
| 	if has_super_access():
 | |
| 		return {'result': result, 'score': score, 'status': status}
 | |
| 
 | |
| 	if student:
 | |
| 		enrollment = get_or_create_course_enrollment(course, program)
 | |
| 		if quiz.allowed_attempt(enrollment, quiz_name):
 | |
| 			enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
 | |
| 			return {'result': result, 'score': score, 'status': status}
 | |
| 		else:
 | |
| 			return None
 | |
| 
 | |
| @frappe.whitelist()
 | |
| def get_quiz(quiz_name, course):
 | |
| 	try:
 | |
| 		quiz = frappe.get_doc("Quiz", quiz_name)
 | |
| 		questions = quiz.get_questions()
 | |
| 	except:
 | |
| 		frappe.throw(_("Quiz {0} does not exist".format(quiz_name)))
 | |
| 		return None
 | |
| 
 | |
| 	questions = [{
 | |
| 		'name': question.name,
 | |
| 		'question': question.question,
 | |
| 		'type': question.question_type,
 | |
| 		'options': [{'name': option.name, 'option': option.option}
 | |
| 					for option in question.options],
 | |
| 		} for question in questions]
 | |
| 
 | |
| 	if has_super_access():
 | |
| 		return {'questions': questions, 'activity': None}
 | |
| 
 | |
| 	student = get_current_student()
 | |
| 	course_enrollment = get_enrollment("course", course, student.name)
 | |
| 	status, score, result = check_quiz_completion(quiz, course_enrollment)
 | |
| 	return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}}
 | |
| 
 | |
| def get_topic_progress(topic, course_name, program):
 | |
| 	"""
 | |
| 	Return the porgress of a course in a program as well as the content to continue from.
 | |
| 		:param topic_name:
 | |
| 		:param course_name:
 | |
| 	"""
 | |
| 	student = get_current_student()
 | |
| 	if not student:
 | |
| 		return None
 | |
| 	course_enrollment = get_or_create_course_enrollment(course_name, program)
 | |
| 	progress = student.get_topic_progress(course_enrollment.name, topic)
 | |
| 	if not progress:
 | |
| 		return None
 | |
| 	count = sum([activity['is_complete'] for activity in progress])
 | |
| 	if count == 0:
 | |
| 		return {'completed': False, 'started': False}
 | |
| 	elif count == len(progress):
 | |
| 		return {'completed': True, 'started': True}
 | |
| 	elif count < len(progress):
 | |
| 		return {'completed': False, 'started': True}
 | |
| 
 | |
| def get_course_progress(course, program):
 | |
| 	"""
 | |
| 	Return the porgress of a course in a program as well as the content to continue from.
 | |
| 		:param topic_name:
 | |
| 		:param course_name:
 | |
| 	"""
 | |
| 	course_progress = []
 | |
| 	for course_topic in course.topics:
 | |
| 		topic = frappe.get_doc("Topic", course_topic.topic)
 | |
| 		progress = get_topic_progress(topic, course.name, program)
 | |
| 		if progress:
 | |
| 			course_progress.append(progress)
 | |
| 	if course_progress:
 | |
| 		number_of_completed_topics = sum([activity['completed'] for activity in course_progress])
 | |
| 		total_topics = len(course_progress)
 | |
| 		if total_topics == 1:
 | |
| 			return course_progress[0]
 | |
| 		if number_of_completed_topics == 0:
 | |
| 			return {'completed': False, 'started': False}
 | |
| 		if number_of_completed_topics == total_topics:
 | |
| 			return {'completed': True, 'started': True}
 | |
| 		if number_of_completed_topics < total_topics:
 | |
| 			return {'completed': False, 'started': True}
 | |
| 
 | |
| 	return None
 | |
| 
 | |
| def get_program_progress(program):
 | |
| 	program_progress = []
 | |
| 	if not program.courses:
 | |
| 		return None
 | |
| 	for program_course in program.courses:
 | |
| 		course = frappe.get_doc("Course", program_course.course)
 | |
| 		progress = get_course_progress(course, program.name)
 | |
| 		if progress:
 | |
| 			progress['name'] = course.name
 | |
| 			progress['course'] = course.course_name
 | |
| 			program_progress.append(progress)
 | |
| 
 | |
| 	if program_progress:
 | |
| 		return program_progress
 | |
| 
 | |
| 	return None
 | |
| 
 | |
| def get_program_completion(program):
 | |
| 	topics = frappe.db.sql("""select `tabCourse Topic`.topic, `tabCourse Topic`.parent
 | |
| 	from `tabCourse Topic`,
 | |
| 		 `tabProgram Course`
 | |
| 	where `tabCourse Topic`.parent = `tabProgram Course`.course
 | |
| 			and `tabProgram Course`.parent = %s""", program.name)
 | |
| 
 | |
| 	progress = []
 | |
| 	for topic in topics:
 | |
| 		topic_doc = frappe.get_doc('Topic', topic[0])
 | |
| 		topic_progress = get_topic_progress(topic_doc, topic[1], program.name)
 | |
| 		if topic_progress:
 | |
| 			progress.append(topic_progress)
 | |
| 
 | |
| 	if progress:
 | |
| 		number_of_completed_topics = sum([activity['completed'] for activity in progress if activity])
 | |
| 		total_topics = len(progress)
 | |
| 		try:
 | |
| 			return int((float(number_of_completed_topics)/total_topics)*100)
 | |
| 		except ZeroDivisionError:
 | |
| 			return 0
 | |
| 
 | |
| 	return 0
 | |
| 
 | |
| def create_student_from_current_user():
 | |
| 	user = frappe.get_doc("User", frappe.session.user)
 | |
| 
 | |
| 	student = frappe.get_doc({
 | |
| 		"doctype": "Student",
 | |
| 		"first_name": user.first_name,
 | |
| 		"last_name": user.last_name,
 | |
| 		"student_email_id": user.email,
 | |
| 		"user": frappe.session.user
 | |
| 		})
 | |
| 
 | |
| 	student.save(ignore_permissions=True)
 | |
| 	return student
 | |
| 
 | |
| def get_or_create_course_enrollment(course, program):
 | |
| 	student = get_current_student()
 | |
| 	course_enrollment = get_enrollment("course", course, student.name)
 | |
| 	if not course_enrollment:
 | |
| 		program_enrollment = get_enrollment('program', program, student.name)
 | |
| 		if not program_enrollment:
 | |
| 			frappe.throw(_("You are not enrolled in program {0}".format(program)))
 | |
| 			return
 | |
| 		return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name))
 | |
| 	else:
 | |
| 		return frappe.get_doc('Course Enrollment', course_enrollment)
 | |
| 
 | |
| def check_content_completion(content_name, content_type, enrollment_name):
 | |
| 	activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment_name, 'content_type': content_type, 'content': content_name})
 | |
| 	if activity:
 | |
| 		return True
 | |
| 	else:
 | |
| 		return False
 | |
| 
 | |
| def check_quiz_completion(quiz, enrollment_name):
 | |
| 	attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
 | |
| 	status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
 | |
| 	score = None
 | |
| 	result = None
 | |
| 	if attempts:
 | |
| 		if quiz.grading_basis == 'Last Highest Score':
 | |
| 			attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
 | |
| 		score = attempts[0]['score']
 | |
| 		result = attempts[0]['status']
 | |
| 		if result == 'Pass':
 | |
| 			status = True
 | |
| 	return status, score, result |