Merge branch 'develop' into pick-list-enhance
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 9ddd7cc..fd009ec 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -661,4 +661,5 @@
 erpnext.patches.v12_0.create_irs_1099_field_united_states
 erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
 erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
-erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
\ No newline at end of file
+erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
+erpnext.patches.v12_0.set_updated_purpose_in_pick_list
diff --git a/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
new file mode 100644
index 0000000..63ca540
--- /dev/null
+++ b/erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+    frappe.reload_doc("stock", "doctype", "pick_list")
+    frappe.db.sql("""UPDATE `tabPick List` set purpose = 'Delivery'
+        WHERE docstatus = 1  and purpose = 'Delivery against Sales Order' """)
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ef2d19a..05e4aa8 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -1036,7 +1036,7 @@
 		},
 	}, target_doc)
 
-	doc.purpose = 'Delivery against Sales Order'
+	doc.purpose = 'Delivery'
 
 	doc.set_item_locations()
 
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 2789711..d46b98b 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -38,13 +38,17 @@
 			};
 		});
 	},
-	get_item_locations: (frm) => {
-		if (!frm.doc.locations || !frm.doc.locations.length) {
-			frappe.msgprint(__('First add items in the Item Locations table'));
+	set_item_locations:(frm, save) => {
+		if (!(frm.doc.locations && frm.doc.locations.length)) {
+			frappe.msgprint(__('Add items in the Item Locations table'));
 		} else {
-			frm.call('set_item_locations');
+			frm.call('set_item_locations', {save: save});
 		}
 	},
+	get_item_locations: (frm) => {
+		// Button on the form
+		frm.events.set_item_locations(frm, false);
+	},
 	refresh: (frm) => {
 		frm.trigger('add_get_items_button');
 		if (frm.doc.docstatus === 1) {
@@ -52,8 +56,13 @@
 				'pick_list_name': frm.doc.name,
 				'purpose': frm.doc.purpose
 			}).then(target_document_exists => {
+				frm.set_df_property("locations", "allow_on_submit", target_document_exists ? 0 : 1);
+
 				if (target_document_exists) return;
-				if (frm.doc.purpose === 'Delivery against Sales Order') {
+
+				frm.add_custom_button(__('Update Current Stock'), () => frm.trigger('update_pick_list_stock'));
+
+				if (frm.doc.purpose === 'Delivery') {
 					frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create'));
 				} else {
 					frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
@@ -105,6 +114,7 @@
 			method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note',
 			frm: frm
 		});
+
 	},
 	create_stock_entry: (frm) => {
 		frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', {
@@ -114,9 +124,12 @@
 			frappe.set_route("Form", 'Stock Entry', stock_entry.name);
 		});
 	},
+	update_pick_list_stock: (frm) => {
+		frm.events.set_item_locations(frm, true);
+	},
 	add_get_items_button: (frm) => {
 		let purpose = frm.doc.purpose;
-		if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return;
+		if (purpose != 'Delivery' || frm.doc.docstatus !== 0) return;
 		let get_query_filters = {
 			docstatus: 1,
 			per_delivered: ['<', 100],
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index 8d5ef3d..c01388d 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "naming_series:",
  "creation": "2019-07-11 16:03:13.681045",
  "doctype": "DocType",
@@ -44,7 +45,7 @@
    "options": "Warehouse"
   },
   {
-   "depends_on": "eval:doc.purpose==='Delivery against Sales Order'",
+   "depends_on": "eval:doc.purpose==='Delivery'",
    "fieldname": "customer",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -59,6 +60,7 @@
    "options": "Work Order"
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "locations",
    "fieldtype": "Table",
    "label": "Item Locations",
@@ -86,7 +88,7 @@
    "fieldname": "purpose",
    "fieldtype": "Select",
    "label": "Purpose",
-   "options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order"
+   "options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery"
   },
   {
    "depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)",
@@ -111,7 +113,8 @@
   }
  ],
  "is_submittable": 1,
- "modified": "2019-08-29 21:10:11.572387",
+ "links": [],
+ "modified": "2020-03-17 11:38:41.932875",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Pick List",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index c4d8c41..616de5e 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -29,7 +29,7 @@
 			frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
 				.format(frappe.bold(item.item_code), frappe.bold(item.idx)))
 
-	def set_item_locations(self):
+	def set_item_locations(self, save=False):
 		items = self.aggregate_item_qty()
 		self.item_location_map = frappe._dict()
 
@@ -43,7 +43,7 @@
 			item_code = item_doc.item_code
 
 			self.item_location_map.setdefault(item_code,
-				get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code)))
+				get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code), self.company))
 
 			locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
 
@@ -59,12 +59,17 @@
 				location.update(row)
 				self.append('locations', location)
 
+		if save:
+			self.save()
+
 	def aggregate_item_qty(self):
 		locations = self.get('locations')
 		self.item_count_map = {}
 		# aggregate qty for same item
 		item_map = OrderedDict()
 		for item in locations:
+			if not item.item_code:
+				frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
 			item_code = item.item_code
 			reference = item.sales_order_item or item.material_request_item
 			key = (item_code, item.uom, reference)
@@ -85,6 +90,10 @@
 		return item_map.values()
 
 
+def validate_item_locations(pick_list):
+	if not pick_list.locations:
+		frappe.throw(_("Add items in the Item Locations table"))
+
 def get_items_with_location_and_quantity(item_doc, item_location_map):
 	available_locations = item_location_map.get(item_doc.item_code)
 	locations = []
@@ -130,14 +139,14 @@
 	item_location_map[item_doc.item_code] = available_locations
 	return locations
 
-def get_available_item_locations(item_code, from_warehouses, required_qty):
+def get_available_item_locations(item_code, from_warehouses, required_qty, company):
 	locations = []
 	if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
-		locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty)
+		locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company)
 	elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
-		locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty)
+		locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
 	else:
-		locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty)
+		locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company)
 
 	total_qty_available = sum(location.get('qty') for location in locations)
 
@@ -150,9 +159,10 @@
 	return locations
 
 
-def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty):
+def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company):
 	filters = frappe._dict({
 		'item_code': item_code,
+		'company': company,
 		'warehouse': ['!=', '']
 	})
 
@@ -180,7 +190,7 @@
 
 	return locations
 
-def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty):
+def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company):
 	warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
 	batch_locations = frappe.db.sql("""
 		SELECT
@@ -192,6 +202,7 @@
 		WHERE
 			sle.batch_no = batch.name
 			and sle.`item_code`=%(item_code)s
+			and sle.`company` = %(company)s
 			and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
 			{warehouse_condition}
 		GROUP BY
@@ -202,16 +213,20 @@
 		ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
 	""".format(warehouse_condition=warehouse_condition), { #nosec
 		'item_code': item_code,
+		'company': company,
 		'today': today(),
 		'warehouses': from_warehouses
 	}, as_dict=1)
 
 	return batch_locations
 
-def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty):
+def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company):
 	# gets all items available in different warehouses
+	warehouses = [x.get('name') for x in frappe.get_list("Warehouse", {'company': company}, "name")]
+
 	filters = frappe._dict({
 		'item_code': item_code,
+		'warehouse': ['in', warehouses],
 		'actual_qty': ['>', 0]
 	})
 
@@ -230,7 +245,9 @@
 @frappe.whitelist()
 def create_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]
+	validate_item_locations(pick_list)
+
+	sales_orders = [d.sales_order for d in pick_list.locations if d.sales_order]
 	sales_orders = set(sales_orders)
 
 	delivery_note = None
@@ -238,6 +255,10 @@
 		delivery_note = create_delivery_note_from_sales_order(sales_order,
 			delivery_note, skip_item_mapping=True)
 
+	# map rows without sales orders as well
+	if not delivery_note:
+		delivery_note = frappe.new_doc("Delivery Note")
+
 	item_table_mapper = {
 		'doctype': 'Delivery Note Item',
 		'field_map': {
@@ -248,9 +269,25 @@
 		'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
 	}
 
+	item_table_mapper_without_so = {
+		'doctype': 'Delivery Note Item',
+		'field_map': {
+			'rate': 'rate',
+			'name': 'name',
+			'parent': '',
+		}
+	}
+
 	for location in pick_list.locations:
-		sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
-		dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper)
+		if location.sales_order_item:
+			sales_order_item = frappe.get_cached_doc('Sales Order Item', {'name':location.sales_order_item})
+		else:
+			sales_order_item = None
+
+		source_doc, table_mapper = [sales_order_item, item_table_mapper] if sales_order_item \
+			else [location, item_table_mapper_without_so]
+
+		dn_item = map_child_doc(source_doc, delivery_note, table_mapper)
 
 		if dn_item:
 			dn_item.warehouse = location.warehouse
@@ -258,7 +295,7 @@
 			dn_item.batch_no = location.batch_no
 			dn_item.serial_no = location.serial_no
 
-			update_delivery_note_item(sales_order_item, dn_item, delivery_note)
+			update_delivery_note_item(source_doc, dn_item, delivery_note)
 
 	set_delivery_note_missing_values(delivery_note)
 
@@ -269,6 +306,7 @@
 @frappe.whitelist()
 def create_stock_entry(pick_list):
 	pick_list = frappe.get_doc(json.loads(pick_list))
+	validate_item_locations(pick_list)
 
 	if stock_entry_exists(pick_list.get('name')):
 		return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
@@ -318,7 +356,7 @@
 
 @frappe.whitelist()
 def target_document_exists(pick_list_name, purpose):
-	if purpose == 'Delivery against Sales Order':
+	if purpose == 'Delivery':
 		return frappe.db.exists('Delivery Note', {
 			'pick_list': pick_list_name
 		})
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
index c7a35df..71fbf9a 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2019-07-11 16:01:22.832885",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -8,6 +9,7 @@
   "item_name",
   "column_break_2",
   "description",
+  "item_group",
   "section_break_5",
   "warehouse",
   "quantity_section",
@@ -120,7 +122,8 @@
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Item",
-   "options": "Item"
+   "options": "Item",
+   "reqd": 1
   },
   {
    "fieldname": "quantity_section",
@@ -166,10 +169,18 @@
    "fieldtype": "Data",
    "label": "Material Request Item",
    "read_only": 1
+  },
+  {
+   "fetch_from": "item_code.item_group",
+   "fieldname": "item_group",
+   "fieldtype": "Data",
+   "label": "Item Group",
+   "read_only": 1
   }
  ],
  "istable": 1,
- "modified": "2019-08-29 21:28:39.539007",
+ "links": [],
+ "modified": "2020-03-13 19:08:21.995986",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Pick List Item",