Staffing Plan - validations, get employee count on designation change, calc estimated cost
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
index 3cadfc5..17f03e6 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js
@@ -2,7 +2,91 @@
 // 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(child.designation){
+			frappe.call({
+				"method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_current_employee_count",
+				args: {
+					designation: child.designation
+				},
+				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..588e536 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -5,6 +5,51 @@
 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):
+	if not designation:
+		return False
+	employee_count = frappe.db.sql("""select count(*) from `tabEmployee` where \
+							designation = '{0}' and status='Active'""".format(designation))[0][0]
+	return employee_count
+
+@frappe.whitelist()
+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 = "spd.designation='{0}' and sp.docstatus=1 and \
+	sp.company='{1}'".format(designation, company)
+
+	if(department): #Department is an optional field
+		conditions += " and sp.department='{0}'".format(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 spd.parent, spd.vacancies \
+		from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
+		where {0}""".format(conditions))
+
+	# Only a signle staffing plan can be active for a designation on given date
+	return staffing_plan[0] if staffing_plan else False
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",