Staffing Plan (#14346)

* validate staffing plan

* staffing plan docs

* get_active_staffing_plan_and_vacancies now returns dict, mandatory fields set

* validate with parent and child company plans, plan now considers open job openings

* Failure details in validate error messages

* Fixed sql injection
This commit is contained in:
Ranjith Kurungadam 2018-06-05 11:40:58 +05:30 committed by Nabin Hait
parent 4fa600a8dd
commit d580c92377
9 changed files with 457 additions and 284 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -0,0 +1,18 @@
# Staffing Plan
Staffing Plan helps you to plan human resource recruitments for your Company. ERPNext allows you to do this at a group company level helping you efficiently plan and budget new hirings for a period. Job Openings can only be created as per the number of vacancies and budget as per the active Staffing Plan.
> Human Resources > Setup > Staffing Plan > New Staffing Plan
<img class="screenshot" alt="Staffing Plan"
src="{{docs_base_url}}/assets/img/human-resources/staffing-plan.png">
- **Designation:** The designations for which you are creating the Staffing Plan.
- **Number of Positions:** The number of positions you plan to recruit for between the Staffing Plan from and to dates.
- **Current Count:** This is the number of Employees already hired for the Designation.
- **Vacancies:** The number of vacancies based on the Number of Positions you wish to recruit and the current Employee count.
- **Estimated Cost Per Position:** You can specify the cost to company per position so that hiring officials can stick to the budget.
<img class="screenshot" alt="Staffing Plan Detail"
src="{{docs_base_url}}/assets/img/human-resources/staffing-plan-detail.png">
**Total Estimated Budget** Once you enter the recruitment plan for all the designations, Staffing Plan will draw up the total estimated budget.

View File

@ -14,7 +14,7 @@ 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",
"method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_active_staffing_plan_details",
args: {
company: frm.doc.company,
designation: frm.doc.designation,
@ -23,8 +23,8 @@ frappe.ui.form.on('Job Opening', {
},
callback: function (data) {
if(data.message){
frm.set_value('staffing_plan', data.message[0]);
frm.set_value('planned_vacancies', data.message[1]);
frm.set_value('staffing_plan', data.message[0].name);
frm.set_value('planned_vacancies', data.message[0].vacancies);
} else {
frm.set_value('staffing_plan', "");
frm.set_value('planned_vacancies', 0);

View File

@ -8,7 +8,7 @@ import frappe
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
from erpnext.hr.doctype.staffing_plan.staffing_plan import get_designation_counts, get_active_staffing_plan_details
class JobOpening(WebsiteGenerator):
website = frappe._dict(
@ -24,11 +24,11 @@ class JobOpening(WebsiteGenerator):
def validate_current_vacancies(self):
if not self.staffing_plan:
vacancies = get_active_staffing_plan_and_vacancies(self.company,
staffing_plan = get_active_staffing_plan_details(self.company,
self.designation, self.department)
if vacancies:
self.staffing_plan = vacancies[0]
self.planned_vacancies = vacancies[1]
if staffing_plan:
self.staffing_plan = staffing_plan[0].name
self.planned_vacancies = staffing_plan[0].vacancies
elif not self.planned_vacancies:
planned_vacancies = frappe.db.sql("""
select vacancies from `tabStaffing Plan Detail`
@ -39,14 +39,13 @@ class JobOpening(WebsiteGenerator):
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]
designation_counts = get_designation_counts(self.designation, self.company)
current_count = designation_counts['employee_count'] + designation_counts['job_openings']
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)))
frappe.throw(_("Job Openings for designation {0} already open \
or hiring completed as per Staffing Plan {1}"
.format(self.designation, self.staffing_plan)))
def get_context(self, context):
context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]

View File

@ -33,17 +33,22 @@ frappe.ui.form.on('Staffing Plan Detail', {
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",
"method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_designation_counts",
args: {
designation: child.designation,
company: frm.doc.company
},
callback: function (data) {
if(data.message){
frappe.model.set_value(cdt, cdn, 'current_count', data.message);
frappe.model.set_value(cdt, cdn, 'current_count', data.message.employee_count);
frappe.model.set_value(cdt, cdn, 'current_openings', data.message.job_openings);
if (child.number_of_positions < (data.message.employee_count + data.message.job_openings)){
frappe.model.set_value(cdt, cdn, 'number_of_positions', data.message.employee_count + data.message.job_openings);
}
}
else{ // No employees for this designation
frappe.model.set_value(cdt, cdn, 'current_count', 0);
frappe.model.set_value(cdt, cdn, 'current_openings', 0);
}
}
});
@ -67,19 +72,25 @@ frappe.ui.form.on('Staffing Plan Detail', {
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);
if (child.number_of_positions < (child.current_count + child.current_openings)){
frappe.throw(__("Number of positions cannot be less then current count of employees"))
}
if(child.number_of_positions > 0) {
frappe.model.set_value(cdt, cdn, 'vacancies', child.number_of_positions - (child.current_count + child.current_openings));
}
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
// Validate for > 0 ?
var set_total_estimated_cost = function(frm, cdt, cdn) {
let child = locals[cdt][cdn]
if(child.number_of_positions && child.estimated_cost_per_position) {
if(child.vacancies > 0 && child.estimated_cost_per_position) {
frappe.model.set_value(cdt, cdn, 'total_estimated_cost', child.vacancies * child.estimated_cost_per_position);
}
else {

View File

@ -15,6 +15,7 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -47,6 +48,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -79,6 +81,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -109,6 +112,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -132,7 +136,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@ -140,6 +144,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -163,7 +168,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@ -171,6 +176,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -202,6 +208,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -234,6 +241,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -264,6 +272,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -296,6 +305,7 @@
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -336,7 +346,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-18 19:10:34.394249",
"modified": "2018-05-28 18:30:27.041395",
"modified_by": "Administrator",
"module": "HR",
"name": "Staffing Plan",

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import getdate, nowdate
from frappe.utils import getdate, nowdate, cint, flt
class StaffingPlan(Document):
def validate(self):
@ -14,32 +14,128 @@ class StaffingPlan(Document):
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)))
self.total_estimated_budget = 0
if overlap and overlap [0][0]:
frappe.throw(_("Staffing Plan {0} already exist for designation {1}"
.format(overlap[0][0], detail.designation)))
for detail in self.get("staffing_details"):
self.validate_overlap(detail)
self.validate_with_subsidiary_plans(detail)
self.validate_with_parent_plan(detail)
#Set readonly fields
designation_counts = get_designation_counts(detail.designation, self.company)
detail.current_count = designation_counts['employee_count']
detail.current_openings = designation_counts['job_openings']
if detail.number_of_positions < (detail.current_count + detail.current_openings):
frappe.throw(_("Number of positions cannot be less then current count of employees"))
elif detail.number_of_positions > 0:
detail.vacancies = detail.number_of_positions - (detail.current_count + detail.current_openings)
if detail.vacancies > 0 and detail.estimated_cost_per_position:
detail.total_estimated_cost = detail.vacancies * detail.estimated_cost_per_position
else: detail.total_estimated_cost = 0
else: detail.vacancies = detail.number_of_positions = detail.total_estimated_cost = 0
self.total_estimated_budget += detail.total_estimated_cost
def validate_overlap(self, staffing_plan_detail):
# Validate if any submitted Staffing Plan exist for any Designations in this plan
# and spd.vacancies>0 ?
overlap = frappe.db.sql("""select spd.parent
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <= %s and sp.company = %s
""", (staffing_plan_detail.designation, self.from_date, self.to_date, self.company))
if overlap and overlap [0][0]:
frappe.throw(_("Staffing Plan {0} already exist for designation {1}"
.format(overlap[0][0], staffing_plan_detail.designation)))
def validate_with_parent_plan(self, staffing_plan_detail):
if not frappe.db.get_value("Company", self.company, "parent_company"):
return # No parent, nothing to validate
# Get staffing plan applicable for the company (Parent Company)
parent_plan_details = get_active_staffing_plan_details(self.company, staffing_plan_detail.designation)
if not parent_plan_details:
return #no staffing plan for any parent Company in herarchy
# Fetch parent company which owns the staffing plan. NOTE: Parent could be higher up in the heirarchy
parent_company = frappe.db.get_value("Staffing Plan", parent_plan_details[0].name, "company")
# Parent plan available, validate with parent, siblings as well as children of staffing plan Company
if staffing_plan_detail.vacancies > cint(parent_plan_details[0].vacancies) or \
staffing_plan_detail.total_estimated_cost > flt(parent_plan_details[0].total_estimated_cost):
frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \
for {2} as per staffing plan {3} for parent company {4}"
.format(cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_plan_details[0].name,
parent_company)))
#Get vacanices already planned for all companies down the herarchy of Parent Company
lft, rgt = frappe.db.get_value("Company", parent_company, ["lft", "rgt"])
all_sibling_details = frappe.db.sql("""select sum(spd.vacancies) as vacancies,
sum(spd.total_estimated_cost) as total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <=%s
and sp.company in (select name from tabCompany where lft > %s and rgt < %s)
""", (staffing_plan_detail.designation, self.from_date, self.to_date, lft, rgt), as_dict = 1)[0]
if (cint(parent_plan_details[0].vacancies) < \
(staffing_plan_detail.vacancies + cint(all_sibling_details.vacancies))) or \
(flt(parent_plan_details[0].total_estimated_cost) < \
(staffing_plan_detail.total_estimated_cost + flt(all_sibling_details.total_estimated_cost))):
frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}"
.format(cint(all_sibling_details.vacancies),
all_sibling_details.total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_company,
cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
parent_plan_details[0].name)))
def validate_with_subsidiary_plans(self, staffing_plan_detail):
#Valdate this plan with all child company plan
children_details = frappe.db.sql("""select sum(spd.vacancies) as vacancies,
sum(spd.total_estimated_cost) as total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <=%s
and sp.company in (select name from tabCompany where parent_company = %s)
""", (staffing_plan_detail.designation, self.from_date, self.to_date, self.company), as_dict = 1)[0]
if children_details and \
staffing_plan_detail.vacancies < cint(children_details.vacancies) or \
staffing_plan_detail.total_estimated_cost < flt(children_details.total_estimated_cost):
frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
.format(self.company,
cint(children_details.vacancies),
children_details.total_estimated_cost,
frappe.bold(staffing_plan_detail.designation))))
@frappe.whitelist()
def get_current_employee_count(designation, company):
def get_designation_counts(designation, company):
if not designation:
return False
employee_counts_dict = {}
lft, rgt = frappe.db.get_value("Company", company, ["lft", "rgt"])
employee_count = frappe.db.sql("""select count(*) from `tabEmployee`
employee_counts_dict["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())):
employee_counts_dict['job_openings'] = 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)
""", (designation, lft, rgt))[0][0]
return employee_counts_dict
@frappe.whitelist()
def get_active_staffing_plan_details(company, designation, department=None, date=getdate(nowdate())):
if not company or not designation:
frappe.throw(_("Please select Company and Designation"))
@ -51,16 +147,16 @@ def get_active_staffing_plan_and_vacancies(company, designation, department=None
conditions += " and '{0}' between sp.from_date and sp.to_date".format(date)
staffing_plan = frappe.db.sql("""
select sp.name, spd.vacancies
select sp.name, spd.vacancies, spd.total_estimated_cost
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))
""".format(conditions), (company, designation), as_dict = 1)
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,
staffing_plan = get_active_staffing_plan_details(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
# Only a single staffing plan can be active for a designation on given date
return staffing_plan if staffing_plan else None

View File

@ -1,258 +1,297 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-04-13 18:04:20.978931",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-04-13 18:04:20.978931",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "designation",
"fieldtype": "Link",
"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": "Designation",
"length": 0,
"no_copy": 0,
"options": "Designation",
"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,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "designation",
"fieldtype": "Link",
"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": "Designation",
"length": 0,
"no_copy": 0,
"options": "Designation",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"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": "number_of_positions",
"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": "Number Of Positions",
"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,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "number_of_positions",
"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": "Number Of Positions",
"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,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Estimated Cost Per Position",
"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,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "estimated_cost_per_position",
"fieldtype": "Currency",
"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": "Estimated Cost Per Position",
"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": "total_estimated_cost",
"fieldtype": "Currency",
"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": "Total Estimated Cost",
"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,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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,
"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,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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": 0,
"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": "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,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_openings",
"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": "Current Openings",
"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,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-04-15 16:09:12.622186",
"modified_by": "Administrator",
"module": "HR",
"name": "Staffing Plan Detail",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_estimated_cost",
"fieldtype": "Currency",
"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": "Total Estimated Cost",
"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,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-06-01 17:03:38.020993",
"modified_by": "Administrator",
"module": "HR",
"name": "Staffing Plan Detail",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}
}