From 2383051b740246cd7f051e36a130b45475d42725 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Thu, 21 Aug 2025 18:34:05 +0530 Subject: [PATCH 1/5] feat: reporting_currency on company --- erpnext/setup/doctype/company/company.js | 7 +++++++ erpnext/setup/doctype/company/company.json | 9 +++++++++ erpnext/setup/doctype/company/company.py | 12 +++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 19df429e491..8b6329da031 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -17,6 +17,9 @@ frappe.ui.form.on("Company", { frm.toggle_enable("default_currency", !r.message); }); } + if (frm.doc.__islocal) { + frm.set_value("reporting_currency", ""); + } }, setup: function (frm) { frm.__rename_queue = "long"; @@ -156,6 +159,10 @@ frappe.ui.form.on("Company", { } } + if (frm.doc.__islocal) { + frm.set_value("reporting_currency", ""); + } + erpnext.company.set_chart_of_accounts_options(frm.doc); }, diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 41c47147503..54611d6479d 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -22,6 +22,7 @@ "domain", "date_of_establishment", "parent_company", + "reporting_currency", "company_info", "company_logo", "date_of_incorporation", @@ -835,6 +836,14 @@ "fieldtype": "Select", "label": "Reconciliation Takes Effect On", "options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date" + }, + { + "fieldname": "reporting_currency", + "fieldtype": "Link", + "label": "Reporting Currency", + "options": "Currency", + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-building", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8146083075e..160748b5e69 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -91,6 +91,7 @@ class Company(NestedSet): "Advance Payment Date", "Oldest Of Invoice Or Advance", "Reconciliation Date" ] registration_details: DF.Code | None + reporting_currency: DF.Link | None rgt: DF.Int round_off_account: DF.Link | None round_off_cost_center: DF.Link | None @@ -153,6 +154,7 @@ class Company(NestedSet): self.check_parent_changed() self.set_chart_of_accounts() self.validate_parent_company() + self.set_reporting_currency() def validate_abbr(self): if not self.abbr: @@ -490,6 +492,14 @@ class Company(NestedSet): if not is_group: frappe.throw(_("Parent Company must be a group company")) + def set_reporting_currency(self): + self.reporting_currency = self.default_currency + if self.parent_company: + parent_reporting_currency = frappe.db.get_value( + "Company", self.parent_company, ["reporting_currency"] + ) + self.reporting_currency = parent_reporting_currency + def set_default_accounts(self): default_accounts = { "default_cash_account": "Cash", @@ -681,7 +691,7 @@ class Company(NestedSet): frappe.db.sql("delete from tabBOM where company=%s", self.name) for dt in ("BOM Operation", "BOM Item", "BOM Scrap Item", "BOM Explosion Item"): frappe.db.sql( - "delete from `tab{}` where parent in ({})" "".format(dt, ", ".join(["%s"] * len(boms))), + "delete from `tab{}` where parent in ({})".format(dt, ", ".join(["%s"] * len(boms))), tuple(boms), ) From fab9c4d7df56229743f559e939f8ab950d27053d Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 22 Aug 2025 14:06:45 +0530 Subject: [PATCH 2/5] feat: dr/cr amounts in reporting_currency on gl entries --- .../accounts/doctype/gl_entry/gl_entry.json | 26 ++++++++++++++++-- erpnext/accounts/doctype/gl_entry/gl_entry.py | 27 ++++++++++++++++++- erpnext/exceptions.py | 4 +++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 769fbbc427a..b3afb906c6b 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -29,14 +29,17 @@ "against_voucher", "voucher_detail_no", "transaction_exchange_rate", + "reporting_currency_exchange_rate", "amounts_section", "debit_in_account_currency", "debit", "debit_in_transaction_currency", + "debit_in_reporting_currency", "column_break_bm1w", "credit_in_account_currency", "credit", "credit_in_transaction_currency", + "credit_in_reporting_currency", "dimensions_section", "cost_center", "column_break_lmnm", @@ -353,13 +356,31 @@ { "fieldname": "column_break_8abq", "fieldtype": "Column Break" + }, + { + "fieldname": "debit_in_reporting_currency", + "fieldtype": "Currency", + "label": "Debit Amount in Reporting Currency", + "options": "Company:company:reporting_currency" + }, + { + "fieldname": "credit_in_reporting_currency", + "fieldtype": "Currency", + "label": "Credit Amount in Reporting Currency", + "options": "Company:company:reporting_currency" + }, + { + "fieldname": "reporting_currency_exchange_rate", + "fieldtype": "Float", + "label": "Reporting Currency Exchange Rate", + "precision": "9" } ], "icon": "fa fa-list", "idx": 1, "in_create": 1, "links": [], - "modified": "2025-03-21 15:29:11.221890", + "modified": "2025-08-22 12:57:17.750252", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -390,8 +411,9 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "voucher_no,account,posting_date,against_voucher", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index fe3e607aec4..a0f5866fcd4 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -19,7 +19,8 @@ from erpnext.accounts.party import ( validate_party_gle_currency, ) from erpnext.accounts.utils import OUTSTANDING_DOCTYPES, get_account_currency, get_fiscal_year -from erpnext.exceptions import InvalidAccountCurrency +from erpnext.exceptions import InvalidAccountCurrency, ReportingCurrencyExchangeNotFoundError +from erpnext.setup.utils import get_exchange_rate exclude_from_linked_with = True @@ -42,9 +43,11 @@ class GLEntry(Document): cost_center: DF.Link | None credit: DF.Currency credit_in_account_currency: DF.Currency + credit_in_reporting_currency: DF.Currency credit_in_transaction_currency: DF.Currency debit: DF.Currency debit_in_account_currency: DF.Currency + debit_in_reporting_currency: DF.Currency debit_in_transaction_currency: DF.Currency due_date: DF.Date | None finance_book: DF.Link | None @@ -57,6 +60,7 @@ class GLEntry(Document): posting_date: DF.Date | None project: DF.Link | None remarks: DF.Text | None + reporting_currency_exchange_rate: DF.Float to_rename: DF.Check transaction_currency: DF.Link | None transaction_date: DF.Date | None @@ -88,6 +92,8 @@ class GLEntry(Document): self.validate_party() self.validate_currency() + self.set_amount_in_reporting_currency() + def on_update(self): adv_adj = self.flags.adv_adj if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": @@ -292,6 +298,25 @@ class GLEntry(Document): if self.party_type and self.party: validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency) + def set_amount_in_reporting_currency(self): + default_currency, reporting_currency = frappe.get_cached_value( + "Company", self.company, ["default_currency", "reporting_currency"] + ) + transaction_date = self.transaction_date or self.posting_date + self.reporting_currency_exchange_rate = get_exchange_rate( + default_currency, reporting_currency, transaction_date + ) + if not self.reporting_currency_exchange_rate: + frappe.throw( + title=_("Reporting Currency Exchange Not Found"), + msg=_( + "Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually." + ).format(default_currency, reporting_currency, transaction_date), + exc=ReportingCurrencyExchangeNotFoundError, + ) + self.debit_in_reporting_currency = flt(self.debit * self.reporting_currency_exchange_rate) + self.credit_in_reporting_currency = flt(self.credit * self.reporting_currency_exchange_rate) + def validate_and_set_fiscal_year(self): if not self.fiscal_year: self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0] diff --git a/erpnext/exceptions.py b/erpnext/exceptions.py index 86c29d476b2..e12c69757e0 100644 --- a/erpnext/exceptions.py +++ b/erpnext/exceptions.py @@ -24,3 +24,7 @@ class InvalidAccountDimensionError(frappe.ValidationError): class MandatoryAccountDimensionError(frappe.ValidationError): pass + + +class ReportingCurrencyExchangeNotFoundError(frappe.ValidationError): + pass From 8dbbcf5ffbf715ec3d19cb8fc44a14703ce845c7 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Fri, 22 Aug 2025 19:34:34 +0530 Subject: [PATCH 3/5] feat: dr/cr amounts in reporting_currency on account_closing_balance --- .../account_closing_balance.json | 26 ++++++++++++-- .../account_closing_balance.py | 35 ++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json index 7cafacf738b..b980a8378a9 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json @@ -11,6 +11,9 @@ "cost_center", "debit", "credit", + "reporting_currency_exchange_rate", + "debit_in_reporting_currency", + "credit_in_reporting_currency", "account_currency", "debit_in_account_currency", "credit_in_account_currency", @@ -124,12 +127,30 @@ "fieldname": "is_period_closing_voucher_entry", "fieldtype": "Check", "label": "Is Period Closing Voucher Entry" + }, + { + "fieldname": "debit_in_reporting_currency", + "fieldtype": "Currency", + "label": "Debit Amount in Reporting Currency", + "options": "Company:company:reporting_currency" + }, + { + "fieldname": "credit_in_reporting_currency", + "fieldtype": "Currency", + "label": "Credit Amount in Reporting Currency", + "options": "Company:company:reporting_currency" + }, + { + "fieldname": "reporting_currency_exchange_rate", + "fieldtype": "Float", + "label": "Reporting Currency Exchange Rate", + "precision": "9" } ], "icon": "fa fa-list", "in_create": 1, "links": [], - "modified": "2024-03-27 13:05:56.710541", + "modified": "2025-08-22 19:13:50.400404", "modified_by": "Administrator", "module": "Accounts", "name": "Account Closing Balance", @@ -158,7 +179,8 @@ "role": "Auditor" } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py index 6d5e023f039..0afccc1b9f2 100644 --- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py +++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py @@ -2,12 +2,15 @@ # For license information, please see license.txt import frappe +from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, cstr +from frappe.utils import cint, cstr, flt from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) +from erpnext.exceptions import ReportingCurrencyExchangeNotFoundError +from erpnext.setup.utils import get_exchange_rate class AccountClosingBalance(Document): @@ -26,12 +29,15 @@ class AccountClosingBalance(Document): cost_center: DF.Link | None credit: DF.Currency credit_in_account_currency: DF.Currency + credit_in_reporting_currency: DF.Currency debit: DF.Currency debit_in_account_currency: DF.Currency + debit_in_reporting_currency: DF.Currency finance_book: DF.Link | None is_period_closing_voucher_entry: DF.Check period_closing_voucher: DF.Link | None project: DF.Link | None + reporting_currency_exchange_rate: DF.Float # end: auto-generated types pass @@ -55,6 +61,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date): "closing_date": closing_date, } ) + set_amount_in_reporting_currency(cle, company, closing_date) cle.flags.ignore_permissions = True cle.flags.ignore_links = True cle.submit() @@ -144,3 +151,29 @@ def get_previous_closing_entries(company, closing_date, accounting_dimensions): entries = query.run(as_dict=1) return entries + + +def set_amount_in_reporting_currency(cle, company, closing_date): + default_currency, reporting_currency = frappe.get_cached_value( + "Company", company, ["default_currency", "reporting_currency"] + ) + + reporting_currency_exchange_rate = get_exchange_rate(default_currency, reporting_currency, closing_date) + if not reporting_currency_exchange_rate: + frappe.throw( + title=_("Reporting Currency Exchange Not Found"), + msg=_( + "Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually." + ).format(default_currency, reporting_currency, closing_date), + exc=ReportingCurrencyExchangeNotFoundError, + ) + debit_in_reporting_currency = flt(cle.get("debit", 0) * reporting_currency_exchange_rate) + credit_in_reporting_currency = flt(cle.get("credit", 0) * reporting_currency_exchange_rate) + + cle.update( + { + "reporting_currency_exchange_rate": reporting_currency_exchange_rate, + "debit_in_reporting_currency": debit_in_reporting_currency, + "credit_in_reporting_currency": credit_in_reporting_currency, + } + ) From d8babf66ae418ff04eb91a22eb7d626f4d84621f Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Mon, 25 Aug 2025 03:28:11 +0530 Subject: [PATCH 4/5] feat: add patch to set reporting_currency on GL Entry and Account Closing Balance --- erpnext/patches.txt | 1 + .../patches/v16_0/set_reporting_currency.py | 159 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 erpnext/patches/v16_0/set_reporting_currency.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ca063938c79..b1b20514f5a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -436,3 +436,4 @@ erpnext.patches.v16_0.update_serial_no_reference_name erpnext.patches.v16_0.set_invoice_type_in_pos_settings erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter erpnext.patches.v16_0.make_workstation_operating_components #1 +erpnext.patches.v16_0.set_reporting_currency diff --git a/erpnext/patches/v16_0/set_reporting_currency.py b/erpnext/patches/v16_0/set_reporting_currency.py new file mode 100644 index 00000000000..3dc488a52aa --- /dev/null +++ b/erpnext/patches/v16_0/set_reporting_currency.py @@ -0,0 +1,159 @@ +import frappe +from frappe.utils import getdate +from frappe.utils.nestedset import get_descendants_of + +from erpnext.accounts.utils import get_fiscal_year +from erpnext.setup.utils import get_exchange_rate + + +def execute(): + set_company_reporting_currency() + set_amounts_in_reporting_currency_on_gle_and_acb() + + +def set_company_reporting_currency(): + root_companies = frappe.db.get_all( + "Company", fields=["name", "default_currency"], filters={"parent_company": ""}, order_by="lft" + ) + + for d in root_companies: + company_subtree = get_descendants_of("Company", d.name) + company_subtree.append(d.name) + + update_company_subtree_reporting_currency(company_subtree, d.default_currency) + + +def update_company_subtree_reporting_currency(companies, currency): + Company = frappe.qb.DocType("Company") + + frappe.qb.update(Company).set(Company.reporting_currency, currency).where( + Company.name.isin(companies) + ).run() + + +def set_amounts_in_reporting_currency_on_gle_and_acb(): + # get all the companies + companies = frappe.db.get_all( + "Company", fields=["name", "default_currency", "reporting_currency"], order_by="lft" + ) + + # get current fiscal year + current_fiscal_year = get_fiscal_year(getdate(), as_dict=1, raise_on_missing=False) + + if not current_fiscal_year: + return + + previous_fiscal_year = frappe.db.get_value( + "Fiscal Year", + filters={"year_end_date": ("<", current_fiscal_year.year_start_date)}, + fields=["name", "year_start_date", "year_end_date"], + order_by="year_end_date desc", + as_dict=1, + ) + + for d in companies: + posting_dates = get_posting_closing_date(d, current_fiscal_year, previous_fiscal_year) + exchange_rate_available = check_exchange_rate_availability(d, posting_dates) + if not exchange_rate_available: + continue + set_reporting_currency_by_doctype("GL Entry", d, posting_dates.get("GL Entry")) + + set_reporting_currency_by_doctype( + "Account Closing Balance", d, posting_dates.get("Account Closing Balance") + ) + + +def get_posting_closing_date(company_details, current_fiscal_year, previous_fiscal_year=None): + posting_dates = {} + posting_dates["GL Entry"] = get_closing_posting_dates( + "GL Entry", company_details.get("name"), current_fiscal_year + ) + + posting_dates["Account Closing Balance"] = get_closing_posting_dates( + "Account Closing Balance", company_details.get("name"), current_fiscal_year + ) + + if previous_fiscal_year: + prev_fy_last_pcv_closing_date = frappe.db.get_value( + "Period Closing Voucher", + filters={"fiscal_year": previous_fiscal_year.name, "company": company_details.get("name")}, + fieldname=["transaction_date"], + order_by="period_start_date desc", + ) + + if prev_fy_last_pcv_closing_date: + prev_fy_acb_closing_dates = get_closing_posting_dates( + "Account Closing Balance", + company_details.get("name"), + closing_date=prev_fy_last_pcv_closing_date, + ) + posting_dates.setdefault("Account Closing Balance", []) + posting_dates["Account Closing Balance"].extend(prev_fy_acb_closing_dates) + + return posting_dates + + +def check_exchange_rate_availability(company_details, posting_dates): + exchange_rate_available = True + for doctype, values in posting_dates.items(): + if not exchange_rate_available: + return False + date_column = "posting_date" if doctype == "GL Entry" else "closing_date" + for d in values: + exchange_rate = get_exchange_rate( + company_details.get("default_currency"), + company_details.get("reporting_currency"), + d[date_column], + ) + + if not exchange_rate: + exchange_rate_available = False + break + + return exchange_rate_available + + +def set_reporting_currency_by_doctype(doctype, company_details, posting_closing_dates): + date_column = "posting_date" if doctype == "GL Entry" else "closing_date" + for d in posting_closing_dates: + exchange_rate = get_exchange_rate( + company_details.get("default_currency"), + company_details.get("reporting_currency"), + d[date_column], + ) + + set_reporting_currency_on_individual_documents( + doctype, company_details.get("name"), d[date_column], exchange_rate + ) + + +def get_closing_posting_dates(doctype, company, fiscal_year=None, closing_date=None): + dt = frappe.qb.DocType(doctype) + + date_column = "posting_date" if doctype == "GL Entry" else "closing_date" + query = frappe.qb.from_(dt).select(dt[date_column]).where(dt.company == company).groupby(dt[date_column]) + + if doctype == "GL Entry" and fiscal_year: + query = query.where(dt.fiscal_year == fiscal_year.name) + + if doctype == "Account Closing Balance": + if fiscal_year: + query = query.where(dt.closing_date[fiscal_year.year_start_date : fiscal_year.year_end_date]) + if closing_date: + query = query.where(dt.closing_date == closing_date) + + posting_closing_dates = query.run(as_dict=1) + + return posting_closing_dates + + +def set_reporting_currency_on_individual_documents(doctype, company, posting_closing_date, exchange_rate): + dt = frappe.qb.DocType(doctype) + + date_column = "posting_date" if doctype == "GL Entry" else "closing_date" + + frappe.qb.update(dt).set(dt.reporting_currency_exchange_rate, exchange_rate).set( + dt.debit_in_reporting_currency, exchange_rate * dt.debit + ).set(dt.credit_in_reporting_currency, exchange_rate * dt.credit).where( + (dt.company == company) & (dt[date_column] == posting_closing_date) + ).run() From ceff8c92fdc0d2ab5927e13b375cf01f60f5538c Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Mon, 1 Sep 2025 11:48:18 +0530 Subject: [PATCH 5/5] fix: used wrong parameter for get_value to fetch previous fiscal year --- erpnext/patches/v16_0/set_reporting_currency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v16_0/set_reporting_currency.py b/erpnext/patches/v16_0/set_reporting_currency.py index 3dc488a52aa..ce57fbc6235 100644 --- a/erpnext/patches/v16_0/set_reporting_currency.py +++ b/erpnext/patches/v16_0/set_reporting_currency.py @@ -46,7 +46,7 @@ def set_amounts_in_reporting_currency_on_gle_and_acb(): previous_fiscal_year = frappe.db.get_value( "Fiscal Year", filters={"year_end_date": ("<", current_fiscal_year.year_start_date)}, - fields=["name", "year_start_date", "year_end_date"], + fieldname=["name", "year_start_date", "year_end_date"], order_by="year_end_date desc", as_dict=1, )