diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7c895bbdcf..d5f2ccb2ca 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -91,3 +91,4 @@ erpnext.patches.v4_2.update_requested_and_ordered_qty 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 147a3b1d2c..54c1d8f3af 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -42,9 +42,13 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten 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 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten // 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 a9ace56d11..6107778d57 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 f78c0a7bbf..ec77cf3720 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -73,7 +73,7 @@ class MaterialRequest(BuyingController): 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 @@ class MaterialRequest(BuyingController): 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 @@ def make_supplier_quotation(source_name, target_doc=None): @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 @@ def make_stock_entry(source_name, target_doc=None): "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 @@ def make_stock_entry(source_name, target_doc=None): "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 ab1d3ccd95..0b23af057c 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 @@ class TestMaterialRequest(unittest.TestCase): 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 @@ class TestMaterialRequest(unittest.TestCase): "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 @@ class TestMaterialRequest(unittest.TestCase): "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 @@ class TestMaterialRequest(unittest.TestCase): # 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 @@ class TestMaterialRequest(unittest.TestCase): # 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 @@ class TestMaterialRequest(unittest.TestCase): 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 @@ class TestMaterialRequest(unittest.TestCase): 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 @@ class TestMaterialRequest(unittest.TestCase): 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 d06a761075..f0af283d99 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -219,7 +219,7 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): 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 eae1bf68bf..62cc397ef9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -87,7 +87,7 @@ def update_entries_after(args, allow_zero_rate=False, verbose=1): 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):