fix: Stricter validations

- Validation for overreceipt on Purchase Invoice, Stock Entry, Purchase Receipt & Stock Reconciliation
- Every incoming stock transaction must be checked to avoid overcapacity
- However application of rule and splitting only on certain doctypes
- Validate capacity < stock balance on save in putaway rule, irrespective
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index af26d7b..e0fcf47 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -396,38 +396,54 @@
 	def validate_putaway_capacity(self):
 		# if over receipt is attempted while 'apply putaway rule' is disabled
 		# and if rule was applied on the transaction, validate it.
-		from erpnext.stock.doctype.putaway_rule.putaway_rule import get_putaway_capacity
-		valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry")
-		rule_applied = any(item.get("putaway_rule") for item in self.get("items"))
+		from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
+		valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
+			"Stock Reconciliation")
 
-		if valid_doctype and rule_applied and not self.apply_putaway_rule:
+		if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
+			valid_doctype = False
+
+		if valid_doctype:
 			rule_map = defaultdict(dict)
 			for item in self.get("items"):
-				if item.get("putaway_rule"):
-					rule = item.get("putaway_rule")
-					disabled = frappe.db.get_value("Putaway Rule", rule, "disable")
-					if disabled: return # dont validate for disabled rule
-					stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
-					warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
-					if not rule_map[rule]:
-						rule_map[rule]["warehouse"] = item.get(warehouse_field)
-						rule_map[rule]["item"] = item.get("item_code")
-						rule_map[rule]["qty_put"] = 0
-						rule_map[rule]["capacity"] = get_putaway_capacity(rule)
-					rule_map[rule]["qty_put"] += flt(stock_qty)
+				warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
+				rule = frappe.db.get_value("Putaway Rule",
+					{
+						"item_code": item.get("item_code"),
+						"warehouse": item.get(warehouse_field)
+					},
+					["name", "disable"], as_dict=True)
+				if rule:
+					if rule.get("disabled"): continue # dont validate for disabled rule
+
+					if self.doctype == "Stock Reconciliation":
+						stock_qty = flt(item.qty)
+					else:
+						stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
+
+					rule_name = rule.get("name")
+					if not rule_map[rule_name]:
+						rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
+						rule_map[rule_name]["item"] = item.get("item_code")
+						rule_map[rule_name]["qty_put"] = 0
+						rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
+					rule_map[rule_name]["qty_put"] += flt(stock_qty)
 
 			for rule, values in rule_map.items():
 				if flt(values["qty_put"]) > flt(values["capacity"]):
-					message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
-						.format(
-							frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
-							frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
-						)
-					message += "<br><br>"
-					rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
-					message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
+					message = self.prepare_over_receipt_message(rule, values)
 					frappe.throw(msg=message, title=_("Over Receipt"))
-			return rule_map
+
+	def prepare_over_receipt_message(self, rule, values):
+		message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
+			.format(
+				frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
+				frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
+			)
+		message += "<br><br>"
+		rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
+		message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
+		return message
 
 	def repost_future_sle_and_gle(self):
 		args = frappe._dict({
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5e6a3f2..61c5310 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -86,7 +86,7 @@
 	def before_validate(self):
 		from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
 
-		if self.get("items") and self.apply_putaway_rule:
+		if self.get("items") and self.apply_putaway_rule and not self.get("is_return"):
 			apply_putaway_rule(self.doctype, self.get("items"), self.company)
 
 	def validate(self):
@@ -415,7 +415,7 @@
 		if warehouse_with_no_account:
 			frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
 				"\n".join(warehouse_with_no_account))
-		
+
 		return process_gl_map(gl_entries)
 
 	def get_asset_gl_entry(self, gl_entries):
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 5675258..ea26cac 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -44,7 +44,7 @@
 		stock_uom = frappe.db.get_value("Item", self.item_code, "stock_uom")
 		balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate())
 
-		if flt(self.stock_capacity) < flt(balance_qty) and self.get('__islocal'):
+		if flt(self.stock_capacity) < flt(balance_qty):
 			frappe.throw(_("Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} {2}.")
 				.format(self.item_code, frappe.bold(balance_qty), stock_uom),
 				title=_("Insufficient Capacity"))
@@ -56,7 +56,7 @@
 		self.stock_capacity = (flt(self.conversion_factor) or 1) * flt(self.capacity)
 
 @frappe.whitelist()
-def get_putaway_capacity(rule):
+def get_available_putaway_capacity(rule):
 	stock_capacity, item_code, warehouse = frappe.db.get_value("Putaway Rule", rule,
 		["stock_capacity", "item_code", "warehouse"])
 	balance_qty = get_stock_balance(item_code, warehouse, nowdate())
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 5b40292..f0a90f9 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -30,6 +30,7 @@
 		self.validate_data()
 		self.validate_expense_account()
 		self.set_total_qty_and_amount()
+		self.validate_putaway_capacity()
 
 		if self._action=="submit":
 			self.make_batches('warehouse')