diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 8703ab5b057..083171850c4 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",
@@ -783,6 +784,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,
@@ -796,7 +807,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 402d4539142..87576ec599d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -71,12 +71,16 @@ class PaymentEntry(AccountsController):
PaymentEntryReference,
)
+ advance_reconciliation_takes_effect_on: DF.Literal[
+ "Advance Payment Date", "Oldest Of Invoice Or Advance", "Reconciliation Date"
+ ]
amended_from: DF.Link | None
apply_tax_withholding_amount: DF.Check
auto_repeat: DF.Link | None
bank: DF.ReadOnly | None
bank_account: DF.Link | None
bank_account_no: DF.ReadOnly | None
+ base_in_words: DF.SmallText | None
base_paid_amount: DF.Currency
base_paid_amount_after_tax: DF.Currency
base_received_amount: DF.Currency
@@ -92,6 +96,8 @@ class PaymentEntry(AccountsController):
custom_remarks: DF.Check
deductions: DF.Table[PaymentEntryDeduction]
difference_amount: DF.Currency
+ in_words: DF.SmallText | None
+ is_opening: DF.Literal["No", "Yes"]
letter_head: DF.Link | None
mode_of_payment: DF.Link | None
naming_series: DF.Literal["ACC-PAY-.YYYY.-"]
@@ -119,6 +125,7 @@ class PaymentEntry(AccountsController):
purchase_taxes_and_charges_template: DF.Link | None
received_amount: DF.Currency
received_amount_after_tax: DF.Currency
+ reconcile_on_advance_payment_date: DF.Check
reference_date: DF.Date | None
reference_no: DF.Data | None
references: DF.Table[PaymentEntryReference]
@@ -1500,16 +1507,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 f5d39c134b5..fccbbb72f3c 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 3316a0ada00..438ec08bea5 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 import IntegrationTestCase, UnitTestCase
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
@@ -1680,7 +1681,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
{
"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",
},
)
@@ -1729,7 +1730,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
{
"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
@@ -1838,7 +1839,7 @@ class TestPaymentReconciliation(IntegrationTestCase):
{
"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
@@ -2057,6 +2058,102 @@ class TestPaymentReconciliation(IntegrationTestCase):
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 e088580aa48..c2283e05f13 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -730,6 +730,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 29739a4e01f..4c650ac05cf 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -394,4 +394,5 @@ erpnext.patches.v14_0.update_stock_uom_in_work_order_item
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
\ No newline at end of file
+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 074c67583c4..b10b1ae9ae3 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",
@@ -780,6 +781,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"
},
{
@@ -825,6 +827,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",
@@ -832,7 +841,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 7984e8f2a7e..c41f6106adb 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