337 lines
9.3 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 = {}) {
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<Object[]>}
*/
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<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;