add query for client details

This commit is contained in:
Casey Wittrock 2025-11-13 15:17:43 -06:00
parent 172927e069
commit 0b280cec8e
8 changed files with 114 additions and 69 deletions

View File

@ -92,50 +92,90 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
def get_client(client_name): def get_client(client_name):
"""Get detailed information for a specific client including address, customer, and projects.""" """Get detailed information for a specific client including address, customer, and projects."""
try: try:
address = frappe.get_doc("Address", client_name) clientData = {"addresses": []}
customer_name = address.custom_customer_to_bill if address.custom_customer_to_bill else [link.link_name for link in address.links if link.link_doctype == "Customer"][0] if address.links else None customer = frappe.get_doc("Customer", client_name)
if not customer_name: clientData = {**clientData, **customer.as_dict()}
raise Exception(f"No customer linked to address {client_name}. Suggested fix: Ensure the address is linked to a customer via the ERPnext UI.") addresses = frappe.db.get_all("Address", fields=["*"], filters={"custom_customer_to_bill": client_name})
project_names = frappe.db.get_all("Project", fields=["name"], or_filters=[ for address in addresses if addresses else []:
["custom_installation_address", "=", address.address_title], addressData = {"jobs": []}
["custom_address", "=", address.address_title] addressData = {**addressData, **address}
], limit_page_length=100) addressData["estimates"] = frappe.db.get_all("Quotation", fields=["*"], filters={"custom_installation_address": address.address_title})
# contacts = [] # currently not needed as the customer doctype comes with contacts addressData["onsite_meetings"] = frappe.db.get_all("On-Site Meeting", fields=["*"], filters={"address": address.address_title})
onsite_meetings = frappe.db.get_all( jobs = frappe.db.get_all("Project", fields=["*"], or_filters=[
"On-Site Meeting", ["custom_installation_address", "=", address.address_title],
fields=["*"], ["custom_address", "=", address.address_title]
filters={"address": address.address_title} ])
) for job in jobs if jobs else []:
quotations = frappe.db.get_all( jobData = {}
"Quotation", jobData = {**jobData, **job}
fields=["*"], jobData["sales_invoices"] = frappe.db.get_all("Sales Invoice", fields=["*"], filters={"project": job.name})
filters={"custom_installation_address": address.address_title} jobData["payment_entries"] = frappe.db.get_all(
) "Payment Entry",
sales_orders = [] fields=["*"],
projects = [frappe.get_doc("Project", proj["name"]) for proj in project_names] filters={"party_type": "Customer"},
sales_invoices = [] or_filters=[
payment_entries = frappe.db.get_all( ["party", "=", client_name],
doctype="Payment Entry", ["party_name", "=", client_name]
fields=["*"], ])
filters={"party": customer_name}) jobData["sales_orders"] = frappe.db.get_all("Sales Order", fields=["*"], filters={"project": job.name})
payment_orders = [] jobData["tasks"] = frappe.db.get_all("Task", fields=["*"], filters={"project": job.name})
jobs = [] addressData["jobs"].append(jobData)
for project in projects: clientData["addresses"].append(addressData)
job = [] return build_success_response(clientData)
jobs.append(job) except frappe.ValidationError as ve:
customer = frappe.get_doc("Customer", customer_name) return build_error_response(str(ve), 400)
# get all associated data as needed except Exception as e:
return build_success_response({ return build_error_response(str(e), 500)
"address": address,
"customer": customer,
# "contacts": [], # currently not needed as the customer doctype comes with contacts # address = frappe.get_doc("Address", client_name)
"jobs": jobs, # customer_name = address.custom_customer_to_bill if address.custom_customer_to_bill else [link.link_name for link in address.links if link.link_doctype == "Customer"][0] if address.links else None
"sales_invoices": sales_invoices, # if not customer_name:
"payment_entries": payment_entries, # raise Exception(f"No customer linked to address {client_name}. Suggested fix: Ensure the address is linked to a customer via the ERPnext UI.")
"sales_orders": sales_orders, # project_names = frappe.db.get_all("Project", fields=["name"], or_filters=[
"quotations": quotations, # ["custom_installation_address", "=", address.address_title],
"onsite_meetings": onsite_meetings, # ["custom_address", "=", address.address_title]
}) # ], limit_page_length=100)
# # contacts = [] # currently not needed as the customer doctype comes with contacts
# onsite_meetings = frappe.db.get_all(
# "On-Site Meeting",
# fields=["*"],
# filters={"address": address.address_title}
# )
# quotations = frappe.db.get_all(
# "Quotation",
# fields=["*"],
# filters={"custom_installation_address": address.address_title}
# )
# sales_orders = []
# projects = [frappe.get_doc("Project", proj["name"]) for proj in project_names]
# sales_invoices = []
# payment_entries = frappe.db.get_all(
# "Payment Entry",
# fields=["*"],
# filters={"party_type": "Customer"},
# or_filters=[
# ["party", "=", customer_name],
# ["party_name", "=", customer_name]
# ])
# payment_orders = []
# jobs = []
# for project in projects:
# job = []
# jobs.append(job)
# customer = frappe.get_doc("Customer", customer_name)
# # get all associated data as needed
# return build_success_response({
# "address": address,
# "customer": customer,
# # "contacts": [], # currently not needed as the customer doctype comes with contacts
# "jobs": jobs,
# "sales_invoices": sales_invoices,
# "payment_entries": payment_entries,
# "sales_orders": sales_orders,
# "quotations": quotations,
# "onsite_meetings": onsite_meetings,
# })
except frappe.ValidationError as ve: except frappe.ValidationError as ve:
return build_error_response(str(ve), 400) return build_error_response(str(ve), 400)
except Exception as e: except Exception as e:

View File

@ -68,6 +68,7 @@ def process_filters(filters):
def process_sorting(sortings): def process_sorting(sortings):
sortings = json.loads(sortings) if isinstance(sortings, str) else sortings sortings = json.loads(sortings) if isinstance(sortings, str) else sortings
order_by = "" order_by = ""
print("DEBUG: Original sorting:", sortings)
if sortings and len(sortings) > 0: if sortings and len(sortings) > 0:
for sorting in sortings: for sorting in sortings:
mapped_field = map_field_name(sorting[0].strip()) mapped_field = map_field_name(sorting[0].strip())

View File

@ -2,10 +2,8 @@ import frappe
def after_insert(doc, method): def after_insert(doc, method):
print(doc.address) print(doc.address)
frappe.msgprint(f"On-Site Meeting '{doc.name}' has been created. Updating related records...") if doc.address:
address_name = frappe.db.get_value("Address", fieldname="name", filters={"address_line1": doc.address}) address_name = frappe.db.get_value("Address", fieldname="name", filters={"address_line1": doc.address})
address_doc = frappe.get_doc("Address", address_name) address_doc = frappe.get_doc("Address", address_name)
frappe.msgprint(f"Related Address '{address_doc.address_title}' has been retrieved.") address_doc.custom_onsite_meeting_scheduled = "Completed"
address_doc.custom_onsite_meeting_scheduled = "Completed" address_doc.save()
address_doc.save()
frappe.msgprint(f"Related Address '{address_doc.address_title}' has been updated with custom_onsite_meeting_scheduled = 'Completed'.")

View File

@ -10,10 +10,9 @@ def after_insert(doc, method):
def after_save(doc, method): def after_save(doc, method):
if doc.custome_sent: address_title = doc.custom_installation_address
address_title = doc.custom_installation_address address_name = frappe.db.get_value("Address", fieldname="name", filters={"address_title": address_title})
address_name = frappe.db.get_value("Address", fieldname="name", filters={"address_title": address_title}) if doc.custome_sent and address_name:
if address_name: address_doc = frappe.get_doc("Address", address_name)
address_doc = frappe.get_doc("Address", address_name) address_doc.custom_quotation_sent = "Completed"
address_doc.custom_quotation_sent = "Completed" address_doc.save()
address_doc.save()

View File

@ -1,4 +1,5 @@
import ApiUtils from "./apiUtils"; import ApiUtils from "./apiUtils";
import { useErrorStore } from "./stores/errors";
const ZIPPOPOTAMUS_BASE_URL = "https://api.zippopotam.us/us"; const ZIPPOPOTAMUS_BASE_URL = "https://api.zippopotam.us/us";
const FRAPPE_PROXY_METHOD = "custom_ui.api.proxy.request"; const FRAPPE_PROXY_METHOD = "custom_ui.api.proxy.request";
@ -15,6 +16,7 @@ const FRAPPE_GET_CLIENT_NAMES_METHOD = "custom_ui.api.db.clients.get_client_name
class Api { class Api {
static async request(frappeMethod, args = {}) { static async request(frappeMethod, args = {}) {
const errorStore = useErrorStore();
args = ApiUtils.toSnakeCaseObject(args); args = ApiUtils.toSnakeCaseObject(args);
const request = { method: frappeMethod, args }; const request = { method: frappeMethod, args };
console.log("DEBUG: API - Request Args: ", request); console.log("DEBUG: API - Request Args: ", request);
@ -28,6 +30,7 @@ class Api {
return response.message.data; return response.message.data;
} catch (error) { } catch (error) {
console.error("ERROR: API - Request Error: ", error); console.error("ERROR: API - Request Error: ", error);
errorStore.setApiError("Frappe API", error.message || "API request error");
throw error; throw error;
} }
} }
@ -112,11 +115,11 @@ class Api {
* @param {Object} sorting - Sorting parameters from store (optional) * @param {Object} sorting - Sorting parameters from store (optional)
* @returns {Promise<{data: Array, pagination: Object}>} * @returns {Promise<{data: Array, pagination: Object}>}
*/ */
static async getPaginatedClientDetails(paginationParams = {}, filters = {}, sorting = []) { static async getPaginatedClientDetails(paginationParams = {}, filters = {}, sortings = []) {
const { page = 0, pageSize = 10 } = paginationParams; const { page = 0, pageSize = 10 } = paginationParams;
const result = await this.request(FRAPPE_GET_CLIENT_TABLE_DATA_METHOD, { const result = await this.request(FRAPPE_GET_CLIENT_TABLE_DATA_METHOD, {
filters, filters,
sorting, sortings,
page: page + 1, page: page + 1,
pageSize, pageSize,
}); });

View File

@ -3,7 +3,7 @@ class ApiUtils {
console.log("Converting to snake case:", obj); console.log("Converting to snake case:", 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());
if (key === "sorting") { if (key === "sortings") {
value = value value = value
? value.map((item) => { ? value.map((item) => {
const [field, order] = item; const [field, order] = item;

View File

@ -37,10 +37,9 @@ import TabPanel from "primevue/tabpanel";
import Api from "../../api"; import Api from "../../api";
import ApiWithToast from "../../api-toast"; import ApiWithToast from "../../api-toast";
import { useLoadingStore } from "../../stores/loading"; import { useLoadingStore } from "../../stores/loading";
import { useErrorStore } from "../../stores/errors";
const loadingStore = useLoadingStore(); const loadingStore = useLoadingStore();
const errorStore = useErrorStore();
const clientNames = ref([]); const clientNames = ref([]);
const client = ref({}); const client = ref({});
const { clientName } = defineProps({ const { clientName } = defineProps({
@ -50,10 +49,10 @@ const { clientName } = defineProps({
const getClientNames = async (type) => { const getClientNames = async (type) => {
loadingStore.setLoading(true); loadingStore.setLoading(true);
try { try {
const names = await Api.getCustomerNames(type); const names = await Api.getClientNames(type);
clientNames.value = names; clientNames.value = names;
} catch (error) { } catch (error) {
errorStore.addError(error.message || "Error fetching client names"); console.error("Error fetching client names in Client.vue: ", error.message || error);
} finally { } finally {
loadingStore.setLoading(false); loadingStore.setLoading(false);
} }
@ -61,9 +60,14 @@ const getClientNames = async (type) => {
const getClient = async (name) => { const getClient = async (name) => {
loadingStore.setLoading(true); loadingStore.setLoading(true);
const clientData = await ApiWithToast.makeApiCall(() => Api.getClient(name)); try {
client.value = clientData || {}; const clientData = await Api.getClient(name);
loadingStore.setLoading(false); client.value = clientData || {};
} catch (error) {
console.error("Error fetching client data in Client.vue: ", error.message || error);
} finally {
loadingStore.setLoading(false);
}
}; };
onMounted(async () => { onMounted(async () => {

View File

@ -165,7 +165,7 @@ const tableActions = [
{ {
label: "View Details", label: "View Details",
action: (rowData) => { action: (rowData) => {
router.push(`/clients/${rowData.id}`); router.push(`/clients/${rowData.customerName}`);
}, },
type: "button", type: "button",
style: "info", style: "info",