massage data
This commit is contained in:
parent
616fa1be79
commit
60d3f35988
@ -1,4 +1,5 @@
|
|||||||
import frappe, json
|
import frappe, json
|
||||||
|
from custom_ui.db_utils import calculate_appointment_scheduled_status, calculate_estimate_sent_status, calculate_payment_recieved_status, calculate_job_scheduled_status
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_clients(options):
|
def get_clients(options):
|
||||||
@ -8,11 +9,13 @@ def get_clients(options):
|
|||||||
"filters": {},
|
"filters": {},
|
||||||
"sorting": {},
|
"sorting": {},
|
||||||
"page": 1,
|
"page": 1,
|
||||||
"page_size": 10
|
"page_size": 10,
|
||||||
|
"for_table": False
|
||||||
}
|
}
|
||||||
options = {**defaultOptions, **options}
|
options = {**defaultOptions, **options}
|
||||||
|
|
||||||
clients = []
|
clients = []
|
||||||
|
tableRows = []
|
||||||
|
|
||||||
count = frappe.db.count("Address", filters=options["filters"])
|
count = frappe.db.count("Address", filters=options["filters"])
|
||||||
print("DEBUG: Total addresses count:", count)
|
print("DEBUG: Total addresses count:", count)
|
||||||
@ -28,29 +31,58 @@ def get_clients(options):
|
|||||||
|
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
client = {}
|
client = {}
|
||||||
|
tableRow = {}
|
||||||
print("DEBUG: Processing address:", address)
|
print("DEBUG: Processing address:", address)
|
||||||
|
|
||||||
|
on_site_meetings = frappe.db.get_all(
|
||||||
|
"On-Site Meeting",
|
||||||
|
fields=["*"],
|
||||||
|
filters={"address": address["address_title"]}
|
||||||
|
)
|
||||||
|
|
||||||
|
sales_invvoices = frappe.db.get_all(
|
||||||
|
"Sales Invoice",
|
||||||
|
fields=["*"],
|
||||||
|
filters={"custom_installation_address": address["address_title"]}
|
||||||
|
)
|
||||||
|
|
||||||
quotations = frappe.db.get_all(
|
quotations = frappe.db.get_all(
|
||||||
"Quotation",
|
"Quotation",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"custom_installation_address": address["name"]}
|
filters={"custom_installation_address": address["address_title"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
jobs = frappe.db.get_all("Project",
|
jobs = frappe.db.get_all(
|
||||||
fields=["*"],
|
"Project",
|
||||||
filters={"custom_installation_address": address["name"],
|
fields=["*"],
|
||||||
"project_template": "SNW Install"})
|
filters={
|
||||||
|
"custom_installation_address": address["address_title"],
|
||||||
|
"project_template": "SNW Install"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tableRow["id"] = address["name"]
|
||||||
|
tableRow["address_name"] = address.get("address_title", "")
|
||||||
|
tableRow["appointment_scheduled_status"] = calculate_appointment_scheduled_status(on_site_meetings[0]) if on_site_meetings else "Not Started"
|
||||||
|
tableRow["estimate_sent_status"] = calculate_estimate_sent_status(quotations[0]) if quotations else "Not Started"
|
||||||
|
tableRow["payment_received_status"] = calculate_payment_recieved_status(sales_invvoices[0]) if sales_invvoices else "Not Started"
|
||||||
|
tableRow["job_scheduled_status"] = calculate_job_scheduled_status(jobs[0]) if jobs else "Not Started"
|
||||||
|
tableRows.append(tableRow)
|
||||||
|
|
||||||
client["address"] = address
|
client["address"] = address
|
||||||
client["on_site_meetings"] = []
|
client["on_site_meetings"] = on_site_meetings
|
||||||
client["jobs"] = jobs
|
client["jobs"] = jobs
|
||||||
client["quotations"] = quotations
|
client["quotations"] = quotations
|
||||||
clients.append(client)
|
clients.append(client)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"count": count,
|
"pagination": {
|
||||||
"page": options["page"],
|
"total": count,
|
||||||
"page_size": options["page_size"],
|
"page": options["page"],
|
||||||
"clients": clients
|
"page_size": options["page_size"],
|
||||||
|
"total_pages": (count + options["page_size"] - 1) // options["page_size"]
|
||||||
|
},
|
||||||
|
"data": tableRows if options["for_table"] else clients
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
28
custom_ui/db_utils.py
Normal file
28
custom_ui/db_utils.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
def calculate_appointment_scheduled_status(on_site_meeting):
|
||||||
|
if not on_site_meeting:
|
||||||
|
return "Not Started"
|
||||||
|
# if on_site_meeting["end_time"] < today:
|
||||||
|
# return "In Progress"
|
||||||
|
return "Completed"
|
||||||
|
|
||||||
|
def calculate_estimate_sent_status(quotation):
|
||||||
|
if not quotation:
|
||||||
|
return "Not Started"
|
||||||
|
if quotation["custom_sent"] == 1:
|
||||||
|
return "Completed"
|
||||||
|
return "In Progress"
|
||||||
|
|
||||||
|
def calculate_payment_recieved_status(sales_invoice):
|
||||||
|
if not sales_invoice:
|
||||||
|
return "Not Started"
|
||||||
|
if sales_invoice and sales_invoice["status"] == "Paid":
|
||||||
|
return "Completed"
|
||||||
|
return "In Progress"
|
||||||
|
|
||||||
|
def calculate_job_scheduled_status(project):
|
||||||
|
if not project:
|
||||||
|
return "Not Started"
|
||||||
|
if not project["start_time"]:
|
||||||
|
return "In Progress"
|
||||||
|
return "Completed"
|
||||||
@ -246,23 +246,30 @@ class Api {
|
|||||||
* Get paginated client data with filtering and sorting
|
* Get paginated client data with filtering and sorting
|
||||||
* @param {Object} paginationParams - Pagination parameters from store
|
* @param {Object} paginationParams - Pagination parameters from store
|
||||||
* @param {Object} filters - Filter parameters from store
|
* @param {Object} filters - Filter parameters from store
|
||||||
* @returns {Promise<{data: Array, totalRecords: number}>}
|
* @returns {Promise<{data: Array, pagination: Object}>}
|
||||||
*/
|
*/
|
||||||
static async getPaginatedClientDetails(paginationParams = {}, filters = {}) {
|
static async getPaginatedClientDetails(paginationParams = {}, filters = {}) {
|
||||||
const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams;
|
const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
page: page + 1,
|
page: page + 1, // Backend expects 1-based pages
|
||||||
pageSize,
|
page_size: pageSize,
|
||||||
filters,
|
filters,
|
||||||
sortField,
|
sorting: sortField ? `${sortField} ${sortOrder === -1 ? "desc" : "asc"}` : null,
|
||||||
sortOrder,
|
for_table: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.getClientDetails(options);
|
const result = await this.request("custom_ui.api.db.get_clients", { options });
|
||||||
|
|
||||||
|
// Transform the response to match what the frontend expects
|
||||||
return {
|
return {
|
||||||
...result,
|
data: result.data,
|
||||||
|
pagination: {
|
||||||
|
total: result.pagination.total,
|
||||||
|
page: result.pagination.page,
|
||||||
|
pageSize: result.pagination.page_size,
|
||||||
|
totalPages: result.pagination.total_pages,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<!-- Filter Controls Panel -->
|
<!-- Filter Controls Panel -->
|
||||||
<div v-if="hasFilters" class="filter-controls-panel mb-3 p-3 bg-light rounded">
|
<div
|
||||||
|
v-if="hasFilters"
|
||||||
|
:key="`filter-controls-${tableName}`"
|
||||||
|
class="filter-controls-panel mb-3 p-3 bg-light rounded"
|
||||||
|
>
|
||||||
<div class="row g-3 align-items-end">
|
<div class="row g-3 align-items-end">
|
||||||
<div v-for="col in filterableColumns" :key="col.fieldName" class="col-md-4 col-lg-3">
|
<div v-for="col in filterableColumns" :key="col.fieldName" class="col-md-4 col-lg-3">
|
||||||
<label :for="`filter-${col.fieldName}`" class="form-label small fw-semibold">
|
<label :for="`filter-${col.fieldName}`" class="form-label small fw-semibold">
|
||||||
@ -41,7 +45,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Page Jump Controls -->
|
<!-- Page Jump Controls -->
|
||||||
<div v-if="totalPages > 1" class="page-controls-panel mb-3 p-2 bg-light rounded">
|
<div
|
||||||
|
v-if="totalPages > 1"
|
||||||
|
:key="`page-controls-${totalPages}-${getPageInfo().total}`"
|
||||||
|
class="page-controls-panel mb-3 p-2 bg-light rounded"
|
||||||
|
>
|
||||||
<div class="row g-3 align-items-center">
|
<div class="row g-3 align-items-center">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<small class="text-muted">Quick navigation:</small>
|
<small class="text-muted">Quick navigation:</small>
|
||||||
@ -278,6 +286,28 @@ const filterRef = computed({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Watch for changes in total records to update page controls reactivity
|
||||||
|
watch(
|
||||||
|
() => props.totalRecords,
|
||||||
|
(newTotal) => {
|
||||||
|
if (props.lazy && newTotal !== undefined) {
|
||||||
|
// Force reactivity update for page controls
|
||||||
|
selectedPageJump.value = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Watch for data changes to update page controls for non-lazy tables
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(newData) => {
|
||||||
|
if (!props.lazy && newData) {
|
||||||
|
// Force reactivity update for page controls
|
||||||
|
selectedPageJump.value = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Watch for filter changes to sync match mode changes
|
// Watch for filter changes to sync match mode changes
|
||||||
watch(
|
watch(
|
||||||
filterRef,
|
filterRef,
|
||||||
|
|||||||
@ -44,26 +44,31 @@ const onClick = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
fullName: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
addressName: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
label: "Name",
|
label: "Name",
|
||||||
fieldName: "fullName",
|
fieldName: "addressName",
|
||||||
type: "text",
|
type: "text",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Appt. Scheduled",
|
label: "Appt. Scheduled",
|
||||||
fieldName: "appointmentStatus",
|
fieldName: "appointmentScheduledStatus",
|
||||||
type: "status",
|
type: "status",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{ label: "Estimate Sent", fieldName: "estimateStatus", type: "status", sortable: true },
|
{ label: "Estimate Sent", fieldName: "estimateSentStatus", type: "status", sortable: true },
|
||||||
{ label: "Payment Received", fieldName: "paymentStatus", type: "status", sortable: true },
|
{
|
||||||
{ label: "Job Status", fieldName: "jobStatus", type: "status", sortable: true },
|
label: "Payment Received",
|
||||||
|
fieldName: "paymentReceivedStatus",
|
||||||
|
type: "status",
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{ label: "Job Status", fieldName: "jobScheduledStatus", type: "status", sortable: true },
|
||||||
];
|
];
|
||||||
// Handle lazy loading events from DataTable
|
// Handle lazy loading events from DataTable
|
||||||
const handleLazyLoad = async (event) => {
|
const handleLazyLoad = async (event) => {
|
||||||
@ -125,12 +130,19 @@ const handleLazyLoad = async (event) => {
|
|||||||
// Call API with pagination and filters
|
// Call API with pagination and filters
|
||||||
const result = await Api.getPaginatedClientDetails(paginationParams, filters);
|
const result = await Api.getPaginatedClientDetails(paginationParams, filters);
|
||||||
|
|
||||||
// Update local state
|
// Update local state - extract from pagination structure
|
||||||
tableData.value = result.data;
|
tableData.value = result.data;
|
||||||
totalRecords.value = result.totalRecords;
|
totalRecords.value = result.pagination.total;
|
||||||
|
|
||||||
// Update pagination store with new total
|
// Update pagination store with new total
|
||||||
paginationStore.setTotalRecords("clients", result.totalRecords);
|
paginationStore.setTotalRecords("clients", result.pagination.total);
|
||||||
|
|
||||||
|
console.log("Updated pagination state:", {
|
||||||
|
tableData: tableData.value.length,
|
||||||
|
totalRecords: totalRecords.value,
|
||||||
|
storeTotal: paginationStore.getTablePagination("clients").totalRecords,
|
||||||
|
storeTotalPages: paginationStore.getTotalPages("clients"),
|
||||||
|
});
|
||||||
|
|
||||||
// Cache the result
|
// Cache the result
|
||||||
paginationStore.setCachedPage(
|
paginationStore.setCachedPage(
|
||||||
@ -142,13 +154,13 @@ const handleLazyLoad = async (event) => {
|
|||||||
filters,
|
filters,
|
||||||
{
|
{
|
||||||
records: result.data,
|
records: result.data,
|
||||||
totalRecords: result.totalRecords,
|
totalRecords: result.pagination.total,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("Loaded from API:", {
|
console.log("Loaded from API:", {
|
||||||
records: result.data.length,
|
records: result.data.length,
|
||||||
total: result.totalRecords,
|
total: result.pagination.total,
|
||||||
page: paginationParams.page + 1,
|
page: paginationParams.page + 1,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export const useFiltersStore = defineStore("filters", {
|
|||||||
// Store filters by table/component name
|
// Store filters by table/component name
|
||||||
tableFilters: {
|
tableFilters: {
|
||||||
clients: {
|
clients: {
|
||||||
fullName: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
addressName: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
},
|
},
|
||||||
jobs: {
|
jobs: {
|
||||||
customer: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
customer: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
@ -25,8 +25,8 @@ export const useFiltersStore = defineStore("filters", {
|
|||||||
routes: {
|
routes: {
|
||||||
technician: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
technician: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
routeId: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
routeId: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
// Generic method to get filters for a specific table
|
// Generic method to get filters for a specific table
|
||||||
@ -42,7 +42,7 @@ export const useFiltersStore = defineStore("filters", {
|
|||||||
if (!this.tableFilters[tableName][fieldName]) {
|
if (!this.tableFilters[tableName][fieldName]) {
|
||||||
this.tableFilters[tableName][fieldName] = {
|
this.tableFilters[tableName][fieldName] = {
|
||||||
value: null,
|
value: null,
|
||||||
matchMode: FilterMatchMode.CONTAINS
|
matchMode: FilterMatchMode.CONTAINS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.tableFilters[tableName][fieldName].value = value;
|
this.tableFilters[tableName][fieldName].value = value;
|
||||||
@ -55,7 +55,7 @@ export const useFiltersStore = defineStore("filters", {
|
|||||||
// Method to clear all filters for a table
|
// Method to clear all filters for a table
|
||||||
clearTableFilters(tableName) {
|
clearTableFilters(tableName) {
|
||||||
if (this.tableFilters[tableName]) {
|
if (this.tableFilters[tableName]) {
|
||||||
Object.keys(this.tableFilters[tableName]).forEach(key => {
|
Object.keys(this.tableFilters[tableName]).forEach((key) => {
|
||||||
this.tableFilters[tableName][key].value = null;
|
this.tableFilters[tableName][key].value = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,11 +67,11 @@ export const useFiltersStore = defineStore("filters", {
|
|||||||
this.tableFilters[tableName] = {};
|
this.tableFilters[tableName] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.forEach(column => {
|
columns.forEach((column) => {
|
||||||
if (column.filterable && !this.tableFilters[tableName][column.fieldName]) {
|
if (column.filterable && !this.tableFilters[tableName][column.fieldName]) {
|
||||||
this.tableFilters[tableName][column.fieldName] = {
|
this.tableFilters[tableName][column.fieldName] = {
|
||||||
value: null,
|
value: null,
|
||||||
matchMode: FilterMatchMode.CONTAINS
|
matchMode: FilterMatchMode.CONTAINS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -79,12 +79,12 @@ export const useFiltersStore = defineStore("filters", {
|
|||||||
|
|
||||||
// Legacy method for backward compatibility
|
// Legacy method for backward compatibility
|
||||||
setClientNameFilter(filterValue) {
|
setClientNameFilter(filterValue) {
|
||||||
this.updateTableFilter('clients', 'fullName', filterValue);
|
this.updateTableFilter("clients", "addressName", filterValue);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Getter for legacy compatibility
|
// Getter for legacy compatibility
|
||||||
get clientNameFilter() {
|
get clientNameFilter() {
|
||||||
return this.tableFilters?.clients?.fullName?.value || "";
|
return this.tableFilters?.clients?.addressName?.value || "";
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1698,7 +1698,15 @@ class DataUtils {
|
|||||||
static toSnakeCaseObject(obj) {
|
static toSnakeCaseObject(obj) {
|
||||||
const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
|
const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
|
||||||
const snakeKey = key.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase());
|
const snakeKey = key.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase());
|
||||||
value = Object.prototype.toString.call(value) === "[object Object]" ? this.toSnakeCaseObject(value) : value;
|
if (Array.isArray(value)) {
|
||||||
|
value = value.map((item) => {
|
||||||
|
return Object.prototype.toString.call(item) === "[object Object]"
|
||||||
|
? this.toSnakeCaseObject(item)
|
||||||
|
: item;
|
||||||
|
});
|
||||||
|
} else if (Object.prototype.toString.call(value) === "[object Object]") {
|
||||||
|
value = this.toSnakeCaseObject(value);
|
||||||
|
}
|
||||||
acc[snakeKey] = value;
|
acc[snakeKey] = value;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
@ -1709,7 +1717,16 @@ class DataUtils {
|
|||||||
static toCamelCaseObject(obj) {
|
static toCamelCaseObject(obj) {
|
||||||
const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
|
const newObj = Object.entries(obj).reduce((acc, [key, value]) => {
|
||||||
const camelKey = key.replace(/_([a-z])/g, (match, p1) => p1.toUpperCase());
|
const camelKey = key.replace(/_([a-z])/g, (match, p1) => p1.toUpperCase());
|
||||||
value = Object.prototype.toString.call(value) === "[object Object]" ? this.toCamelCaseObject(value) : value;
|
// check if value is an array
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = value.map((item) => {
|
||||||
|
return Object.prototype.toString.call(item) === "[object Object]"
|
||||||
|
? this.toCamelCaseObject(item)
|
||||||
|
: item;
|
||||||
|
});
|
||||||
|
} else if (Object.prototype.toString.call(value) === "[object Object]") {
|
||||||
|
value = this.toCamelCaseObject(value);
|
||||||
|
}
|
||||||
acc[camelKey] = value;
|
acc[camelKey] = value;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user