diff --git a/academia/academia/doctype/faculty_member/faculty_member.json b/academia/academia/doctype/faculty_member/faculty_member.json index 6899e4b1..606e99d7 100644 --- a/academia/academia/doctype/faculty_member/faculty_member.json +++ b/academia/academia/doctype/faculty_member/faculty_member.json @@ -68,6 +68,11 @@ "faculty_member_activity", "awards_and_appreciation_certificates_section", "faculty_member_award_and_appreciation_certificate", + "availability_tab", + "available_for_work", + "start_date", + "column_break_nvaw", + "pledge_document", "connections_tab" ], "fields": [ @@ -435,6 +440,35 @@ "fieldname": "date_of_obtaining_the_academic_rank", "fieldtype": "Date", "label": "Date of Obtaining the Academic Rank" + }, + { + "fieldname": "availability_tab", + "fieldtype": "Tab Break", + "label": "Availability" + }, + { + "default": "0", + "fieldname": "available_for_work", + "fieldtype": "Check", + "label": "Available for work" + }, + { + "depends_on": "available_for_work", + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date", + "mandatory_depends_on": "available_for_work" + }, + { + "fieldname": "column_break_nvaw", + "fieldtype": "Column Break" + }, + { + "depends_on": "available_for_work", + "fieldname": "pledge_document", + "fieldtype": "Attach", + "label": "Pledge Document", + "mandatory_depends_on": "available_for_work" } ], "image_field": "image", @@ -472,7 +506,7 @@ "link_fieldname": "evaluatee_party" } ], - "modified": "2024-08-18 04:40:39.354598", + "modified": "2024-11-04 13:19:00.510422", "modified_by": "Administrator", "module": "Academia", "name": "Faculty Member", diff --git a/academia/academia/doctype/faculty_member/faculty_member.py b/academia/academia/doctype/faculty_member/faculty_member.py index ef6ca08a..fd4ddd3e 100644 --- a/academia/academia/doctype/faculty_member/faculty_member.py +++ b/academia/academia/doctype/faculty_member/faculty_member.py @@ -8,153 +8,195 @@ class FacultyMember(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from academia.academia.doctype.faculty_member_academic_ranking.faculty_member_academic_ranking import FacultyMemberAcademicRanking - from academia.academia.doctype.faculty_member_academic_services.faculty_member_academic_services import FacultyMemberAcademicServices - from academia.academia.doctype.faculty_member_activity.faculty_member_activity import FacultyMemberActivity - from academia.academia.doctype.faculty_member_award_and_appreciation_certificate.faculty_member_award_and_appreciation_certificate import FacultyMemberAwardandAppreciationCertificate - from academia.academia.doctype.faculty_member_conference_and_workshop.faculty_member_conference_and_workshop import FacultyMemberConferenceandWorkshop - from academia.academia.doctype.faculty_member_course.faculty_member_course import FacultyMemberCourse - from academia.academia.doctype.faculty_member_language.faculty_member_language import FacultyMemberLanguage - from academia.academia.doctype.faculty_member_training_course.faculty_member_training_course import FacultyMemberTrainingCourse - from academia.academia.doctype.faculty_member_university_and_community_service.faculty_member_university_and_community_service import FacultyMemberUniversityandCommunityService - from frappe.types import DF - - academic_rank: DF.Link - academic_services: DF.TableMultiSelect[FacultyMemberAcademicServices] - commencement_of_work_attachment: DF.Attach | None - commencement_of_work_date: DF.Date | None - commencement_of_work_decision_number: DF.Data | None - company: DF.Link - courses: DF.TableMultiSelect[FacultyMemberCourse] - date: DF.Date | None - date_of_joining_in_service: DF.Date | None - date_of_joining_in_university: DF.Date | None - date_of_obtaining_the_academic_rank: DF.Date | None - decision_attachment: DF.Attach | None - decision_number: DF.Data | None - department: DF.Link - email: DF.Data - employee: DF.Link - employment_type: DF.Link | None - external_faculty: DF.Link | None - faculty: DF.Link - faculty_member_academic_ranking: DF.Table[FacultyMemberAcademicRanking] - faculty_member_activity: DF.Table[FacultyMemberActivity] - faculty_member_award_and_appreciation_certificate: DF.Table[FacultyMemberAwardandAppreciationCertificate] - faculty_member_conference_and_workshop: DF.Table[FacultyMemberConferenceandWorkshop] - faculty_member_name: DF.Data - faculty_member_name_english: DF.Data | None - faculty_member_training_course: DF.Table[FacultyMemberTrainingCourse] - faculty_member_university_and_community_service: DF.Table[FacultyMemberUniversityandCommunityService] - from_another_university: DF.Link | None - general_field: DF.Data | None - google_scholar_profile_link: DF.Data | None - image: DF.AttachImage | None - is_eligible_for_granting_tenure: DF.Check - languages: DF.TableMultiSelect[FacultyMemberLanguage] - naming_series: DF.Literal["ACAD-FM-"] - nationality: DF.Link | None - probation_period_end_date: DF.Date | None - scientific_degree: DF.Link - specialist_field: DF.Data | None - tenure_status: DF.Literal["", "On Probation", "Tenured"] - # end: auto-generated types - # Begin auto-generated types - - def validate(self): - # Calling functions - self.validate_duplicate_employee() - self.validate_date() - self.validate_url() - self.validate_decision_number() - self.get_probation_end_date() - self.check_tenure_eligibility() - # End of validate controller hook - - # Validate duplicate 'employee' field - def validate_duplicate_employee(self): - if self.employee: - exist_employee = frappe.get_value("Faculty Member", {"employee": self.employee, "name": ["!=", self.name]}, "faculty_member_name") - if exist_employee: - frappe.throw(_(f"Employee {self.employee} is already assigned to {exist_employee}")) - - # Validate 'date_of_joining_in_university' and 'date_of_joining_in_service' and 'date in tenure data' fields - def validate_date(self): - today = frappe.utils.today() - - if self.date_of_joining_in_service: - if self.date_of_joining_in_service > today: - frappe.throw(_("Date of joining in service cannot be in the future.")) - - if self.date_of_joining_in_university: - if self.date_of_joining_in_university > today: - frappe.throw(_("Date of joining in university cannot be in the future.")) - - if self.date_of_joining_in_service and self.date_of_joining_in_university: - if self.date_of_joining_in_service > self.date_of_joining_in_university: - frappe.throw(_("Date of joining in service must be before the date of joining in university.")) - - if self.date: - if self.date > today: - frappe.throw(_("Date in tenure data section cannot be in the future.")) - - # Validate 'google_scholar_profile_link' field - def validate_url(self): - url_pattern = re.compile(r'^(http|https|ftp)://\S+$') - if self.google_scholar_profile_link: - if not url_pattern.match(self.google_scholar_profile_link): - frappe.throw(_("Google Scholar Profile Link is not valid. Please enter a valid URL starting with http, https, or ftp.")) - - # Validate 'decision_number' field - def validate_decision_number(self): - if self.decision_number and not self.decision_number.isdigit(): - frappe.throw(_("Decision Number should contain only digits.")) - - # Fetch and set probation end date - def get_probation_end_date(self): - if self.commencement_of_work_date and self.tenure_status == "On Probation" and self.employment_type == "Official": - faculty_member_settings = frappe.get_all( - "Faculty Member Settings", - filters=[ - ["academic_rank", "=", self.academic_rank], - ["valid_from", "<=", self.commencement_of_work_date], - ], - fields=["name", "probation_period"], - order_by="valid_from desc", - ) - - if faculty_member_settings: - faculty_member_settings = faculty_member_settings[0] - self.probation_period_end_date = add_months( - self.commencement_of_work_date, - int(faculty_member_settings.probation_period), - ) - else: - frappe.throw( - _( - "Probation periods not found in Faculty Member Settings for {} from this date. Please create appropriate settings.".format(self.academic_rank) - ), - title=_("Missing Probation Settings"), - ) - - # Check tenure eligibility - def check_tenure_eligibility(self): - if self.probation_period_end_date: - try: - probation_end_date = datetime.strptime(self.probation_period_end_date, "%Y-%m-%d").date() - today = datetime.strptime(nowdate(), "%Y-%m-%d").date() - - if probation_end_date >= today: - self.is_eligible_for_granting_tenure = 1 - else: - self.is_eligible_for_granting_tenure = 0 - - except ValueError as e: - frappe.msgprint(_("Error parsing probation end date: {0}").format(e), title=_("Date Parsing Error"), indicator="red") - self.is_eligible_for_granting_tenure = 0 + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from academia.academia.doctype.faculty_member_academic_ranking.faculty_member_academic_ranking import ( + FacultyMemberAcademicRanking, + ) + from academia.academia.doctype.faculty_member_academic_services.faculty_member_academic_services import ( + FacultyMemberAcademicServices, + ) + from academia.academia.doctype.faculty_member_activity.faculty_member_activity import ( + FacultyMemberActivity, + ) + from academia.academia.doctype.faculty_member_award_and_appreciation_certificate.faculty_member_award_and_appreciation_certificate import ( + FacultyMemberAwardandAppreciationCertificate, + ) + from academia.academia.doctype.faculty_member_conference_and_workshop.faculty_member_conference_and_workshop import ( + FacultyMemberConferenceandWorkshop, + ) + from academia.academia.doctype.faculty_member_course.faculty_member_course import FacultyMemberCourse + from academia.academia.doctype.faculty_member_language.faculty_member_language import ( + FacultyMemberLanguage, + ) + from academia.academia.doctype.faculty_member_training_course.faculty_member_training_course import ( + FacultyMemberTrainingCourse, + ) + from academia.academia.doctype.faculty_member_university_and_community_service.faculty_member_university_and_community_service import ( + FacultyMemberUniversityandCommunityService, + ) + from frappe.types import DF + + academic_rank: DF.Link + academic_services: DF.TableMultiSelect[FacultyMemberAcademicServices] + available_for_work: DF.Check + commencement_of_work_attachment: DF.Attach | None + commencement_of_work_date: DF.Date | None + commencement_of_work_decision_number: DF.Data | None + company: DF.Link + courses: DF.TableMultiSelect[FacultyMemberCourse] + date: DF.Date | None + date_of_joining_in_service: DF.Date | None + date_of_joining_in_university: DF.Date | None + date_of_obtaining_the_academic_rank: DF.Date | None + decision_attachment: DF.Attach | None + decision_number: DF.Data | None + department: DF.Link + email: DF.Data + employee: DF.Link + employment_type: DF.Link | None + external_faculty: DF.Link | None + faculty: DF.Link + faculty_member_academic_ranking: DF.Table[FacultyMemberAcademicRanking] + faculty_member_activity: DF.Table[FacultyMemberActivity] + faculty_member_award_and_appreciation_certificate: DF.Table[ + FacultyMemberAwardandAppreciationCertificate + ] + faculty_member_conference_and_workshop: DF.Table[FacultyMemberConferenceandWorkshop] + faculty_member_name: DF.Data + faculty_member_name_english: DF.Data | None + faculty_member_training_course: DF.Table[FacultyMemberTrainingCourse] + faculty_member_university_and_community_service: DF.Table[FacultyMemberUniversityandCommunityService] + from_another_university: DF.Link | None + general_field: DF.Data | None + google_scholar_profile_link: DF.Data | None + image: DF.AttachImage | None + is_eligible_for_granting_tenure: DF.Check + languages: DF.TableMultiSelect[FacultyMemberLanguage] + naming_series: DF.Literal["ACAD-FM-"] + nationality: DF.Link | None + pledge_document: DF.Attach | None + probation_period_end_date: DF.Date | None + scientific_degree: DF.Link + specialist_field: DF.Data | None + start_date: DF.Date | None + tenure_status: DF.Literal["", "On Probation", "Tenured"] + # end: auto-generated types + # Begin auto-generated types + + def validate(self): + # Calling functions + self.validate_duplicate_employee() + self.validate_date() + self.validate_url() + self.validate_decision_number() + self.get_probation_end_date() + self.check_tenure_eligibility() + + # End of validate controller hook + + # Validate duplicate 'employee' field + def validate_duplicate_employee(self): + if self.employee: + exist_employee = frappe.get_value( + "Faculty Member", + {"employee": self.employee, "name": ["!=", self.name]}, + "faculty_member_name", + ) + if exist_employee: + frappe.throw(_(f"Employee {self.employee} is already assigned to {exist_employee}")) + + # Validate 'date_of_joining_in_university' and 'date_of_joining_in_service' and 'date in tenure data' fields + def validate_date(self): + today = frappe.utils.today() + + if self.date_of_joining_in_service: + if self.date_of_joining_in_service > today: + frappe.throw(_("Date of joining in service cannot be in the future.")) + + if self.date_of_joining_in_university: + if self.date_of_joining_in_university > today: + frappe.throw(_("Date of joining in university cannot be in the future.")) + + if self.date_of_joining_in_service and self.date_of_joining_in_university: + if self.date_of_joining_in_service > self.date_of_joining_in_university: + frappe.throw( + _("Date of joining in service must be before the date of joining in university.") + ) + + if self.date: + if self.date > today: + frappe.throw(_("Date in tenure data section cannot be in the future.")) + + # Validate 'google_scholar_profile_link' field + def validate_url(self): + url_pattern = re.compile(r"^(http|https|ftp)://\S+$") + if self.google_scholar_profile_link: + if not url_pattern.match(self.google_scholar_profile_link): + frappe.throw( + _( + "Google Scholar Profile Link is not valid. Please enter a valid URL starting with http, https, or ftp." + ) + ) + + # Validate 'decision_number' field + def validate_decision_number(self): + if self.decision_number and not self.decision_number.isdigit(): + frappe.throw(_("Decision Number should contain only digits.")) + + # Fetch and set probation end date + def get_probation_end_date(self): + if ( + self.commencement_of_work_date + and self.tenure_status == "On Probation" + and self.employment_type == "Official" + ): + faculty_member_settings = frappe.get_all( + "Faculty Member Settings", + filters=[ + ["academic_rank", "=", self.academic_rank], + ["valid_from", "<=", self.commencement_of_work_date], + ], + fields=["name", "probation_period"], + order_by="valid_from desc", + ) + + if faculty_member_settings: + faculty_member_settings = faculty_member_settings[0] + self.probation_period_end_date = add_months( + self.commencement_of_work_date, + int(faculty_member_settings.probation_period), + ) + else: + frappe.throw( + _( + "Probation periods not found in Faculty Member Settings for {} from this date. Please create appropriate settings.".format( + self.academic_rank + ) + ), + title=_("Missing Probation Settings"), + ) + + # Check tenure eligibility + def check_tenure_eligibility(self): + if self.probation_period_end_date: + try: + probation_end_date = datetime.strptime(self.probation_period_end_date, "%Y-%m-%d").date() + today = datetime.strptime(nowdate(), "%Y-%m-%d").date() + + if probation_end_date >= today: + self.is_eligible_for_granting_tenure = 1 + else: + self.is_eligible_for_granting_tenure = 0 + + except ValueError as e: + frappe.msgprint( + _("Error parsing probation end date: {0}").format(e), + title=_("Date Parsing Error"), + indicator="red", + ) + self.is_eligible_for_granting_tenure = 0