Merge pull request #62960 from aerele/fix/pick-list-bundle-second-delivery-note

fix(stock): map only undelivered bundles from a pick list and ask about drafts before creating the delivery note
This commit is contained in:
mergify[bot]
2026-10-29 13:02:20 +00:00
committed by GitHub
5 changed files with 44 additions and 8 deletions
+7 -3
View File
@@ -35,9 +35,13 @@ erpnext.utils.confirm_if_drafts_exist = async function (source_doc, target_docty
};
if (frappe.model.add_mapped_doc_guard) {
frappe.model.add_mapped_doc_guard((mapped_doc, opts) =>
erpnext.utils.confirm_if_drafts_exist(opts.frm && opts.frm.doc, mapped_doc.doctype)
);
frappe.model.add_mapped_doc_guard((mapped_doc, opts) => {
// a mapper that saved the target already created the draft, so ask before calling it instead
if (mapped_doc.name && !mapped_doc.__islocal) {
return true;
}
return erpnext.utils.confirm_if_drafts_exist(opts.frm && opts.frm.doc, mapped_doc.doctype);
});
}
function get_draft_warning(source_doc, target_doctype, drafts) {
+5 -1
View File
@@ -360,8 +360,12 @@ def add_product_bundles_to_target(
)
)
pending_qty = pick_list._compute_picked_qty_for_bundle(so_row, bundle_items, pending_only=True)
if not pending_qty:
continue
target_bundle_item = map_child_doc(sales_order_item, target_doc, item_mapper)
target_bundle_item.qty = pick_list._compute_picked_qty_for_bundle(so_row, bundle_items)
target_bundle_item.qty = pending_qty
target_bundle_item.against_pick_list = pick_list.name
update_child_item(sales_order_item, target_bundle_item, target_doc)
+5 -1
View File
@@ -241,7 +241,11 @@ frappe.ui.form.on("Pick List", {
frm.clear_table("locations");
frm.trigger("add_get_items_button");
},
create_delivery(frm, doctype) {
async create_delivery(frm, doctype) {
if (!(await erpnext.utils.confirm_if_drafts_exist(frm.doc, doctype))) {
return;
}
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.pick_list.mapper.create_delivery",
args: {
+4 -3
View File
@@ -1080,8 +1080,8 @@ class PickList(TransactionBase):
}
return product_bundle_qty_map
def _compute_picked_qty_for_bundle(self, bundle_row, bundle_items) -> int:
"""Compute how many full bundles can be created from picked items."""
def _compute_picked_qty_for_bundle(self, bundle_row, bundle_items, pending_only=False) -> int:
"""Compute how many full bundles can be created from picked (or picked but undelivered) items."""
precision = frappe.get_precision("Stock Ledger Entry", "qty_after_transaction")
possible_bundles = {}
for item in self.locations:
@@ -1089,8 +1089,9 @@ class PickList(TransactionBase):
continue
if qty_in_bundle := bundle_items.get(item.item_code):
qty = flt(item.picked_qty) - (flt(item.delivered_qty) if pending_only else 0)
possible_bundles.setdefault(item.product_bundle_item, 0)
possible_bundles[item.product_bundle_item] += item.picked_qty / qty_in_bundle
possible_bundles[item.product_bundle_item] += qty / qty_in_bundle
return int(flt(min(possible_bundles.values()), precision or 6)) if possible_bundles else 0
@@ -2197,6 +2197,29 @@ class TestPickList(ERPNextTestSuite):
so.reload()
self.assertEqual(so.per_delivered, 100)
def test_second_delivery_note_maps_only_undelivered_bundles(self):
warehouse = "_Test Warehouse - _TC"
bundle, components = create_product_bundle([5, 2], warehouse=warehouse)
so = make_sales_order(item_code=bundle, qty=4, rate=42)
pl = create_pick_list(so.name).save().submit()
first_dn = create_delivery_note(pl.name)
first_dn.items[0].qty = 1
first_dn.save().submit()
second_dn = create_delivery_note(pl.name)
self.assertEqual(second_dn.items[0].qty, 3)
packed_qty = {row.item_code: row.qty for row in second_dn.packed_items}
self.assertEqual(packed_qty, {components[0]: 15, components[1]: 6})
second_dn.submit()
so.reload()
self.assertEqual(so.items[0].delivered_qty, 4)
self.assertEqual(so.per_delivered, 100)
pl.reload()
self.assertEqual(pl.per_delivered, 100)
self.assertEqual(pl.status, "Completed")
def test_pick_list_status(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(properties={"is_stock_item": 1}).name