From b5637c43faa3df575a051d191910031e6919e05b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Dec 2024 13:40:03 +0530 Subject: [PATCH 1/7] refactor: configurable posting date for Exc Gain / Loss journal (cherry picked from commit 3fbd2ca0d9d9cef759669050964e2faa63af2429) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json # erpnext/accounts/doctype/accounts_settings/accounts_settings.py --- .../accounts_settings/accounts_settings.json | 81 +++++++++++++++++++ .../accounts_settings/accounts_settings.py | 56 +++++++++++++ .../payment_reconciliation.py | 5 ++ 3 files changed, 142 insertions(+) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 5581ab038c5..cb93720589d 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -43,7 +43,14 @@ "allow_stale", "section_break_jpd0", "auto_reconcile_payments", +<<<<<<< HEAD "stale_days", +======= + "auto_reconciliation_job_trigger", + "reconciliation_queue_size", + "column_break_resa", + "exchange_gain_loss_posting_date", +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) "invoicing_settings_tab", "accounts_transactions_settings_section", "over_billing_allowance", @@ -462,6 +469,76 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" +<<<<<<< HEAD +======= + }, + { + "default": "0", + "description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well", + "fieldname": "enable_immutable_ledger", + "fieldtype": "Check", + "label": "Enable Immutable Ledger" + }, + { + "fieldname": "column_break_gjcc", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation", + "fieldname": "calculate_depr_using_total_days", + "fieldtype": "Check", + "label": "Calculate daily depreciation using total days in depreciation period" + }, + { + "description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.", + "fieldname": "payment_request_settings", + "fieldtype": "Tab Break", + "label": "Payment Request" + }, + { + "default": "1", + "fieldname": "create_pr_in_draft_status", + "fieldtype": "Check", + "label": "Create in Draft Status" + }, + { + "fieldname": "column_break_yuug", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_resa", + "fieldtype": "Column Break" + }, + { + "default": "15", + "description": "Interval should be between 1 to 59 MInutes", + "fieldname": "auto_reconciliation_job_trigger", + "fieldtype": "Int", + "label": "Auto Reconciliation Job Trigger" + }, + { + "default": "5", + "description": "Documents Processed on each trigger. Queue Size should be between 5 and 100", + "fieldname": "reconciliation_queue_size", + "fieldtype": "Int", + "label": "Reconciliation Queue Size" + }, + { + "default": "0", + "description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports", + "fieldname": "ignore_is_opening_check_for_reporting", + "fieldtype": "Check", + "label": "Ignore Is Opening check for reporting" + }, + { + "default": "Payment", + "description": "Only applies for Normal Payments", + "fieldname": "exchange_gain_loss_posting_date", + "fieldtype": "Select", + "label": "Posting Date Inheritance for Exchange Gain / Loss", + "options": "Invoice\nPayment" +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) } ], "icon": "icon-cog", @@ -469,7 +546,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2024-01-22 12:10:10.151819", +======= + "modified": "2025-01-22 17:53:47.968079", +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ac3d44bb5e7..94361f23b8c 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,6 +14,62 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + acc_frozen_upto: DF.Date | None + add_taxes_from_item_tax_template: DF.Check + allow_multi_currency_invoices_against_single_party_account: DF.Check + allow_stale: DF.Check + auto_reconcile_payments: DF.Check + auto_reconciliation_job_trigger: DF.Int + automatically_fetch_payment_terms: DF.Check + automatically_process_deferred_accounting_entry: DF.Check + book_asset_depreciation_entry_automatically: DF.Check + book_deferred_entries_based_on: DF.Literal["Days", "Months"] + book_deferred_entries_via_journal_entry: DF.Check + book_tax_discount_loss: DF.Check + calculate_depr_using_total_days: DF.Check + check_supplier_invoice_uniqueness: DF.Check + create_pr_in_draft_status: DF.Check + credit_controller: DF.Link | None + delete_linked_ledger_entries: DF.Check + determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] + enable_common_party_accounting: DF.Check + enable_fuzzy_matching: DF.Check + enable_immutable_ledger: DF.Check + enable_party_matching: DF.Check + exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment"] + frozen_accounts_modifier: DF.Link | None + general_ledger_remarks_length: DF.Int + ignore_account_closing_balance: DF.Check + ignore_is_opening_check_for_reporting: DF.Check + make_payment_via_journal_entry: DF.Check + merge_similar_account_heads: DF.Check + over_billing_allowance: DF.Currency + post_change_gl_entries: DF.Check + receivable_payable_remarks_length: DF.Int + reconciliation_queue_size: DF.Int + role_allowed_to_over_bill: DF.Link | None + round_row_wise_tax: DF.Check + show_balance_in_coa: DF.Check + show_inclusive_tax_in_print: DF.Check + show_payment_schedule_in_print: DF.Check + show_taxes_as_table_in_print: DF.Check + stale_days: DF.Int + submit_journal_entries: DF.Check + unlink_advance_payment_on_cancelation_of_order: DF.Check + unlink_payment_on_cancellation_of_invoice: DF.Check + # end: auto-generated types + +>>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e452d729ccf..aa629ac7dcf 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -354,6 +354,9 @@ class PaymentReconciliation(Document): def allocate_entries(self, args): self.validate_entries() + exc_gain_loss_posting_date = frappe.db.get_single_value( + "Accounts Settings", "exchange_gain_loss_posting_date", cache=True + ) invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments")) default_exchange_gain_loss_account = frappe.get_cached_value( "Company", self.company, "exchange_gain_loss_account" @@ -380,6 +383,8 @@ class PaymentReconciliation(Document): res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") res.update({"gain_loss_posting_date": pay.get("posting_date")}) + if exc_gain_loss_posting_date == "Invoice": + res.update({"gain_loss_posting_date": inv.get("invoice_date")}) if pay.get("amount") == 0: entries.append(res) From 035139d4c79963468bc09985ed2c6990046527f3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 19 Dec 2024 13:40:03 +0530 Subject: [PATCH 2/7] refactor: configurable posting date for Exc Gain / Loss journal (cherry picked from commit 5257413a932f4a9eb1331d5fca6110390531fadf) --- .../accounts/doctype/accounts_settings/accounts_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index cb93720589d..ddee7670ea1 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -390,7 +390,7 @@ { "fieldname": "section_break_jpd0", "fieldtype": "Section Break", - "label": "Payment Reconciliations" + "label": "Payment Reconciliation Settings" }, { "default": "0", From 5a62bd6e85273ad9dad9175a77b1d4c57166ef2e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 13:48:09 +0530 Subject: [PATCH 3/7] refactor: allow reconciliation date for exchange gain / loss (cherry picked from commit 95af63e305c51a19bfea43c7f9580c68fb93433e) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++++++- .../doctype/accounts_settings/accounts_settings.py | 2 +- .../payment_reconciliation/payment_reconciliation.py | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index ddee7670ea1..b1393d5de9f 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -537,8 +537,12 @@ "fieldname": "exchange_gain_loss_posting_date", "fieldtype": "Select", "label": "Posting Date Inheritance for Exchange Gain / Loss", +<<<<<<< HEAD "options": "Invoice\nPayment" >>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) +======= + "options": "Invoice\nPayment\nReconciliation Date" +>>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) } ], "icon": "icon-cog", @@ -546,11 +550,15 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2024-01-22 12:10:10.151819", ======= "modified": "2025-01-22 17:53:47.968079", >>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) +======= + "modified": "2025-01-23 13:15:44.077853", +>>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -579,4 +587,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 94361f23b8c..ca3b18671a6 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -46,7 +46,7 @@ class AccountsSettings(Document): enable_fuzzy_matching: DF.Check enable_immutable_ledger: DF.Check enable_party_matching: DF.Check - exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment"] + exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"] frozen_accounts_modifier: DF.Link | None general_ledger_remarks_length: DF.Int ignore_account_closing_balance: DF.Check diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index aa629ac7dcf..e9ed8c4a34b 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -385,6 +385,8 @@ class PaymentReconciliation(Document): res.update({"gain_loss_posting_date": pay.get("posting_date")}) if exc_gain_loss_posting_date == "Invoice": res.update({"gain_loss_posting_date": inv.get("invoice_date")}) + elif exc_gain_loss_posting_date == "Reconciliation Date": + res.update({"gain_loss_posting_date": nowdate()}) if pay.get("amount") == 0: entries.append(res) From ef6e26488732663c13bfd4b590c2d0bef6ad474d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 14:10:15 +0530 Subject: [PATCH 4/7] refactor: only apply configuration on normal payments patch to update default value (cherry picked from commit b2c3da135ea85fe245ec9c6066a8e68b42b64f7f) # Conflicts: # erpnext/controllers/accounts_controller.py # erpnext/patches.txt --- .../payment_reconciliation.py | 10 ++- erpnext/controllers/accounts_controller.py | 82 +++++++++++++++++++ erpnext/patches.txt | 11 +++ 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e9ed8c4a34b..3aee5c89c9d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -270,6 +270,7 @@ class PaymentReconciliation(Document): for payment in non_reconciled_payments: row = self.append("payments", {}) row.update(payment) + row.is_advance = payment.book_advance_payments_in_separate_party_account def get_invoice_entries(self): # Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against @@ -383,10 +384,11 @@ class PaymentReconciliation(Document): res.difference_account = default_exchange_gain_loss_account res.exchange_rate = inv.get("exchange_rate") res.update({"gain_loss_posting_date": pay.get("posting_date")}) - if exc_gain_loss_posting_date == "Invoice": - res.update({"gain_loss_posting_date": inv.get("invoice_date")}) - elif exc_gain_loss_posting_date == "Reconciliation Date": - res.update({"gain_loss_posting_date": nowdate()}) + if not pay.get("is_advance"): + if exc_gain_loss_posting_date == "Invoice": + res.update({"gain_loss_posting_date": inv.get("invoice_date")}) + elif exc_gain_loss_posting_date == "Reconciliation Date": + res.update({"gain_loss_posting_date": nowdate()}) if pay.get("amount") == 0: entries.append(res) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 160c478f299..07f6ae62746 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2767,6 +2767,7 @@ def get_advance_payment_entries( condition.append(pe.name.like(f"%%{payment_name}%%")) if order_list or against_all_orders: +<<<<<<< HEAD orders_condition = [] if order_list: orders_condition.append(per.reference_name.isin(order_list)) @@ -2784,6 +2785,87 @@ def get_advance_payment_entries( pe.posting_date, pe[currency_field].as_("currency"), pe[exchange_rate_field].as_("exchange_rate"), +======= + q = get_common_query( + party_type, + party, + party_account, + default_advance_account, + limit, + condition, + ) + payment_ref = frappe.qb.DocType("Payment Entry Reference") + + q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) + q = q.select( + (payment_ref.allocated_amount).as_("amount"), + (payment_ref.name).as_("reference_row"), + (payment_ref.reference_name).as_("against_order"), + (payment_entry.book_advance_payments_in_separate_party_account), + ) + + q = q.where(payment_ref.reference_doctype == order_doctype) + if order_list: + q = q.where(payment_ref.reference_name.isin(order_list)) + + allocated = list(q.run(as_dict=True)) + payment_entries += allocated + if include_unallocated: + q = get_common_query( + party_type, + party, + party_account, + default_advance_account, + limit, + condition, + ) + q = q.select((payment_entry.unallocated_amount).as_("amount")) + q = q.where(payment_entry.unallocated_amount > 0) + + unallocated = list(q.run(as_dict=True)) + payment_entries += unallocated + return payment_entries + + +def get_common_query( + party_type, + party, + party_account, + default_advance_account, + limit, + condition, +): + account_type = frappe.db.get_value("Party Type", party_type, "account_type") + payment_type = "Receive" if account_type == "Receivable" else "Pay" + payment_entry = frappe.qb.DocType("Payment Entry") + + q = ( + frappe.qb.from_(payment_entry) + .select( + ConstantColumn("Payment Entry").as_("reference_type"), + (payment_entry.name).as_("reference_name"), + payment_entry.posting_date, + (payment_entry.remarks).as_("remarks"), + (payment_entry.book_advance_payments_in_separate_party_account), + ) + .where(payment_entry.payment_type == payment_type) + .where(payment_entry.party_type == party_type) + .where(payment_entry.party == party) + .where(payment_entry.docstatus == 1) + ) + + field = "paid_from" if payment_type == "Receive" else "paid_to" + + q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency")) + q = q.select(payment_entry[field]) + account_condition = payment_entry[field].isin(party_account) + if default_advance_account: + q = q.where( + account_condition + | ( + (payment_entry[field] == default_advance_account) + & (payment_entry.book_advance_payments_in_separate_party_account == 1) +>>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) ) .where( (pe[party_account_field] == party_account) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 46fccd1bb6f..93e49b023b1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,5 +368,16 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v14_0.update_stock_uom_in_work_order_item +<<<<<<< HEAD erpnext.patches.v14_0.disable_add_row_in_gross_profit +======= +erpnext.patches.v15_0.enable_allow_existing_serial_no +erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts +erpnext.patches.v15_0.refactor_closing_stock_balance #5 +erpnext.patches.v15_0.update_asset_status_to_work_in_progress +erpnext.patches.v15_0.rename_manufacturing_settings_field +erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect +erpnext.patches.v15_0.sync_auto_reconcile_config +execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") +>>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) From 620cdc24891932b1e4d2fd7da3031c51c46e66a6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 14:44:40 +0530 Subject: [PATCH 5/7] test: exc gain/loss posting date based on configuration (cherry picked from commit 2f3281579a5b4393ff336e7e2ff274d0b60c5d66) # Conflicts: # erpnext/controllers/tests/test_accounts_controller.py --- .../tests/test_accounts_controller.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 1e0f4cce27b..bd32cbafccf 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -9,6 +9,7 @@ from frappe import qb from frappe.query_builder.functions import Sum from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate +from frappe.utils.data import getdate as convert_to_date from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry @@ -707,6 +708,135 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) +<<<<<<< HEAD +======= + @IntegrationTestCase.change_settings( + "Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1} + ) + def test_16_internal_transfer_at_arms_length_price(self): + from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + prepare_data_for_internal_transfer() + company = "_Test Company with perpetual inventory" + target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) + warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company) + arms_length_price = 40 + + si = create_sales_invoice( + company=company, + customer="_Test Internal Customer 2", + debit_to="Debtors - TCP1", + target_warehouse=target_warehouse, + warehouse=warehouse, + income_account="Sales - TCP1", + expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", + update_stock=True, + do_not_save=True, + do_not_submit=True, + ) + + si.items[0].rate = arms_length_price + si.save() + # rate should not reset to incoming rate + self.assertEqual(si.items[0].rate, arms_length_price) + + frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0) + si.items[0].rate = arms_length_price + si.save() + # rate should reset to incoming rate + self.assertEqual(si.items[0].rate, 100) + + si.update_stock = 0 + si.save() + si.submit() + + pi = make_inter_company_purchase_invoice(si.name) + pi.update_stock = 1 + pi.items[0].rate = arms_length_price + pi.items[0].warehouse = target_warehouse + pi.items[0].from_warehouse = warehouse + pi.save() + + self.assertEqual(pi.items[0].rate, 100) + self.assertEqual(pi.items[0].valuation_rate, 100) + + frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 1) + pi = make_inter_company_purchase_invoice(si.name) + pi.update_stock = 1 + pi.items[0].rate = arms_length_price + pi.items[0].warehouse = target_warehouse + pi.items[0].from_warehouse = warehouse + pi.save() + + self.assertEqual(pi.items[0].rate, arms_length_price) + self.assertEqual(pi.items[0].valuation_rate, 100) + + @IntegrationTestCase.change_settings( + "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} + ) + def test_17_gain_loss_posting_date_for_normal_payment(self): + # Sales Invoice in Foreign Currency + rate = 80 + rate_in_account_currency = 1 + + adv_date = convert_to_date(add_days(nowdate(), -2)) + inv_date = convert_to_date(add_days(nowdate(), -1)) + + si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=rate_in_account_currency) + + # Test payments with different exchange rates + pe = self.create_payment_entry(posting_date=adv_date, amount=1, source_exc_rate=75.1).save().submit() + + pr = self.create_payment_reconciliation() + pr.from_invoice_date = add_days(nowdate(), -1) + pr.to_invoice_date = nowdate() + pr.from_payment_date = add_days(nowdate(), -2) + pr.to_payment_date = nowdate() + + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 1) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # Outstanding in both currencies should be '0' + si.reload() + self.assertEqual(si.outstanding_amount, 0) + self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0) + + # Exchange Gain/Loss Journal should've been created. + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertNotEqual(exc_je_for_si, []) + self.assertEqual(len(exc_je_for_si), 1) + self.assertEqual(len(exc_je_for_pe), 1) + self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0]) + + self.assertEqual( + getdate(nowdate()), frappe.db.get_value("Journal Entry", exc_je_for_pe[0].parent, "posting_date") + ) + # Cancel Payment + pe.reload() + pe.cancel() + + # outstanding should be same as grand total + si.reload() + self.assertEqual(si.outstanding_amount, rate_in_account_currency) + self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_si = self.get_journals_for(si.doctype, si.name) + exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name) + self.assertEqual(exc_je_for_si, []) + self.assertEqual(exc_je_for_pe, []) + +>>>>>>> 2f3281579a (test: exc gain/loss posting date based on configuration) def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) From cc275318e33600cddfe1cc08119838d7345ececc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Jan 2025 16:09:54 +0530 Subject: [PATCH 6/7] refactor: support JE posting date in semi-auto reconciilation tool (cherry picked from commit a71718883e933c7eadc15842cae2dd59c6b1d005) # Conflicts: # erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json # erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py --- ...ayment_reconciliation_log_allocations.json | 10 ++++++ ..._payment_reconciliation_log_allocations.py | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json index b97d73886a9..5c771c54f52 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -20,6 +20,7 @@ "is_advance", "section_break_5", "difference_amount", + "gain_loss_posting_date", "column_break_7", "difference_account", "exchange_rate", @@ -153,11 +154,20 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Reconciled" + }, + { + "fieldname": "gain_loss_posting_date", + "fieldtype": "Date", + "label": "Difference Posting Date" } ], "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-03-20 21:05:43.121945", +======= + "modified": "2025-01-23 16:09:01.058574", +>>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log Allocations", diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py index c3e43297d08..265ba4ff664 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py @@ -6,4 +6,35 @@ from frappe.model.document import Document class ProcessPaymentReconciliationLogAllocations(Document): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + allocated_amount: DF.Currency + amount: DF.Currency + currency: DF.Link | None + difference_account: DF.Link | None + difference_amount: DF.Currency + exchange_rate: DF.Float + gain_loss_posting_date: DF.Date | None + invoice_number: DF.DynamicLink + invoice_type: DF.Link + is_advance: DF.Data | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + reconciled: DF.Check + reference_name: DF.DynamicLink + reference_row: DF.Data | None + reference_type: DF.Link + unreconciled_amount: DF.Currency + # end: auto-generated types + +>>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) pass From e9d934d378cb63d9416cf4578cad3675fc68489c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Feb 2025 13:22:24 +0530 Subject: [PATCH 7/7] chore: resolve conflicts --- .../accounts_settings/accounts_settings.json | 80 ------------------ .../accounts_settings/accounts_settings.py | 56 ------------- ...ayment_reconciliation_log_allocations.json | 4 - ..._payment_reconciliation_log_allocations.py | 31 ------- erpnext/controllers/accounts_controller.py | 82 ------------------- .../tests/test_accounts_controller.py | 72 +--------------- erpnext/patches.txt | 11 --- 7 files changed, 2 insertions(+), 334 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index b1393d5de9f..16c5479c210 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -43,14 +43,8 @@ "allow_stale", "section_break_jpd0", "auto_reconcile_payments", -<<<<<<< HEAD "stale_days", -======= - "auto_reconciliation_job_trigger", - "reconciliation_queue_size", - "column_break_resa", "exchange_gain_loss_posting_date", ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) "invoicing_settings_tab", "accounts_transactions_settings_section", "over_billing_allowance", @@ -469,67 +463,6 @@ "fieldname": "remarks_section", "fieldtype": "Section Break", "label": "Remarks Column Length" -<<<<<<< HEAD -======= - }, - { - "default": "0", - "description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well", - "fieldname": "enable_immutable_ledger", - "fieldtype": "Check", - "label": "Enable Immutable Ledger" - }, - { - "fieldname": "column_break_gjcc", - "fieldtype": "Column Break" - }, - { - "default": "0", - "description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation", - "fieldname": "calculate_depr_using_total_days", - "fieldtype": "Check", - "label": "Calculate daily depreciation using total days in depreciation period" - }, - { - "description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.", - "fieldname": "payment_request_settings", - "fieldtype": "Tab Break", - "label": "Payment Request" - }, - { - "default": "1", - "fieldname": "create_pr_in_draft_status", - "fieldtype": "Check", - "label": "Create in Draft Status" - }, - { - "fieldname": "column_break_yuug", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_resa", - "fieldtype": "Column Break" - }, - { - "default": "15", - "description": "Interval should be between 1 to 59 MInutes", - "fieldname": "auto_reconciliation_job_trigger", - "fieldtype": "Int", - "label": "Auto Reconciliation Job Trigger" - }, - { - "default": "5", - "description": "Documents Processed on each trigger. Queue Size should be between 5 and 100", - "fieldname": "reconciliation_queue_size", - "fieldtype": "Int", - "label": "Reconciliation Queue Size" - }, - { - "default": "0", - "description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports", - "fieldname": "ignore_is_opening_check_for_reporting", - "fieldtype": "Check", - "label": "Ignore Is Opening check for reporting" }, { "default": "Payment", @@ -537,12 +470,7 @@ "fieldname": "exchange_gain_loss_posting_date", "fieldtype": "Select", "label": "Posting Date Inheritance for Exchange Gain / Loss", -<<<<<<< HEAD - "options": "Invoice\nPayment" ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) -======= "options": "Invoice\nPayment\nReconciliation Date" ->>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) } ], "icon": "icon-cog", @@ -550,15 +478,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2024-01-22 12:10:10.151819", -======= - "modified": "2025-01-22 17:53:47.968079", ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) -======= "modified": "2025-01-23 13:15:44.077853", ->>>>>>> 95af63e305 (refactor: allow reconciliation date for exchange gain / loss) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index ca3b18671a6..ac3d44bb5e7 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -14,62 +14,6 @@ from erpnext.stock.utils import check_pending_reposting class AccountsSettings(Document): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - acc_frozen_upto: DF.Date | None - add_taxes_from_item_tax_template: DF.Check - allow_multi_currency_invoices_against_single_party_account: DF.Check - allow_stale: DF.Check - auto_reconcile_payments: DF.Check - auto_reconciliation_job_trigger: DF.Int - automatically_fetch_payment_terms: DF.Check - automatically_process_deferred_accounting_entry: DF.Check - book_asset_depreciation_entry_automatically: DF.Check - book_deferred_entries_based_on: DF.Literal["Days", "Months"] - book_deferred_entries_via_journal_entry: DF.Check - book_tax_discount_loss: DF.Check - calculate_depr_using_total_days: DF.Check - check_supplier_invoice_uniqueness: DF.Check - create_pr_in_draft_status: DF.Check - credit_controller: DF.Link | None - delete_linked_ledger_entries: DF.Check - determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] - enable_common_party_accounting: DF.Check - enable_fuzzy_matching: DF.Check - enable_immutable_ledger: DF.Check - enable_party_matching: DF.Check - exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"] - frozen_accounts_modifier: DF.Link | None - general_ledger_remarks_length: DF.Int - ignore_account_closing_balance: DF.Check - ignore_is_opening_check_for_reporting: DF.Check - make_payment_via_journal_entry: DF.Check - merge_similar_account_heads: DF.Check - over_billing_allowance: DF.Currency - post_change_gl_entries: DF.Check - receivable_payable_remarks_length: DF.Int - reconciliation_queue_size: DF.Int - role_allowed_to_over_bill: DF.Link | None - round_row_wise_tax: DF.Check - show_balance_in_coa: DF.Check - show_inclusive_tax_in_print: DF.Check - show_payment_schedule_in_print: DF.Check - show_taxes_as_table_in_print: DF.Check - stale_days: DF.Int - submit_journal_entries: DF.Check - unlink_advance_payment_on_cancelation_of_order: DF.Check - unlink_payment_on_cancellation_of_invoice: DF.Check - # end: auto-generated types - ->>>>>>> 3fbd2ca0d9 (refactor: configurable posting date for Exc Gain / Loss journal) def validate(self): old_doc = self.get_doc_before_save() clear_cache = False diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json index 5c771c54f52..d7ea1c2ca68 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json @@ -163,11 +163,7 @@ ], "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-03-20 21:05:43.121945", -======= "modified": "2025-01-23 16:09:01.058574", ->>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) "modified_by": "Administrator", "module": "Accounts", "name": "Process Payment Reconciliation Log Allocations", diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py index 265ba4ff664..c3e43297d08 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.py @@ -6,35 +6,4 @@ from frappe.model.document import Document class ProcessPaymentReconciliationLogAllocations(Document): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - allocated_amount: DF.Currency - amount: DF.Currency - currency: DF.Link | None - difference_account: DF.Link | None - difference_amount: DF.Currency - exchange_rate: DF.Float - gain_loss_posting_date: DF.Date | None - invoice_number: DF.DynamicLink - invoice_type: DF.Link - is_advance: DF.Data | None - parent: DF.Data - parentfield: DF.Data - parenttype: DF.Data - reconciled: DF.Check - reference_name: DF.DynamicLink - reference_row: DF.Data | None - reference_type: DF.Link - unreconciled_amount: DF.Currency - # end: auto-generated types - ->>>>>>> a71718883e (refactor: support JE posting date in semi-auto reconciilation tool) pass diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 07f6ae62746..160c478f299 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2767,7 +2767,6 @@ def get_advance_payment_entries( condition.append(pe.name.like(f"%%{payment_name}%%")) if order_list or against_all_orders: -<<<<<<< HEAD orders_condition = [] if order_list: orders_condition.append(per.reference_name.isin(order_list)) @@ -2785,87 +2784,6 @@ def get_advance_payment_entries( pe.posting_date, pe[currency_field].as_("currency"), pe[exchange_rate_field].as_("exchange_rate"), -======= - q = get_common_query( - party_type, - party, - party_account, - default_advance_account, - limit, - condition, - ) - payment_ref = frappe.qb.DocType("Payment Entry Reference") - - q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent) - q = q.select( - (payment_ref.allocated_amount).as_("amount"), - (payment_ref.name).as_("reference_row"), - (payment_ref.reference_name).as_("against_order"), - (payment_entry.book_advance_payments_in_separate_party_account), - ) - - q = q.where(payment_ref.reference_doctype == order_doctype) - if order_list: - q = q.where(payment_ref.reference_name.isin(order_list)) - - allocated = list(q.run(as_dict=True)) - payment_entries += allocated - if include_unallocated: - q = get_common_query( - party_type, - party, - party_account, - default_advance_account, - limit, - condition, - ) - q = q.select((payment_entry.unallocated_amount).as_("amount")) - q = q.where(payment_entry.unallocated_amount > 0) - - unallocated = list(q.run(as_dict=True)) - payment_entries += unallocated - return payment_entries - - -def get_common_query( - party_type, - party, - party_account, - default_advance_account, - limit, - condition, -): - account_type = frappe.db.get_value("Party Type", party_type, "account_type") - payment_type = "Receive" if account_type == "Receivable" else "Pay" - payment_entry = frappe.qb.DocType("Payment Entry") - - q = ( - frappe.qb.from_(payment_entry) - .select( - ConstantColumn("Payment Entry").as_("reference_type"), - (payment_entry.name).as_("reference_name"), - payment_entry.posting_date, - (payment_entry.remarks).as_("remarks"), - (payment_entry.book_advance_payments_in_separate_party_account), - ) - .where(payment_entry.payment_type == payment_type) - .where(payment_entry.party_type == party_type) - .where(payment_entry.party == party) - .where(payment_entry.docstatus == 1) - ) - - field = "paid_from" if payment_type == "Receive" else "paid_to" - - q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency")) - q = q.select(payment_entry[field]) - account_condition = payment_entry[field].isin(party_account) - if default_advance_account: - q = q.where( - account_condition - | ( - (payment_entry[field] == default_advance_account) - & (payment_entry.book_advance_payments_in_separate_party_account == 1) ->>>>>>> b2c3da135e (refactor: only apply configuration on normal payments) ) .where( (pe[party_account_field] == party_account) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index bd32cbafccf..7470f4eda25 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -7,7 +7,7 @@ from datetime import datetime import frappe from frappe import qb from frappe.query_builder.functions import Sum -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, getdate, nowdate from frappe.utils.data import getdate as convert_to_date @@ -708,74 +708,7 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) -<<<<<<< HEAD -======= - @IntegrationTestCase.change_settings( - "Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1} - ) - def test_16_internal_transfer_at_arms_length_price(self): - from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice - from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - - prepare_data_for_internal_transfer() - company = "_Test Company with perpetual inventory" - target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company) - warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company) - arms_length_price = 40 - - si = create_sales_invoice( - company=company, - customer="_Test Internal Customer 2", - debit_to="Debtors - TCP1", - target_warehouse=target_warehouse, - warehouse=warehouse, - income_account="Sales - TCP1", - expense_account="Cost of Goods Sold - TCP1", - cost_center="Main - TCP1", - update_stock=True, - do_not_save=True, - do_not_submit=True, - ) - - si.items[0].rate = arms_length_price - si.save() - # rate should not reset to incoming rate - self.assertEqual(si.items[0].rate, arms_length_price) - - frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0) - si.items[0].rate = arms_length_price - si.save() - # rate should reset to incoming rate - self.assertEqual(si.items[0].rate, 100) - - si.update_stock = 0 - si.save() - si.submit() - - pi = make_inter_company_purchase_invoice(si.name) - pi.update_stock = 1 - pi.items[0].rate = arms_length_price - pi.items[0].warehouse = target_warehouse - pi.items[0].from_warehouse = warehouse - pi.save() - - self.assertEqual(pi.items[0].rate, 100) - self.assertEqual(pi.items[0].valuation_rate, 100) - - frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 1) - pi = make_inter_company_purchase_invoice(si.name) - pi.update_stock = 1 - pi.items[0].rate = arms_length_price - pi.items[0].warehouse = target_warehouse - pi.items[0].from_warehouse = warehouse - pi.save() - - self.assertEqual(pi.items[0].rate, arms_length_price) - self.assertEqual(pi.items[0].valuation_rate, 100) - - @IntegrationTestCase.change_settings( - "Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"} - ) + @change_settings("Accounts Settings", {"exchange_gain_loss_posting_date": "Reconciliation Date"}) def test_17_gain_loss_posting_date_for_normal_payment(self): # Sales Invoice in Foreign Currency rate = 80 @@ -836,7 +769,6 @@ class TestAccountsController(FrappeTestCase): self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_pe, []) ->>>>>>> 2f3281579a (test: exc gain/loss posting date based on configuration) def test_20_journal_against_sales_invoice(self): # Invoice in Foreign Currency si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 93e49b023b1..94e48ef135d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,16 +368,5 @@ erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v14_0.update_stock_uom_in_work_order_item -<<<<<<< HEAD erpnext.patches.v14_0.disable_add_row_in_gross_profit - -======= -erpnext.patches.v15_0.enable_allow_existing_serial_no -erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts -erpnext.patches.v15_0.refactor_closing_stock_balance #5 -erpnext.patches.v15_0.update_asset_status_to_work_in_progress -erpnext.patches.v15_0.rename_manufacturing_settings_field -erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect -erpnext.patches.v15_0.sync_auto_reconcile_config execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") ->>>>>>> b2c3da135e (refactor: only apply configuration on normal payments)