diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js index fa8b2bee55..3b07e4e80c 100644 --- a/erpnext/stock/doctype/batch/batch.js +++ b/erpnext/stock/doctype/batch/batch.js @@ -47,8 +47,6 @@ frappe.ui.form.on('Batch', { return; } - debugger - const section = frm.dashboard.add_section('', __("Stock Levels")); // sort by qty diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 0e4132db8e..7fb672c0cb 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -59,6 +59,73 @@ class TestBatch(FrappeTestCase): return receipt + def test_batch_stock_levels(self, batch_qty=100): + """Test automated batch creation from Purchase Receipt""" + self.make_batch_item("ITEM-BATCH-1") + + receipt = frappe.get_doc( + dict( + doctype="Purchase Receipt", + supplier="_Test Supplier", + company="_Test Company", + items=[dict(item_code="ITEM-BATCH-1", qty=10, rate=10, warehouse="Stores - _TC")], + ) + ).insert() + receipt.submit() + + receipt.load_from_db() + batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle) + + bundle_id = ( + SerialBatchCreation( + { + "item_code": "ITEM-BATCH-1", + "warehouse": "_Test Warehouse - _TC", + "actual_qty": 20, + "voucher_type": "Purchase Receipt", + "batches": frappe._dict({batch_no: 20}), + "type_of_transaction": "Inward", + "company": receipt.company, + } + ) + .make_serial_and_batch_bundle() + .name + ) + + receipt2 = frappe.get_doc( + dict( + doctype="Purchase Receipt", + supplier="_Test Supplier", + company="_Test Company", + items=[ + dict( + item_code="ITEM-BATCH-1", + qty=20, + rate=10, + warehouse="_Test Warehouse - _TC", + serial_and_batch_bundle=bundle_id, + ) + ], + ) + ).insert() + receipt2.submit() + + receipt.load_from_db() + receipt2.load_from_db() + + self.assertTrue(receipt.items[0].serial_and_batch_bundle) + self.assertTrue(receipt2.items[0].serial_and_batch_bundle) + + batchwise_qty = frappe._dict({}) + for receipt in [receipt, receipt2]: + batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle) + key = (batch_no, receipt.items[0].warehouse) + batchwise_qty[key] = receipt.items[0].qty + + batches = get_batch_qty(batch_no) + for d in batches: + self.assertEqual(d.qty, batchwise_qty[(d.batch_no, d.warehouse)]) + def test_stock_entry_incoming(self): """Test batch creation via Stock Entry (Work Order)""" diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 75b6ec7ef8..0c6d33bae2 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -1272,24 +1272,29 @@ def get_reserved_batches_for_pos(kwargs): if ids: for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids): - if d.batch_no not in pos_batches: - pos_batches[d.batch_no] = frappe._dict( + key = (d.batch_no, d.warehouse) + if key not in pos_batches: + pos_batches[key] = frappe._dict( { "qty": d.qty, "warehouse": d.warehouse, } ) else: - pos_batches[d.batch_no].qty += d.qty + pos_batches[key].qty += d.qty for row in pos_invoices: if not row.batch_no: continue - if row.batch_no in pos_batches: - pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty + if kwargs.get("batch_no") and row.batch_no != kwargs.get("batch_no"): + continue + + key = (row.batch_no, row.warehouse) + if key in pos_batches: + pos_batches[key] -= row.qty * -1 if row.is_return else row.qty else: - pos_batches[row.batch_no] = frappe._dict( + pos_batches[key] = frappe._dict( { "qty": (row.qty * -1 if row.is_return else row.qty), "warehouse": row.warehouse, @@ -1309,6 +1314,7 @@ def get_auto_batch_nos(kwargs): update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches) available_batches = list(filter(lambda x: x.qty > 0, available_batches)) + if not qty: return available_batches @@ -1351,10 +1357,11 @@ def get_qty_based_available_batches(available_batches, qty): def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None): for batches in [reserved_batches, pos_invoice_batches]: if batches: - for batch_no, data in batches.items(): + for key, data in batches.items(): + batch_no, warehouse = key batch_not_exists = True for batch in available_batches: - if batch.batch_no == batch_no and batch.warehouse == data.warehouse: + if batch.batch_no == batch_no and batch.warehouse == warehouse: batch.qty += data.qty batch_not_exists = False @@ -1563,7 +1570,7 @@ def get_stock_ledgers_batches(kwargs): .groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse) ) - for field in ["warehouse", "item_code"]: + for field in ["warehouse", "item_code", "batch_no"]: if not kwargs.get(field): continue @@ -1582,6 +1589,10 @@ def get_stock_ledgers_batches(kwargs): data = query.run(as_dict=True) batches = {} for d in data: - batches[d.batch_no] = d + key = (d.batch_no, d.warehouse) + if key not in batches: + batches[key] = d + else: + batches[key].qty += d.qty return batches