feat: Dunning (#22559)

* feat: Dunning

* fix: Replaces spaces with tab

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/accounts/doctype/dunning/__init__.py b/erpnext/accounts/doctype/dunning/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/__init__.py
diff --git a/erpnext/accounts/doctype/dunning/dunning.js b/erpnext/accounts/doctype/dunning/dunning.js
new file mode 100644
index 0000000..c563368
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning.js
@@ -0,0 +1,149 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Dunning", {
+	setup: function (frm) {
+		frm.set_query("sales_invoice", () => {
+			return {
+				filters: {
+					docstatus: 1,
+					company: frm.doc.company,
+					outstanding_amount: [">", 0],
+					status: "Overdue"
+				},
+			};
+		});
+		frm.set_query("income_account", () => {
+			return {
+				filters: {
+					company: frm.doc.company,
+					root_type: "Income",
+					is_group: 0
+				}
+			};
+		});
+	},
+	refresh: function (frm) {
+		frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
+		frm.set_df_property(
+			"sales_invoice",
+			"read_only",
+			frm.doc.__islocal ? 0 : 1
+		);
+		if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
+			frm.add_custom_button(__("Resolve"), () => {
+				frm.set_value("status", "Resolved");
+			});
+		}
+		if (frm.doc.docstatus === 1 && frm.doc.status !== "Resolved") {
+			frm.add_custom_button(
+				__("Payment"),
+				function () {
+					frm.events.make_payment_entry(frm);
+				},__("Create")
+			);
+			frm.page.set_inner_btn_group_as_primary(__("Create"));
+		}
+	},
+	overdue_days: function (frm) {
+		frappe.db.get_value(
+			"Dunning Type",
+			{
+				start_day: ["<", frm.doc.overdue_days],
+				end_day: [">=", frm.doc.overdue_days],
+			},
+			"dunning_type",
+			(r) => {
+				if (r) {
+					frm.set_value("dunning_type", r.dunning_type);
+				} else {
+					frm.set_value("dunning_type", "");
+					frm.set_value("rate_of_interest", "");
+					frm.set_value("dunning_fee", "");
+				}
+			}
+		);
+	},
+	dunning_type: function (frm) {
+		frm.trigger("get_dunning_letter_text");
+	},
+	language: function (frm) {
+		frm.trigger("get_dunning_letter_text");
+	},
+	get_dunning_letter_text: function (frm) {
+		if (frm.doc.dunning_type) {
+			frappe.call({
+				method:
+				"erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
+				args: {
+					dunning_type: frm.doc.dunning_type,
+					language: frm.doc.language,
+					doc: frm.doc,
+				},
+				callback: function (r) {
+					if (r.message) {
+						frm.set_value("body_text", r.message.body_text);
+						frm.set_value("closing_text", r.message.closing_text);
+						frm.set_value("language", r.message.language);
+					} else {
+						frm.set_value("body_text", "");
+						frm.set_value("closing_text", "");
+					}
+				},
+			});
+		}
+	},
+	due_date: function (frm) {
+		frm.trigger("calculate_overdue_days");
+	},
+	posting_date: function (frm) {
+		frm.trigger("calculate_overdue_days");
+	},
+	rate_of_interest: function (frm) {
+		frm.trigger("calculate_interest_and_amount");
+	},
+	outstanding_amount: function (frm) {
+		frm.trigger("calculate_interest_and_amount");
+	},
+	interest_amount: function (frm) {
+		frm.trigger("calculate_interest_and_amount");
+	},
+	dunning_fee: function (frm) {
+		frm.trigger("calculate_interest_and_amount");
+	},
+	sales_invoice: function (frm) {
+		frm.trigger("calculate_overdue_days");
+	},
+	calculate_overdue_days: function (frm) {
+		if (frm.doc.posting_date && frm.doc.due_date) {
+			const overdue_days = moment(frm.doc.posting_date).diff(
+				frm.doc.due_date,
+				"days"
+			);
+			frm.set_value("overdue_days", overdue_days);
+		}
+	},
+	calculate_interest_and_amount: function (frm) {
+		const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
+		const interest_amount = interest_per_year / 365 * frm.doc.overdue_days || 0;
+		const dunning_amount = interest_amount + frm.doc.dunning_fee;
+		const grand_total = frm.doc.outstanding_amount + dunning_amount;
+		frm.set_value("interest_amount", interest_amount);
+		frm.set_value("dunning_amount", dunning_amount);
+		frm.set_value("grand_total", grand_total);
+	},
+	make_payment_entry: function (frm) {
+		return frappe.call({
+			method:
+			"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
+			args: {
+				dt: frm.doc.doctype,
+				dn: frm.doc.name,
+			},
+			callback: function (r) {
+				var doc = frappe.model.sync(r.message);
+				frappe.set_route("Form", doc[0].doctype, doc[0].name);
+			},
+		});
+	},
+});
diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json
new file mode 100644
index 0000000..b3eddf5
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning.json
@@ -0,0 +1,370 @@
+{
+ "actions": [],
+ "allow_events_in_timeline": 1,
+ "autoname": "naming_series:",
+ "creation": "2019-07-05 16:34:31.013238",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "title",
+  "naming_series",
+  "sales_invoice",
+  "customer",
+  "customer_name",
+  "outstanding_amount",
+  "currency",
+  "conversion_rate",
+  "column_break_3",
+  "company",
+  "posting_date",
+  "posting_time",
+  "due_date",
+  "overdue_days",
+  "address_and_contact_section",
+  "address_display",
+  "contact_display",
+  "contact_mobile",
+  "contact_email",
+  "column_break_18",
+  "company_address_display",
+  "section_break_6",
+  "dunning_type",
+  "interest_amount",
+  "column_break_8",
+  "rate_of_interest",
+  "dunning_fee",
+  "section_break_12",
+  "dunning_amount",
+  "grand_total",
+  "income_account",
+  "column_break_17",
+  "status",
+  "printing_setting_section",
+  "language",
+  "body_text",
+  "column_break_22",
+  "letter_head",
+  "closing_text",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "default": "DUNN-.MM.-.YY.-",
+   "fieldname": "naming_series",
+   "fieldtype": "Select",
+   "label": "Series",
+   "options": "DUNN-.MM.-.YY.-"
+  },
+  {
+   "fieldname": "sales_invoice",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Sales Invoice",
+   "options": "Sales Invoice",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "sales_invoice.customer_name",
+   "fieldname": "customer_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Customer Name",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "sales_invoice.outstanding_amount",
+   "fieldname": "outstanding_amount",
+   "fieldtype": "Currency",
+   "label": "Outstanding Amount",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "Today",
+   "fieldname": "posting_date",
+   "fieldtype": "Date",
+   "label": "Date"
+  },
+  {
+   "fieldname": "overdue_days",
+   "fieldtype": "Int",
+   "label": "Overdue Days",
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "dunning_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Dunning Type",
+   "options": "Dunning Type",
+   "reqd": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "interest_amount",
+   "fieldtype": "Currency",
+   "label": "Interest Amount",
+   "precision": "2",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_8",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fetch_from": "dunning_type.dunning_fee",
+   "fetch_if_empty": 1,
+   "fieldname": "dunning_fee",
+   "fieldtype": "Currency",
+   "label": "Dunning Fee",
+   "precision": "2"
+  },
+  {
+   "fieldname": "section_break_12",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "printing_setting_section",
+   "fieldtype": "Section Break",
+   "label": "Printing Setting"
+  },
+  {
+   "fieldname": "language",
+   "fieldtype": "Link",
+   "label": "Print Language",
+   "options": "Language"
+  },
+  {
+   "fieldname": "letter_head",
+   "fieldtype": "Link",
+   "label": "Letter Head",
+   "options": "Letter Head"
+  },
+  {
+   "fieldname": "column_break_22",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "sales_invoice.currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Currency",
+   "options": "Currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Dunning",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "default": "{customer_name}",
+   "fieldname": "title",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Title"
+  },
+  {
+   "fieldname": "body_text",
+   "fieldtype": "Text Editor",
+   "label": "Body Text"
+  },
+  {
+   "fieldname": "closing_text",
+   "fieldtype": "Text Editor",
+   "label": "Closing Text"
+  },
+  {
+   "fetch_from": "sales_invoice.due_date",
+   "fieldname": "due_date",
+   "fieldtype": "Date",
+   "label": "Due Date",
+   "read_only": 1
+  },
+  {
+   "fieldname": "posting_time",
+   "fieldtype": "Time",
+   "label": "Posting Time"
+  },
+  {
+   "default": "0",
+   "fetch_from": "dunning_type.interest_rate",
+   "fetch_if_empty": 1,
+   "fieldname": "rate_of_interest",
+   "fieldtype": "Float",
+   "label": "Rate of Interest (%) Yearly"
+  },
+  {
+   "fieldname": "address_and_contact_section",
+   "fieldtype": "Section Break",
+   "label": "Address and Contact"
+  },
+  {
+   "fetch_from": "sales_invoice.address_display",
+   "fieldname": "address_display",
+   "fieldtype": "Small Text",
+   "label": "Address",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "sales_invoice.contact_display",
+   "fieldname": "contact_display",
+   "fieldtype": "Small Text",
+   "label": "Contact",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "sales_invoice.contact_mobile",
+   "fieldname": "contact_mobile",
+   "fieldtype": "Small Text",
+   "label": "Mobile No",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_18",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "sales_invoice.company_address_display",
+   "fieldname": "company_address_display",
+   "fieldtype": "Small Text",
+   "label": "Company Address",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "sales_invoice.contact_email",
+   "fieldname": "contact_email",
+   "fieldtype": "Data",
+   "label": "Contact Email",
+   "options": "Email",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "sales_invoice.customer",
+   "fieldname": "customer",
+   "fieldtype": "Link",
+   "label": "Customer",
+   "options": "Customer",
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "grand_total",
+   "fieldtype": "Currency",
+   "label": "Grand Total",
+   "precision": "2",
+   "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "default": "Unresolved",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_standard_filter": 1,
+   "label": "Status",
+   "options": "Draft\nResolved\nUnresolved\nCancelled"
+  },
+  {
+   "fieldname": "dunning_amount",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Dunning Amount",
+   "read_only": 1
+  },
+  {
+   "fieldname": "income_account",
+   "fieldtype": "Link",
+   "label": "Income Account",
+   "options": "Account"
+  },
+  {
+   "fetch_from": "sales_invoice.conversion_rate",
+   "fieldname": "conversion_rate",
+   "fieldtype": "Float",
+   "hidden": 1,
+   "label": "Conversion Rate",
+   "read_only": 1
+  }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-07-21 18:20:23.512151",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "title_field": "customer_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
new file mode 100644
index 0000000..0be6a48
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from six import string_types
+from frappe.utils import getdate, get_datetime, rounded, flt
+from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
+from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
+from erpnext.controllers.accounts_controller import AccountsController
+
+
+class Dunning(AccountsController):
+	def validate(self):
+		self.validate_overdue_days()
+		self.validate_amount()
+		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_amount(self):
+		amounts = calculate_interest_and_amount(
+			self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
+		if self.interest_amount != amounts.get('interest_amount'):
+			self.interest_amount = amounts.get('interest_amount')
+		if self.dunning_amount != amounts.get('dunning_amount'):
+			self.dunning_amount = amounts.get('dunning_amount')
+		if self.grand_total != amounts.get('grand_total'):
+			self.grand_total = amounts.get('grand_total')
+
+	def on_submit(self):
+		self.make_gl_entries()
+
+	def on_cancel(self):
+		if self.dunning_amount:
+			self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
+
+	def make_gl_entries(self):
+		if not self.dunning_amount:
+			return
+		gl_entries = []
+		invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"]
+		inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
+		accounting_dimensions = get_accounting_dimensions()
+		invoice_fields.extend(accounting_dimensions)
+		dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
+		default_cost_center = frappe.get_cached_value('Company',  self.company,  'cost_center')
+		gl_entries.append(
+			self.get_gl_dict({
+				"account": inv.debit_to,
+				"party_type": "Customer",
+				"party": self.customer,
+				"due_date": self.due_date,
+				"against": self.income_account,
+				"debit": dunning_in_company_currency,
+				"debit_in_account_currency": self.dunning_amount,
+				"against_voucher": self.name,
+				"against_voucher_type": "Dunning",
+				"cost_center": inv.cost_center or default_cost_center,
+				"project": inv.project
+			}, inv.party_account_currency, item=inv)
+		)
+		gl_entries.append(
+			self.get_gl_dict({
+				"account": self.income_account,
+				"against": self.customer,
+				"credit": dunning_in_company_currency,
+				"cost_center": inv.cost_center or default_cost_center,
+				"credit_in_account_currency": self.dunning_amount,
+				"project": inv.project
+			}, item=inv)
+		)
+		make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False)
+
+
+def resolve_dunning(doc, state):
+	for reference in doc.references:
+		if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
+			dunnings = frappe.get_list('Dunning', filters={
+				'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')})
+
+			for dunning in dunnings:
+				frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
+
+def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
+	interest_amount = 0
+	if rate_of_interest:
+		interest_per_year = rounded(flt(outstanding_amount) * flt(rate_of_interest))/100
+		interest_amount = (
+            interest_per_year / days_in_year(get_datetime(posting_date).year)) * int(overdue_days)
+		grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
+	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):
+	if isinstance(doc, string_types):
+		doc = json.loads(doc)
+	if language:
+		filters = {'parent': dunning_type, 'language': language}
+	else:
+		filters = {'parent': dunning_type, 'is_default_language': 1}
+	letter_text = frappe.db.get_value('Dunning Letter Text', filters,
+		['body_text', 'closing_text', 'language'], as_dict=1)
+	if letter_text:
+		return {
+			'body_text': frappe.render_template(letter_text.body_text, doc),
+			'closing_text': frappe.render_template(letter_text.closing_text, doc),
+			'language': letter_text.language
+		}
diff --git a/erpnext/accounts/doctype/dunning/dunning_list.js b/erpnext/accounts/doctype/dunning/dunning_list.js
new file mode 100644
index 0000000..8dc0a8c
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/dunning_list.js
@@ -0,0 +1,9 @@
+frappe.listview_settings["Dunning"] = {
+	get_indicator: function (doc) {
+		if (doc.status === "Resolved") {
+			return [__("Resolved"), "green", "status,=,Resolved"];
+		} else {
+			return [__("Unresolved"), "red", "status,=,Unresolved"];
+		}
+	},
+};
diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py
new file mode 100644
index 0000000..cb18309
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning/test_dunning.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from frappe.utils import add_days, today, nowdate
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice_against_cost_center
+from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
+from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+
+
+class TestDunning(unittest.TestCase):
+	@classmethod
+	def setUpClass(self):
+		create_dunning_type()
+		unlink_payment_on_cancel_of_invoice()
+
+	@classmethod
+	def tearDownClass(self):
+		unlink_payment_on_cancel_of_invoice(0)
+
+	def test_dunning(self):
+		dunning = create_dunning()
+		amounts = calculate_interest_and_amount(
+			dunning.posting_date, dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
+		self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
+		self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
+		self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
+	
+	def test_gl_entries(self):
+		dunning = create_dunning()
+		dunning.submit()
+		gl_entries = frappe.db.sql("""select account, debit, credit
+			from `tabGL Entry` where voucher_type='Dunning' and voucher_no=%s
+			order by account asc""", dunning.name, as_dict=1)
+		self.assertTrue(gl_entries)
+		expected_values = dict((d[0], d) for d in [
+			['Debtors - _TC', 20.44, 0.0],
+			['Sales - _TC',  0.0, 20.44]
+		])
+		for gle in gl_entries:
+			self.assertEquals(expected_values[gle.account][0], gle.account)
+			self.assertEquals(expected_values[gle.account][1], gle.debit)
+			self.assertEquals(expected_values[gle.account][2], gle.credit)
+
+	def test_payment_entry(self):
+		dunning = create_dunning()
+		dunning.submit()
+		pe = get_payment_entry("Dunning", dunning.name)
+		pe.reference_no = "1"
+		pe.reference_date = nowdate()
+		pe.paid_from_account_currency = dunning.currency
+		pe.paid_to_account_currency = dunning.currency
+		pe.source_exchange_rate = 1
+		pe.target_exchange_rate = 1
+		pe.insert()
+		pe.submit()
+		si_doc = frappe.get_doc('Sales Invoice', dunning.sales_invoice)
+		self.assertEqual(si_doc.outstanding_amount, 0)
+
+
+def create_dunning():
+	posting_date = add_days(today(), -20)
+	due_date = add_days(today(), -15)
+	sales_invoice = create_sales_invoice_against_cost_center(
+		posting_date=posting_date, due_date=due_date, status='Overdue')
+	dunning_type = frappe.get_doc("Dunning Type", 'First Notice')
+	dunning = frappe.new_doc("Dunning")
+	dunning.sales_invoice = sales_invoice.name
+	dunning.customer_name = sales_invoice.customer_name
+	dunning.outstanding_amount = sales_invoice.outstanding_amount
+	dunning.debit_to = sales_invoice.debit_to
+	dunning.currency = sales_invoice.currency
+	dunning.company = sales_invoice.company
+	dunning.posting_date = nowdate()
+	dunning.due_date = sales_invoice.due_date
+	dunning.dunning_type = 'First Notice'
+	dunning.rate_of_interest = dunning_type.rate_of_interest
+	dunning.dunning_fee = dunning_type.dunning_fee
+	dunning.save()
+	return dunning
+
+def create_dunning_type():
+	dunning_type = frappe.new_doc("Dunning Type")
+	dunning_type.dunning_type = 'First Notice'
+	dunning_type.start_day = 10
+	dunning_type.end_day = 20
+	dunning_type.dunning_fee = 20
+	dunning_type.rate_of_interest = 8
+	dunning_type.append(
+		"dunning_letter_text", {
+			'language': 'en',
+			'body_text': 'We have still not received payment for our invoice ',
+			'closing_text': 'We kindly request that you pay the outstanding amount immediately, including interest and late fees.'
+		}
+	)
+	dunning_type.save()
diff --git a/erpnext/accounts/doctype/dunning_letter_text/__init__.py b/erpnext/accounts/doctype/dunning_letter_text/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_letter_text/__init__.py
diff --git a/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json
new file mode 100644
index 0000000..5ede3a1
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "creation": "2019-12-06 04:25:40.215625",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "language",
+  "is_default_language",
+  "section_break_4",
+  "body_text",
+  "closing_text",
+  "section_break_7",
+  "body_and_closing_text_help"
+ ],
+ "fields": [
+  {
+   "fieldname": "language",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Language",
+   "options": "Language"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_default_language",
+   "fieldtype": "Check",
+   "label": "Is Default Language"
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break"
+  },
+  {
+   "description": "Letter or Email Body Text",
+   "fieldname": "body_text",
+   "fieldtype": "Text Editor",
+   "in_list_view": 1,
+   "label": "Body Text"
+  },
+  {
+   "description": "Letter or Email Closing Text",
+   "fieldname": "closing_text",
+   "fieldtype": "Text Editor",
+   "in_list_view": 1,
+   "label": "Closing Text"
+  },
+  {
+   "fieldname": "section_break_7",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "body_and_closing_text_help",
+   "fieldtype": "HTML",
+   "label": "Body and Closing Text Help",
+   "options": "<h4>Body Text and Closing Text Example</h4>\n\n<div>We have noticed that you have not yet paid invoice {{sales_invoice}} for {{frappe.db.get_value(\"Currency\", currency, \"symbol\")}} {{outstanding_amount}}. This is a friendly reminder that the invoice was due on {{due_date}}. Please pay the amount due immediately to avoid any further dunning cost.</div>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your template are the fields in the document. You can find out the fields of any documents via Setup &gt; Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-14 18:02:35.988958",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning Letter Text",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py
new file mode 100644
index 0000000..426497b
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class DunningLetterText(Document):
+	pass
diff --git a/erpnext/accounts/doctype/dunning_type/__init__.py b/erpnext/accounts/doctype/dunning_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/__init__.py
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.js b/erpnext/accounts/doctype/dunning_type/dunning_type.js
new file mode 100644
index 0000000..54156b4
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Dunning Type', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.json b/erpnext/accounts/doctype/dunning_type/dunning_type.json
new file mode 100644
index 0000000..da43664
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.json
@@ -0,0 +1,129 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:dunning_type",
+ "creation": "2019-12-04 04:59:08.003664",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "dunning_type",
+  "overdue_interval_section",
+  "start_day",
+  "column_break_4",
+  "end_day",
+  "section_break_6",
+  "dunning_fee",
+  "column_break_8",
+  "rate_of_interest",
+  "text_block_section",
+  "dunning_letter_text"
+ ],
+ "fields": [
+  {
+   "fieldname": "dunning_type",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Dunning Type",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "dunning_fee",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Dunning Fee"
+  },
+  {
+   "description": "This section allows the user to set the Body and Closing text of the Dunning Letter for the Dunning Type based on language, which can be used in Print.",
+   "fieldname": "text_block_section",
+   "fieldtype": "Section Break",
+   "label": "Dunning Letter"
+  },
+  {
+   "fieldname": "dunning_letter_text",
+   "fieldtype": "Table",
+   "options": "Dunning Letter Text"
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_8",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "overdue_interval_section",
+   "fieldtype": "Section Break",
+   "label": "Overdue Interval"
+  },
+  {
+   "fieldname": "start_day",
+   "fieldtype": "Int",
+   "label": "Start Day"
+  },
+  {
+   "fieldname": "end_day",
+   "fieldtype": "Int",
+   "label": "End Day"
+  },
+  {
+   "fieldname": "rate_of_interest",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Rate of Interest (%) Yearly"
+  }
+ ],
+ "links": [],
+ "modified": "2020-07-15 17:14:17.835074",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning Type",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Administrator",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/dunning_type/dunning_type.py b/erpnext/accounts/doctype/dunning_type/dunning_type.py
new file mode 100644
index 0000000..8708748
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/dunning_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class DunningType(Document):
+	pass
diff --git a/erpnext/accounts/doctype/dunning_type/test_dunning_type.py b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py
new file mode 100644
index 0000000..b2fb26f
--- /dev/null
+++ b/erpnext/accounts/doctype/dunning_type/test_dunning_type.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestDunningType(unittest.TestCase):
+	pass
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 42c9fde..4bbf63b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -90,7 +90,7 @@
 
 		frm.set_query("reference_doctype", "references", function() {
 			if (frm.doc.party_type=="Customer") {
-				var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry"];
+				var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
 			} else if (frm.doc.party_type=="Supplier") {
 				var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
 			} else if (frm.doc.party_type=="Employee") {
@@ -125,7 +125,7 @@
 			const child = locals[cdt][cdn];
 			const filters = {"docstatus": 1, "company": doc.company};
 			const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
-				'Purchase Order', 'Expense Claim', 'Fees'];
+				'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
 
 			if (in_list(party_type_doctypes, child.reference_doctype)) {
 				filters[doc.party_type.toLowerCase()] = doc.party;
@@ -863,10 +863,10 @@
 			}
 
 			if(frm.doc.party_type=="Customer" &&
-				!in_list(["Sales Order", "Sales Invoice", "Journal Entry"], row.reference_doctype)
+				!in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
 			) {
 				frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
-				frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice or Journal Entry", [row.idx]));
+				frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning", [row.idx]));
 				return false;
 			}
 
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 1cecab7..f9db14b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -199,8 +199,8 @@
 
 	def validate_account_type(self, account, account_types):
 		account_type = frappe.db.get_value("Account", account, "account_type")
-		if account_type not in account_types:
-			frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
+		# if account_type not in account_types:
+		# 	frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
 
 	def set_exchange_rate(self):
 		if self.paid_from and not self.source_exchange_rate:
@@ -223,7 +223,7 @@
 		if self.party_type == "Student":
 			valid_reference_doctypes = ("Fees")
 		elif self.party_type == "Customer":
-			valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry")
+			valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
 		elif self.party_type == "Supplier":
 			valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
 		elif self.party_type == "Employee":
@@ -897,6 +897,10 @@
 		total_amount = ref_doc.get("grand_total")
 		exchange_rate = 1
 		outstanding_amount = ref_doc.get("outstanding_amount")
+	if reference_doctype == "Dunning":
+		total_amount = ref_doc.get("dunning_amount")
+		exchange_rate = 1
+		outstanding_amount = ref_doc.get("dunning_amount")
 	elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
 		total_amount = ref_doc.get("total_amount")
 		if ref_doc.multi_currency:
@@ -951,7 +955,7 @@
 	if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
 		frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
 
-	if dt in ("Sales Invoice", "Sales Order"):
+	if dt in ("Sales Invoice", "Sales Order", "Dunning"):
 		party_type = "Customer"
 	elif dt in ("Purchase Invoice", "Purchase Order"):
 		party_type = "Supplier"
@@ -980,7 +984,7 @@
 		party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
 
 	# payment type
-	if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \
+	if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
 		or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
 			payment_type = "Receive"
 	else:
@@ -1006,6 +1010,9 @@
 	elif dt == "Fees":
 		grand_total = doc.grand_total
 		outstanding_amount = doc.outstanding_amount
+	elif dt == "Dunning":
+		grand_total = doc.grand_total
+		outstanding_amount = doc.grand_total
 	else:
 		if party_account_currency == doc.company_currency:
 			grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
@@ -1075,15 +1082,35 @@
 			for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
 				pe.append('references', reference)
 		else:
-			pe.append("references", {
-				'reference_doctype': dt,
-				'reference_name': dn,
-				"bill_no": doc.get("bill_no"),
-				"due_date": doc.get("due_date"),
-				'total_amount': grand_total,
-				'outstanding_amount': outstanding_amount,
-				'allocated_amount': outstanding_amount
-			})
+			if dt == "Dunning":
+				pe.append("references", {
+					'reference_doctype': 'Sales Invoice',
+					'reference_name': doc.get('sales_invoice'),
+					"bill_no": doc.get("bill_no"),
+					"due_date": doc.get("due_date"),
+					'total_amount': doc.get('outstanding_amount'),
+					'outstanding_amount': doc.get('outstanding_amount'),
+					'allocated_amount': doc.get('outstanding_amount')
+				})
+				pe.append("references", {
+					'reference_doctype': dt,
+					'reference_name': dn,
+					"bill_no": doc.get("bill_no"),
+					"due_date": doc.get("due_date"),
+					'total_amount': doc.get('dunning_amount'),
+					'outstanding_amount': doc.get('dunning_amount'),
+					'allocated_amount': doc.get('dunning_amount')
+				})
+			else:	
+				pe.append("references", {
+					'reference_doctype': dt,
+					'reference_name': dn,
+					"bill_no": doc.get("bill_no"),
+					"due_date": doc.get("due_date"),
+					'total_amount': grand_total,
+					'outstanding_amount': outstanding_amount,
+					'allocated_amount': outstanding_amount
+				})
 
 	pe.setup_party_account_field()
 	pe.set_missing_values()
@@ -1172,4 +1199,4 @@
 
 	}, target_doc, set_missing_values)
 
-	return doclist
+	return doclist
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index df0c3d2..061ce1c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -96,6 +96,12 @@
 				cur_frm.add_custom_button(__('Invoice Discounting'), function() {
 					cur_frm.events.create_invoice_discounting(cur_frm);
 				}, __('Create'));
+
+				if (doc.due_date < frappe.datetime.get_today()) {
+					cur_frm.add_custom_button(__('Dunning'), function() {
+						cur_frm.events.create_dunning(cur_frm);
+					}, __('Create'));
+				}
 			}
 
 			if (doc.docstatus === 1) {
@@ -824,6 +830,12 @@
 			method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_invoice_discounting",
 			frm: frm
 		});
+	},
+	create_dunning: function(frm) {
+		frappe.model.open_mapped_doc({
+			method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning",
+			frm: frm
+		});
 	}
 })
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index bab5208..8984348 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1602,3 +1602,37 @@
 	})
 
 	return invoice_discounting
+
+@frappe.whitelist()
+def create_dunning(source_name, target_doc=None):
+	from frappe.model.mapper import get_mapped_doc
+	from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
+	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]})
+			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())
+			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
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/dunning_letter/__init__.py b/erpnext/accounts/print_format/dunning_letter/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/print_format/dunning_letter/__init__.py
diff --git a/erpnext/accounts/print_format/dunning_letter/dunning_letter.json b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
new file mode 100644
index 0000000..a7eac70
--- /dev/null
+++ b/erpnext/accounts/print_format/dunning_letter/dunning_letter.json
@@ -0,0 +1,25 @@
+{
+ "align_labels_right": 0,
+ "creation": "2019-12-11 04:37:14.012805",
+ "css": ".print-format th {\n    background-color: transparent !important;\n    border-bottom: 1px solid !important;\n    border-top: none !important;\n}\n.print-format .ql-editor {\n    padding-left: 0px;\n    padding-right: 0px;\n}\n\n.print-format table {\n    margin-bottom: 0px;\n    }\n.print-format .table-data tr:last-child { \n    border-bottom: 1px solid !important;\n}\n\n.print-format .table-inner tr:last-child {\n    border-bottom:none !important;\n}\n.print-format .table-inner {\n    margin: 0px 0px;\n}\n\n.print-format .table-data ul li { \n    color:#787878 !important;\n}\n\n.no-top-border {\n    border-top:none !important;\n}\n\n.table-inner td {\n    padding-left: 0px !important;    \n    padding-top: 1px !important;\n    padding-bottom: 1px !important;\n    color:#787878 !important;\n}\n\n.total {\n    background-color: lightgrey !important;\n    padding-top: 4px !important;\n    padding-bottom: 4px !important;\n}\n",
+ "custom_format": 0,
+ "default_print_language": "en",
+ "disabled": 0,
+ "doc_type": "Dunning",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Arial",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<b>{{doc.customer_name}}</b> <br />\\n{{doc.address_display}}\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<div style=\\\"text-align:left;\\\">\\n<div style=\\\"font-size:24px; text-transform:uppercase;\\\">{{_(doc.dunning_type)}}</div>\\n<div style=\\\"font-size:16px;padding-bottom:5px;\\\">{{ doc.name }}</div>\\n</div>\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldname\": \"sales_invoice\", \"print_hide\": 0, \"label\": \"Sales Invoice\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"label\": \"Due Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"body_text\", \"print_hide\": 0, \"label\": \"Body Text\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"<table class=\\\"table table-borderless table-data\\\">\\n   <tbody>\\n        <tr>\\n            <th>{{_(\\\"Description\\\")}}</th>\\n\\t        <th style=\\\"text-align: right;\\\">{{_(\\\"Amount\\\")}}</th>\\n        </tr>\\n        <tr>\\n            <td>\\n                {{_(\\\"Outstanding Amount\\\")}}\\n             </td>\\n            <td style=\\\"text-align: right;\\\">\\n                {{doc.get_formatted(\\\"outstanding_amount\\\")}}\\n            </td>\\n        </tr>\\n        {%if doc.rate_of_interest > 0%}\\n        <tr>\\n            <td>\\n                {{_(\\\"Interest \\\")}} {{doc.rate_of_interest}}% p.a. ({{doc.overdue_days}} {{_(\\\"days\\\")}})\\n             </td>\\n            <td style=\\\"text-align: right;\\\">\\n                {{doc.get_formatted(\\\"interest_amount\\\")}}\\n            </td>\\n        </tr>\\n        {% endif %}\\n        {%if doc.dunning_fee > 0%}\\n        <tr>\\n            <td>\\n                {{_(\\\"Dunning Fee\\\")}}\\n             </td>\\n            <td style=\\\"text-align: right;\\\">\\n                {{doc.get_formatted(\\\"dunning_fee\\\")}}\\n            </td>\\n        </tr>\\n        {% endif %}\\n    </tbody>\\n</table>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n<div class=\\\"row total\\\" style =\\\"margin-right: 0px;\\\">\\n\\t\\t<div class=\\\"col-xs-5\\\">\\n\\t\\t\\t<b>{{_(\\\"Grand Total\\\")}}</b></div>\\n\\t\\t<div class=\\\"col-xs-7 text-right\\\" style=\\\"padding-right: 4px;\\\">\\n\\t\\t\\t<b>{{doc.get_formatted(\\\"grand_total\\\")}}</b>\\n\\t\\t</div>\\n</div>\\n\\n\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"closing_text\", \"print_hide\": 0, \"label\": \"Closing Text\"}]",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2020-07-14 18:25:44.348207",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Dunning Letter",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 0,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e8dda20..95a836f 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -249,7 +249,7 @@
 		"validate": "erpnext.regional.india.utils.update_grand_total_for_rcm"
 	},
 	"Payment Entry": {
-		"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
+		"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
 		"on_trash": "erpnext.regional.check_deletion_permission"
 	},
 	'Address': {
@@ -552,4 +552,4 @@
 		{'doctype': 'Hotel Room Package', 'index': 3},
 		{'doctype': 'Hotel Room Type', 'index': 4}
 	]
-}
+}
\ No newline at end of file