Merge branch 'develop' into early-payment-loss
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 91374ae..5a56a6b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -245,8 +245,6 @@
 		frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
 			party_account_currency, "references");
 
-		frm.set_currency_labels(["amount"], company_currency, "deductions");
-
 		cur_frm.set_df_property("source_exchange_rate", "description",
 			("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency));
 
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cd5b6d5..6e612ee 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -424,15 +424,28 @@
 					payment_schedule = frappe.get_all(
 						"Payment Schedule",
 						filters={"parent": ref.reference_name},
-						fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
+						fields=[
+							"paid_amount",
+							"payment_amount",
+							"payment_term",
+							"discount",
+							"outstanding",
+							"discount_type",
+						],
 					)
 					for term in payment_schedule:
 						invoice_key = (term.payment_term, ref.reference_name)
 						invoice_paid_amount_map.setdefault(invoice_key, {})
 						invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
-						invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
-							term.discount / 100
-						)
+						if not (term.discount_type and term.discount):
+							continue
+
+						if term.discount_type == "Percentage":
+							invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
+								term.discount / 100
+							)
+						else:
+							invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
 
 		for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1):
 			if not invoice_paid_amount_map.get(key):
@@ -1669,7 +1682,7 @@
 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
 	)
 
-	paid_amount, received_amount, discount_amount = apply_early_payment_discount(
+	paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
 		paid_amount, received_amount, doc
 	)
 
@@ -1769,16 +1782,21 @@
 	if party_account and bank:
 		pe.set_exchange_rate(ref_doc=reference_doc)
 		pe.set_amounts()
-		if discount_amount:
+
+		discount_amount = set_early_payment_discount_loss(pe, doc, valid_discounts, discount_amount)
+		if discount_amount > 0:
+			# Set pending base discount amount in deductions
+			positive_negative = -1 if payment_type == "Pay" else 1
 			pe.set_gain_or_loss(
 				account_details={
 					"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
 					"cost_center": pe.cost_center
 					or frappe.get_cached_value("Company", pe.company, "cost_center"),
-					"amount": discount_amount * (-1 if payment_type == "Pay" else 1),
+					"amount": discount_amount * positive_negative * doc.get("conversion_rate", 1),
 				}
 			)
-			pe.set_difference_amount()
+
+		pe.set_difference_amount()
 
 	return pe
 
@@ -1891,6 +1909,7 @@
 
 def apply_early_payment_discount(paid_amount, received_amount, doc):
 	total_discount = 0
+	valid_discounts = []
 	eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
 	has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
 
@@ -1911,13 +1930,99 @@
 					received_amount -= discount_amount
 					paid_amount -= discount_amount_in_foreign_currency
 
+				valid_discounts.append({"type": term.discount_type, "discount": term.discount})
 				total_discount += discount_amount
 
 		if total_discount:
 			money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
 			frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
 
-	return paid_amount, received_amount, total_discount
+	return paid_amount, received_amount, total_discount, valid_discounts
+
+
+def set_early_payment_discount_loss(pe, doc, valid_discounts, discount_amount):
+	"""Split early bird discount deductions into Income Loss & Tax Loss."""
+	if not (discount_amount and valid_discounts):
+		return discount_amount
+
+	total_discount_percent = get_total_discount_percent(doc, valid_discounts)
+
+	if not total_discount_percent:
+		return discount_amount
+
+	loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
+	loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
+
+	return flt(discount_amount - (loss_on_income + loss_on_taxes))
+
+
+def get_total_discount_percent(doc, valid_discounts) -> float:
+	"""Get total percentage and amount discount applied as a percentage."""
+	total_discount_percent = (
+		sum(
+			discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
+		)
+		or 0.0
+	)
+
+	# Operate in percentages only as it makes the income & tax split easier
+	total_discount_amount = (
+		sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount")
+		or 0.0
+	)
+
+	if total_discount_amount:
+		discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100
+		total_discount_percent += discount_percentage
+		return total_discount_percent
+
+	return total_discount_percent
+
+
+def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
+	"""Add loss on income discount in base currency."""
+	precision = doc.precision("total")
+	loss_on_income = flt(doc.get("total") * (total_discount_percent / 100), precision)
+	pe.append(
+		"deductions",
+		{
+			"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
+			"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+			"amount": flt(loss_on_income * doc.get("conversion_rate", 1), precision),
+		},
+	)
+	return loss_on_income
+
+
+def add_tax_discount_loss(pe, doc, total_discount_percenatage) -> float:
+	"""Add loss on tax discount in base currency."""
+	tax_discount_loss = {}
+	total_tax_loss = 0
+	precision = doc.precision("tax_amount_after_discount_amount", "taxes")
+
+	# The same account head could be used more than once
+	for tax in doc.get("taxes", []):
+		tax_loss = flt(
+			tax.get("tax_amount_after_discount_amount") * (total_discount_percenatage / 100), precision
+		)
+		account = tax.get("account_head")
+		if not tax_discount_loss.get(account):
+			tax_discount_loss[account] = tax_loss
+		else:
+			tax_discount_loss[account] += tax_loss
+
+	for account, loss in tax_discount_loss.items():
+		total_tax_loss += loss
+		pe.append(
+			"deductions",
+			{
+				"account": account,
+				"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+				"amount": flt(loss * doc.get("conversion_rate", 1), precision),
+			},
+		)
+
+	return total_tax_loss
 
 
 def get_reference_as_per_payment_terms(
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 123b5df..fe80bad 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -260,6 +260,14 @@
 		si.submit()
 
 		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+
+		self.assertEqual(pe.references[0].payment_term, "30 Credit Days with 10% Discount")
+		self.assertEqual(pe.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe.paid_amount, 212.4)
+		self.assertEqual(pe.deductions[0].amount, 20.0)  # Loss on Income
+		self.assertEqual(pe.deductions[1].amount, 3.6)  # Loss on Tax
+		self.assertEqual(pe.deductions[1].account, "_Test Account Service Tax - _TC")
+
 		pe.submit()
 		si.load_from_db()
 
@@ -269,6 +277,46 @@
 		self.assertEqual(si.payment_schedule[0].outstanding, 0)
 		self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
 
+	def test_payment_entry_against_payment_terms_with_discount_amount(self):
+		si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
+
+		si.payment_terms_template = "Test Discount Amount Template"
+		create_payment_terms_template_with_discount(
+			name="30 Credit Days with Rs.50 Discount",
+			discount_type="Amount",
+			discount=50,
+			template_name="Test Discount Amount Template",
+		)
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+
+		si.append(
+			"taxes",
+			{
+				"charge_type": "On Net Total",
+				"account_head": "_Test Account Service Tax - _TC",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "Service Tax",
+				"rate": 18,
+			},
+		)
+		si.save()
+		si.submit()
+
+		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+		self.assertEqual(pe.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe.paid_amount, 186)
+		self.assertEqual(pe.deductions[0].amount, 42.37)  # Loss on Income
+		self.assertEqual(pe.deductions[1].amount, 7.63)  # Loss on Tax
+		self.assertEqual(pe.deductions[1].account, "_Test Account Service Tax - _TC")
+
+		pe.submit()
+		si.load_from_db()
+
+		self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
+		self.assertEqual(si.payment_schedule[0].paid_amount, 186)
+		self.assertEqual(si.payment_schedule[0].outstanding, 0)
+		self.assertEqual(si.payment_schedule[0].discounted_amount, 50)
+
 	def test_payment_against_purchase_invoice_to_check_status(self):
 		pi = make_purchase_invoice(
 			supplier="_Test Supplier USD",
@@ -839,24 +887,27 @@
 		).insert()
 
 
-def create_payment_terms_template_with_discount():
+def create_payment_terms_template_with_discount(
+	name=None, discount_type=None, discount=None, template_name=None
+):
+	create_payment_term(name or "30 Credit Days with 10% Discount")
+	template_name = template_name or "Test Discount Template"
 
-	create_payment_term("30 Credit Days with 10% Discount")
-
-	if not frappe.db.exists("Payment Terms Template", "Test Discount Template"):
-		payment_term_template = frappe.get_doc(
+	if not frappe.db.exists("Payment Terms Template", template_name):
+		frappe.get_doc(
 			{
 				"doctype": "Payment Terms Template",
-				"template_name": "Test Discount Template",
+				"template_name": template_name,
 				"allocate_payment_based_on_payment_terms": 1,
 				"terms": [
 					{
 						"doctype": "Payment Terms Template Detail",
-						"payment_term": "30 Credit Days with 10% Discount",
+						"payment_term": name or "30 Credit Days with 10% Discount",
 						"invoice_portion": 100,
 						"credit_days_based_on": "Day(s) after invoice date",
 						"credit_days": 2,
-						"discount": 10,
+						"discount_type": discount_type or "Percentage",
+						"discount": discount or 10,
 						"discount_validity_based_on": "Day(s) after invoice date",
 						"discount_validity": 1,
 					}
diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
index 61a1462..1c31829 100644
--- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
+++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
@@ -3,6 +3,7 @@
  "creation": "2016-06-15 15:56:30.815503",
  "doctype": "DocType",
  "editable_grid": 1,
+ "engine": "InnoDB",
  "field_order": [
   "account",
   "cost_center",
@@ -17,9 +18,7 @@
    "in_list_view": 1,
    "label": "Account",
    "options": "Account",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "cost_center",
@@ -28,37 +27,30 @@
    "label": "Cost Center",
    "options": "Cost Center",
    "print_hide": 1,
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Amount",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Amount (Company Currency)",
+   "options": "Company:company:default_currency",
+   "reqd": 1
   },
   {
    "fieldname": "column_break_2",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "description",
    "fieldtype": "Small Text",
-   "label": "Description",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Description"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-12 20:38:08.110674",
+ "modified": "2023-03-06 07:11:57.739619",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry Deduction",
@@ -66,5 +58,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file