mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 07:01:56 +00:00
refactor(accounts): extract deferred-accounting and document-schedule out of AccountsController
Continues the AccountsController service decomposition (Phase 5). - Add accounts/services/deferred_accounting.py with DeferredAccountingService owning the deferred revenue/expense validations (income/expense account defaulting and service start/end date checks). - Move the document-schedule orchestration (validate_all_documents_schedule and the invoice/non-invoice variants) into PaymentScheduleService, where they already delegated, removing the controller-to-service round trip. - Update the three validate() call sites; keep validate_auto_repeat_subscription_dates on the controller (still called by buying/selling controllers). No behavior change. accounts_controller.py 1818 -> 1745 lines.
This commit is contained in:
57
erpnext/accounts/services/deferred_accounting.py
Normal file
57
erpnext/accounts/services/deferred_accounting.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# 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"))
|
||||
@@ -293,6 +293,39 @@ class PaymentScheduleService:
|
||||
_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")
|
||||
)
|
||||
|
||||
def validate_all_documents_schedule(self) -> None:
|
||||
if self.doc.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
self.validate_invoice_documents_schedule()
|
||||
elif self.doc.doctype in ("Quotation", "Purchase Order", "Sales Order"):
|
||||
self.validate_non_invoice_documents_schedule()
|
||||
|
||||
def validate_invoice_documents_schedule(self) -> None:
|
||||
doc = self.doc
|
||||
if (
|
||||
doc.is_return
|
||||
or (doc.doctype == "Purchase Invoice" and doc.is_paid)
|
||||
or (doc.doctype == "Sales Invoice" and doc.is_pos)
|
||||
or doc.get("is_opening") == "Yes"
|
||||
):
|
||||
doc.payment_terms_template = ""
|
||||
doc.payment_schedule = []
|
||||
|
||||
if doc.is_return:
|
||||
return
|
||||
|
||||
self.validate_payment_schedule_dates()
|
||||
self.set_due_date()
|
||||
self.set_payment_schedule()
|
||||
if not doc.get("ignore_default_payment_terms_template"):
|
||||
self.validate_payment_schedule_amount()
|
||||
doc.validate_due_date()
|
||||
doc.validate_advance_entries()
|
||||
|
||||
def validate_non_invoice_documents_schedule(self) -> None:
|
||||
self.set_payment_schedule()
|
||||
self.validate_payment_schedule_dates()
|
||||
self.validate_payment_schedule_amount()
|
||||
|
||||
|
||||
def linked_order_has_payment_terms_template(po_or_so, doctype) -> str | None:
|
||||
return frappe.get_value(doctype, po_or_so, "payment_terms_template")
|
||||
|
||||
@@ -234,7 +234,9 @@ class AccountsController(TransactionBase):
|
||||
if self.is_return:
|
||||
self.validate_qty()
|
||||
else:
|
||||
self.validate_deferred_start_and_end_date()
|
||||
from erpnext.accounts.services.deferred_accounting import DeferredAccountingService
|
||||
|
||||
DeferredAccountingService(self).validate_start_and_end_date()
|
||||
|
||||
from erpnext.accounts.services.internal_transfer import InternalTransferService
|
||||
|
||||
@@ -262,7 +264,9 @@ class AccountsController(TransactionBase):
|
||||
|
||||
validate_return(self)
|
||||
|
||||
self.validate_all_documents_schedule()
|
||||
from erpnext.accounts.services.payment_schedule import PaymentScheduleService
|
||||
|
||||
PaymentScheduleService(self).validate_all_documents_schedule()
|
||||
|
||||
from erpnext.accounts.services.party_validation import PartyValidator
|
||||
|
||||
@@ -286,7 +290,9 @@ class AccountsController(TransactionBase):
|
||||
|
||||
self.set_advance_gain_or_loss()
|
||||
|
||||
self.validate_deferred_income_expense_account()
|
||||
from erpnext.accounts.services.deferred_accounting import DeferredAccountingService
|
||||
|
||||
DeferredAccountingService(self).validate_income_expense_account()
|
||||
InternalTransferService(self).set_account()
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
@@ -504,89 +510,10 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
)
|
||||
|
||||
def validate_deferred_income_expense_account(self):
|
||||
field_map = {
|
||||
"Sales Invoice": "deferred_revenue_account",
|
||||
"Purchase Invoice": "deferred_expense_account",
|
||||
}
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
|
||||
if not item.get(field_map.get(self.doctype)):
|
||||
default_deferred_account = frappe.get_cached_value(
|
||||
"Company", self.company, "default_" + field_map.get(self.doctype)
|
||||
)
|
||||
if not default_deferred_account:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Please update deferred revenue/expense account in item row or default account in company master"
|
||||
).format(item.idx)
|
||||
)
|
||||
else:
|
||||
item.set(field_map.get(self.doctype), default_deferred_account)
|
||||
|
||||
def validate_auto_repeat_subscription_dates(self):
|
||||
if self.get("from_date") and self.get("to_date") and getdate(self.from_date) > getdate(self.to_date):
|
||||
frappe.throw(_("To Date cannot be before From Date"), title=_("Invalid Auto Repeat Date"))
|
||||
|
||||
def validate_deferred_start_and_end_date(self):
|
||||
for d in self.items:
|
||||
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||
if not (d.service_start_date and d.service_end_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Service Start and End Date is required for deferred accounting").format(
|
||||
d.idx
|
||||
)
|
||||
)
|
||||
elif getdate(d.service_start_date) > getdate(d.service_end_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Service Start Date cannot be greater than Service End Date").format(
|
||||
d.idx
|
||||
)
|
||||
)
|
||||
elif getdate(self.posting_date) > getdate(d.service_end_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Service End Date cannot be before Invoice Posting Date").format(d.idx)
|
||||
)
|
||||
|
||||
def validate_invoice_documents_schedule(self):
|
||||
if (
|
||||
self.is_return
|
||||
or (self.doctype == "Purchase Invoice" and self.is_paid)
|
||||
or (self.doctype == "Sales Invoice" and self.is_pos)
|
||||
or self.get("is_opening") == "Yes"
|
||||
):
|
||||
self.payment_terms_template = ""
|
||||
self.payment_schedule = []
|
||||
|
||||
if self.is_return:
|
||||
return
|
||||
|
||||
from erpnext.accounts.services.payment_schedule import PaymentScheduleService
|
||||
|
||||
ps = PaymentScheduleService(self)
|
||||
ps.validate_payment_schedule_dates()
|
||||
ps.set_due_date()
|
||||
ps.set_payment_schedule()
|
||||
if not self.get("ignore_default_payment_terms_template"):
|
||||
ps.validate_payment_schedule_amount()
|
||||
self.validate_due_date()
|
||||
self.validate_advance_entries()
|
||||
|
||||
def validate_non_invoice_documents_schedule(self):
|
||||
from erpnext.accounts.services.payment_schedule import PaymentScheduleService
|
||||
|
||||
ps = PaymentScheduleService(self)
|
||||
ps.set_payment_schedule()
|
||||
ps.validate_payment_schedule_dates()
|
||||
ps.validate_payment_schedule_amount()
|
||||
|
||||
def validate_all_documents_schedule(self):
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
self.validate_invoice_documents_schedule()
|
||||
elif self.doctype in ("Quotation", "Purchase Order", "Sales Order"):
|
||||
self.validate_non_invoice_documents_schedule()
|
||||
|
||||
def before_print(self, settings=None):
|
||||
if self.doctype in [
|
||||
"Purchase Order",
|
||||
|
||||
Reference in New Issue
Block a user