From 059890ecefe2144cf49b0e2678265a84930afadb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Jun 2019 01:27:58 +0530 Subject: [PATCH] Test cases for batch no --- erpnext/stock/doctype/batch/batch.py | 4 +- erpnext/stock/doctype/serial_no/serial_no.py | 1 - .../stock_reconciliation.js | 16 ++++- .../stock_reconciliation.py | 24 ++++---- .../test_stock_reconciliation.py | 60 +++++++++++++++++-- .../stock_reconciliation_item.json | 10 +--- 6 files changed, 87 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index c540ac5227..4881983b8e 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -257,10 +257,10 @@ def get_batches(item_code, warehouse, qty=1, throw=False): 'on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )' 'where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s ' 'and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL)' - 'group by batch_id having qty > 0' + 'group by batch_id ' 'order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC', (item_code, warehouse), - as_dict=True, debug=1 + as_dict=True ) return batches diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 4ab95c7e14..43bc5e2530 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -423,7 +423,6 @@ def update_serial_nos_after_submit(controller, parentfield): else d.stock_qty) for sle in stock_ledger_entries: - print(accepted_serial_nos_updated, qty, sle.actual_qty) if sle.voucher_detail_no==d.name: if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \ and sle.warehouse == warehouse and sle.serial_no != d.serial_no: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index a00e6e64bf..5ac0b098fe 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -153,6 +153,7 @@ frappe.ui.form.on("Stock Reconciliation Item", { barcode: function(frm, cdt, cdn) { frm.events.set_item_code(frm, cdt, cdn); }, + warehouse: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; if (child.batch_no) { @@ -161,22 +162,35 @@ frappe.ui.form.on("Stock Reconciliation Item", { frm.events.set_valuation_rate_and_qty(frm, cdt, cdn); }, + item_code: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; if (child.batch_no) { - frappe.model.set_value(child.cdt, child.cdn, "batch_no", ""); + frappe.model.set_value(cdt, cdn, "batch_no", ""); } frm.events.set_valuation_rate_and_qty(frm, cdt, cdn); }, + batch_no: function(frm, cdt, cdn) { frm.events.set_valuation_rate_and_qty(frm, cdt, cdn); }, + qty: function(frm, cdt, cdn) { frm.events.set_amount_quantity(frm, cdt, cdn); }, + valuation_rate: function(frm, cdt, cdn) { frm.events.set_amount_quantity(frm, cdt, cdn); + }, + + serial_no: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + + if (child.serial_no) { + const serial_nos = child.serial_no.trim().split('\n'); + frappe.model.set_value(cdt, cdn, "qty", serial_nos.length); + } } }); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index cdf6068f11..2be667c340 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -32,6 +32,9 @@ class StockReconciliation(StockController): self.validate_expense_account() self.set_total_qty_and_amount() + if self._action=="submit": + self.make_batches('warehouse') + def on_submit(self): self.update_stock_ledger() self.make_gl_entries() @@ -50,16 +53,16 @@ class StockReconciliation(StockController): item_dict = get_stock_balance_for(item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no) - if ((item.qty==None or item.qty==item_dict.get("qty")) - and (item.valuation_rate==None or item.valuation_rate==item_dict.get("rate")) - and item.serial_no == item_dict.get("serial_nos")): + if (((item.qty is None or item.qty==item_dict.get("qty")) and + (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no) + or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))): return False else: # set default as current rates - if item.qty==None: + if item.qty is None: item.qty = item_dict.get("qty") - if item.valuation_rate==None: + if item.valuation_rate is None: item.valuation_rate = item_dict.get("rate") if item_dict.get("serial_nos"): @@ -162,15 +165,12 @@ class StockReconciliation(StockController): # item should not be serialized if item.has_serial_no and not row.serial_no and not item.serial_no_series: - raise frappe.ValidationError(_("Serial nos are required for serialized item {0}").format(item_code)) + raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code)) # 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 self._action=="submit" and item.create_new_batch: - self.make_batches('warehouse') - # docstatus should be < 2 validate_cancelled_item(item_code, item.docstatus, verbose=0) @@ -203,7 +203,7 @@ class StockReconciliation(StockController): row.valuation_rate = previous_sle.get("valuation_rate", 0) if row.qty and not row.valuation_rate: - frappe.throw(_("Valuation Rate required for Item in row {0}").format(row.idx)) + frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx)) if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction") and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0)) @@ -270,7 +270,7 @@ class StockReconciliation(StockController): sl_entries.append(args) - if self.docstatus == 1 and not row.remove_serial_no_from_stock: + if self.docstatus == 1 and row.qty: args = self.get_sle_for_items(row) args.update({ @@ -332,7 +332,7 @@ class StockReconciliation(StockController): sl_entries = [] for row in self.items: - if row.serial_no or row.batch_no: + if row.serial_no or row.batch_no or row.current_serial_no: self.get_sle_for_serialized_items(row, sl_entries) if sl_entries: diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 5ee8228edf..f0c71cf39a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -13,7 +13,7 @@ from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.item.test_item import create_item -from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos +from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos, get_stock_value_on from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class TestStockReconciliation(unittest.TestCase): @@ -169,10 +169,62 @@ class TestStockReconciliation(unittest.TestCase): if frappe.db.exists("Serial No", d): frappe.delete_doc("Serial No", d) + def test_stock_reco_for_batch_item(self): + set_perpetual_inventory() + + to_delete_records = [] + to_delete_serial_nos = [] + + # Add new serial nos + item_code = "Stock-Reco-batch-Item-1" + warehouse = "_Test Warehouse for Stock Reco2 - _TC" + + sr = create_stock_reconciliation(item_code=item_code, + warehouse = warehouse, qty=5, rate=200, do_not_submit=1) + sr.save(ignore_permissions=True) + sr.submit() + + self.assertTrue(sr.items[0].batch_no) + to_delete_records.append(sr.name) + + sr1 = create_stock_reconciliation(item_code=item_code, + warehouse = warehouse, qty=6, rate=300, batch_no=sr.items[0].batch_no) + + args = { + "item_code": item_code, + "warehouse": warehouse, + "posting_date": nowdate(), + "posting_time": nowtime(), + } + + valuation_rate = get_incoming_rate(args) + self.assertEqual(valuation_rate, 300) + to_delete_records.append(sr1.name) + + + sr2 = create_stock_reconciliation(item_code=item_code, + warehouse = warehouse, qty=0, rate=0, batch_no=sr.items[0].batch_no) + + stock_value = get_stock_value_on(warehouse, nowdate(), item_code) + self.assertEqual(stock_value, 0) + to_delete_records.append(sr2.name) + + to_delete_records.reverse() + for d in to_delete_records: + stock_doc = frappe.get_doc("Stock Reconciliation", d) + stock_doc.cancel() + + frappe.delete_doc("Batch", sr.items[0].batch_no) + for d in to_delete_records: + frappe.delete_doc("Stock Reconciliation", d) + def create_batch_or_serial_no_items(): create_warehouse("_Test Warehouse for Stock Reco1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"}) + create_warehouse("_Test Warehouse for Stock Reco2", + {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"}) + serial_item_doc = create_item("Stock-Reco-Serial-Item-1", is_stock_item=1) if not serial_item_doc.has_serial_no: serial_item_doc.has_serial_no = 1 @@ -202,12 +254,12 @@ def create_stock_reconciliation(**args): "qty": args.qty, "valuation_rate": args.rate, "serial_no": args.serial_no, - "batch_no": args.batch_no, - "remove_serial_no_from_stock": args.remove_serial_no_from_stock or 0 + "batch_no": args.batch_no }) try: - sr.submit() + if not args.do_not_submit: + sr.submit() except EmptyStockReconciliationItemsError: pass return sr diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index fa42c9c25c..e53db0772b 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -15,7 +15,6 @@ "amount", "serial_no_and_batch_section", "serial_no", - "remove_serial_no_from_stock", "column_break_11", "batch_no", "section_break_3", @@ -110,6 +109,7 @@ "label": "Before reconciliation" }, { + "default": "0", "fieldname": "current_qty", "fieldtype": "Float", "label": "Current Qty", @@ -166,16 +166,10 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" - }, - { - "default": "0", - "fieldname": "remove_serial_no_from_stock", - "fieldtype": "Check", - "label": "Remove Serial No from Stock" } ], "istable": 1, - "modified": "2019-06-01 03:16:38.459307", + "modified": "2019-06-14 17:10:53.188305", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item",