diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 35b33b7a195..02f7eeb7095 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -93,6 +93,7 @@ "receivable_payable_remarks_length", "accounts_receivable_payable_tuning_section", "receivable_payable_fetch_method", + "default_ageing_range", "column_break_ntmi", "drop_ar_procedures", "legacy_section", @@ -657,6 +658,12 @@ "fieldname": "show_party_balance", "fieldtype": "Check", "label": "Show Party Balance" + }, + { + "default": "30, 60, 90, 120", + "fieldname": "default_ageing_range", + "fieldtype": "Data", + "label": "Default Ageing Range" } ], "icon": "icon-cog", @@ -664,7 +671,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-11-06 17:48:07.682837", + "modified": "2025-12-26 19:46:55.093717", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -694,4 +701,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index c54ae9bac65..793f44bd5c8 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -41,6 +41,7 @@ class AccountsSettings(Document): check_supplier_invoice_uniqueness: DF.Check create_pr_in_draft_status: DF.Check credit_controller: DF.Link | None + default_ageing_range: DF.Data | None delete_linked_ledger_entries: DF.Check determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] enable_common_party_accounting: DF.Check diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index f04bf6b22b3..181edb4cd6b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -20,6 +20,23 @@ frappe.ui.form.on("Journal Entry", { "Unreconcile Payment Entries", "Bank Transaction", ]; + frm.trigger("set_queries"); + }, + + set_queries(frm) { + frm.set_query("project", "accounts", function (doc, cdt, cdn) { + let row = frappe.get_doc(cdt, cdn); + let filters = { + company: doc.company, + }; + if (row.party_type == "Customer") { + filters.customer = row.party; + } + return { + query: "erpnext.controllers.queries.get_project_name", + filters, + }; + }); }, refresh: function (frm) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index f1e9e261309..0aa48dcd721 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -6,6 +6,7 @@ import json import frappe from frappe import _, msgprint, scrub +from frappe.core.doctype.submission_queue.submission_queue import queue_submission from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate import erpnext @@ -172,15 +173,13 @@ class JournalEntry(AccountsController): def submit(self): if len(self.accounts) > 100: - msgprint(_("The task has been enqueued as a background job."), alert=True) - self.queue_action("submit", timeout=4600) + queue_submission(self, "_submit") else: return self._submit() def cancel(self): if len(self.accounts) > 100: - msgprint(_("The task has been enqueued as a background job."), alert=True) - self.queue_action("cancel", timeout=4600) + queue_submission(self, "_cancel") else: return self._cancel() diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js index f07d90b6ef3..57b05d19d83 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js @@ -13,9 +13,9 @@ frappe.ui.form.on("Period Closing Voucher", { return { filters: [ ["Account", "company", "=", frm.doc.company], - ["Account", "is_group", "=", "0"], + ["Account", "is_group", "=", 0], ["Account", "freeze_account", "=", "No"], - ["Account", "root_type", "in", "Liability, Equity"], + ["Account", "root_type", "in", ["Liability", "Equity"]], ], }; }); diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 9af8c93a52e..c061b0c3902 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -165,6 +165,10 @@ frappe.query_reports["Accounts Payable"] = { var filters = report.get_values(); frappe.set_route("query-report", "Accounts Payable Summary", { company: filters.company }); }); + + if (frappe.boot.sysdefaults.default_ageing_range) { + report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range); + } }, }; diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 6a043f5e185..a4cb0584bf1 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -114,6 +114,10 @@ frappe.query_reports["Accounts Payable Summary"] = { var filters = report.get_values(); frappe.set_route("query-report", "Accounts Payable", { company: filters.company }); }); + + if (frappe.boot.sysdefaults.default_ageing_range) { + report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range); + } }, }; diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index b052d50838d..4255568d1f9 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -192,6 +192,10 @@ frappe.query_reports["Accounts Receivable"] = { var filters = report.get_values(); frappe.set_route("query-report", "Accounts Receivable Summary", { company: filters.company }); }); + + if (frappe.boot.sysdefaults.default_ageing_range) { + report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range); + } }, }; diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 1ac2b27ca71..c8e59d6e054 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -137,6 +137,10 @@ frappe.query_reports["Accounts Receivable Summary"] = { var filters = report.get_values(); frappe.set_route("query-report", "Accounts Receivable", { company: filters.company }); }); + + if (frappe.boot.sysdefaults.default_ageing_range) { + report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range); + } }, }; diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js index 62482ac162c..ecddd7271ea 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js @@ -81,5 +81,11 @@ frappe.query_reports["Trial Balance for Party"] = { label: __("Show zero values"), fieldtype: "Check", }, + { + fieldname: "exclude_zero_balance_parties", + label: __("Exclude Zero Balance Parties"), + fieldtype: "Check", + default: 1, + }, ], }; diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index f6c79eb6c45..86c8463b021 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -75,20 +75,20 @@ def get_data(filters, show_party_name): closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit) row.update({"closing_debit": closing_debit, "closing_credit": closing_credit}) - # totals - for col in total_row: - total_row[col] += row.get(col) - row.update({"currency": company_currency}) has_value = False if opening_debit or opening_credit or debit or credit or closing_debit or closing_credit: has_value = True + # Exclude zero balance parties if filter is set + if filters.get("exclude_zero_balance_parties") and not closing_debit and not closing_credit: + continue if cint(filters.show_zero_values) or has_value: data.append(row) - - # Add total row + # totals + for col in total_row: + total_row[col] += row.get(col) total_row.update({"party": "'" + _("Totals") + "'", "currency": company_currency}) data.append(total_row) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index 9b6d878378b..45e531d6976 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_import": 1, "autoname": "naming_series:", "creation": "2017-10-23 11:38:54.004355", "doctype": "DocType", @@ -250,7 +251,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-11-17 18:35:54.575265", + "modified": "2026-01-06 15:48:13.862505", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", @@ -264,6 +265,7 @@ "delete": 1, "email": 1, "export": 1, + "import": 1, "print": 1, "read": 1, "report": 1, @@ -279,6 +281,7 @@ "delete": 1, "email": 1, "export": 1, + "import": 1, "print": 1, "read": 1, "report": 1, @@ -295,4 +298,4 @@ "title_field": "asset_name", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 83816f6cc09..ff1141fb07e 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -64,6 +64,9 @@ def boot_session(bootinfo): bootinfo.party_account_types = frappe._dict(party_account_types) bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company") + bootinfo.sysdefaults.default_ageing_range = frappe.db.get_single_value( + "Accounts Settings", "default_ageing_range" + ) def update_page_info(bootinfo): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 6833036122f..b1a1cdece37 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -140,6 +140,24 @@ frappe.ui.form.on("Stock Entry", { }; }); + frm.set_query("project", "items", function (doc) { + return { + query: "erpnext.controllers.queries.get_project_name", + filters: { + company: doc.company, + }, + }; + }); + + frm.set_query("project", function (doc) { + return { + query: "erpnext.controllers.queries.get_project_name", + filters: { + company: doc.company, + }, + }; + }); + frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); @@ -914,6 +932,9 @@ frappe.ui.form.on("Stock Entry Detail", { item_code(frm, cdt, cdn) { var d = locals[cdt][cdn]; + // since some items may not have image, so empty the image field to avoid setting the image of previous item + d.image = ""; + if (d.item_code) { var args = { item_code: d.item_code, diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 2bf4333b14e..629a50e1c28 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -879,7 +879,7 @@ class StockReconciliation(StockController): if row.get(dimension.get("fieldname")): has_dimensions = True - if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): + if self.docstatus == 2: if row.current_qty and current_bundle: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index b45934ffebd..63228e5a764 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1645,6 +1645,59 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): batch_qty = get_batch_qty(batch_no, warehouse, item_code) self.assertEqual(batch_qty, 4) + def test_sabb_cancel_on_stock_reco_cancellation(self): + item_code = self.make_item( + "Test Item for SABB Cancel on Stock Reco Cancellation", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-SABBCANC-.###", + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + sr = create_stock_reconciliation( + item_code=item_code, + warehouse=warehouse, + qty=10, + rate=100, + use_serial_batch_fields=1, + ) + + sr.reload() + + batch_no = get_batch_from_bundle(sr.items[0].serial_and_batch_bundle) + + sr1 = create_stock_reconciliation( + item_code=item_code, + warehouse=warehouse, + qty=20, + rate=100, + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + sr1.reload() + + current_serial_and_batch_bundle = sr1.items[0].current_serial_and_batch_bundle + serial_and_batch_bundle = sr1.items[0].serial_and_batch_bundle + + self.assertTrue(current_serial_and_batch_bundle) + self.assertTrue(serial_and_batch_bundle) + + sr1.cancel() + + for sabb in [serial_and_batch_bundle, current_serial_and_batch_bundle]: + docstatus = frappe.db.get_value( + "Serial and Batch Bundle", + sabb, + "docstatus", + ) + + self.assertEqual(docstatus, 2) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index a6a69f3271e..ca31f33bf76 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -445,6 +445,7 @@ class StockReservationEntry(Document): voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor) allowed_qty = min(self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)) + allowed_qty = flt(allowed_qty, self.precision("reserved_qty")) qty_to_be_reserved = flt(qty_to_be_reserved, self.precision("reserved_qty")) if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0: @@ -537,6 +538,7 @@ def get_available_qty_to_reserve( & (sre.reserved_qty >= sre.delivered_qty) & (sre.status.notin(["Delivered", "Cancelled"])) ) + .for_update() ) if ignore_sre: