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} */ 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} */ 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;