# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

import unittest

import frappe
from frappe.utils import add_days, nowdate, today

from erpnext.accounts.doctype.dunning.dunning import calculate_interest_and_amount
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
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,
)


class TestDunning(unittest.TestCase):
	@classmethod
	def setUpClass(self):
		create_dunning_type()
		create_dunning_type_with_zero_interest_rate()
		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.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_dunning_with_zero_interest_rate(self):
		dunning = create_dunning_with_zero_interest_rate()
		amounts = calculate_interest_and_amount(
			dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days
		)
		self.assertEqual(round(amounts.get("interest_amount"), 2), 0)
		self.assertEqual(round(amounts.get("dunning_amount"), 2), 20)
		self.assertEqual(round(amounts.get("grand_total"), 2), 120)

	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.assertEqual(expected_values[gle.account][0], gle.account)
			self.assertEqual(expected_values[gle.account][1], gle.debit)
			self.assertEqual(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_with_zero_interest_rate():
	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 with 0% Rate of Interest")
	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 with 0% Rate of Interest"
	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()


def create_dunning_type_with_zero_interest_rate():
	dunning_type = frappe.new_doc("Dunning Type")
	dunning_type.dunning_type = "First Notice with 0% Rate of Interest"
	dunning_type.start_day = 10
	dunning_type.end_day = 20
	dunning_type.dunning_fee = 20
	dunning_type.rate_of_interest = 0
	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, and late fees.",
		},
	)
	dunning_type.save()
