Merge pull request #29271 from marination/allow-multiple-fg-in-repack

fix: Don't validate FG in repack entry
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d1149ea..a61b319 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -86,8 +86,11 @@
 		self.validate_warehouse()
 		self.validate_work_order()
 		self.validate_bom()
-		self.mark_finished_and_scrap_items()
-		self.validate_finished_goods()
+
+		if self.purpose in ("Manufacture", "Repack"):
+			self.mark_finished_and_scrap_items()
+			self.validate_finished_goods()
+
 		self.validate_with_material_request()
 		self.validate_batch()
 		self.validate_inspection()
@@ -706,26 +709,25 @@
 				validate_bom_no(item_code, d.bom_no)
 
 	def mark_finished_and_scrap_items(self):
-		if self.purpose in ("Repack", "Manufacture"):
-			if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
-				return
+		if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
+			return
 
-			finished_item = self.get_finished_item()
+		finished_item = self.get_finished_item()
 
-			if not finished_item and self.purpose == "Manufacture":
-				# In case of independent Manufacture entry, don't auto set
-				# user must decide and set
-				return
+		if not finished_item and self.purpose == "Manufacture":
+			# 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:
-						d.is_finished_item = 1
-					else:
-						d.is_scrap_item = 1
+		for d in self.items:
+			if d.t_warehouse and not d.s_warehouse:
+				if self.purpose=="Repack" or d.item_code == finished_item:
+					d.is_finished_item = 1
 				else:
-					d.is_finished_item = 0
-					d.is_scrap_item = 0
+					d.is_scrap_item = 1
+			else:
+				d.is_finished_item = 0
+				d.is_scrap_item = 0
 
 	def get_finished_item(self):
 		finished_item = None
@@ -738,9 +740,9 @@
 
 	def validate_finished_goods(self):
 		"""
-			1. Check if FG exists
-			2. Check if Multiple FG Items are present
-			3. Check FG Item and Qty against WO if present
+			1. Check if FG exists (mfg, repack)
+			2. Check if Multiple FG Items are present (mfg)
+			3. Check FG Item and Qty against WO if present (mfg)
 		"""
 		production_item, wo_qty, finished_items = None, 0, []
 
@@ -753,8 +755,9 @@
 		for d in self.get('items'):
 			if d.is_finished_item:
 				if not self.work_order:
+					# Independent MFG Entry/ Repack Entry, no WO to match against
 					finished_items.append(d.item_code)
-					continue # Independent Manufacture Entry, no WO to match against
+					continue
 
 				if d.item_code != production_item:
 					frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
@@ -767,19 +770,17 @@
 
 				finished_items.append(d.item_code)
 
-		if len(set(finished_items)) > 1:
+		if not finished_items:
 			frappe.throw(
-				msg=_("Multiple items cannot be marked as finished item"),
-				title=_("Note"),
-				exc=FinishedGoodError
+				msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
+				title=_("Missing Finished Good"), exc=FinishedGoodError
 			)
 
 		if self.purpose == "Manufacture":
-			if not finished_items:
+			if len(set(finished_items)) > 1:
 				frappe.throw(
-					msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
-					title=_("Missing Finished Good"),
-					exc=FinishedGoodError
+					msg=_("Multiple items cannot be marked as finished item"),
+					title=_("Note"), exc=FinishedGoodError
 				)
 
 			allowance_percentage = flt(
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b874874..8f5d442 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -226,9 +226,47 @@
 
 		mtn.cancel()
 
-	def test_repack_no_change_in_valuation(self):
-		company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
+	def test_repack_multiple_fg(self):
+		"Test `is_finished_item` for one item repacked into two items."
+		make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=100, basic_rate=100)
 
+		repack = frappe.copy_doc(test_records[3])
+		repack.posting_date = nowdate()
+		repack.posting_time = nowtime()
+
+		repack.items[0].qty = 100.0
+		repack.items[0].transfer_qty = 100.0
+		repack.items[1].qty = 50.0
+
+		repack.append("items", {
+			"conversion_factor": 1.0,
+			"cost_center": "_Test Cost Center - _TC",
+			"doctype": "Stock Entry Detail",
+			"expense_account": "Stock Adjustment - _TC",
+			"basic_rate": 150,
+			"item_code": "_Test Item 2",
+			"parentfield": "items",
+			"qty": 50.0,
+			"stock_uom": "_Test UOM",
+			"t_warehouse": "_Test Warehouse - _TC",
+			"transfer_qty": 50.0,
+			"uom": "_Test UOM"
+		})
+		repack.set_stock_entry_type()
+		repack.insert()
+
+		self.assertEqual(repack.items[1].is_finished_item, 1)
+		self.assertEqual(repack.items[2].is_finished_item, 1)
+
+		repack.items[1].is_finished_item = 0
+		repack.items[2].is_finished_item = 0
+
+		# must raise error if 0 fg in repack entry
+		self.assertRaises(FinishedGoodError, repack.validate_finished_goods)
+
+		repack.delete() # teardown
+
+	def test_repack_no_change_in_valuation(self):
 		make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
 		make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
 			qty=50, basic_rate=100)