feat: UI for unreconcile
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 642e99c..fe931ee 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -183,6 +183,50 @@
 				}, __('Create'));
 			}
 		}
+
+		if (doc.docstatus == 1) {
+			frappe.call({
+				"method": "erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.doc_has_payments",
+				"args": {
+					"doctype": this.frm.doc.doctype,
+					"docname": this.frm.doc.name
+				},
+				callback: function(r) {
+					if (r.message) {
+						me.frm.add_custom_button(__("Un-Reconcile"), function() {
+							me.unreconcile_prompt();
+						});
+					}
+				}
+			});
+		}
+	}
+
+	unreconcile_prompt() {
+		// get linked payments
+		let query_args = {
+			query:"erpnext.accounts.doctype.unreconcile_payments.unreconcile_payments.get_linked_payments_for_doc",
+			filters: {
+				doctype: this.frm.doc.doctype,
+				docname: this.frm.doc.name
+			}
+		}
+
+		new frappe.ui.form.MultiSelectDialog({
+			doctype: "Payment Ledger Entry",
+			target: this.cur_frm,
+			setters: { },
+			add_filters_group: 0,
+			date_field: "posting_date",
+			columns: ["voucher_type", "voucher_no", "allocated_amount"],
+			get_query() {
+				return query_args;
+			},
+			action(selections) {
+				console.log(selections);
+			}
+		});
+
 	}
 
 	make_maintenance_schedule() {
diff --git a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py b/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py
index ab2cc71..ed978cb 100644
--- a/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py
+++ b/erpnext/accounts/doctype/unreconcile_payments/unreconcile_payments.py
@@ -4,7 +4,7 @@
 import frappe
 from frappe import qb
 from frappe.model.document import Document
-from frappe.query_builder.functions import Sum
+from frappe.query_builder.functions import Abs, Sum
 
 from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries, update_voucher_outstanding
 
@@ -55,3 +55,50 @@
 				alloc.reference_doctype, alloc.reference_name, account, party_type, party
 			)
 			frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
+
+
+@frappe.whitelist()
+def doc_has_payments(doctype, docname):
+	if doctype in ["Sales Invoice", "Purchase Invoice"]:
+		return frappe.db.count(
+			"Payment Ledger Entry",
+			filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
+		)
+	else:
+		return frappe.db.count(
+			"Payment Ledger Entry",
+			filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
+		)
+
+
+@frappe.whitelist()
+def get_linked_payments_for_doc(doctype, txt, searchfield, start, page_len, filters):
+	if filters.get("doctype") and filters.get("docname"):
+		_dt = filters.get("doctype")
+		_dn = filters.get("docname")
+		ple = qb.DocType("Payment Ledger Entry")
+		if _dt in ["Sales Invoice", "Purchase Invoice"]:
+			res = (
+				qb.from_(ple)
+				.select(
+					ple.voucher_type,
+					ple.voucher_no,
+					Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
+				)
+				.where((ple.delinked == 0) & (ple.against_voucher_no == _dn) & (ple.amount < 0))
+				.groupby(ple.against_voucher_no)
+				.run(as_dict=True)
+			)
+			return res
+		else:
+			return frappe.db.get_all(
+				"Payment Ledger Entry",
+				filters={
+					"delinked": 0,
+					"voucher_no": _dn,
+					"against_voucher_no": ["!=", _dn],
+					"amount": ["<", 0],
+				},
+				group_by="against_voucher_no",
+				fields=["against_voucher_type", "against_voucher_no", "Sum(amount_in_account_currency)"],
+			)