V15 Serial and Batch Bundle Feature - Any Idea How It Is Meant to Work?

How does it handle multiple batches in single item?

We are planning to create a stand alone pos with local db which will sync with erpnext and was planning to base it on V15 but looking at the way it handles batches, it seems complicated but the pop-up option it good and if there is a way to create new batches then its fine.

Can this handle more than 100 items?

my solution for your kind reference

how about keep the original small text Serial No and batch field in the xxx Item (Purchase Invoice Item) row, user can copy from excel multi serial no per row, or same item code and different batch in multi rows, in the backend python code to handle auto create serial no and batch master as needed, also create the serial batch bundle, this way the user can almost work the same way as before.

here is my customized sample code to handle batch no in multi row

items_has_batch_no = frappe.get_all("Item",
	filters = {
		"name": ("in", [r.item_code for r in doc.items if not r.serial_and_batch_bundle]),
		"has_batch_no": 1},
	pluck="name")
item_code_batch_map = {}
for row in doc.items:
	if row.item_code in items_has_batch_no and row.custom_new_batch_no:
		item_code_batch_map.setdefault((row.item_code, row.warehouse), []).append(
			(row.qty, row.custom_new_batch_no, row.custom_supplier_batch_no))
item_code_sabb_map = {}
if item_code_batch_map:
	for (item_code, warehouse), batches in item_code_batch_map.items():
		sabb = frappe.new_doc("Serial and Batch Bundle")
		sabb.update({
			"item_code": item_code,
			"warehouse": warehouse,
			"type_of_transaction": "Inward",
			"has_batch_no": 1,
			"voucher_type": "Purchase Receipt"
		})
		existing_batches = frappe.get_all("Batch", 
			filters={"name": ("in", [b[1] for b in batches])},
			pluck ="name" )
		for (qty, batch_no, custom_supplier_batch_no) in batches:		
			if not batch_no in existing_batches:
				batch_doc = frappe.new_doc("Batch")
				batch_doc.update({
					"item": item_code,
					"batch_id": batch_no,
					#"batch_qty": qty,
					"custom_supplier_batch_no": custom_supplier_batch_no
				})
				batch_doc.save()
			sabb.append(
				"entries",
				{				
					"qty": qty,
					"batch_no": batch_no
				},
			)
		sabb.save()
		item_code_sabb_map[item_code] = sabb.name
	#按物料号合并	
	qty_map = {}        #中间变量记录物料号与数量,库存数量,客户po对照表
	row_names = []      #列表变量,记录料号出现第一次的行编号,用来删除其它重复行
	if len(doc.items) > len({r.item_code for r in doc.items}):  #只有包括重复料号时,才执行这段代码
		for row in doc.items:
			item_code = row.item_code
			qty_dict = qty_map.get(item_code)
			if not qty_dict:                                    #判断物料是否第一次出现
				qty_map[item_code] = {}
				for field in ['qty','stock_qty','received_stock_qty','received_qty']: 
					qty_map[item_code][field] = row.get(field)								
				row_names.append(row.name)
			else:
				for field in ['qty','stock_qty','received_stock_qty','received_qty']:                                               #接下来的重复物料行,累加数量,合并字符串
					qty_map[item_code][field] = qty_map[item_code][field] + row.get(field)				
		#doc.items = [r for r in doc.items if r.name in row_names]   #仅保留首次出现的行,即删除重复行,这一句脚本环境运行不了!
		items = []
		for r in doc.items:
			if r.name in row_names:
				items.append(r)
		doc.items = items
		for row in doc.items:                                   #修改数量,库存数量,客户po字段    
			item_code = row.item_code
			row.serial_and_batch_bundle = item_code_sabb_map.get(row.item_code)
			row.custom_new_batch_no = ''
			row.custom_supplier_batch_no = ''
			for field in ['qty','stock_qty','received_stock_qty','received_qty']:
				row.update({field:qty_map[item_code][field]})
#根据采购订单号获取采购订单明细ID
po_item_list = frappe.get_all("Purchase Order Item",
	filters = {"parent": ("in", [r.purchase_order for r in doc.items if r.purchase_order and not r.purchase_order_item])},
	fields = ['parent as po','item_code','name', 'rate'])
if po_item_list:
	item_code_po_item_map = {(r.po, r.item_code):(r.name, r.rate) for r in po_item_list}
	for row in doc.items:
	    po_info =item_code_po_item_map.get((row.purchase_order,row.item_code))
	    if po_info:
		    row.purchase_order_item = po_info[0]
		    row.rate = po_info[1]
2 Likes

Thank you @rohit_w for clean instructions.

I wonder, how we should proceed when bulk importing data for new companies?

I think we should follow this path:

  • Data import Items,
  • Then serial nos (or batches)
  • Then Serial and Batch Bundles,
  • Then transaction (Stock reconciliation)

Is that right?

I’m also having this issue here, not sure if it’s related to the new Serial and Batch bundle approach tho. Anyone else managed to fix batch and serial numbers not showing in the Stock Ledger report?

Thanks rohit_w,

We will wait for this update and check the options.

br
Andreas

Thanks!
already tested and it’s working well. Saves time.

V15 we are coming :smiley:

br
Andreas

1 Like

Made a new changes to use Old Serial / Batch fields to make Serial and Batch Bundle. Now user can able to use the old Serial and Batch fields and system will auto create the Serial and Batch bundle on submission of the stock transaction.
To know more please check below PR link. Special thanks to @szufisher for the proposing the solution.

3 Likes

It seems that old way came back.

Does it completely ignore SBB or creates SBB on the fly?

It won’t ignore SBB, if the user has used old fields to enter serial / batches then system will create the SBB in the backend on the submission of the respective stock transaction.

Valuation and quantity calculation will be based on the SBB, while the old serial/batch fields are retained to solve UX issues .

1 Like

Ok. Then importing the old transactions, serials will be simplified. Because some machine manufacturers prefer to migrate all old serial nos to new system.

Thank for your efforts.

1 Like