fix: incorrect qty in serial batch bundle against pick list (#38964) (cherry picked from commit 47ee801d373058a2739c2fd42d971d624c42d5a7) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
parent
ab9fce333d
commit
5874be0f79
@ -502,6 +502,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
|
|
||||||
set_data(data) {
|
set_data(data) {
|
||||||
data.forEach(d => {
|
data.forEach(d => {
|
||||||
|
d.qty = Math.abs(d.qty);
|
||||||
this.dialog.fields_dict.entries.df.data.push(d);
|
this.dialog.fields_dict.entries.df.data.push(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -311,11 +311,13 @@ class DeliveryNote(SellingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def set_serial_and_batch_bundle_from_pick_list(self):
|
def set_serial_and_batch_bundle_from_pick_list(self):
|
||||||
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
|
|
||||||
if not self.pick_list:
|
if not self.pick_list:
|
||||||
return
|
return
|
||||||
|
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if item.pick_list_item:
|
if item.pick_list_item and not item.serial_and_batch_bundle:
|
||||||
filters = {
|
filters = {
|
||||||
"item_code": item.item_code,
|
"item_code": item.item_code,
|
||||||
"voucher_type": "Pick List",
|
"voucher_type": "Pick List",
|
||||||
@ -326,7 +328,17 @@ class DeliveryNote(SellingController):
|
|||||||
bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name")
|
bundle_id = frappe.db.get_value("Serial and Batch Bundle", filters, "name")
|
||||||
|
|
||||||
if bundle_id:
|
if bundle_id:
|
||||||
item.serial_and_batch_bundle = bundle_id
|
cls_obj = SerialBatchCreation(
|
||||||
|
{
|
||||||
|
"type_of_transaction": "Outward",
|
||||||
|
"serial_and_batch_bundle": bundle_id,
|
||||||
|
"item_code": item.get("item_code"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls_obj.duplicate_package()
|
||||||
|
|
||||||
|
item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
|
||||||
|
|
||||||
def validate_proj_cust(self):
|
def validate_proj_cust(self):
|
||||||
"""check for does customer belong to same project as entered.."""
|
"""check for does customer belong to same project as entered.."""
|
||||||
@ -408,6 +420,7 @@ class DeliveryNote(SellingController):
|
|||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
self.cancel_packing_slips()
|
self.cancel_packing_slips()
|
||||||
|
self.update_pick_list_status()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
@ -283,6 +283,7 @@ frappe.ui.form.on('Pick List Item', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
uom: (frm, cdt, cdn) => {
|
uom: (frm, cdt, cdn) => {
|
||||||
let row = frappe.get_doc(cdt, cdn);
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
if (row.uom) {
|
if (row.uom) {
|
||||||
@ -291,13 +292,50 @@ frappe.ui.form.on('Pick List Item', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
qty: (frm, cdt, cdn) => {
|
qty: (frm, cdt, cdn) => {
|
||||||
let row = frappe.get_doc(cdt, cdn);
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
||||||
},
|
},
|
||||||
|
|
||||||
conversion_factor: (frm, cdt, cdn) => {
|
conversion_factor: (frm, cdt, cdn) => {
|
||||||
let row = frappe.get_doc(cdt, cdn);
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
||||||
|
},
|
||||||
|
|
||||||
|
pick_serial_and_batch(frm, cdt, cdn) {
|
||||||
|
let item = locals[cdt][cdn];
|
||||||
|
let path = "assets/erpnext/js/utils/serial_no_batch_selector.js";
|
||||||
|
|
||||||
|
frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||||
|
.then((r) => {
|
||||||
|
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||||
|
item.has_serial_no = r.message.has_serial_no;
|
||||||
|
item.has_batch_no = r.message.has_batch_no;
|
||||||
|
item.type_of_transaction = item.qty > 0 ? "Outward":"Inward";
|
||||||
|
|
||||||
|
item.title = item.has_serial_no ?
|
||||||
|
__("Select Serial No") : __("Select Batch No");
|
||||||
|
|
||||||
|
if (item.has_serial_no && item.has_batch_no) {
|
||||||
|
item.title = __("Select Serial and Batch");
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.require(path, function() {
|
||||||
|
new erpnext.SerialBatchPackageSelector(
|
||||||
|
frm, item, (r) => {
|
||||||
|
if (r) {
|
||||||
|
let qty = Math.abs(r.total_qty);
|
||||||
|
frappe.model.set_value(item.doctype, item.name, {
|
||||||
|
"serial_and_batch_bundle": r.name,
|
||||||
|
"qty": qty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ from erpnext.selling.doctype.sales_order.sales_order import (
|
|||||||
)
|
)
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
get_auto_batch_nos,
|
get_auto_batch_nos,
|
||||||
|
get_picked_serial_nos,
|
||||||
)
|
)
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
@ -167,6 +168,9 @@ class PickList(Document):
|
|||||||
"Serial and Batch Bundle", row.serial_and_batch_bundle
|
"Serial and Batch Bundle", row.serial_and_batch_bundle
|
||||||
).set_serial_and_batch_values(self, row)
|
).set_serial_and_batch_values(self, row)
|
||||||
|
|
||||||
|
def on_trash(self):
|
||||||
|
self.remove_serial_and_batch_bundle()
|
||||||
|
|
||||||
def remove_serial_and_batch_bundle(self):
|
def remove_serial_and_batch_bundle(self):
|
||||||
for row in self.locations:
|
for row in self.locations:
|
||||||
if row.serial_and_batch_bundle:
|
if row.serial_and_batch_bundle:
|
||||||
@ -723,13 +727,14 @@ def get_available_item_locations(
|
|||||||
def get_available_item_locations_for_serialized_item(
|
def get_available_item_locations_for_serialized_item(
|
||||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
||||||
):
|
):
|
||||||
|
picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
|
||||||
|
|
||||||
sn = frappe.qb.DocType("Serial No")
|
sn = frappe.qb.DocType("Serial No")
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(sn)
|
frappe.qb.from_(sn)
|
||||||
.select(sn.name, sn.warehouse)
|
.select(sn.name, sn.warehouse)
|
||||||
.where((sn.item_code == item_code) & (sn.company == company))
|
.where((sn.item_code == item_code) & (sn.company == company))
|
||||||
.orderby(sn.creation)
|
.orderby(sn.creation)
|
||||||
.limit(cint(required_qty + total_picked_qty))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if from_warehouses:
|
if from_warehouses:
|
||||||
@ -742,6 +747,9 @@ def get_available_item_locations_for_serialized_item(
|
|||||||
warehouse_serial_nos_map = frappe._dict()
|
warehouse_serial_nos_map = frappe._dict()
|
||||||
picked_qty = required_qty
|
picked_qty = required_qty
|
||||||
for serial_no, warehouse in serial_nos:
|
for serial_no, warehouse in serial_nos:
|
||||||
|
if serial_no in picked_serial_nos:
|
||||||
|
continue
|
||||||
|
|
||||||
if picked_qty <= 0:
|
if picked_qty <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -786,7 +794,8 @@ def get_available_item_locations_for_batched_item(
|
|||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"warehouse": from_warehouses,
|
"warehouse": from_warehouses,
|
||||||
"qty": required_qty + total_picked_qty,
|
"qty": required_qty,
|
||||||
|
"is_pick_list": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1050,7 +1059,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def target_document_exists(pick_list_name, purpose):
|
def target_document_exists(pick_list_name, purpose):
|
||||||
if purpose == "Delivery":
|
if purpose == "Delivery":
|
||||||
return frappe.db.exists("Delivery Note", {"pick_list": pick_list_name})
|
return frappe.db.exists("Delivery Note", {"pick_list": pick_list_name, "docstatus": 1})
|
||||||
|
|
||||||
return stock_entry_exists(pick_list_name)
|
return stock_entry_exists(pick_list_name)
|
||||||
|
|
||||||
|
@ -644,6 +644,122 @@ class TestPickList(FrappeTestCase):
|
|||||||
so.reload()
|
so.reload()
|
||||||
self.assertEqual(so.per_picked, 50)
|
self.assertEqual(so.per_picked, 50)
|
||||||
|
|
||||||
|
def test_picklist_for_batch_item(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item(
|
||||||
|
properties={"is_stock_item": 1, "has_batch_no": 1, "batch_no_series": "PICKLT-.######"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
# create batch
|
||||||
|
for batch_id in ["PICKLT-000001", "PICKLT-000002"]:
|
||||||
|
if not frappe.db.exists("Batch", batch_id):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Batch",
|
||||||
|
"batch_id": batch_id,
|
||||||
|
"item": item,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item=item,
|
||||||
|
to_warehouse=warehouse,
|
||||||
|
qty=50,
|
||||||
|
basic_rate=100,
|
||||||
|
batches=frappe._dict({"PICKLT-000001": 30, "PICKLT-000002": 20}),
|
||||||
|
)
|
||||||
|
|
||||||
|
so = make_sales_order(item_code=item, qty=25.0, rate=100)
|
||||||
|
pl = create_pick_list(so.name)
|
||||||
|
# pick half the qty
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.qty, 25.0)
|
||||||
|
self.assertTrue(loc.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
fields=["qty", "batch_no"],
|
||||||
|
filters={"parent": loc.serial_and_batch_bundle},
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
self.assertEqual(d.batch_no, "PICKLT-000001")
|
||||||
|
self.assertEqual(d.qty, 25.0 * -1)
|
||||||
|
|
||||||
|
pl.save()
|
||||||
|
pl.submit()
|
||||||
|
|
||||||
|
so1 = make_sales_order(item_code=item, qty=10.0, rate=100)
|
||||||
|
pl = create_pick_list(so1.name)
|
||||||
|
# pick half the qty
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.qty, 10.0)
|
||||||
|
self.assertTrue(loc.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
fields=["qty", "batch_no"],
|
||||||
|
filters={"parent": loc.serial_and_batch_bundle},
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
self.assertTrue(d.batch_no in ["PICKLT-000001", "PICKLT-000002"])
|
||||||
|
if d.batch_no == "PICKLT-000001":
|
||||||
|
self.assertEqual(d.qty, 5.0 * -1)
|
||||||
|
elif d.batch_no == "PICKLT-000002":
|
||||||
|
self.assertEqual(d.qty, 5.0 * -1)
|
||||||
|
|
||||||
|
pl.save()
|
||||||
|
pl.submit()
|
||||||
|
pl.cancel()
|
||||||
|
|
||||||
|
def test_picklist_for_serial_item(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item(
|
||||||
|
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-PICKLT-.######"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_stock_entry(item=item, to_warehouse=warehouse, qty=50, basic_rate=100)
|
||||||
|
|
||||||
|
so = make_sales_order(item_code=item, qty=25.0, rate=100)
|
||||||
|
pl = create_pick_list(so.name)
|
||||||
|
picked_serial_nos = []
|
||||||
|
# pick half the qty
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.qty, 25.0)
|
||||||
|
self.assertTrue(loc.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Serial and Batch Entry", fields=["serial_no"], filters={"parent": loc.serial_and_batch_bundle}
|
||||||
|
)
|
||||||
|
|
||||||
|
picked_serial_nos = [d.serial_no for d in data]
|
||||||
|
self.assertEqual(len(picked_serial_nos), 25)
|
||||||
|
|
||||||
|
pl.save()
|
||||||
|
pl.submit()
|
||||||
|
|
||||||
|
so1 = make_sales_order(item_code=item, qty=10.0, rate=100)
|
||||||
|
pl = create_pick_list(so1.name)
|
||||||
|
# pick half the qty
|
||||||
|
for loc in pl.locations:
|
||||||
|
self.assertEqual(loc.qty, 10.0)
|
||||||
|
self.assertTrue(loc.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Serial and Batch Entry",
|
||||||
|
fields=["qty", "batch_no"],
|
||||||
|
filters={"parent": loc.serial_and_batch_bundle},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(data), 10)
|
||||||
|
for d in data:
|
||||||
|
self.assertTrue(d.serial_no not in picked_serial_nos)
|
||||||
|
|
||||||
|
pl.save()
|
||||||
|
pl.submit()
|
||||||
|
pl.cancel()
|
||||||
|
|
||||||
def test_picklist_with_bundles(self):
|
def test_picklist_with_bundles(self):
|
||||||
warehouse = "_Test Warehouse - _TC"
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
@ -732,7 +848,7 @@ class TestPickList(FrappeTestCase):
|
|||||||
|
|
||||||
dn.cancel()
|
dn.cancel()
|
||||||
pl.reload()
|
pl.reload()
|
||||||
self.assertEqual(pl.status, "Completed")
|
self.assertEqual(pl.status, "Open")
|
||||||
|
|
||||||
pl.cancel()
|
pl.cancel()
|
||||||
pl.reload()
|
pl.reload()
|
||||||
|
@ -936,7 +936,7 @@ def parse_csv_file_to_get_serial_batch(reader):
|
|||||||
if index == 0:
|
if index == 0:
|
||||||
has_serial_no = row[0] == "Serial No"
|
has_serial_no = row[0] == "Serial No"
|
||||||
has_batch_no = row[0] == "Batch No"
|
has_batch_no = row[0] == "Batch No"
|
||||||
if not has_batch_no:
|
if not has_batch_no and len(row) > 1:
|
||||||
has_batch_no = row[1] == "Batch No"
|
has_batch_no = row[1] == "Batch No"
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@ -1611,10 +1611,17 @@ def get_auto_batch_nos(kwargs):
|
|||||||
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
|
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
|
||||||
pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
|
pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
|
||||||
sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
|
sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
|
||||||
|
picked_batches = frappe._dict()
|
||||||
|
if kwargs.get("is_pick_list"):
|
||||||
|
picked_batches = get_picked_batches(kwargs)
|
||||||
|
|
||||||
if stock_ledgers_batches or pos_invoice_batches or sre_reserved_batches:
|
if stock_ledgers_batches or pos_invoice_batches or sre_reserved_batches or picked_batches:
|
||||||
update_available_batches(
|
update_available_batches(
|
||||||
available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
|
available_batches,
|
||||||
|
stock_ledgers_batches,
|
||||||
|
pos_invoice_batches,
|
||||||
|
sre_reserved_batches,
|
||||||
|
picked_batches,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not kwargs.consider_negative_batches:
|
if not kwargs.consider_negative_batches:
|
||||||
@ -1771,6 +1778,102 @@ def get_voucher_wise_serial_batch_from_bundle(**kwargs) -> Dict[str, Dict]:
|
|||||||
return group_by_voucher
|
return group_by_voucher
|
||||||
|
|
||||||
|
|
||||||
|
def get_picked_batches(kwargs) -> dict[str, dict]:
|
||||||
|
picked_batches = frappe._dict()
|
||||||
|
|
||||||
|
table = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
|
child_table = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
pick_list_table = frappe.qb.DocType("Pick List")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.inner_join(child_table)
|
||||||
|
.on(table.name == child_table.parent)
|
||||||
|
.inner_join(pick_list_table)
|
||||||
|
.on(table.voucher_no == pick_list_table.name)
|
||||||
|
.select(
|
||||||
|
child_table.batch_no,
|
||||||
|
child_table.warehouse,
|
||||||
|
Sum(child_table.qty).as_("qty"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(table.docstatus != 2)
|
||||||
|
& (pick_list_table.status != "Completed")
|
||||||
|
& (table.type_of_transaction == "Outward")
|
||||||
|
& (table.is_cancelled == 0)
|
||||||
|
& (table.voucher_type == "Pick List")
|
||||||
|
& (table.voucher_no.isnotnull())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if kwargs.get("item_code"):
|
||||||
|
query = query.where(table.item_code == kwargs.get("item_code"))
|
||||||
|
|
||||||
|
if kwargs.get("warehouse"):
|
||||||
|
if isinstance(kwargs.warehouse, list):
|
||||||
|
query = query.where(table.warehouse.isin(kwargs.warehouse))
|
||||||
|
else:
|
||||||
|
query = query.where(table.warehouse == kwargs.get("warehouse"))
|
||||||
|
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
for row in data:
|
||||||
|
if not row.qty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (row.batch_no, row.warehouse)
|
||||||
|
if key not in picked_batches:
|
||||||
|
picked_batches[key] = frappe._dict(
|
||||||
|
{
|
||||||
|
"qty": row.qty,
|
||||||
|
"warehouse": row.warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
picked_batches[key].qty += row.qty
|
||||||
|
|
||||||
|
return picked_batches
|
||||||
|
|
||||||
|
|
||||||
|
def get_picked_serial_nos(item_code, warehouse=None) -> list[str]:
|
||||||
|
table = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
|
child_table = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
pick_list_table = frappe.qb.DocType("Pick List")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.inner_join(child_table)
|
||||||
|
.on(table.name == child_table.parent)
|
||||||
|
.inner_join(pick_list_table)
|
||||||
|
.on(table.voucher_no == pick_list_table.name)
|
||||||
|
.select(
|
||||||
|
child_table.serial_no,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(table.docstatus != 2)
|
||||||
|
& (pick_list_table.status != "Completed")
|
||||||
|
& (table.type_of_transaction == "Outward")
|
||||||
|
& (table.is_cancelled == 0)
|
||||||
|
& (table.voucher_type == "Pick List")
|
||||||
|
& (table.voucher_no.isnotnull())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_code:
|
||||||
|
query = query.where(table.item_code == item_code)
|
||||||
|
|
||||||
|
if warehouse:
|
||||||
|
if isinstance(warehouse, list):
|
||||||
|
query = query.where(table.warehouse.isin(warehouse))
|
||||||
|
else:
|
||||||
|
query = query.where(table.warehouse == warehouse)
|
||||||
|
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
if not data:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [row.serial_no for row in data if row.serial_no]
|
||||||
|
|
||||||
|
|
||||||
def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
|
def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
|
||||||
bundle_table = frappe.qb.DocType("Serial and Batch Bundle")
|
bundle_table = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
serial_batch_table = frappe.qb.DocType("Serial and Batch Entry")
|
serial_batch_table = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user