diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 7bcab314e1a..ce5affdfdbe 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -19,6 +19,7 @@ from erpnext.stock.doctype.material_request.material_request import ( make_supplier_quotation, raise_work_orders, ) +from erpnext.stock.doctype.stock_entry.stock_entry import make_stock_in_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -926,6 +927,49 @@ class TestMaterialRequest(FrappeTestCase): pl_for_pending = create_pick_list(mr.name) self.assertEqual(pl_for_pending.locations[0].qty, 5) + def test_mr_status_with_partial_and_excess_end_transit(self): + material_request = make_material_request( + material_request_type="Material Transfer", + item_code="_Test Item Home Desktop 100", + ) + + in_transit_wh = get_in_transit_warehouse(material_request.company) + + # Make sure stock is available in source warehouse + self._insert_stock_entry(20.0, 20.0) + + # Stock Entry (Transfer to In-Transit) + stock_entry_1 = make_in_transit_stock_entry(material_request.name, in_transit_wh) + stock_entry_1.items[0].update( + { + "qty": 5, + "s_warehouse": "_Test Warehouse 1 - _TC", + } + ) + stock_entry_1.save().submit() + + stock_entry_2 = make_in_transit_stock_entry(material_request.name, in_transit_wh) + stock_entry_2.items[0].update( + { + "qty": 5, + "s_warehouse": "_Test Warehouse 1 - _TC", + } + ) + stock_entry_2.save().submit() + + end_transit_1 = make_stock_in_entry(stock_entry_1.name) + end_transit_1.save().submit() + + # Material Request Transfer Status should still be In Transit + material_request.load_from_db() + self.assertEqual(material_request.transfer_status, "In Transit") + + end_transit_2 = make_stock_in_entry(stock_entry_2.name) + end_transit_2.items[0].update({"qty": 6}) # More than transferred + end_transit_2.save() + + self.assertRaises(frappe.ValidationError, end_transit_2.submit) + def get_in_transit_warehouse(company): if not frappe.db.exists("Warehouse Type", "Transit"): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e3e7c461ef7..a441664ab9c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -8,6 +8,7 @@ from collections import defaultdict import frappe from frappe import _, bold from frappe.model.mapper import get_mapped_doc +from frappe.query_builder import DocType from frappe.query_builder.functions import Sum from frappe.utils import ( cint, @@ -2815,6 +2816,17 @@ class StockEntry(StockController): }, ) + if d.docstatus == 1: + transfer_qty = frappe.get_value("Stock Entry Detail", d.ste_detail, "transfer_qty") + + if transferred_qty and transferred_qty[0]: + if transferred_qty[0].qty > transfer_qty: + frappe.throw( + _( + "Row {0}: Transferred quantity cannot be greater than the requested quantity." + ).format(d.idx) + ) + stock_entries[(d.against_stock_entry, d.ste_detail)] = ( transferred_qty[0].qty if transferred_qty and transferred_qty[0] else 0.0 ) or 0.0 @@ -2878,7 +2890,7 @@ class StockEntry(StockController): parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, "add_to_transit") for item in self.items: - material_request = item.material_request or None + material_request = item.get("material_request") if self.purpose == "Material Transfer" and material_request not in material_requests: if self.outgoing_stock_entry and parent_se: material_request = frappe.get_value( @@ -2887,6 +2899,11 @@ class StockEntry(StockController): if material_request and material_request not in material_requests: material_requests.append(material_request) + if status == "Completed": + qty = get_transferred_qty(material_request) + if qty.get("transfer_qty") > qty.get("transferred_qty"): + status = "In Transit" + frappe.db.set_value("Material Request", material_request, "transfer_status", status) def set_serial_no_batch_for_finished_good(self): @@ -3545,3 +3562,18 @@ def get_batchwise_serial_nos(item_code, row): batchwise_serial_nos[batch_no] = sorted([serial_no.name for serial_no in serial_nos]) return batchwise_serial_nos + + +def get_transferred_qty(material_request): + sed = DocType("Stock Entry Detail") + + query = ( + frappe.qb.from_(sed) + .select( + Sum(sed.transfer_qty).as_("transfer_qty"), + Sum(sed.transferred_qty).as_("transferred_qty"), + ) + .where((sed.material_request == material_request) & (sed.docstatus == 1)) + ).run(as_dict=True) + + return query[0]