From 6fc35dc2a3f3ec8d2b53c58612a58597bbbe8fc5 Mon Sep 17 00:00:00 2001 From: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com> Date: Wed, 29 Jul 2026 04:18:45 +0530 Subject: [PATCH] fix: clear deferred revenue/expense fields on uncheck (#57140) (cherry picked from commit 5125d64b7f39d30a5062b724cca5a65245e6a808) # Conflicts: # erpnext/accounts/services/deferred_accounting.py # erpnext/controllers/accounts_controller.py --- .../accounts/services/deferred_accounting.py | 70 +++++++++++++++++++ erpnext/controllers/accounts_controller.py | 6 ++ 2 files changed, 76 insertions(+) create mode 100644 erpnext/accounts/services/deferred_accounting.py diff --git a/erpnext/accounts/services/deferred_accounting.py b/erpnext/accounts/services/deferred_accounting.py new file mode 100644 index 00000000000..55a9ac44f47 --- /dev/null +++ b/erpnext/accounts/services/deferred_accounting.py @@ -0,0 +1,70 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +"""Deferred revenue/expense accounting validations.""" + +import frappe +from frappe import _ +from frappe.utils import getdate + +DEFERRED_ACCOUNT_FIELD = { + "Sales Invoice": "deferred_revenue_account", + "Purchase Invoice": "deferred_expense_account", +} + + +class DeferredAccountingService: + def __init__(self, doc): + self.doc = doc + + def validate_income_expense_account(self) -> None: + account_field = DEFERRED_ACCOUNT_FIELD.get(self.doc.doctype) + + for item in self.doc.get("items"): + if not self._is_deferred(item) or item.get(account_field): + continue + + default_account = frappe.get_cached_value("Company", self.doc.company, "default_" + account_field) + if not default_account: + frappe.throw( + _( + "Row #{0}: Please update deferred revenue/expense account in item row or default account in company master" + ).format(item.idx) + ) + item.set(account_field, default_account) + + def validate_start_and_end_date(self) -> None: + for item in self.doc.items: + if not self._is_deferred(item): + continue + + if not (item.service_start_date and item.service_end_date): + frappe.throw( + _("Row #{0}: Service Start and End Date is required for deferred accounting").format( + item.idx + ) + ) + elif getdate(item.service_start_date) > getdate(item.service_end_date): + frappe.throw( + _("Row #{0}: Service Start Date cannot be greater than Service End Date").format(item.idx) + ) + elif getdate(self.doc.posting_date) > getdate(item.service_end_date): + frappe.throw( + _("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(item.idx) + ) + + def _is_deferred(self, item) -> bool: + return bool(item.get("enable_deferred_revenue") or item.get("enable_deferred_expense")) + + def clear_stale_deferred_fields(self) -> None: + account_field = DEFERRED_ACCOUNT_FIELD.get(self.doc.doctype) + + for item in self.doc.get("items"): + if self._is_deferred(item): + continue + + item.service_start_date = None + item.service_end_date = None + item.service_stop_date = None + if account_field: + item.set(account_field, None) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 365e481890f..d9083401ee1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -260,9 +260,15 @@ class AccountsController(TransactionBase): else: self.validate_deferred_start_and_end_date() +<<<<<<< HEAD self.validate_inter_company_reference() # validate inter company transaction rate self.validate_internal_transaction() +======= + deferred_service = DeferredAccountingService(self) + deferred_service.clear_stale_deferred_fields() + deferred_service.validate_start_and_end_date() +>>>>>>> 5125d64b7f (fix: clear deferred revenue/expense fields on uncheck (#57140)) self.disable_pricing_rule_on_internal_transfer() self.disable_tax_included_prices_for_internal_transfer()