Merge pull request #23192 from marination/batch-source-reference

fix: Unlink and delete batch created from stock reco on cancel
This commit is contained in:
rohitwaghchaure
2020-09-08 12:12:30 +05:30
committed by GitHub
5 changed files with 216 additions and 11 deletions

View File

@@ -242,10 +242,11 @@ class StockController(AccountsController):
_(self.doctype), self.name, item.get("item_code")))
def delete_auto_created_batches(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for d in self.items:
if not d.batch_no: continue
serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': d.batch_no})]
serial_nos = get_serial_nos(d.serial_no)
if serial_nos:
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)

View File

@@ -13,6 +13,7 @@ from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_ord
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_rm_stock_entry
import unittest
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError
class TestItemAlternative(unittest.TestCase):
def setUp(self):
@@ -110,8 +111,11 @@ def make_items():
if not frappe.db.exists('Item', item_code):
create_item(item_code)
create_stock_reconciliation(item_code="Test FG A RW 1",
warehouse='_Test Warehouse - _TC', qty=10, rate=2000)
try:
create_stock_reconciliation(item_code="Test FG A RW 1",
warehouse='_Test Warehouse - _TC', qty=10, rate=2000)
except EmptyStockReconciliationItemsError:
pass
if frappe.db.exists('Item', 'Test FG A RW 1'):
doc = frappe.get_doc('Item', 'Test FG A RW 1')

View File

@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from six import iteritems
def get_sle(**args):
@@ -483,6 +484,100 @@ class TestStockEntry(unittest.TestCase):
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
self.assertFalse(frappe.db.get_value("Serial No", serial_no, "warehouse"))
def test_serial_batch_item_stock_entry(self):
"""
Behaviour: 1) Submit Stock Entry (Receipt) with Serial & Batched Item
2) Cancel same Stock Entry
Expected Result: 1) Batch is created with Reference in Serial No
2) Batch is deleted and Serial No is Inactive
"""
from erpnext.stock.doctype.batch.batch import get_batch_qty
item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
if not item:
item = create_item("Batched and Serialised Item")
item.has_batch_no = 1
item.create_new_batch = 1
item.has_serial_no = 1
item.batch_number_series = "B-BATCH-.##"
item.serial_no_series = "S-.####"
item.save()
else:
item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
se = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100)
batch_no = se.items[0].batch_no
serial_no = get_serial_nos(se.items[0].serial_no)[0]
batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no")
self.assertEqual(batch_in_serial_no, batch_no)
self.assertEqual(batch_qty, 1)
se.cancel()
batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no")
self.assertEqual(batch_in_serial_no, None)
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Inactive")
self.assertEqual(frappe.db.exists("Batch", batch_no), None)
def test_serial_batch_item_qty_deduction(self):
"""
Behaviour: Create 2 Stock Entries, both adding Serial Nos to same batch
Expected Result: 1) Cancelling first Stock Entry (origin transaction of created batch)
should throw a LinkExistsError
2) Cancelling second Stock Entry should make Serial Nos that are, linked to mentioned batch
and in that transaction only, Inactive.
"""
from erpnext.stock.doctype.batch.batch import get_batch_qty
item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
if not item:
item = create_item("Batched and Serialised Item")
item.has_batch_no = 1
item.create_new_batch = 1
item.has_serial_no = 1
item.batch_number_series = "B-BATCH-.##"
item.serial_no_series = "S-.####"
item.save()
else:
item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
se1 = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100)
batch_no = se1.items[0].batch_no
serial_no1 = get_serial_nos(se1.items[0].serial_no)[0]
# Check Source (Origin) Document of Batch
self.assertEqual(frappe.db.get_value("Batch", batch_no, "reference_name"), se1.name)
se2 = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100,
batch_no=batch_no)
serial_no2 = get_serial_nos(se2.items[0].serial_no)[0]
batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
self.assertEqual(batch_qty, 2)
frappe.db.commit()
# Cancelling Origin Document of Batch
self.assertRaises(frappe.LinkExistsError, se1.cancel)
frappe.db.rollback()
se2.cancel()
# Check decrease in Batch Qty
batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
self.assertEqual(batch_qty, 1)
# Check if Serial No from Stock Entry 1 is intact
self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "batch_no"), batch_no)
self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "status"), "Active")
# Check if Serial No from Stock Entry 2 is Unlinked and Inactive
self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "batch_no"), None)
self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "status"), "Inactive")
def test_warehouse_company_validation(self):
company = frappe.db.get_value('Warehouse', '_Test Warehouse 2 - _TC1', 'company')
set_perpetual_inventory(0, company)

View File

@@ -45,6 +45,7 @@ class StockReconciliation(StockController):
def on_cancel(self):
self.delete_and_repost_sle()
self.make_gl_entries_on_cancel()
self.delete_auto_created_batches()
def remove_items_with_no_change(self):
"""Remove items if qty or rate is not changed"""

View File

@@ -170,7 +170,7 @@ class TestStockReconciliation(unittest.TestCase):
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)
warehouse = warehouse, qty=5, rate=200, do_not_save=1, do_not_submit=1)
sr.save(ignore_permissions=True)
sr.submit()
@@ -204,6 +204,110 @@ class TestStockReconciliation(unittest.TestCase):
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
def test_stock_reco_for_serial_and_batch_item(self):
set_perpetual_inventory()
item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
if not item:
item = create_item("Batched and Serialised Item")
item.has_batch_no = 1
item.create_new_batch = 1
item.has_serial_no = 1
item.batch_number_series = "B-BATCH-.##"
item.serial_no_series = "S-.####"
item.save()
else:
item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
sr = create_stock_reconciliation(item_code=item.item_code,
warehouse = warehouse, qty=1, rate=100)
batch_no = sr.items[0].batch_no
serial_nos = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos), 1)
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "batch_no"), batch_no)
sr.cancel()
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Inactive")
self.assertEqual(frappe.db.exists("Batch", batch_no), None)
if frappe.db.exists("Serial No", serial_nos[0]):
frappe.delete_doc("Serial No", serial_nos[0])
def test_stock_reco_for_serial_and_batch_item_with_future_dependent_entry(self):
"""
Behaviour: 1) Create Stock Reconciliation, which will be the origin document
of a new batch having a serial no
2) Create a Stock Entry that adds a serial no to the same batch following this
Stock Reconciliation
3) Cancel Stock Reconciliation
4) Cancel Stock Entry
Expected Result: 3) Cancelling the Stock Reco throws a LinkExistsError since
Stock Entry is dependent on the batch involved
4) Serial No only in the Stock Entry is Inactive and Batch qty decreases
"""
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.batch.batch import get_batch_qty
set_perpetual_inventory()
item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
if not item:
item = create_item("Batched and Serialised Item")
item.has_batch_no = 1
item.create_new_batch = 1
item.has_serial_no = 1
item.batch_number_series = "B-BATCH-.##"
item.serial_no_series = "S-.####"
item.save()
else:
item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
stock_reco = create_stock_reconciliation(item_code=item.item_code,
warehouse = warehouse, qty=1, rate=100)
batch_no = stock_reco.items[0].batch_no
serial_no = get_serial_nos(stock_reco.items[0].serial_no)[0]
stock_entry = make_stock_entry(item_code=item.item_code, target=warehouse, qty=1, basic_rate=100,
batch_no=batch_no)
serial_no_2 = get_serial_nos(stock_entry.items[0].serial_no)[0]
# Check Batch qty after 2 transactions
batch_qty = get_batch_qty(batch_no, warehouse, item.item_code)
self.assertEqual(batch_qty, 2)
frappe.db.commit()
# Cancelling Origin Document of Batch
self.assertRaises(frappe.LinkExistsError, stock_reco.cancel)
frappe.db.rollback()
stock_entry.cancel()
# Check Batch qty after cancellation
batch_qty = get_batch_qty(batch_no, warehouse, item.item_code)
self.assertEqual(batch_qty, 1)
# Check if Serial No from Stock Reconcilation is intact
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "batch_no"), batch_no)
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active")
# Check if Serial No from Stock Entry is Unlinked and Inactive
self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "batch_no"), None)
self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "status"), "Inactive")
stock_reco.load_from_db()
stock_reco.cancel()
for sn in (serial_no, serial_no_2):
if frappe.db.exists("Serial No", sn):
frappe.delete_doc("Serial No", sn)
def test_stock_reco_for_same_item_with_multiple_batches(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -322,14 +426,14 @@ def create_stock_reconciliation(**args):
"batch_no": batch
})
try:
if args.do_not_save:
return sr
if not args.do_not_save:
sr.insert()
try:
if not args.do_not_submit:
sr.submit()
except EmptyStockReconciliationItemsError:
pass
if not args.do_not_submit:
sr.submit()
except EmptyStockReconciliationItemsError:
pass
return sr
def set_valuation_method(item_code, valuation_method):