Employee Promotion, Transfer - modal to add details to child table
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.js b/erpnext/hr/doctype/employee_promotion/employee_promotion.js
index c1bb788..54e06f4 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.js
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+{% include 'erpnext/hr/employee_property_update.js' %}
+
 frappe.ui.form.on('Employee Promotion', {
 	refresh: function(frm) {
 
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.js b/erpnext/hr/doctype/employee_transfer/employee_transfer.js
index 1d694bd..af751a7 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.js
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+{% include 'erpnext/hr/employee_property_update.js' %}
+
 frappe.ui.form.on('Employee Transfer', {
 	refresh: function(frm) {
 
diff --git a/erpnext/hr/employee_property_update.js b/erpnext/hr/employee_property_update.js
new file mode 100644
index 0000000..d49c1ab
--- /dev/null
+++ b/erpnext/hr/employee_property_update.js
@@ -0,0 +1,147 @@
+frappe.ui.form.on(cur_frm.doctype, {
+  setup: function(frm) {
+    frm.set_query("employee", function() {
+      return {
+				filters: {
+					"status": "Active"
+				}
+			};
+    })
+  },
+  onload: function(frm){
+		if(frm.doc.__islocal){
+      if(frm.doctype == "Employee Promotion"){
+        frm.doc.promotion_details = [];
+      }else if (frm.doctype == "Employee Transfer") {
+        frm.doc.transfer_details = [];
+      }
+		}
+	},
+  employee: function(frm) {
+		frm.add_fetch("employee", "company", "company");
+	},  
+	refresh: function(frm) {
+    var table;
+    if(frm.doctype == "Employee Promotion"){
+      table = "promotion_details";
+    }else if (frm.doctype == "Employee Transfer") {
+      table = "transfer_details"
+    }
+    if(!table){return}
+		cur_frm.fields_dict[table].grid.wrapper.find('.grid-add-row').hide();
+		cur_frm.fields_dict[table].grid.add_custom_button(__('Add Row'), () => {
+			if(!frm.doc.employee){
+				frappe.msgprint(__("Please select Employee"));
+				return;
+			}
+			frappe.call({
+				method: 'erpnext.hr.utils.get_employee_fields_label',
+				callback: function(r) {
+					if(r.message){
+						show_dialog(frm, table, r.message);
+					}
+				}
+			});
+		});
+	}
+});
+
+var show_dialog = function(frm, table, field_labels) {
+	var d = new frappe.ui.Dialog({
+		title: "Update Property",
+		fields: [
+			{fieldname: "property", label: __('Select Property'), fieldtype:"Select", options: field_labels},
+			{fieldname: "current", fieldtype: "Data", label:__('Current'), read_only: true},
+			{fieldname: "field_html", fieldtype: "HTML"}
+		],
+		primary_action_label: __('Add to Details'),
+		primary_action: () => {
+			d.get_primary_btn().attr('disabled', true);
+			if(d.data){
+				add_to_details(frm, d, table);
+			}
+		}
+	});
+	d.fields_dict["property"].df.onchange = () => {
+		let property = d.get_values().property;
+		d.data.fieldname = property;
+		if(!property){return;}
+		frappe.call({
+			method: 'erpnext.hr.utils.get_employee_field_property',
+			args: {employee: frm.doc.employee, fieldname: property},
+			callback: function(r) {
+				if(r.message){
+					d.data.current = r.message.value;
+					d.data.property = r.message.label;
+					d.fields_dict.field_html.$wrapper.html("");
+					d.set_value('current', r.message.value);
+					render_dynamic_field(d, r.message.datatype, r.message.options, property);
+					d.get_primary_btn().attr('disabled', false);
+				}
+			}
+		});
+	};
+	d.get_primary_btn().attr('disabled', true);
+	d.data = {};
+	d.show();
+}
+
+var render_dynamic_field = function(d, fieldtype, options, fieldname) {
+	d.data.new = null;
+	var dynamic_field = frappe.ui.form.make_control({
+		df: {
+			"fieldtype": fieldtype,
+			"fieldname": fieldname,
+			"options": options || ''
+		},
+		parent: d.fields_dict.field_html.wrapper,
+		only_input: false
+	});
+	dynamic_field.make_input();
+	$(dynamic_field.label_area).text(__("New"));
+	dynamic_field.$input.on("change", function(e) {
+		d.data.new = e.target.value;
+	}).on("awesomplete-close", function(e) {
+		d.data.new = e.target.value;
+	});
+}
+
+var add_to_details = function(frm, d, table) {
+	let data = d.data;
+	if(data.fieldname){
+		if(validate_duplicate(frm, table, data.fieldname)){
+			frappe.show_alert({message:__("Property already added"), indicator:'orange'});
+			return false;
+		}
+		if(data.current == data.new){
+			frappe.show_alert({message:__("Nothing to change"), indicator:'orange'});
+			d.get_primary_btn().attr('disabled', false);
+			return false;
+		}
+		frm.add_child(table, {
+			fieldname: data.fieldname,
+			property: data.property,
+			current: data.current,
+			new: data.new
+		});
+		frm.refresh_field(table);
+		d.fields_dict.field_html.$wrapper.html("");
+		d.set_value("property", "");
+		d.set_value('current', "");
+		frappe.show_alert({message:__("Added to details"),indicator:'green'});
+		d.data = {};
+	}else {
+		frappe.show_alert({message:__("Value missing"),indicator:'red'});
+	}
+}
+
+var validate_duplicate =  function(frm, table, fieldname){
+	let duplicate = false;
+	$.each(frm.doc[table], function(i, detail) {
+		if(detail.fieldname === fieldname){
+			duplicate = true;
+			return;
+		}
+	});
+	return duplicate;
+}
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index aa456aa..057f406 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -4,7 +4,48 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
+from frappe.utils import formatdate, format_datetime
+from frappe.utils import getdate, get_datetime
 
 def set_employee_name(doc):
 	if doc.employee and not doc.employee_name:
 		doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
+
+@frappe.whitelist()
+def get_employee_fields_label():
+	fields = []
+	for df in frappe.get_meta("Employee").get("fields"):
+		if df.fieldtype in ["Data", "Date", "Datetime", "Float", "Int",
+		"Link", "Percent", "Select", "Small Text"] and df.fieldname not in ["lft", "rgt", "old_parent"]:
+			fields.append({"value": df.fieldname, "label": df.label})
+	return fields
+
+@frappe.whitelist()
+def get_employee_field_property(employee, fieldname):
+	if employee and fieldname:
+		field = frappe.get_meta("Employee").get_field(fieldname)
+		value = frappe.db.get_value("Employee", employee, fieldname)
+		options = field.options
+		if field.fieldtype == "Date":
+			value = formatdate(value)
+		elif field.fieldtype == "Datetime":
+			value = format_datetime(value)
+		return {
+			"value" : value,
+			"datatype" : field.fieldtype,
+			"label" : field.label,
+			"options" : options
+		}
+	else:
+		return False
+
+def update_employee(employee, details, cancel=False):
+	for item in details:
+		fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype
+		new_data = item.new if not cancel else item.current
+		if fieldtype == "Date" and new_data:
+			new_data = getdate(new_data)
+		elif fieldtype =="Datetime" and new_data:
+			new_data = get_datetime(new_data)
+		setattr(employee, item.fieldname, new_data)
+	return employee