From 354720e0bcf2eb35c3083c427509078788d0e0e2 Mon Sep 17 00:00:00 2001 From: iamtalib13 Date: Mon, 8 Jun 2026 23:34:19 +0530 Subject: [PATCH] feat: implement lease renewal enhancement with actions grouping and confirmation dialog --- .../doctype/lease/lease.js | 86 ++++++++++++++++++- .../doctype/lease/lease.json | 32 ++++++- .../doctype/lease/lease.py | 59 +++++++++++++ 3 files changed, 172 insertions(+), 5 deletions(-) diff --git a/propms/property_management_solution/doctype/lease/lease.js b/propms/property_management_solution/doctype/lease/lease.js index 1e8edbd..1212957 100755 --- a/propms/property_management_solution/doctype/lease/lease.js +++ b/propms/property_management_solution/doctype/lease/lease.js @@ -27,15 +27,17 @@ frappe.ui.form.on('Lease', { }); }, refresh: function(frm) { + frm.trigger("custom_set_intro"); + cur_frm.add_custom_button(__("Make Invoice Schedule"), function() { make_lease_invoice_schedule(cur_frm); - }); + }, __("Actions")); cur_frm.add_custom_button(__("Generate Pending Invoice"), function() { generate_pending_invoice(); - }); + }, __("Actions")); cur_frm.add_custom_button(__("Make Invoice Schedule for all Lease"), function() { getAllLease(cur_frm); - }); + }, __("Actions")); // Add custom buttons for Accounts Receivable and Accounting Ledger if (!frm.doc.__islocal) { @@ -62,6 +64,39 @@ frappe.ui.form.on('Lease', { }, __("View") ); + + // Add "Renew Lease" custom button + if ( + ["Active", "Expired"].includes(frm.doc.lease_status) && + (frappe.user.has_role("Property Manager") || + frappe.user.has_role("System Manager")) + ) { + frm.add_custom_button( + __("Renew Lease"), + function () { + frappe.confirm(__("Are you sure you want to renew this lease?"), function () { + frappe.call({ + method: "propms.property_management_solution.doctype.lease.lease.initiate_lease_renewal", + args: { + source_lease_name: frm.doc.name, + }, + freeze: true, + freeze_message: __("Initiating Lease Renewal..."), + callback: function (r) { + if (r.message) { + frappe.show_alert({ + message: __("Lease renewal draft created successfully: {0}", [r.message]), + indicator: "green", + }); + frappe.set_route("Form", "Lease", r.message); + } + }, + }); + }); + }, + __("Actions") + ); + } } }, skip_end_date: function(frm) { @@ -111,7 +146,50 @@ frappe.ui.form.on('Lease', { } }); } - } + }, + + custom_set_intro: function (frm) { + // Show intro banner if this is a renewal lease + if (frm.doc.renewed_from) { + frappe.db + .get_value("Lease", frm.doc.name, ["renewal_initiated_by", "creation"]) + .then((r) => { + if (r.message) { + let d = r.message; + let created_on = moment(d.creation).format("DD-MM-YYYY hh:mm A"); + + frm.set_intro( + __("This lease is a renewal of {0}, initiated by {1} on {2}.", [ + `${frm.doc.renewed_from}`, + `${d.renewal_initiated_by || __("Unknown")}`, + `${created_on}`, + ]), + "blue" + ); + } + }); + } + + // Show intro banner if this lease has already been renewed + frappe.db + .get_value("Lease", { renewed_from: frm.doc.name }, ["name", "renewal_initiated_by", "creation"]) + .then((r) => { + if (r && r.message && r.message.name) { + let d = r.message; + + let creation_time = moment(d.creation).format("DD-MM-YYYY hh:mm A"); + + frm.set_intro( + __("This lease has been renewed: {0} by {1} on {2}.", [ + `${d.name}`, + `${d.renewal_initiated_by || ""}`, + `${creation_time}`, + ]), + "green" + ); + } + }); + }, }); var make_lease_invoice_schedule = function(frm){ diff --git a/propms/property_management_solution/doctype/lease/lease.json b/propms/property_management_solution/doctype/lease/lease.json index 9399c9f..1d1ebe6 100755 --- a/propms/property_management_solution/doctype/lease/lease.json +++ b/propms/property_management_solution/doctype/lease/lease.json @@ -53,7 +53,11 @@ "witness_2", "witness_1", "section_break_36", - "amended_from" + "amended_from", + "lease_renewal_details_section", + "renewed_from", + "column_break_renewal", + "renewal_initiated_by" ], "fields": [ { @@ -325,6 +329,32 @@ "fieldname": "skip_end_date", "fieldtype": "Check", "label": "Skip End Date" + }, + { + "fieldname": "lease_renewal_details_section", + "fieldtype": "Section Break", + "label": "Lease Renewal Details", + "collapsible": 1 + }, + { + "fieldname": "renewed_from", + "fieldtype": "Link", + "label": "Renewed From", + "options": "Lease", + "read_only": 1, + "no_copy": 1 + }, + { + "fieldname": "column_break_renewal", + "fieldtype": "Column Break" + }, + { + "fieldname": "renewal_initiated_by", + "fieldtype": "Link", + "label": "Renewal Initiated By", + "options": "User", + "read_only": 1, + "no_copy": 1 } ], "links": [], diff --git a/propms/property_management_solution/doctype/lease/lease.py b/propms/property_management_solution/doctype/lease/lease.py index 72b1b52..3e71687 100755 --- a/propms/property_management_solution/doctype/lease/lease.py +++ b/propms/property_management_solution/doctype/lease/lease.py @@ -506,3 +506,62 @@ def make_lease_invoice_schedule(leasedoc): except Exception as e: frappe.msgprint("Exception error! Check app error log.") app_error_log(frappe.session.user, str(e)) + + +@frappe.whitelist() +def initiate_lease_renewal(source_lease_name): + # 1. Permission checks + if not any(r in frappe.get_roles() for r in ["Property Manager", "System Manager"]): + frappe.throw(_("You are not authorized to renew leases. Only Property Managers and System Managers can perform this action."), frappe.PermissionError) + + # 2. Fetch source document + source_doc = frappe.get_doc("Lease", source_lease_name) + + # 3. Status checks + if source_doc.lease_status not in ["Active", "Expired"]: + frappe.throw(_("Lease {0} is not eligible for renewal. Status must be Active or Expired.").format(source_lease_name), frappe.ValidationError) + + # 4. Duplicate renewal check (ignoring terminated or aborted renewals) + duplicate_exists = frappe.db.exists("Lease", { + "renewed_from": source_lease_name, + "lease_status": ["not in", ["Not Materialized", "Terminated"]] + }) + if duplicate_exists: + frappe.throw(_("A renewal lease already exists for this lease: {0}").format(duplicate_exists), frappe.ValidationError) + + # 5. Clone Lease using standard frappe.copy_doc + new_lease = frappe.copy_doc(source_doc) + new_lease.set("lease_invoice_schedule", []) # Clear old invoice schedules + + # Set renewal reference fields + new_lease.renewed_from = source_lease_name + new_lease.lease_status = "Renewal to Previous Lease" + new_lease.renewal_initiated_by = frappe.session.user + + # Calculate dates + if source_doc.end_date: + new_lease.start_date = add_days(source_doc.end_date, 1) + if source_doc.start_date: + duration_days = (getdate(source_doc.end_date) - getdate(source_doc.start_date)).days + new_lease.end_date = add_days(new_lease.start_date, duration_days) + else: + new_lease.start_date = today() + + # Update valid_from date for items + for item in new_lease.lease_item: + item.valid_from = new_lease.start_date + + # Insert and save the new Lease as draft + new_lease.insert(ignore_permissions=True) + + # Post a message/comment to the old lease with initiator and link details + comment_text = _("Lease renewal draft {0} has been initiated by {1} on {2}.").format( + new_lease.name, + frappe.session.user, + frappe.utils.formatdate(today()) + ) + source_doc.add_comment(text=comment_text) + + return new_lease.name + +