diff --git a/hrms/hr/doctype/interview/interview.js b/hrms/hr/doctype/interview/interview.js index 4df982e8c8..705ce75bca 100644 --- a/hrms/hr/doctype/interview/interview.js +++ b/hrms/hr/doctype/interview/interview.js @@ -72,7 +72,7 @@ frappe.ui.form.on("Interview", { frappe.call({ method: "hrms.hr.doctype.interview.interview.get_expected_skill_set", args: { - interview_round: frm.doc.interview_round, + interview_type: frm.doc.interview_type, }, callback: function (r) { frm.events.show_feedback_dialog(frm, r.message); @@ -198,15 +198,15 @@ frappe.ui.form.on("Interview", { }); }, - interview_round: function (frm) { + interview_type: function (frm) { frm.set_value("job_applicant", ""); frm.trigger("set_applicable_interviewers"); }, job_applicant: function (frm) { - if (!frm.doc.interview_round) { + if (!frm.doc.interview_type) { frm.set_value("job_applicant", ""); - frappe.throw(__("Select Interview Round First")); + frappe.throw(__("Select Interview Type First")); } if (frm.doc.job_applicant && !frm.doc.designation) { @@ -218,7 +218,7 @@ frappe.ui.form.on("Interview", { frappe.call({ method: "hrms.hr.doctype.interview.interview.get_interviewers", args: { - interview_round: frm.doc.interview_round || "", + interview_type: frm.doc.interview_type || "", }, callback: function (r) { frm.clear_table("interview_details"); diff --git a/hrms/hr/doctype/interview/interview.json b/hrms/hr/doctype/interview/interview.json index 0ed0e06b91..60e7caad44 100644 --- a/hrms/hr/doctype/interview/interview.json +++ b/hrms/hr/doctype/interview/interview.json @@ -7,7 +7,7 @@ "engine": "InnoDB", "field_order": [ "interview_details_section", - "interview_round", + "interview_type", "job_applicant", "job_opening", "designation", @@ -48,15 +48,6 @@ "options": "Job Opening", "read_only": 1 }, - { - "fieldname": "interview_round", - "fieldtype": "Link", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Interview Round", - "options": "Interview Round", - "reqd": 1 - }, { "default": "Pending", "fieldname": "status", @@ -102,7 +93,7 @@ "label": "Details" }, { - "fetch_from": "interview_round.expected_average_rating", + "fetch_from": "interview_type.expected_average_rating", "fieldname": "expected_average_rating", "fieldtype": "Rating", "label": "Expected Average Rating", @@ -119,7 +110,7 @@ "fieldtype": "Column Break" }, { - "fetch_from": "interview_round.designation", + "fetch_from": "interview_type.designation", "fieldname": "designation", "fieldtype": "Link", "in_list_view": 1, @@ -189,6 +180,15 @@ "fieldtype": "Table", "label": "Interviewers", "options": "Interview Detail" + }, + { + "fieldname": "interview_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview Type", + "options": "Interview Type", + "reqd": 1 } ], "index_web_pages_for_search": 1, @@ -199,11 +199,11 @@ "link_fieldname": "interview" } ], - "modified": "2025-11-21 17:24:02.676395", + "modified": "2026-02-19 15:40:38.547112", "modified_by": "Administrator", "module": "HR", "name": "Interview", - "naming_rule": "Expression (old style)", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { diff --git a/hrms/hr/doctype/interview/interview.py b/hrms/hr/doctype/interview/interview.py index 98490da12a..414caf16a4 100644 --- a/hrms/hr/doctype/interview/interview.py +++ b/hrms/hr/doctype/interview/interview.py @@ -32,8 +32,8 @@ class Interview(Document): expected_average_rating: DF.Rating from_time: DF.Time interview_details: DF.Table[InterviewDetail] - interview_round: DF.Link interview_summary: DF.Text | None + interview_type: DF.Link job_applicant: DF.Link job_opening: DF.Link | None reminded: DF.Check @@ -58,13 +58,13 @@ def on_submit(self): def validate_duplicate_interview(self): duplicate_interview = frappe.db.exists( "Interview", - {"job_applicant": self.job_applicant, "interview_round": self.interview_round, "docstatus": 1}, + {"job_applicant": self.job_applicant, "interview_type": self.interview_type, "docstatus": 1}, ) if duplicate_interview: frappe.throw( _( - "Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}" + "Job Applicants are not allowed to appear twice for the same Interview Type. Interview {0} already scheduled for Job Applicant {1}" ).format( frappe.bold(get_link_to_form("Interview", duplicate_interview)), frappe.bold(self.job_applicant), @@ -77,8 +77,8 @@ def validate_designation(self): if self.designation != applicant_designation: frappe.throw( _( - "Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}" - ).format(self.interview_round, frappe.bold(self.designation), applicant_designation), + "Interview Type {0} is only for Designation {1}. Job Applicant has applied for the role {2}" + ).format(self.interview_type, frappe.bold(self.designation), applicant_designation), exc=DuplicateInterviewRoundError, ) else: @@ -155,8 +155,8 @@ def on_discard(self): @frappe.whitelist() -def get_interviewers(interview_round: str) -> list[str]: - return frappe.get_all("Interviewer", filters={"parent": interview_round}, fields=["user as interviewer"]) +def get_interviewers(interview_type: str) -> list[dict]: + return frappe.get_all("Interviewer", filters={"parent": interview_type}, fields=["user as interviewer"]) def get_recipients(name, for_feedback=0): @@ -337,9 +337,9 @@ def send_daily_feedback_reminder(): @frappe.whitelist() -def get_expected_skill_set(interview_round: str) -> list[dict]: +def get_expected_skill_set(interview_type: str): return frappe.get_all( - "Expected Skill Set", filters={"parent": interview_round}, fields=["skill"], order_by="idx" + "Expected Skill Set", filters={"parent": interview_type}, fields=["skill"], order_by="idx" ) @@ -424,7 +424,7 @@ def get_events(start: str, end: str, filters: str | None = None): interviews = frappe.db.sql( f""" SELECT DISTINCT - `tabInterview`.name, `tabInterview`.job_applicant, `tabInterview`.interview_round, + `tabInterview`.name, `tabInterview`.job_applicant, `tabInterview`.interview_type, `tabInterview`.scheduled_on, `tabInterview`.status, `tabInterview`.from_time as from_time, `tabInterview`.to_time as to_time from @@ -441,7 +441,7 @@ def get_events(start: str, end: str, filters: str | None = None): for d in interviews: subject_data = [] - for field in ["name", "job_applicant", "interview_round"]: + for field in ["name", "job_applicant", "interview_type"]: if not d.get(field): continue subject_data.append(d.get(field)) diff --git a/hrms/hr/doctype/interview/test_interview.py b/hrms/hr/doctype/interview/test_interview.py index 39c78ae58a..ce307d425b 100644 --- a/hrms/hr/doctype/interview/test_interview.py +++ b/hrms/hr/doctype/interview/test_interview.py @@ -111,7 +111,7 @@ def test_get_interview_details_for_applicant_dashboard(self): details.get("interviews").get(interview.name), { "name": interview.name, - "interview_round": interview.interview_round, + "interview_type": interview.interview_type, "scheduled_on": interview.scheduled_on, "average_rating": interview.average_rating * 5, "status": "Pending", @@ -221,7 +221,7 @@ def create_interview_and_dependencies( create_user("test_interviewer1@example.com", "Interviewer") create_user("test_interviewer2@example.com", "Interviewer") - interview_round = create_interview_round( + interview_type = create_interview_type( "Technical Round", ["Python", "JS"], ["test_interviewer1@example.com", "test_interviewer2@example.com"], @@ -230,7 +230,7 @@ def create_interview_and_dependencies( ) interview = frappe.new_doc("Interview") - interview.interview_round = interview_round.name + interview.interview_type = interview_type.name interview.job_applicant = job_applicant interview.scheduled_on = scheduled_on or getdate() interview.from_time = from_time or nowtime() @@ -247,26 +247,25 @@ def create_interview_and_dependencies( return interview -def create_interview_round(name, skill_set, interviewers=None, designation=None, save=True): +def create_interview_type(name, skill_set, interviewers=None, designation=None, save=True): create_skill_set(skill_set) - interview_round = frappe.new_doc("Interview Round") - interview_round.round_name = name - interview_round.interview_type = create_interview_type() + interview_type = frappe.new_doc("Interview Type") + interview_type.interview_type_name = name + interview_type.description = "_Test_Description" # average rating = 4 - interview_round.expected_average_rating = 0.8 + interview_type.expected_average_rating = 0.8 if designation: - interview_round.designation = designation + interview_type.designation = designation for skill in skill_set: - interview_round.append("expected_skill_set", {"skill": skill}) + interview_type.append("expected_skill_set", {"skill": skill}) for interviewer in interviewers: - interview_round.append("interviewers", {"user": interviewer}) + interview_type.append("interviewers", {"user": interviewer}) if save: - interview_round.save() - - return interview_round + interview_type.save() + return interview_type def create_skill_set(skill_set): @@ -277,18 +276,6 @@ def create_skill_set(skill_set): doc.save() -def create_interview_type(name="test_interview_type"): - if frappe.db.exists("Interview Type", name): - return frappe.get_doc("Interview Type", name).name - else: - doc = frappe.new_doc("Interview Type") - doc.name = name - doc.description = "_Test_Description" - doc.save() - - return doc.name - - def setup_reminder_settings(): if not frappe.db.exists("Email Template", _("Interview Reminder")): base_path = frappe.get_app_path("erpnext", "hr", "doctype") diff --git a/hrms/hr/doctype/interview_feedback/interview_feedback.js b/hrms/hr/doctype/interview_feedback/interview_feedback.js index 0487db1889..42382b38f9 100644 --- a/hrms/hr/doctype/interview_feedback/interview_feedback.js +++ b/hrms/hr/doctype/interview_feedback/interview_feedback.js @@ -14,11 +14,11 @@ frappe.ui.form.on("Interview Feedback", { }); }, - interview_round: function (frm) { + interview_type: function (frm) { frappe.call({ method: "hrms.hr.doctype.interview.interview.get_expected_skill_set", args: { - interview_round: frm.doc.interview_round, + interview_type: frm.doc.interview_type, }, callback: function (r) { frm.set_value("skill_assessment", r.message); diff --git a/hrms/hr/doctype/interview_feedback/interview_feedback.json b/hrms/hr/doctype/interview_feedback/interview_feedback.json index 508af6aec2..f756cd8249 100644 --- a/hrms/hr/doctype/interview_feedback/interview_feedback.json +++ b/hrms/hr/doctype/interview_feedback/interview_feedback.json @@ -8,7 +8,7 @@ "field_order": [ "details_section", "interview", - "interview_round", + "interview_type", "job_applicant", "column_break_3", "interviewer", @@ -31,18 +31,6 @@ "options": "Interview", "reqd": 1 }, - { - "allow_in_quick_entry": 1, - "fetch_from": "interview.interview_round", - "fieldname": "interview_round", - "fieldtype": "Link", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Interview Round", - "options": "Interview Round", - "read_only": 1, - "reqd": 1 - }, { "allow_in_quick_entry": 1, "fieldname": "interviewer", @@ -118,16 +106,28 @@ "label": "Job Applicant", "options": "Job Applicant", "read_only": 1 + }, + { + "allow_in_quick_entry": 1, + "fetch_from": "interview.interview_type", + "fieldname": "interview_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview Type", + "options": "Interview Type", + "read_only": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-27 13:09:51.973613", + "modified": "2026-02-19 16:31:33.463199", "modified_by": "Administrator", "module": "HR", "name": "Interview Feedback", - "naming_rule": "Expression (old style)", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -164,9 +164,10 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "title_field": "interviewer", "track_changes": 1 -} \ No newline at end of file +} diff --git a/hrms/hr/doctype/interview_feedback/interview_feedback.py b/hrms/hr/doctype/interview_feedback/interview_feedback.py index 1286a57aa7..31220e18ee 100644 --- a/hrms/hr/doctype/interview_feedback/interview_feedback.py +++ b/hrms/hr/doctype/interview_feedback/interview_feedback.py @@ -24,7 +24,7 @@ class InterviewFeedback(Document): average_rating: DF.Rating feedback: DF.Text | None interview: DF.Link - interview_round: DF.Link + interview_type: DF.Link interviewer: DF.Link job_applicant: DF.Link | None result: DF.Literal["", "Cleared", "Rejected"] diff --git a/hrms/hr/doctype/interview_feedback/test_interview_feedback.py b/hrms/hr/doctype/interview_feedback/test_interview_feedback.py index c0847dc5b2..3b62164718 100644 --- a/hrms/hr/doctype/interview_feedback/test_interview_feedback.py +++ b/hrms/hr/doctype/interview_feedback/test_interview_feedback.py @@ -19,7 +19,7 @@ def test_validation_for_skill_set(self): interview = create_interview_and_dependencies( job_applicant.name, scheduled_on=add_days(getdate(), -1) ) - skill_ratings = get_skills_rating(interview.interview_round) + skill_ratings = get_skills_rating(interview.interview_type) interviewer = "test_interviewer1@example.com" create_skill_set(["Leadership"]) @@ -37,7 +37,7 @@ def test_average_ratings_on_feedback_submission_and_cancellation(self): interview = create_interview_and_dependencies( job_applicant.name, scheduled_on=add_days(getdate(), -1) ) - skill_ratings = get_skills_rating(interview.interview_round) + skill_ratings = get_skills_rating(interview.interview_type) # For First Interviewer Feedback interviewer = "test_interviewer1@example.com" @@ -85,10 +85,10 @@ def create_interview_feedback(interview, interviewer, skills_ratings): return interview_feedback -def get_skills_rating(interview_round): +def get_skills_rating(interview_type): import random - skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields=["skill"]) + skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_type}, fields=["skill"]) for d in skills: d["rating"] = random.random() return skills diff --git a/hrms/hr/doctype/interview_round/__init__.py b/hrms/hr/doctype/interview_round/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/hrms/hr/doctype/interview_round/interview_round.js b/hrms/hr/doctype/interview_round/interview_round.js deleted file mode 100644 index c6edcae1d4..0000000000 --- a/hrms/hr/doctype/interview_round/interview_round.js +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on("Interview Round", { - refresh: function (frm) { - if (!frm.doc.__islocal) { - frm.add_custom_button(__("Create Interview"), function () { - frm.events.create_interview(frm); - }); - } - }, - designation: function (frm) { - if (frm.doc.designation) { - frappe.db.get_doc("Designation", frm.doc.designation).then((designation) => { - frappe.model.clear_table(frm.doc, "expected_skill_set"); - - designation.skills.forEach((designation_skill) => { - const row = frm.add_child("expected_skill_set"); - row.skill = designation_skill.skill; - }); - - refresh_field("expected_skill_set"); - }); - } - }, - create_interview: function (frm) { - frappe.call({ - method: "hrms.hr.doctype.interview_round.interview_round.create_interview", - args: { - interview_round: frm.doc.name, - }, - callback: function (r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - }, - }); - }, -}); diff --git a/hrms/hr/doctype/interview_round/interview_round.json b/hrms/hr/doctype/interview_round/interview_round.json deleted file mode 100644 index 9ae7e73b92..0000000000 --- a/hrms/hr/doctype/interview_round/interview_round.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "field:round_name", - "creation": "2021-04-12 12:57:19.902866", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "round_name", - "interview_type", - "interviewers", - "column_break_3", - "expected_average_rating", - "expected_skills_section", - "designation", - "expected_skill_set" - ], - "fields": [ - { - "fieldname": "round_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Round Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "designation", - "fieldtype": "Link", - "label": "Designation", - "options": "Designation" - }, - { - "fieldname": "expected_skills_section", - "fieldtype": "Section Break" - }, - { - "fieldname": "expected_skill_set", - "fieldtype": "Table", - "label": "Expected Skillset", - "options": "Expected Skill Set", - "reqd": 1 - }, - { - "fieldname": "expected_average_rating", - "fieldtype": "Rating", - "label": "Expected Average Rating" - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "interview_type", - "fieldtype": "Link", - "label": "Interview Type", - "options": "Interview Type" - }, - { - "fieldname": "interviewers", - "fieldtype": "Table MultiSelect", - "label": "Interviewers", - "options": "Interviewer" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2024-05-01 11:57:32.754037", - "modified_by": "Administrator", - "module": "HR", - "name": "Interview Round", - "naming_rule": "By fieldname", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Interviewer", - "select": 1, - "share": 1, - "write": 1 - } - ], - "sort_field": "creation", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/hrms/hr/doctype/interview_round/interview_round.py b/hrms/hr/doctype/interview_round/interview_round.py deleted file mode 100644 index 8cc64847e1..0000000000 --- a/hrms/hr/doctype/interview_round/interview_round.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import json - -import frappe -from frappe.model.document import Document - - -class InterviewRound(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 frappe.types import DF - - from hrms.hr.doctype.expected_skill_set.expected_skill_set import ExpectedSkillSet - from hrms.hr.doctype.interviewer.interviewer import Interviewer - - designation: DF.Link | None - expected_average_rating: DF.Rating - expected_skill_set: DF.Table[ExpectedSkillSet] - interview_type: DF.Link | None - interviewers: DF.TableMultiSelect[Interviewer] - round_name: DF.Data - # end: auto-generated types - - pass - - -@frappe.whitelist() -def create_interview(interview_round: str) -> Document: - doc = frappe.get_doc("Interview Round", interview_round) - - interview = frappe.new_doc("Interview") - interview.interview_round = doc.name - interview.designation = doc.designation - - if doc.interviewers: - interview.interview_details = [] - for d in doc.interviewers: - interview.append("interview_details", {"interviewer": d.user}) - - return interview diff --git a/hrms/hr/doctype/interview_round/test_interview_round.py b/hrms/hr/doctype/interview_round/test_interview_round.py deleted file mode 100644 index d887042988..0000000000 --- a/hrms/hr/doctype/interview_round/test_interview_round.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -from hrms.tests.utils import HRMSTestSuite - -# import frappe - - -class TestInterviewRound(HRMSTestSuite): - pass diff --git a/hrms/hr/doctype/interview_type/interview_type.js b/hrms/hr/doctype/interview_type/interview_type.js index 9f74c8dfee..6babac9401 100644 --- a/hrms/hr/doctype/interview_type/interview_type.js +++ b/hrms/hr/doctype/interview_type/interview_type.js @@ -2,6 +2,37 @@ // For license information, please see license.txt frappe.ui.form.on("Interview Type", { - // refresh: function(frm) { - // } + refresh: function (frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Create Interview"), function () { + frm.events.create_interview(frm); + }); + } + }, + designation: function (frm) { + if (frm.doc.designation) { + frappe.db.get_doc("Designation", frm.doc.designation).then((designation) => { + frappe.model.clear_table(frm.doc, "expected_skill_set"); + + designation.skills.forEach((designation_skill) => { + const row = frm.add_child("expected_skill_set"); + row.skill = designation_skill.skill; + }); + + refresh_field("expected_skill_set"); + }); + } + }, + create_interview: function (frm) { + frappe.call({ + method: "hrms.hr.doctype.interview_type.interview_type.create_interview", + args: { + docname: frm.doc.name, + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + }, + }); + }, }); diff --git a/hrms/hr/doctype/interview_type/interview_type.json b/hrms/hr/doctype/interview_type/interview_type.json index 1a3c8d525d..ba0e651fea 100644 --- a/hrms/hr/doctype/interview_type/interview_type.json +++ b/hrms/hr/doctype/interview_type/interview_type.json @@ -1,33 +1,78 @@ { "actions": [], "allow_rename": 1, - "autoname": "Prompt", - "creation": "2021-04-12 14:44:40.664034", + "autoname": "field:interview_type_name", + "creation": "2021-04-12 12:57:19.902866", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "interview_type_name", + "interviewers", + "column_break_3", + "expected_average_rating", + "expected_skills_section", + "designation", + "expected_skill_set", + "section_break_xlzv", "description" ], "fields": [ + { + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" + }, + { + "fieldname": "expected_skills_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "expected_skill_set", + "fieldtype": "Table", + "label": "Expected Skillset", + "options": "Expected Skill Set", + "reqd": 1 + }, + { + "fieldname": "expected_average_rating", + "fieldtype": "Rating", + "label": "Expected Average Rating" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "interviewers", + "fieldtype": "Table MultiSelect", + "label": "Interviewers", + "options": "Interviewer" + }, + { + "fieldname": "section_break_xlzv", + "fieldtype": "Section Break" + }, { "fieldname": "description", "fieldtype": "Text", - "in_list_view": 1, "label": "Description" - } - ], - "index_web_pages_for_search": 1, - "links": [ + }, { - "link_doctype": "Interview Round", - "link_fieldname": "interview_type" + "fieldname": "interview_type_name", + "fieldtype": "Data", + "label": "Interview Type Name", + "unique": 1 } ], - "modified": "2024-03-27 13:09:52.310012", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-02-16 15:52:26.011116", "modified_by": "Administrator", "module": "HR", "name": "Interview Type", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { @@ -38,7 +83,7 @@ "print": 1, "read": 1, "report": 1, - "role": "System Manager", + "role": "HR User", "share": 1, "write": 1 }, @@ -62,13 +107,15 @@ "print": 1, "read": 1, "report": 1, - "role": "HR User", + "role": "Interviewer", + "select": 1, "share": 1, "write": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/hrms/hr/doctype/interview_type/interview_type.py b/hrms/hr/doctype/interview_type/interview_type.py index 5d391bca5a..747c3bfdf6 100644 --- a/hrms/hr/doctype/interview_type/interview_type.py +++ b/hrms/hr/doctype/interview_type/interview_type.py @@ -2,7 +2,9 @@ # For license information, please see license.txt -# import frappe +import json + +import frappe from frappe.model.document import Document @@ -19,3 +21,19 @@ class InterviewType(Document): # end: auto-generated types pass + + +@frappe.whitelist() +def create_interview(docname: str): + interview_type = frappe.get_doc("Interview Type", docname) + + interview = frappe.new_doc("Interview") + interview.interview_type = interview_type.name + interview.designation = interview_type.designation + + if interview_type.interviewers: + interview.interview_details = [] + for d in interview_type.interviewers: + interview.append("interview_details", {"interviewer": d.user}) + + return interview diff --git a/hrms/hr/doctype/interview_type/test_interview_type.py b/hrms/hr/doctype/interview_type/test_interview_type.py index 17d9e7546f..a141363af9 100644 --- a/hrms/hr/doctype/interview_type/test_interview_type.py +++ b/hrms/hr/doctype/interview_type/test_interview_type.py @@ -4,6 +4,8 @@ # import frappe from hrms.tests.utils import HRMSTestSuite +# import frappe + class TestInterviewType(HRMSTestSuite): pass diff --git a/hrms/hr/doctype/job_applicant/job_applicant.js b/hrms/hr/doctype/job_applicant/job_applicant.js index 7eb7cb1aac..a19dcbc6cb 100644 --- a/hrms/hr/doctype/job_applicant/job_applicant.js +++ b/hrms/hr/doctype/job_applicant/job_applicant.js @@ -15,52 +15,86 @@ frappe.ui.form.on("Job Applicant", { }, }; }); + frm.events.show_resume(frm); frm.events.create_custom_buttons(frm); - frm.events.make_dashboard(frm); + frm.events.get_interview_for_dashboard(frm); + frm.toolbar.make_navigation(); }, + show_resume: function (frm) { + frm.toggle_display("resume_preview_html", false); + frm.toggle_display("open_resume_button", false); + if (frm.doc.resume_link) { + const src_url = frappe.utils.escape_html(frm.doc.resume_link); + if (src_url.endsWith(".pdf")) { + frm.toggle_display("resume_preview_html", true); + frm.events.show_pdf_preview(frm, src_url); + } else { + frm.toggle_display("open_resume_button", true); + } + } + }, + show_pdf_preview: function (frm, src_url) { + let $preview = $(`
+ + + +
`); + frm.get_field("resume_preview_html").$wrapper.html($preview); + }, + open_resume_button: function (frm) { + window.open(frm.doc.resume_link, "_blank", "noopener"); + }, create_custom_buttons: function (frm) { - if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") { - frm.add_custom_button( - __("Interview"), - function () { + if (!frm.doc.__islocal) { + if (frm.doc.status == "Open") { + frm.add_custom_button(__("Shortlist"), () => { + frm.set_value("status", "Shortlisted"); + frm.save(); + frm.refresh(); + }); + frm.add_custom_button(__("Reject"), () => { + frm.set_value("status", "Rejected"); + frm.save(); + frm.refresh(); + }); + } + if (frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") { + frm.add_custom_button(__("Create Interview"), function () { frm.events.create_dialog(frm); - }, - __("Create"), - ); + }); + } } if (!frm.doc.__islocal && frm.doc.status == "Accepted") { if (frm.doc.__onload && frm.doc.__onload.job_offer) { $('[data-doctype="Employee Onboarding"]').find("button").show(); $('[data-doctype="Job Offer"]').find("button").hide(); - frm.add_custom_button( - __("Job Offer"), - function () { - frappe.set_route("Form", "Job Offer", frm.doc.__onload.job_offer); - }, - __("View"), - ); + frm.add_custom_button(__("View Job Offer"), function () { + frappe.set_route("Form", "Job Offer", frm.doc.__onload.job_offer); + }); } else { $('[data-doctype="Employee Onboarding"]').find("button").hide(); $('[data-doctype="Job Offer"]').find("button").show(); - frm.add_custom_button( - __("Job Offer"), - function () { - frappe.route_options = { - job_applicant: frm.doc.name, - applicant_name: frm.doc.applicant_name, - designation: frm.doc.job_opening || frm.doc.designation, - }; - frappe.new_doc("Job Offer"); - }, - __("Create"), - ); + frm.add_custom_button(__("Create Job Offer"), function () { + frappe.route_options = { + job_applicant: frm.doc.name, + applicant_name: frm.doc.applicant_name, + designation: frm.doc.job_opening || frm.doc.designation, + }; + frappe.new_doc("Job Offer"); + }); } } }, - make_dashboard: function (frm) { + get_interview_for_dashboard: function (frm) { + $("div").remove(".form-dashboard-section.custom"); frappe.call({ method: "hrms.hr.doctype.job_applicant.job_applicant.get_interview_details", args: { @@ -68,28 +102,36 @@ frappe.ui.form.on("Job Applicant", { }, callback: function (r) { if (r.message) { - $("div").remove(".form-dashboard-section.custom"); - frm.dashboard.add_section( - frappe.render_template("job_applicant_dashboard", { - data: r.message.interviews, - number_of_stars: r.message.stars, - }), - __("Interview Summary"), - ); + frm.events.make_dashboard(frm, r.message); } }, }); }, + make_dashboard: function (frm, message) { + frm.dashboard.add_section( + frappe.render_template("job_applicant_dashboard", { + data: message.interviews, + number_of_stars: message.stars, + }), + __("Interview Summary"), + ); + }, + create_dialog: function (frm) { let d = new frappe.ui.Dialog({ - title: __("Enter Interview Round"), + title: __("Enter Interview Type"), fields: [ { - label: "Interview Round", - fieldname: "interview_round", + label: "Interview Type", + fieldname: "interview_type", fieldtype: "Link", - options: "Interview Round", + options: "Interview Type", + get_query: function () { + return { + filters: [["designation", "=", frm.doc.designation]], + }; + }, }, ], primary_action_label: __("Create Interview"), @@ -106,7 +148,7 @@ frappe.ui.form.on("Job Applicant", { method: "hrms.hr.doctype.job_applicant.job_applicant.create_interview", args: { job_applicant: frm.doc.name, - interview_round: values.interview_round, + interview_type: values.interview_type, }, callback: function (r) { var doclist = frappe.model.sync(r.message); diff --git a/hrms/hr/doctype/job_applicant/job_applicant.json b/hrms/hr/doctype/job_applicant/job_applicant.json index 1147f51565..33e34a0325 100644 --- a/hrms/hr/doctype/job_applicant/job_applicant.json +++ b/hrms/hr/doctype/job_applicant/job_applicant.json @@ -14,23 +14,26 @@ "applicant_name", "email_id", "phone_number", - "country", "column_break_3", "job_title", "designation", + "country", + "column_break_enue", "status", - "source_and_rating_section", - "source", - "source_name", - "employee_referral", - "column_break_13", "applicant_rating", "section_break_6", - "notes", + "resume_link", + "resume_preview_html", + "open_resume_button", "cover_letter", "resume_attachment", - "resume_link", - "section_break_16", + "notes", + "source_and_rating_section", + "source", + "source_name", + "column_break_13", + "employee_referral", + "salary_expectation_tab", "currency", "column_break_18", "lower_range", @@ -42,7 +45,7 @@ "fieldname": "applicant_name", "fieldtype": "Data", "in_global_search": 1, - "label": "Applicant Name", + "label": "Full Name", "reqd": 1 }, { @@ -59,7 +62,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "options": "Open\nReplied\nRejected\nHold\nAccepted", + "options": "Open\nReplied\nShortlisted\nRejected\nHold\nAccepted", "reqd": 1 }, { @@ -101,7 +104,7 @@ { "fieldname": "resume_attachment", "fieldtype": "Attach", - "label": "Resume Attachment" + "label": "Attachment" }, { "fieldname": "notes", @@ -124,7 +127,7 @@ { "fieldname": "resume_link", "fieldtype": "Data", - "label": "Resume Link" + "label": "Link" }, { "fieldname": "applicant_rating", @@ -132,11 +135,6 @@ "in_list_view": 1, "label": "Applicant Rating" }, - { - "fieldname": "section_break_16", - "fieldtype": "Section Break", - "label": "Salary Expectation" - }, { "fieldname": "lower_range", "fieldtype": "Currency", @@ -176,7 +174,7 @@ { "fieldname": "source_and_rating_section", "fieldtype": "Section Break", - "label": "Source and Rating" + "label": "Source" }, { "fieldname": "column_break_13", @@ -189,17 +187,38 @@ "fieldtype": "Link", "label": "Designation", "options": "Designation" + }, + { + "fieldname": "column_break_enue", + "fieldtype": "Column Break" + }, + { + "fieldname": "salary_expectation_tab", + "fieldtype": "Tab Break", + "label": "Salary Expectation" + }, + { + "fieldname": "resume_preview_html", + "fieldtype": "HTML", + "hidden": 1, + "label": "Preview" + }, + { + "fieldname": "open_resume_button", + "fieldtype": "Button", + "hidden": 1, + "label": "Open Resume in New Tab" } ], "icon": "fa fa-user", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-16 13:06:05.312255", + "modified": "2026-04-14 11:35:48.142348", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", - "naming_rule": "Expression (old style)", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -214,6 +233,7 @@ "write": 1 } ], + "row_format": "Dynamic", "search_fields": "applicant_name, email_id, job_title, phone_number", "sender_field": "email_id", "sort_field": "creation", @@ -221,4 +241,4 @@ "states": [], "subject_field": "notes", "title_field": "applicant_name" -} \ No newline at end of file +} diff --git a/hrms/hr/doctype/job_applicant/job_applicant.py b/hrms/hr/doctype/job_applicant/job_applicant.py index f1c820528e..01b1139ebd 100644 --- a/hrms/hr/doctype/job_applicant/job_applicant.py +++ b/hrms/hr/doctype/job_applicant/job_applicant.py @@ -42,7 +42,7 @@ class JobApplicant(Document): resume_link: DF.Data | None source: DF.Link | None source_name: DF.Link | None - status: DF.Literal["Open", "Replied", "Rejected", "Hold", "Accepted"] + status: DF.Literal["Open", "Replied", "Shortlisted", "Rejected", "Hold", "Accepted"] upper_range: DF.Currency # end: auto-generated types @@ -86,26 +86,26 @@ def set_status_for_employee_referral(self): @frappe.whitelist() -def create_interview(job_applicant: str, interview_round: str) -> Document: +def create_interview(job_applicant: str, interview_type: str) -> Document: doc = frappe.get_doc("Job Applicant", job_applicant) - round_designation = frappe.db.get_value("Interview Round", interview_round, "designation") + round_designation = frappe.db.get_value("Interview Type", interview_type, "designation") if round_designation and doc.designation and round_designation != doc.designation: frappe.throw( - _("Interview Round {0} is only applicable for the Designation {1}").format( - interview_round, round_designation + _("Interview Type {0} is only applicable for the Designation {1}").format( + interview_type, round_designation ) ) interview = frappe.new_doc("Interview") - interview.interview_round = interview_round + interview.interview_type = interview_type interview.job_applicant = doc.name interview.designation = doc.designation interview.resume_link = doc.resume_link interview.job_opening = doc.job_title - interviewers = get_interviewers(interview_round) + interviewers = get_interviewers(interview_type) for d in interviewers: interview.append("interview_details", {"interviewer": d.interviewer}) @@ -117,8 +117,11 @@ def get_interview_details(job_applicant: str) -> dict: interview_details = frappe.db.get_all( "Interview", filters={"job_applicant": job_applicant, "docstatus": ["!=", 2]}, - fields=["name", "interview_round", "scheduled_on", "average_rating", "status"], + fields=["name", "interview_type", "scheduled_on", "average_rating", "status"], ) + if not interview_details: + return None + interview_detail_map = {} meta = frappe.get_meta("Interview") number_of_stars = meta.get_options("average_rating") or 5 diff --git a/hrms/hr/doctype/job_applicant/job_applicant_dashboard.html b/hrms/hr/doctype/job_applicant/job_applicant_dashboard.html index 55ebedb1db..4787cbd6ba 100644 --- a/hrms/hr/doctype/job_applicant/job_applicant_dashboard.html +++ b/hrms/hr/doctype/job_applicant/job_applicant_dashboard.html @@ -5,7 +5,7 @@ {{ __("Interview") }} - {{ __("Interview Round") }} + {{ __("Interview Type") }} {{ __("Date") }} {{ __("Status") }} {{ __("Rating") }} @@ -17,7 +17,7 @@ {%= key %} - {%= value["interview_round"] %} + {%= value["interview_type"] %} {%= frappe.datetime.str_to_user(value["scheduled_on"]) %} {%= value["status"] %} {% let right_class = ''; %} diff --git a/hrms/hr/doctype/job_opening/job_opening.js b/hrms/hr/doctype/job_opening/job_opening.js index 2cff2b24ff..41e04c0e03 100644 --- a/hrms/hr/doctype/job_opening/job_opening.js +++ b/hrms/hr/doctype/job_opening/job_opening.js @@ -12,6 +12,9 @@ frappe.ui.form.on("Job Opening", { }); }, designation: function (frm) { + if (frm.doc.designation) { + frm.set_value("job_title", frm.doc.designation); + } if (frm.doc.designation && frm.doc.company) { frappe.call({ method: "hrms.hr.doctype.staffing_plan.staffing_plan.get_active_staffing_plan_details", @@ -45,16 +48,32 @@ frappe.ui.form.on("Job Opening", { job_opening_template: function (frm) { if (!frm.doc.job_opening_template) return; - frappe.db.get_doc("Job Opening Template", frm.doc.job_opening_template).then((doc) => { frm.set_value({ + designation: doc.designation, department: doc.department, employment_type: doc.employment_type, location: doc.location, description: doc.description, + currency: doc.currency, + upper_range: doc.upper_range, + lower_range: doc.lower_range, + salary_per: doc.salary_per, + publish_salary_range: doc.publish_salary_range, }); frm.refresh_fields(); }); }, + publish: function (frm) { + if (frm.doc.publish && !frm.doc.route) { + frm.trigger("set_route"); + } + }, + set_route: function (frm) { + if (frm.doc.publish && !frm.doc.route) { + route = `jobs/${frappe.scrub(frm.doc.company)}/${frappe.scrub(frm.doc.job_title)}`; + frm.set_value("route", route); + } + }, }); diff --git a/hrms/hr/doctype/job_opening/job_opening.json b/hrms/hr/doctype/job_opening/job_opening.json index 96b587f393..95729f8777 100644 --- a/hrms/hr/doctype/job_opening/job_opening.json +++ b/hrms/hr/doctype/job_opening/job_opening.json @@ -37,11 +37,11 @@ "job_application_route", "section_break_14", "description", - "section_break_16", + "pay_details_tab", "currency", "lower_range", "upper_range", - "column_break_20", + "column_break_pfir", "salary_per", "publish_salary_range" ], @@ -56,6 +56,8 @@ { "fieldname": "company", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Company", "options": "Company", "reqd": 1 @@ -75,6 +77,8 @@ { "fieldname": "designation", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Designation", "options": "Designation", "reqd": 1 @@ -82,6 +86,8 @@ { "fieldname": "department", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Department", "options": "Department" }, @@ -107,6 +113,8 @@ "default": "0", "fieldname": "publish", "fieldtype": "Check", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Publish on website" }, { @@ -131,11 +139,6 @@ "fieldname": "section_break_14", "fieldtype": "Section Break" }, - { - "collapsible": 1, - "fieldname": "section_break_16", - "fieldtype": "Section Break" - }, { "fieldname": "currency", "fieldtype": "Link", @@ -156,16 +159,12 @@ "options": "currency", "precision": "0" }, - { - "fieldname": "column_break_20", - "fieldtype": "Column Break" - }, { "depends_on": "publish", "description": "Route to the custom Job Application Webform", "fieldname": "job_application_route", "fieldtype": "Data", - "label": "Job Application Route" + "label": "Application Web Form Route" }, { "default": "0", @@ -210,6 +209,8 @@ { "fieldname": "employment_type", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Employment Type", "options": "Employment Type" }, @@ -258,6 +259,15 @@ "fieldtype": "Link", "label": "Job Opening Template", "options": "Job Opening Template" + }, + { + "fieldname": "pay_details_tab", + "fieldtype": "Tab Break", + "label": "Pay Details" + }, + { + "fieldname": "column_break_pfir", + "fieldtype": "Column Break" } ], "has_web_view": 1, @@ -265,11 +275,11 @@ "idx": 1, "is_published_field": "publish", "links": [], - "modified": "2025-12-11 19:18:36.145062", + "modified": "2026-04-14 15:47:13.527606", "modified_by": "Administrator", "module": "HR", "name": "Job Opening", - "naming_rule": "Expression (old style)", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { diff --git a/hrms/hr/doctype/job_opening/templates/job_opening_list.html b/hrms/hr/doctype/job_opening/templates/job_opening_list.html new file mode 100644 index 0000000000..3e892a5603 --- /dev/null +++ b/hrms/hr/doctype/job_opening/templates/job_opening_list.html @@ -0,0 +1,5 @@ +{% extends "templates/web.html" %} + +{% block page_content %} + {% include "templates/includes/list/list.html" %} +{% endblock %} \ No newline at end of file diff --git a/hrms/hr/doctype/job_opening_template/job_opening_template.js b/hrms/hr/doctype/job_opening_template/job_opening_template.js index 9006d24aa5..99e88a7c61 100644 --- a/hrms/hr/doctype/job_opening_template/job_opening_template.js +++ b/hrms/hr/doctype/job_opening_template/job_opening_template.js @@ -1,8 +1,15 @@ // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("Job Opening Template", { -// refresh(frm) { - -// }, -// }); +frappe.ui.form.on("Job Opening Template", { + refresh(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Create Job Opening"), () => { + frappe.model.open_mapped_doc({ + method: "hrms.hr.doctype.job_opening_template.job_opening_template.create_job_opening_from_template", + frm: frm, + }); + }); + } + }, +}); diff --git a/hrms/hr/doctype/job_opening_template/job_opening_template.json b/hrms/hr/doctype/job_opening_template/job_opening_template.json index b1762284b9..7b57250e1c 100644 --- a/hrms/hr/doctype/job_opening_template/job_opening_template.json +++ b/hrms/hr/doctype/job_opening_template/job_opening_template.json @@ -8,9 +8,17 @@ "field_order": [ "template_title", "department", + "designation", "column_break_wkcr", "employment_type", "location", + "pay_details_section", + "currency", + "upper_range", + "lower_range", + "column_break_vqyu", + "salary_per", + "publish_salary_range", "section_break_dwfh", "description" ], @@ -54,12 +62,56 @@ "fieldname": "description", "fieldtype": "Text Editor", "label": "Description" + }, + { + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" + }, + { + "fieldname": "pay_details_section", + "fieldtype": "Section Break", + "label": "Pay Details" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "upper_range", + "fieldtype": "Currency", + "label": "Upper Range" + }, + { + "fieldname": "lower_range", + "fieldtype": "Currency", + "label": "Lower Range" + }, + { + "fieldname": "column_break_vqyu", + "fieldtype": "Column Break" + }, + { + "default": "Month", + "fieldname": "salary_per", + "fieldtype": "Select", + "label": "Salary Paid Per", + "options": "Month\nYear" + }, + { + "default": "0", + "fieldname": "publish_salary_range", + "fieldtype": "Check", + "label": "Publish Salary Range" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-12-12 12:52:12.217926", + "modified": "2026-03-30 14:57:27.717762", "modified_by": "Administrator", "module": "HR", "name": "Job Opening Template", diff --git a/hrms/hr/doctype/job_opening_template/job_opening_template.py b/hrms/hr/doctype/job_opening_template/job_opening_template.py index 4ced9ab0ed..a5bb148b17 100644 --- a/hrms/hr/doctype/job_opening_template/job_opening_template.py +++ b/hrms/hr/doctype/job_opening_template/job_opening_template.py @@ -1,8 +1,9 @@ # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc class JobOpeningTemplate(Document): @@ -22,3 +23,25 @@ class JobOpeningTemplate(Document): # end: auto-generated types pass + + +@frappe.whitelist() +def create_job_opening_from_template(source: str | Document) -> Document: + target_doc = frappe.new_doc("Job Opening") + + def set_missing_values(source_doc, target_doc): + target_doc.job_title = source_doc.designation + + doc = get_mapped_doc( + "Job Opening Template", + source, + { + "Job Opening Template": {"doctype": "Job Opening"}, + "field_map": { + "name": "job_opening_template", + }, + }, + target_doc, + set_missing_values, + ) + return doc diff --git a/hrms/hr/workspace/recruitment/recruitment.json b/hrms/hr/workspace/recruitment/recruitment.json index 8aee5de705..023995b4b0 100644 --- a/hrms/hr/workspace/recruitment/recruitment.json +++ b/hrms/hr/workspace/recruitment/recruitment.json @@ -14,60 +14,10 @@ "for_user": "", "hide_custom": 0, "icon": "users", - "idx": 4, + "idx": 7, "is_hidden": 0, "label": "Recruitment", "links": [ - { - "hidden": 0, - "is_query_report": 0, - "label": "Interviews", - "link_count": 4, - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Interview Type", - "link_count": 0, - "link_to": "Interview Type", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Interview Round", - "link_count": 0, - "link_to": "Interview Round", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Interview Type, Interview Round", - "hidden": 0, - "is_query_report": 0, - "label": "Interview", - "link_count": 0, - "link_to": "Interview", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Interview", - "hidden": 0, - "is_query_report": 0, - "label": "Interview Feedback", - "link_count": 0, - "link_to": "Interview Feedback", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -182,9 +132,50 @@ "link_type": "DocType", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Interviews", + "link_count": 3, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Interview Type", + "link_count": 0, + "link_to": "Interview Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Interview Type", + "hidden": 0, + "is_query_report": 0, + "label": "Interview", + "link_count": 0, + "link_to": "Interview", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Interview", + "hidden": 0, + "is_query_report": 0, + "label": "Interview Feedback", + "link_count": 0, + "link_to": "Interview Feedback", + "link_type": "DocType", + "onboard": 0, + "type": "Link" } ], - "modified": "2026-01-09 18:00:59.928738", + "modified": "2026-02-19 16:36:49.582837", "modified_by": "Administrator", "module": "HR", "name": "Recruitment", diff --git a/hrms/locale/th.po b/hrms/locale/th.po index 5b66ade46e..d68a89c4ff 100644 --- a/hrms/locale/th.po +++ b/hrms/locale/th.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: contact@frappe.io\n" "POT-Creation-Date: 2026-04-12 09:46+0000\n" -"PO-Revision-Date: 2026-04-13 16:27\n" +"PO-Revision-Date: 2026-04-14 16:22\n" "Last-Translator: contact@frappe.io\n" "Language-Team: Thai\n" "MIME-Version: 1.0\n" @@ -6319,7 +6319,7 @@ msgstr "สีเขียวมะนาว" #. Label of the limits_tab (Tab Break) field in DocType 'Leave Type' #: hrms/hr/doctype/leave_type/leave_type.json msgid "Limits" -msgstr "" +msgstr "ขีดจำกัด" #. Description of the 'Appraisal Linking' (Section Break) field in DocType #. 'Goal' diff --git a/hrms/patches.txt b/hrms/patches.txt index 0c080d0361..c2880c30e7 100644 --- a/hrms/patches.txt +++ b/hrms/patches.txt @@ -1,5 +1,6 @@ [pre_model_sync] hrms.patches.v15_0.check_version_compatibility_with_frappe #2023-06-27 +hrms.patches.v16_0.merge_interview_round_with_interview_type [post_model_sync] hrms.patches.post_install.set_payroll_entry_status @@ -43,3 +44,4 @@ hrms.patches.v16_0.create_holiday_list_assignments hrms.patches.v16_0.set_base_paid_amount_in_employee_advance hrms.patches.v16_0.set_currency_and_base_fields_in_expense_claim hrms.patches.v16_0.remove_ess_user_type_limit + diff --git a/hrms/patches/v16_0/merge_interview_round_with_interview_type.py b/hrms/patches/v16_0/merge_interview_round_with_interview_type.py new file mode 100644 index 0000000000..3fb176c8b4 --- /dev/null +++ b/hrms/patches/v16_0/merge_interview_round_with_interview_type.py @@ -0,0 +1,10 @@ +import frappe +from frappe.model.rename_doc import rename_doc + + +def execute(): + if frappe.db.has_table("Interview Round"): + for interview_round, interview_type in frappe.get_all( + "Interview Round", fields=["name", "interview_type"], as_list=True + ): + rename_doc("Interview Type", interview_type, interview_round) diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py index bf74651ea8..ba67198767 100644 --- a/hrms/payroll/doctype/salary_slip/salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/salary_slip.py @@ -1627,6 +1627,11 @@ def add_additional_salary_components(self, component_type): for additional_salary in additional_salaries: component_data = get_salary_component_data(additional_salary.component) + remove_if_zero_valued = frappe.get_cached_value( + "Salary Component", additional_salary.component, "remove_if_zero_valued" + ) + if flt(additional_salary.amount) == 0 and remove_if_zero_valued: + continue self.update_component_row( component_data, additional_salary.amount, @@ -1827,7 +1832,7 @@ def update_component_row( ): component_row.set(attr, component_data.get(attr)) - if additional_salary and amount: + if additional_salary: if additional_salary.overwrite: component_row.additional_amount = flt( flt(amount) - flt(component_row.get("default_amount", 0)), diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py index 2c5341015d..01f09dacf2 100644 --- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py @@ -1932,6 +1932,7 @@ def test_salary_component_for_additional_salary_zero(self): "type": "Earning", "is_income_tax_component": 0, "amount": 350, + "remove_if_zero_valued": 0, }, ] make_salary_component(data, False, company_list=["_Test Company"]) diff --git a/hrms/workspace_sidebar/recruitment.json b/hrms/workspace_sidebar/recruitment.json index bed0cad6ce..2b64ff56d1 100644 --- a/hrms/workspace_sidebar/recruitment.json +++ b/hrms/workspace_sidebar/recruitment.json @@ -30,6 +30,18 @@ "show_arrow": 0, "type": "Link" }, + { + "child": 0, + "collapsible": 1, + "icon": "user-round-plus", + "indent": 0, + "keep_closed": 0, + "label": "Job Opening", + "link_to": "Job Opening", + "link_type": "DocType", + "show_arrow": 0, + "type": "Link" + }, { "child": 0, "collapsible": 1, @@ -43,7 +55,7 @@ "type": "Link" }, { - "child": 1, + "child": 0, "collapsible": 1, "icon": "videotape", "indent": 0, @@ -55,7 +67,7 @@ "type": "Link" }, { - "child": 1, + "child": 0, "collapsible": 1, "icon": "user-round-check", "indent": 0, @@ -67,9 +79,9 @@ "type": "Link" }, { - "child": 1, + "child": 0, "collapsible": 1, - "icon": "file", + "icon": "file-text", "indent": 0, "keep_closed": 0, "label": "Appointment Letter", @@ -81,10 +93,10 @@ { "child": 0, "collapsible": 1, - "icon": "notepad-text", + "icon": "align-start-horizontal", "indent": 1, "keep_closed": 1, - "label": "Reports", + "label": "Planning", "link_type": "DocType", "show_arrow": 0, "type": "Section Break" @@ -95,22 +107,11 @@ "icon": "", "indent": 0, "keep_closed": 0, - "label": "Recruitment Analytics", - "link_to": "Recruitment Analytics", - "link_type": "Report", - "show_arrow": 0, - "type": "Link" - }, - { - "child": 0, - "collapsible": 1, - "icon": "database", - "indent": 1, - "keep_closed": 1, - "label": "Setup", + "label": "Job Requisition", + "link_to": "Job Requisition", "link_type": "DocType", "show_arrow": 0, - "type": "Section Break" + "type": "Link" }, { "child": 1, @@ -127,27 +128,48 @@ { "child": 1, "collapsible": 1, - "icon": "", "indent": 0, "keep_closed": 0, - "label": "Job Requisition", - "link_to": "Job Requisition", + "label": "Employee Referral", + "link_to": "Employee Referral", "link_type": "DocType", "show_arrow": 0, "type": "Link" }, + { + "child": 0, + "collapsible": 1, + "icon": "notepad-text", + "indent": 1, + "keep_closed": 1, + "label": "Reports", + "link_type": "DocType", + "show_arrow": 0, + "type": "Section Break" + }, { "child": 1, "collapsible": 1, "icon": "", "indent": 0, "keep_closed": 0, - "label": "Job Opening", - "link_to": "Job Opening", - "link_type": "DocType", + "label": "Recruitment Analytics", + "link_to": "Recruitment Analytics", + "link_type": "Report", "show_arrow": 0, "type": "Link" }, + { + "child": 0, + "collapsible": 1, + "icon": "database", + "indent": 1, + "keep_closed": 1, + "label": "Setup", + "link_type": "DocType", + "show_arrow": 0, + "type": "Section Break" + }, { "child": 1, "collapsible": 1, @@ -163,11 +185,10 @@ { "child": 1, "collapsible": 1, - "icon": "", "indent": 0, "keep_closed": 0, - "label": "Interview Round", - "link_to": "Interview Round", + "label": "Job Opening Template", + "link_to": "Job Opening Template", "link_type": "DocType", "show_arrow": 0, "type": "Link" @@ -184,6 +205,29 @@ "show_arrow": 0, "type": "Link" }, + { + "child": 1, + "collapsible": 1, + "indent": 0, + "keep_closed": 0, + "label": "Job Offer Term Template", + "link_to": "Job Offer Term Template", + "link_type": "DocType", + "show_arrow": 0, + "type": "Link" + }, + { + "child": 0, + "collapsible": 1, + "icon": "web", + "indent": 0, + "keep_closed": 0, + "label": "Job Portal", + "link_type": "URL", + "show_arrow": 0, + "type": "Link", + "url": "/jobs" + }, { "child": 0, "collapsible": 1, @@ -193,12 +237,11 @@ "label": "Settings", "link_to": "HR Settings", "link_type": "DocType", - "navigate_to_tab": "recruitment_tab", "show_arrow": 0, "type": "Link" } ], - "modified": "2026-01-08 14:16:38.419516", + "modified": "2026-04-14 12:19:55.146861", "modified_by": "Administrator", "module": "HR", "name": "Recruitment",