refactor: disable quick entry for batched and serialized items
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index ed325a1..c84acc5 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -19,13 +19,24 @@
 		this.content.on('click', '.btn-move', function() {
 			let item = unescape($(this).attr('data-item'));
 			let warehouse = unescape($(this).attr('data-warehouse'));
-			open_stock_entry(item, warehouse, "Material Transfer");
+			let actual_qty = unescape($(this).attr('data-actual_qty'));
+			let disable_quick_entry = Number(unescape($(this).attr('data-disable_quick_entry')));
+
+			if (disable_quick_entry) open_stock_entry(item, warehouse, "Material Transfer");
+
+			else erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); })
 		});
 
 		this.content.on('click', '.btn-add', function() {
 			let item = unescape($(this).attr('data-item'));
 			let warehouse = unescape($(this).attr('data-warehouse'));
-			open_stock_entry(item, warehouse);
+			let actual_qty = unescape($(this).attr('data-actual_qty'));
+			let disable_quick_entry = Number(unescape($(this).attr('data-disable_quick_entry')));
+			let rate = unescape($(this).attr('data-rate'));
+
+			if (disable_quick_entry) open_stock_entry(item, warehouse);
+
+			else erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); })
 		});
 
 		function open_stock_entry(item, warehouse, entry_type) {
@@ -124,4 +135,89 @@
 			show_item: show_item || false
 		}
 	}
-})
\ No newline at end of file
+})
+
+erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
+	var dialog = new frappe.ui.Dialog({
+		title: target ? __('Add Item') : __('Move Item'),
+		fields: [
+			{fieldname: 'item_code', label: __('Item'),
+				fieldtype: 'Link', options: 'Item', read_only: 1},
+			{fieldname: 'source', label: __('Source Warehouse'),
+				fieldtype: 'Link', options: 'Warehouse', read_only: 1},
+			{fieldname: 'target', label: __('Target Warehouse'),
+				fieldtype: 'Link', options: 'Warehouse', reqd: 1},
+			{fieldname: 'qty', label: __('Quantity'), reqd: 1,
+				fieldtype: 'Float', description: __('Available {0}', [actual_qty]) },
+			{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 },
+		],
+	})
+	dialog.show();
+	dialog.get_field('item_code').set_input(item);
+
+	if(source) {
+		dialog.get_field('source').set_input(source);
+	} else {
+		dialog.get_field('source').df.hidden = 1;
+		dialog.get_field('source').refresh();
+	}
+
+	if(rate) {
+		dialog.get_field('rate').set_value(rate);
+		dialog.get_field('rate').df.hidden = 0;
+		dialog.get_field('rate').refresh();
+	}
+
+	if(target) {
+		dialog.get_field('target').df.read_only = 1;
+		dialog.get_field('target').value = target;
+		dialog.get_field('target').refresh();
+	}
+
+	dialog.set_primary_action(__('Submit'), function() {
+		var values = dialog.get_values();
+		if(!values) {
+			return;
+		}
+		if(source && values.qty > actual_qty) {
+			frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
+			return;
+		}
+		if(values.source === values.target) {
+			frappe.msgprint(__('Source and target warehouse must be different'));
+		}
+
+		frappe.call({
+			method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
+			args: values,
+			freeze: true,
+			callback: function(r) {
+				frappe.show_alert(__('Stock Entry {0} created',
+					['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
+				dialog.hide();
+				callback(r);
+			},
+		});
+	});
+
+	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">'
+		+ __("Add more items or open full form") + '</a></p>')
+		.appendTo(dialog.body)
+		.find('.link-open')
+		.on('click', function() {
+			frappe.model.with_doctype('Stock Entry', function() {
+				var doc = frappe.model.get_new_doc('Stock Entry');
+				doc.from_warehouse = dialog.get_value('source');
+				doc.to_warehouse = dialog.get_value('target');
+				var row = frappe.model.add_child(doc, 'items');
+				row.item_code = dialog.get_value('item_code');
+				row.f_warehouse = dialog.get_value('target');
+				row.t_warehouse = dialog.get_value('target');
+				row.qty = dialog.get_value('qty');
+				row.conversion_factor = 1;
+				row.transfer_qty = dialog.get_value('qty');
+				row.basic_rate = dialog.get_value('rate');
+				frappe.set_route('Form', doc.doctype, doc.name);
+			})
+		});
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 487c765..7634ff0 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -44,7 +44,9 @@
 
 	for item in items:
 		item.update({
-			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name')
+			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
+			'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no')
+									or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'),
 		})
 
 	return items
diff --git a/erpnext/stock/dashboard/item_dashboard_list.html b/erpnext/stock/dashboard/item_dashboard_list.html
index 5a3fa2e..e1914ed 100644
--- a/erpnext/stock/dashboard/item_dashboard_list.html
+++ b/erpnext/stock/dashboard/item_dashboard_list.html
@@ -43,11 +43,13 @@
 			<div class="col-sm-2 text-right" style="margin-top: 8px;">
 				{% if d.actual_qty %}
 				<button class="btn btn-default btn-xs btn-move"
+					data-disable_quick_entry="{{ d.disable_quick_entry }}"
 					data-warehouse="{{ d.warehouse }}"
 					data-actual_qty="{{ d.actual_qty }}"
 					data-item="{{ escape(d.item_code) }}">{{ __("Move") }}</a>
 				{% endif %}
 				<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add"
+					data-disable_quick_entry="{{ d.disable_quick_entry }}"
 					data-warehouse="{{ d.warehouse }}"
 					data-actual_qty="{{ d.actual_qty }}"
 					data-item="{{ escape(d.item_code) }}"