From eb81a862bd1534cee79d16802367401795ac8cd2 Mon Sep 17 00:00:00 2001 From: Jatin3128 <140256508+Jatin3128@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:17:14 +0530 Subject: [PATCH] fix: restore Save button on reverse journal entry (#56770) Reversing a submitted Journal Entry opened a draft with reversal_of set, which called frm.set_read_only(). That strips the write and submit perms from frm.perm, so the toolbar never rendered the Save (or later Submit) button and the reversal could not be saved. Lock the fields and the accounts grid as read_only instead, leaving perms intact so Save and Submit still work while nothing stays editable. Ticket: 72857 (cherry picked from commit 0a05dd44264812dcfdeaac7aca2dbb193f64c989) # Conflicts: # erpnext/accounts/doctype/journal_entry/journal_entry.js --- .../doctype/journal_entry/journal_entry.js | 353 +++++++++++++++++- 1 file changed, 352 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 232c33d4def..af7dc70403e 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -41,7 +41,7 @@ frappe.ui.form.on("Journal Entry", { refresh: function (frm) { if (frm.doc.reversal_of && (frm.is_new() || frm.doc.docstatus == 0)) { - frm.set_read_only(); + erpnext.journal_entry.lock_reversal_entry(frm); } erpnext.toggle_naming_series(); @@ -498,8 +498,359 @@ $.extend(erpnext.journal_entry, { var grid = frm.get_field("accounts").grid; if (grid) grid.set_column_disp(fields, frm.doc.multi_currency); +<<<<<<< HEAD // dynamic label var field_label_map = { +======= + lock_reversal_entry(frm) { + frm.fields + .filter((field) => field.has_input) + .forEach((field) => frm.set_df_property(field.df.fieldname, "read_only", 1)); + frm.set_df_property("accounts", "read_only", 1); + }, + + add_custom_buttons(frm) { + if (frm.doc.docstatus > 0) { + frm.add_custom_button( + __("Ledger"), + () => erpnext.journal_entry.show_general_ledger(frm), + __("View") + ); + } + + if (frm.doc.docstatus == 1) { + frm.add_custom_button( + __("Reverse Journal Entry"), + () => erpnext.journal_entry.reverse_journal_entry(frm), + __("Actions") + ); + } + + if (frm.doc.__islocal) { + frm.add_custom_button(__("Quick Entry"), () => erpnext.journal_entry.quick_entry(frm)); + } + + if ( + frm.doc.voucher_type == "Inter Company Journal Entry" && + frm.doc.docstatus == 1 && + !frm.doc.inter_company_journal_entry_reference + ) { + frm.add_custom_button( + __("Create Inter Company Journal Entry"), + () => erpnext.journal_entry.make_inter_company_journal_entry(frm), + __("Make") + ); + } + }, + + show_general_ledger(frm) { + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format("YYYY-MM-DD"), + company: frm.doc.company, + finance_book: frm.doc.finance_book, + categorize_by: "", + show_cancelled_entries: frm.doc.docstatus === 2, + }; + frappe.set_route("query-report", "General Ledger"); + }, + + make_inter_company_journal_entry(frm) { + const dialog = new frappe.ui.Dialog({ + title: __("Select Company"), + fields: [ + { + fieldname: "company", + fieldtype: "Link", + label: __("Company"), + options: "Company", + reqd: 1, + get_query: () => { + return { filters: [["Company", "name", "!=", frm.doc.company]] }; + }, + }, + ], + }); + + dialog.set_primary_action(__("Create"), () => { + dialog.hide(); + frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.mapper.make_inter_company_journal_entry", + args: { + name: frm.doc.name, + voucher_type: frm.doc.voucher_type, + company: dialog.get_value("company"), + }, + callback: ({ message }) => { + if (message) { + const doc = frappe.model.sync(message)[0]; + frappe.set_route("Form", doc.doctype, doc.name); + } + }, + }); + }); + dialog.show(); + }, + + reverse_journal_entry(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.accounts.doctype.journal_entry.mapper.make_reverse_journal_entry", + frm: frm, + }); + }, + + quick_entry(frm) { + const naming_series_options = frm.fields_dict.naming_series.df.options; + const naming_series_default = + frm.fields_dict.naming_series.df.default || naming_series_options.split("\n")[0]; + + const dialog = new frappe.ui.Dialog({ + title: __("Quick Journal Entry"), + fields: [ + { fieldtype: "Currency", fieldname: "debit", label: __("Amount"), reqd: 1 }, + { + fieldtype: "Link", + fieldname: "debit_account", + label: __("Debit Account"), + reqd: 1, + options: "Account", + get_query: () => erpnext.journal_entry.account_query(frm), + }, + { + fieldtype: "Link", + fieldname: "credit_account", + label: __("Credit Account"), + reqd: 1, + options: "Account", + get_query: () => erpnext.journal_entry.account_query(frm), + }, + { + fieldtype: "Date", + fieldname: "posting_date", + label: __("Date"), + reqd: 1, + default: frm.doc.posting_date, + }, + { fieldtype: "Small Text", fieldname: "remark", label: __("Remark") }, + { + fieldtype: "Select", + fieldname: "naming_series", + label: __("Series"), + reqd: 1, + options: naming_series_options, + default: naming_series_default, + }, + ], + }); + + dialog.set_primary_action(__("Save"), () => { + erpnext.journal_entry.save_quick_entry(frm, dialog.get_values()); + dialog.hide(); + }); + dialog.show(); + }, + + save_quick_entry(frm, values) { + frm.set_value("posting_date", values.posting_date); + frm.set_value("naming_series", values.naming_series); + frm.set_value("custom_remark", values.remark ? 1 : 0); + frm.set_value("remark", values.remark || ""); + + // clear table in case a previous add left a partially populated row behind + frm.clear_table("accounts"); + + // grid.add_new_row() adds the row in the UI as well as locals, which the triggers need + erpnext.journal_entry.add_quick_entry_row( + frm, + values.debit_account, + "debit_in_account_currency", + values.debit + ); + erpnext.journal_entry.add_quick_entry_row( + frm, + values.credit_account, + "credit_in_account_currency", + values.debit + ); + + frm.save(); + }, + + add_quick_entry_row(frm, account, amount_field, amount) { + const row = frm.fields_dict.accounts.grid.add_new_row(); + frappe.model.set_value(row.doctype, row.name, "account", account); + frappe.model.set_value(row.doctype, row.name, amount_field, amount); + }, + + get_outstanding(frm, reference_type, reference_name, child) { + return frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_outstanding", + args: { + doctype: reference_type, + docname: reference_name, + company: frm.doc.company, + account: child.account, + party: child.party, + account_currency: child.account_currency, + }, + callback: ({ message }) => { + if (!message) return; + Object.entries(message).forEach(([field, value]) => + frappe.model.set_value(child.doctype, child.name, field, value) + ); + }, + }); + }, + + set_account_details(frm, cdt, cdn) { + const row = frappe.get_doc(cdt, cdn); + if (!row.account) { + erpnext.journal_entry.clear_fields(frm, cdt, cdn); + return; + } + if (!frm.doc.company) frappe.throw(__("Please select Company first")); + if (!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first")); + + return frappe.call({ + method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type", + args: { + account: row.account, + date: frm.doc.posting_date, + company: frm.doc.company, + debit: flt(row.debit_in_account_currency), + credit: flt(row.credit_in_account_currency), + exchange_rate: row.exchange_rate, + }, + callback: ({ message }) => { + if (!message) return; + $.extend(row, message); + erpnext.journal_entry.set_amount_on_last_row(frm, cdt, cdn); + erpnext.journal_entry.set_debit_credit_in_company_currency(frm, cdt, cdn); + frm.refresh_field("accounts"); + }, + }); + }, + + set_amount_on_last_row(frm, cdt, cdn) { + const row = frappe.get_doc(cdt, cdn); + if (row.idx != frm.doc.accounts.length) return; + + const difference = frm.doc.accounts.reduce((total, account) => { + return account.idx == row.idx ? total : total + account.debit - account.credit; + }, 0); + erpnext.journal_entry.set_balancing_amount(row, difference); + }, + + set_balancing_amount(row, difference) { + if (!difference) return; + + const exchange_rate = row.exchange_rate || 1; + if (difference > 0) { + row.credit_in_account_currency = difference / exchange_rate; + row.credit = difference; + } else { + row.debit_in_account_currency = -difference / exchange_rate; + row.debit = -difference; + } + }, + + clear_fields(frm, cdt, cdn) { + const row = frappe.get_doc(cdt, cdn); + row.party_type = null; + row.party = null; + row.bank_account = null; + frm.refresh_field("accounts"); + }, + + setup_queries(frm) { + frm.set_query("periodic_entry_difference_account", () => { + return { filters: { is_group: 0, company: frm.doc.company } }; + }); + + frm.set_query("stock_asset_account", () => { + return { filters: { is_group: 0, account_type: "Stock", company: frm.doc.company } }; + }); + + frm.set_query("project", "accounts", (doc, cdt, cdn) => { + const row = frappe.get_doc(cdt, cdn); + const filters = { company: doc.company }; + if (row.party_type == "Customer") filters.customer = row.party; + return { query: "erpnext.controllers.queries.get_project_name", filters }; + }); + + frm.set_query("account", "accounts", () => erpnext.journal_entry.account_query(frm)); + + frm.set_query("party_type", "accounts", (doc, cdt, cdn) => { + return { + query: "erpnext.setup.doctype.party_type.party_type.get_party_type", + filters: { account: frappe.get_doc(cdt, cdn).account }, + }; + }); + + frm.set_query("reference_name", "accounts", (doc, cdt, cdn) => { + return erpnext.journal_entry.reference_name_query(frappe.get_doc(cdt, cdn)); + }); + }, + + reference_name_query(row) { + if (row.reference_type === "Journal Entry") { + frappe.model.validate_missing(row, "account"); + return { + query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_jv", + filters: { account: row.account, party: row.party }, + }; + } + + const out = { filters: [[row.reference_type, "docstatus", "=", 1]] }; + + if (["Sales Invoice", "Purchase Invoice"].includes(row.reference_type)) { + out.filters.push([row.reference_type, "outstanding_amount", "!=", 0]); + if (row.cost_center) { + out.filters.push([row.reference_type, "cost_center", "in", ["", row.cost_center]]); + } + frappe.model.validate_missing(row, "account"); + const party_account_field = row.reference_type === "Sales Invoice" ? "debit_to" : "credit_to"; + out.filters.push([row.reference_type, party_account_field, "=", row.account]); + } + + if (["Sales Order", "Purchase Order"].includes(row.reference_type)) { + frappe.model.validate_missing(row, "party_type"); + frappe.model.validate_missing(row, "party"); + out.filters.push([row.reference_type, "per_billed", "<", 100]); + } + + if (row.party_type && row.party) { + let party_field = ""; + if (row.reference_type.indexOf("Sales") === 0) { + party_field = "customer"; + } else if (row.reference_type.indexOf("Purchase") === 0) { + party_field = "supplier"; + } + if (party_field) out.filters.push([row.reference_type, party_field, "=", row.party]); + } + + return out; + }, + + account_query(frm) { + const filters = { company: frm.doc.company, is_group: 0 }; + if (!frm.doc.multi_currency) { + const company_currency = erpnext.get_currency(frm.doc.company); + filters.account_currency = ["in", [company_currency, null]]; + } + return { filters }; + }, + + toggle_fields_based_on_currency(frm) { + const fields = ["currency_section", "account_currency", "exchange_rate", "debit", "credit"]; + const grid = frm.get_field("accounts").grid; + if (!grid) return; + + grid.set_column_disp(fields, frm.doc.multi_currency); + + const field_label_map = { +>>>>>>> 0a05dd4426 (fix: restore Save button on reverse journal entry (#56770)) debit_in_account_currency: "Debit", credit_in_account_currency: "Credit", };