* fix: do not consider rejected warehouses in pick list (backport #39539) (#39804) * fix: do not consider rejected warehouses in pick list (#39539) * fix: do not picked rejected materials * test: test case for pick list without rejected materials (cherry picked from commit f6725e43425043eaba7dcdd3cf3768a857a39ee6) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py # erpnext/stock/doctype/pick_list/pick_list.json # erpnext/stock/doctype/pick_list/pick_list.py * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fixed test case --------- Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> (cherry picked from commit 2c8e4c1ab3bde6a9ab37a41d2801c447d76903ef) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py * chore: fix conflicts --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
parent
0bde22fe46
commit
30daccecc7
@ -20,6 +20,7 @@ from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_
|
|||||||
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import (
|
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||||
WarehouseRequired,
|
WarehouseRequired,
|
||||||
|
create_pick_list,
|
||||||
make_delivery_note,
|
make_delivery_note,
|
||||||
make_material_request,
|
make_material_request,
|
||||||
make_raw_material_request,
|
make_raw_material_request,
|
||||||
@ -1973,6 +1974,83 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
self.assertEqual(so.items[0].rate, scenario.get("expected_rate"))
|
self.assertEqual(so.items[0].rate, scenario.get("expected_rate"))
|
||||||
self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate"))
|
self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate"))
|
||||||
|
|
||||||
|
def test_pick_list_without_rejected_materials(self):
|
||||||
|
serial_and_batch_item = make_item(
|
||||||
|
"_Test Serial and Batch Item for Rejected Materials",
|
||||||
|
properties={
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BAT-TSBIFRM-.#####",
|
||||||
|
"serial_no_series": "SN-TSBIFRM-.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
serial_item = make_item(
|
||||||
|
"_Test Serial Item for Rejected Materials",
|
||||||
|
properties={
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "SN-TSIFRM-.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
batch_item = make_item(
|
||||||
|
"_Test Batch Item for Rejected Materials",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BAT-TBIFRM-.#####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
normal_item = make_item("_Test Normal Item for Rejected Materials").name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
rejected_warehouse = "_Test Dummy Rejected Warehouse - _TC"
|
||||||
|
|
||||||
|
if not frappe.db.exists("Warehouse", rejected_warehouse):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Warehouse",
|
||||||
|
"warehouse_name": rejected_warehouse,
|
||||||
|
"company": "_Test Company",
|
||||||
|
"warehouse_group": "_Test Warehouse Group",
|
||||||
|
"is_rejected_warehouse": 1,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
se = make_stock_entry(item_code=normal_item, qty=1, to_warehouse=warehouse, do_not_submit=True)
|
||||||
|
for item in [serial_and_batch_item, serial_item, batch_item]:
|
||||||
|
se.append("items", {"item_code": item, "qty": 1, "t_warehouse": warehouse})
|
||||||
|
|
||||||
|
se.save()
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=normal_item, qty=1, to_warehouse=rejected_warehouse, do_not_submit=True
|
||||||
|
)
|
||||||
|
for item in [serial_and_batch_item, serial_item, batch_item]:
|
||||||
|
se.append("items", {"item_code": item, "qty": 1, "t_warehouse": rejected_warehouse})
|
||||||
|
|
||||||
|
se.save()
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
so = make_sales_order(item_code=normal_item, qty=2, do_not_submit=True)
|
||||||
|
|
||||||
|
for item in [serial_and_batch_item, serial_item, batch_item]:
|
||||||
|
so.append("items", {"item_code": item, "qty": 2, "warehouse": warehouse})
|
||||||
|
|
||||||
|
so.save()
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
pick_list = create_pick_list(so.name)
|
||||||
|
|
||||||
|
pick_list.save()
|
||||||
|
for row in pick_list.locations:
|
||||||
|
self.assertEqual(row.qty, 1.0)
|
||||||
|
self.assertFalse(row.warehouse == rejected_warehouse)
|
||||||
|
self.assertTrue(row.warehouse == warehouse)
|
||||||
|
|
||||||
|
|
||||||
def automatically_fetch_payment_terms(enable=1):
|
def automatically_fetch_payment_terms(enable=1):
|
||||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"for_qty",
|
"for_qty",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"parent_warehouse",
|
"parent_warehouse",
|
||||||
|
"consider_rejected_warehouses",
|
||||||
"get_item_locations",
|
"get_item_locations",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
@ -184,11 +185,18 @@
|
|||||||
"report_hide": 1,
|
"report_hide": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Enable it if users want to consider rejected materials to dispatch.",
|
||||||
|
"fieldname": "consider_rejected_warehouses",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Consider Rejected Warehouses"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-02-01 16:17:44.877426",
|
"modified": "2024-02-02 16:17:44.877426",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Pick List",
|
"name": "Pick List",
|
||||||
|
|||||||
@ -369,6 +369,7 @@ class PickList(Document):
|
|||||||
self.item_count_map.get(item_code),
|
self.item_count_map.get(item_code),
|
||||||
self.company,
|
self.company,
|
||||||
picked_item_details=picked_items_details.get(item_code),
|
picked_item_details=picked_items_details.get(item_code),
|
||||||
|
consider_rejected_warehouses=self.consider_rejected_warehouses,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -710,6 +711,7 @@ def get_available_item_locations(
|
|||||||
company,
|
company,
|
||||||
ignore_validation=False,
|
ignore_validation=False,
|
||||||
picked_item_details=None,
|
picked_item_details=None,
|
||||||
|
consider_rejected_warehouses=False,
|
||||||
):
|
):
|
||||||
locations = []
|
locations = []
|
||||||
total_picked_qty = (
|
total_picked_qty = (
|
||||||
@ -725,18 +727,34 @@ def get_available_item_locations(
|
|||||||
required_qty,
|
required_qty,
|
||||||
company,
|
company,
|
||||||
total_picked_qty,
|
total_picked_qty,
|
||||||
|
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||||
)
|
)
|
||||||
elif has_serial_no:
|
elif has_serial_no:
|
||||||
locations = get_available_item_locations_for_serialized_item(
|
locations = get_available_item_locations_for_serialized_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
item_code,
|
||||||
|
from_warehouses,
|
||||||
|
required_qty,
|
||||||
|
company,
|
||||||
|
total_picked_qty,
|
||||||
|
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||||
)
|
)
|
||||||
elif has_batch_no:
|
elif has_batch_no:
|
||||||
locations = get_available_item_locations_for_batched_item(
|
locations = get_available_item_locations_for_batched_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
item_code,
|
||||||
|
from_warehouses,
|
||||||
|
required_qty,
|
||||||
|
company,
|
||||||
|
total_picked_qty,
|
||||||
|
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
locations = get_available_item_locations_for_other_item(
|
locations = get_available_item_locations_for_other_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
item_code,
|
||||||
|
from_warehouses,
|
||||||
|
required_qty,
|
||||||
|
company,
|
||||||
|
total_picked_qty,
|
||||||
|
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||||
)
|
)
|
||||||
|
|
||||||
total_qty_available = sum(location.get("qty") for location in locations)
|
total_qty_available = sum(location.get("qty") for location in locations)
|
||||||
@ -775,6 +793,7 @@ def get_available_item_locations_for_serial_and_batched_item(
|
|||||||
required_qty,
|
required_qty,
|
||||||
company,
|
company,
|
||||||
total_picked_qty=0,
|
total_picked_qty=0,
|
||||||
|
consider_rejected_warehouses=False,
|
||||||
):
|
):
|
||||||
# Get batch nos by FIFO
|
# Get batch nos by FIFO
|
||||||
locations = get_available_item_locations_for_batched_item(
|
locations = get_available_item_locations_for_batched_item(
|
||||||
@ -782,6 +801,7 @@ def get_available_item_locations_for_serial_and_batched_item(
|
|||||||
from_warehouses,
|
from_warehouses,
|
||||||
required_qty,
|
required_qty,
|
||||||
company,
|
company,
|
||||||
|
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||||
)
|
)
|
||||||
|
|
||||||
if locations:
|
if locations:
|
||||||
@ -811,7 +831,12 @@ def get_available_item_locations_for_serial_and_batched_item(
|
|||||||
|
|
||||||
|
|
||||||
def get_available_item_locations_for_serialized_item(
|
def get_available_item_locations_for_serialized_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
item_code,
|
||||||
|
from_warehouses,
|
||||||
|
required_qty,
|
||||||
|
company,
|
||||||
|
total_picked_qty=0,
|
||||||
|
consider_rejected_warehouses=False,
|
||||||
):
|
):
|
||||||
picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
|
picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
|
||||||
|
|
||||||
@ -828,6 +853,10 @@ def get_available_item_locations_for_serialized_item(
|
|||||||
else:
|
else:
|
||||||
query = query.where(Coalesce(sn.warehouse, "") != "")
|
query = query.where(Coalesce(sn.warehouse, "") != "")
|
||||||
|
|
||||||
|
if not consider_rejected_warehouses:
|
||||||
|
if rejected_warehouses := get_rejected_warehouses():
|
||||||
|
query = query.where(sn.warehouse.notin(rejected_warehouses))
|
||||||
|
|
||||||
serial_nos = query.run(as_list=True)
|
serial_nos = query.run(as_list=True)
|
||||||
|
|
||||||
warehouse_serial_nos_map = frappe._dict()
|
warehouse_serial_nos_map = frappe._dict()
|
||||||
@ -860,7 +889,12 @@ def get_available_item_locations_for_serialized_item(
|
|||||||
|
|
||||||
|
|
||||||
def get_available_item_locations_for_batched_item(
|
def get_available_item_locations_for_batched_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
item_code,
|
||||||
|
from_warehouses,
|
||||||
|
required_qty,
|
||||||
|
company,
|
||||||
|
total_picked_qty=0,
|
||||||
|
consider_rejected_warehouses=False,
|
||||||
):
|
):
|
||||||
locations = []
|
locations = []
|
||||||
data = get_auto_batch_nos(
|
data = get_auto_batch_nos(
|
||||||
@ -875,7 +909,14 @@ def get_available_item_locations_for_batched_item(
|
|||||||
)
|
)
|
||||||
|
|
||||||
warehouse_wise_batches = frappe._dict()
|
warehouse_wise_batches = frappe._dict()
|
||||||
|
rejected_warehouses = get_rejected_warehouses()
|
||||||
|
|
||||||
for d in data:
|
for d in data:
|
||||||
|
if (
|
||||||
|
not consider_rejected_warehouses and rejected_warehouses and d.warehouse in rejected_warehouses
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
if d.warehouse not in warehouse_wise_batches:
|
if d.warehouse not in warehouse_wise_batches:
|
||||||
warehouse_wise_batches.setdefault(d.warehouse, defaultdict(float))
|
warehouse_wise_batches.setdefault(d.warehouse, defaultdict(float))
|
||||||
|
|
||||||
@ -898,7 +939,12 @@ def get_available_item_locations_for_batched_item(
|
|||||||
|
|
||||||
|
|
||||||
def get_available_item_locations_for_other_item(
|
def get_available_item_locations_for_other_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
item_code,
|
||||||
|
from_warehouses,
|
||||||
|
required_qty,
|
||||||
|
company,
|
||||||
|
total_picked_qty=0,
|
||||||
|
consider_rejected_warehouses=False,
|
||||||
):
|
):
|
||||||
bin = frappe.qb.DocType("Bin")
|
bin = frappe.qb.DocType("Bin")
|
||||||
query = (
|
query = (
|
||||||
@ -915,6 +961,10 @@ def get_available_item_locations_for_other_item(
|
|||||||
wh = frappe.qb.DocType("Warehouse")
|
wh = frappe.qb.DocType("Warehouse")
|
||||||
query = query.from_(wh).where((bin.warehouse == wh.name) & (wh.company == company))
|
query = query.from_(wh).where((bin.warehouse == wh.name) & (wh.company == company))
|
||||||
|
|
||||||
|
if not consider_rejected_warehouses:
|
||||||
|
if rejected_warehouses := get_rejected_warehouses():
|
||||||
|
query = query.where(bin.warehouse.notin(rejected_warehouses))
|
||||||
|
|
||||||
item_locations = query.run(as_dict=True)
|
item_locations = query.run(as_dict=True)
|
||||||
|
|
||||||
return item_locations
|
return item_locations
|
||||||
@ -1236,3 +1286,15 @@ def update_common_item_properties(item, location):
|
|||||||
item.serial_no = location.serial_no
|
item.serial_no = location.serial_no
|
||||||
item.batch_no = location.batch_no
|
item.batch_no = location.batch_no
|
||||||
item.material_request_item = location.material_request_item
|
item.material_request_item = location.material_request_item
|
||||||
|
|
||||||
|
|
||||||
|
def get_rejected_warehouses():
|
||||||
|
if not hasattr(frappe.local, "rejected_warehouses"):
|
||||||
|
frappe.local.rejected_warehouses = []
|
||||||
|
|
||||||
|
if not frappe.local.rejected_warehouses:
|
||||||
|
frappe.local.rejected_warehouses = frappe.get_all(
|
||||||
|
"Warehouse", filters={"is_rejected_warehouse": 1}, pluck="name"
|
||||||
|
)
|
||||||
|
|
||||||
|
return frappe.local.rejected_warehouses
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"column_break_3",
|
"column_break_3",
|
||||||
"is_group",
|
"is_group",
|
||||||
"parent_warehouse",
|
"parent_warehouse",
|
||||||
|
"is_rejected_warehouse",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"account",
|
"account",
|
||||||
"company",
|
"company",
|
||||||
@ -249,13 +250,20 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_qajx",
|
"fieldname": "column_break_qajx",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "If yes, then this warehouse will be used to store rejected materials",
|
||||||
|
"fieldname": "is_rejected_warehouse",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Rejected Warehouse"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-building",
|
"icon": "fa fa-building",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-29 13:10:43.333160",
|
"modified": "2024-01-24 16:27:28.299520",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Warehouse",
|
"name": "Warehouse",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user