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 82821e140ea..6d5e023f039 100644
--- a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
+++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
@@ -113,9 +113,9 @@ def get_previous_closing_entries(company, closing_date, accounting_dimensions):
entries = []
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
- filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
+ filters={"docstatus": 1, "company": company, "period_end_date": ("<", closing_date)},
fields=["name"],
- order_by="posting_date desc",
+ order_by="period_end_date desc",
limit=1,
)
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index 172ef93f14d..300d216618e 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -101,6 +101,8 @@ def validate_accounting_period_on_doc_save(doc, method=None):
date = doc.available_for_use_date
elif doc.doctype == "Asset Repair":
date = doc.completion_date
+ elif doc.doctype == "Period Closing Voucher":
+ date = doc.period_end_date
else:
date = doc.posting_date
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index 2d106ad8cee..c285a33f73e 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -6,38 +6,50 @@
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
+ "dates_section",
"posting_date",
"transaction_date",
+ "column_break_avko",
+ "fiscal_year",
+ "due_date",
+ "account_details_section",
"account",
+ "account_currency",
+ "column_break_ifvf",
+ "against",
"party_type",
"party",
- "cost_center",
- "debit",
- "credit",
- "account_currency",
- "debit_in_account_currency",
- "credit_in_account_currency",
- "against",
+ "transaction_details_section",
+ "voucher_type",
+ "voucher_no",
+ "voucher_subtype",
+ "transaction_currency",
+ "column_break_dpsx",
"against_voucher_type",
"against_voucher",
- "voucher_type",
- "voucher_subtype",
- "voucher_no",
"voucher_detail_no",
+ "transaction_exchange_rate",
+ "amounts_section",
+ "debit_in_account_currency",
+ "debit",
+ "debit_in_transaction_currency",
+ "column_break_bm1w",
+ "credit_in_account_currency",
+ "credit",
+ "credit_in_transaction_currency",
+ "dimensions_section",
+ "cost_center",
+ "column_break_lmnm",
"project",
- "remarks",
+ "more_info_section",
+ "finance_book",
+ "company",
"is_opening",
"is_advance",
- "fiscal_year",
- "company",
- "finance_book",
+ "column_break_8abq",
"to_rename",
- "due_date",
"is_cancelled",
- "transaction_currency",
- "debit_in_transaction_currency",
- "credit_in_transaction_currency",
- "transaction_exchange_rate"
+ "remarks"
],
"fields": [
{
@@ -285,13 +297,67 @@
"fieldname": "voucher_subtype",
"fieldtype": "Small Text",
"label": "Voucher Subtype"
+ },
+ {
+ "fieldname": "dates_section",
+ "fieldtype": "Section Break",
+ "label": "Dates"
+ },
+ {
+ "fieldname": "column_break_avko",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "account_details_section",
+ "fieldtype": "Section Break",
+ "label": "Account Details"
+ },
+ {
+ "fieldname": "column_break_ifvf",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "transaction_details_section",
+ "fieldtype": "Section Break",
+ "label": "Transaction Details"
+ },
+ {
+ "fieldname": "amounts_section",
+ "fieldtype": "Section Break",
+ "label": "Amounts"
+ },
+ {
+ "fieldname": "column_break_dpsx",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "more_info_section",
+ "fieldtype": "Section Break",
+ "label": "More Info"
+ },
+ {
+ "fieldname": "column_break_bm1w",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Dimensions"
+ },
+ {
+ "fieldname": "column_break_lmnm",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_8abq",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-list",
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2024-07-02 14:31:51.496466",
+ "modified": "2024-08-22 13:03:39.997475",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index d74224c4aa2..a7e7edb098d 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -430,8 +430,9 @@ def update_against_account(voucher_type, voucher_no):
def on_doctype_update():
- frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
+ frappe.db.add_index("GL Entry", ["posting_date", "company"])
+ frappe.db.add_index("GL Entry", ["party_type", "party"])
def rename_gle_sle_docs():
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 883c638398c..1b19949bb7e 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -1986,13 +1986,15 @@ def make_period_closing_voucher(company, cost_center, posting_date=None, submit=
parent_account=parent_account,
doctype="Account",
)
+ fy = get_fiscal_year(posting_date, company=company)
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": posting_date or today(),
- "posting_date": posting_date or today(),
+ "period_start_date": fy[1],
+ "period_end_date": fy[2],
"company": company,
- "fiscal_year": get_fiscal_year(posting_date or today(), company=company)[0],
+ "fiscal_year": fy[0],
"cost_center": cost_center,
"closing_account_head": surplus_account,
"remarks": "test",
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 82d8cb37fe7..095310c7e70 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js
@@ -19,6 +19,24 @@ frappe.ui.form.on("Period Closing Voucher", {
});
},
+ fiscal_year: function (frm) {
+ if (frm.doc.fiscal_year) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.get_period_start_end_date",
+ args: {
+ fiscal_year: frm.doc.fiscal_year,
+ company: frm.doc.company,
+ },
+ callback: function (r) {
+ if (r.message) {
+ frm.set_value("period_start_date", r.message[0]);
+ frm.set_value("period_end_date", r.message[1]);
+ }
+ },
+ });
+ }
+ },
+
refresh: function (frm) {
if (frm.doc.docstatus > 0) {
frm.add_custom_button(
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
index 624b5f82f64..f41cff0e0d8 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
@@ -6,39 +6,32 @@
"engine": "InnoDB",
"field_order": [
"transaction_date",
- "posting_date",
- "fiscal_year",
- "year_start_date",
- "amended_from",
"company",
+ "fiscal_year",
+ "period_start_date",
+ "period_end_date",
+ "amended_from",
"column_break1",
"closing_account_head",
- "remarks",
"gle_processing_status",
+ "remarks",
"error_message"
],
"fields": [
{
+ "default": "Today",
"fieldname": "transaction_date",
"fieldtype": "Date",
"label": "Transaction Date",
"oldfieldname": "transaction_date",
"oldfieldtype": "Date"
},
- {
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "label": "Posting Date",
- "oldfieldname": "posting_date",
- "oldfieldtype": "Date",
- "reqd": 1
- },
{
"fieldname": "fiscal_year",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
- "label": "Closing Fiscal Year",
+ "label": "Fiscal Year",
"oldfieldname": "fiscal_year",
"oldfieldtype": "Select",
"options": "Fiscal Year",
@@ -103,16 +96,25 @@
"read_only": 1
},
{
- "fieldname": "year_start_date",
+ "fieldname": "period_end_date",
"fieldtype": "Date",
- "label": "Year Start Date"
+ "label": "Period End Date",
+ "reqd": 1
+ },
+ {
+ "fieldname": "period_start_date",
+ "fieldtype": "Date",
+ "label": "Period Start Date",
+ "oldfieldname": "posting_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-09-11 20:19:11.810533",
+ "modified": "2024-09-15 17:22:45.291628",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",
@@ -148,7 +150,7 @@
"write": 1
}
],
- "search_fields": "posting_date, fiscal_year",
+ "search_fields": "fiscal_year, period_start_date, period_end_date",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index dd8a5ff16c3..d6bd217650b 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -2,15 +2,20 @@
# License: GNU General Public License v3. See license.txt
+import copy
+
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
-from frappe.utils import add_days, flt
+from frappe.utils import add_days, flt, formatdate, getdate
+from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
+ make_closing_entries,
+)
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
-from erpnext.accounts.utils import get_account_currency, get_fiscal_year, validate_fiscal_year
+from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
@@ -29,36 +34,386 @@ class PeriodClosingVoucher(AccountsController):
error_message: DF.Text | None
fiscal_year: DF.Link
gle_processing_status: DF.Literal["In Progress", "Completed", "Failed"]
- posting_date: DF.Date
+ period_end_date: DF.Date
+ period_start_date: DF.Date
remarks: DF.SmallText
transaction_date: DF.Date | None
- year_start_date: DF.Date | None
# end: auto-generated types
def validate(self):
- self.validate_account_head()
- self.validate_posting_date()
+ self.validate_start_and_end_date()
+ self.check_if_previous_year_closed()
+ self.block_if_future_closing_voucher_exists()
+ self.check_closing_account_type()
+ self.check_closing_account_currency()
+
+ def validate_start_and_end_date(self):
+ self.fy_start_date, self.fy_end_date = frappe.db.get_value(
+ "Fiscal Year", self.fiscal_year, ["year_start_date", "year_end_date"]
+ )
+
+ prev_closed_period_end_date = get_previous_closed_period_in_current_year(
+ self.fiscal_year, self.company
+ )
+ valid_start_date = (
+ add_days(prev_closed_period_end_date, 1) if prev_closed_period_end_date else self.fy_start_date
+ )
+
+ if getdate(self.period_start_date) != getdate(valid_start_date):
+ frappe.throw(_("Period Start Date must be {0}").format(formatdate(valid_start_date)))
+
+ if getdate(self.period_start_date) > getdate(self.period_end_date):
+ frappe.throw(_("Period Start Date cannot be greater than Period End Date"))
+
+ if getdate(self.period_end_date) > getdate(self.fy_end_date):
+ frappe.throw(_("Period End Date cannot be greater than Fiscal Year End Date"))
+
+ def check_if_previous_year_closed(self):
+ last_year_closing = add_days(self.fy_start_date, -1)
+ previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
+ if not previous_fiscal_year:
+ return
+
+ previous_fiscal_year_start_date = previous_fiscal_year[0][1]
+ gle_exists_in_previous_year = frappe.db.exists(
+ "GL Entry",
+ {
+ "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
+ "company": self.company,
+ "is_cancelled": 0,
+ },
+ )
+ if not gle_exists_in_previous_year:
+ return
+
+ previous_fiscal_year_closed = frappe.db.exists(
+ "Period Closing Voucher",
+ {
+ "period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
+ "docstatus": 1,
+ "company": self.company,
+ },
+ )
+ if not previous_fiscal_year_closed:
+ frappe.throw(_("Previous Year is not closed, please close it first"))
+
+ def block_if_future_closing_voucher_exists(self):
+ future_closing_voucher = self.get_future_closing_voucher()
+ if future_closing_voucher and future_closing_voucher[0][0]:
+ action = "cancel" if self.docstatus == 2 else "create"
+ frappe.throw(
+ _(
+ "You cannot {0} this document because another Period Closing Entry {1} exists after {2}"
+ ).format(action, future_closing_voucher[0][0], self.period_end_date)
+ )
+
+ def get_future_closing_voucher(self):
+ return frappe.db.get_value(
+ "Period Closing Voucher",
+ {"period_end_date": (">", self.period_end_date), "docstatus": 1, "company": self.company},
+ "name",
+ )
+
+ def check_closing_account_type(self):
+ closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
+
+ if closing_account_type not in ["Liability", "Equity"]:
+ frappe.throw(
+ _("Closing Account {0} must be of type Liability / Equity").format(self.closing_account_head)
+ )
+
+ def check_closing_account_currency(self):
+ account_currency = get_account_currency(self.closing_account_head)
+ company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
+ if account_currency != company_currency:
+ frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency))
def on_submit(self):
self.db_set("gle_processing_status", "In Progress")
- get_opening_entries = False
-
- if not frappe.db.exists(
- "Period Closing Voucher", {"company": self.company, "docstatus": 1, "name": ("!=", self.name)}
- ):
- get_opening_entries = True
-
- self.make_gl_entries(get_opening_entries=get_opening_entries)
+ self.make_gl_entries()
def on_cancel(self):
- self.validate_future_closing_vouchers()
- self.db_set("gle_processing_status", "In Progress")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
- gle_count = frappe.db.count(
+ self.block_if_future_closing_voucher_exists()
+ self.db_set("gle_processing_status", "In Progress")
+ self.cancel_gl_entries()
+
+ def make_gl_entries(self):
+ if self.get_gle_count_in_selected_period() > 5000:
+ frappe.enqueue(
+ process_gl_and_closing_entries,
+ doc=self,
+ timeout=1800,
+ )
+ frappe.msgprint(
+ _(
+ "The GL Entries and closing balances will be processed in the background, it can take a few minutes."
+ ),
+ alert=True,
+ )
+ else:
+ process_gl_and_closing_entries(self)
+
+ def get_gle_count_in_selected_period(self):
+ return frappe.db.count(
"GL Entry",
- {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
+ {
+ "posting_date": ["between", [self.period_start_date, self.period_end_date]],
+ "company": self.company,
+ "is_cancelled": 0,
+ },
)
- if gle_count > 5000:
+
+ def get_pcv_gl_entries(self):
+ self.pl_accounts_reverse_gle = []
+ self.closing_account_gle = []
+
+ pl_account_balances = self.get_account_balances_based_on_dimensions(report_type="Profit and Loss")
+ for dimensions, account_balances in pl_account_balances.items():
+ for acc, balances in account_balances.items():
+ balance_in_company_currency = flt(balances.debit_in_account_currency) - flt(
+ balances.credit_in_account_currency
+ )
+ if balance_in_company_currency and acc != "balances":
+ self.pl_accounts_reverse_gle.append(
+ self.get_gle_for_pl_account(acc, balances, dimensions)
+ )
+
+ # closing liability account
+ self.closing_account_gle.append(
+ self.get_gle_for_closing_account(account_balances["balances"], dimensions)
+ )
+
+ return self.pl_accounts_reverse_gle + self.closing_account_gle
+
+ def get_gle_for_pl_account(self, acc, balances, dimensions):
+ balance_in_account_currency = flt(balances.debit_in_account_currency) - flt(
+ balances.credit_in_account_currency
+ )
+ balance_in_company_currency = flt(balances.debit) - flt(balances.credit)
+ gl_entry = frappe._dict(
+ {
+ "company": self.company,
+ "posting_date": self.period_end_date,
+ "account": acc,
+ "account_currency": balances.account_currency,
+ "debit_in_account_currency": abs(balance_in_account_currency)
+ if balance_in_account_currency < 0
+ else 0,
+ "debit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0,
+ "credit_in_account_currency": abs(balance_in_account_currency)
+ if balance_in_account_currency > 0
+ else 0,
+ "credit": abs(balance_in_company_currency) if balance_in_company_currency > 0 else 0,
+ "is_period_closing_voucher_entry": 1,
+ "voucher_type": "Period Closing Voucher",
+ "voucher_no": self.name,
+ "fiscal_year": self.fiscal_year,
+ "remarks": self.remarks,
+ "is_opening": "No",
+ }
+ )
+ self.update_default_dimensions(gl_entry, dimensions)
+ return gl_entry
+
+ def get_gle_for_closing_account(self, dimension_balance, dimensions):
+ balance_in_account_currency = flt(dimension_balance.balance_in_account_currency)
+ balance_in_company_currency = flt(dimension_balance.balance_in_company_currency)
+ gl_entry = frappe._dict(
+ {
+ "company": self.company,
+ "posting_date": self.period_end_date,
+ "account": self.closing_account_head,
+ "account_currency": frappe.db.get_value(
+ "Account", self.closing_account_head, "account_currency"
+ ),
+ "debit_in_account_currency": balance_in_account_currency
+ if balance_in_account_currency > 0
+ else 0,
+ "debit": balance_in_company_currency if balance_in_company_currency > 0 else 0,
+ "credit_in_account_currency": abs(balance_in_account_currency)
+ if balance_in_account_currency < 0
+ else 0,
+ "credit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0,
+ "is_period_closing_voucher_entry": 1,
+ "voucher_type": "Period Closing Voucher",
+ "voucher_no": self.name,
+ "fiscal_year": self.fiscal_year,
+ "remarks": self.remarks,
+ "is_opening": "No",
+ }
+ )
+ self.update_default_dimensions(gl_entry, dimensions)
+ return gl_entry
+
+ def update_default_dimensions(self, gl_entry, dimensions):
+ for i, dimension in enumerate(self.accounting_dimension_fields):
+ gl_entry[dimension] = dimensions[i]
+
+ def get_account_balances_based_on_dimensions(self, report_type):
+ """Get balance for dimension-wise pl accounts"""
+ self.get_accounting_dimension_fields()
+ acc_bal_dict = frappe._dict()
+ gl_entries = []
+
+ with frappe.db.unbuffered_cursor():
+ gl_entries = self.get_gl_entries_for_current_period(report_type, as_iterator=True)
+ for gle in gl_entries:
+ acc_bal_dict = self.set_account_balance_dict(gle, acc_bal_dict)
+
+ if report_type == "Balance Sheet" and self.is_first_period_closing_voucher():
+ opening_entries = self.get_gl_entries_for_current_period(report_type, only_opening_entries=True)
+ for gle in opening_entries:
+ acc_bal_dict = self.set_account_balance_dict(gle, acc_bal_dict)
+
+ return acc_bal_dict
+
+ def get_accounting_dimension_fields(self):
+ default_dimensions = ["cost_center", "finance_book", "project"]
+ self.accounting_dimension_fields = default_dimensions + get_accounting_dimensions()
+
+ def get_gl_entries_for_current_period(self, report_type, only_opening_entries=False, as_iterator=False):
+ date_condition = ""
+ if only_opening_entries:
+ date_condition = "is_opening = 'Yes'"
+ else:
+ date_condition = f"posting_date BETWEEN '{self.period_start_date}' AND '{self.period_end_date}' and is_opening = 'No'"
+
+ # nosemgrep
+ return frappe.db.sql(
+ """
+ SELECT
+ name,
+ posting_date,
+ account,
+ account_currency,
+ debit_in_account_currency,
+ credit_in_account_currency,
+ debit,
+ credit,
+ {}
+ FROM `tabGL Entry`
+ WHERE
+ {}
+ AND company = %s
+ AND voucher_type != 'Period Closing Voucher'
+ AND EXISTS(SELECT name FROM `tabAccount` WHERE name = account AND report_type = %s)
+ AND is_cancelled = 0
+ """.format(
+ ", ".join(self.accounting_dimension_fields),
+ date_condition,
+ ),
+ (self.company, report_type),
+ as_dict=1,
+ as_iterator=as_iterator,
+ )
+
+ def set_account_balance_dict(self, gle, acc_bal_dict):
+ key = self.get_key(gle)
+
+ acc_bal_dict.setdefault(key, frappe._dict()).setdefault(
+ gle.account,
+ frappe._dict(
+ {
+ "debit_in_account_currency": 0,
+ "credit_in_account_currency": 0,
+ "debit": 0,
+ "credit": 0,
+ "account_currency": gle.account_currency,
+ }
+ ),
+ )
+
+ acc_bal_dict[key][gle.account].debit_in_account_currency += flt(gle.debit_in_account_currency)
+ acc_bal_dict[key][gle.account].credit_in_account_currency += flt(gle.credit_in_account_currency)
+ acc_bal_dict[key][gle.account].debit += flt(gle.debit)
+ acc_bal_dict[key][gle.account].credit += flt(gle.credit)
+
+ # dimension-wise total balances
+ acc_bal_dict[key].setdefault(
+ "balances",
+ frappe._dict(
+ {
+ "balance_in_account_currency": 0,
+ "balance_in_company_currency": 0,
+ }
+ ),
+ )
+
+ balance_in_account_currency = flt(gle.debit_in_account_currency) - flt(gle.credit_in_account_currency)
+ balance_in_company_currency = flt(gle.debit) - flt(gle.credit)
+
+ acc_bal_dict[key]["balances"].balance_in_account_currency += balance_in_account_currency
+ acc_bal_dict[key]["balances"].balance_in_company_currency += balance_in_company_currency
+
+ return acc_bal_dict
+
+ def get_key(self, gle):
+ return tuple([gle.get(dimension) for dimension in self.accounting_dimension_fields])
+
+ def get_account_closing_balances(self):
+ pl_closing_entries = self.get_closing_entries_for_pl_accounts()
+ bs_closing_entries = self.get_closing_entries_for_balance_sheet_accounts()
+ closing_entries = pl_closing_entries + bs_closing_entries
+ return closing_entries
+
+ def get_closing_entries_for_pl_accounts(self):
+ closing_entries = copy.deepcopy(self.pl_accounts_reverse_gle)
+ for d in self.pl_accounts_reverse_gle:
+ # reverse debit and credit
+ gle_copy = copy.deepcopy(d)
+ gle_copy.debit = d.credit
+ gle_copy.credit = d.debit
+ gle_copy.debit_in_account_currency = d.credit_in_account_currency
+ gle_copy.credit_in_account_currency = d.debit_in_account_currency
+ gle_copy.is_period_closing_voucher_entry = 0
+ gle_copy.period_closing_voucher = self.name
+ closing_entries.append(gle_copy)
+
+ return closing_entries
+
+ def get_closing_entries_for_balance_sheet_accounts(self):
+ closing_entries = []
+ balance_sheet_account_balances = self.get_account_balances_based_on_dimensions(
+ report_type="Balance Sheet"
+ )
+
+ for dimensions, account_balances in balance_sheet_account_balances.items():
+ for acc, balances in account_balances.items():
+ balance_in_company_currency = flt(balances.debit_in_account_currency) - flt(
+ balances.credit_in_account_currency
+ )
+ if acc != "balances" and balance_in_company_currency:
+ closing_entries.append(self.get_closing_entry(acc, balances, dimensions))
+
+ return closing_entries
+
+ def get_closing_entry(self, account, balances, dimensions):
+ closing_entry = frappe._dict(
+ {
+ "company": self.company,
+ "closing_date": self.period_end_date,
+ "period_closing_voucher": self.name,
+ "account": account,
+ "account_currency": balances.account_currency,
+ "debit_in_account_currency": flt(balances.debit_in_account_currency),
+ "debit": flt(balances.debit),
+ "credit_in_account_currency": flt(balances.credit_in_account_currency),
+ "credit": flt(balances.credit),
+ "is_period_closing_voucher_entry": 0,
+ }
+ )
+ self.update_default_dimensions(closing_entry, dimensions)
+ return closing_entry
+
+ def is_first_period_closing_voucher(self):
+ return not frappe.db.exists(
+ "Period Closing Voucher",
+ {"company": self.company, "docstatus": 1, "name": ("!=", self.name)},
+ )
+
+ def cancel_gl_entries(self):
+ if self.get_gle_count_against_current_pcv() > 5000:
frappe.enqueue(
process_cancellation,
voucher_type="Period Closing Voucher",
@@ -73,308 +428,30 @@ class PeriodClosingVoucher(AccountsController):
else:
process_cancellation(voucher_type="Period Closing Voucher", voucher_no=self.name)
- def validate_future_closing_vouchers(self):
- if frappe.db.exists(
- "Period Closing Voucher",
- {"posting_date": (">", self.posting_date), "docstatus": 1, "company": self.company},
- ):
- frappe.throw(
- _(
- "You can not cancel this Period Closing Voucher, please cancel the future Period Closing Vouchers first"
- )
- )
-
- def validate_account_head(self):
- closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
-
- if closing_account_type not in ["Liability", "Equity"]:
- frappe.throw(
- _("Closing Account {0} must be of type Liability / Equity").format(self.closing_account_head)
- )
-
- account_currency = get_account_currency(self.closing_account_head)
- company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
- if account_currency != company_currency:
- frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency))
-
- def validate_posting_date(self):
- validate_fiscal_year(
- self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
- )
-
- self.year_start_date = get_fiscal_year(self.posting_date, self.fiscal_year, company=self.company)[1]
-
- self.check_if_previous_year_closed()
-
- pcv = frappe.qb.DocType("Period Closing Voucher")
- existing_entry = (
- frappe.qb.from_(pcv)
- .select(pcv.name)
- .where(
- (pcv.posting_date >= self.posting_date)
- & (pcv.fiscal_year == self.fiscal_year)
- & (pcv.docstatus == 1)
- & (pcv.company == self.company)
- )
- .run()
- )
-
- if existing_entry and existing_entry[0][0]:
- frappe.throw(
- _("Another Period Closing Entry {0} has been made after {1}").format(
- existing_entry[0][0], self.posting_date
- )
- )
-
- def check_if_previous_year_closed(self):
- last_year_closing = add_days(self.year_start_date, -1)
- previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
- if not previous_fiscal_year:
- return
-
- previous_fiscal_year_start_date = previous_fiscal_year[0][1]
- if not frappe.db.exists(
+ def get_gle_count_against_current_pcv(self):
+ return frappe.db.count(
"GL Entry",
- {
- "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
- "company": self.company,
- "is_cancelled": 0,
- },
- ):
- return
-
- if not frappe.db.exists(
- "Period Closing Voucher",
- {
- "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
- "docstatus": 1,
- "company": self.company,
- },
- ):
- frappe.throw(_("Previous Year is not closed, please close it first"))
-
- def make_gl_entries(self, get_opening_entries=False):
- gl_entries = self.get_gl_entries()
- closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
- if len(gl_entries + closing_entries) > 3000:
- frappe.enqueue(
- process_gl_and_closing_entries,
- gl_entries=gl_entries,
- closing_entries=closing_entries,
- voucher_name=self.name,
- company=self.company,
- closing_date=self.posting_date,
- timeout=3000,
- )
-
- frappe.msgprint(
- _("The GL Entries will be processed in the background, it can take a few minutes."),
- alert=True,
- )
- else:
- process_gl_and_closing_entries(
- gl_entries, closing_entries, self.name, self.company, self.posting_date
- )
-
- def get_grouped_gl_entries(self, get_opening_entries=False):
- closing_entries = []
- for acc in self.get_balances_based_on_dimensions(
- group_by_account=True, for_aggregation=True, get_opening_entries=get_opening_entries
- ):
- closing_entries.append(self.get_closing_entries(acc))
-
- return closing_entries
-
- def get_gl_entries(self):
- gl_entries = []
-
- # pl account
- for acc in self.get_balances_based_on_dimensions(
- group_by_account=True, report_type="Profit and Loss"
- ):
- if flt(acc.bal_in_company_currency):
- gl_entries.append(self.get_gle_for_pl_account(acc))
-
- # closing liability account
- for acc in self.get_balances_based_on_dimensions(
- group_by_account=False, report_type="Profit and Loss"
- ):
- if flt(acc.bal_in_company_currency):
- gl_entries.append(self.get_gle_for_closing_account(acc))
-
- return gl_entries
-
- def get_gle_for_pl_account(self, acc):
- gl_entry = self.get_gl_dict(
- {
- "company": self.company,
- "closing_date": self.posting_date,
- "account": acc.account,
- "cost_center": acc.cost_center,
- "finance_book": acc.finance_book,
- "account_currency": acc.account_currency,
- "debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
- if flt(acc.bal_in_account_currency) < 0
- else 0,
- "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
- "credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
- if flt(acc.bal_in_account_currency) > 0
- else 0,
- "credit": abs(flt(acc.bal_in_company_currency))
- if flt(acc.bal_in_company_currency) > 0
- else 0,
- "is_period_closing_voucher_entry": 1,
- },
- item=acc,
- )
- self.update_default_dimensions(gl_entry, acc)
- return gl_entry
-
- def get_gle_for_closing_account(self, acc):
- gl_entry = self.get_gl_dict(
- {
- "company": self.company,
- "closing_date": self.posting_date,
- "account": self.closing_account_head,
- "cost_center": acc.cost_center,
- "finance_book": acc.finance_book,
- "account_currency": acc.account_currency,
- "debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
- if flt(acc.bal_in_account_currency) > 0
- else 0,
- "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
- "credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
- if flt(acc.bal_in_account_currency) < 0
- else 0,
- "credit": abs(flt(acc.bal_in_company_currency))
- if flt(acc.bal_in_company_currency) < 0
- else 0,
- "is_period_closing_voucher_entry": 1,
- },
- item=acc,
- )
- self.update_default_dimensions(gl_entry, acc)
- return gl_entry
-
- def get_closing_entries(self, acc):
- closing_entry = self.get_gl_dict(
- {
- "company": self.company,
- "closing_date": self.posting_date,
- "period_closing_voucher": self.name,
- "account": acc.account,
- "cost_center": acc.cost_center,
- "finance_book": acc.finance_book,
- "account_currency": acc.account_currency,
- "debit_in_account_currency": flt(acc.debit_in_account_currency),
- "debit": flt(acc.debit),
- "credit_in_account_currency": flt(acc.credit_in_account_currency),
- "credit": flt(acc.credit),
- },
- item=acc,
+ {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
)
- for dimension in self.accounting_dimensions:
- closing_entry.update({dimension: acc.get(dimension)})
- return closing_entry
-
- def update_default_dimensions(self, gl_entry, acc):
- if not self.accounting_dimensions:
- self.accounting_dimensions = get_accounting_dimensions()
-
- for dimension in self.accounting_dimensions:
- gl_entry.update({dimension: acc.get(dimension)})
-
- def get_balances_based_on_dimensions(
- self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False
- ):
- """Get balance for dimension-wise pl accounts"""
-
- qb_dimension_fields = ["cost_center", "finance_book", "project"]
-
- self.accounting_dimensions = get_accounting_dimensions()
- for dimension in self.accounting_dimensions:
- qb_dimension_fields.append(dimension)
-
- if group_by_account:
- qb_dimension_fields.append("account")
-
- account_filters = {
- "company": self.company,
- "is_group": 0,
- }
-
- if report_type:
- account_filters.update({"report_type": report_type})
-
- accounts = frappe.get_all("Account", filters=account_filters, pluck="name")
-
- gl_entry = frappe.qb.DocType("GL Entry")
- query = frappe.qb.from_(gl_entry).select(gl_entry.account, gl_entry.account_currency)
-
- if not for_aggregation:
- query = query.select(
- (Sum(gl_entry.debit_in_account_currency) - Sum(gl_entry.credit_in_account_currency)).as_(
- "bal_in_account_currency"
- ),
- (Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("bal_in_company_currency"),
- )
- else:
- query = query.select(
- (Sum(gl_entry.debit_in_account_currency)).as_("debit_in_account_currency"),
- (Sum(gl_entry.credit_in_account_currency)).as_("credit_in_account_currency"),
- (Sum(gl_entry.debit)).as_("debit"),
- (Sum(gl_entry.credit)).as_("credit"),
- )
-
- for dimension in qb_dimension_fields:
- query = query.select(gl_entry[dimension])
-
- query = query.where(
- (gl_entry.company == self.company)
- & (gl_entry.is_cancelled == 0)
- & (gl_entry.account.isin(accounts))
- )
-
- if get_opening_entries:
- query = query.where(
- ( # noqa: UP034
- (gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date))
- | (gl_entry.is_opening == "Yes")
- )
- )
- else:
- query = query.where(
- gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
- & gl_entry.is_opening
- == "No"
- )
-
- if for_aggregation:
- query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
-
- for dimension in qb_dimension_fields:
- query = query.groupby(gl_entry[dimension])
-
- return query.run(as_dict=1)
-
-
-def process_gl_and_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
- from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
- make_closing_entries,
- )
+def process_gl_and_closing_entries(doc):
from erpnext.accounts.general_ledger import make_gl_entries
try:
+ gl_entries = doc.get_pcv_gl_entries()
if gl_entries:
make_gl_entries(gl_entries, merge_entries=False)
- make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
- frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
+
+ closing_entries = doc.get_account_closing_balances()
+ if closing_entries:
+ make_closing_entries(closing_entries, doc.name, doc.company, doc.period_end_date)
+
+ frappe.db.set_value(doc.doctype, doc.name, "gle_processing_status", "Completed")
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
- frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
+ frappe.db.set_value(doc.doctype, doc.name, "gle_processing_status", "Failed")
def process_cancellation(voucher_type, voucher_no):
@@ -395,3 +472,29 @@ def delete_closing_entries(voucher_no):
frappe.qb.from_(closing_balance).delete().where(
closing_balance.period_closing_voucher == voucher_no
).run()
+
+
+@frappe.whitelist()
+def get_period_start_end_date(fiscal_year, company):
+ fy_start_date, fy_end_date = frappe.db.get_value(
+ "Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
+ )
+ prev_closed_period_end_date = get_previous_closed_period_in_current_year(fiscal_year, company)
+ period_start_date = (
+ add_days(prev_closed_period_end_date, 1) if prev_closed_period_end_date else fy_start_date
+ )
+ return period_start_date, fy_end_date
+
+
+def get_previous_closed_period_in_current_year(fiscal_year, company):
+ prev_closed_period_end_date = frappe.db.get_value(
+ "Period Closing Voucher",
+ filters={
+ "company": company,
+ "fiscal_year": fiscal_year,
+ "docstatus": 1,
+ },
+ fieldname=["period_end_date"],
+ order_by="period_end_date desc",
+ )
+ return prev_closed_period_end_date
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index 1bd565e1b36..e9d65f7f856 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -317,16 +317,18 @@ class TestPeriodClosingVoucher(unittest.TestCase):
repost_doc.posting_date = today()
repost_doc.save()
- def make_period_closing_voucher(self, posting_date=None, submit=True):
+ def make_period_closing_voucher(self, posting_date, submit=True):
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
+ fy = get_fiscal_year(posting_date, company="Test PCV Company")
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": posting_date or today(),
- "posting_date": posting_date or today(),
+ "period_start_date": fy[1],
+ "period_end_date": fy[2],
"company": "Test PCV Company",
- "fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
+ "fiscal_year": fy[0],
"cost_center": cost_center,
"closing_account_head": surplus_account,
"remarks": "test",
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 8c8ba633df0..f37e542dd89 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -46,8 +46,8 @@ class RepostAccountingLedger(Document):
frappe.db.get_all(
"Period Closing Voucher",
filters={"company": self.company},
- order_by="posting_date desc",
- pluck="posting_date",
+ order_by="period_end_date desc",
+ pluck="period_end_date",
limit=1,
)
or None
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
index f631ef437d6..9f906bb7647 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
@@ -129,13 +129,15 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
cost_center=self.cost_center,
rate=100,
)
+ fy = get_fiscal_year(today(), company=self.company)
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": today(),
- "posting_date": today(),
+ "period_start_date": fy[1],
+ "period_end_date": today(),
"company": self.company,
- "fiscal_year": get_fiscal_year(today(), company=self.company)[0],
+ "fiscal_year": fy[0],
"cost_center": self.cost_center,
"closing_account_head": self.retained_earnings,
"remarks": "test",
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index ad8cc97e101..856e2b96af0 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -37,13 +37,14 @@ def make_gl_entries(
validate_disabled_accounts(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
- create_payment_ledger_entry(
- gl_map,
- cancel=0,
- adv_adj=adv_adj,
- update_outstanding=update_outstanding,
- from_repost=from_repost,
- )
+ if gl_map[0].voucher_type != "Period Closing Voucher":
+ create_payment_ledger_entry(
+ gl_map,
+ cancel=0,
+ adv_adj=adv_adj,
+ update_outstanding=update_outstanding,
+ from_repost=from_repost,
+ )
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries
elif gl_map:
@@ -116,17 +117,16 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
def validate_disabled_accounts(gl_map):
accounts = [d.account for d in gl_map if d.account]
- Account = frappe.qb.DocType("Account")
+ disabled_accounts = frappe.get_all(
+ "Account",
+ filters={"disabled": 1, "is_group": 0, "company": gl_map[0].company},
+ fields=["name"],
+ )
- disabled_accounts = (
- frappe.qb.from_(Account)
- .where(Account.name.isin(accounts) & Account.disabled == 1)
- .select(Account.name, Account.disabled)
- ).run(as_dict=True)
-
- if disabled_accounts:
+ used_disabled_accounts = set(accounts).intersection(set([d.name for d in disabled_accounts]))
+ if used_disabled_accounts:
account_list = "
"
- account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
+ account_list += ", ".join([frappe.bold(d) for d in used_disabled_accounts])
frappe.throw(
_("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
title=_("Disabled Account Selected"),
@@ -708,7 +708,7 @@ def validate_against_pcv(is_opening, posting_date, company):
)
last_pcv_date = frappe.db.get_value(
- "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
+ "Period Closing Voucher", {"docstatus": 1, "company": company}, "max(period_end_date)"
)
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 43100c81285..8d4a8579ae3 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -385,6 +385,7 @@ class ReceivablePayableReport:
self.delivery_notes = frappe._dict()
# delivery note link inside sales invoice
+ # nosemgrep
si_against_dn = frappe.db.sql(
"""
select parent, delivery_note
@@ -400,6 +401,7 @@ class ReceivablePayableReport:
if d.delivery_note:
self.delivery_notes.setdefault(d.parent, set()).add(d.delivery_note)
+ # nosemgrep
dn_against_si = frappe.db.sql(
"""
select distinct parent, against_sales_invoice
@@ -417,13 +419,16 @@ class ReceivablePayableReport:
def get_invoice_details(self):
self.invoice_details = frappe._dict()
if self.account_type == "Receivable":
+ # nosemgrep
si_list = frappe.db.sql(
"""
select name, due_date, po_no
from `tabSales Invoice`
where posting_date <= %s
+ and company = %s
+ and docstatus = 1
""",
- self.filters.report_date,
+ (self.filters.report_date, self.filters.company),
as_dict=1,
)
for d in si_list:
@@ -431,6 +436,7 @@ class ReceivablePayableReport:
# Get Sales Team
if self.filters.show_sales_person:
+ # nosemgrep
sales_team = frappe.db.sql(
"""
select parent, sales_person
@@ -445,25 +451,33 @@ class ReceivablePayableReport:
)
if self.account_type == "Payable":
+ # nosemgrep
for pi in frappe.db.sql(
"""
select name, due_date, bill_no, bill_date
from `tabPurchase Invoice`
- where posting_date <= %s
+ where
+ posting_date <= %s
+ and company = %s
+ and docstatus = 1
""",
- self.filters.report_date,
+ (self.filters.report_date, self.filters.company),
as_dict=1,
):
self.invoice_details.setdefault(pi.name, pi)
# Invoices booked via Journal Entries
+ # nosemgrep
journal_entries = frappe.db.sql(
"""
select name, due_date, bill_no, bill_date
from `tabJournal Entry`
- where posting_date <= %s
+ where
+ posting_date <= %s
+ and company = %s
+ and docstatus = 1
""",
- self.filters.report_date,
+ (self.filters.report_date, self.filters.company),
as_dict=1,
)
@@ -472,6 +486,8 @@ class ReceivablePayableReport:
self.invoice_details.setdefault(je.name, je)
def set_party_details(self, row):
+ if not row.party:
+ return
# customer / supplier name
party_details = self.get_party_details(row.party) or {}
row.update(party_details)
@@ -496,6 +512,7 @@ class ReceivablePayableReport:
def get_payment_terms(self, row):
# build payment_terms for row
+ # nosemgrep
payment_terms_details = frappe.db.sql(
f"""
select
@@ -708,6 +725,7 @@ class ReceivablePayableReport:
def get_return_entries(self):
doctype = "Sales Invoice" if self.account_type == "Receivable" else "Purchase Invoice"
filters = {
+ "posting_date": ("<=", self.filters.report_date),
"is_return": 1,
"docstatus": 1,
"company": self.filters.company,
@@ -815,6 +833,7 @@ class ReceivablePayableReport:
if self.filters.get("sales_person"):
lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"])
+ # nosemgrep
records = frappe.db.sql(
"""
select distinct parent, parenttype
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 6d7635979bb..c233f3c7e2b 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -9,6 +9,7 @@ import re
import frappe
from frappe import _
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
+from pypika.terms import ExistsCriterion
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@@ -181,12 +182,12 @@ def get_data(
company,
period_list[0]["year_start_date"] if only_current_fiscal_year else None,
period_list[-1]["to_date"],
- root.lft,
- root.rgt,
filters,
gl_entries_by_account,
- ignore_closing_entries=ignore_closing_entries,
+ root.lft,
+ root.rgt,
root_type=root_type,
+ ignore_closing_entries=ignore_closing_entries,
)
calculate_values(
@@ -419,93 +420,78 @@ def set_gl_entries_by_account(
company,
from_date,
to_date,
- root_lft,
- root_rgt,
filters,
gl_entries_by_account,
+ root_lft=None,
+ root_rgt=None,
+ root_type=None,
ignore_closing_entries=False,
ignore_opening_entries=False,
- root_type=None,
):
"""Returns a dict like { "account": [gl entries], ... }"""
gl_entries = []
- account_filters = {
- "company": company,
- "is_group": 0,
- "lft": (">=", root_lft),
- "rgt": ("<=", root_rgt),
- }
-
- if root_type:
- account_filters.update(
- {
- "root_type": root_type,
- }
+ # For balance sheet
+ ignore_closing_balances = frappe.db.get_single_value(
+ "Accounts Settings", "ignore_account_closing_balance"
+ )
+ if not from_date and not ignore_closing_balances:
+ last_period_closing_voucher = frappe.db.get_all(
+ "Period Closing Voucher",
+ filters={
+ "docstatus": 1,
+ "company": filters.company,
+ "period_end_date": ("<", filters["period_start_date"]),
+ },
+ fields=["period_end_date", "name"],
+ order_by="period_end_date desc",
+ limit=1,
)
+ if last_period_closing_voucher:
+ gl_entries += get_accounting_entries(
+ "Account Closing Balance",
+ from_date,
+ to_date,
+ filters,
+ root_lft,
+ root_rgt,
+ root_type,
+ ignore_closing_entries,
+ last_period_closing_voucher[0].name,
+ )
+ from_date = add_days(last_period_closing_voucher[0].period_end_date, 1)
+ ignore_opening_entries = True
- accounts_list = frappe.db.get_all(
- "Account",
- filters=account_filters,
- pluck="name",
+ gl_entries += get_accounting_entries(
+ "GL Entry",
+ from_date,
+ to_date,
+ filters,
+ root_lft,
+ root_rgt,
+ root_type,
+ ignore_closing_entries,
+ ignore_opening_entries=ignore_opening_entries,
)
- if accounts_list:
- # For balance sheet
- ignore_closing_balances = frappe.db.get_single_value(
- "Accounts Settings", "ignore_account_closing_balance"
- )
- if not from_date and not ignore_closing_balances:
- last_period_closing_voucher = frappe.db.get_all(
- "Period Closing Voucher",
- filters={
- "docstatus": 1,
- "company": filters.company,
- "posting_date": ("<", filters["period_start_date"]),
- },
- fields=["posting_date", "name"],
- order_by="posting_date desc",
- limit=1,
- )
- if last_period_closing_voucher:
- gl_entries += get_accounting_entries(
- "Account Closing Balance",
- from_date,
- to_date,
- accounts_list,
- filters,
- ignore_closing_entries,
- last_period_closing_voucher[0].name,
- )
- from_date = add_days(last_period_closing_voucher[0].posting_date, 1)
- ignore_opening_entries = True
+ if filters and filters.get("presentation_currency"):
+ convert_to_presentation_currency(gl_entries, get_currency(filters))
- gl_entries += get_accounting_entries(
- "GL Entry",
- from_date,
- to_date,
- accounts_list,
- filters,
- ignore_closing_entries,
- ignore_opening_entries=ignore_opening_entries,
- )
+ for entry in gl_entries:
+ gl_entries_by_account.setdefault(entry.account, []).append(entry)
- if filters and filters.get("presentation_currency"):
- convert_to_presentation_currency(gl_entries, get_currency(filters))
-
- for entry in gl_entries:
- gl_entries_by_account.setdefault(entry.account, []).append(entry)
-
- return gl_entries_by_account
+ return gl_entries_by_account
def get_accounting_entries(
doctype,
from_date,
to_date,
- accounts,
filters,
- ignore_closing_entries,
+ root_lft=None,
+ root_rgt=None,
+ root_type=None,
+ ignore_closing_entries=None,
period_closing_voucher=None,
ignore_opening_entries=False,
):
@@ -535,13 +521,30 @@ def get_accounting_entries(
query = query.where(gl_entry.period_closing_voucher == period_closing_voucher)
query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters)
- query = query.where(gl_entry.account.isin(accounts))
+
+ if (root_lft and root_rgt) or root_type:
+ account_filter_query = get_account_filter_query(root_lft, root_rgt, root_type, gl_entry)
+ query = query.where(ExistsCriterion(account_filter_query))
entries = query.run(as_dict=True)
return entries
+def get_account_filter_query(root_lft, root_rgt, root_type, gl_entry):
+ acc = frappe.qb.DocType("Account")
+ exists_query = (
+ frappe.qb.from_(acc).select(acc.name).where(acc.name == gl_entry.account).where(acc.is_group == 0)
+ )
+ if root_lft and root_rgt:
+ exists_query = exists_query.where(acc.lft >= root_lft).where(acc.rgt <= root_rgt)
+
+ if root_type:
+ exists_query = exists_query.where(acc.root_type == root_type)
+
+ return exists_query
+
+
def apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters):
gl_entry = frappe.qb.DocType(doctype)
accounting_dimensions = get_accounting_dimensions(as_list=False)
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index f216ecea15a..8ca850f301e 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -94,12 +94,6 @@ def get_data(filters):
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
- min_lft, max_rgt = frappe.db.sql(
- """select min(lft), max(rgt) from `tabAccount`
- where company=%s""",
- (filters.company,),
- )[0]
-
gl_entries_by_account = {}
opening_balances = get_opening_balances(filters)
@@ -112,10 +106,10 @@ def get_data(filters):
filters.company,
filters.from_date,
filters.to_date,
- min_lft,
- max_rgt,
filters,
gl_entries_by_account,
+ root_lft=None,
+ root_rgt=None,
ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
ignore_opening_entries=True,
)
@@ -150,9 +144,9 @@ def get_rootwise_opening_balances(filters, report_type):
if not ignore_closing_balances:
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
- filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
- fields=["posting_date", "name"],
- order_by="posting_date desc",
+ filters={"docstatus": 1, "company": filters.company, "period_end_date": ("<", filters.from_date)},
+ fields=["period_end_date", "name"],
+ order_by="period_end_date desc",
limit=1,
)
@@ -168,8 +162,8 @@ def get_rootwise_opening_balances(filters, report_type):
)
# Report getting generate from the mid of a fiscal year
- if getdate(last_period_closing_voucher[0].posting_date) < getdate(add_days(filters.from_date, -1)):
- start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
+ if getdate(last_period_closing_voucher[0].period_end_date) < getdate(add_days(filters.from_date, -1)):
+ start_date = add_days(last_period_closing_voucher[0].period_end_date, 1)
gle += get_opening_balance(
"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index e59938909c7..515a299504a 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -314,6 +314,7 @@ erpnext.patches.v13_0.update_docs_link
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
+erpnext.patches.v14_0.set_period_start_end_date_in_pcv
erpnext.patches.v14_0.update_closing_balances #14-07-2023
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts
diff --git a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py
index e305b375c7f..5609b6bb895 100644
--- a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py
+++ b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py
@@ -15,7 +15,7 @@ def execute():
def find_broken_stock_entries() -> list[StockEntryCode]:
period_closing_date = frappe.db.get_value(
- "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc"
+ "Period Closing Voucher", {"docstatus": 1}, "period_end_date", order_by="period_end_date desc"
)
stock_entries_to_patch = frappe.db.sql(
diff --git a/erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py b/erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py
new file mode 100644
index 00000000000..8020a286f69
--- /dev/null
+++ b/erpnext/patches/v14_0/set_period_start_end_date_in_pcv.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+
+import frappe
+
+
+def execute():
+ # nosemgrep
+ frappe.db.sql(
+ """
+ UPDATE `tabPeriod Closing Voucher`
+ SET
+ period_start_date = (select year_start_date from `tabFiscal Year` where name = fiscal_year),
+ period_end_date = posting_date
+ """
+ )
diff --git a/erpnext/patches/v14_0/single_to_multi_dunning.py b/erpnext/patches/v14_0/single_to_multi_dunning.py
index 3b01871d437..98be0204518 100644
--- a/erpnext/patches/v14_0/single_to_multi_dunning.py
+++ b/erpnext/patches/v14_0/single_to_multi_dunning.py
@@ -66,7 +66,7 @@ def get_accounts_closing_date():
) # always returns datetime.date
period_closing_date = frappe.db.get_value(
- "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc"
+ "Period Closing Voucher", {"docstatus": 1}, "period_end_date", order_by="period_end_date desc"
)
# Set most recent frozen/closing date as filter
diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py
index cfc29c87fa1..a2670717ee9 100644
--- a/erpnext/patches/v14_0/update_closing_balances.py
+++ b/erpnext/patches/v14_0/update_closing_balances.py
@@ -7,67 +7,68 @@ import frappe
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+ get_accounting_dimensions,
+)
from erpnext.accounts.utils import get_fiscal_year
def execute():
frappe.db.truncate("Account Closing Balance")
+ gle_fields = get_gle_fields()
+
for company in frappe.get_all("Company", pluck="name"):
i = 0
company_wise_order = {}
- for pcv in frappe.db.get_all(
- "Period Closing Voucher",
- fields=["company", "posting_date", "name"],
- filters={"docstatus": 1, "company": company},
- order_by="posting_date",
- ):
+ for pcv in get_period_closing_vouchers(company):
company_wise_order.setdefault(pcv.company, [])
- if pcv.posting_date not in company_wise_order[pcv.company]:
- pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
- pcv_doc.year_start_date = get_fiscal_year(
- pcv.posting_date, pcv.fiscal_year, company=pcv.company
- )[1]
+ if pcv.period_end_date not in company_wise_order[pcv.company]:
+ pcv.pl_accounts_reverse_gle = get_pcv_gl_entries(pcv, gle_fields)
+ closing_entries = pcv.get_account_closing_balances()
+ if closing_entries:
+ make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date)
- # get gl entries against pcv
- gl_entries = frappe.db.get_all(
- "GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"]
- )
- for entry in gl_entries:
- entry["is_period_closing_voucher_entry"] = 1
- entry["closing_date"] = pcv_doc.posting_date
- entry["period_closing_voucher"] = pcv_doc.name
-
- closing_entries = []
-
- if pcv.posting_date not in company_wise_order[pcv.company]:
- # get all gl entries for the year
- closing_entries = frappe.db.get_all(
- "GL Entry",
- filters={
- "is_cancelled": 0,
- "voucher_no": ["!=", pcv.name],
- "posting_date": ["between", [pcv_doc.year_start_date, pcv.posting_date]],
- "is_opening": "No",
- "company": company,
- },
- fields=["*"],
- )
-
- if i == 0:
- # add opening entries only for the first pcv
- closing_entries += frappe.db.get_all(
- "GL Entry",
- filters={"is_cancelled": 0, "is_opening": "Yes", "company": company},
- fields=["*"],
- )
-
- for entry in closing_entries:
- entry["closing_date"] = pcv_doc.posting_date
- entry["period_closing_voucher"] = pcv_doc.name
-
- entries = gl_entries + closing_entries
-
- make_closing_entries(entries, pcv.name, pcv.company, pcv.posting_date)
- company_wise_order[pcv.company].append(pcv.posting_date)
+ company_wise_order[pcv.company].append(pcv.period_end_date)
i += 1
+
+
+def get_gle_fields():
+ default_diemnsion_fields = ["cost_center", "finance_book", "project"]
+ accounting_dimension_fields = get_accounting_dimensions()
+ gle_fields = [
+ "name",
+ "posting_date",
+ "account",
+ "account_currency",
+ "debit",
+ "credit",
+ "debit_in_account_currency",
+ "credit_in_account_currency",
+ *default_diemnsion_fields,
+ *accounting_dimension_fields,
+ ]
+
+ return gle_fields
+
+
+def get_period_closing_vouchers(company):
+ return frappe.db.get_all(
+ "Period Closing Voucher",
+ fields=["name", "closing_account_head", "period_start_date", "period_end_date", "company"],
+ filters={"docstatus": 1, "company": company},
+ order_by="period_end_date",
+ )
+
+
+def get_pcv_gl_entries(pcv, gle_fields):
+ gl_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={"voucher_no": pcv.name, "account": ["!=", pcv.closing_account_head], "is_cancelled": 0},
+ fields=gle_fields,
+ )
+ for entry in gl_entries:
+ entry["is_period_closing_voucher_entry"] = 1
+ entry["closing_date"] = pcv.period_end_date
+ entry["period_closing_voucher"] = pcv.name
+ return gl_entries
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index d203b979c61..cb006bb3e99 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -1188,18 +1188,19 @@ def make_shipment(source_name, target_doc=None):
# As we are using session user details in the pickup_contact then pickup_contact_person will be session user
target.pickup_contact_person = frappe.session.user
- contact = frappe.db.get_value(
- "Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
- )
- delivery_contact_display = f"{source.contact_display}"
- if contact:
- if contact.email_id:
- delivery_contact_display += "
" + contact.email_id
- if contact.phone:
- delivery_contact_display += "
" + contact.phone
- if contact.mobile_no and not contact.phone:
- delivery_contact_display += "
" + contact.mobile_no
- target.delivery_contact = delivery_contact_display
+ if source.contact_person:
+ contact = frappe.db.get_value(
+ "Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
+ )
+ delivery_contact_display = f"{source.contact_display}"
+ if contact:
+ if contact.email_id:
+ delivery_contact_display += "
" + contact.email_id
+ if contact.phone:
+ delivery_contact_display += "
" + contact.phone
+ if contact.mobile_no and not contact.phone:
+ delivery_contact_display += "
" + contact.mobile_no
+ target.delivery_contact = delivery_contact_display
if source.shipping_address_name:
target.delivery_address_name = source.shipping_address_name
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index c20eadeb78d..0c81a296d5e 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -125,7 +125,7 @@ class RepostItemValuation(Document):
query = (
frappe.qb.from_(table)
- .select(Max(table.posting_date))
+ .select(Max(table.period_end_date))
.where((table.company == company) & (table.docstatus == 1))
).run()
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index ae6ca4e25d0..5993580032f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1309,10 +1309,10 @@ class StockEntry(StockController):
3. Check FG Item and Qty against WO if present (mfg)
"""
production_item, wo_qty, finished_items = None, 0, []
-
- wo_details = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"])
- if wo_details:
- production_item, wo_qty = wo_details
+ if self.work_order:
+ wo_details = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"])
+ if wo_details:
+ production_item, wo_qty = wo_details
for d in self.get("items"):
if d.is_finished_item:
diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py
index 85337a3bf54..ad2b46b393f 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -1,6 +1,7 @@
import unittest
import frappe
+from frappe.utils.make_random import get_random
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
@@ -11,7 +12,7 @@ DEFAULT_FILTERS = {
}
-batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc")
+batch = get_random("Batch")
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
("Stock Ledger", {"_optional": True}),
diff --git a/erpnext/tests/test_perf.py b/erpnext/tests/test_perf.py
index fc17b1dcbda..db54ca97395 100644
--- a/erpnext/tests/test_perf.py
+++ b/erpnext/tests/test_perf.py
@@ -3,7 +3,7 @@ from frappe.tests.utils import FrappeTestCase
INDEXED_FIELDS = {
"Bin": ["item_code"],
- "GL Entry": ["voucher_type", "against_voucher_type"],
+ "GL Entry": ["voucher_no", "posting_date", "company", "party"],
"Purchase Order Item": ["item_code"],
"Stock Ledger Entry": ["warehouse"],
}