mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-20 03:47:11 +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")
|
_("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:
|
def linked_order_has_payment_terms_template(po_or_so, doctype) -> str | None:
|
||||||
return frappe.get_value(doctype, po_or_so, "payment_terms_template")
|
return frappe.get_value(doctype, po_or_so, "payment_terms_template")
|
||||||
|
|||||||
@@ -234,7 +234,9 @@ class AccountsController(TransactionBase):
|
|||||||
if self.is_return:
|
if self.is_return:
|
||||||
self.validate_qty()
|
self.validate_qty()
|
||||||
else:
|
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
|
from erpnext.accounts.services.internal_transfer import InternalTransferService
|
||||||
|
|
||||||
@@ -262,7 +264,9 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
validate_return(self)
|
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
|
from erpnext.accounts.services.party_validation import PartyValidator
|
||||||
|
|
||||||
@@ -286,7 +290,9 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
self.set_advance_gain_or_loss()
|
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()
|
InternalTransferService(self).set_account()
|
||||||
|
|
||||||
if self.doctype == "Purchase Invoice":
|
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):
|
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):
|
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"))
|
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):
|
def before_print(self, settings=None):
|
||||||
if self.doctype in [
|
if self.doctype in [
|
||||||
"Purchase Order",
|
"Purchase Order",
|
||||||
|
|||||||
Reference in New Issue
Block a user