Merge branch 'version-12-hotfix' into batch-source-reference

This commit is contained in:
Marica
2020-09-03 15:39:44 +05:30
committed by GitHub
2 changed files with 143 additions and 90 deletions

View File

@@ -184,17 +184,11 @@ class StockReconciliation(StockController):
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
sl_entries = [] sl_entries = []
has_serial_no = False
has_batch_no = False
for row in self.items:
item = frappe.get_doc("Item", row.item_code)
if item.has_batch_no:
has_batch_no = True
if item.has_serial_no or item.has_batch_no: serialized_items = False
has_serial_no = True for row in self.items:
self.get_sle_for_serialized_items(row, sl_entries) item = frappe.get_cached_doc("Item", row.item_code)
else: if not (item.has_serial_no or item.has_batch_no):
if row.serial_no or row.batch_no: if row.serial_no or row.batch_no:
frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \ 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))) .format(row.idx, frappe.bold(row.item_code)))
@@ -222,24 +216,29 @@ class StockReconciliation(StockController):
sl_entries.append(self.get_sle_for_items(row)) sl_entries.append(self.get_sle_for_items(row))
else:
serialized_items = True
if serialized_items:
self.get_sle_for_serialized_items(sl_entries)
if sl_entries: if sl_entries:
if has_serial_no: allow_negative_stock = frappe.get_cached_value("Stock Settings", None, "allow_negative_stock")
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
allow_negative_stock = False
if has_batch_no:
allow_negative_stock = True
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
if has_serial_no and sl_entries: def get_sle_for_serialized_items(self, sl_entries):
self.issue_existing_serial_and_batch(sl_entries)
self.add_new_serial_and_batch(sl_entries)
self.update_valuation_rate_for_serial_no() self.update_valuation_rate_for_serial_no()
def get_sle_for_serialized_items(self, row, sl_entries): if sl_entries:
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
def issue_existing_serial_and_batch(self, sl_entries):
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
serial_nos = get_serial_nos(row.serial_no) for row in self.items:
serial_nos = get_serial_nos(row.serial_no) or []
# To issue existing serial nos # 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 or row.batch_no):
@@ -290,6 +289,8 @@ class StockReconciliation(StockController):
sl_entries.append(new_args) sl_entries.append(new_args)
def add_new_serial_and_batch(self, sl_entries):
for row in self.items:
if row.qty: if row.qty:
args = self.get_sle_for_items(row) args = self.get_sle_for_items(row)
@@ -301,10 +302,6 @@ class StockReconciliation(StockController):
sl_entries.append(args) sl_entries.append(args)
if serial_nos == get_serial_nos(row.current_serial_no):
# update valuation rate
self.update_valuation_rate_for_serial_nos(row, serial_nos)
def update_valuation_rate_for_serial_no(self): def update_valuation_rate_for_serial_no(self):
for d in self.items: for d in self.items:
if not d.serial_no: continue if not d.serial_no: continue
@@ -361,17 +358,9 @@ class StockReconciliation(StockController):
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
sl_entries = [] sl_entries = []
self.get_sle_for_serialized_items(sl_entries)
has_serial_no = False
for row in self.items:
if row.serial_no or row.batch_no or row.current_serial_no:
has_serial_no = True
self.get_sle_for_serialized_items(row, sl_entries)
if sl_entries: if sl_entries:
if has_serial_no:
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
sl_entries.reverse() sl_entries.reverse()
allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)

View File

@@ -308,6 +308,59 @@ class TestStockReconciliation(unittest.TestCase):
if frappe.db.exists("Serial No", sn): if frappe.db.exists("Serial No", sn):
frappe.delete_doc("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
set_perpetual_inventory()
item_code = "Stock-Reco-batch-Item-2"
warehouse = "_Test Warehouse for Stock Reco3 - _TC"
create_warehouse("_Test Warehouse for Stock Reco3", {"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.####"
})
# inward entries with different batch and valuation rate
ste1=make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code=item_code,
target=warehouse, qty=6, basic_rate=700)
ste2=make_stock_entry(posting_date="2012-12-16", posting_time="02:00", item_code=item_code,
target=warehouse, qty=3, basic_rate=200)
ste3=make_stock_entry(posting_date="2012-12-17", posting_time="02:00", item_code=item_code,
target=warehouse, qty=2, basic_rate=500)
ste4=make_stock_entry(posting_date="2012-12-17", posting_time="02:00", item_code=item_code,
target=warehouse, qty=4, basic_rate=100)
batchwise_item_details = {}
for stock_doc in [ste1, ste2, ste3, ste4]:
self.assertEqual(item_code, stock_doc.items[0].item_code)
batchwise_item_details[stock_doc.items[0].batch_no] = [stock_doc.items[0].qty, 0.01]
stock_balance = frappe.get_all("Stock Ledger Entry",
filters = {"item_code": item_code, "warehouse": warehouse},
fields=["sum(stock_value_difference)"], as_list=1)
self.assertEqual(flt(stock_balance[0][0]), 6200.00)
sr = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, batch_details = batchwise_item_details)
stock_balance = frappe.get_all("Stock Ledger Entry",
filters = {"item_code": item_code, "warehouse": warehouse},
fields=["sum(stock_value_difference)"], as_list=1)
self.assertEqual(flt(stock_balance[0][0]), 0.15)
for doc in [sr, ste1, ste2, ste3, ste4]:
doc.cancel()
frappe.delete_doc(doc.doctype, doc.name)
def insert_existing_sle(warehouse): def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -354,6 +407,7 @@ def create_stock_reconciliation(**args):
or frappe.get_cached_value("Company", sr.company, "cost_center") \ or frappe.get_cached_value("Company", sr.company, "cost_center") \
or "_Test Cost Center - _TC" or "_Test Cost Center - _TC"
if not args.batch_details:
sr.append("items", { sr.append("items", {
"item_code": args.item_code or "_Test Item", "item_code": args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
@@ -362,6 +416,15 @@ def create_stock_reconciliation(**args):
"serial_no": args.serial_no, "serial_no": args.serial_no,
"batch_no": args.batch_no "batch_no": args.batch_no
}) })
elif args.batch_details:
for batch, data in args.batch_details.items():
sr.append("items", {
"item_code": args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": data[0],
"valuation_rate": data[1],
"batch_no": batch
})
if not args.do_not_save: if not args.do_not_save:
sr.insert() sr.insert()
@@ -370,6 +433,7 @@ def create_stock_reconciliation(**args):
sr.submit() sr.submit()
except EmptyStockReconciliationItemsError: except EmptyStockReconciliationItemsError:
pass pass
return sr return sr
def set_valuation_method(item_code, valuation_method): def set_valuation_method(item_code, valuation_method):