Merge branch 'develop' of github.com:frappe/erpnext into feature-pick-list
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 14bf9d5..f6d4eee 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -263,7 +263,7 @@
 					if self.get("is_subcontracted"):
 						args["is_subcontracted"] = self.is_subcontracted
 
-					ret = get_item_details(args, self)
+					ret = get_item_details(args, self, overwrite_warehouse=False)
 
 					for fieldname, value in ret.items():
 						if item.meta.get_field(fieldname) and value is not None:
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 22613cc..d345c0b 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -161,6 +161,13 @@
 			frm.add_custom_button(__('Create BOM'), () => {
 				frm.trigger("make_bom");
 			});
+
+			frm.add_custom_button(__('Pick List'), () => {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.manufacturing.doctype.work_order.work_order.make_pick_list",
+					frm
+				});
+			}, __('Make'));
 		}
 	},
 
@@ -264,6 +271,10 @@
 		});
 	},
 
+	make_pick_list() {
+
+	},
+
 	show_progress: function(frm) {
 		var bars = [];
 		var message = '';
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 2b70325..988cd22 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -19,6 +19,7 @@
 from frappe.utils.csvutils import getlink
 from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
 from erpnext.utilities.transaction_base import validate_uom_is_integer
+from frappe.model.mapper import get_mapped_doc
 
 class OverProductionError(frappe.ValidationError): pass
 class StockOverProductionError(frappe.ValidationError): pass
@@ -707,3 +708,29 @@
 	for d in work_order.operations:
 		if d.operation == operation and d.workstation == workstation:
 			return d
+
+@frappe.whitelist()
+def create_pick_list(source_name, target_doc=None):
+	def update_item_quantity(source, target, source_parent):
+		qty = source.required_qty - source.transferred_qty
+		target.qty = qty
+		target.stock_qty = qty
+		target.uom = frappe.get_value('Item', source.item_code, 'stock_uom')
+		target.stock_uom = target.uom
+		target.conversion_factor = 1
+
+	doc = get_mapped_doc("Work Order", source_name, {
+		"Work Order": {
+			"doctype": "Pick List",
+			"validation": {
+				"docstatus": ["=", 1]
+			}
+		},
+		"Work Order Item": {
+			"doctype": "Pick List Reference Item",
+			"postprocess": update_item_quantity,
+			"condition": lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty)
+		},
+	}, target_doc)
+
+	return doc
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 2e5f255..1949710 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -109,7 +109,9 @@
 		this._super();
 		let allow_delivery = false;
 
-		if(doc.docstatus==1) {
+		if (doc.docstatus==1) {
+			this.frm.add_custom_button(__('Pick List'), () => this.make_pick_list(), __('Create'));
+
 			if(this.frm.has_perm("submit")) {
 				if(doc.status === 'On Hold') {
 				   // un-hold
@@ -233,6 +235,13 @@
 		this.order_type(doc);
 	},
 
+	make_pick_list() {
+		frappe.model.open_mapped_doc({
+			method: "erpnext.selling.doctype.sales_order.sales_order.make_pick_list",
+			frm: this.frm
+		})
+	},
+
 	make_work_order() {
 		var me = this;
 		this.frm.call({
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 09dc9a9..dd156d0 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -568,7 +568,7 @@
 	return doc
 
 @frappe.whitelist()
-def make_delivery_note(source_name, target_doc=None):
+def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
 	def set_missing_values(source, target):
 		target.ignore_pricing_rule = 1
 		target.run_method("set_missing_values")
@@ -593,23 +593,13 @@
 				or item.get("buying_cost_center") \
 				or item_group.get("buying_cost_center")
 
-	target_doc = get_mapped_doc("Sales Order", source_name, {
+	mapper = {
 		"Sales Order": {
 			"doctype": "Delivery Note",
 			"validation": {
 				"docstatus": ["=", 1]
 			}
 		},
-		"Sales Order Item": {
-			"doctype": "Delivery Note Item",
-			"field_map": {
-				"rate": "rate",
-				"name": "so_detail",
-				"parent": "against_sales_order",
-			},
-			"postprocess": update_item,
-			"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
-		},
 		"Sales Taxes and Charges": {
 			"doctype": "Sales Taxes and Charges",
 			"add_if_empty": True
@@ -618,7 +608,21 @@
 			"doctype": "Sales Team",
 			"add_if_empty": True
 		}
-	}, target_doc, set_missing_values)
+	}
+
+	if not skip_item_mapping:
+		mapper["Sales Order Item"] = {
+			"doctype": "Delivery Note Item",
+			"field_map": {
+				"rate": "rate",
+				"name": "so_detail",
+				"parent": "against_sales_order",
+			},
+			"postprocess": update_item,
+			"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+		}
+
+	target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
 
 	return target_doc
 
@@ -996,3 +1000,32 @@
 def make_inter_company_purchase_order(source_name, target_doc=None):
 	from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
 	return make_inter_company_transaction("Sales Order", source_name, target_doc)
+
+@frappe.whitelist()
+def make_pick_list(source_name, target_doc=None):
+	def update_item_quantity(source, target, source_parent):
+		target.qty = flt(source.qty) - flt(source.delivered_qty)
+		target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor)
+
+	doc = get_mapped_doc("Sales Order", source_name, {
+		"Sales Order": {
+			"doctype": "Pick List",
+			"field_map": {
+				"doctype": "items_based_on"
+			},
+			"validation": {
+				"docstatus": ["=", 1]
+			}
+		},
+		"Sales Order Item": {
+			"doctype": "Pick List Reference Item",
+			"field_map": {
+				"parent": "sales_order",
+				"name": "sales_order_item"
+			},
+			"postprocess": update_item_quantity,
+			"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+		},
+	}, target_doc)
+
+	return doc
diff --git a/erpnext/stock/doctype/pick_list/__init__.py b/erpnext/stock/doctype/pick_list/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list/__init__.py
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
new file mode 100644
index 0000000..bd893aa
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Pick List', {
+	setup: (frm) => {
+		frm.set_query('parent_warehouse', () => {
+			return {
+				filters: {
+					'is_group': 1,
+					'company': frm.doc.company
+				}
+			};
+		});
+		frm.set_query('work_order', () => {
+			return {
+				query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders',
+				filters: {
+					'company': frm.doc.company
+				}
+			};
+		});
+	},
+	refresh: (frm) => {
+		frm.trigger('add_get_items_button');
+
+		if (frm.doc.items && (frm.doc.items.length > 1 || frm.doc.items[0].item_code)) {
+			frm.add_custom_button(__('Get Item Locations'), () => {
+				frm.call('set_item_locations');
+			}).addClass('btn-primary');
+		}
+
+		frm.add_custom_button(__('Delivery Note'), () => frm.trigger('make_delivery_note'), __('Create'));
+	},
+	work_order: (frm) => {
+		frm.clear_table('items');
+		erpnext.utils.map_current_doc({
+			method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list',
+			target: frm,
+			source_name: frm.doc.work_order
+		});
+	},
+	items_based_on: (frm) => {
+		frm.trigger('add_get_items_button');
+	},
+	make_delivery_note(frm) {
+		frappe.model.open_mapped_doc({
+			method: 'erpnext.stock.doctype.pick_list.pick_list.make_delivery_note',
+			frm: frm
+		});
+	},
+	add_get_items_button(frm) {
+		let source_doctype = frm.doc.items_based_on;
+		if (source_doctype != 'Sales Order') return;
+		let get_query_filters = {
+			docstatus: 1,
+			per_delivered: ['<', 100],
+			status: ['!=', ''],
+			customer: frm.doc.customer
+		};
+		frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => {
+			if (!frm.doc.customer) {
+				frappe.msgprint(__('Please select Customer first'));
+				return;
+			}
+			erpnext.utils.map_current_doc({
+				method: 'erpnext.selling.doctype.sales_order.sales_order.make_pick_list',
+				source_doctype: 'Sales Order',
+				target: frm,
+				setters: {
+					company: frm.doc.company,
+					customer: frm.doc.customer
+				},
+				date_field: 'transaction_date',
+				get_query_filters: get_query_filters
+			});
+		});
+	}
+});
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
new file mode 100644
index 0000000..1a33622
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -0,0 +1,109 @@
+{
+ "autoname": "PICK.####",
+ "creation": "2019-07-11 16:03:13.681045",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "items_based_on",
+  "customer",
+  "work_order",
+  "column_break_4",
+  "parent_warehouse",
+  "company",
+  "section_break_4",
+  "items",
+  "section_break_6",
+  "locations"
+ ],
+ "fields": [
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break"
+  },
+  {
+   "description": "Items under this warehouse will be suggested",
+   "fieldname": "parent_warehouse",
+   "fieldtype": "Link",
+   "label": "Parent Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "default": "Work Order",
+   "fieldname": "items_based_on",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Items Based On",
+   "options": "Sales Order\nWork Order",
+   "reqd": 1
+  },
+  {
+   "depends_on": "eval:doc.items_based_on===\"Sales Order\"",
+   "fieldname": "customer",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Customer",
+   "options": "Customer",
+   "reqd": 1
+  },
+  {
+   "depends_on": "eval:doc.items_based_on===\"Work Order\"",
+   "fieldname": "work_order",
+   "fieldtype": "Link",
+   "label": "Work Order",
+   "options": "Work Order"
+  },
+  {
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "label": "Items To Be Picked",
+   "options": "Pick List Reference Item",
+   "reqd": 1
+  },
+  {
+   "fieldname": "locations",
+   "fieldtype": "Table",
+   "label": "Item Locations",
+   "options": "Pick List Item"
+  }
+ ],
+ "modified": "2019-08-20 16:57:11.006221",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Pick List",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
new file mode 100644
index 0000000..8699ee6
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -0,0 +1,264 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from six import iteritems
+from frappe.model.mapper import get_mapped_doc, map_child_doc
+from frappe.utils import floor, flt, today
+from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note as make_delivery_note_from_sales_order
+
+# TODO: Prioritize SO or WO group warehouse
+
+class PickList(Document):
+	def set_item_locations(self):
+		items = self.items
+		self.item_location_map = frappe._dict()
+
+		from_warehouses = None
+		if self.parent_warehouse:
+			from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse)
+
+		# Reset
+		self.delete_key('locations')
+		for item_doc in items:
+			item_code = item_doc.item_code
+			if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
+				locations = get_item_locations_based_on_serial_nos(item_doc)
+			elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
+				locations = get_item_locations_based_on_batch_nos(item_doc)
+			else:
+				if item_code not in self.item_location_map:
+					self.item_location_map[item_code] = get_available_items(item_code, from_warehouses)
+				locations = get_items_with_warehouse_and_quantity(item_doc, from_warehouses, self.item_location_map)
+
+			for row in locations:
+				row.update({
+					'item_code': item_code,
+					'sales_order': item_doc.sales_order,
+					'sales_order_item': item_doc.sales_order_item,
+					'uom': item_doc.uom,
+					'stock_uom': item_doc.stock_uom,
+					'conversion_factor': item_doc.conversion_factor,
+					'stock_qty': row.get("qty", 0) * item_doc.conversion_factor,
+					'picked_qty': row.get("qty", 0) * item_doc.conversion_factor
+				})
+				self.append('locations', row)
+
+def get_items_with_warehouse_and_quantity(item_doc, from_warehouses, item_location_map):
+	available_locations = item_location_map.get(item_doc.item_code)
+
+	locations = []
+	remaining_stock_qty = item_doc.stock_qty
+	while remaining_stock_qty > 0 and available_locations:
+		item_location = available_locations.pop(0)
+		stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
+		qty = stock_qty / (item_doc.conversion_factor or 1)
+
+		uom_must_be_whole_number = frappe.db.get_value("UOM", item_doc.uom, "must_be_whole_number")
+		if uom_must_be_whole_number:
+			qty = floor(qty)
+			stock_qty = qty * item_doc.conversion_factor
+
+		locations.append({
+			'qty': qty,
+			'warehouse': item_location.warehouse
+		})
+		remaining_stock_qty -= stock_qty
+
+		qty_diff = item_location.qty - stock_qty
+		# if extra quantity is available push current warehouse to available locations
+		if qty_diff:
+			item_location.qty = qty_diff
+			available_locations = [item_location] + available_locations
+
+	if remaining_stock_qty:
+		frappe.msgprint('{0} {1} of {2} is not available.'
+			.format(remaining_stock_qty / item_doc.conversion_factor, item_doc.uom, item_doc.item_code))
+
+	# update available locations for the item
+	item_location_map[item_doc.item_code] = available_locations
+	return locations
+
+def get_available_items(item_code, from_warehouses):
+	# gets all items available in different warehouses
+	filters = frappe._dict({
+		'item_code': item_code,
+		'actual_qty': ['>', 0]
+	})
+	if from_warehouses:
+		filters.warehouse = ['in', from_warehouses]
+
+	available_items = frappe.get_all('Bin',
+		fields=['warehouse', 'actual_qty as qty'],
+		filters=filters,
+		order_by='creation')
+
+	return available_items
+
+def set_serial_nos(item_doc):
+	serial_nos = frappe.get_all('Serial No', {
+		'item_code': item_doc.item_code,
+		'warehouse': item_doc.warehouse
+	}, limit=item_doc.stock_qty, order_by='purchase_date')
+	item_doc.set('serial_no', '\n'.join([serial_no.name for serial_no in serial_nos]))
+
+	# should we assume that all serialized item_code available in stock will have serial no?
+
+def get_item_locations_based_on_serial_nos(item_doc):
+	serial_nos = frappe.get_all('Serial No',
+		fields = ['name', 'warehouse'],
+		filters = {
+			'item_code': item_doc.item_code,
+			'warehouse': ['!=', '']
+		}, limit=item_doc.stock_qty, order_by='purchase_date', as_list=1)
+
+	remaining_stock_qty = flt(item_doc.stock_qty) - len(serial_nos)
+	if remaining_stock_qty:
+		frappe.msgprint('{0} {1} of {2} is not available.'
+			.format(remaining_stock_qty, item_doc.stock_uom, item_doc.item_code))
+
+	warehouse_serial_nos_map = frappe._dict()
+	for serial_no, warehouse in serial_nos:
+		warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no)
+
+	locations = []
+	for warehouse, serial_nos in iteritems(warehouse_serial_nos_map):
+		locations.append({
+			'qty': len(serial_nos),
+			'warehouse': warehouse,
+			'serial_no': '\n'.join(serial_nos)
+		})
+
+	return locations
+
+def get_item_locations_based_on_batch_nos(item_doc):
+	batch_qty = frappe.db.sql("""
+		SELECT
+			sle.`warehouse`,
+			sle.`batch_no`,
+			SUM(sle.`actual_qty`) AS `qty`
+		FROM
+			`tabStock Ledger Entry` sle, `tabBatch` batch
+		WHERE
+			sle.batch_no = batch.name
+			and sle.`item_code`=%(item_code)s
+			and IFNULL(batch.expiry_date, '2200-01-01') > %(today)s
+		GROUP BY
+			`warehouse`,
+			`batch_no`,
+			`item_code`
+		HAVING `qty` > 0
+		ORDER BY IFNULL(batch.expiry_date, '2200-01-01')
+	""", {
+		'item_code': item_doc.item_code,
+		'today': today()
+	}, as_dict=1)
+
+	locations = []
+	required_qty = item_doc.qty
+	for d in batch_qty:
+		if d.qty > required_qty:
+			d.qty = required_qty
+		else:
+			required_qty -= d.qty
+
+		locations.append(d)
+
+		if required_qty <= 0:
+			break
+
+	if required_qty:
+		frappe.msgprint('No batches found for {} qty of {}.'.format(required_qty, item_doc.item_code))
+
+	return locations
+
+@frappe.whitelist()
+def make_delivery_note(source_name, target_doc=None):
+	pick_list = frappe.get_doc('Pick List', source_name)
+	sales_orders = [d.sales_order for d in pick_list.locations]
+	sales_orders = set(sales_orders)
+
+	delivery_note = None
+	for sales_order in sales_orders:
+		delivery_note = make_delivery_note_from_sales_order(sales_order,
+			delivery_note, skip_item_mapping=True)
+
+	for location in pick_list.locations:
+		sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
+		item_table_mapper = {
+			"doctype": "Delivery Note Item",
+			"field_map": {
+				"rate": "rate",
+				"name": "so_detail",
+				"parent": "against_sales_order",
+			},
+			"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+		}
+
+		dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper)
+
+		if dn_item:
+			dn_item.warehouse = location.warehouse
+			dn_item.qty = location.qty
+
+			update_delivery_note_item(sales_order_item, dn_item, delivery_note)
+
+	set_delivery_note_missing_values(delivery_note)
+
+	return delivery_note
+
+
+def set_delivery_note_missing_values(target):
+	target.run_method("set_missing_values")
+	target.run_method("set_po_nos")
+	target.run_method("calculate_taxes_and_totals")
+
+def update_delivery_note_item(source, target, delivery_note):
+	cost_center = frappe.db.get_value("Project", delivery_note.project, "cost_center")
+	if not cost_center:
+		cost_center = frappe.db.get_value('Item Default',
+			fieldname=['buying_cost_center'],
+			filters={
+				'parent': source.item_code,
+				'parenttype': 'Item',
+				'company': delivery_note.company
+			})
+
+	if not cost_center:
+		cost_center = frappe.db.get_value('Item Default',
+			fieldname=['buying_cost_center'],
+			filters={
+				'parent': source.item_group,
+				'parenttype': 'Item Group',
+				'company': delivery_note.company
+			})
+
+	target.cost_center = cost_center
+
+@frappe.whitelist()
+def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict):
+	return frappe.db.sql("""
+		SELECT
+			`name`, `company`, `planned_start_date`
+		FROM
+			`tabWork Order`
+		WHERE
+			`status` not in ('Completed', 'Stopped')
+			AND `qty` > `produced_qty`
+			AND `docstatus` = 1
+			AND `company` = %(company)s
+			AND `name` like %(txt)s
+		ORDER BY
+			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
+		LIMIT
+			%(start)s, %(page_length)s""",
+		{
+			'txt': "%%%s%%" % txt,
+			'_txt': txt.replace('%', ''),
+			'start': start,
+			'page_length': frappe.utils.cint(page_length),
+			'company': filters.get('company')
+		}, as_dict=as_dict)
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
new file mode 100644
index 0000000..4048e5d
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+# test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
+
+from erpnext.selling.doctype.sales_order.sales_order import make_pick_list
+
+class TestPickList(unittest.TestCase):
+	def test_pick_list_picks_warehouse_for_each_item(self):
+		pick_list = frappe.get_doc({
+			'doctype': 'Pick List',
+			'company': '_Test Company',
+			'items': [{
+				'item': '_Test Item Home Desktop 100',
+				'reference_doctype': 'Sales Order',
+				'qty': 5,
+				'reference_name': '_T-Sales Order-1',
+			}],
+		})
+
+		pick_list.set_item_locations()
+
+		self.assertEqual(pick_list.items_locations[0].item, '_Test Item Home Desktop 100')
+		self.assertEqual(pick_list.items_locations[0].warehouse, '_Test Warehouse - _TC')
+		self.assertEqual(pick_list.items_locations[0].qty, 5)
+
+	def test_pick_list_skips_out_of_stock_item(self):
+		pick_list = frappe.get_doc({
+			'doctype': 'Pick List',
+			'company': '_Test Company',
+			'items': [{
+				'item': '_Test Item Warehouse Group Wise Reorder',
+				'reference_doctype': 'Sales Order',
+				'qty': 1000,
+				'reference_name': '_T-Sales Order-1',
+			}],
+		})
+
+		pick_list.set_item_locations()
+
+		self.assertEqual(pick_list.items_locations[0].item, '_Test Item Warehouse Group Wise Reorder')
+		self.assertEqual(pick_list.items_locations[0].warehouse, '_Test Warehouse Group-C1 - _TC')
+		self.assertEqual(pick_list.items_locations[0].qty, 30)
+
+
+	def test_pick_list_skips_items_in_expired_batch(self):
+		pass
+
+	def test_pick_list_shows_serial_no_for_serialized_item(self):
+
+		stock_reconciliation = frappe.get_doc({
+			'doctype': 'Stock Reconciliation',
+			'company': '_Test Company',
+			'items': [{
+				'item_code': '_Test Serialized Item',
+				'warehouse': '_Test Warehouse - _TC',
+				'qty': 5,
+				'serial_no': '123450\n123451\n123452\n123453\n123454'
+			}]
+		})
+
+		stock_reconciliation.submit()
+
+		pick_list = frappe.get_doc({
+			'doctype': 'Pick List',
+			'company': '_Test Company',
+			'items': [{
+				'item': '_Test Serialized Item',
+				'reference_doctype': 'Sales Order',
+				'qty': 1000,
+				'reference_name': '_T-Sales Order-1',
+			}],
+		})
+
+		pick_list.set_item_locations()
+		self.assertEqual(pick_list.items_locations[0].item, '_Test Serialized Item')
+		self.assertEqual(pick_list.items_locations[0].warehouse, '_Test Warehouse Group-C1 - _TC')
+		self.assertEqual(pick_list.items_locations[0].qty, 30)
+		self.assertEqual(pick_list.items_locations[0].serial_no, 30)
+
+
+	def test_pick_list_for_multiple_reference_doctypes(self):
+		pass
+
+
+## records required
+
+'''
+batch no
+items
+sales invoice
+stock entries
+	bin
+	stock ledger entry
+warehouses
+'''
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list_item/__init__.py b/erpnext/stock/doctype/pick_list_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_item/__init__.py
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
new file mode 100644
index 0000000..9ee806a
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -0,0 +1,168 @@
+{
+ "creation": "2019-07-11 16:01:22.832885",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_code",
+  "item_name",
+  "column_break_2",
+  "description",
+  "section_break_5",
+  "warehouse",
+  "quantity_section",
+  "qty",
+  "stock_qty",
+  "picked_qty",
+  "column_break_11",
+  "uom",
+  "stock_uom",
+  "conversion_factor",
+  "serial_no_and_batch_section",
+  "serial_no",
+  "column_break_20",
+  "batch_no",
+  "column_break_15",
+  "sales_order",
+  "sales_order_item"
+ ],
+ "fields": [
+  {
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Qty",
+   "read_only": 1
+  },
+  {
+   "fieldname": "picked_qty",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Picked Qty"
+  },
+  {
+   "fieldname": "warehouse",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Warehouse",
+   "options": "Warehouse",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "item_code.description",
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "label": "Description",
+   "read_only": 1
+  },
+  {
+   "depends_on": "serial_no",
+   "fieldname": "serial_no",
+   "fieldtype": "Small Text",
+   "label": "Serial No"
+  },
+  {
+   "depends_on": "batch_no",
+   "fieldname": "batch_no",
+   "fieldtype": "Link",
+   "label": "Batch No",
+   "options": "Batch"
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "stock_uom",
+   "fieldtype": "Link",
+   "label": "Stock UOM",
+   "options": "UOM",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "label": "UOM",
+   "options": "UOM",
+   "read_only": 1
+  },
+  {
+   "fieldname": "conversion_factor",
+   "fieldtype": "Float",
+   "label": "UOM Conversion Factor",
+   "read_only": 1
+  },
+  {
+   "fieldname": "stock_qty",
+   "fieldtype": "Float",
+   "label": "Qty as per Stock UOM",
+   "read_only": 1
+  },
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item",
+   "options": "Item",
+   "read_only": 1
+  },
+  {
+   "fieldname": "quantity_section",
+   "fieldtype": "Section Break",
+   "label": "Quantity"
+  },
+  {
+   "fieldname": "column_break_15",
+   "fieldtype": "Section Break",
+   "label": "Reference"
+  },
+  {
+   "fieldname": "sales_order",
+   "fieldtype": "Link",
+   "label": "Sales Order",
+   "options": "Sales Order",
+   "read_only": 1
+  },
+  {
+   "fieldname": "sales_order_item",
+   "fieldtype": "Data",
+   "label": "Sales Order Item",
+   "read_only": 1
+  },
+  {
+   "fieldname": "serial_no_and_batch_section",
+   "fieldtype": "Section Break",
+   "label": "Serial No and Batch"
+  },
+  {
+   "fieldname": "column_break_20",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "istable": 1,
+ "modified": "2019-08-14 18:41:37.727388",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Pick List Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.py b/erpnext/stock/doctype/pick_list_item/pick_list_item.py
new file mode 100644
index 0000000..8797b8d
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class PickListItem(Document):
+	pass
diff --git a/erpnext/stock/doctype/pick_list_reference_item/__init__.py b/erpnext/stock/doctype/pick_list_reference_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_reference_item/__init__.py
diff --git a/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.js b/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.js
new file mode 100644
index 0000000..875ce23
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Pick List Reference Item', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.json b/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.json
new file mode 100644
index 0000000..0aa94fe
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.json
@@ -0,0 +1,90 @@
+{
+ "creation": "2019-07-24 16:11:07.415562",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_code",
+  "quantity_section",
+  "qty",
+  "stock_qty",
+  "column_break_5",
+  "uom",
+  "stock_uom",
+  "conversion_factor",
+  "reference_section",
+  "sales_order",
+  "sales_order_item"
+ ],
+ "fields": [
+  {
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Qty"
+  },
+  {
+   "fieldname": "quantity_section",
+   "fieldtype": "Section Break",
+   "label": "Quantity"
+  },
+  {
+   "fieldname": "stock_qty",
+   "fieldtype": "Float",
+   "label": "Stock Qty"
+  },
+  {
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "label": "UOM",
+   "options": "UOM"
+  },
+  {
+   "fieldname": "stock_uom",
+   "fieldtype": "Link",
+   "label": "Stock UOM",
+   "options": "UOM"
+  },
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item",
+   "options": "Item"
+  },
+  {
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "reference_section",
+   "fieldtype": "Section Break",
+   "label": "Reference"
+  },
+  {
+   "fieldname": "sales_order",
+   "fieldtype": "Link",
+   "label": "Sales Order",
+   "options": "Sales Order"
+  },
+  {
+   "fieldname": "sales_order_item",
+   "fieldtype": "Data",
+   "label": "Sales Order Item"
+  },
+  {
+   "fieldname": "conversion_factor",
+   "fieldtype": "Float",
+   "label": "UOM Conversion Factor"
+  }
+ ],
+ "istable": 1,
+ "modified": "2019-08-14 18:38:28.867113",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Pick List Reference Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.py b/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.py
new file mode 100644
index 0000000..74f0563
--- /dev/null
+++ b/erpnext/stock/doctype/pick_list_reference_item/pick_list_reference_item.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class PickListReferenceItem(Document):
+	pass
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index f1d784c..41101f4 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -22,7 +22,7 @@
 purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
 
 @frappe.whitelist()
-def get_item_details(args, doc=None):
+def get_item_details(args, doc=None, overwrite_warehouse=True):
 	"""
 		args = {
 			"item_code": "",
@@ -44,11 +44,15 @@
 			"set_warehouse": ""
 		}
 	"""
+
 	args = process_args(args)
+	print('warehouse', args.warehouse, '========')
 	item = frappe.get_cached_doc("Item", args.item_code)
 	validate_item_details(args, item)
 
-	out = get_basic_details(args, item)
+	out = get_basic_details(args, item, overwrite_warehouse)
+
+	print('warehouse2', out.warehouse, '========')
 
 	get_item_tax_template(args, item, out)
 	out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \
@@ -178,7 +182,7 @@
 			throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
 
 
-def get_basic_details(args, item):
+def get_basic_details(args, item, overwrite_warehouse=True):
 	"""
 	:param args: {
 			"item_code": "",
@@ -225,14 +229,26 @@
 	item_group_defaults = get_item_group_defaults(item.name, args.company)
 	brand_defaults = get_brand_defaults(item.name, args.company)
 
-	warehouse = (args.get("set_warehouse") or item_defaults.get("default_warehouse") or
-		item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or args.warehouse)
+	if overwrite_warehouse or not args.warehouse:
+		warehouse = (
+			args.get("set_warehouse") or
+			item_defaults.get("default_warehouse") or
+			item_group_defaults.get("default_warehouse") or
+			brand_defaults.get("default_warehouse") or
+			args.warehouse
+		)
 
-	if not warehouse:
-		defaults = frappe.defaults.get_defaults() or {}
-		if defaults.get("default_warehouse") and frappe.db.exists("Warehouse",
-			{'name': defaults.default_warehouse, 'company': args.company}):
-			warehouse = defaults.default_warehouse
+		if not warehouse:
+			defaults = frappe.defaults.get_defaults() or {}
+			warehouse_exists = frappe.db.exists("Warehouse", {
+				'name': defaults.default_warehouse,
+				'company': args.company
+			})
+			if defaults.get("default_warehouse") and warehouse_exists:
+				warehouse = defaults.default_warehouse
+
+	else:
+		warehouse = args.warehouse
 
 	if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
 		args['material_request_type'] = frappe.db.get_value('Material Request',
@@ -784,6 +800,7 @@
 
 @frappe.whitelist()
 def get_bin_details(item_code, warehouse):
+	print(item_code, warehouse, '---------------------------')
 	return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
 		["projected_qty", "actual_qty", "reserved_qty"], as_dict=True, cache=True) \
 			or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
diff --git a/erpnext/stock/print_format/__init__.py b/erpnext/stock/print_format/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/print_format/__init__.py
diff --git a/erpnext/stock/print_format/pick_list/__init__.py b/erpnext/stock/print_format/pick_list/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/print_format/pick_list/__init__.py
diff --git a/erpnext/stock/print_format/pick_list/pick_list.json b/erpnext/stock/print_format/pick_list/pick_list.json
new file mode 100644
index 0000000..56c819f
--- /dev/null
+++ b/erpnext/stock/print_format/pick_list/pick_list.json
@@ -0,0 +1,23 @@
+{
+ "align_labels_right": 1,
+ "creation": "2019-08-02 07:27:42.533305",
+ "custom_format": 0,
+ "disabled": 0,
+ "doc_type": "Pick List",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>Pick List<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"parent_warehouse\", \"label\": \"Parent Warehouse\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"item_name\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"warehouse\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"serial_no\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"batch_no\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"item_locations\", \"label\": \"Item Locations\"}]",
+ "idx": 0,
+ "line_breaks": 1,
+ "modified": "2019-08-02 07:58:35.504361",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Pick List",
+ "owner": "Administrator",
+ "print_format_builder": 1,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 1,
+ "standard": "Yes"
+}
\ No newline at end of file