mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 16:34:46 +00:00
feat: rework dunning backend
This commit is contained in:
@@ -15,25 +15,34 @@ from erpnext.controllers.accounts_controller import AccountsController
|
|||||||
|
|
||||||
|
|
||||||
class Dunning(AccountsController):
|
class Dunning(AccountsController):
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_overdue_days()
|
self.validate_overdue_payments()
|
||||||
self.validate_amount()
|
self.validate_totals()
|
||||||
|
|
||||||
if not self.income_account:
|
if not self.income_account:
|
||||||
self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account")
|
self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account")
|
||||||
|
|
||||||
def validate_overdue_days(self):
|
def validate_overdue_payments(self):
|
||||||
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
|
for row in self.overdue_payments:
|
||||||
|
row.overdue_days = (getdate(self.posting_date) - getdate(row.due_date)).days or 0
|
||||||
|
interest_per_year = flt(row.outstanding) * flt(self.rate_of_interest) / 100
|
||||||
|
row.interest_amount = (interest_per_year * cint(row.overdue_days)) / 365
|
||||||
|
|
||||||
def validate_amount(self):
|
def validate_totals(self):
|
||||||
amounts = calculate_interest_and_amount(
|
total_outstanding = sum(row.outstanding for row in self.overdue_payments)
|
||||||
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days
|
total_interest = sum(row.interest_amount for row in self.overdue_payments)
|
||||||
)
|
dunning_amount = flt(total_interest) + flt(self.dunning_fee)
|
||||||
if self.interest_amount != amounts.get("interest_amount"):
|
grand_total = flt(total_outstanding) + flt(dunning_amount)
|
||||||
self.interest_amount = flt(amounts.get("interest_amount"), self.precision("interest_amount"))
|
|
||||||
if self.dunning_amount != amounts.get("dunning_amount"):
|
if self.total_outstanding != total_outstanding:
|
||||||
self.dunning_amount = flt(amounts.get("dunning_amount"), self.precision("dunning_amount"))
|
self.total_outstanding = flt(total_outstanding, self.precision('total_outstanding'))
|
||||||
if self.grand_total != amounts.get("grand_total"):
|
if self.total_interest != total_interest:
|
||||||
self.grand_total = flt(amounts.get("grand_total"), self.precision("grand_total"))
|
self.total_interest = flt(total_interest, self.precision('total_interest'))
|
||||||
|
if self.dunning_amount != dunning_amount:
|
||||||
|
self.dunning_amount = flt(dunning_amount, self.precision('dunning_amount'))
|
||||||
|
if self.grand_total != grand_total:
|
||||||
|
self.grand_total = flt(grand_total, self.precision('grand_total'))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
@@ -113,20 +122,6 @@ def resolve_dunning(doc, state):
|
|||||||
frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
|
frappe.db.set_value("Dunning", dunning.name, "status", "Resolved")
|
||||||
|
|
||||||
|
|
||||||
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
|
||||||
interest_amount = 0
|
|
||||||
grand_total = flt(outstanding_amount) + flt(dunning_fee)
|
|
||||||
if rate_of_interest:
|
|
||||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
|
||||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
|
||||||
grand_total += flt(interest_amount)
|
|
||||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
|
||||||
return {
|
|
||||||
"interest_amount": interest_amount,
|
|
||||||
"grand_total": grand_total,
|
|
||||||
"dunning_amount": dunning_amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_dunning_letter_text(dunning_type, doc, language=None):
|
def get_dunning_letter_text(dunning_type, doc, language=None):
|
||||||
|
|||||||
@@ -2510,55 +2510,58 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_dunning(source_name, target_doc=None):
|
def create_dunning(source_name, target_doc=None, ignore_permissions=False):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
from erpnext.accounts.doctype.dunning.dunning import (
|
def postprocess_dunning(source, target):
|
||||||
calculate_interest_and_amount,
|
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text
|
||||||
get_dunning_letter_text,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
dunning_type = frappe.db.exists('Dunning Type', {'is_default': 1})
|
||||||
target.sales_invoice = source_name
|
if dunning_type:
|
||||||
target.outstanding_amount = source.outstanding_amount
|
dunning_type = frappe.get_doc("Dunning Type", dunning_type)
|
||||||
overdue_days = (getdate(target.posting_date) - getdate(source.due_date)).days
|
|
||||||
target.overdue_days = overdue_days
|
|
||||||
if frappe.db.exists(
|
|
||||||
"Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
|
|
||||||
):
|
|
||||||
dunning_type = frappe.get_doc(
|
|
||||||
"Dunning Type", {"start_day": ["<", overdue_days], "end_day": [">=", overdue_days]}
|
|
||||||
)
|
|
||||||
target.dunning_type = dunning_type.name
|
target.dunning_type = dunning_type.name
|
||||||
target.rate_of_interest = dunning_type.rate_of_interest
|
target.rate_of_interest = dunning_type.rate_of_interest
|
||||||
target.dunning_fee = dunning_type.dunning_fee
|
target.dunning_fee = dunning_type.dunning_fee
|
||||||
letter_text = get_dunning_letter_text(dunning_type=dunning_type.name, doc=target.as_dict())
|
letter_text = get_dunning_letter_text(
|
||||||
if letter_text:
|
dunning_type=dunning_type.name,
|
||||||
target.body_text = letter_text.get("body_text")
|
doc=target.as_dict(),
|
||||||
target.closing_text = letter_text.get("closing_text")
|
language=source.language
|
||||||
target.language = letter_text.get("language")
|
|
||||||
amounts = calculate_interest_and_amount(
|
|
||||||
target.outstanding_amount,
|
|
||||||
target.rate_of_interest,
|
|
||||||
target.dunning_fee,
|
|
||||||
target.overdue_days,
|
|
||||||
)
|
)
|
||||||
target.interest_amount = amounts.get("interest_amount")
|
|
||||||
target.dunning_amount = amounts.get("dunning_amount")
|
|
||||||
target.grand_total = amounts.get("grand_total")
|
|
||||||
|
|
||||||
doclist = get_mapped_doc(
|
if letter_text:
|
||||||
"Sales Invoice",
|
target.body_text = letter_text.get('body_text')
|
||||||
source_name,
|
target.closing_text = letter_text.get('closing_text')
|
||||||
{
|
target.language = letter_text.get('language')
|
||||||
|
|
||||||
|
def postprocess_overdue_payment(source, target, source_parent):
|
||||||
|
target.overdue_days = (getdate(nowdate()) - getdate(source.due_date)).days
|
||||||
|
|
||||||
|
return get_mapped_doc(
|
||||||
|
from_doctype="Sales Invoice",
|
||||||
|
from_docname=source_name,
|
||||||
|
table_maps={
|
||||||
"Sales Invoice": {
|
"Sales Invoice": {
|
||||||
"doctype": "Dunning",
|
"doctype": "Dunning",
|
||||||
|
"field_map": {
|
||||||
|
"customer_address": "customer_address",
|
||||||
|
"parent": "sales_invoice"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Payment Schedule": {
|
||||||
|
"doctype": "Overdue Payment",
|
||||||
|
"field_map": {
|
||||||
|
"name": "payment_schedule",
|
||||||
|
"parent": "sales_invoice"
|
||||||
|
},
|
||||||
|
"condition": lambda doc: doc.outstanding > 0,
|
||||||
|
"postprocess": postprocess_overdue_payment
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
target_doc,
|
target_doc=target_doc,
|
||||||
set_missing_values,
|
postprocess=postprocess_dunning,
|
||||||
|
ignore_permissions=ignore_permissions
|
||||||
)
|
)
|
||||||
return doclist
|
|
||||||
|
|
||||||
|
|
||||||
def check_if_return_invoice_linked_with_payment_entry(self):
|
def check_if_return_invoice_linked_with_payment_entry(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user