diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index e590beb0261..1a574edfe86 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -43,7 +43,7 @@ class GLEntry(Document): self.validate_and_set_fiscal_year() self.pl_must_have_cost_center() - if not self.flags.from_repost: + if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": self.check_mandatory() self.validate_cost_center() self.check_pl_account() @@ -52,7 +52,7 @@ class GLEntry(Document): def on_update(self): adv_adj = self.flags.adv_adj - if not self.flags.from_repost: + if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs() self.validate_allowed_dimensions() 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 84c941ecc10..54a76b34196 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -10,10 +10,11 @@ "fiscal_year", "amended_from", "company", - "cost_center_wise_pnl", "column_break1", "closing_account_head", - "remarks" + "remarks", + "gle_processing_status", + "error_message" ], "fields": [ { @@ -86,17 +87,26 @@ "reqd": 1 }, { - "default": "0", - "fieldname": "cost_center_wise_pnl", - "fieldtype": "Check", - "label": "Book Cost Center Wise Profit/Loss" + "depends_on": "eval:doc.docstatus!=0", + "fieldname": "gle_processing_status", + "fieldtype": "Select", + "label": "GL Entry Processing Status", + "options": "In Progress\nCompleted\nFailed", + "read_only": 1 + }, + { + "depends_on": "eval:doc.gle_processing_status=='Failed'", + "fieldname": "error_message", + "fieldtype": "Text", + "label": "Error Message", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-20 15:27:37.210458", + "modified": "2022-07-20 14:51:04.714154", "modified_by": "Administrator", "module": "Accounts", "name": "Period Closing Voucher", 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 5a86376199c..866a94d04b1 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -8,7 +8,6 @@ from frappe.utils import flt from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, - get_dimensions, ) from erpnext.accounts.utils import get_account_currency from erpnext.controllers.accounts_controller import AccountsController @@ -20,13 +19,28 @@ class PeriodClosingVoucher(AccountsController): self.validate_posting_date() def on_submit(self): + self.db_set("gle_processing_status", "In Progress") self.make_gl_entries() def on_cancel(self): + self.db_set("gle_processing_status", "In Progress") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") - from erpnext.accounts.general_ledger import make_reverse_gl_entries - - make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) + gle_count = frappe.db.count( + "GL Entry", + {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0}, + ) + if gle_count > 5000: + frappe.enqueue( + make_reverse_gl_entries, + voucher_type="Period Closing Voucher", + voucher_no=self.name, + queue="long", + ) + frappe.msgprint( + _("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True + ) + else: + make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) def validate_account_head(self): closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") @@ -67,90 +81,80 @@ class PeriodClosingVoucher(AccountsController): def make_gl_entries(self): gl_entries = self.get_gl_entries() if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - - make_gl_entries(gl_entries) + if len(gl_entries) > 5000: + frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long") + frappe.msgprint( + _("The GL Entries will be processed in the background, it can take a few minutes."), + alert=True, + ) + else: + process_gl_entries(gl_entries) def get_gl_entries(self): gl_entries = [] - pl_accounts = self.get_pl_balances() - for acc in pl_accounts: + # pl account + for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True): if flt(acc.bal_in_company_currency): - gl_entries.append( - self.get_gl_dict( - { - "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, - }, - item=acc, - ) - ) + gl_entries.append(self.get_gle_for_pl_account(acc)) - if gl_entries: - gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts) - gl_entries += gle_for_net_pl_bal + # closing liability account + for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False): + if flt(acc.bal_in_company_currency): + gl_entries.append(self.get_gle_for_closing_account(acc)) return gl_entries - def get_pnl_gl_entry(self, pl_accounts): - company_cost_center = frappe.db.get_value("Company", self.company, "cost_center") - gl_entries = [] + def get_gle_for_pl_account(self, acc): + gl_entry = self.get_gl_dict( + { + "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, + }, + item=acc, + ) + self.update_default_dimensions(gl_entry, acc) + return gl_entry - for acc in pl_accounts: - if flt(acc.bal_in_company_currency): - cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center - gl_entry = self.get_gl_dict( - { - "account": self.closing_account_head, - "cost_center": 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, - }, - item=acc, - ) + def get_gle_for_closing_account(self, acc): + gl_entry = self.get_gl_dict( + { + "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, + }, + item=acc, + ) + self.update_default_dimensions(gl_entry, acc) + return gl_entry - self.update_default_dimensions(gl_entry) - - gl_entries.append(gl_entry) - - return gl_entries - - def update_default_dimensions(self, gl_entry): + def update_default_dimensions(self, gl_entry, acc): if not self.accounting_dimensions: self.accounting_dimensions = get_accounting_dimensions() - _, default_dimensions = get_dimensions() for dimension in self.accounting_dimensions: - gl_entry.update({dimension: default_dimensions.get(self.company, {}).get(dimension)}) + gl_entry.update({dimension: acc.get(dimension)}) - def get_pl_balances(self): + def get_pl_balances_based_on_dimensions(self, group_by_account=False): """Get balance for dimension-wise pl accounts""" dimension_fields = ["t1.cost_center", "t1.finance_book"] @@ -159,20 +163,56 @@ class PeriodClosingVoucher(AccountsController): for dimension in self.accounting_dimensions: dimension_fields.append("t1.{0}".format(dimension)) + if group_by_account: + dimension_fields.append("t1.account") + return frappe.db.sql( """ select - t1.account, t2.account_currency, {dimension_fields}, + t2.account_currency, + {dimension_fields}, sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency, sum(t1.debit) - sum(t1.credit) as bal_in_company_currency from `tabGL Entry` t1, `tabAccount` t2 - where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss' - and t2.docstatus < 2 and t2.company = %s - and t1.posting_date between %s and %s - group by t1.account, {dimension_fields} + where + t1.is_cancelled = 0 + and t1.account = t2.name + and t2.report_type = 'Profit and Loss' + and t2.docstatus < 2 + and t2.company = %s + and t1.posting_date between %s and %s + group by {dimension_fields} """.format( dimension_fields=", ".join(dimension_fields) ), (self.company, self.get("year_start_date"), self.posting_date), as_dict=1, ) + + +def process_gl_entries(gl_entries): + from erpnext.accounts.general_ledger import make_gl_entries + + try: + make_gl_entries(gl_entries, merge_entries=False) + frappe.db.set_value( + "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed" + ) + except Exception as e: + frappe.db.rollback() + frappe.log_error(e) + frappe.db.set_value( + "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed" + ) + + +def make_reverse_gl_entries(voucher_type, voucher_no): + from erpnext.accounts.general_ledger import make_reverse_gl_entries + + try: + make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no) + frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed") + except Exception as e: + frappe.db.rollback() + frappe.log_error(e) + frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed") 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 a944a373832..93869ed6c04 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 @@ -49,7 +49,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): expected_gle = ( ("Cost of Goods Sold - TPC", 0.0, 600.0), - (surplus_account, 600.0, 400.0), + (surplus_account, 200.0, 0.0), ("Sales - TPC", 400.0, 0.0), ) @@ -59,7 +59,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): """, (pcv.name), ) - + pcv.reload() + self.assertEqual(pcv.gle_processing_status, "Completed") self.assertEqual(pcv_gle, expected_gle) def test_cost_center_wise_posting(self): @@ -94,7 +95,6 @@ class TestPeriodClosingVoucher(unittest.TestCase): ) pcv = self.make_period_closing_voucher(submit=False) - pcv.cost_center_wise_pnl = 1 pcv.save() pcv.submit() surplus_account = pcv.closing_account_head @@ -117,6 +117,16 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertEqual(pcv_gle, expected_gle) + pcv.reload() + pcv.cancel() + + self.assertFalse( + frappe.db.get_value( + "GL Entry", + {"voucher_type": "Period Closing Voucher", "voucher_no": pcv.name, "is_cancelled": 0}, + ) + ) + def test_period_closing_with_finance_book_entries(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dcf09940ed0..7525369d4f6 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -148,6 +148,7 @@ def update_net_values(entry): def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() + for entry in gl_map: # if there is already an entry in this account then just add it # to that entry @@ -229,9 +230,10 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.flags.from_repost = from_repost gle.flags.adv_adj = adv_adj gle.flags.update_outstanding = update_outstanding or "Yes" + gle.flags.notify_update = False gle.submit() - if not from_repost: + if not from_repost and gle.voucher_type != "Period Closing Voucher": validate_expense_against_budget(args)