diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 69debbec5c7..5f191e4800a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -21,6 +21,7 @@
"party_name",
"book_advance_payments_in_separate_party_account",
"reconcile_on_advance_payment_date",
+ "advance_reconciliation_takes_effect_on",
"column_break_11",
"bank_account",
"party_bank_account",
@@ -782,6 +783,16 @@
"options": "No\nYes",
"print_hide": 1,
"search_index": 1
+ },
+ {
+ "default": "Oldest Of Invoice Or Advance",
+ "fetch_from": "company.reconciliation_takes_effect_on",
+ "fieldname": "advance_reconciliation_takes_effect_on",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Advance Reconciliation Takes Effect On",
+ "no_copy": 1,
+ "options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date"
}
],
"index_web_pages_for_search": 1,
@@ -795,7 +806,7 @@
"table_fieldname": "payment_entries"
}
],
- "modified": "2024-11-07 11:19:19.320883",
+ "modified": "2025-01-13 16:03:47.169699",
"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 7e3d8a5833b..d714df0927b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1401,16 +1401,26 @@ class PaymentEntry(AccountsController):
"voucher_detail_no": invoice.name,
}
- if self.reconcile_on_advance_payment_date:
- posting_date = self.posting_date
+ if invoice.reconcile_effect_on:
+ posting_date = invoice.reconcile_effect_on
else:
- date_field = "posting_date"
- if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
- date_field = "transaction_date"
- posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
-
- if getdate(posting_date) < getdate(self.posting_date):
+ # For backwards compatibility
+ # Supporting reposting on payment entries reconciled before select field introduction
+ if self.advance_reconciliation_takes_effect_on == "Advance Payment Date":
posting_date = self.posting_date
+ elif self.advance_reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
+ date_field = "posting_date"
+ if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
+ date_field = "transaction_date"
+ posting_date = frappe.db.get_value(
+ invoice.reference_doctype, invoice.reference_name, date_field
+ )
+
+ if getdate(posting_date) < getdate(self.posting_date):
+ posting_date = self.posting_date
+ elif self.advance_reconciliation_takes_effect_on == "Reconciliation Date":
+ posting_date = nowdate()
+ frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
args_dict["account"] = account
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 361f516b830..57e401275fd 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -13,6 +13,7 @@
"payment_term_outstanding",
"account_type",
"payment_type",
+ "reconcile_effect_on",
"column_break_4",
"total_amount",
"outstanding_amount",
@@ -144,12 +145,18 @@
"is_virtual": 1,
"label": "Payment Request Outstanding",
"read_only": 1
+ },
+ {
+ "fieldname": "reconcile_effect_on",
+ "fieldtype": "Date",
+ "label": "Reconcile Effect On",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-09-16 18:11:50.019343",
+ "modified": "2025-01-13 15:56:18.895082",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
index 2ac92ba4a84..1d869b92715 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.py
@@ -30,6 +30,7 @@ class PaymentEntryReference(Document):
payment_term: DF.Link | None
payment_term_outstanding: DF.Float
payment_type: DF.Data | None
+ reconcile_effect_on: DF.Date | None
reference_doctype: DF.Link
reference_name: DF.DynamicLink
total_amount: DF.Float
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 3f0fb29d671..061bbf556fc 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -6,6 +6,7 @@ import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
+from frappe.utils.data import getdate as convert_to_date
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -1671,7 +1672,7 @@ class TestPaymentReconciliation(FrappeTestCase):
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
- "reconcile_on_advance_payment_date": 1,
+ "reconciliation_takes_effect_on": "Advance Payment Date",
},
)
@@ -1720,7 +1721,7 @@ class TestPaymentReconciliation(FrappeTestCase):
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_received_account": self.advance_receivable_account,
- "reconcile_on_advance_payment_date": 0,
+ "reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
amount = 200.0
@@ -1829,7 +1830,7 @@ class TestPaymentReconciliation(FrappeTestCase):
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
- "reconcile_on_advance_payment_date": 0,
+ "reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
amount = 200.0
@@ -2048,6 +2049,102 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(pr.get("invoices"), [])
self.assertEqual(pr.get("payments"), [])
+ def test_advance_reconciliation_effect_on_same_date(self):
+ frappe.db.set_value(
+ "Company",
+ self.company,
+ {
+ "book_advance_payments_in_separate_party_account": 1,
+ "default_advance_received_account": self.advance_receivable_account,
+ "reconciliation_takes_effect_on": "Reconciliation Date",
+ },
+ )
+ inv_date = convert_to_date(add_days(nowdate(), -1))
+ adv_date = convert_to_date(add_days(nowdate(), -2))
+
+ si = self.create_sales_invoice(posting_date=inv_date, qty=1, rate=200)
+ pe = self.create_payment_entry(posting_date=adv_date, amount=80).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.default_advance_account = self.advance_receivable_account
+
+ # reconcile multiple payments against invoice
+ pr.get_unreconciled_entries()
+ invoices = [x.as_dict() for x in pr.get("invoices")]
+ payments = [x.as_dict() for x in pr.get("payments")]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+ # Difference amount should not be calculated for base currency accounts
+ for row in pr.allocation:
+ self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
+ pr.reconcile()
+
+ si.reload()
+ self.assertEqual(si.status, "Partly Paid")
+ # check PR tool output post reconciliation
+ self.assertEqual(len(pr.get("invoices")), 1)
+ self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 120)
+ self.assertEqual(pr.get("payments"), [])
+
+ # Assert Ledger Entries
+ gl_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={"voucher_no": pe.name},
+ fields=["account", "posting_date", "voucher_no", "against_voucher", "debit", "credit"],
+ order_by="account, against_voucher, debit",
+ )
+
+ expected_gl = [
+ {
+ "account": self.advance_receivable_account,
+ "posting_date": adv_date,
+ "voucher_no": pe.name,
+ "against_voucher": pe.name,
+ "debit": 0.0,
+ "credit": 80.0,
+ },
+ {
+ "account": self.advance_receivable_account,
+ "posting_date": convert_to_date(nowdate()),
+ "voucher_no": pe.name,
+ "against_voucher": pe.name,
+ "debit": 80.0,
+ "credit": 0.0,
+ },
+ {
+ "account": self.debit_to,
+ "posting_date": convert_to_date(nowdate()),
+ "voucher_no": pe.name,
+ "against_voucher": si.name,
+ "debit": 0.0,
+ "credit": 80.0,
+ },
+ {
+ "account": self.bank,
+ "posting_date": adv_date,
+ "voucher_no": pe.name,
+ "against_voucher": None,
+ "debit": 80.0,
+ "credit": 0.0,
+ },
+ ]
+
+ self.assertEqual(expected_gl, gl_entries)
+
+ # cancel PE
+ pe.reload()
+ pe.cancel()
+ pr.get_unreconciled_entries()
+ # check PR tool output
+ self.assertEqual(len(pr.get("invoices")), 1)
+ self.assertEqual(len(pr.get("payments")), 0)
+ self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
+
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 51b4ed248ce..ae90819b409 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -712,6 +712,23 @@ def update_reference_in_payment_entry(
}
update_advance_paid = []
+ # Update Reconciliation effect date in reference
+ if payment_entry.book_advance_payments_in_separate_party_account:
+ if payment_entry.advance_reconciliation_takes_effect_on == "Advance Payment Date":
+ reconcile_on = payment_entry.posting_date
+ elif payment_entry.advance_reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
+ date_field = "posting_date"
+ if d.against_voucher_type in ["Sales Order", "Purchase Order"]:
+ date_field = "transaction_date"
+ reconcile_on = frappe.db.get_value(d.against_voucher_type, d.against_voucher, date_field)
+
+ if getdate(reconcile_on) < getdate(payment_entry.posting_date):
+ reconcile_on = payment_entry.posting_date
+ elif payment_entry.advance_reconciliation_takes_effect_on == "Reconciliation Date":
+ reconcile_on = nowdate()
+
+ reference_details.update({"reconcile_effect_on": reconcile_on})
+
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index cfc8be90b4b..a16f87de0dc 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -387,3 +387,4 @@ erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions
erpnext.patches.v15_0.enable_allow_existing_serial_no
erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts
erpnext.patches.v15_0.update_asset_status_to_work_in_progress
+erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
diff --git a/erpnext/patches/v15_0/migrate_checkbox_to_select_for_reconciliation_effect.py b/erpnext/patches/v15_0/migrate_checkbox_to_select_for_reconciliation_effect.py
new file mode 100644
index 00000000000..883921cfdf8
--- /dev/null
+++ b/erpnext/patches/v15_0/migrate_checkbox_to_select_for_reconciliation_effect.py
@@ -0,0 +1,18 @@
+import frappe
+
+
+def execute():
+ """
+ A New select field 'reconciliation_takes_effect_on' has been added to control Advance Payment Reconciliation dates.
+ Migrate old checkbox configuration to new select field on 'Company' and 'Payment Entry'
+ """
+ companies = frappe.db.get_all("Company", fields=["name", "reconciliation_takes_effect_on"])
+ for x in companies:
+ new_value = (
+ "Advance Payment Date" if x.reconcile_on_advance_payment_date else "Oldest Of Invoice Or Advance"
+ )
+ frappe.db.set_value("Company", x.name, "reconciliation_takes_effect_on", new_value)
+
+ frappe.db.sql(
+ """update `tabPayment Entry` set advance_reconciliation_takes_effect_on = if(reconcile_on_advance_payment_date = 0, 'Oldest Of Invoice Or Advance', 'Advance Payment Date')"""
+ )
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 271b440fbda..ae473e15917 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -75,6 +75,7 @@
"advance_payments_section",
"book_advance_payments_in_separate_party_account",
"reconcile_on_advance_payment_date",
+ "reconciliation_takes_effect_on",
"column_break_fwcf",
"default_advance_received_account",
"default_advance_paid_account",
@@ -796,6 +797,7 @@
"description": "If Enabled - Reconciliation happens on the Advance Payment posting date
\nIf Disabled - Reconciliation happens on oldest of 2 Dates: Invoice Date or the Advance Payment posting date
\n",
"fieldname": "reconcile_on_advance_payment_date",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Reconcile on Advance Payment Date"
},
{
@@ -841,6 +843,13 @@
{
"fieldname": "column_break_dcdl",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "Oldest Of Invoice Or Advance",
+ "fieldname": "reconciliation_takes_effect_on",
+ "fieldtype": "Select",
+ "label": "Reconciliation Takes Effect On",
+ "options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date"
}
],
"icon": "fa fa-building",
@@ -848,7 +857,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2024-12-02 15:37:32.723176",
+ "modified": "2025-01-09 20:12:25.471544",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 27005d99301..8ae843abc24 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -87,6 +87,9 @@ class Company(NestedSet):
payment_terms: DF.Link | None
phone_no: DF.Data | None
reconcile_on_advance_payment_date: DF.Check
+ reconciliation_takes_effect_on: DF.Literal[
+ "Advance Payment Date", "Oldest Of Invoice Or Advance", "Reconciliation Date"
+ ]
registration_details: DF.Code | None
rgt: DF.Int
round_off_account: DF.Link | None