perf: performance optimizations for accounting reports by refactoring account closing balance and period closing voucher (#43798)

* fix: Gl Entry form cleanup

* fix: Added indexes in gl entry table

* perf: Refactored period closing voucher to handle large volume of gle

* fix: fixes as per new period start and end date fields in PCV

* perf: performance optimization for  accounting reports

* perf: performance optimizations for account closing balance patch

* fix: test cases

* fix: lenter issues - direct use of sql query

* fix: test cases

* fix: test cases

* fix: test cases

* fix: wrong fieldname

* fix: test cases
This commit is contained in:
Nabin Hait
2024-10-23 13:07:16 +05:30
committed by GitHub
parent 9c0f17e13d
commit ced76ca5c0
25 changed files with 772 additions and 537 deletions

View File

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

View File

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

View File

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

View File

@@ -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():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = "<br>"
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):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 += "<br>" + contact.email_id
if contact.phone:
delivery_contact_display += "<br>" + contact.phone
if contact.mobile_no and not contact.phone:
delivery_contact_display += "<br>" + 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 += "<br>" + contact.email_id
if contact.phone:
delivery_contact_display += "<br>" + contact.phone
if contact.mobile_no and not contact.phone:
delivery_contact_display += "<br>" + contact.mobile_no
target.delivery_contact = delivery_contact_display
if source.shipping_address_name:
target.delivery_address_name = source.shipping_address_name

View File

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

View File

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

View File

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

View File

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