diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1aefeaacf7..ed374c1eb1 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -474,10 +474,12 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n # update ref in advance entry if voucher_type == "Journal Entry": - update_reference_in_journal_entry(entry, doc, do_not_save=True) + referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False) # advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss # amount and account in args - doc.make_exchange_gain_loss_journal(args) + # referenced_row is used to deduplicate gain/loss journal + entry.update({"referenced_row": referenced_row}) + doc.make_exchange_gain_loss_journal([entry]) else: update_reference_in_payment_entry( entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe @@ -627,6 +629,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): if not do_not_save: journal_entry.save(ignore_permissions=True) + return new_row.name + def update_reference_in_payment_entry( d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False @@ -1901,6 +1905,7 @@ def create_gain_loss_journal( journal_entry.company = company journal_entry.posting_date = nowdate() journal_entry.multi_currency = 1 + journal_entry.is_system_generated = True party_account_currency = frappe.get_cached_value("Account", party_account, "account_currency") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1d50639043..a4c81c32a2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1023,6 +1023,44 @@ class AccountsController(TransactionBase): ) ) + def gain_loss_journal_already_booked( + self, + gain_loss_account, + exc_gain_loss, + ref2_dt, + ref2_dn, + ref2_detail_no, + ) -> bool: + """ + Check if gain/loss is booked + """ + if res := frappe.db.get_all( + "Journal Entry Account", + filters={ + "docstatus": 1, + "account": gain_loss_account, + "reference_type": ref2_dt, # this will be Journal Entry + "reference_name": ref2_dn, + "reference_detail_no": ref2_detail_no, + }, + pluck="parent", + ): + # deduplicate + res = list({x for x in res}) + if exc_vouchers := frappe.db.get_all( + "Journal Entry", + filters={"name": ["in", res], "voucher_type": "Exchange Gain Or Loss"}, + fields=["voucher_type", "total_debit", "total_credit"], + ): + booked_voucher = exc_vouchers[0] + if ( + booked_voucher.total_debit == exc_gain_loss + and booked_voucher.total_credit == exc_gain_loss + and booked_voucher.voucher_type == "Exchange Gain Or Loss" + ): + return True + return False + def make_exchange_gain_loss_journal(self, args: dict = None) -> None: """ Make Exchange Gain/Loss journal for Invoices and Payments @@ -1051,27 +1089,34 @@ class AccountsController(TransactionBase): reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" - je = create_gain_loss_journal( - self.company, - arg.get("party_type"), - arg.get("party"), - party_account, + if not self.gain_loss_journal_already_booked( gain_loss_account, difference_amount, - dr_or_cr, - reverse_dr_or_cr, - arg.get("against_voucher_type"), - arg.get("against_voucher"), - arg.get("idx"), self.doctype, self.name, - arg.get("idx"), - ) - frappe.msgprint( - _("Exchange Gain/Loss amount has been booked through {0}").format( - get_link_to_form("Journal Entry", je) + arg.get("referenced_row"), + ): + je = create_gain_loss_journal( + self.company, + arg.get("party_type"), + arg.get("party"), + party_account, + gain_loss_account, + difference_amount, + dr_or_cr, + reverse_dr_or_cr, + arg.get("against_voucher_type"), + arg.get("against_voucher"), + arg.get("idx"), + self.doctype, + self.name, + arg.get("referenced_row"), + ) + frappe.msgprint( + _("Exchange Gain/Loss amount has been booked through {0}").format( + get_link_to_form("Journal Entry", je) + ) ) - ) if self.get("doctype") == "Payment Entry": # For Payment Entry, exchange_gain_loss field in the `references` table is the trigger for journal creation