feat: select child item when creating one document from another

(cherry picked from commit a9936ae133)
This commit is contained in:
Mihir Kandoi
2025-08-12 20:27:41 +05:30
committed by Mergify
parent 76b0f4fb25
commit 5f06d87f01
12 changed files with 168 additions and 27 deletions

View File

@@ -153,6 +153,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
per_billed: ["<", 99.99],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")
@@ -175,6 +178,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
company: me.frm.doc.company,
is_return: 0,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _, qb, throw
from frappe.model.mapper import get_mapped_doc
@@ -2070,7 +2072,12 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
def make_purchase_receipt(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.received_qty)
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
@@ -2080,6 +2087,11 @@ def make_purchase_receipt(source_name, target_doc=None):
(flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
)
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doc = get_mapped_doc(
"Purchase Invoice",
source_name,
@@ -2103,7 +2115,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
},

View File

@@ -259,6 +259,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
per_billed: ["<", 99.99],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")
@@ -288,6 +291,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
status: ["!=", "Lost"],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
});
},
__("Get Items From")
@@ -319,6 +325,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
filters: filters,
};
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
});
},
__("Get Items From")

View File

@@ -724,7 +724,12 @@ def set_missing_values(source, target):
@frappe.whitelist()
def make_purchase_receipt(source_name, target_doc=None):
def make_purchase_receipt(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
has_unit_price_items = frappe.db.get_value("Purchase Order", source_name, "has_unit_price_items")
def is_unit_price_row(source):
@@ -738,6 +743,11 @@ def make_purchase_receipt(source_name, target_doc=None):
(flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
)
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doc = get_mapped_doc(
"Purchase Order",
source_name,
@@ -765,7 +775,8 @@ def make_purchase_receipt(source_name, target_doc=None):
"condition": lambda doc: (
True if is_unit_price_row(doc) else abs(doc.received_qty) < abs(doc.qty)
)
and doc.delivered_by_supplier != 1,
and doc.delivered_by_supplier != 1
and select_item(doc),
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
},
@@ -777,8 +788,8 @@ def make_purchase_receipt(source_name, target_doc=None):
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
return get_mapped_purchase_invoice(source_name, target_doc)
def make_purchase_invoice(source_name, target_doc=None, args=None):
return get_mapped_purchase_invoice(source_name, target_doc, args=args)
@frappe.whitelist()
@@ -792,7 +803,12 @@ def make_purchase_invoice_from_portal(purchase_order_name):
frappe.response.location = "/purchase-invoices/" + doc.name
def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False):
def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
def postprocess(source, target):
target.flags.ignore_permissions = ignore_permissions
set_missing_values(source, target)
@@ -832,6 +848,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
or item_group.get("buying_cost_center")
)
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
fields = {
"Purchase Order": {
"doctype": "Purchase Invoice",
@@ -854,7 +875,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount))
and select_item(doc),
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
}

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _
from frappe.model.mapper import get_mapped_doc
@@ -347,7 +349,7 @@ def get_list_context(context=None):
@frappe.whitelist()
def make_sales_order(source_name: str, target_doc=None):
def make_sales_order(source_name: str, target_doc=None, args=None):
if not frappe.db.get_singles_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
):
@@ -359,10 +361,15 @@ def make_sales_order(source_name: str, target_doc=None):
):
frappe.throw(_("Validity period of this quotation has ended."))
return _make_sales_order(source_name, target_doc)
return _make_sales_order(source_name, target_doc, args=args)
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
customer = _make_customer(source_name, ignore_permissions)
ordered_items = get_ordered_items(source_name)
@@ -430,6 +437,11 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
# Simple row
return True
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doclist = get_mapped_doc(
"Quotation",
source_name,
@@ -439,7 +451,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
"doctype": "Sales Order Item",
"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
"postprocess": update_item,
"condition": can_map_row,
"condition": lambda d: can_map_row(d) and select_item(d),
},
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
@@ -476,11 +488,16 @@ def set_expired_status():
@frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None):
return _make_sales_invoice(source_name, target_doc)
def make_sales_invoice(source_name, target_doc=None, args=None):
return _make_sales_invoice(source_name, target_doc, args=args)
def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
customer = _make_customer(source_name, ignore_permissions)
def set_missing_values(source, target):
@@ -496,6 +513,11 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
target.cost_center = None
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doclist = get_mapped_doc(
"Quotation",
source_name,
@@ -504,7 +526,7 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
"Quotation Item": {
"doctype": "Sales Invoice Item",
"postprocess": update_item,
"condition": lambda row: not row.is_alternative,
"condition": lambda row: not row.is_alternative and select_item(row),
},
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},

View File

@@ -793,6 +793,9 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
docstatus: 1,
status: ["!=", "Lost"],
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
});
setTimeout(() => {

View File

@@ -977,6 +977,11 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
def is_unit_price_row(source):
return has_unit_price_items and source.qty == 0
def select_item(d):
filtered_items = kwargs.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
def set_missing_values(source, target):
if kwargs.get("ignore_pricing_rule"):
# Skip pricing rule when the dn is creating from the pick list
@@ -1042,7 +1047,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
"name": "so_detail",
"parent": "against_sales_order",
},
"condition": condition,
"condition": lambda d: condition(d) and select_item(d),
"postprocess": update_item,
}
@@ -1098,7 +1103,12 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
@frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
# 0 qty is accepted, as the qty is uncertain for some items
has_unit_price_items = frappe.db.get_value("Sales Order", source_name, "has_unit_price_items")
@@ -1158,6 +1168,11 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
if cost_center:
target.cost_center = cost_center
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doclist = get_mapped_doc(
"Sales Order",
source_name,
@@ -1182,7 +1197,8 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
True
if is_unit_price_row(doc)
else (doc.qty and (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)))
),
)
and select_item(doc),
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",

View File

@@ -182,6 +182,9 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
company: me.frm.doc.company,
project: me.frm.doc.project || undefined,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "delivered_qty"],
});
},
__("Get Items From")
@@ -231,6 +234,9 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
},
get_query_method: "erpnext.stock.doctype.pick_list.pick_list.get_pick_list_query",
size: "extra-large",
allow_child_item_selection: true,
child_fieldname: "locations",
child_columns: ["item_code", "item_name", "stock_qty", "delivered_qty"],
});
},
__("Get Items From")

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import get_company_address
@@ -824,6 +826,11 @@ def get_returned_qty_map(delivery_note):
@frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
doc = frappe.get_doc("Delivery Note", source_name)
to_make_invoice_qty_map = {}
@@ -875,6 +882,11 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
return pending_qty
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doc = get_mapped_doc(
"Delivery Note",
source_name,
@@ -897,6 +909,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
"filter": lambda d: get_pending_qty(d) <= 0
if not doc.get("is_return")
else get_pending_qty(d) > 0,
"condition": select_item,
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",

View File

@@ -1252,11 +1252,16 @@ def create_dn_wo_so(pick_list, delivery_note=None):
@frappe.whitelist()
def create_dn_for_pick_lists(source_name, target_doc=None, kwargs=None):
"""Get Items from Multiple Pick Lists and create a Delivery Note for filtered customer"""
if kwargs is None:
kwargs = {}
if isinstance(kwargs, str):
kwargs = json.loads(kwargs)
pick_list = frappe.get_doc("Pick List", source_name)
validate_item_locations(pick_list)
sales_order_arg = kwargs.get("sales_order") if kwargs else None
customer_arg = kwargs.get("customer") if kwargs else None
sales_order_arg = kwargs.get("sales_order")
customer_arg = kwargs.get("customer")
if sales_order_arg:
sales_orders = {sales_order_arg}
@@ -1270,7 +1275,7 @@ def create_dn_for_pick_lists(source_name, target_doc=None, kwargs=None):
pluck="name",
)
delivery_note = create_dn_from_so(pick_list, sales_orders, delivery_note=target_doc)
delivery_note = create_dn_from_so(pick_list, sales_orders, delivery_note=target_doc, kwargs=kwargs)
if not sales_order_arg and not all(item.sales_order for item in pick_list.locations):
if isinstance(delivery_note, str):
@@ -1296,10 +1301,15 @@ def create_dn_with_so(sales_dict, pick_list):
return delivery_note
def create_dn_from_so(pick_list, sales_order_list, delivery_note=None):
def create_dn_from_so(pick_list, sales_order_list, delivery_note=None, kwargs=None):
if not sales_order_list:
return delivery_note
def select_item(d):
filtered_items = kwargs.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
item_table_mapper = {
"doctype": "Delivery Note Item",
"field_map": {
@@ -1307,7 +1317,9 @@ def create_dn_from_so(pick_list, sales_order_list, delivery_note=None):
"name": "so_detail",
"parent": "against_sales_order",
},
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
and doc.delivered_by_supplier != 1
and select_item(doc),
}
kwargs = {"skip_item_mapping": True, "ignore_pricing_rule": pick_list.ignore_pricing_rule}

View File

@@ -150,7 +150,11 @@ frappe.ui.form.on("Purchase Receipt", {
docstatus: 1,
per_received: ["<", 100],
company: frm.doc.company,
update_stock: 0,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "received_qty"],
});
},
__("Get Items From")
@@ -255,6 +259,9 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
per_received: ["<", 99.99],
company: me.frm.doc.company,
},
allow_child_item_selection: true,
child_fieldname: "items",
child_columns: ["item_code", "item_name", "qty", "received_qty"],
});
},
__("Get Items From")

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _, throw
from frappe.desk.notifications import clear_doctype_notifications
@@ -1225,6 +1227,11 @@ def get_item_wise_returned_qty(pr_doc):
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
from erpnext.accounts.party import get_payment_terms_template
doc = frappe.get_doc("Purchase Receipt", source_name)
@@ -1279,6 +1286,11 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
return pending_qty, returned_qty
def select_item(d):
filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return child_filter
doclist = get_mapped_doc(
"Purchase Receipt",
source_name,
@@ -1308,9 +1320,10 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
"filter": lambda d: get_pending_qty(d)[0] <= 0
if not doc.get("is_return")
else get_pending_qty(d)[0] > 0,
"filter": lambda d: (
get_pending_qty(d)[0] <= 0 if not doc.get("is_return") else get_pending_qty(d)[0] > 0
)
and select_item(d),
},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",