diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index b2c4d92a6af9..10f30512c0b6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -703,40 +703,74 @@ def cancel_exchange_gain_loss_journal( Cancel Exchange Gain/Loss for Sales/Purchase Invoice, if they have any. """ if parent_doc.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]: - journals = frappe.db.get_all( - "Journal Entry Account", - filters={ - "reference_type": parent_doc.doctype, - "reference_name": parent_doc.name, - "docstatus": 1, - }, - fields=["parent"], - as_list=1, + gain_loss_journals = get_linked_exchange_gain_loss_journal( + referenced_dt=parent_doc.doctype, referenced_dn=parent_doc.name, je_docstatus=1 ) - - if journals: - gain_loss_journals = frappe.db.get_all( - "Journal Entry", - filters={ - "name": ["in", [x[0] for x in journals]], - "voucher_type": "Exchange Gain Or Loss", - "docstatus": 1, - }, - as_list=1, - ) - for doc in gain_loss_journals: - gain_loss_je = frappe.get_doc("Journal Entry", doc[0]) - if referenced_dt and referenced_dn: - references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts] - if ( - len(references) == 2 - and (referenced_dt, referenced_dn) in references - and (parent_doc.doctype, parent_doc.name) in references - ): - # only cancel JE generated against parent_doc and referenced_dn - gain_loss_je.cancel() - else: + for doc in gain_loss_journals: + gain_loss_je = frappe.get_doc("Journal Entry", doc) + if referenced_dt and referenced_dn: + references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts] + if ( + len(references) == 2 + and (referenced_dt, referenced_dn) in references + and (parent_doc.doctype, parent_doc.name) in references + ): + # only cancel JE generated against parent_doc and referenced_dn gain_loss_je.cancel() + else: + gain_loss_je.cancel() + + +def delete_exchange_gain_loss_journal( + parent_doc: dict | object, referenced_dt: str | None = None, referenced_dn: str | None = None +) -> None: + """ + Delete Exchange Gain/Loss for Sales/Purchase Invoice, if they have any. + """ + if parent_doc.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]: + gain_loss_journals = get_linked_exchange_gain_loss_journal( + referenced_dt=parent_doc.doctype, referenced_dn=parent_doc.name, je_docstatus=2 + ) + for doc in gain_loss_journals: + gain_loss_je = frappe.get_doc("Journal Entry", doc) + if referenced_dt and referenced_dn: + references = [(x.reference_type, x.reference_name) for x in gain_loss_je.accounts] + if ( + len(references) == 2 + and (referenced_dt, referenced_dn) in references + and (parent_doc.doctype, parent_doc.name) in references + ): + # only delete JE generated against parent_doc and referenced_dn + gain_loss_je.delete() + else: + gain_loss_je.delete() + + +def get_linked_exchange_gain_loss_journal(referenced_dt: str, referenced_dn: str, je_docstatus: int) -> list: + """ + Get all the linked exchange gain/loss journal entries for a given document. + """ + gain_loss_journals = [] + if journals := frappe.db.get_all( + "Journal Entry Account", + { + "reference_type": referenced_dt, + "reference_name": referenced_dn, + "docstatus": je_docstatus, + }, + pluck="parent", + ): + gain_loss_journals = frappe.db.get_all( + "Journal Entry", + { + "name": ["in", journals], + "voucher_type": "Exchange Gain Or Loss", + "is_system_generated": 1, + "docstatus": je_docstatus, + }, + pluck="name", + ) + return gain_loss_journals def cancel_common_party_journal(self): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a8a790719bff..ab7ee7731582 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -326,11 +326,16 @@ def _remove_references_in_repost_doctypes(self): repost_doc.save(ignore_permissions=True) def on_trash(self): + from erpnext.accounts.utils import delete_exchange_gain_loss_journal + self._remove_references_in_repost_doctypes() self._remove_references_in_unreconcile() # delete sl and gl entries on deletion of transaction if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): + # delete linked exchange gain/loss journal + delete_exchange_gain_loss_journal(self) + ple = frappe.qb.DocType("Payment Ledger Entry") frappe.qb.from_(ple).delete().where( (ple.voucher_type == self.doctype) & (ple.voucher_no == self.name)