fix: handle returns as well

This commit is contained in:
Sagar Vora
2025-10-17 19:21:24 +05:30
parent 0968f435d2
commit 0e026b9ccd
2 changed files with 82 additions and 24 deletions

View File

@@ -2965,29 +2965,46 @@ class AccountsController(TransactionBase):
return
item_doctype = self.meta.get_field("items").options
item_meta = frappe.get_meta(item_doctype)
reference_fieldname = next(
(
row.fieldname
for row in item_meta.fields
if row.fieldtype == "Link"
and row.options == source_doc.doctype
and not row.get("is_custom_field")
),
None,
)
if not reference_fieldname:
return
doctype_table = frappe.qb.DocType(self.doctype)
item_table = frappe.qb.DocType(item_doctype)
discount_already_applied = (
is_same_doctype = self.doctype == source_doc.doctype
is_return = self.get("is_return") and is_same_doctype
if is_same_doctype and not is_return:
# should never happen
# you don't map to the same doctype without it being a return
return
query = (
frappe.qb.from_(doctype_table)
.where(doctype_table.docstatus == 1)
.where(doctype_table.discount_amount != 0)
.where(
.select(Sum(doctype_table.discount_amount))
)
if is_return:
query = query.where(doctype_table.is_return == 1).where(
doctype_table.return_against == source_doc.name
)
else:
item_meta = frappe.get_meta(item_doctype)
reference_fieldname = next(
(
row.fieldname
for row in item_meta.fields
if row.fieldtype == "Link"
and row.options == source_doc.doctype
and not row.get("is_custom_field")
),
None,
)
if not reference_fieldname:
return
query = query.where(
doctype_table.name.isin(
frappe.qb.from_(item_table)
.select(item_table.parent)
@@ -2995,20 +3012,29 @@ class AccountsController(TransactionBase):
.distinct()
)
)
.select(Sum(doctype_table.discount_amount))
).run()
result = query.run()
if not result:
return
discount_already_applied = result[0][0]
if not discount_already_applied:
return
discount_already_applied = flt(discount_already_applied[0][0], self.precision("discount_amount"))
if is_return:
# returns have negative discount
discount_already_applied *= -1
if (source_doc.discount_amount * (discount_already_applied - source_doc.discount_amount)) >= 0:
# full discount already applied or exceeded
self.discount_amount = 0
else:
self.discount_amount = flt(
self.discount_amount - discount_already_applied, self.precision("discount_amount")
)
discount_amount = source_doc.discount_amount - discount_already_applied
if is_return:
# returns have negative discount
discount_amount *= -1
self.discount_amount = flt(discount_amount, self.precision("discount_amount"))
self.calculate_taxes_and_totals()

View File

@@ -2400,3 +2400,35 @@ class TestAccountsController(IntegrationTestCase):
# and not affected by the repeated mapping logic
self.assertEqual(dn.additional_discount_percentage, 10)
self.assertEqual(dn.discount_amount, 50) # 10% of 500
def test_discount_amount_for_multiple_returns(self):
"""
Test that discount amount is correctly adjusted when multiple return invoices
are created against the same original invoice to prevent over-returning discount
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
# Create original sales invoice with discount
si = create_sales_invoice(qty=10, rate=100, do_not_submit=True)
si.apply_discount_on = "Net Total"
si.discount_amount = 100
si.save()
si.submit()
# Create first return - Frappe will copy full discount by default, we need to adjust it
return_si_1 = make_sales_return(si.name)
return_si_1.items[0].qty = -6 # Return 6 out of 10 items
# Manually set discount to match the proportion (60% of discount)
return_si_1.discount_amount = -60
return_si_1.save()
return_si_1.submit()
self.assertEqual(return_si_1.discount_amount, -60)
# Create second return for remaining items
return_si_2 = make_sales_return(si.name)
return_si_2.items[0].qty = -4 # Return remaining 4 out of 10 items
return_si_2.save()
# Second return should only get remaining discount (100 - 60 = 40)
self.assertEqual(return_si_2.discount_amount, -40)