mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-01 23:53:21 +00:00
fix(stock): correct reservation and pick list quantities (#58613)
This commit is contained in:
@@ -1310,9 +1310,9 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
|
|||||||
# if extra quantity is available push current warehouse to available locations
|
# if extra quantity is available push current warehouse to available locations
|
||||||
if qty_diff > 0:
|
if qty_diff > 0:
|
||||||
item_location.qty = qty_diff
|
item_location.qty = qty_diff
|
||||||
if item_location.serial_no:
|
if item_location.serial_nos:
|
||||||
# set remaining serial numbers
|
# set remaining serial numbers
|
||||||
item_location.serial_no = item_location.serial_no[-int(qty_diff) :]
|
item_location.serial_nos = item_location.serial_nos[-int(qty_diff) :]
|
||||||
available_locations = [item_location, *available_locations]
|
available_locations = [item_location, *available_locations]
|
||||||
|
|
||||||
# update available locations for the item
|
# update available locations for the item
|
||||||
@@ -1456,13 +1456,16 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list
|
|||||||
filterd_locations.append(row)
|
filterd_locations.append(row)
|
||||||
continue
|
continue
|
||||||
if picked_qty > row.qty:
|
if picked_qty > row.qty:
|
||||||
row.qty = 0
|
|
||||||
picked_item_details[key]["picked_qty"] -= row.qty
|
picked_item_details[key]["picked_qty"] -= row.qty
|
||||||
|
row.qty = 0
|
||||||
else:
|
else:
|
||||||
row.qty -= picked_qty
|
row.qty -= picked_qty
|
||||||
picked_item_details[key]["picked_qty"] = 0.0
|
picked_item_details[key]["picked_qty"] = 0.0
|
||||||
if row.serial_nos:
|
if row.serial_nos:
|
||||||
row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no")))
|
picked_serial_nos = set(picked_item_details[key].get("serial_no") or [])
|
||||||
|
row.serial_nos = [
|
||||||
|
serial_no for serial_no in row.serial_nos if serial_no not in picked_serial_nos
|
||||||
|
]
|
||||||
|
|
||||||
if flt(row.qty, precision) > 0:
|
if flt(row.qty, precision) > 0:
|
||||||
filterd_locations.append(row)
|
filterd_locations.append(row)
|
||||||
|
|||||||
@@ -29,6 +29,61 @@ from erpnext.tests.utils import ERPNextTestSuite
|
|||||||
|
|
||||||
|
|
||||||
class TestPickList(ERPNextTestSuite):
|
class TestPickList(ERPNextTestSuite):
|
||||||
|
def test_filter_locations_consumes_picked_qty_across_rows(self):
|
||||||
|
from erpnext.stock.doctype.pick_list.pick_list import filter_locations_by_picked_materials
|
||||||
|
|
||||||
|
key = ("Test Warehouse", "Test Batch")
|
||||||
|
locations = [
|
||||||
|
_dict(warehouse=key[0], batch_no=key[1], qty=5),
|
||||||
|
_dict(warehouse=key[0], batch_no=key[1], qty=5),
|
||||||
|
]
|
||||||
|
picked_item_details = {key: {"picked_qty": 7}}
|
||||||
|
|
||||||
|
filtered_locations = filter_locations_by_picked_materials(locations, picked_item_details)
|
||||||
|
|
||||||
|
self.assertEqual(len(filtered_locations), 1)
|
||||||
|
self.assertEqual(filtered_locations[0].qty, 3)
|
||||||
|
self.assertEqual(picked_item_details[key]["picked_qty"], 0)
|
||||||
|
|
||||||
|
def test_filter_locations_preserves_serial_order(self):
|
||||||
|
from erpnext.stock.doctype.pick_list.pick_list import filter_locations_by_picked_materials
|
||||||
|
|
||||||
|
warehouse = "Test Warehouse"
|
||||||
|
locations = [
|
||||||
|
_dict(
|
||||||
|
warehouse=warehouse,
|
||||||
|
batch_no=None,
|
||||||
|
qty=4,
|
||||||
|
serial_nos=["SN-1", "SN-2", "SN-3", "SN-4"],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
picked_item_details = {warehouse: {"picked_qty": 2, "serial_no": ["SN-2", "SN-4"]}}
|
||||||
|
|
||||||
|
filtered_locations = filter_locations_by_picked_materials(locations, picked_item_details)
|
||||||
|
|
||||||
|
self.assertEqual(filtered_locations[0].serial_nos, ["SN-1", "SN-3"])
|
||||||
|
|
||||||
|
def test_get_items_with_location_trims_allocated_serial_nos(self):
|
||||||
|
from erpnext.stock.doctype.pick_list.pick_list import get_items_with_location_and_quantity
|
||||||
|
|
||||||
|
item = _dict(item_code="Test Serial Item", qty=2, stock_qty=2, conversion_factor=1, uom="Nos")
|
||||||
|
item_location_map = {
|
||||||
|
item.item_code: [
|
||||||
|
_dict(
|
||||||
|
warehouse="Test Warehouse",
|
||||||
|
batch_no=None,
|
||||||
|
qty=4,
|
||||||
|
serial_nos=["SN-1", "SN-2", "SN-3", "SN-4"],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
first_locations = get_items_with_location_and_quantity(item, item_location_map, docstatus=0)
|
||||||
|
second_locations = get_items_with_location_and_quantity(item, item_location_map, docstatus=0)
|
||||||
|
|
||||||
|
self.assertEqual(first_locations[0].serial_no, "SN-1\nSN-2")
|
||||||
|
self.assertEqual(second_locations[0].serial_no, "SN-3\nSN-4")
|
||||||
|
|
||||||
def test_pick_list_allocation_takes_advisory_gate(self):
|
def test_pick_list_allocation_takes_advisory_gate(self):
|
||||||
if frappe.db.db_type != "postgres":
|
if frappe.db.db_type != "postgres":
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1971,7 +1971,7 @@ def update_serial_batch_delivered_qty(row, name, is_cancelled=False):
|
|||||||
.where((doctype.parent == name) & (doctype.batch_no == batch_no))
|
.where((doctype.parent == name) & (doctype.batch_no == batch_no))
|
||||||
)
|
)
|
||||||
|
|
||||||
query.run()
|
query.run()
|
||||||
|
|
||||||
|
|
||||||
def get_reserved_materials(voucher_no):
|
def get_reserved_materials(voucher_no):
|
||||||
|
|||||||
@@ -1168,3 +1168,44 @@ class TestStockReservationEntryValidation(ERPNextTestSuite):
|
|||||||
result = doc.get_serial_batch_entries()
|
result = doc.get_serial_batch_entries()
|
||||||
self.assertEqual(result.serial_nos, ["SN1", "SN2"])
|
self.assertEqual(result.serial_nos, ["SN1", "SN2"])
|
||||||
self.assertEqual(result.batches["B1"], 8)
|
self.assertEqual(result.batches["B1"], 8)
|
||||||
|
|
||||||
|
def test_update_serial_batch_delivered_qty_updates_each_batch(self):
|
||||||
|
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||||
|
update_serial_batch_delivered_qty,
|
||||||
|
)
|
||||||
|
|
||||||
|
item = make_batch_item()
|
||||||
|
first_batch = frappe.get_doc(doctype="Batch", item=item.name).insert()
|
||||||
|
second_batch = frappe.get_doc(doctype="Batch", item=item.name).insert()
|
||||||
|
batches = {first_batch.name: 2, second_batch.name: 3}
|
||||||
|
sre = make_stock_reservation_entry(
|
||||||
|
item_code=item.name,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
reserved_qty=5,
|
||||||
|
ignore_validate=True,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
sre.reservation_based_on = "Serial and Batch"
|
||||||
|
for batch_no, qty in batches.items():
|
||||||
|
sre.append("sb_entries", {"batch_no": batch_no, "qty": qty})
|
||||||
|
sre.save()
|
||||||
|
|
||||||
|
row = frappe._dict(serial_nos=[], batches=batches)
|
||||||
|
update_serial_batch_delivered_qty(row, sre.name)
|
||||||
|
delivered_qty_by_batch = {
|
||||||
|
d.batch_no: d.delivered_qty
|
||||||
|
for d in frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
filters={"parent": sre.name},
|
||||||
|
fields=["batch_no", "delivered_qty"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
self.assertEqual(delivered_qty_by_batch, batches)
|
||||||
|
|
||||||
|
update_serial_batch_delivered_qty(row, sre.name, is_cancelled=True)
|
||||||
|
delivered_qty = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
filters={"parent": sre.name},
|
||||||
|
pluck="delivered_qty",
|
||||||
|
)
|
||||||
|
self.assertEqual(delivered_qty, [0, 0])
|
||||||
|
|||||||
Reference in New Issue
Block a user