-
Notifications
You must be signed in to change notification settings - Fork 9.9k
feat: Add Party GL Balance in Purchase & Sales Documents When Creating #50695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
feat: Add Party GL Balance in Purchase & Sales Documents When Creating #50695
Conversation
|
@DipenFrappe I have developed the requested feature. If everything looks good, please consider merging it into the upcoming version. |
📝 WalkthroughWalkthroughThis PR introduces a feature to display General Ledger balances for customers and suppliers in buying and sales documents. It adds a new configuration field Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Example instruction:
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. Comment |
There was a problem hiding this 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
📒 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.jserpnext/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 forget_party_gl_balancelook correctWiring this helper into
refresh()andsupplier()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 flowInvoking
erpnext.utils.get_party_gl_balance(this.frm)in bothrefresh()andcustomer()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 forshow_gl_balance_in_documentis correctThe added
DF.Checkannotation matches the JSON field definition and keeps the auto-generated types in sync with the new setting.
| "balance_view_settings_section", | ||
| "show_gl_balance_in_document", | ||
| "item_price_settings_section", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tidy up field_order and align label with feature intent
field_ordernow 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.
| 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", ""); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_party_gl_balance ignores the setting, mis-detects Quotation, and leaks a global
Current implementation has several functional issues:
-
Settings flag is not respected
The call tofrappe.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 ofshow_gl_balance_in_document. The setting cannot disable the feature and you still issue DB + GL balance calls on every invocation. -
Quotation special-case never triggers
party_fieldis computed usingargs.doctype == "Quotation", butargs.doctypeis never set. This condition is always false, so Quotation falls through to"customer"/"supplier"instead of"party_name". -
Global variable pollution and weak guards
party_fieldis assigned withoutlet/const/var, creating a global and risking collisions. Additionally, if this helper is ever called for a doctype outsideSALES_DOCTYPES/PURCHASE_DOCTYPES,argsstays empty but you still compute and useparty_fieldbased 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> |
There was a problem hiding this comment.
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.
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
New option in Accounts Settings:
• Fetch current balance using existing
get_balance_on• Display it under Customer/Supplier field
Applies to the following doctypes:
• Sales Invoice
• Sales Order
• Delivery Note
• Purchase Invoice
• Purchase Order
• Purchase Receipt
Fully client-side + server-side integration ensuring:
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