From 44539f094469003c1663ee202c3a1618a655e6d9 Mon Sep 17 00:00:00 2001 From: Sagar Vora <16315650+sagarvora@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:31:21 +0530 Subject: [PATCH] fix: ensure that additional discount amount is not mapped repeatedly (cherry picked from commit feb62102d95868756108665a75f6130150a8f837) --- erpnext/controllers/accounts_controller.py | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6410c28ed19..3a1f4c718b0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -39,6 +39,8 @@ from erpnext.accounts.doctype.pricing_rule.utils import ( ) from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center from erpnext.accounts.party import ( + PURCHASE_TRANSACTION_TYPES, + SALES_TRANSACTION_TYPES, get_party_account, get_party_account_currency, get_party_gle_currency, @@ -2918,6 +2920,83 @@ class AccountsController(TransactionBase): x["transaction_currency"] = self.currency x["transaction_exchange_rate"] = self.get("conversion_rate") or 1 + def after_mapping(self, source_doc): + self.set_discount_amount_after_mapping(source_doc) + + def set_discount_amount_after_mapping(self, source_doc): + """ + Ensures that Additional Discount Amount is not copied repeatedly + for multiple mappings of a single source transaction. + """ + + # source and target doctypes should both be buying / selling + for transaction_types in (PURCHASE_TRANSACTION_TYPES, SALES_TRANSACTION_TYPES): + if self.doctype in transaction_types and source_doc.doctype in transaction_types: + break + + else: + return + + # ensure both doctypes have discount_amount field + if not self.meta.get_field("discount_amount") or not source_doc.meta.get_field("discount_amount"): + return + + # ensure discount_amount is set in source doc + if not source_doc.discount_amount: + return + + # ensure additional_discount_percentage is not set in the source doc + if source_doc.get("additional_discount_percentage"): + 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 = ( + frappe.qb.from_(doctype_table) + .where(doctype_table.docstatus == 1) + .where(doctype_table.discount_amount != 0) + .where( + doctype_table.name.isin( + frappe.qb.from_(item_table) + .select(item_table.parent) + .where(item_table[reference_fieldname] == source_doc.name) + .distinct() + ) + ) + .select(Sum(doctype_table.discount_amount)) + ).run() + + if not discount_already_applied: + return + + discount_already_applied = flt(discount_already_applied[0][0], self.precision("discount_amount")) + 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") + ) + + self.calculate_taxes_and_totals() + @frappe.whitelist() def get_tax_rate(account_head):