Skip to content

Conversation

@MeetSherasiya
Copy link

This PR introduces an optional feature to automatically display the
current Customer/Supplier ledger balance (Receivable/Payable) directly
inside transaction forms such as Sales Invoice, Sales Order, Delivery Note,
Purchase Invoice, Purchase Order, and Purchase Receipt.

Why this is needed

Users currently need to manually navigate to Customer/Supplier Ledger
or AR/AP reports to verify outstanding balances while creating
transactions. This breaks workflow efficiency and increases the risk
of incorrect entries.

What this PR adds

  1. New option in Accounts Settings:

    • "Show Party Ledger Balance in Transactions"
    • When enabled, ERPNext will:
      • Fetch current balance using existing get_balance_on
      • Display it under Customer/Supplier field
  2. Applies to the following doctypes:
    • Sales Invoice
    • Sales Order
    • Delivery Note
    • Purchase Invoice
    • Purchase Order
    • Purchase Receipt

  3. Fully client-side + server-side integration ensuring:

    • Fast fetch
    • Minimal DB load
    • No UI blocking
    • Configurable behavior via Accounts Settings

How it works

A small JS controller enhancement listens to Customer/Supplier selection.
It triggers a server-side method that returns current ledger balance
based on company and party. The result is rendered below the
field with conditional color formatting with real time balance.

Screenshots

Screenshot 2025-11-22 at 12 18 42 Screenshot 2025-11-22 at 12 17 52

@github-actions github-actions bot added the needs-tests This PR needs automated unit-tests. label Nov 22, 2025
@MeetSherasiya
Copy link
Author

@DipenFrappe
Kindly review this PR. It is based on the feature request mentioned in the following reference issue:
#50463

I have developed the requested feature. If everything looks good, please consider merging it into the upcoming version.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 22, 2025

📝 Walkthrough

Walkthrough

This PR introduces a feature to display General Ledger balances for customers and suppliers in buying and sales documents. It adds a new configuration field show_gl_balance_in_document to Accounts Settings, implements a client-side utility function get_party_gl_balance() to fetch and display party balances, and integrates this function into the refresh and party selection handlers of buying and sales document forms.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • get_party_gl_balance() function in party.js: Contains multiple conditional branches for document type detection, field mapping, balance fetching, and HTML formatting with colored display logic that warrants careful review for edge cases and security considerations.
  • Integration points (buying.js and sales_common.js): While repetitive, verify the placement of calls relative to existing form logic and ensure proper timing relative to other field updates.
  • HTML snippet generation: Review the balance display HTML formatting to ensure proper escaping and no potential XSS vulnerabilities.
  • Configuration flag handling: Verify that the new show_gl_balance_in_document setting is properly propagated and checked.

Suggested labels

accounts, buying, needs-tests

Suggested reviewers

  • ruthra-kumar
  • rohitwaghchaure
  • mihir-kandoi

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a feature to display party GL balance in purchase and sales documents during creation.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the feature's purpose, implementation, affected doctypes, and including screenshots.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47a6d34 and d7e83cb.

📒 Files selected for processing (5)
  • erpnext/accounts/doctype/accounts_settings/accounts_settings.json (4 hunks)
  • erpnext/accounts/doctype/accounts_settings/accounts_settings.py (1 hunks)
  • erpnext/public/js/controllers/buying.js (2 hunks)
  • erpnext/public/js/utils/party.js (1 hunks)
  • erpnext/public/js/utils/sales_common.js (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-16T16:00:08.157Z
Learnt from: barredterra
Repo: frappe/erpnext PR: 50159
File: erpnext/public/js/utils/sales_common.js:122-125
Timestamp: 2025-11-16T16:00:08.157Z
Learning: In ERPNext sales transactions (erpnext/public/js/utils/sales_common.js and erpnext/controllers/selling_controller.py), the company_contact_person field has different update behaviors: Frontend set_default_company_contact_person() should update the contact when the company field changes (intentional override on user action), while backend set_company_contact_person() in set_missing_values() should only fill empty fields (no override during save operations).

Applied to files:

  • erpnext/public/js/utils/sales_common.js
  • erpnext/public/js/controllers/buying.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
erpnext/public/js/controllers/buying.js (1)

141-145: Call sites for get_party_gl_balance look correct

Wiring this helper into refresh() and supplier() is consistent with the feature’s intent (showing balance on load and on supplier change). Once the helper’s own logic is fixed (see party.js comment), these entry points don’t need further adjustment.

Also applies to: 160-166

erpnext/public/js/utils/sales_common.js (1)

105-119: Selling controller integration is aligned with buying flow

Invoking erpnext.utils.get_party_gl_balance(this.frm) in both refresh() and customer() mirrors the buying controller and ensures the balance is refreshed on load and customer change. No additional guards are required at these call sites; remaining concerns are confined to the helper’s implementation.

Also applies to: 170-176

erpnext/accounts/doctype/accounts_settings/accounts_settings.py (1)

69-80: Type hint for show_gl_balance_in_document is correct

The added DF.Check annotation matches the JSON field definition and keeps the auto-generated types in sync with the new setting.

Comment on lines +43 to 45
"balance_view_settings_section",
"show_gl_balance_in_document",
"item_price_settings_section",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tidy up field_order and align label with feature intent

  • field_order now lists "column_break_xrnd" twice (Lines 52–55 and 104–105). This is harmless but noisy; consider dropping the trailing occurrence to keep the order list clean.
  • The new option label "Show GL Balance in Document" is functional but vague given this feature specifically shows the party’s ledger balance in transactions. Consider aligning the label/help text with that intent (e.g. “Show Party Ledger Balance in Transactions”) so users immediately understand what is being toggled.

Also applies to: 104-106, 654-675

🤖 Prompt for AI Agents
In erpnext/accounts/doctype/accounts_settings/accounts_settings.json around
lines 43–45 (and also review and apply the same changes at 104–106 and 654–675),
tidy up the field_order array by removing the duplicate "column_break_xrnd"
trailing occurrence so the order list is not noisy, and rename the label for
"show_gl_balance_in_document" to a clearer text such as "Show Party Ledger
Balance in Transactions" and update its help/description to reflect that this
toggles display of the party’s ledger balance in transaction documents; make
these exact edits in the JSON entries at the referenced line ranges.

Comment on lines +127 to +183
erpnext.utils.get_party_gl_balance = function (frm) {
frappe.db.get_single_value("Accounts Settings", "show_gl_balance_in_document").then((value) => {
if (!value) {
return
}
});

let args = {};

if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
args = {
party: frm.doc.customer || frm.doc.party_name,
party_type: "Customer",
};
}

if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
args = {
party: frm.doc.supplier,
party_type: "Supplier",
};
}

party_field = args.doctype == "Quotation" ? "party_name" : args.party_type == "Customer" ? "customer" : "supplier"

if(args.party && frm.doc.docstatus == 0) {
frappe.call({
method: "erpnext.accounts.utils.get_balance_on",
args: {
company: frm.doc.company,
party_type: args.party_type,
party: args.party,
},
callback: function(r) {
if (r.message) {
let balance = r.message;

if(balance == 0) {
frm.set_df_property(party_field, "description", "");
return
}
let color = balance <= 0 ? "green" : "red";
let html = `<b>Current GL Balance:</b>
<span style="color:${color}; font-weight:bold;">
${format_currency(balance, frm.doc.currency || "INR")}
</span>`;

frm.set_df_property(party_field, "description", html);
} else {
frm.set_df_property(party_field, "description", "");
}
}
});
} else {
frm.set_df_property(party_field, "description", "");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

get_party_gl_balance ignores the setting, mis-detects Quotation, and leaks a global

Current implementation has several functional issues:

  1. Settings flag is not respected
    The call to frappe.db.get_single_value(...).then(...) only gates code inside its callback. All of the balance‑fetching logic lives after the promise, so it runs regardless of show_gl_balance_in_document. The setting cannot disable the feature and you still issue DB + GL balance calls on every invocation.

  2. Quotation special-case never triggers
    party_field is computed using args.doctype == "Quotation", but args.doctype is never set. This condition is always false, so Quotation falls through to "customer"/"supplier" instead of "party_name".

  3. Global variable pollution and weak guards
    party_field is assigned without let/const/var, creating a global and risking collisions. Additionally, if this helper is ever called for a doctype outside SALES_DOCTYPES / PURCHASE_DOCTYPES, args stays empty but you still compute and use party_field based on undefined properties.

Fixing these in one place will make all call sites behave correctly and avoid unnecessary load. A concrete refactor:

-erpnext.utils.get_party_gl_balance = function (frm) {
-	frappe.db.get_single_value("Accounts Settings", "show_gl_balance_in_document").then((value) => {
-		if (!value) {
-			return
-		}
-	});
-
-	let args = {};
-
-	if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
-			args = {
-				party: frm.doc.customer || frm.doc.party_name,
-				party_type: "Customer",
-			};
-		}
-
-		if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
-			args = {
-				party: frm.doc.supplier,
-				party_type: "Supplier",
-			};
-		}
-
-	party_field = args.doctype == "Quotation" ? "party_name" : args.party_type == "Customer" ? "customer" : "supplier"
-
-	if(args.party && frm.doc.docstatus == 0) {
-		frappe.call({
-			method: "erpnext.accounts.utils.get_balance_on",
-			args: {
-				company: frm.doc.company,
-				party_type: args.party_type,
-				party: args.party,
-			},
-			callback: function(r) {
-				if (r.message) {
-					let balance = r.message;
-
-					if(balance == 0) {
-						frm.set_df_property(party_field, "description", "");
-						return
-					}
-					let color = balance <= 0 ? "green" : "red";
-					let html = `<b>Current GL Balance:</b>
-						<span style="color:${color}; font-weight:bold;">
-							${format_currency(balance, frm.doc.currency || "INR")}
-						</span>`;
-
-					frm.set_df_property(party_field, "description", html);
-				} else {
-					frm.set_df_property(party_field, "description", "");
-				}
-			}
-		});
-	} else {
-		frm.set_df_property(party_field, "description", "");
-	}
-}
+erpnext.utils.get_party_gl_balance = function (frm) {
+	frappe.db
+		.get_single_value("Accounts Settings", "show_gl_balance_in_document")
+		.then((value) => {
+			// Respect Accounts Settings toggle
+			if (!value) {
+				return;
+			}
+
+			let args = {};
+
+			if (in_list(SALES_DOCTYPES, frm.doc.doctype)) {
+				args = {
+					party: frm.doc.customer || frm.doc.party_name,
+					party_type: "Customer",
+					doctype: frm.doc.doctype,
+				};
+			} else if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) {
+				args = {
+					party: frm.doc.supplier,
+					party_type: "Supplier",
+					doctype: frm.doc.doctype,
+				};
+			} else {
+				// Not a supported doctype; nothing to do
+				return;
+			}
+
+			const party_field =
+				args.doctype === "Quotation"
+					? "party_name"
+					: args.party_type === "Customer"
+						? "customer"
+						: "supplier";
+
+			if (args.party && frm.doc.docstatus === 0) {
+				frappe.call({
+					method: "erpnext.accounts.utils.get_balance_on",
+					args: {
+						company: frm.doc.company,
+						party_type: args.party_type,
+						party: args.party,
+					},
+					callback(r) {
+						if (r.message) {
+							const balance = r.message;
+
+							if (balance == 0) {
+								frm.set_df_property(party_field, "description", "");
+								return;
+							}
+
+							const color = balance <= 0 ? "green" : "red";
+							const html = `<b>Current GL Balance:</b>
+								<span style="color:${color}; font-weight:bold;">
+									${format_currency(balance, frm.doc.currency || "INR")}
+								</span>`;
+
+							frm.set_df_property(party_field, "description", html);
+						} else {
+							frm.set_df_property(party_field, "description", "");
+						}
+					},
+				});
+			} else {
+				frm.set_df_property(party_field, "description", "");
+			}
+		});
+};

If you’d like, this can be further optimized by caching the show_gl_balance_in_document value client‑side to avoid repeated get_single_value lookups per form interaction.

return
}
let color = balance <= 0 ? "green" : "red";
let html = `<b>Current GL Balance:</b>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done using a virtual field and fetch on demand because this is a heavy query, causing performance issues in multiple sites.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-tests This PR needs automated unit-tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants