fix: Taxes aren't discounted on early payment discount
- Deductions in payment entry must be split into income loss and tax loss
- Compute total discount in percentage, makes discounting different amounts proportionately easier
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cd5b6d5..91d31ab 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1669,7 +1669,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,7 +1769,9 @@
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:
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
@@ -1891,6 +1893,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 +1914,96 @@
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:
+ loss_on_income = flt(doc.get("total") * (total_discount_percent / 100), doc.precision("total"))
+ 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": loss_on_income,
+ },
+ )
+ return loss_on_income
+
+
+def add_tax_discount_loss(pe, doc, total_discount_percenatage) -> float:
+ 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": loss,
+ },
+ )
+
+ return total_tax_loss
def get_reference_as_per_payment_terms(