Merge pull request #2329 from neilLasrado/material-issue

Material Issue added to Material Request #2320
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7c895bb..d5f2ccb 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -91,3 +91,4 @@
 erpnext.patches.v4_2.party_model
 erpnext.patches.v5_0.update_frozen_accounts_permission_role
 erpnext.patches.v5_0.update_dn_against_doc_fields
+execute:frappe.db.sql("update `tabMaterial Request` set material_request_type = 'Material Transfer' where material_request_type = 'Transfer'")
\ No newline at end of file
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 147a3b1..54c1d8f 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -42,9 +42,13 @@
 					this.make_supplier_quotation,
 						frappe.boot.doctype_icons["Supplier Quotation"]);
 
-			if(doc.material_request_type === "Transfer" && doc.status === "Submitted")
+			if(doc.material_request_type === "Material Transfer" && doc.status === "Submitted")
 				cur_frm.add_custom_button(__("Transfer Material"), this.make_stock_entry,
 					frappe.boot.doctype_icons["Stock Entry"]);
+		
+			if(doc.material_request_type === "Material Issue" && doc.status === "Submitted")
+				cur_frm.add_custom_button(__("Issue Material"), this.make_stock_entry,
+					frappe.boot.doctype_icons["Stock Entry"]);
 
 			if(flt(doc.per_ordered, 2) < 100) {
 				if(doc.material_request_type === "Purchase")
@@ -165,7 +169,7 @@
 // for backward compatibility: combine new and previous states
 $.extend(cur_frm.cscript, new erpnext.buying.MaterialRequestController({frm: cur_frm}));
 
-cur_frm.cscript.qty = function(doc, cdt, cdn) {
+cur_frm.cscript.qty = function(cdt, cdn) {
 	var d = locals[cdt][cdn];
 	if (flt(d.qty) < flt(d.min_order_qty))
 		alert(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index a9ace56..6107778 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -17,7 +17,7 @@
    "fieldtype": "Select", 
    "in_list_view": 1, 
    "label": "Type", 
-   "options": "Purchase\nTransfer", 
+   "options": "Purchase\nMaterial Transfer\nMaterial Issue", 
    "permlevel": 0, 
    "reqd": 1
   }, 
@@ -235,7 +235,7 @@
  "icon": "icon-ticket", 
  "idx": 1, 
  "is_submittable": 1, 
- "modified": "2014-09-09 05:35:31.735821", 
+ "modified": "2014-10-27 12:16:38.833386", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Material Request", 
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index f78c0a7..ec77cf3 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -73,7 +73,7 @@
 		from erpnext.utilities import validate_status
 		validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"])
 
-		self.validate_value("material_request_type", "in", ["Purchase", "Transfer"])
+		self.validate_value("material_request_type", "in", ["Purchase", "Material Transfer", "Material Issue"])
 
 		pc_obj = frappe.get_doc('Purchase Common')
 		pc_obj.validate_for_items(self)
@@ -112,7 +112,7 @@
 		frappe.db.set(self,'status','Cancelled')
 
 	def update_completed_qty(self, mr_items=None):
-		if self.material_request_type != "Transfer":
+		if self.material_request_type == "Purchase":
 			return
 
 		item_doclist = self.get("indent_details")
@@ -294,12 +294,19 @@
 @frappe.whitelist()
 def make_stock_entry(source_name, target_doc=None):
 	def update_item(obj, target, source_parent):
+		qty = flt(obj.qty) - flt(obj.ordered_qty) \
+			if flt(obj.qty) > flt(obj.ordered_qty) else 0
+		target.qty = qty
+		target.transfer_qty = qty
 		target.conversion_factor = 1
-		target.qty = flt(obj.qty) - flt(obj.ordered_qty)
-		target.transfer_qty = flt(obj.qty) - flt(obj.ordered_qty)
+		
+		if source_parent.material_request_type == "Material Transfer":
+			target.t_warehouse = obj.warehouse
+		else:
+			target.s_warehouse = obj.warehouse
 
 	def set_missing_values(source, target):
-		target.purpose = "Material Transfer"
+		target.purpose = source.material_request_type
 		target.run_method("get_stock_and_rate")
 
 	doclist = get_mapped_doc("Material Request", source_name, {
@@ -307,7 +314,7 @@
 			"doctype": "Stock Entry",
 			"validation": {
 				"docstatus": ["=", 1],
-				"material_request_type": ["=", "Transfer"]
+				"material_request_type": ["in", ["Material Transfer", "Material Issue"]]
 			}
 		},
 		"Material Request Item": {
@@ -316,7 +323,6 @@
 				"name": "material_request_item",
 				"parent": "material_request",
 				"uom": "stock_uom",
-				"warehouse": "t_warehouse"
 			},
 			"postprocess": update_item
 		}
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index ab1d3cc..0b23af0 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -51,14 +51,14 @@
 			mr.name)
 
 		mr = frappe.get_doc("Material Request", mr.name)
-		mr.material_request_type = "Transfer"
+		mr.material_request_type = "Material Transfer"
 		mr.submit()
 		se = make_stock_entry(mr.name)
 
 		self.assertEquals(se.doctype, "Stock Entry")
 		self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details")))
 
-	def _insert_stock_entry(self, qty1, qty2):
+	def _insert_stock_entry(self, qty1, qty2, warehouse = None ):
 		se = frappe.get_doc({
 				"company": "_Test Company",
 				"doctype": "Stock Entry",
@@ -77,7 +77,7 @@
 						"stock_uom": "_Test UOM 1",
 						"transfer_qty": qty1,
 						"uom": "_Test UOM 1",
-						"t_warehouse": "_Test Warehouse 1 - _TC",
+						"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
 					},
 					{
 						"conversion_factor": 1.0,
@@ -89,7 +89,7 @@
 						"stock_uom": "_Test UOM 1",
 						"transfer_qty": qty2,
 						"uom": "_Test UOM 1",
-						"t_warehouse": "_Test Warehouse 1 - _TC",
+						"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
 					}
 				]
 			})
@@ -168,7 +168,7 @@
 
 		# submit material request of type Purchase
 		mr = frappe.copy_doc(test_records[0])
-		mr.material_request_type = "Transfer"
+		mr.material_request_type = "Material Transfer"
 		mr.insert()
 		mr.submit()
 
@@ -257,7 +257,7 @@
 
 		# submit material request of type Purchase
 		mr = frappe.copy_doc(test_records[0])
-		mr.material_request_type = "Transfer"
+		mr.material_request_type = "Material Transfer"
 		mr.insert()
 		mr.submit()
 
@@ -330,9 +330,9 @@
 		self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
 
 	def test_incorrect_mapping_of_stock_entry(self):
-		# submit material request of type Purchase
+		# submit material request of type Transfer
 		mr = frappe.copy_doc(test_records[0])
-		mr.material_request_type = "Transfer"
+		mr.material_request_type = "Material Transfer"
 		mr.insert()
 		mr.submit()
 
@@ -363,6 +363,17 @@
 		se = frappe.copy_doc(se_doc)
 		self.assertRaises(frappe.MappingMismatchError, se.insert)
 
+		# submit material request of type Transfer
+		mr = frappe.copy_doc(test_records[0])
+		mr.material_request_type = "Material Issue"
+		mr.insert()
+		mr.submit()
+
+		# map a stock entry
+		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
+		se_doc = make_stock_entry(mr.name)
+		self.assertEquals(se_doc.get("mtn_details")[0].s_warehouse, "_Test Warehouse - _TC")
+
 	def test_warehouse_company_validation(self):
 		from erpnext.stock.utils import InvalidWarehouseCompany
 		mr = frappe.copy_doc(test_records[0])
@@ -372,6 +383,56 @@
 	def _get_requested_qty(self, item_code, warehouse):
 		return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
 
+	def test_make_stock_entry_for_Material_Issue(self):
+		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
+
+		mr = frappe.copy_doc(test_records[0]).insert()
+
+		self.assertRaises(frappe.ValidationError, make_stock_entry,
+			mr.name)
+
+		mr = frappe.get_doc("Material Request", mr.name)
+		mr.material_request_type = "Material Issue"
+		mr.submit()
+		se = make_stock_entry(mr.name)
+
+		self.assertEquals(se.doctype, "Stock Entry")
+		self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details")))
+
+	def test_compleated_qty_for_issue(self):
+		def _get_requested_qty():
+			return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
+				"warehouse": "_Test Warehouse - _TC"}, "indented_qty"))
+
+		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
+
+		existing_requested_qty = _get_requested_qty()
+
+		mr = frappe.copy_doc(test_records[0])
+		mr.material_request_type = "Material Issue"
+		mr.submit()
+
+		#testing bin value after material request is submitted 
+		self.assertEquals(_get_requested_qty(), existing_requested_qty + 54.0)
+
+		# receive items to allow issue
+		self._insert_stock_entry(60, 6, "_Test Warehouse - _TC")
+
+		# make stock entry against MR
+
+		se_doc = make_stock_entry(mr.name)
+		se_doc.fiscal_year = "_Test Fiscal Year 2014"
+		se_doc.get("mtn_details")[0].qty = 60.0
+		se_doc.insert()
+		se_doc.submit()
+
+		# check if per complete is as expected
+		mr.load_from_db()
+		self.assertEquals(mr.get("indent_details")[0].ordered_qty, 60.0)
+		self.assertEquals(mr.get("indent_details")[1].ordered_qty, 3.0)
+
+		#testing bin requested qty after issuing stock against material request 
+		self.assertEquals(_get_requested_qty(), existing_requested_qty)
 
 test_dependencies = ["Currency Exchange"]
 test_records = frappe.get_test_records('Material Request')
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d06a761..f0af283 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -219,7 +219,7 @@
 		if not self.posting_date or not self.posting_time:
 			frappe.throw(_("Posting date and posting time is mandatory"))
 
-		allow_negative_stock = cint(frappe.db.get_default("allow_negative_stock"))
+		allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
 
 		for d in self.get('mtn_details'):
 			d.transfer_qty = flt(d.transfer_qty)
@@ -625,7 +625,8 @@
 				mreq_item = frappe.db.get_value("Material Request Item",
 					{"name": item.material_request_item, "parent": item.material_request},
 					["item_code", "warehouse", "idx"], as_dict=True)
-				if mreq_item.item_code != item.item_code or mreq_item.warehouse != item.t_warehouse:
+				if mreq_item.item_code != item.item_code or \
+				mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse):
 					frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx),
 						frappe.MappingMismatchError)
 
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index eae1bf6..62cc397 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -87,7 +87,7 @@
 	stock_value_difference = 0.0
 
 	for sle in entries_to_fix:
-		if sle.serial_no or not cint(frappe.db.get_default("allow_negative_stock")):
+		if sle.serial_no or not cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")):
 			# validate negative stock for serialized items, fifo valuation
 			# or when negative stock is not allowed for moving average
 			if not validate_negative_stock(qty_after_transaction, sle):