From b4db5e9561abc6cfae1797bfb9e657ab3253ecd2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 17:40:49 +0530 Subject: [PATCH] fix: validate docs in closed accounting period on save (#36157) fix: validate docs in closed accounting period on save (#36157) (cherry picked from commit 5985e02574e387ef92a2bf0a9d2d1b49ad57cdd3) Co-authored-by: Anand Baburajan --- .../accounting_period/accounting_period.js | 6 +++ .../accounting_period/accounting_period.py | 43 +++++++++++++++++++ .../test_accounting_period.py | 10 +++-- erpnext/accounts/general_ledger.py | 5 +-- erpnext/controllers/queries.py | 9 ++++ erpnext/hooks.py | 33 ++++++++++---- 6 files changed, 89 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.js b/erpnext/accounts/doctype/accounting_period/accounting_period.js index e3d805a168..f17b6f9c69 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.js +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.js @@ -20,5 +20,11 @@ frappe.ui.form.on('Accounting Period', { } }); } + + frm.set_query("document_type", "closed_documents", () => { + return { + query: "erpnext.controllers.queries.get_doctypes_for_closing", + } + }); } }); diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 80c9715e8e..d5f37a6806 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -11,6 +11,10 @@ class OverlapError(frappe.ValidationError): pass +class ClosedAccountingPeriod(frappe.ValidationError): + pass + + class AccountingPeriod(Document): def validate(self): self.validate_overlap() @@ -65,3 +69,42 @@ class AccountingPeriod(Document): "closed_documents", {"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed}, ) + + +def validate_accounting_period_on_doc_save(doc, method=None): + if doc.doctype == "Bank Clearance": + return + elif doc.doctype == "Asset": + if doc.is_existing_asset: + return + else: + date = doc.available_for_use_date + elif doc.doctype == "Asset Repair": + date = doc.completion_date + else: + date = doc.posting_date + + ap = frappe.qb.DocType("Accounting Period") + cd = frappe.qb.DocType("Closed Document") + + accounting_period = ( + frappe.qb.from_(ap) + .from_(cd) + .select(ap.name) + .where( + (ap.name == cd.parent) + & (ap.company == doc.company) + & (cd.closed == 1) + & (cd.document_type == doc.doctype) + & (date >= ap.start_date) + & (date <= ap.end_date) + ) + ).run(as_dict=1) + + if accounting_period: + frappe.throw( + _("You cannot create a {0} within the closed Accounting Period {1}").format( + doc.doctype, frappe.bold(accounting_period[0]["name"]) + ), + ClosedAccountingPeriod, + ) diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 85025d190f..41d94797ad 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -6,9 +6,11 @@ import unittest import frappe from frappe.utils import add_months, nowdate -from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError +from erpnext.accounts.doctype.accounting_period.accounting_period import ( + ClosedAccountingPeriod, + OverlapError, +) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.accounts.general_ledger import ClosedAccountingPeriod test_dependencies = ["Item"] @@ -33,9 +35,9 @@ class TestAccountingPeriod(unittest.TestCase): ap1.save() doc = create_sales_invoice( - do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC" + do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC" ) - self.assertRaises(ClosedAccountingPeriod, doc.submit) + self.assertRaises(ClosedAccountingPeriod, doc.save) def tearDown(self): for d in frappe.get_all("Accounting Period"): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f1dad875fa..e9dc5fc0cc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -13,14 +13,11 @@ import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, ) +from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.utils import create_payment_ledger_entry -class ClosedAccountingPeriod(frappe.ValidationError): - pass - - def make_gl_entries( gl_map, cancel=False, diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 3bb11282f1..d1dcd6a109 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -822,6 +822,15 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_doctypes_for_closing(doctype, txt, searchfield, start, page_len, filters): + doctypes = frappe.get_hooks("period_closing_doctypes") + if txt: + doctypes = [d for d in doctypes if txt.lower() in d.lower()] + return [(d,) for d in set(doctypes)] + + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_tax_template(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 316d9437fb..b21f37cf62 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -285,10 +285,34 @@ standard_queries = { "Customer": "erpnext.controllers.queries.customer_query", } +period_closing_doctypes = [ + "Sales Invoice", + "Purchase Invoice", + "Journal Entry", + "Bank Clearance", + "Stock Entry", + "Dunning", + "Invoice Discounting", + "Payment Entry", + "Period Closing Voucher", + "Process Deferred Accounting", + "Asset", + "Asset Capitalization", + "Asset Repair", + "Delivery Note", + "Landed Cost Voucher", + "Purchase Receipt", + "Stock Reconciliation", + "Subcontracting Receipt", +] + doc_events = { "*": { "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", }, + tuple(period_closing_doctypes): { + "validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save", + }, "Stock Entry": { "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", @@ -464,15 +488,6 @@ advance_payment_doctypes = ["Sales Order", "Purchase Order"] invoice_doctypes = ["Sales Invoice", "Purchase Invoice"] -period_closing_doctypes = [ - "Sales Invoice", - "Purchase Invoice", - "Journal Entry", - "Bank Clearance", - "Asset", - "Stock Entry", -] - bank_reconciliation_doctypes = [ "Payment Entry", "Journal Entry",