mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-23 06:59:20 +00:00
Merge pull request #45265 from frappe/mergify/bp/version-15-hotfix/pr-45182
refactor: configurable reconciliation dates for Advance Payments (backport #45182)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
"party_name",
|
"party_name",
|
||||||
"book_advance_payments_in_separate_party_account",
|
"book_advance_payments_in_separate_party_account",
|
||||||
"reconcile_on_advance_payment_date",
|
"reconcile_on_advance_payment_date",
|
||||||
|
"advance_reconciliation_takes_effect_on",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
"bank_account",
|
"bank_account",
|
||||||
"party_bank_account",
|
"party_bank_account",
|
||||||
@@ -782,6 +783,16 @@
|
|||||||
"options": "No\nYes",
|
"options": "No\nYes",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"search_index": 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,
|
"index_web_pages_for_search": 1,
|
||||||
@@ -795,7 +806,7 @@
|
|||||||
"table_fieldname": "payment_entries"
|
"table_fieldname": "payment_entries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-11-07 11:19:19.320883",
|
"modified": "2025-01-13 16:03:47.169699",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -1401,16 +1401,26 @@ class PaymentEntry(AccountsController):
|
|||||||
"voucher_detail_no": invoice.name,
|
"voucher_detail_no": invoice.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.reconcile_on_advance_payment_date:
|
if invoice.reconcile_effect_on:
|
||||||
posting_date = self.posting_date
|
posting_date = invoice.reconcile_effect_on
|
||||||
else:
|
else:
|
||||||
date_field = "posting_date"
|
# For backwards compatibility
|
||||||
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
|
# Supporting reposting on payment entries reconciled before select field introduction
|
||||||
date_field = "transaction_date"
|
if self.advance_reconciliation_takes_effect_on == "Advance Payment 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
|
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)
|
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
|
||||||
args_dict["account"] = account
|
args_dict["account"] = account
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"payment_term_outstanding",
|
"payment_term_outstanding",
|
||||||
"account_type",
|
"account_type",
|
||||||
"payment_type",
|
"payment_type",
|
||||||
|
"reconcile_effect_on",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"total_amount",
|
"total_amount",
|
||||||
"outstanding_amount",
|
"outstanding_amount",
|
||||||
@@ -144,12 +145,18 @@
|
|||||||
"is_virtual": 1,
|
"is_virtual": 1,
|
||||||
"label": "Payment Request Outstanding",
|
"label": "Payment Request Outstanding",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reconcile_effect_on",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Reconcile Effect On",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-09-16 18:11:50.019343",
|
"modified": "2025-01-13 15:56:18.895082",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Reference",
|
"name": "Payment Entry Reference",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class PaymentEntryReference(Document):
|
|||||||
payment_term: DF.Link | None
|
payment_term: DF.Link | None
|
||||||
payment_term_outstanding: DF.Float
|
payment_term_outstanding: DF.Float
|
||||||
payment_type: DF.Data | None
|
payment_type: DF.Data | None
|
||||||
|
reconcile_effect_on: DF.Date | None
|
||||||
reference_doctype: DF.Link
|
reference_doctype: DF.Link
|
||||||
reference_name: DF.DynamicLink
|
reference_name: DF.DynamicLink
|
||||||
total_amount: DF.Float
|
total_amount: DF.Float
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import frappe
|
|||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
|
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 import get_default_cost_center
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
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,
|
"book_advance_payments_in_separate_party_account": 1,
|
||||||
"default_advance_paid_account": self.advance_payable_account,
|
"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,
|
"book_advance_payments_in_separate_party_account": 1,
|
||||||
"default_advance_received_account": self.advance_receivable_account,
|
"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
|
amount = 200.0
|
||||||
@@ -1829,7 +1830,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
{
|
{
|
||||||
"book_advance_payments_in_separate_party_account": 1,
|
"book_advance_payments_in_separate_party_account": 1,
|
||||||
"default_advance_paid_account": self.advance_payable_account,
|
"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
|
amount = 200.0
|
||||||
@@ -2048,6 +2049,102 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(pr.get("invoices"), [])
|
self.assertEqual(pr.get("invoices"), [])
|
||||||
self.assertEqual(pr.get("payments"), [])
|
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):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
|||||||
@@ -712,6 +712,23 @@ def update_reference_in_payment_entry(
|
|||||||
}
|
}
|
||||||
update_advance_paid = []
|
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:
|
if d.voucher_detail_no:
|
||||||
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
||||||
|
|
||||||
|
|||||||
@@ -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.enable_allow_existing_serial_no
|
||||||
erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts
|
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.update_asset_status_to_work_in_progress
|
||||||
|
erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
|
||||||
|
|||||||
@@ -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')"""
|
||||||
|
)
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
"advance_payments_section",
|
"advance_payments_section",
|
||||||
"book_advance_payments_in_separate_party_account",
|
"book_advance_payments_in_separate_party_account",
|
||||||
"reconcile_on_advance_payment_date",
|
"reconcile_on_advance_payment_date",
|
||||||
|
"reconciliation_takes_effect_on",
|
||||||
"column_break_fwcf",
|
"column_break_fwcf",
|
||||||
"default_advance_received_account",
|
"default_advance_received_account",
|
||||||
"default_advance_paid_account",
|
"default_advance_paid_account",
|
||||||
@@ -796,6 +797,7 @@
|
|||||||
"description": "If <b>Enabled</b> - Reconciliation happens on the <b>Advance Payment posting date</b><br>\nIf <b>Disabled</b> - Reconciliation happens on oldest of 2 Dates: <b>Invoice Date</b> or the <b>Advance Payment posting date</b><br>\n",
|
"description": "If <b>Enabled</b> - Reconciliation happens on the <b>Advance Payment posting date</b><br>\nIf <b>Disabled</b> - Reconciliation happens on oldest of 2 Dates: <b>Invoice Date</b> or the <b>Advance Payment posting date</b><br>\n",
|
||||||
"fieldname": "reconcile_on_advance_payment_date",
|
"fieldname": "reconcile_on_advance_payment_date",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Reconcile on Advance Payment Date"
|
"label": "Reconcile on Advance Payment Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -841,6 +843,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_dcdl",
|
"fieldname": "column_break_dcdl",
|
||||||
"fieldtype": "Column Break"
|
"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",
|
"icon": "fa fa-building",
|
||||||
@@ -848,7 +857,7 @@
|
|||||||
"image_field": "company_logo",
|
"image_field": "company_logo",
|
||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-12-02 15:37:32.723176",
|
"modified": "2025-01-09 20:12:25.471544",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Company",
|
"name": "Company",
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ class Company(NestedSet):
|
|||||||
payment_terms: DF.Link | None
|
payment_terms: DF.Link | None
|
||||||
phone_no: DF.Data | None
|
phone_no: DF.Data | None
|
||||||
reconcile_on_advance_payment_date: DF.Check
|
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
|
registration_details: DF.Code | None
|
||||||
rgt: DF.Int
|
rgt: DF.Int
|
||||||
round_off_account: DF.Link | None
|
round_off_account: DF.Link | None
|
||||||
|
|||||||
Reference in New Issue
Block a user