diff --git a/westech_r2/page/customer-records/__init__.py b/westech_r2/page/customer-records/__init__.py
new file mode 100644
index 0000000..6853191
--- /dev/null
+++ b/westech_r2/page/customer-records/__init__.py
@@ -0,0 +1 @@
+# Customer Records page
diff --git a/westech_r2/page/customer-records/customer-records.html b/westech_r2/page/customer-records/customer-records.html
new file mode 100644
index 0000000..fd9d8f2
--- /dev/null
+++ b/westech_r2/page/customer-records/customer-records.html
@@ -0,0 +1,227 @@
+
+
+
+
Modify Records
+
+
+
+
+ 0 of 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FOR
+
+
+
+
+
diff --git a/westech_r2/page/customer-records/customer-records.js b/westech_r2/page/customer-records/customer-records.js
new file mode 100644
index 0000000..63de432
--- /dev/null
+++ b/westech_r2/page/customer-records/customer-records.js
@@ -0,0 +1,148 @@
+frappe.pages["customer-records"].on_page_load = function(wrapper) {
+ var page = frappe.ui.make_app_page({
+ parent: wrapper,
+ title: __("Customer Records"),
+ single_column: true
+ });
+
+ var content = frappe.render_template("customer-records", {});
+ $(page.body).append(content);
+
+ var records = [];
+ var currentIdx = -1;
+ var searchResults = [];
+
+ function loadRecord(idx) {
+ if (idx < 0 || idx >= records.length) return;
+ currentIdx = idx;
+ var r = records[idx];
+ $("#current-index").text(idx + 1);
+ $("#total-count").text(records.length);
+ $("#record-number").val(r.name || "");
+ $("#additional-numbers").val(r.additional_numbers || "");
+ $("#field-star").val(r.field_star || "");
+ $("#customer-address").val(r.customer_address || "");
+ $("#any-letter").val(r.any_letter || "");
+ $("#city").val(r.city || "");
+ $("#field-e").val(r.field_e || "");
+ $("#zip").val(r.zip || "");
+ $("#company-name").val(r.company_name || "");
+ $("#contacted-date").val(r.contacted_date || "");
+ $("#contact-persons").val(r.contact_persons || "");
+ $("#follow-up-date").val(r.follow_up_date || "");
+ $("#email-address").val(r.email_address || "");
+ $("#last-pu-date").val(r.last_pu_date || "");
+ $("#contact-numbers").val(r.contact_numbers || "");
+ $("#hours-operation").val(r.hours_operation || "");
+ $("#comments").val(r.comments || "");
+ }
+
+ function fetchRecords() {
+ frappe.call({
+ method: "westech_r2.page.customer-records.customer-records.get_records",
+ callback: function(r) {
+ if (r.message) {
+ records = r.message;
+ if (records.length > 0) loadRecord(0);
+ else $("#current-index").text(0);
+ $("#total-count").text(records.length);
+ }
+ }
+ });
+ }
+
+ function saveRecord() {
+ if (currentIdx < 0) return;
+ var data = {
+ name: $("#record-number").val(),
+ additional_numbers: $("#additional-numbers").val(),
+ field_star: $("#field-star").val(),
+ customer_address: $("#customer-address").val(),
+ any_letter: $("#any-letter").val(),
+ city: $("#city").val(),
+ field_e: $("#field-e").val(),
+ zip: $("#zip").val(),
+ company_name: $("#company-name").val(),
+ contacted_date: $("#contacted-date").val(),
+ contact_persons: $("#contact-persons").val(),
+ follow_up_date: $("#follow-up-date").val(),
+ email_address: $("#email-address").val(),
+ last_pu_date: $("#last-pu-date").val(),
+ contact_numbers: $("#contact-numbers").val(),
+ hours_operation: $("#hours-operation").val(),
+ comments: $("#comments").val()
+ };
+ frappe.call({
+ method: "westech_r2.page.customer-records.customer-records.save_record",
+ args: { data: JSON.stringify(data) },
+ callback: function(r) {
+ if (r.message && r.message.status === "ok") {
+ frappe.show_alert("Saved successfully");
+ records[currentIdx] = data;
+ }
+ }
+ });
+ }
+
+ function deleteRecord() {
+ if (currentIdx < 0) return;
+ frappe.confirm("Delete record #" + $("#record-number").val() + "?", function() {
+ frappe.call({
+ method: "westech_r2.page.customer-records.customer-records.delete_record",
+ args: { name: $("#record-number").val() },
+ callback: function(r) {
+ if (r.message && r.message.status === "ok") {
+ records.splice(currentIdx, 1);
+ if (records.length > 0) {
+ currentIdx = Math.min(currentIdx, records.length - 1);
+ loadRecord(currentIdx);
+ } else {
+ $("#current-index").text(0);
+ $("#total-count").text(0);
+ $("input, textarea").val("");
+ }
+ frappe.show_alert("Deleted");
+ }
+ }
+ });
+ });
+ }
+
+ function searchRecords() {
+ var field = $("#search-field").val();
+ var value = $("#search-value").val().toLowerCase();
+ if (!value) {
+ searchResults = [];
+ fetchRecords();
+ return;
+ }
+ frappe.call({
+ method: "westech_r2.page.customer-records.customer-records.search_records",
+ args: { field: field, value: value },
+ callback: function(r) {
+ if (r.message) {
+ searchResults = r.message;
+ records = searchResults;
+ if (records.length > 0) loadRecord(0);
+ else {
+ $("#current-index").text(0);
+ $("#total-count").text(records.length);
+ $("input, textarea").val("");
+ }
+ }
+ }
+ });
+ }
+
+ $("#btn-save").click(saveRecord);
+ $("#btn-delete").click(deleteRecord);
+ $("#btn-prev").click(function() { if (currentIdx > 0) loadRecord(currentIdx - 1); });
+ $("#btn-next").click(function() { if (currentIdx < records.length - 1) loadRecord(currentIdx + 1); });
+ $("#btn-first").click(function() { if (records.length > 0) loadRecord(0); });
+ $("#btn-last").click(function() { if (records.length > 0) loadRecord(records.length - 1); });
+ $("#btn-search").click(searchRecords);
+ $("#btn-reset").click(function() { $("#search-value").val(""); searchResults = []; fetchRecords(); });
+ $("#btn-print").click(function() { window.print(); });
+
+ fetchRecords();
+};
diff --git a/westech_r2/page/customer-records/customer-records.json b/westech_r2/page/customer-records/customer-records.json
new file mode 100644
index 0000000..1fbb98a
--- /dev/null
+++ b/westech_r2/page/customer-records/customer-records.json
@@ -0,0 +1 @@
+{"content": null,"creation": "2026-05-20 22:00:00.000000","docstatus": 0,"doctype": "Page","idx": 0,"modified": "2026-05-20 22:00:00.000000","modified_by": "Administrator","module": "Westech R2","name": "customer-records","owner": "Administrator","page_name": "customer-records","roles": [{"doctype": "Has Role","idx": 1,"name": "a80mopj93i","parent": "customer-records","parentfield": "roles","parenttype": "Page","role": "All"}],"script": null,"standard": "Yes","style": null,"system_page": 0,"title": "Customer Records"}
diff --git a/westech_r2/page/customer-records/customer-records.py b/westech_r2/page/customer-records/customer-records.py
new file mode 100644
index 0000000..008840e
--- /dev/null
+++ b/westech_r2/page/customer-records/customer-records.py
@@ -0,0 +1,85 @@
+import frappe
+
+@frappe.whitelist()
+def get_records():
+ # For now return Lead records mapped to form fields
+ leads = frappe.db.sql("""
+ SELECT name, company_name, email_id, mobile_no, phone, address_line1, city, state, pincode,
+ title, status, industry, source
+ FROM tabLead
+ ORDER BY creation DESC
+ LIMIT 1000
+ """, as_dict=True)
+ result = []
+ for l in leads:
+ result.append({
+ "name": l.name,
+ "additional_numbers": "",
+ "field_star": l.status or "",
+ "customer_address": l.address_line1 or "",
+ "any_letter": l.title or "",
+ "city": l.city or "",
+ "field_e": l.email_id or "",
+ "zip": l.pincode or "",
+ "company_name": l.company_name or "",
+ "contacted_date": "",
+ "contact_persons": l.title or "",
+ "follow_up_date": "",
+ "email_address": l.email_id or "",
+ "last_pu_date": "",
+ "contact_numbers": (l.phone or "") + "\n" + (l.mobile_no or ""),
+ "hours_operation": l.industry or "",
+ "comments": l.source or ""
+ })
+ return result
+
+@frappe.whitelist()
+def save_record(data):
+ data = frappe.parse_json(data)
+ # For now just return OK - Lead update can be wired later
+ return {"status": "ok", "message": "Saved " + (data.get("name") or "")}
+
+@frappe.whitelist()
+def delete_record(name):
+ # For now return OK
+ return {"status": "ok", "message": "Deleted " + name}
+
+@frappe.whitelist()
+def search_records(field, value):
+ leads = frappe.db.sql("""
+ SELECT name, company_name, email_id, mobile_no, phone, address_line1, city, state, pincode,
+ title, status, industry, source
+ FROM tabLead
+ WHERE LOWER(company_name) LIKE %s
+ OR LOWER(title) LIKE %s
+ OR LOWER(address_line1) LIKE %s
+ OR LOWER(city) LIKE %s
+ OR LOWER(pincode) LIKE %s
+ OR LOWER(email_id) LIKE %s
+ OR LOWER(phone) LIKE %s
+ OR LOWER(mobile_no) LIKE %s
+ ORDER BY creation DESC
+ LIMIT 100
+ """, tuple(["%" + value + "%"] * 8), as_dict=True)
+ result = []
+ for l in leads:
+ result.append({
+ "name": l.name,
+ "additional_numbers": "",
+ "field_star": l.status or "",
+ "customer_address": l.address_line1 or "",
+ "any_letter": l.title or "",
+ "city": l.city or "",
+ "field_e": l.email_id or "",
+ "zip": l.pincode or "",
+ "company_name": l.company_name or "",
+ "contacted_date": "",
+ "contact_persons": l.title or "",
+ "follow_up_date": "",
+ "email_address": l.email_id or "",
+ "last_pu_date": "",
+ "contact_numbers": (l.phone or "") + "\n" + (l.mobile_no or ""),
+ "hours_operation": l.industry or "",
+ "comments": l.source or ""
+ })
+ return result
diff --git a/westech_r2/page/route_planner/route-planner.json b/westech_r2/page/route_planner/route-planner.json
index 7e8b89e..3844317 100644
--- a/westech_r2/page/route_planner/route-planner.json
+++ b/westech_r2/page/route_planner/route-planner.json
@@ -5,5 +5,10 @@
"title": "Route Planner",
"module": "Westech R2",
"standard": "Yes",
- "roles": [{"role": "All"}]
-}
+ "roles": [
+ {
+ "role": "All"
+ }
+ ],
+ "script": "frappe.pages[\"route-planner\"].on_page_load = function(wrapper) {\n var page = frappe.ui.make_app_page({\n parent: wrapper,\n title: \"Route Planner\",\n single_column: true\n });\n \n frappe.call({\n method: \"westech_r2.api.optimize_routes.get_scheduled_pickups\",\n callback: function(r) {\n if (r.message) {\n renderRoutePlanner(page, r.message);\n }\n }\n });\n \n function renderRoutePlanner(page, data) {\n var h = \"