From fe2161ea0cd5b479dd6aaf0d120efb6cd54b3133 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:58:02 +0530 Subject: [PATCH] fix(sales invoice): toggle Get Items From button based on is_return and POS view (backport #52594) (#54139) Co-authored-by: NaviN <118178330+Navin-S-R@users.noreply.github.com> Co-authored-by: Navin-S-R fix(sales invoice): toggle Get Items From button based on is_return and POS view (#52594) --- .../doctype/sales_invoice/sales_invoice.js | 172 ++++++++++-------- erpnext/controllers/queries.py | 59 +++--- 2 files changed, 131 insertions(+), 100 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 64728cd1e0a..90c0da74f26 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -165,13 +165,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( ); } } - - // Show buttons only when pos view is active - if (cint(doc.docstatus == 0) && this.frm.page.current_view_name !== "pos" && !doc.is_return) { - this.frm.cscript.sales_order_btn(); - this.frm.cscript.delivery_note_btn(); - this.frm.cscript.quotation_btn(); - } + this.toggle_get_items(); this.set_default_print_format(); if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) { @@ -260,6 +254,93 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( } } + toggle_get_items() { + const buttons = ["Sales Order", "Quotation", "Timesheet", "Delivery Note"]; + + buttons.forEach((label) => { + this.frm.remove_custom_button(label, "Get Items From"); + }); + + if (cint(this.frm.doc.docstatus) !== 0 || this.frm.page.current_view_name === "pos") { + return; + } + + if (!this.frm.doc.is_return) { + this.frm.cscript.sales_order_btn(); + this.frm.cscript.quotation_btn(); + this.frm.cscript.timesheet_btn(); + } + + this.frm.cscript.delivery_note_btn(); + } + + timesheet_btn() { + var me = this; + + me.frm.add_custom_button( + __("Timesheet"), + function () { + let d = new frappe.ui.Dialog({ + title: __("Fetch Timesheet"), + fields: [ + { + label: __("From"), + fieldname: "from_time", + fieldtype: "Date", + reqd: 1, + }, + { + label: __("Item Code"), + fieldname: "item_code", + fieldtype: "Link", + options: "Item", + get_query: () => { + return { + query: "erpnext.controllers.queries.item_query", + filters: { + is_sales_item: 1, + customer: me.frm.doc.customer, + has_variants: 0, + }, + }; + }, + }, + { + fieldtype: "Column Break", + fieldname: "col_break_1", + }, + { + label: __("To"), + fieldname: "to_time", + fieldtype: "Date", + reqd: 1, + }, + { + label: __("Project"), + fieldname: "project", + fieldtype: "Link", + options: "Project", + default: me.frm.doc.project, + }, + ], + primary_action: function () { + const data = d.get_values(); + me.frm.events.add_timesheet_data(me.frm, { + from_time: data.from_time, + to_time: data.to_time, + project: data.project, + item_code: data.item_code, + }); + d.hide(); + }, + primary_action_label: __("Get Timesheets"), + }); + d.show(); + }, + __("Get Items From") + ); + } + sales_order_btn() { var me = this; @@ -331,6 +412,12 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( this.$delivery_note_btn = this.frm.add_custom_button( __("Delivery Note"), function () { + if (!me.frm.doc.customer) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Customer"), + }); + } erpnext.utils.map_current_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", source_doctype: "Delivery Note", @@ -343,7 +430,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( var filters = { docstatus: 1, company: me.frm.doc.company, - is_return: 0, + is_return: me.frm.doc.is_return, }; if (me.frm.doc.customer) filters["customer"] = me.frm.doc.customer; return { @@ -610,6 +697,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( apply_tds(frm) { this.frm.clear_table("tax_withholding_entries"); } + + is_return() { + this.toggle_get_items(); + } }; // for backward compatibility: combine new and previous states @@ -1061,71 +1152,6 @@ frappe.ui.form.on("Sales Invoice", { }, refresh: function (frm) { - if (frm.doc.docstatus === 0 && !frm.doc.is_return) { - frm.add_custom_button( - __("Timesheet"), - function () { - let d = new frappe.ui.Dialog({ - title: __("Fetch Timesheet"), - fields: [ - { - label: __("From"), - fieldname: "from_time", - fieldtype: "Date", - reqd: 1, - }, - { - label: __("Item Code"), - fieldname: "item_code", - fieldtype: "Link", - options: "Item", - get_query: () => { - return { - query: "erpnext.controllers.queries.item_query", - filters: { - is_sales_item: 1, - customer: frm.doc.customer, - has_variants: 0, - }, - }; - }, - }, - { - fieldtype: "Column Break", - fieldname: "col_break_1", - }, - { - label: __("To"), - fieldname: "to_time", - fieldtype: "Date", - reqd: 1, - }, - { - label: __("Project"), - fieldname: "project", - fieldtype: "Link", - options: "Project", - default: frm.doc.project, - }, - ], - primary_action: function () { - const data = d.get_values(); - frm.events.add_timesheet_data(frm, { - from_time: data.from_time, - to_time: data.to_time, - project: data.project, - item_code: data.item_code, - }); - d.hide(); - }, - primary_action_label: __("Get Timesheets"), - }); - d.show(); - }, - __("Get Items From") - ); - } - if (frm.doc.is_debit_note) { frm.set_df_property("return_against", "label", __("Adjustment Against")); } diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 9b663c0d2b9..b2e7337a4ec 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -364,38 +364,43 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs -def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): - doctype = "Delivery Note" +def get_delivery_notes_to_be_billed( + doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict, as_dict: bool = False +): + DeliveryNote = frappe.qb.DocType("Delivery Note") + fields = get_fields(doctype, ["name", "customer", "posting_date"]) - return frappe.db.sql( - """ - select {fields} - from `tabDelivery Note` - where `tabDelivery Note`.`{key}` like {txt} and - `tabDelivery Note`.docstatus = 1 - and status not in ('Stopped', 'Closed') {fcond} - and ( - (`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100) - or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100) - or ( - `tabDelivery Note`.is_return = 1 - and return_against in (select name from `tabDelivery Note` where per_billed < 100) + original_dn = ( + frappe.qb.from_(DeliveryNote) + .select(DeliveryNote.name) + .where((DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0) & (DeliveryNote.per_billed > 0)) + ) + + query = ( + frappe.qb.from_(DeliveryNote) + .select(*[DeliveryNote[f] for f in fields]) + .where( + (DeliveryNote.docstatus == 1) + & (DeliveryNote.status.notin(["Stopped", "Closed"])) + & (DeliveryNote[searchfield].like(f"%{txt}%")) + & ( + ((DeliveryNote.is_return == 0) & (DeliveryNote.per_billed < 100)) + | ((DeliveryNote.grand_total == 0) & (DeliveryNote.per_billed < 100)) + | ( + (DeliveryNote.is_return == 1) + & (DeliveryNote.per_billed < 100) + & (DeliveryNote.return_against.isin(original_dn)) ) ) - {mcond} order by `tabDelivery Note`.`{key}` asc limit {page_len} offset {start} - """.format( - fields=", ".join([f"`tabDelivery Note`.{f}" for f in fields]), - key=searchfield, - fcond=get_filters_cond(doctype, filters, []), - mcond=get_match_cond(doctype), - start=start, - page_len=page_len, - txt="%(txt)s", - ), - {"txt": ("%%%s%%" % txt)}, - as_dict=as_dict, + ) ) + if filters and isinstance(filters, dict): + for key, value in filters.items(): + query = query.where(DeliveryNote[key] == value) + + query = query.orderby(DeliveryNote[searchfield], order=Order.asc).limit(page_len).offset(start) + return query.run(as_dict=as_dict) @frappe.whitelist()