mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-02 03:39:11 +00:00
fix: Aggregation with previous closing balance
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
@@ -12,45 +10,113 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
|
|
||||||
|
|
||||||
class ClosingBalance(Document):
|
class ClosingBalance(Document):
|
||||||
def aggregate_with_last_closing_balance(self, accounting_dimensions: List[str]):
|
pass
|
||||||
closing_balance = frappe.qb.DocType("Closing Balance")
|
|
||||||
|
|
||||||
query = (
|
|
||||||
frappe.qb.from_(closing_balance)
|
def make_closing_entries(closing_entries, voucher_name):
|
||||||
.select(closing_balance.debit, closing_balance.credit)
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
.where(
|
company = closing_entries[0].get("company")
|
||||||
closing_balance.closing_date < self.closing_date,
|
closing_date = closing_entries[0].get("closing_date")
|
||||||
)
|
|
||||||
|
previous_closing_entries = get_previous_closing_entries(
|
||||||
|
company, closing_date, accounting_dimensions
|
||||||
|
)
|
||||||
|
combined_entries = closing_entries + previous_closing_entries
|
||||||
|
|
||||||
|
merged_entries = aggregate_with_last_closing_balance(combined_entries, accounting_dimensions)
|
||||||
|
|
||||||
|
for key, value in merged_entries.items():
|
||||||
|
cle = frappe.new_doc("Closing Balance")
|
||||||
|
cle.update(value)
|
||||||
|
cle.update(value["dimensions"])
|
||||||
|
cle.update(
|
||||||
|
{
|
||||||
|
"period_closing_voucher": voucher_name,
|
||||||
|
"closing_date": closing_date,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cle.submit()
|
||||||
|
|
||||||
|
|
||||||
|
def aggregate_with_last_closing_balance(entries, accounting_dimensions):
|
||||||
|
merged_entries = {}
|
||||||
|
for entry in entries:
|
||||||
|
key, key_values = generate_key(entry, accounting_dimensions)
|
||||||
|
merged_entries.setdefault(
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
"debit": 0,
|
||||||
|
"credit": 0,
|
||||||
|
"debit_in_account_currency": 0,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
merged_entries[key]["dimensions"] = key_values
|
||||||
|
merged_entries[key]["debit"] += entry.get("debit")
|
||||||
|
merged_entries[key]["credit"] += entry.get("credit")
|
||||||
|
merged_entries[key]["debit_in_account_currency"] += entry.get("debit_in_account_currency")
|
||||||
|
merged_entries[key]["credit_in_account_currency"] += entry.get("credit_in_account_currency")
|
||||||
|
|
||||||
|
return merged_entries
|
||||||
|
|
||||||
|
|
||||||
|
def generate_key(entry, accounting_dimensions):
|
||||||
|
key = [
|
||||||
|
entry.get("account"),
|
||||||
|
entry.get("account_currency"),
|
||||||
|
entry.get("cost_center"),
|
||||||
|
entry.get("project"),
|
||||||
|
entry.get("finance_book"),
|
||||||
|
entry.get("is_period_closing_voucher_entry"),
|
||||||
|
]
|
||||||
|
|
||||||
|
key_values = {
|
||||||
|
"account": entry.get("account"),
|
||||||
|
"account_currency": entry.get("account_currency"),
|
||||||
|
"cost_center": entry.get("cost_center"),
|
||||||
|
"project": entry.get("project"),
|
||||||
|
"finance_book": entry.get("finance_book"),
|
||||||
|
"is_period_closing_voucher_entry": entry.get("is_period_closing_voucher_entry"),
|
||||||
|
}
|
||||||
|
for dimension in accounting_dimensions:
|
||||||
|
key.append(entry.get(dimension))
|
||||||
|
key_values[dimension] = entry.get(dimension)
|
||||||
|
|
||||||
|
return tuple(key), key_values
|
||||||
|
|
||||||
|
|
||||||
|
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)},
|
||||||
|
fields=["name"],
|
||||||
|
order_by="posting_date desc",
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if last_period_closing_voucher:
|
||||||
|
closing_balance = frappe.qb.DocType("Closing Balance")
|
||||||
|
query = frappe.qb.from_(closing_balance).select(
|
||||||
|
closing_balance.account,
|
||||||
|
closing_balance.account_currency,
|
||||||
|
closing_balance.debit,
|
||||||
|
closing_balance.credit,
|
||||||
|
closing_balance.debit_in_account_currency,
|
||||||
|
closing_balance.credit_in_account_currency,
|
||||||
|
closing_balance.cost_center,
|
||||||
|
closing_balance.project,
|
||||||
|
closing_balance.finance_book,
|
||||||
|
closing_balance.is_period_closing_voucher_entry,
|
||||||
)
|
)
|
||||||
|
|
||||||
for dimension in accounting_dimensions:
|
for dimension in accounting_dimensions:
|
||||||
query = query.where(closing_balance[dimension] == self.get(dimension))
|
query = query.select(closing_balance[dimension])
|
||||||
|
|
||||||
query = query.orderby(closing_balance.closing_date, order=frappe.qb.desc).limit(1)
|
query = query.where(
|
||||||
|
closing_balance.period_closing_voucher == last_period_closing_voucher[0].name
|
||||||
|
)
|
||||||
|
entries = query.run(as_dict=1)
|
||||||
|
|
||||||
last_closing_balance = query.run(as_dict=1)
|
return entries
|
||||||
|
|
||||||
if last_closing_balance:
|
|
||||||
self.debit += last_closing_balance[0].debit
|
|
||||||
self.credit += last_closing_balance[0].credit
|
|
||||||
|
|
||||||
|
|
||||||
def make_closing_entries(
|
|
||||||
closing_entries, is_period_closing_voucher_entry=False, voucher_name=None
|
|
||||||
):
|
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
|
||||||
for entry in closing_entries:
|
|
||||||
cle = frappe.new_doc("Closing Balance")
|
|
||||||
cle.update(entry)
|
|
||||||
|
|
||||||
if is_period_closing_voucher_entry:
|
|
||||||
cle.update(
|
|
||||||
{
|
|
||||||
"closing_date": entry.get("posting_date"),
|
|
||||||
"is_period_closing_voucher_entry": 1,
|
|
||||||
"period_closing_voucher": voucher_name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
cle.aggregate_with_last_closing_balance(accounting_dimensions)
|
|
||||||
cle.submit()
|
|
||||||
|
|||||||
@@ -21,8 +21,14 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.db_set("gle_processing_status", "In Progress")
|
self.db_set("gle_processing_status", "In Progress")
|
||||||
self.make_gl_entries()
|
get_opening_entries = False
|
||||||
self.make_closing_entries()
|
|
||||||
|
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)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.db_set("gle_processing_status", "In Progress")
|
self.db_set("gle_processing_status", "In Progress")
|
||||||
@@ -88,34 +94,30 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_gl_entries(self):
|
def make_gl_entries(self, get_opening_entries=False):
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
|
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
if len(gl_entries) > 5000:
|
if len(gl_entries) > 5000:
|
||||||
frappe.enqueue(process_gl_entries, gl_entries=gl_entries, voucher_name=self.name, queue="long")
|
frappe.enqueue(
|
||||||
|
process_gl_entries,
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
closing_entries=closing_entries,
|
||||||
|
voucher_name=self.name,
|
||||||
|
queue="long",
|
||||||
|
)
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||||
alert=True,
|
alert=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
process_gl_entries(gl_entries, voucher_name=self.name)
|
process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
|
||||||
|
|
||||||
def make_closing_entries(self):
|
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||||
closing_entries = self.get_grouped_gl_entries()
|
|
||||||
|
|
||||||
if closing_entries:
|
|
||||||
if len(closing_entries) > 5000:
|
|
||||||
frappe.enqueue(process_closing_entries, gl_entries=closing_entries, queue="long")
|
|
||||||
frappe.msgprint(
|
|
||||||
_("The Opening Entries will be processed in the background, it can take a few minutes."),
|
|
||||||
alert=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
process_closing_entries(closing_entries)
|
|
||||||
|
|
||||||
def get_grouped_gl_entries(self):
|
|
||||||
closing_entries = []
|
closing_entries = []
|
||||||
for acc in self.get_balances_based_on_dimensions(group_by_account=True, for_aggregation=True):
|
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))
|
closing_entries.append(self.get_closing_entries(acc))
|
||||||
|
|
||||||
return closing_entries
|
return closing_entries
|
||||||
@@ -142,6 +144,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def get_gle_for_pl_account(self, acc):
|
def get_gle_for_pl_account(self, acc):
|
||||||
gl_entry = self.get_gl_dict(
|
gl_entry = self.get_gl_dict(
|
||||||
{
|
{
|
||||||
|
"closing_date": self.posting_date,
|
||||||
"account": acc.account,
|
"account": acc.account,
|
||||||
"cost_center": acc.cost_center,
|
"cost_center": acc.cost_center,
|
||||||
"finance_book": acc.finance_book,
|
"finance_book": acc.finance_book,
|
||||||
@@ -154,6 +157,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
if flt(acc.bal_in_account_currency) > 0
|
if flt(acc.bal_in_account_currency) > 0
|
||||||
else 0,
|
else 0,
|
||||||
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_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,
|
item=acc,
|
||||||
)
|
)
|
||||||
@@ -163,6 +167,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def get_gle_for_closing_account(self, acc):
|
def get_gle_for_closing_account(self, acc):
|
||||||
gl_entry = self.get_gl_dict(
|
gl_entry = self.get_gl_dict(
|
||||||
{
|
{
|
||||||
|
"closing_date": self.posting_date,
|
||||||
"account": self.closing_account_head,
|
"account": self.closing_account_head,
|
||||||
"cost_center": acc.cost_center,
|
"cost_center": acc.cost_center,
|
||||||
"finance_book": acc.finance_book,
|
"finance_book": acc.finance_book,
|
||||||
@@ -175,6 +180,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
if flt(acc.bal_in_account_currency) < 0
|
if flt(acc.bal_in_account_currency) < 0
|
||||||
else 0,
|
else 0,
|
||||||
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_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,
|
item=acc,
|
||||||
)
|
)
|
||||||
@@ -211,11 +217,11 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
gl_entry.update({dimension: acc.get(dimension)})
|
gl_entry.update({dimension: acc.get(dimension)})
|
||||||
|
|
||||||
def get_balances_based_on_dimensions(
|
def get_balances_based_on_dimensions(
|
||||||
self, group_by_account=False, report_type=None, for_aggregation=False
|
self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False
|
||||||
):
|
):
|
||||||
"""Get balance for dimension-wise pl accounts"""
|
"""Get balance for dimension-wise pl accounts"""
|
||||||
|
|
||||||
qb_dimension_fields = ["cost_center", "finance_book"]
|
qb_dimension_fields = ["cost_center", "finance_book", "project"]
|
||||||
|
|
||||||
self.accounting_dimensions = get_accounting_dimensions()
|
self.accounting_dimensions = get_accounting_dimensions()
|
||||||
for dimension in self.accounting_dimensions:
|
for dimension in self.accounting_dimensions:
|
||||||
@@ -263,9 +269,21 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
(gl_entry.company == self.company)
|
(gl_entry.company == self.company)
|
||||||
& (gl_entry.is_cancelled == 0)
|
& (gl_entry.is_cancelled == 0)
|
||||||
& (gl_entry.account.isin(accounts))
|
& (gl_entry.account.isin(accounts))
|
||||||
& (gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_opening_entries:
|
||||||
|
query = query.where(
|
||||||
|
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:
|
if for_aggregation:
|
||||||
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
|
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
|
||||||
|
|
||||||
@@ -285,13 +303,13 @@ def process_closing_entries(closing_entries):
|
|||||||
frappe.log_error(e)
|
frappe.log_error(e)
|
||||||
|
|
||||||
|
|
||||||
def process_gl_entries(gl_entries, voucher_name=None):
|
def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
|
||||||
from erpnext.accounts.doctype.closing_balance.closing_balance import make_closing_entries
|
from erpnext.accounts.doctype.closing_balance.closing_balance import make_closing_entries
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
make_gl_entries(gl_entries, merge_entries=False)
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
make_closing_entries(gl_entries, is_period_closing_voucher_entry=True, voucher_name=voucher_name)
|
make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
|
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -328,4 +328,4 @@ erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
|
|||||||
erpnext.patches.v14_0.set_pick_list_status
|
erpnext.patches.v14_0.set_pick_list_status
|
||||||
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
||||||
erpnext.patches.v14_0.update_closing_balances #13
|
erpnext.patches.v14_0.update_closing_balances
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
company_wise_order = {}
|
company_wise_order = {}
|
||||||
|
get_opening_entries = True
|
||||||
for pcv in frappe.db.get_all(
|
for pcv in frappe.db.get_all(
|
||||||
"Period Closing Voucher",
|
"Period Closing Voucher",
|
||||||
fields=["company", "posting_date", "name"],
|
fields=["company", "posting_date", "name"],
|
||||||
@@ -23,7 +24,8 @@ def execute():
|
|||||||
pcv_doc.year_start_date = get_fiscal_year(
|
pcv_doc.year_start_date = get_fiscal_year(
|
||||||
pcv.posting_date, pcv.fiscal_year, company=pcv.company
|
pcv.posting_date, pcv.fiscal_year, company=pcv.company
|
||||||
)[1]
|
)[1]
|
||||||
pcv_doc.make_closing_entries()
|
|
||||||
gl_entries = pcv_doc.get_gl_entries()
|
gl_entries = pcv_doc.get_gl_entries()
|
||||||
make_closing_entries(gl_entries, is_period_closing_voucher_entry=True, voucher_name=pcv.name)
|
closing_entries = pcv_doc.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||||
|
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
|
||||||
company_wise_order[pcv.company].append(pcv.posting_date)
|
company_wise_order[pcv.company].append(pcv.posting_date)
|
||||||
|
get_opening_entries = False
|
||||||
|
|||||||
Reference in New Issue
Block a user