diff --git a/erpnext/accounts/bulk_payment.py b/erpnext/accounts/bulk_payment.py index 6e37969205f..37ccafba44d 100644 --- a/erpnext/accounts/bulk_payment.py +++ b/erpnext/accounts/bulk_payment.py @@ -51,7 +51,13 @@ def get_payable_invoices(invoices: str | list | None = None): names = [d["voucher_no"] for d in frappe.parse_json(invoices or "[]") if d.get("voucher_no")] payable, excluded = _partition_payable_invoices(names) - return {"payable": payable, "excluded": excluded} + + currency = None + if payable: + company = frappe.get_cached_value("Purchase Invoice", payable[0]["voucher_no"], "company") + currency = frappe.get_cached_value("Company", company, "default_currency") + + return {"payable": payable, "excluded": excluded, "currency": currency} def _partition_payable_invoices(names): @@ -63,7 +69,7 @@ def _partition_payable_invoices(names): if not names: return [], [] - rows = frappe.get_all( + rows = frappe.get_list( "Purchase Invoice", filters={"name": ["in", names], "docstatus": 1}, fields=[ @@ -75,6 +81,7 @@ def _partition_payable_invoices(names): "is_return", "is_internal_supplier", ], + limit_page_length=0, ) payable, excluded = [], [] @@ -95,6 +102,12 @@ def _partition_payable_invoices(names): } ) + # names not returned were cancelled/deleted or no longer readable after the report loaded + found = {r.name for r in rows} + for name in names: + if name not in found: + excluded.append({"voucher_no": name, "reason": _("Not available")}) + return payable, excluded diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 12a6a5c56ba..b0823a7c702 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -239,17 +239,17 @@ function create_payment_entries_from_payable_report(report) { method: "erpnext.accounts.bulk_payment.get_payable_invoices", args: { invoices: rows.map((r) => ({ voucher_no: r.voucher_no })) }, callback: ({ message }) => { - const { payable = [], excluded = [] } = message || {}; + const { payable = [], excluded = [], currency } = message || {}; if (!payable.length) { frappe.msgprint(__("None of the selected invoices are payable")); return; } - show_create_payment_entries_dialog(report, payable, excluded); + show_create_payment_entries_dialog(report, payable, excluded, currency); }, }); } -function show_create_payment_entries_dialog(report, payable, excluded) { +function show_create_payment_entries_dialog(report, payable, excluded, currency) { // group by (supplier, party_account) for the overview — matches the backend grouping key const supplierMap = {}; for (const inv of payable) { @@ -319,6 +319,14 @@ function show_create_payment_entries_dialog(report, payable, excluded) { })), }); + const pe_count = Object.keys(supplierMap).length; + const grand_total = Object.values(supplierMap).reduce((sum, d) => sum + d.outstanding, 0); + fields.push({ + fieldtype: "HTML", + fieldname: "summary_footer", + options: summary_footer_html(pe_count, grand_total, currency), + }); + const dialog = new frappe.ui.Dialog({ title: __("Create Payment Entries"), fields: fields, @@ -348,6 +356,20 @@ function show_create_payment_entries_dialog(report, payable, excluded) { dialog.show(); } +function summary_footer_html(pe_count, grand_total, currency) { + return `
+ ${__("Payment Entries are created as drafts for your review")} + ${__("{0} Payment Entries", [pe_count])} · + ${format_currency(grand_total, currency)} +
`; +} + function excluded_note_html(excluded) { const counts = {}; for (const e of excluded) {