feat: Add Customer Records page with Lead integration
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
# Customer Records page
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<style>
|
||||||
|
.customer-records-page {
|
||||||
|
font-family: Segoe UI, Arial, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.customer-records-page h1 {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.toolbar button {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background: #f0f0f0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.toolbar button:hover { background: #e0e0e0; }
|
||||||
|
.toolbar .record-counter {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.toolbar .nav-btn {
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 18px;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 140px;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.form-group input,
|
||||||
|
.form-group textarea {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
.form-group textarea {
|
||||||
|
min-height: 60px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.form-group.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
}
|
||||||
|
.search-bar label {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.search-bar select,
|
||||||
|
.search-bar input {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.search-bar .search-label {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.search-bar button {
|
||||||
|
padding: 6px 20px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background: #f0f0f0;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.search-bar button:hover { background: #e0e0e0; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="customer-records-page">
|
||||||
|
<h1>Modify Records</h1>
|
||||||
|
|
||||||
|
<div class="toolbar">
|
||||||
|
<button id="btn-save">Save</button>
|
||||||
|
<span class="record-counter">
|
||||||
|
<span id="current-index">0</span> of <span id="total-count">0</span>
|
||||||
|
</span>
|
||||||
|
<button class="nav-btn" id="btn-prev"><</button>
|
||||||
|
<button class="nav-btn" id="btn-next">></button>
|
||||||
|
<button class="nav-btn" id="btn-first"><<</button>
|
||||||
|
<button class="nav-btn" id="btn-last">>></button>
|
||||||
|
<button id="btn-delete">Delete</button>
|
||||||
|
<button id="btn-print">Print</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Record #</label>
|
||||||
|
<input type="text" id="record-number" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Additional Numbers:</label>
|
||||||
|
<input type="text" id="additional-numbers">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>*</label>
|
||||||
|
<input type="text" id="field-star">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Customer Address:</label>
|
||||||
|
<input type="text" id="customer-address">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Any Letter:</label>
|
||||||
|
<input type="text" id="any-letter">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>City:</label>
|
||||||
|
<input type="text" id="city">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>E</label>
|
||||||
|
<input type="text" id="field-e">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Zip:</label>
|
||||||
|
<input type="text" id="zip">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Company Name:</label>
|
||||||
|
<input type="text" id="company-name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Contacted date:</label>
|
||||||
|
<input type="date" id="contacted-date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Contact Person(s):</label>
|
||||||
|
<textarea id="contact-persons"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Follow up date:</label>
|
||||||
|
<input type="text" id="follow-up-date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>E-Mail Address:</label>
|
||||||
|
<input type="email" id="email-address">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Last P/U date:</label>
|
||||||
|
<input type="text" id="last-pu-date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Contact Numbers:</label>
|
||||||
|
<textarea id="contact-numbers"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Hours of Operation:</label>
|
||||||
|
<input type="text" id="hours-operation">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Comments:</label>
|
||||||
|
<textarea id="comments"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-bar">
|
||||||
|
<label>SEARCH:</label>
|
||||||
|
<select id="search-field">
|
||||||
|
<option value="address">Address</option>
|
||||||
|
<option value="company_name">Company Name</option>
|
||||||
|
<option value="contact_person">Contact Person</option>
|
||||||
|
<option value="phone">Phone</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="city">City</option>
|
||||||
|
<option value="zip">Zip</option>
|
||||||
|
<option value="record_number">Record #</option>
|
||||||
|
</select>
|
||||||
|
<span class="search-label">FOR</span>
|
||||||
|
<input type="text" id="search-value" style="min-width:200px;">
|
||||||
|
<button id="btn-search">Search!</button>
|
||||||
|
<button id="btn-reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -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();
|
||||||
|
};
|
||||||
@@ -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"}
|
||||||
@@ -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
|
||||||
@@ -5,5 +5,10 @@
|
|||||||
"title": "Route Planner",
|
"title": "Route Planner",
|
||||||
"module": "Westech R2",
|
"module": "Westech R2",
|
||||||
"standard": "Yes",
|
"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 = \"<div style='padding:20px;'>\";\n h += \"<h2>Route Planner</h2>\";\n h += \"<p>Schedule and optimize pickup routes.</p>\";\n h += \"<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>\";\n h += \"<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>\";\n h += \"<div id='routes-container' style='margin-top:20px;'></div>\";\n h += \"</div>\";\n $(page.body).html(h);\n \n $(\"#optimize-btn\").on(\"click\", function() {\n frappe.call({\n method: \"westech_r2.api.optimize_routes.optimize_routes\",\n callback: function(r) {\n if (r.message) {\n frappe.show_alert({message: \"Routes optimized\", indicator: \"green\"});\n }\n }\n });\n });\n }\n};\n"
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
|||||||
|
frappe.pages['customer-records'].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: 'Customer Records',
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2026-05-20 15:03:29.017530",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2026-05-20 15:03:29.017530",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"name": "customer-records",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "customer-records",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": null,
|
||||||
|
"standard": "Yes",
|
||||||
|
"style": null,
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "Customer Records"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user