338 lines
9.1 KiB
JavaScript
338 lines
9.1 KiB
JavaScript
import { da } from "vuetify/locale";
|
|
import DataUtils from "./utils";
|
|
|
|
const ZIPPOPOTAMUS_BASE_URL = "https://api.zippopotam.us/us";
|
|
const FRAPPE_PROXY_METHOD = "custom_ui.api.proxy.request";
|
|
const FRAPPE_UPSERT_CLIENT_METHOD = "custom_ui.api.db.upsert_client";
|
|
|
|
class Api {
|
|
static async request(frappeMethod, args = {}) {
|
|
try {
|
|
const response = await frappe.call({
|
|
method: frappeMethod,
|
|
args: {
|
|
...args,
|
|
},
|
|
});
|
|
console.log("DEBUG: API - Request Response: ", response);
|
|
return response.message;
|
|
} catch (error) {
|
|
console.error("DEBUG: API - Request Error: ", error);
|
|
// Re-throw the error so calling code can handle it
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
static async getClientDetails(options = {}) {
|
|
const {
|
|
forTable = true,
|
|
page = 0,
|
|
pageSize = 10,
|
|
filters = {},
|
|
sortField = null,
|
|
sortOrder = null,
|
|
searchTerm = null,
|
|
} = options;
|
|
|
|
console.log("DEBUG: API - getClientDetails called with options:", options);
|
|
|
|
// Build filters for the Address query
|
|
let addressFilters = {};
|
|
|
|
// Add search functionality across multiple fields
|
|
if (searchTerm) {
|
|
// Note: This is a simplified version. You might want to implement
|
|
// full-text search on the backend for better performance
|
|
addressFilters.address_line1 = ["like", `%${searchTerm}%`];
|
|
}
|
|
|
|
// Add any custom filters
|
|
Object.keys(filters).forEach((key) => {
|
|
if (filters[key] && filters[key].value) {
|
|
// Map frontend filter names to backend field names if needed
|
|
switch (key) {
|
|
case "fullName":
|
|
// This will need special handling since fullName is constructed
|
|
// For now, we'll search in address_line1 or city
|
|
addressFilters.address_line1 = ["like", `%${filters[key].value}%`];
|
|
break;
|
|
// Add other filter mappings as needed
|
|
}
|
|
}
|
|
});
|
|
|
|
try {
|
|
// Get total count first for pagination
|
|
const totalCount = await this.getDocCount("Address", addressFilters);
|
|
|
|
// Get paginated addresses
|
|
const addresses = await this.getDocsList(
|
|
"Address",
|
|
["*"],
|
|
addressFilters,
|
|
page,
|
|
pageSize,
|
|
);
|
|
|
|
const data = [];
|
|
const processedData = [];
|
|
|
|
// Process each address to build client details
|
|
for (const addr of addresses) {
|
|
try {
|
|
const clientDetail = {};
|
|
|
|
const customer = await this.getDetailedDoc(
|
|
"Customer",
|
|
addr["custom_customer_to_bill"],
|
|
);
|
|
|
|
const quotations = await this.getDocsList("Quotation", [], {
|
|
custom_installation_address: addr["name"],
|
|
});
|
|
const quoteDetails =
|
|
quotations.length > 0
|
|
? await this.getDetailedDoc("Quotation", quotations[0]["name"])
|
|
: null;
|
|
|
|
const jobs = await this.getDocsList("Project", [], {
|
|
project_template: "SNW Install",
|
|
custom_installation_address: addr["name"],
|
|
});
|
|
const jobDetails =
|
|
jobs.length > 0
|
|
? await this.getDetailedDoc("Project", jobs[0]["name"])
|
|
: null;
|
|
|
|
clientDetail.customer = customer;
|
|
clientDetail.address = addr;
|
|
clientDetail.estimate = quoteDetails;
|
|
clientDetail.job = jobDetails;
|
|
|
|
const totalPaid = quoteDetails
|
|
? quoteDetails.payment_schedule
|
|
? quoteDetails.payment_schedule.reduce(
|
|
(sum, payment) => sum + (payment.paid_amount || 0),
|
|
0,
|
|
)
|
|
: 0
|
|
: 0;
|
|
|
|
const tableRow = {
|
|
id: addr.name, // Add unique ID for DataTable
|
|
fullName: `${customer.customer_name} - ${addr.address_line1}, ${addr.city} ${addr.state}`,
|
|
appointmentStatus: "not started",
|
|
estimateStatus: quoteDetails
|
|
? quoteDetails.custom_response == "Accepted"
|
|
? "completed"
|
|
: "in progress"
|
|
: "not started",
|
|
paymentStatus: quoteDetails
|
|
? totalPaid < quoteDetails.grand_total
|
|
? "in progress"
|
|
: "completed"
|
|
: "not started",
|
|
jobStatus: jobDetails
|
|
? jobDetails.status === "Completed"
|
|
? "completed"
|
|
: "in progress"
|
|
: "not started",
|
|
};
|
|
|
|
if (forTable) {
|
|
data.push(tableRow);
|
|
} else {
|
|
data.push(clientDetail);
|
|
}
|
|
processedData.push(clientDetail);
|
|
} catch (error) {
|
|
console.error(`Error processing address ${addr.name}:`, error);
|
|
// Continue with other addresses even if one fails
|
|
}
|
|
}
|
|
|
|
// Apply client-side sorting if needed (better to do on server)
|
|
if (sortField && forTable) {
|
|
data.sort((a, b) => {
|
|
const aValue = a[sortField] || "";
|
|
const bValue = b[sortField] || "";
|
|
const comparison = aValue.localeCompare(bValue);
|
|
return sortOrder === -1 ? -comparison : comparison;
|
|
});
|
|
}
|
|
|
|
// Apply client-side filtering for constructed fields like fullName
|
|
let filteredData = data;
|
|
if (filters.fullName && filters.fullName.value && forTable) {
|
|
const searchValue = filters.fullName.value.toLowerCase();
|
|
filteredData = data.filter((item) =>
|
|
item.fullName.toLowerCase().includes(searchValue),
|
|
);
|
|
}
|
|
|
|
console.log("DEBUG: API - Fetched Client Details:", {
|
|
total: totalCount,
|
|
page: page,
|
|
pageSize: pageSize,
|
|
returned: filteredData.length,
|
|
});
|
|
|
|
// Return paginated response with metadata
|
|
return {
|
|
data: filteredData,
|
|
pagination: {
|
|
page: page,
|
|
pageSize: pageSize,
|
|
total: totalCount,
|
|
totalPages: Math.ceil(totalCount / pageSize),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error("DEBUG: API - Error fetching client details:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
static async getJobDetails() {
|
|
const data = DataUtils.dummyJobData.map((job) => ({
|
|
...job,
|
|
stepProgress: DataUtils.calculateStepProgress(job.steps),
|
|
}));
|
|
console.log("DEBUG: API - getJobDetails result: ", data);
|
|
return data;
|
|
}
|
|
|
|
static async getServiceData() {
|
|
const data = DataUtils.dummyServiceData;
|
|
console.log("DEBUG: API - getServiceData result: ", data);
|
|
return data;
|
|
}
|
|
|
|
static async getRouteData() {
|
|
const data = DataUtils.dummyRouteData;
|
|
console.log("DEBUG: API - getRouteData result: ", data);
|
|
return data;
|
|
}
|
|
|
|
static async getWarrantyData() {
|
|
const data = DataUtils.dummyWarrantyData;
|
|
console.log("DEBUG: API - getWarrantyData result: ", data);
|
|
return data;
|
|
}
|
|
|
|
static async getTimesheetData() {
|
|
const data = DataUtils.dummyTimesheetData;
|
|
console.log("DEBUG: API - getTimesheetData result: ", data);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get paginated client data with filtering and sorting
|
|
* @param {Object} paginationParams - Pagination parameters from store
|
|
* @param {Object} filters - Filter parameters from store
|
|
* @returns {Promise<{data: Array, totalRecords: number}>}
|
|
*/
|
|
static async getPaginatedClientDetails(paginationParams = {}, filters = {}) {
|
|
const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams;
|
|
|
|
const options = {
|
|
forTable: true,
|
|
page,
|
|
pageSize,
|
|
filters,
|
|
sortField,
|
|
sortOrder,
|
|
};
|
|
|
|
const result = await this.getClientDetails(options);
|
|
|
|
return {
|
|
data: result.data,
|
|
totalRecords: result.pagination.total,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Fetch a list of documents from a specific doctype.
|
|
*
|
|
* @param {String} doctype
|
|
* @param {string[]} fields
|
|
* @param {Object} filters
|
|
* @returns {Promise<Object[]>}
|
|
*/
|
|
static async getDocsList(doctype, fields = [], filters = {}, page = 0, pageLength = 600) {
|
|
const docs = await frappe.db.get_list(doctype, {
|
|
fields,
|
|
filters,
|
|
start: page * pageLength,
|
|
limit: pageLength,
|
|
});
|
|
console.log(`DEBUG: API - Fetched ${doctype} list: `, docs);
|
|
return docs;
|
|
}
|
|
|
|
/**
|
|
* Fetch a detailed document by doctype and name.
|
|
*
|
|
* @param {String} doctype
|
|
* @param {String} name
|
|
* @param {Object} filters
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
static async getDetailedDoc(doctype, name, filters = {}) {
|
|
const doc = await frappe.db.get_doc(doctype, name, filters);
|
|
console.log(`DEBUG: API - Fetched Detailed ${doctype}: `, doc);
|
|
return doc;
|
|
}
|
|
|
|
static async getDocCount(doctype, filters = {}) {
|
|
const count = await frappe.db.count(doctype, filters);
|
|
console.log(`DEBUG: API - Counted ${doctype}: `, count);
|
|
return count;
|
|
}
|
|
|
|
static async createDoc(doctype, data) {
|
|
const doc = await frappe.db.insert({
|
|
...data,
|
|
doctype,
|
|
});
|
|
console.log(`DEBUG: API - Created ${doctype}: `, doc);
|
|
return doc;
|
|
}
|
|
|
|
static async getCustomerNames() {
|
|
const customers = await this.getDocsList("Customer", ["name"]);
|
|
const customerNames = customers.map((customer) => customer.name);
|
|
console.log("DEBUG: API - Fetched Customer Names: ", customerNames);
|
|
return customerNames;
|
|
}
|
|
|
|
// Create methods
|
|
|
|
static async createClient(clientData) {
|
|
const payload = DataUtils.toSnakeCaseObject(clientData);
|
|
const result = await this.request(FRAPPE_UPSERT_CLIENT_METHOD, { data: payload });
|
|
console.log("DEBUG: API - Created/Updated Client: ", result);
|
|
return result;
|
|
}
|
|
|
|
// External API calls
|
|
|
|
/**
|
|
* Fetch a list of places (city/state) by zipcode using Zippopotamus API.
|
|
*
|
|
* @param {String} zipcode
|
|
* @returns {Promise<Object[]>}
|
|
*/
|
|
static async getCityStateByZip(zipcode) {
|
|
const url = `${ZIPPOPOTAMUS_BASE_URL}/${zipcode}`;
|
|
const response = await this.request(FRAPPE_PROXY_METHOD, { url, method: "GET" });
|
|
const { places } = response || {};
|
|
if (!places || places.length === 0) {
|
|
throw new Error(`No location data found for zip code ${zipcode}`);
|
|
}
|
|
return places;
|
|
}
|
|
}
|
|
|
|
export default Api;
|