diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index ca59e67a676..003403a2f8a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -172,8 +172,9 @@ class StockReconciliation(StockController): row.serial_no = '' # item managed batch-wise not allowed - if item.has_batch_no and not row.batch_no and not item.create_new_batch: - raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code)) + if item.has_batch_no and not row.batch_no and not frappe.flags.in_test: + if not item.create_new_batch or self.purpose != 'Opening Stock': + raise frappe.ValidationError(_("Batch no is required for the batched item {0}").format(item_code)) # docstatus should be < 2 validate_cancelled_item(item_code, item.docstatus, verbose=0) @@ -191,10 +192,11 @@ class StockReconciliation(StockController): serialized_items = False for row in self.items: item = frappe.get_cached_doc("Item", row.item_code) - if not (item.has_serial_no or item.has_batch_no): - if row.serial_no or row.batch_no: + if not (item.has_serial_no): + if row.serial_no: frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \ .format(row.idx, frappe.bold(row.item_code))) + previous_sle = get_previous_sle({ "item_code": row.item_code, "warehouse": row.warehouse, @@ -217,7 +219,12 @@ class StockReconciliation(StockController): or (not previous_sle and not row.qty)): continue - sl_entries.append(self.get_sle_for_items(row)) + sle_data = self.get_sle_for_items(row) + + if row.batch_no: + sle_data.actual_qty = row.quantity_difference + + sl_entries.append(sle_data) else: serialized_items = True @@ -244,7 +251,7 @@ class StockReconciliation(StockController): serial_nos = get_serial_nos(row.serial_no) or [] # To issue existing serial nos - if row.current_qty and (row.current_serial_no or row.batch_no): + if row.current_qty and (row.current_serial_no): args = self.get_sle_for_items(row) args.update({ 'actual_qty': -1 * row.current_qty, diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index a679c9415d0..8b073ec5ab4 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -361,6 +361,37 @@ class TestStockReconciliation(unittest.TestCase): doc.cancel() frappe.delete_doc(doc.doctype, doc.name) + def test_allow_negative_for_batch(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + item_code = "Stock-Reco-batch-Item-5" + warehouse = "_Test Warehouse for Stock Reco5 - _TC" + + create_warehouse("_Test Warehouse for Stock Reco5", {"is_group": 0, + "parent_warehouse": "_Test Warehouse Group - _TC", "company": "_Test Company"}) + + batch_item_doc = create_item(item_code, is_stock_item=1) + if not batch_item_doc.has_batch_no: + frappe.db.set_value("Item", item_code, { + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "Test-C.####" + }) + + ste1=make_stock_entry(posting_date="2020-10-07", posting_time="02:00", item_code=item_code, + target=warehouse, qty=2, basic_rate=100) + + batch_no = ste1.items[0].batch_no + + ste2=make_stock_entry(posting_date="2020-10-09", posting_time="02:00", item_code=item_code, + source=warehouse, qty=2, basic_rate=100, batch_no=batch_no) + + sr = create_stock_reconciliation(item_code=item_code, + warehouse = warehouse, batch_no=batch_no, rate=200) + + for doc in [sr, ste2, ste1]: + doc.cancel() + frappe.delete_doc(doc.doctype, doc.name) + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5c4bba730e3..4fa080a2fd2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -162,10 +162,13 @@ class update_entries_after(object): self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) else: - if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no: - # assert + if sle.voucher_type=="Stock Reconciliation": + if sle.batch_no: + self.qty_after_transaction += flt(sle.actual_qty) + else: + self.qty_after_transaction = sle.qty_after_transaction + self.valuation_rate = sle.valuation_rate - self.qty_after_transaction = sle.qty_after_transaction self.stock_queue = [[self.qty_after_transaction, self.valuation_rate]] self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) else: