mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-13 03:45:08 +00:00
@@ -2950,29 +2950,46 @@ class AccountsController(TransactionBase):
|
|||||||
return
|
return
|
||||||
|
|
||||||
item_doctype = self.meta.get_field("items").options
|
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)
|
doctype_table = frappe.qb.DocType(self.doctype)
|
||||||
item_table = frappe.qb.DocType(item_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)
|
frappe.qb.from_(doctype_table)
|
||||||
.where(doctype_table.docstatus == 1)
|
.where(doctype_table.docstatus == 1)
|
||||||
.where(doctype_table.discount_amount != 0)
|
.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(
|
doctype_table.name.isin(
|
||||||
frappe.qb.from_(item_table)
|
frappe.qb.from_(item_table)
|
||||||
.select(item_table.parent)
|
.select(item_table.parent)
|
||||||
@@ -2980,20 +2997,29 @@ class AccountsController(TransactionBase):
|
|||||||
.distinct()
|
.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:
|
if not discount_already_applied:
|
||||||
return
|
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:
|
if (source_doc.discount_amount * (discount_already_applied - source_doc.discount_amount)) >= 0:
|
||||||
# full discount already applied or exceeded
|
# full discount already applied or exceeded
|
||||||
self.discount_amount = 0
|
self.discount_amount = 0
|
||||||
else:
|
else:
|
||||||
self.discount_amount = flt(
|
discount_amount = source_doc.discount_amount - discount_already_applied
|
||||||
self.discount_amount - discount_already_applied, self.precision("discount_amount")
|
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()
|
self.calculate_taxes_and_totals()
|
||||||
|
|
||||||
|
|||||||
@@ -2396,3 +2396,35 @@ class TestAccountsController(FrappeTestCase):
|
|||||||
# and not affected by the repeated mapping logic
|
# and not affected by the repeated mapping logic
|
||||||
self.assertEqual(dn.additional_discount_percentage, 10)
|
self.assertEqual(dn.additional_discount_percentage, 10)
|
||||||
self.assertEqual(dn.discount_amount, 50) # 10% of 500
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user