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 = {}) { args = DataUtils.toSnakeCaseObject(args); try { let response = await frappe.call({ method: frappeMethod, args: { ...args, }, }); response = DataUtils.toCamelCaseObject(response); 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 = {}) { return await this.request("custom_ui.api.db.get_clients", { options }); // Handle fullName filter by searching in multiple fields // Since fullName is constructed from customer_name + address_line1 + city + state, // we need to search across these fields if (filters.addressTitle && filters.addressTitle.value) { const searchTerm = filters.addressTitle.value; // Search in address fields - this is a simplified approach // In a real implementation, you'd want to join with Customer table and search across all fields addressFilters.address_line1 = ["like", `%${searchTerm}%`]; } // Add any other custom filters Object.keys(filters).forEach((key) => { if (filters[key] && filters[key].value && key !== "fullName") { // Map other frontend filter names to backend field names if needed switch ( key // 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; }); } // Since we're applying filters at the database level, use the fetched data as-is let filteredData = data; console.log("DEBUG: API - Fetched Client Details:", { total: totalCount, page: page, pageSize: pageSize, returned: filteredData.length, filtersApplied: Object.keys(addressFilters).length > 0, }); // 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), //})); const projects = await this.getDocsList("Project"); const data = []; for (let prj of projects) { let project = await this.getDetailedDoc("Project", prj.name); const tableRow = { name: project.name, customInstallationAddress: project.custom_installation_address, customer: project.customer, status: project.status, percentComplete: project.percent_complete, }; data.push(tableRow); } 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 = { page: page + 1, pageSize, filters, sortField, sortOrder, }; const result = await this.getClientDetails(options); return { ...result, }; } /** * Fetch a list of documents from a specific doctype. * * @param {String} doctype * @param {string[]} fields * @param {Object} filters * @returns {Promise} */ static async getDocsList(doctype, fields = [], filters = {}, page = 0, pageLength = 0) { const docs = await frappe.db.get_list(doctype, { fields, filters, start: start, limit: pageLength, }); console.log( `DEBUG: API - Fetched ${doctype} list (page ${page + 1}, start ${start}): `, docs, ); return docs; } /** * Fetch a detailed document by doctype and name. * * @param {String} doctype * @param {String} name * @param {Object} filters * @returns {Promise} */ 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} */ 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;