Merge pull request #2313 from nabinhait/stock_reco

 Fix gl entries for stock transactions 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 31f7113..a2bf78c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -7,7 +7,7 @@
 from frappe.utils import cint, cstr, flt
 from frappe import _, msgprint, throw
 from erpnext.accounts.party import get_party_account, get_due_date
-from erpnext.controllers.stock_controller import update_gl_entries_after, block_negative_stock
+from erpnext.controllers.stock_controller import update_gl_entries_after
 from frappe.model.mapper import get_mapped_doc
 
 from erpnext.controllers.selling_controller import SellingController
@@ -456,8 +456,8 @@
 
 		self.make_sl_entries(sl_entries)
 
-	def make_gl_entries(self, repost_future_gle=True, allow_negative_stock=False):
-		gl_entries = self.get_gl_entries(allow_negative_stock=allow_negative_stock)
+	def make_gl_entries(self, repost_future_gle=True):
+		gl_entries = self.get_gl_entries()
 
 		if gl_entries:
 			from erpnext.accounts.general_ledger import make_gl_entries
@@ -476,7 +476,7 @@
 					items, warehouses = self.get_items_and_warehouses()
 					update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items)
 
-	def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
+	def get_gl_entries(self, warehouse_account=None):
 		from erpnext.accounts.general_ledger import merge_similar_entries
 
 		gl_entries = []
@@ -485,7 +485,7 @@
 
 		self.make_tax_gl_entries(gl_entries)
 
-		self.make_item_gl_entries(gl_entries, allow_negative_stock)
+		self.make_item_gl_entries(gl_entries)
 
 		# merge gl entries before adding pos entries
 		gl_entries = merge_similar_entries(gl_entries)
@@ -520,7 +520,7 @@
 					})
 				)
 
-	def make_item_gl_entries(self, gl_entries, allow_negative_stock=False):
+	def make_item_gl_entries(self, gl_entries):
 		# income account gl entries
 		for item in self.get("entries"):
 			if flt(item.base_amount):
@@ -537,7 +537,7 @@
 		# expense account gl entries
 		if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \
 				and cint(self.update_stock):
-			gl_entries += super(SalesInvoice, self).get_gl_entries(allow_negative_stock=allow_negative_stock)
+			gl_entries += super(SalesInvoice, self).get_gl_entries()
 
 	def make_pos_gl_entries(self, gl_entries):
 		if cint(self.is_pos) and self.cash_bank_account and self.paid_amount:
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index cf57658..fc912c9 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -269,7 +269,7 @@
 			# get raw materials rate
 			if self.doctype == "Purchase Receipt":
 				from erpnext.stock.utils import get_incoming_rate
-				item_rate = get_incoming_rate({
+				rm.rate = get_incoming_rate({
 					"item_code": bom_item.item_code,
 					"warehouse": self.supplier_warehouse,
 					"posting_date": self.posting_date,
@@ -277,10 +277,9 @@
 					"qty": -1 * required_qty,
 					"serial_no": rm.serial_no
 				})
-				if not item_rate:
-					from erpnext.controllers.stock_controller import get_valuation_rate
-					item_rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse)
-				rm.rate = item_rate or bom_item.rate
+				if not rm.rate:
+					from erpnext.stock.stock_ledger import get_valuation_rate
+					rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse)
 			else:
 				rm.rate = bom_item.rate
 
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a72a3d0..0f8eca4 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -11,7 +11,7 @@
 from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
 
 class StockController(AccountsController):
-	def make_gl_entries(self, repost_future_gle=True, allow_negative_stock=False):
+	def make_gl_entries(self, repost_future_gle=True):
 		if self.docstatus == 2:
 			delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
@@ -19,18 +19,16 @@
 			warehouse_account = get_warehouse_account()
 
 			if self.docstatus==1:
-				gl_entries = self.get_gl_entries(warehouse_account, allow_negative_stock=allow_negative_stock)
+				gl_entries = self.get_gl_entries(warehouse_account)
 				make_gl_entries(gl_entries)
 
 			if repost_future_gle:
 				items, warehouses = self.get_items_and_warehouses()
 				update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
-					warehouse_account, allow_negative_stock)
+					warehouse_account)
 
 	def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
-			default_cost_center=None, allow_negative_stock=False):
-
-		# block_negative_stock(allow_negative_stock)
+			default_cost_center=None):
 
 		if not warehouse_account:
 			warehouse_account = get_warehouse_account()
@@ -49,17 +47,12 @@
 
 						self.check_expense_account(detail)
 
-						stock_value_difference = flt(sle.stock_value_difference, 2)
-						if not stock_value_difference:
-							valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse)
-							stock_value_difference = flt(sle.actual_qty)*flt(valuation_rate)
-
 						gl_list.append(self.get_gl_dict({
 							"account": warehouse_account[sle.warehouse],
 							"against": detail.expense_account,
 							"cost_center": detail.cost_center,
 							"remarks": self.get("remarks") or "Accounting Entry for Stock",
-							"debit": stock_value_difference
+							"debit": flt(sle.stock_value_difference, 2)
 						}))
 
 						# to target warehouse / expense account
@@ -68,7 +61,7 @@
 							"against": warehouse_account[sle.warehouse],
 							"cost_center": detail.cost_center,
 							"remarks": self.get("remarks") or "Accounting Entry for Stock",
-							"credit": stock_value_difference
+							"credit": flt(sle.stock_value_difference, 2)
 						}))
 					elif sle.warehouse not in warehouse_with_no_account:
 						warehouse_with_no_account.append(sle.warehouse)
@@ -224,7 +217,7 @@
 		return serialized_items
 
 def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
-		warehouse_account=None, allow_negative_stock=False):
+		warehouse_account=None):
 	def _delete_gl_entries(voucher_type, voucher_no):
 		frappe.db.sql("""delete from `tabGL Entry`
 			where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
@@ -238,12 +231,12 @@
 	for voucher_type, voucher_no in future_stock_vouchers:
 		existing_gle = gle.get((voucher_type, voucher_no), [])
 		voucher_obj = frappe.get_doc(voucher_type, voucher_no)
-		expected_gle = voucher_obj.get_gl_entries(warehouse_account, allow_negative_stock=allow_negative_stock)
+		expected_gle = voucher_obj.get_gl_entries(warehouse_account)
 		if expected_gle:
 			if not existing_gle or not compare_existing_and_expected_gle(existing_gle,
 				expected_gle):
 					_delete_gl_entries(voucher_type, voucher_no)
-					voucher_obj.make_gl_entries(repost_future_gle=False, allow_negative_stock=allow_negative_stock)
+					voucher_obj.make_gl_entries(repost_future_gle=False)
 		else:
 			_delete_gl_entries(voucher_type, voucher_no)
 
@@ -295,31 +288,3 @@
 	warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount
 		where account_type = 'Warehouse' and ifnull(master_name, '') != ''"""))
 	return warehouse_account
-
-def block_negative_stock(allow_negative_stock=False):
-	if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) and not allow_negative_stock:
-		if cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")):
-			frappe.throw(_("Negative stock is not allowed in case of Perpetual Inventory, please disable it from Stock Settings"))
-
-def get_valuation_rate(item_code, warehouse):
-	last_valuation_rate = frappe.db.sql("""select valuation_rate
-		from `tabStock Ledger Entry`
-		where item_code = %s and warehouse = %s
-		and ifnull(valuation_rate, 0) > 0
-		order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
-
-	if not last_valuation_rate:
-		last_valuation_rate = frappe.db.sql("""select valuation_rate
-			from `tabStock Ledger Entry`
-			where item_code = %s and ifnull(valuation_rate, 0) > 0
-			order by posting_date desc, posting_time desc, name desc limit 1""", item_code)
-
-	valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
-
-	if not valuation_rate:
-		valuation_rate = frappe.db.get_value("Item Price", {"item_code": item_code, "buying": 1}, "price_list_rate")
-
-	if not valuation_rate:
-		frappe.throw(_("Purchase rate for item: {0} not found, which is required to book accounting entry. Please mention item price against a buying price list.").format(item_code))
-
-	return valuation_rate
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 92f8395..d75a99c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -85,4 +85,5 @@
 execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance")
 execute:frappe.delete_doc("DocType", "Purchase Request")
 execute:frappe.delete_doc("DocType", "Purchase Request Item")
-erpnext.patches.v4_2.recalculate_bom_cost
\ No newline at end of file
+erpnext.patches.v4_2.recalculate_bom_cost
+erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
index 3c554ae..a7da864 100644
--- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
+++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
@@ -7,7 +7,7 @@
 
 def execute():
 	from erpnext.utilities.repost_stock import repost
-	repost()
+	repost(allow_zero_rate=True)
 
 	warehouse_account = frappe.db.sql("""select name, master_name from tabAccount
 		where ifnull(account_type, '') = 'Warehouse'""")
@@ -40,7 +40,7 @@
 					where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
 
 				voucher = frappe.get_doc(voucher_type, voucher_no)
-				voucher.make_gl_entries(repost_future_gle=False, allow_negative_stock=True)
+				voucher.make_gl_entries(repost_future_gle=False)
 				frappe.db.commit()
 			except Exception, e:
 				print frappe.get_traceback()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index ed4257f..13495a0 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -283,11 +283,8 @@
 	def get_rate(self,arg):
 		return frappe.get_doc('Purchase Common').get_rate(arg,self)
 
-	def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
+	def get_gl_entries(self, warehouse_account=None):
 		from erpnext.accounts.general_ledger import process_gl_map
-		from erpnext.controllers.stock_controller import block_negative_stock
-
-		# block_negative_stock(allow_negative_stock)
 
 		stock_rbnb = self.get_company_default("stock_received_but_not_billed")
 		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index b67090d..530ab9a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -198,12 +198,12 @@
 				"posting_time": self.posting_time
 			})
 
-	def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
+	def get_gl_entries(self, warehouse_account=None):
 		if not self.cost_center:
 			msgprint(_("Please enter Cost Center"), raise_exception=1)
 
 		return super(StockReconciliation, self).get_gl_entries(warehouse_account,
-			self.expense_account, self.cost_center, allow_negative_stock=allow_negative_stock)
+			self.expense_account, self.cost_center)
 
 	def validate_expense_account(self):
 		if not cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 95ace86..12ab0c9 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -12,9 +12,6 @@
 class StockSettings(Document):
 
 	def validate(self):
-		if cint(self.allow_negative_stock) and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
-			frappe.throw(_("Negative stock is not allowed in case of Perpetual Inventory"))
-
 		for key in ["item_naming_by", "item_group", "stock_uom", "allow_negative_stock"]:
 			frappe.db.set_default(key, self.get(key, ""))
 
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 7dc8d83..edbdb1a 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -6,7 +6,6 @@
 from frappe import _
 from frappe.utils import cint, flt, cstr, now
 from erpnext.stock.utils import get_valuation_method
-from erpnext.controllers.stock_controller import get_valuation_rate
 import json
 
 # future reposting
@@ -59,7 +58,7 @@
 	frappe.db.sql("""delete from `tabStock Ledger Entry`
 		where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
 
-def update_entries_after(args, verbose=1):
+def update_entries_after(args, allow_zero_rate=False, verbose=1):
 	"""
 		update valution rate and qty after transaction
 		from the current time-bucket onwards
@@ -107,9 +106,9 @@
 				stock_queue = [[qty_after_transaction, valuation_rate]]
 			else:
 				if valuation_method == "Moving Average":
-					valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate)
+					valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate)
 				else:
-					valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue)
+					valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate)
 
 
 				qty_after_transaction += flt(sle.actual_qty)
@@ -252,7 +251,7 @@
 
 	return valuation_rate
 
-def get_moving_average_values(qty_after_transaction, sle, valuation_rate):
+def get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate):
 	incoming_rate = flt(sle.incoming_rate)
 	actual_qty = flt(sle.actual_qty)
 
@@ -266,18 +265,19 @@
 
 		if new_stock_qty:
 			valuation_rate = new_stock_value / flt(new_stock_qty)
-	elif not valuation_rate:
-		valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse)
+	elif not valuation_rate and qty_after_transaction <= 0:
+		valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate)
 
 	return abs(flt(valuation_rate))
 
-def get_fifo_values(qty_after_transaction, sle, stock_queue):
+def get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate):
 	incoming_rate = flt(sle.incoming_rate)
 	actual_qty = flt(sle.actual_qty)
 
-	intialize_stock_queue(stock_queue, sle.item_code, sle.warehouse, actual_qty)
-
 	if actual_qty > 0:
+		if not stock_queue:
+			stock_queue.append([0, 0])
+
 		if stock_queue[-1][0] > 0:
 			stock_queue.append([actual_qty, incoming_rate])
 		else:
@@ -289,12 +289,12 @@
 	else:
 		qty_to_pop = abs(actual_qty)
 		while qty_to_pop:
-			intialize_stock_queue(stock_queue, sle.item_code, sle.warehouse, actual_qty)
+			if not stock_queue:
+				stock_queue.append([0, get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate)
+					if qty_after_transaction <= 0 else 0])
 
 			batch = stock_queue[0]
 
-			# print qty_to_pop, batch
-
 			if qty_to_pop >= batch[0]:
 				# consume current batch
 				qty_to_pop = qty_to_pop - batch[0]
@@ -318,11 +318,6 @@
 
 	return abs(valuation_rate)
 
-def intialize_stock_queue(stock_queue, item_code, warehouse, actual_qty):
-	if not stock_queue:
-		estimated_val_rate = get_valuation_rate(item_code, warehouse) if actual_qty < 0 else 0
-		stock_queue.append([0, estimated_val_rate])
-
 def _raise_exceptions(args, verbose=1):
 	deficiency = min(e["diff"] for e in _exceptions)
 	msg = _("Negative Stock Error ({6}) for Item {0} in Warehouse {1} on {2} {3} in {4} {5}").format(args["item_code"],
@@ -353,3 +348,26 @@
 		"timestamp(posting_date, posting_time) <= timestamp(%(posting_date)s, %(posting_time)s)"],
 		"desc", "limit 1", for_update=for_update)
 	return sle and sle[0] or {}
+
+def get_valuation_rate(item_code, warehouse, allow_zero_rate=False):
+	last_valuation_rate = frappe.db.sql("""select valuation_rate
+		from `tabStock Ledger Entry`
+		where item_code = %s and warehouse = %s
+		and ifnull(valuation_rate, 0) > 0
+		order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
+
+	if not last_valuation_rate:
+		last_valuation_rate = frappe.db.sql("""select valuation_rate
+			from `tabStock Ledger Entry`
+			where item_code = %s and ifnull(valuation_rate, 0) > 0
+			order by posting_date desc, posting_time desc, name desc limit 1""", item_code)
+
+	valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
+
+	if not valuation_rate:
+		valuation_rate = frappe.db.get_value("Item Price", {"item_code": item_code, "buying": 1}, "price_list_rate")
+
+	if not allow_zero_rate and not valuation_rate and cint(frappe.db.get_value("Accounts Settings", None, "auto_accounting_for_stock")):
+		frappe.throw(_("Purchase rate for item: {0} not found, which is required to book accounting entry (expense). Please mention item price against a buying price list.").format(item_code))
+
+	return valuation_rate
diff --git a/erpnext/utilities/repost_stock.py b/erpnext/utilities/repost_stock.py
index 89494ad..4be9591 100644
--- a/erpnext/utilities/repost_stock.py
+++ b/erpnext/utilities/repost_stock.py
@@ -9,7 +9,7 @@
 from erpnext.stock.stock_ledger import update_entries_after
 from erpnext.accounts.utils import get_fiscal_year
 
-def repost(allow_negative_stock=False):
+def repost(allow_negative_stock=False, allow_zero_rate=False):
 	"""
 	Repost everything!
 	"""
@@ -23,7 +23,7 @@
 		union
 		select item_code, warehouse from `tabStock Ledger Entry`) a"""):
 			try:
-				repost_stock(d[0], d[1])
+				repost_stock(d[0], d[1], allow_zero_rate)
 				frappe.db.commit()
 			except:
 				frappe.db.rollback()
@@ -33,8 +33,8 @@
 			frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
 	frappe.db.auto_commit_on_many_writes = 0
 
-def repost_stock(item_code, warehouse):
-	repost_actual_qty(item_code, warehouse)
+def repost_stock(item_code, warehouse, allow_zero_rate=False):
+	repost_actual_qty(item_code, warehouse, allow_zero_rate)
 
 	if item_code and warehouse:
 		update_bin_qty(item_code, warehouse, {
@@ -44,9 +44,9 @@
 			"planned_qty": get_planned_qty(item_code, warehouse)
 		})
 
-def repost_actual_qty(item_code, warehouse):
+def repost_actual_qty(item_code, warehouse, allow_zero_rate=False):
 	try:
-		update_entries_after({ "item_code": item_code, "warehouse": warehouse })
+		update_entries_after({ "item_code": item_code, "warehouse": warehouse }, allow_zero_rate)
 	except:
 		pass