Merge branch 'develop' into independent-manu-entry
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d31e65a..30e0bb8 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -35,6 +35,7 @@
 from erpnext.stock.utils import get_bin, get_incoming_rate
 
 
+class FinishedGoodError(frappe.ValidationError): pass
 class IncorrectValuationRateError(frappe.ValidationError): pass
 class DuplicateEntryForWorkOrderError(frappe.ValidationError): pass
 class OperationsNotCompleteError(frappe.ValidationError): pass
@@ -699,6 +700,11 @@
 
 			finished_item = self.get_finished_item()
 
+			if not finished_item:
+				# In case of independent Manufacture entry, don't auto set
+				# user must decide and set
+				return
+
 			for d in self.items:
 				if d.t_warehouse and not d.s_warehouse:
 					if self.purpose=="Repack" or d.item_code == finished_item:
@@ -719,38 +725,64 @@
 		return finished_item
 
 	def validate_finished_goods(self):
-		"""validation: finished good quantity should be same as manufacturing quantity"""
-		if not self.work_order: return
+		"""
+			1. Check if FG exists
+			2. Check if Multiple FG Items are present
+			3. Check FG Item and Qty against WO if present
+		"""
+		production_item, wo_qty, finished_items = None, 0, []
 
-		production_item, wo_qty = frappe.db.get_value("Work Order",
-			self.work_order, ["production_item", "qty"])
+		wo_details = frappe.db.get_value(
+			"Work Order", self.work_order, ["production_item", "qty"]
+		)
+		if wo_details:
+			production_item, wo_qty = wo_details
 
-		finished_items = []
 		for d in self.get('items'):
 			if d.is_finished_item:
+				if not self.work_order:
+					finished_items.append(d.item_code)
+					continue # Independent Manufacture Entry, no WO to match against
+
 				if d.item_code != production_item:
 					frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
-						.format(d.item_code, self.work_order))
+						.format(d.item_code, self.work_order)
+					)
 				elif flt(d.transfer_qty) > flt(self.fg_completed_qty):
-					frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
-						format(d.idx, d.transfer_qty, self.fg_completed_qty))
+					frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}")
+						.format(d.idx, d.transfer_qty, self.fg_completed_qty)
+					)
+
 				finished_items.append(d.item_code)
 
 		if len(set(finished_items)) > 1:
-			frappe.throw(_("Multiple items cannot be marked as finished item"))
+			frappe.throw(
+				msg=_("Multiple items cannot be marked as finished item"),
+				title=_("Note"),
+				exc=FinishedGoodError
+			)
 
 		if self.purpose == "Manufacture":
 			if not finished_items:
-				frappe.throw(_('Finished Good has not set in the stock entry {0}')
-					.format(self.name))
+				frappe.throw(
+					msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
+					title=_("Missing Finished Good"),
+					exc=FinishedGoodError
+				)
 
-			allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
-				"overproduction_percentage_for_work_order"))
+			allowance_percentage = flt(
+				frappe.db.get_single_value(
+					"Manufacturing Settings","overproduction_percentage_for_work_order"
+				)
+			)
+			allowed_qty = wo_qty + ((allowance_percentage/100) * wo_qty)
 
-			allowed_qty = wo_qty + (allowance_percentage/100 * wo_qty)
-			if self.fg_completed_qty > allowed_qty:
-				frappe.throw(_("For quantity {0} should not be greater than work order quantity {1}")
-					.format(flt(self.fg_completed_qty), wo_qty))
+			# No work order could mean independent Manufacture entry, if so skip validation
+			if self.work_order and self.fg_completed_qty > allowed_qty:
+				frappe.throw(
+					_("For quantity {0} should not be greater than work order quantity {1}")
+					.format(flt(self.fg_completed_qty), wo_qty)
+				)
 
 	def update_stock_ledger(self):
 		sl_entries = []
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 0679467..59cadc4 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -15,7 +15,7 @@
 	set_item_variant_settings,
 )
 from erpnext.stock.doctype.serial_no.serial_no import *  # noqa
-from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse
+from erpnext.stock.doctype.stock_entry.stock_entry import (move_sample_to_retention_warehouse, FinishedGoodError)
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
@@ -868,6 +868,38 @@
 		distributed_costs = [d.additional_cost for d in se.items]
 		self.assertEqual([40.0, 60.0], distributed_costs)
 
+	def test_independent_manufacture_entry(self):
+		"Test FG items and incoming rate calculation in Maniufacture Entry without WO or BOM linked."
+		se = frappe.get_doc(
+			doctype="Stock Entry",
+			purpose="Manufacture",
+			stock_entry_type="Manufacture",
+			company="_Test Company",
+			items=[
+				frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
+				frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC")
+			]
+		)
+		# SE must have atleast one FG
+		self.assertRaises(FinishedGoodError, se.save)
+
+		se.items[0].is_finished_item = 1
+		se.items[1].is_finished_item = 1
+		# SE cannot have multiple FGs
+		self.assertRaises(FinishedGoodError, se.save)
+
+		se.items[0].is_finished_item = 0
+		se.save()
+
+		# Check if FG cost is calculated based on RM total cost
+		# RM total cost = 200, FG rate = 200/4(FG qty) =  50
+		self.assertEqual(se.items[1].basic_rate, 50)
+		self.assertEqual(se.value_difference, 0.0)
+		self.assertEqual(se.total_incoming_value, se.total_outgoing_value)
+
+		# teardown
+		se.delete()
+
 def make_serialized_item(**args):
 	args = frappe._dict(args)
 	se = frappe.copy_doc(test_records[0])