Merge pull request #49281 from diptanilsaha/reporting_currency

feat: adding reporting_currency and dr/cr in reporting currency fields in GL Entry and Account Closing Balance
This commit is contained in:
Diptanil Saha
2025-09-17 10:44:04 +05:30
committed by GitHub
10 changed files with 299 additions and 7 deletions

View File

@@ -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": []
}
}

View File

@@ -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,
}
)

View File

@@ -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": []
}
}

View File

@@ -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]

View File

@@ -24,3 +24,7 @@ class InvalidAccountDimensionError(frappe.ValidationError):
class MandatoryAccountDimensionError(frappe.ValidationError):
pass
class ReportingCurrencyExchangeNotFoundError(frappe.ValidationError):
pass

View File

@@ -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

View File

@@ -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)},
fieldname=["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()

View File

@@ -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);
},

View File

@@ -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",

View File

@@ -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),
)