feat: rework dunning backend

This commit is contained in:
barredterra
2021-09-16 16:42:51 +02:00
parent 7865ef82c1
commit 5f0140f02e
2 changed files with 65 additions and 51 deletions

View File

@@ -18,24 +18,34 @@ from erpnext.controllers.accounts_controller import AccountsController
class Dunning(AccountsController):
def validate(self):
self.validate_overdue_days()
self.validate_amount()
self.validate_overdue_payments()
self.validate_totals()
if not self.income_account:
self.income_account = frappe.db.get_value('Company', self.company, 'default_income_account')
def validate_overdue_days(self):
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0
def validate_overdue_payments(self):
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):
amounts = calculate_interest_and_amount(
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
if self.interest_amount != amounts.get('interest_amount'):
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
if self.dunning_amount != amounts.get('dunning_amount'):
self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount'))
if self.grand_total != amounts.get('grand_total'):
self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total'))
def validate_totals(self):
total_outstanding = sum(row.outstanding for row in self.overdue_payments)
total_interest = sum(row.interest_amount for row in self.overdue_payments)
dunning_amount = flt(total_interest) + flt(self.dunning_fee)
grand_total = flt(total_outstanding) + flt(dunning_amount)
if self.total_outstanding != total_outstanding:
self.total_outstanding = flt(total_outstanding, self.precision('total_outstanding'))
if self.total_interest != total_interest:
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):
self.make_gl_entries()
@@ -95,18 +105,6 @@ def resolve_dunning(doc, state):
for dunning in dunnings:
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()
def get_dunning_letter_text(dunning_type, doc, language=None):

View File

@@ -2026,42 +2026,58 @@ def get_mode_of_payment_info(mode_of_payment, company):
(company, mode_of_payment), as_dict=1)
@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 erpnext.accounts.doctype.dunning.dunning import (
calculate_interest_and_amount,
get_dunning_letter_text,
)
def set_missing_values(source, target):
target.sales_invoice = source_name
target.outstanding_amount = source.outstanding_amount
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]})
def postprocess_dunning(source, target):
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text
dunning_type = frappe.db.exists('Dunning Type', {'is_default': 1})
if dunning_type:
dunning_type = frappe.get_doc("Dunning Type", dunning_type)
target.dunning_type = dunning_type.name
target.rate_of_interest = dunning_type.rate_of_interest
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(
dunning_type=dunning_type.name,
doc=target.as_dict(),
language=source.language
)
if letter_text:
target.body_text = letter_text.get('body_text')
target.closing_text = letter_text.get('closing_text')
target.language = letter_text.get('language')
amounts = calculate_interest_and_amount(target.posting_date, 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("Sales Invoice", source_name, {
"Sales Invoice": {
"doctype": "Dunning",
}
}, target_doc, set_missing_values)
return doclist
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": {
"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,
postprocess=postprocess_dunning,
ignore_permissions=ignore_permissions
)
def check_if_return_invoice_linked_with_payment_entry(self):
# If a Return invoice is linked with payment entry along with other invoices,