diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 5bf15ac325b..17fad2eca43 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -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") diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index abbdd5523c8..4f0d6a64c36 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -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"}, }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index db9083a9f91..4978f95a132 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -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") diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 246690c3e39..0f4d7cf496c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -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}, } diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 70084764799..d6b2fe73cac 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -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}, diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 78b819dc46a..e36f34b012f 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -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(() => { diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 9b14616373b..75260a5a3fa 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -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", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 440e104abb6..0cd4e24ff93 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -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") diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 762664bd566..6cadbdc5e47 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -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", diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 2420c166161..9417751f682 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -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} diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index bcecf8be14d..db065a80c92 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -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") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 9e7dea631a9..1c4d28c495e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -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",