fix: Multi-currency SI with base currency PE
- Return total discount loss in base currency
- Allocate payment based on terms: Set allocated amount in references table in base currency if accounting is in that currency
- Allocate payment based on terms: While back updating set paid amount (payment schedule) in transaction currency always
- minor: discount msgprint in correct currency
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 152ec4d..9dfc674 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -416,7 +416,7 @@
for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
- key = (ref.payment_term, ref.reference_name)
+ key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount
@@ -434,7 +434,7 @@
],
)
for term in payment_schedule:
- invoice_key = (term.payment_term, ref.reference_name)
+ invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
if not (term.discount_type and term.discount):
@@ -451,6 +451,10 @@
if not invoice_paid_amount_map.get(key):
frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))
+ allocated_amount = self.get_allocated_amount_in_transaction_currency(
+ allocated_amount, key[2], key[1]
+ )
+
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
@@ -485,6 +489,33 @@
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
)
+ def get_allocated_amount_in_transaction_currency(
+ self, allocated_amount, reference_doctype, reference_docname
+ ):
+ """
+ Payment Entry could be in base currency while reference's payment schedule
+ is always in transaction currency.
+ E.g.
+ * SI with base=INR and currency=USD
+ * SI with payment schedule in USD
+ * PE in INR (accounting done in base currency)
+ """
+ ref_currency, ref_exchange_rate = frappe.db.get_value(
+ reference_doctype, reference_docname, ["currency", "conversion_rate"]
+ )
+ is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency
+ # PE in different currency
+ reference_is_multi_currency = self.paid_from_account_currency != ref_currency
+
+ if not (is_single_currency and reference_is_multi_currency):
+ return allocated_amount
+
+ allocated_amount = flt(
+ allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
+ )
+
+ return allocated_amount
+
def set_status(self):
if self.docstatus == 2:
self.status = "Cancelled"
@@ -1731,7 +1762,7 @@
):
for reference in get_reference_as_per_payment_terms(
- doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+ doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
pe.append("references", reference)
else:
@@ -1905,11 +1936,11 @@
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
+ is_multi_currency = party_account_currency != doc.company_currency
if doc.doctype in eligible_for_payments and has_payment_schedule:
for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
- is_multi_currency = party_account_currency != doc.company_currency
if term.discount_type == "Percentage":
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
@@ -1932,7 +1963,8 @@
total_discount += discount_amount
if total_discount:
- money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
+ currency = doc.get("currency") if is_multi_currency else doc.company_currency
+ money = frappe.utils.fmt_money(total_discount, currency=currency)
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
return paid_amount, received_amount, total_discount, valid_discounts
@@ -1952,7 +1984,6 @@
positive_negative = -1 if pe.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 * positive_negative,
}
@@ -1966,10 +1997,10 @@
if not total_discount_percent:
return 0.0
- loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
- loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
+ base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
+ base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
- return flt(loss_on_income + loss_on_taxes)
+ return flt(base_loss_on_income + base_loss_on_taxes)
def get_total_discount_percent(doc, valid_discounts) -> float:
@@ -1999,38 +2030,41 @@
"""Add loss on income discount in base currency."""
precision = doc.precision("total")
loss_on_income = flt(doc.get("total") * (total_discount_percent / 100), precision)
+ base_loss_on_income = flt(loss_on_income * doc.get("conversion_rate", 1), 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),
+ "amount": base_loss_on_income,
},
)
- return loss_on_income
+ return base_loss_on_income
-def add_tax_discount_loss(pe, doc, total_discount_percenatage) -> float:
+def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"""Add loss on tax discount in base currency."""
tax_discount_loss = {}
- total_tax_loss = 0
+ base_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
+ tax.get("tax_amount_after_discount_amount") * (total_discount_percentage / 100), precision
)
+ base_tax_loss = flt(tax_loss * doc.get("conversion_rate", 1), precision)
+
account = tax.get("account_head")
if not tax_discount_loss.get(account):
- tax_discount_loss[account] = tax_loss
+ tax_discount_loss[account] = base_tax_loss
else:
- tax_discount_loss[account] += tax_loss
+ tax_discount_loss[account] += base_tax_loss
for account, loss in tax_discount_loss.items():
- total_tax_loss += loss
- amount = flt(loss * doc.get("conversion_rate", 1), precision)
- if amount == 0.0:
+ base_total_tax_loss += loss
+ if loss == 0.0:
continue
pe.append(
@@ -2038,21 +2072,30 @@
{
"account": account,
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
- "amount": amount,
+ "amount": loss,
},
)
- return total_tax_loss
+ return base_total_tax_loss
def get_reference_as_per_payment_terms(
- payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+ payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
references = []
+ is_multi_currency_acc = (doc.currency != doc.company_currency) and (
+ party_account_currency != doc.company_currency
+ )
+
for payment_term in payment_schedule:
payment_term_outstanding = flt(
payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
)
+ if not is_multi_currency_acc:
+ # If accounting is done in company currency for multi-currency transaction
+ payment_term_outstanding = flt(
+ payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
+ )
if payment_term_outstanding:
references.append(