diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py index 51053f1f68c..3eef6ab3832 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py @@ -188,7 +188,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): pe = get_payment_entry(si.doctype, si.name) pe.paid_amount = 95 - pe.source_exchange_rate = 84.211 + pe.source_exchange_rate = 84.2105 pe.received_amount = 8000 pe.references = [] pe.save().submit() @@ -229,7 +229,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): row = next(x for x in je.accounts if x.account == self.debtors_usd) self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD row = next(x for x in je.accounts if x.account != self.debtors_usd) - self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR + self.assertEqual(flt(row.debit_in_account_currency, precision), 421.05) # in INR # total_debit and total_credit will be 0.0, as JV is posting only to account currency fields self.assertEqual(flt(je.total_debit, precision), 0.0) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2d27cccfff8..f2d11ba9ff3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -324,11 +324,6 @@ frappe.ui.form.on("Payment Entry", { "write_off_difference_amount", frm.doc.difference_amount && frm.doc.party && frm.doc.total_allocated_amount > party_amount ); - - frm.toggle_display( - "set_exchange_gain_loss", - frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount - ); }, set_dynamic_labels: function (frm) { @@ -1119,36 +1114,34 @@ frappe.ui.form.on("Payment Entry", { }, set_unallocated_amount: function (frm) { - var unallocated_amount = 0; - var total_deductions = frappe.utils.sum( - $.map(frm.doc.deductions || [], function (d) { - return flt(d.amount); - }) - ); + let unallocated_amount = 0; + let deductions_to_consider = 0; + + for (const row of frm.doc.deductions || []) { + if (!row.is_exchange_gain_loss) deductions_to_consider += flt(row.amount); + } + const included_taxes = get_included_taxes(frm); if (frm.doc.party) { if ( frm.doc.payment_type == "Receive" && - frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions && - frm.doc.total_allocated_amount < - frm.doc.paid_amount + total_deductions / frm.doc.source_exchange_rate - ) { - unallocated_amount = - (frm.doc.base_received_amount + - total_deductions - - flt(frm.doc.base_total_taxes_and_charges) - - frm.doc.base_total_allocated_amount) / - frm.doc.source_exchange_rate; - } else if ( - frm.doc.payment_type == "Pay" && - frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions && - frm.doc.total_allocated_amount < - frm.doc.received_amount + total_deductions / frm.doc.target_exchange_rate + frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount + deductions_to_consider ) { unallocated_amount = (frm.doc.base_paid_amount + - flt(frm.doc.base_total_taxes_and_charges) - - (total_deductions + frm.doc.base_total_allocated_amount)) / + deductions_to_consider - + frm.doc.base_total_allocated_amount - + included_taxes) / + frm.doc.source_exchange_rate; + } else if ( + frm.doc.payment_type == "Pay" && + frm.doc.base_total_allocated_amount < frm.doc.base_received_amount - deductions_to_consider + ) { + unallocated_amount = + (frm.doc.base_received_amount - + deductions_to_consider - + frm.doc.base_total_allocated_amount - + included_taxes) / frm.doc.target_exchange_rate; } } @@ -1242,77 +1235,85 @@ frappe.ui.form.on("Payment Entry", { }, write_off_difference_amount: function (frm) { - frm.events.set_deductions_entry(frm, "write_off_account"); + frm.events.set_write_off_deduction(frm); }, - set_exchange_gain_loss: function (frm) { - frm.events.set_deductions_entry(frm, "exchange_gain_loss_account"); + base_paid_amount: function (frm) { + frm.events.set_exchange_gain_loss_deduction(frm); }, - set_deductions_entry: function (frm, account) { - if (frm.doc.difference_amount) { - frappe.call({ - method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_company_defaults", - args: { - company: frm.doc.company, - }, - callback: function (r, rt) { - if (r.message) { - const write_off_row = $.map(frm.doc["deductions"] || [], function (t) { - return t.account == r.message[account] ? t : null; - }); + base_received_amount: function (frm) { + frm.events.set_exchange_gain_loss_deduction(frm); + }, - const difference_amount = flt( - frm.doc.difference_amount, - precision("difference_amount") - ); + set_exchange_gain_loss_deduction: async function (frm) { + // wait for allocate_party_amount_against_ref_docs to finish + await frappe.after_ajax(); + const base_paid_amount = frm.doc.base_paid_amount || 0; + const base_received_amount = frm.doc.base_received_amount || 0; + const exchange_gain_loss = flt( + base_paid_amount - base_received_amount, + get_deduction_amount_precision() + ); - const add_deductions = (details) => { - let row = null; - if (!write_off_row.length && difference_amount) { - row = frm.add_child("deductions"); - row.account = details[account]; - row.cost_center = details["cost_center"]; - } else { - row = write_off_row[0]; - } - - if (row) { - row.amount = flt(row.amount) + difference_amount; - } else { - frappe.msgprint(__("No gain or loss in the exchange rate")); - } - refresh_field("deductions"); - }; - - if (!r.message[account]) { - frappe.prompt( - { - label: __("Please Specify Account"), - fieldname: account, - fieldtype: "Link", - options: "Account", - get_query: () => ({ - filters: { - company: frm.doc.company, - }, - }), - }, - (values) => { - const details = Object.assign({}, r.message, values); - add_deductions(details); - }, - __(frappe.unscrub(account)) - ); - } else { - add_deductions(r.message); - } - - frm.events.set_unallocated_amount(frm); - } - }, - }); + if (!exchange_gain_loss) { + frm.events.delete_exchange_gain_loss(frm); + return; } + + const account_fieldname = "exchange_gain_loss_account"; + let row = (frm.doc.deductions || []).find((t) => t.is_exchange_gain_loss); + + if (!row) { + const response = await get_company_defaults(frm.doc.company); + + const account = + response.message?.[account_fieldname] || + (await prompt_for_missing_account(frm, account_fieldname)); + + row = frm.add_child("deductions"); + row.account = account; + row.cost_center = response.message?.cost_center; + row.is_exchange_gain_loss = 1; + } + + row.amount = exchange_gain_loss; + frm.refresh_field("deductions"); + frm.events.set_unallocated_amount(frm); + }, + + delete_exchange_gain_loss: function (frm) { + const exchange_gain_loss_row = (frm.doc.deductions || []).find((row) => row.is_exchange_gain_loss); + + if (!exchange_gain_loss_row) return; + + exchange_gain_loss_row.amount = 0; + frm.get_field("deductions").grid.grid_rows[exchange_gain_loss_row.idx - 1].remove(); + frm.refresh_field("deductions"); + }, + + set_write_off_deduction: async function (frm) { + const difference_amount = flt(frm.doc.difference_amount, get_deduction_amount_precision()); + if (!difference_amount) return; + + const account_fieldname = "write_off_account"; + const response = await get_company_defaults(frm.doc.company); + const write_off_account = + response.message?.[account_fieldname] || + (await prompt_for_missing_account(frm, account_fieldname)); + + if (!write_off_account) return; + + let row = (frm.doc["deductions"] || []).find((t) => t.account == write_off_account); + if (!row) { + row = frm.add_child("deductions"); + row.account = write_off_account; + row.cost_center = response.message?.cost_center; + } + + row.amount = flt(row.amount) + difference_amount; + frm.refresh_field("deductions"); + frm.events.set_unallocated_amount(frm); }, bank_account: function (frm) { @@ -1778,6 +1779,13 @@ frappe.ui.form.on("Advance Taxes and Charges", { }); frappe.ui.form.on("Payment Entry Deduction", { + before_deductions_remove: function (doc, cdt, cdn) { + const row = frappe.get_doc(cdt, cdn); + if (row.is_exchange_gain_loss && row.amount) { + frappe.throw(__("Cannot delete Exchange Gain/Loss row")); + } + }, + amount: function (frm) { frm.events.set_unallocated_amount(frm); }, @@ -1799,3 +1807,53 @@ function set_default_party_type(frm) { if (party_type) frm.set_value("party_type", party_type); } + +function get_included_taxes(frm) { + let included_taxes = 0; + for (const tax of frm.doc.taxes) { + if (!tax.included_in_paid_amount) continue; + + if (tax.add_deduct_tax == "Add") { + included_taxes += tax.base_tax_amount; + } else { + included_taxes -= tax.base_tax_amount; + } + } + + return included_taxes; +} + +function get_company_defaults(company) { + return frappe.call({ + method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_company_defaults", + args: { + company: company, + }, + }); +} + +function prompt_for_missing_account(frm, account) { + return new Promise((resolve) => { + const dialog = frappe.prompt( + { + label: __(frappe.unscrub(account)), + fieldname: account, + fieldtype: "Link", + options: "Account", + get_query: () => ({ + filters: { + company: frm.doc.company, + }, + }), + }, + (values) => resolve(values?.[account]), + __("Please Specify Account") + ); + + dialog.on_hide = () => resolve(""); + }); +} + +function get_deduction_amount_precision() { + return frappe.meta.get_field_precision(frappe.meta.get_field("Payment Entry Deduction", "amount")); +} diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index d420bcca342..69debbec5c7 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -56,7 +56,6 @@ "section_break_34", "total_allocated_amount", "base_total_allocated_amount", - "set_exchange_gain_loss", "column_break_36", "unallocated_amount", "difference_amount", @@ -390,11 +389,6 @@ "print_hide": 1, "read_only": 1 }, - { - "fieldname": "set_exchange_gain_loss", - "fieldtype": "Button", - "label": "Set Exchange Gain / Loss" - }, { "fieldname": "column_break_36", "fieldtype": "Column Break" @@ -801,7 +795,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-05-31 17:07:06.197249", + "modified": "2024-11-07 11:19:19.320883", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5ebe1dd8754..7e3d8a5833b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -893,6 +893,7 @@ class PaymentEntry(AccountsController): self.set_amounts_in_company_currency() self.set_total_allocated_amount() self.set_unallocated_amount() + self.set_exchange_gain_loss() self.set_difference_amount() def validate_amounts(self): @@ -988,10 +989,10 @@ class PaymentEntry(AccountsController): if d.exchange_rate is None: d.exchange_rate = 1 - allocated_amount_in_pe_exchange_rate = flt( + allocated_amount_in_ref_exchange_rate = flt( flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount") ) - d.exchange_gain_loss = base_allocated_amount - allocated_amount_in_pe_exchange_rate + d.exchange_gain_loss = base_allocated_amount - allocated_amount_in_ref_exchange_rate return base_allocated_amount def set_total_allocated_amount(self): @@ -1009,29 +1010,80 @@ class PaymentEntry(AccountsController): def set_unallocated_amount(self): self.unallocated_amount = 0 - if self.party: - total_deductions = sum(flt(d.amount) for d in self.get("deductions")) - included_taxes = self.get_included_taxes() - if ( - self.payment_type == "Receive" - and self.base_total_allocated_amount < self.base_received_amount + total_deductions - and self.total_allocated_amount - < flt(self.paid_amount) + (total_deductions / self.source_exchange_rate) - ): - self.unallocated_amount = ( - self.base_received_amount + total_deductions - self.base_total_allocated_amount - ) / self.source_exchange_rate - self.unallocated_amount -= included_taxes - elif ( - self.payment_type == "Pay" - and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) - and self.total_allocated_amount - < flt(self.received_amount) + (total_deductions / self.target_exchange_rate) - ): - self.unallocated_amount = ( - self.base_paid_amount - (total_deductions + self.base_total_allocated_amount) - ) / self.target_exchange_rate - self.unallocated_amount -= included_taxes + if not self.party: + return + + deductions_to_consider = sum( + flt(d.amount) for d in self.get("deductions") if not d.is_exchange_gain_loss + ) + included_taxes = self.get_included_taxes() + + if self.payment_type == "Receive" and self.base_total_allocated_amount < ( + self.base_paid_amount + deductions_to_consider + ): + self.unallocated_amount = ( + self.base_paid_amount + + deductions_to_consider + - self.base_total_allocated_amount + - included_taxes + ) / self.source_exchange_rate + elif self.payment_type == "Pay" and self.base_total_allocated_amount < ( + self.base_received_amount - deductions_to_consider + ): + self.unallocated_amount = ( + self.base_received_amount + - deductions_to_consider + - self.base_total_allocated_amount + - included_taxes + ) / self.target_exchange_rate + + def set_exchange_gain_loss(self): + exchange_gain_loss = flt( + self.base_paid_amount - self.base_received_amount, + self.precision("amount", "deductions"), + ) + + exchange_gain_loss_rows = [row for row in self.get("deductions") if row.is_exchange_gain_loss] + exchange_gain_loss_row = exchange_gain_loss_rows.pop(0) if exchange_gain_loss_rows else None + + for row in exchange_gain_loss_rows: + self.remove(row) + + if not exchange_gain_loss: + if exchange_gain_loss_row: + self.remove(exchange_gain_loss_row) + + return + + if not exchange_gain_loss_row: + values = frappe.get_cached_value( + "Company", self.company, ("exchange_gain_loss_account", "cost_center"), as_dict=True + ) + + for fieldname, value in values.items(): + if value: + continue + + label = _(frappe.get_meta("Company").get_label(fieldname)) + return frappe.msgprint( + _("Please set {0} in Company {1} to account for Exchange Gain / Loss").format( + label, get_link_to_form("Company", self.company) + ), + title=_("Missing Default in Company"), + indicator="red" if self.docstatus.is_submitted() else "yellow", + raise_exception=self.docstatus.is_submitted(), + ) + + exchange_gain_loss_row = self.append( + "deductions", + { + "account": values.exchange_gain_loss_account, + "cost_center": values.cost_center, + "is_exchange_gain_loss": 1, + }, + ) + + exchange_gain_loss_row.amount = exchange_gain_loss def set_difference_amount(self): base_unallocated_amount = flt(self.unallocated_amount) * ( @@ -1059,11 +1111,13 @@ class PaymentEntry(AccountsController): def get_included_taxes(self): included_taxes = 0 for tax in self.get("taxes"): - if tax.included_in_paid_amount: - if tax.add_deduct_tax == "Add": - included_taxes += tax.base_tax_amount - else: - included_taxes -= tax.base_tax_amount + if not tax.included_in_paid_amount: + continue + + if tax.add_deduct_tax == "Add": + included_taxes += tax.base_tax_amount + else: + included_taxes -= tax.base_tax_amount return included_taxes @@ -1912,8 +1966,8 @@ class PaymentEntry(AccountsController): def get_matched_payment_request_of_references(references=None): """ Get those `Payment Requests` which are matched with `References`.\n - - Amount must be same. - - Only single `Payment Request` available for this amount. + - Amount must be same. + - Only single `Payment Request` available for this amount. Example: [(reference_doctype, reference_name, allocated_amount, payment_request), ...] """ @@ -2015,7 +2069,7 @@ def get_outstanding_of_references_with_payment_term(references=None): def get_outstanding_of_references_with_no_payment_term(references): """ Fetch outstanding amount of `References` which have no `Payment Term` set.\n - - Fetch outstanding amount from `References` it self. + - Fetch outstanding amount from `References` it self. Note: `None` is used for allocation of `Payment Request` Example: {(reference_doctype, reference_name, None): outstanding_amount, ...} @@ -2829,9 +2883,6 @@ def get_payment_entry( update_accounting_dimensions(pe, doc) if party_account and bank: - pe.set_exchange_rate(ref_doc=doc) - pe.set_amounts() - if discount_amount: base_total_discount_loss = 0 if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"): @@ -2841,7 +2892,8 @@ def get_payment_entry( pe, doc, discount_amount, base_total_discount_loss, party_account_currency ) - pe.set_difference_amount() + pe.set_exchange_rate(ref_doc=doc) + pe.set_amounts() # If PE is created from PR directly, then no need to find open PRs for the references if not created_from_payment_request: @@ -2853,7 +2905,7 @@ def get_payment_entry( def get_open_payment_requests_for_references(references=None): """ Fetch all unpaid Payment Requests for the references. \n - - Each reference can have multiple Payment Requests. \n + - Each reference can have multiple Payment Requests. \n Example: {("Sales Invoice", "SINV-00001"): {"PREQ-00001": 1000, "PREQ-00002": 2000}} """ @@ -3188,13 +3240,14 @@ def set_pending_discount_loss(pe, doc, discount_amount, base_total_discount_loss book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss") account_type = "round_off_account" if book_tax_loss else "default_discount_account" - pe.set_gain_or_loss( - account_details={ + pe.append( + "deductions", + { "account": frappe.get_cached_value("Company", pe.company, account_type), "cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"), "amount": discount_amount * positive_negative, - } + }, ) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 8758110534f..312628d9f97 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -479,16 +479,9 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(pe.deductions[0].account, "Write Off - _TC") # Exchange loss - self.assertEqual(pe.difference_amount, 300.0) - - pe.append( - "deductions", - { - "account": "_Test Exchange Gain/Loss - _TC", - "cost_center": "_Test Cost Center - _TC", - "amount": 300.0, - }, - ) + self.assertEqual(pe.deductions[-1].amount, 300.0) + pe.deductions[-1].account = "_Test Exchange Gain/Loss - _TC" + pe.deductions[-1].cost_center = "_Test Cost Center - _TC" pe.insert() pe.submit() @@ -552,16 +545,10 @@ class TestPaymentEntry(FrappeTestCase): pe.reference_no = "1" pe.reference_date = "2016-01-01" - self.assertEqual(pe.difference_amount, 100) + self.assertEqual(pe.deductions[0].amount, 100) + pe.deductions[0].account = "_Test Exchange Gain/Loss - _TC" + pe.deductions[0].cost_center = "_Test Cost Center - _TC" - pe.append( - "deductions", - { - "account": "_Test Exchange Gain/Loss - _TC", - "cost_center": "_Test Cost Center - _TC", - "amount": 100, - }, - ) pe.insert() pe.submit() @@ -654,16 +641,9 @@ class TestPaymentEntry(FrappeTestCase): pe.set_exchange_rate() pe.set_amounts() - self.assertEqual(pe.difference_amount, 500) - - pe.append( - "deductions", - { - "account": "_Test Exchange Gain/Loss - _TC", - "cost_center": "_Test Cost Center - _TC", - "amount": 500, - }, - ) + self.assertEqual(pe.deductions[0].amount, 500) + pe.deductions[0].account = "_Test Exchange Gain/Loss - _TC" + pe.deductions[0].cost_center = "_Test Cost Center - _TC" pe.insert() pe.submit() diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index 1c31829f0ea..e47b51ae028 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -9,6 +9,7 @@ "cost_center", "amount", "column_break_2", + "is_exchange_gain_loss", "description" ], "fields": [ @@ -45,12 +46,20 @@ "fieldname": "description", "fieldtype": "Small Text", "label": "Description" + }, + { + "default": "0", + "depends_on": "eval:doc.is_exchange_gain_loss", + "fieldname": "is_exchange_gain_loss", + "fieldtype": "Check", + "label": "Is Exchange Gain / Loss?", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-03-06 07:11:57.739619", + "modified": "2024-11-05 16:07:47.307971", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Deduction", diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.py b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.py index fc67c526b28..ae4134fc27a 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.py +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.py @@ -18,6 +18,7 @@ class PaymentEntryDeduction(Document): amount: DF.Currency cost_center: DF.Link description: DF.SmallText | None + is_exchange_gain_loss: DF.Check parent: DF.Data parentfield: DF.Data parenttype: DF.Data diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py index 3d222b22ff8..c058dbfa0b8 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py +++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py @@ -262,6 +262,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): pe1.paid_from = self.debtors_usd pe1.paid_from_account_currency = "USD" pe1.source_exchange_rate = 75 + pe1.paid_amount = 100 pe1.received_amount = 75 * 100 pe1.save() # Allocate payment against both invoices @@ -279,6 +280,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase): pe2.paid_from = self.debtors_usd pe2.paid_from_account_currency = "USD" pe2.source_exchange_rate = 75 + pe2.paid_amount = 100 pe2.received_amount = 75 * 100 pe2.save() # Allocate payment against both invoices diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py index 59cbc11794f..5e108dee9b5 100644 --- a/erpnext/accounts/test/test_utils.py +++ b/erpnext/accounts/test/test_utils.py @@ -92,14 +92,14 @@ class TestUtils(unittest.TestCase): payment_entry.deductions = [] payment_entry.save() - # below is the difference between base_received_amount and base_paid_amount - self.assertEqual(payment_entry.difference_amount, -4855.0) + # below is the difference between base_paid_amount and base_received_amount (exchange gain) + self.assertEqual(payment_entry.deductions[0].amount, -4855.0) payment_entry.target_exchange_rate = 62.9 payment_entry.save() - # below is due to change in exchange rate - self.assertEqual(payment_entry.references[0].exchange_gain_loss, -4855.0) + # after changing the exchange rate, there is no exchange gain / loss + self.assertEqual(payment_entry.deductions, []) payment_entry.references = [] self.assertEqual(payment_entry.difference_amount, 0.0) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2716aa9883b..f53769155bd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -383,3 +383,4 @@ erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v15_0.update_task_assignee_email_field_in_asset_maintenance_log erpnext.patches.v15_0.update_sub_voucher_type_in_gl_entries erpnext.patches.v14_0.update_stock_uom_in_work_order_item +erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions diff --git a/erpnext/patches/v15_0/set_is_exchange_gain_loss_in_payment_entry_deductions.py b/erpnext/patches/v15_0/set_is_exchange_gain_loss_in_payment_entry_deductions.py new file mode 100644 index 00000000000..9ffe272fd5e --- /dev/null +++ b/erpnext/patches/v15_0/set_is_exchange_gain_loss_in_payment_entry_deductions.py @@ -0,0 +1,22 @@ +import frappe + + +def execute(): + default_exchange_gain_loss_accounts = frappe.get_all( + "Company", + filters={"exchange_gain_loss_account": ["!=", ""]}, + pluck="exchange_gain_loss_account", + ) + + if not default_exchange_gain_loss_accounts: + return + + payment_entry = frappe.qb.DocType("Payment Entry") + payment_entry_deduction = frappe.qb.DocType("Payment Entry Deduction") + + frappe.qb.update(payment_entry_deduction).set(payment_entry_deduction.is_exchange_gain_loss, 1).join( + payment_entry, + ).on(payment_entry.name == payment_entry_deduction.parent).where( + (payment_entry.paid_to_account_currency != payment_entry.paid_from_account_currency) + & (payment_entry_deduction.account.isin(default_exchange_gain_loss_accounts)) + ).run()