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",