Merge branch 'ESS-LLP-staffing' into enterprise_sprint
diff --git a/erpnext/hr/doctype/job_opening/job_opening.js b/erpnext/hr/doctype/job_opening/job_opening.js
index e69de29..960f5b3 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.js
+++ b/erpnext/hr/doctype/job_opening/job_opening.js
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Job Opening', {
+ designation: function(frm) {
+ if(frm.doc.designation && frm.doc.company){
+ frappe.call({
+ "method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_active_staffing_plan_and_vacancies",
+ args: {
+ company: frm.doc.company,
+ designation: frm.doc.designation,
+ department: frm.doc.department,
+ date: frappe.datetime.now_date() // ToDo - Date in Job Opening?
+ },
+ callback: function (data) {
+ if(data.message){
+ frm.set_value('staffing_plan', data.message[0]);
+ frm.set_value('planned_vacancies', data.message[1]);
+ } else {
+ frm.set_value('staffing_plan', "");
+ frm.set_value('planned_vacancies', 0);
+ frappe.show_alert({
+ indicator: 'orange',
+ message: __('No Staffing Plans found for this Designation')
+ });
+ }
+ }
+ });
+ }
+ else{
+ frm.set_value('staffing_plan', "");
+ frm.set_value('planned_vacancies', 0);
+ }
+ },
+ company: function(frm) {
+ frm.set_value('designation', "");
+ }
+});
diff --git a/erpnext/hr/doctype/job_opening/job_opening.json b/erpnext/hr/doctype/job_opening/job_opening.json
index de15114..7906439 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.json
+++ b/erpnext/hr/doctype/job_opening/job_opening.json
@@ -41,7 +41,6 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -73,7 +72,6 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -104,7 +102,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -134,7 +131,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -166,7 +162,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -198,7 +193,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -224,13 +218,43 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "staffing_plan",
+ "fieldname": "planned_vacancies",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Planned number of Positions",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
"unique": 0
},
{
@@ -260,7 +284,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -291,7 +314,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
},
{
@@ -323,7 +345,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 1
},
{
@@ -354,7 +375,6 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
"unique": 0
}
],
@@ -369,7 +389,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-04-13 18:52:56.109392",
+ "modified": "2018-05-07 14:16:50.300247",
"modified_by": "Administrator",
"module": "HR",
"name": "Job Opening",
@@ -377,6 +397,7 @@
"permissions": [
{
"amend": 0,
+ "apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -396,6 +417,7 @@
},
{
"amend": 0,
+ "apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py
index 60c911a..b579d6f 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.py
+++ b/erpnext/hr/doctype/job_opening/job_opening.py
@@ -8,6 +8,7 @@
from frappe.website.website_generator import WebsiteGenerator
from frappe import _
+from erpnext.hr.doctype.staffing_plan.staffing_plan import get_current_employee_count, get_active_staffing_plan_and_vacancies
class JobOpening(WebsiteGenerator):
website = frappe._dict(
@@ -19,6 +20,33 @@
def validate(self):
if not self.route:
self.route = frappe.scrub(self.job_title).replace('_', '-')
+ self.validate_current_vacancies()
+
+ def validate_current_vacancies(self):
+ if not self.staffing_plan:
+ vacancies = get_active_staffing_plan_and_vacancies(self.company,
+ self.designation, self.department)
+ if vacancies:
+ self.staffing_plan = vacancies[0]
+ self.planned_vacancies = vacancies[1]
+ elif not self.planned_vacancies:
+ planned_vacancies = frappe.db.sql("""
+ select vacancies from `tabStaffing Plan Detail`
+ where parent=%s and designation=%s""", (self.staffing_plan, self.designation))
+ self.planned_vacancies = planned_vacancies[0][0] if planned_vacancies else None
+
+ if self.staffing_plan and self.planned_vacancies:
+ staffing_plan_company = frappe.db.get_value("Staffing Plan", self.staffing_plan, "company")
+ lft, rgt = frappe.db.get_value("Company", staffing_plan_company, ["lft", "rgt"])
+
+ current_count = get_current_employee_count(self.designation, staffing_plan_company)
+ current_count+= frappe.db.sql("""select count(*) from `tabJob Opening` \
+ where designation=%s and status='Open'
+ and company in (select name from tabCompany where lft>=%s and rgt<=%s)
+ """, (self.designation, lft, rgt))[0][0]
+
+ if self.planned_vacancies <= current_count:
+ frappe.throw(_("Job Openings for designation {0} and company {1} already opened or hiring completed as per Staffing Plan {2}".format(self.designation, staffing_plan_company, self.staffing_plan)))
def get_context(self, context):
context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
index 3cadfc5..1c1a720 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
@@ -2,7 +2,92 @@
// For license information, please see license.txt
frappe.ui.form.on('Staffing Plan', {
- refresh: function(frm) {
-
+ setup: function(frm) {
+ frm.set_query("designation", "staffing_details", function() {
+ let designations = [];
+ $.each(frm.doc.staffing_details, function(index, staff_detail) {
+ if(staff_detail.designation){
+ designations.push(staff_detail.designation)
+ }
+ })
+ // Filter out designations already selected in Staffing Plan Detail
+ return {
+ filters: [
+ ['Designation', 'name', 'not in', designations],
+ ]
+ }
+ });
}
});
+
+frappe.ui.form.on('Staffing Plan Detail', {
+ designation: function(frm, cdt, cdn) {
+ let child = locals[cdt][cdn]
+ if(frm.doc.company && child.designation){
+ frappe.call({
+ "method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_current_employee_count",
+ args: {
+ designation: child.designation,
+ company: frm.doc.company
+ },
+ callback: function (data) {
+ if(data.message){
+ frappe.model.set_value(cdt, cdn, 'current_count', data.message);
+ }
+ else{ // No employees for this designation
+ frappe.model.set_value(cdt, cdn, 'current_count', 0);
+ }
+ }
+ });
+ }
+ },
+
+ number_of_positions: function(frm, cdt, cdn) {
+ set_vacancies(frm, cdt, cdn);
+ },
+
+ current_count: function(frm, cdt, cdn) {
+ set_vacancies(frm, cdt, cdn);
+ },
+
+ estimated_cost_per_position: function(frm, cdt, cdn) {
+ let child = locals[cdt][cdn];
+ set_total_estimated_cost(frm, cdt, cdn);
+ }
+
+});
+
+var set_vacancies = function(frm, cdt, cdn) {
+ let child = locals[cdt][cdn]
+ if(child.number_of_positions) {
+ frappe.model.set_value(cdt, cdn, 'vacancies', child.number_of_positions - child.current_count);
+ }
+ else{
+ frappe.model.set_value(cdt, cdn, 'vacancies', 0);
+ }
+ set_total_estimated_cost(frm, cdt, cdn);
+}
+
+// Note: Estimated Cost is calculated on number of Vacancies
+var set_total_estimated_cost = function(frm, cdt, cdn) {
+ let child = locals[cdt][cdn]
+ if(child.number_of_positions && child.estimated_cost_per_position) {
+ frappe.model.set_value(cdt, cdn, 'total_estimated_cost', child.vacancies * child.estimated_cost_per_position);
+ }
+ else {
+ frappe.model.set_value(cdt, cdn, 'total_estimated_cost', 0);
+ }
+ set_total_estimated_budget(frm);
+};
+
+var set_total_estimated_budget = function(frm) {
+ let estimated_budget = 0.0
+ if(frm.doc.staffing_details) {
+ $.each(frm.doc.staffing_details, function(index, staff_detail) {
+ if(staff_detail.total_estimated_cost){
+ estimated_budget += staff_detail.total_estimated_cost
+ }
+ })
+ frm.set_value('total_estimated_budget', estimated_budget);
+ }
+}
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.json b/erpnext/hr/doctype/staffing_plan/staffing_plan.json
index a5d26e6..229cc05 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.json
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.json
@@ -268,6 +268,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "0.00",
"fieldname": "total_estimated_budget",
"fieldtype": "Currency",
"hidden": 0,
@@ -284,7 +285,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -335,7 +336,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-04-13 18:45:16.729979",
+ "modified": "2018-04-18 19:10:34.394249",
"modified_by": "Administrator",
"module": "HR",
"name": "Staffing Plan",
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 510d2dc..37ff5cb 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -5,6 +5,62 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from frappe import _
+from frappe.utils import getdate, nowdate
class StaffingPlan(Document):
- pass
+ def validate(self):
+ # Validate Dates
+ if self.from_date and self.to_date and self.from_date > self.to_date:
+ frappe.throw(_("From Date cannot be greater than To Date"))
+
+ # Validate if any submitted Staffing Plan exist for Designations in this plan
+ # and spd.vacancies>0 ?
+ for detail in self.get("staffing_details"):
+ overlap = (frappe.db.sql("""select spd.parent \
+ from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name \
+ where spd.designation='{0}' and sp.docstatus=1 \
+ and sp.to_date >= '{1}' and sp.from_date <='{2}'"""
+ .format(detail.designation, self.from_date, self.to_date)))
+
+ if overlap and overlap [0][0]:
+ frappe.throw(_("Staffing Plan {0} already exist for designation {1}"
+ .format(overlap[0][0], detail.designation)))
+
+@frappe.whitelist()
+def get_current_employee_count(designation, company):
+ if not designation:
+ return False
+
+ lft, rgt = frappe.db.get_value("Company", company, ["lft", "rgt"])
+ employee_count = frappe.db.sql("""select count(*) from `tabEmployee`
+ where designation = %s and status='Active'
+ and company in (select name from tabCompany where lft>=%s and rgt<=%s)
+ """, (designation, lft, rgt))[0][0]
+ return employee_count
+
+def get_active_staffing_plan_and_vacancies(company, designation, department=None, date=getdate(nowdate())):
+ if not company or not designation:
+ frappe.throw(_("Please select Company and Designation"))
+
+ conditions = ""
+ if(department): #Department is an optional field
+ conditions += " and sp.department='{0}'".format(frappe.db.escape(department))
+
+ if(date): #ToDo: Date should be mandatory?
+ conditions += " and '{0}' between sp.from_date and sp.to_date".format(date)
+
+ staffing_plan = frappe.db.sql("""
+ select sp.name, spd.vacancies
+ from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
+ where company=%s and spd.designation=%s and sp.docstatus=1 {0}
+ """.format(conditions), (company, designation))
+
+ if not staffing_plan:
+ parent_company = frappe.db.get_value("Company", company, "parent_company")
+ if parent_company:
+ staffing_plan = get_active_staffing_plan_and_vacancies(parent_company,
+ designation, department, date)
+
+ # Only a signle staffing plan can be active for a designation on given date
+ return staffing_plan[0] if staffing_plan else None
diff --git a/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json b/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json
index 7c39564..eb77b43 100644
--- a/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json
+++ b/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json
@@ -81,68 +81,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "current_count",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Current Count",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "vacancies",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Vacancies",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "estimated_cost_per_position",
"fieldtype": "Currency",
"hidden": 0,
@@ -190,6 +128,36 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
@@ -198,6 +166,68 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "current_count",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Current Count",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "vacancies",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Vacancies",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
}
],
"has_web_view": 0,
@@ -210,7 +240,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-04-13 18:39:52.783341",
+ "modified": "2018-04-15 16:09:12.622186",
"modified_by": "Administrator",
"module": "HR",
"name": "Staffing Plan Detail",