diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py index 4abc82d8bec..dc73242ac06 100644 --- a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py +++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py @@ -4,6 +4,8 @@ import unittest import frappe +from frappe.query_builder.functions import Sum +from frappe.tests.utils import change_settings from frappe.utils import add_days, today from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center @@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase): coa2.cancel() jv.cancel() + @change_settings("System Settings", {"rounding_method": "Commercial Rounding"}) + def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + cca = create_cost_center_allocation( + "_Test Company", + "Main Cost Center 1 - _TC", + {"Sub Cost Center 2 - _TC": 50, "Sub Cost Center 3 - _TC": 50}, + ) + + si = create_sales_invoice(rate=145.65, cost_center="Main Cost Center 1 - _TC") + + gl_entry = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gl_entry) + .select(Sum(gl_entry.credit).as_("cr"), Sum(gl_entry.debit).as_("dr")) + .where(gl_entry.voucher_type == "Sales Invoice") + .where(gl_entry.voucher_no == si.name) + ).run(as_dict=1) + + self.assertEqual(gl_entries[0].cr, gl_entries[0].dr) + + si.cancel() + cca.cancel() + def create_cost_center_allocation( company, diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index 153906ffe97..286b915b05e 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -75,7 +75,7 @@ }, { "default": "0", - "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach", + "description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach", "fieldname": "consider_party_ledger_amount", "fieldtype": "Check", "label": "Consider Entire Party Ledger Amount", @@ -102,10 +102,11 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-07-27 21:47:34.396071", + "modified": "2025-07-30 07:13:51.785735", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Category", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { @@ -148,4 +149,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f2bc8b3a2f8..6dc645538f1 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -179,6 +179,15 @@ def process_gl_map(gl_map, merge_entries=True, precision=None): def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None): + round_off_account, default_currency = frappe.get_cached_value( + "Company", gl_map[0].company, ["round_off_account", "default_currency"] + ) + if not precision: + precision = get_field_precision( + frappe.get_meta("GL Entry").get_field("debit"), + currency=default_currency, + ) + new_gl_map = [] for d in gl_map: cost_center = d.get("cost_center") @@ -192,6 +201,11 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None): new_gl_map.append(d) continue + if d.account == round_off_account: + d.cost_center = cost_center_allocation[0][0] + new_gl_map.append(d) + continue + for sub_cost_center, percentage in cost_center_allocation: gle = copy.deepcopy(d) gle.cost_center = sub_cost_center diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 188199292f2..38f9eba4f90 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = { label: __("Show Net Values in Party Account"), fieldtype: "Check", }, + { + fieldname: "show_amount_in_company_currency", + label: __("Show Credit / Debit in Company Currency"), + fieldtype: "Check", + }, { fieldname: "show_remarks", label: __("Show Remarks"), diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 0ae8a6070a1..275e0b7e41b 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -580,6 +580,19 @@ def get_columns(filters): company = filters.get("company") or get_default_company() filters["presentation_currency"] = currency = get_company_currency(company) + company_currency = get_company_currency(filters.get("company") or get_default_company()) + + if ( + filters.get("show_amount_in_company_currency") + and filters["presentation_currency"] != company_currency + ): + frappe.throw( + _("Presentation Currency cannot be {0} , When {1} is enabled.").format( + frappe.bold(filters["presentation_currency"]), + frappe.bold("Show Credit / Debit in Company Currency"), + ) + ) + columns = [ { "label": _("GL Entry"), diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py index d4f0f0a107d..7a2fdc4c925 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -46,6 +46,7 @@ class PaymentLedger: against_voucher_no=ple.against_voucher_no, amount=ple.amount, currency=ple.account_currency, + company=ple.company, ) if self.filters.include_account_currency: @@ -77,6 +78,7 @@ class PaymentLedger: against_voucher_no="Outstanding:", amount=total, currency=voucher_data[0].currency, + company=voucher_data[0].company, ) if self.filters.include_account_currency: @@ -85,7 +87,12 @@ class PaymentLedger: voucher_data.append(entry) # empty row - voucher_data.append(frappe._dict()) + voucher_data.append( + frappe._dict( + currency=voucher_data[0].currency, + company=voucher_data[0].company, + ) + ) self.data.extend(voucher_data) def build_conditions(self): diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 2129f553c8c..92c305e6cd8 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -118,7 +118,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, filters=None): len(account_currencies) == 1 and account_currency == presentation_currency and not exchange_gain_or_loss - ): + ) and not filters.get("show_amount_in_company_currency"): entry["debit"] = debit_in_account_currency entry["credit"] = credit_in_account_currency else: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 8ca34783196..859805f082e 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt +from collections import defaultdict from json import loads from typing import TYPE_CHECKING, Optional @@ -2195,25 +2196,37 @@ def run_ledger_health_checks(): doc.save() +def get_link_fields_grouped_by_option(doctype): + meta = frappe.get_meta(doctype) + link_fields_map = defaultdict(list) + + for df in meta.fields: + if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions: + link_fields_map[df.options].append(df.fieldname) + + return link_fields_map + + def build_qb_match_conditions(doctype, user=None) -> list: match_filters = build_match_conditions(doctype, user, False) + link_fields_map = get_link_fields_grouped_by_option(doctype) criterion = [] apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions") if match_filters: - from frappe import qb - _dt = qb.DocType(doctype) for filter in match_filters: - for d, names in filter.items(): - fieldname = d.lower().replace(" ", "_") - field = _dt[fieldname] + for link_option, allowed_values in filter.items(): + fieldnames = link_fields_map.get(link_option, []) - cond = field.isin(names) - if not apply_strict_user_permissions: - cond = (Coalesce(field, "") == "") | field.isin(names) + for fieldname in fieldnames: + field = _dt[fieldname] + cond = field.isin(allowed_values) - criterion.append(cond) + if not apply_strict_user_permissions: + cond = (Coalesce(field, "") == "") | cond + + criterion.append(cond) return criterion diff --git a/erpnext/edi/doctype/code_list/code_list_import.py b/erpnext/edi/doctype/code_list/code_list_import.py index 50df3be471e..ecabb256026 100644 --- a/erpnext/edi/doctype/code_list/code_list_import.py +++ b/erpnext/edi/doctype/code_list/code_list_import.py @@ -60,7 +60,7 @@ def import_genericode(): "doctype": "File", "attached_to_doctype": "Code List", "attached_to_name": code_list.name, - "folder": "Home/Attachments", + "folder": frappe.db.get_value("File", {"is_attachments_folder": 1}), "file_name": frappe.local.uploaded_filename, "file_url": frappe.local.uploaded_file_url, "is_private": 1, diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 7d63209f478..aae197fe3a6 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -274,14 +274,6 @@ class FIFOSlots: d.actual_qty = d.bal_qty key, fifo_queue, transferred_item_key = self.__init_key_stores(d) - serial_nos = d.serial_no if d.serial_no else [] - if fifo_queue and isinstance(fifo_queue[0][0], str): - d.has_serial_no = 1 - - if d.actual_qty > 0: - self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos) - else: - self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos) self.__update_balances(d, key) @@ -439,10 +431,10 @@ class FIFOSlots: transfer_qty_to_pop = 0 def __update_balances(self, row: dict, key: tuple | str): - self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction or row.bal_qty + self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction or flt(row.bal_qty) if "total_qty" not in self.item_details[key]: - self.item_details[key]["total_qty"] = row.actual_qty or row.bal_qty + self.item_details[key]["total_qty"] = row.actual_qty or flt(row.bal_qty) else: self.item_details[key]["total_qty"] += row.actual_qty diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 0067c321049..65f55b2e8ae 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -277,8 +277,6 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): if isinstance(args, str): args = json.loads(args) - voucher_no = args.get("voucher_no") or args.get("name") - in_rate = None if (args.get("serial_no") or "").strip(): in_rate = get_avg_purchase_rate(args.get("serial_no")) @@ -301,12 +299,13 @@ def get_incoming_rate(args, raise_error_if_no_rate=True): in_rate = ( _get_fifo_lifo_rate(previous_stock_queue, args.get("qty") or 0, valuation_method) if previous_stock_queue - else 0 + else None ) elif valuation_method == "Moving Average": - in_rate = previous_sle.get("valuation_rate") or 0 + in_rate = previous_sle.get("valuation_rate") if in_rate is None: + voucher_no = args.get("voucher_no") or args.get("name") in_rate = get_valuation_rate( args.get("item_code"), args.get("warehouse"),