diff --git a/custom_ui/api/db/addresses.py b/custom_ui/api/db/addresses.py index 17da89e..3244836 100644 --- a/custom_ui/api/db/addresses.py +++ b/custom_ui/api/db/addresses.py @@ -1,6 +1,43 @@ import frappe from custom_ui.db_utils import build_error_response, build_success_response +@frappe.whitelist() +def get_address_by_full_address(full_address): + """Get address by full_address, including associated contacts.""" + try: + address = frappe.get_doc("Address", {"full_address": full_address}).as_dict() + address["customer"] = frappe.get_doc("Customer", address.get("custom_customer_to_bill")).as_dict() + contacts = [] + for contact_link in address.custom_linked_contacts: + contact_doc = frappe.get_doc("Contact", contact_link.contact) + contacts.append(contact_doc.as_dict()) + address["contacts"] = contacts + return build_success_response(address) + except Exception as e: + return build_error_response(str(e), 500) + +@frappe.whitelist() +def get_address(address_name): + """Get a specific address by name.""" + try: + address = frappe.get_doc("Address", address_name) + return build_success_response(address.as_dict()) + except Exception as e: + return build_error_response(str(e), 500) + +@frappe.whitelist() +def get_contacts_for_address(address_name): + """Get contacts linked to a specific address.""" + try: + address = frappe.get_doc("Address", address_name) + contacts = [] + for contact_link in address.custom_linked_contacts: + contact = frappe.get_doc("Contact", contact_link.contact) + contacts.append(contact.as_dict()) + return build_success_response(contacts) + except Exception as e: + return build_error_response(str(e), 500) + @frappe.whitelist() def get_addresses(fields=["*"], filters={}): """Get addresses with optional filtering.""" diff --git a/custom_ui/api/db/clients.py b/custom_ui/api/db/clients.py index cb7165c..7d4dbb7 100644 --- a/custom_ui/api/db/clients.py +++ b/custom_ui/api/db/clients.py @@ -92,96 +92,45 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N def get_client(client_name): """Get detailed information for a specific client including address, customer, and projects.""" try: - clientData = {"addresses": []} + clientData = {"addresses": [], "contacts": [], "jobs": [], "sales_invoices": [], "payment_entries": [], "sales_orders": [], "tasks": []} customer = frappe.get_doc("Customer", client_name) clientData = {**clientData, **customer.as_dict()} - addresses = frappe.db.get_all("Address", fields=["*"], filters={"custom_customer_to_bill": client_name}) - contacts = frappe.db.get_all("Contact", fields=["*"], filters={"custom_customer": client_name}) - clientData["contacts"] = contacts - for address in addresses if addresses else []: - addressData = {"jobs": []} - addressData = {**addressData, **address} - addressData["estimates"] = frappe.db.get_all("Quotation", fields=["*"], filters={"custom_installation_address": address.address_title}) - addressData["onsite_meetings"] = frappe.db.get_all("On-Site Meeting", fields=["*"], filters={"address": address.address_title}) - jobs = frappe.db.get_all("Project", fields=["*"], or_filters=[ - ["custom_installation_address", "=", address.address_title], - ["custom_address", "=", address.address_title] - ]) - for job in jobs if jobs else []: - jobData = {} - jobData = {**jobData, **job} - jobData["sales_invoices"] = frappe.db.get_all("Sales Invoice", fields=["*"], filters={"project": job.name}) - jobData["payment_entries"] = frappe.db.get_all( - "Payment Entry", - fields=["*"], - filters={"party_type": "Customer"}, - or_filters=[ - ["party", "=", client_name], - ["party_name", "=", client_name] - ]) - jobData["sales_orders"] = frappe.db.get_all("Sales Order", fields=["*"], filters={"project": job.name}) - jobData["tasks"] = frappe.db.get_all("Task", fields=["*"], filters={"project": job.name}) - addressData["jobs"].append(jobData) - clientData["addresses"].append(addressData) + + for contact_link in customer.custom_add_contacts: + contact_doc = frappe.get_doc("Contact", contact_link.contact) + clientData["contacts"].append(contact_doc.as_dict()) + + for address_link in customer.custom_select_address: + address_doc = frappe.get_doc("Address", address_link.address_name) + # # addressData = {"jobs": [], "contacts": []} + # addressData = {**addressData, **address_doc.as_dict()} + # addressData["estimates"] = frappe.db.get_all("Quotation", fields=["*"], filters={"custom_installation_address": address_doc.address_title}) + # addressData["onsite_meetings"] = frappe.db.get_all("On-Site Meeting", fields=["*"], filters={"address": address_doc.address_title}) + # jobs = frappe.db.get_all("Project", fields=["*"], or_filters=[ + # ["custom_installation_address", "=", address.address_title], + # ["custom_address", "=", address.address_title] + # ]) + # for job in jobs if jobs else []: + # jobData = {} + # jobData = {**jobData, **job} + # jobData["sales_invoices"] = frappe.db.get_all("Sales Invoice", fields=["*"], filters={"project": job.name}) + # jobData["payment_entries"] = frappe.db.get_all( + # "Payment Entry", + # fields=["*"], + # filters={"party_type": "Customer"}, + # or_filters=[ + # ["party", "=", client_name], + # ["party_name", "=", client_name] + # ]) + # jobData["sales_orders"] = frappe.db.get_all("Sales Order", fields=["*"], filters={"project": job.name}) + # jobData["tasks"] = frappe.db.get_all("Task", fields=["*"], filters={"project": job.name}) + # addressData["jobs"].append(jobData) + clientData["addresses"].append(address_doc.as_dict()) return build_success_response(clientData) except frappe.ValidationError as ve: return build_error_response(str(ve), 400) except Exception as e: return build_error_response(str(e), 500) - - - # address = frappe.get_doc("Address", client_name) - # 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 - # if not customer_name: - # raise Exception(f"No customer linked to address {client_name}. Suggested fix: Ensure the address is linked to a customer via the ERPnext UI.") - # project_names = frappe.db.get_all("Project", fields=["name"], or_filters=[ - # ["custom_installation_address", "=", address.address_title], - # ["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: - return build_error_response(str(ve), 400) - except Exception as e: - return build_error_response(str(e), 500) @frappe.whitelist() def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10): @@ -253,10 +202,11 @@ def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10): def upsert_client(data): """Create or update a client (customer and address).""" try: - data = json.loads(data) # Handle customer creation/update + print("#####DEBUG: Upsert client data received:", data) + print("#####DEBUG: Checking for existing customer with name:", data.get("customer_name")) customer = frappe.db.exists("Customer", {"customer_name": data.get("customer_name")}) if not customer: customer_doc = frappe.get_doc({ @@ -266,21 +216,20 @@ def upsert_client(data): }).insert(ignore_permissions=True) else: customer_doc = frappe.get_doc("Customer", data.get("customer_name")) - print("Customer:", customer_doc.as_dict()) - # Check for existing address - filters = { - "address_line1": data.get("address_line1"), - "city": data.get("city"), - "state": data.get("state"), - } - existing_address = frappe.db.exists("Address", filters) + # Handle address creation + print("#####DEBUG: Checking for existing address for customer:", data.get("customer_name")) + existing_address = frappe.db.exists( + "Address", + { + "address_line1": data.get("address_line1"), + "city": data.get("city"), + "state": data.get("state"), + }) print("Existing address check:", existing_address) if existing_address: frappe.throw(f"Address already exists for customer {data.get('customer_name')}.", frappe.ValidationError) - - # Create address address_doc = frappe.get_doc({ "doctype": "Address", "address_title": data.get("address_title"), @@ -292,37 +241,97 @@ def upsert_client(data): "pincode": data.get("pincode"), "custom_customer_to_bill": customer_doc.name }).insert(ignore_permissions=True) + print("Address:", address_doc.as_dict()) - # Link address to customer - link = { + #Handle contact creation + contact_docs = [] + for contact_data in data.get("contacts", []): + if isinstance(contact_data, str): + contact_data = json.loads(contact_data) + print("#####DEBUG: Processing contact data:", contact_data) + contact_exists = frappe.db.exists("Contact", {"email_id": contact_data.get("email"), "phone": contact_data.get("phone_number")}) + if not contact_exists: + is_primary_contact = 1 if contact_data.get("is_primary_contact") else 0 + contact_doc = frappe.get_doc({ + "doctype": "Contact", + "first_name": contact_data.get("first_name"), + "last_name": contact_data.get("last_name"), + "email_id": contact_data.get("email"), + "phone": contact_data.get("phone_number"), + "custom_customer": customer_doc.name, + "role": contact_data.get("contact_role", "Other"), + "custom_email": contact_data.get("email"), + "is_primary_contact": is_primary_contact + }).insert(ignore_permissions=True) + print("Created new contact:", contact_doc.as_dict()) + else: + contact_doc = frappe.get_doc("Contact", {"email_id": data.get("email")}) + print("Contact already exists:", contact_doc.as_dict()) + contact_docs.append(contact_doc) + + ##### Create links + # Customer -> Address + print("#####DEBUG: Creating links between customer, address, and contacts.") + customer_doc.append("custom_select_address", { + "address_name": address_doc.name, + "address_line_1": address_doc.address_line1, + "city": address_doc.city, + "state": address_doc.state, + "pincode": address_doc.pincode + }) + + # Customer -> Contact + print("#####DEBUG: Linking contacts to customer.") + for contact_doc in contact_docs: + print("Linking contact:", contact_doc.as_dict()) + print("with role:", contact_doc.role) + print("customer to append to:", customer_doc.as_dict()) + customer_doc.append("custom_add_contacts", { + "contact": contact_doc.name, + "email": contact_doc.custom_email, + "phone": contact_doc.phone, + "role": contact_doc.role + }) + + # Address -> Customer + print("#####DEBUG: Linking address to customer.") + address_doc.append("links", { "link_doctype": "Customer", "link_name": customer_doc.name - } - address_doc.append("links", link) - contact_exists = frappe.db.exists("Contact", {"email_id": data.get("contact_email")}) - if not contact_exists: - contact_doc = frappe.get_doc({ - "doctype": "Contact", - "first_name": data.get("first_name"), - "last_name": data.get("last_name"), - "email_id": data.get("email"), - "phone": data.get("phone_number"), - "custom_customer": customer_doc.name, - "links": [{ - "link_doctype": "Customer", - "link_name": customer_doc.name - }] - }).insert(ignore_permissions=True) - print("Created new contact:", contact_doc.as_dict()) - else: - contact_doc = frappe.get_doc("Contact", {"email_id": data.get("contact_email")}) - print("Contact already exists:", contact_doc.as_dict()) - address_doc.custom_contact = contact_doc.name + }) + + # Address -> Contact + print("#####DEBUG: Linking address to contacts.") + address_doc.custom_contact = next((c.name for c in contact_docs if c.is_primary_contact), contact_docs[0].name) + for contact_doc in contact_docs: + address_doc.append("custom_linked_contacts", { + "contact": contact_doc.name, + "email": contact_doc.email_id, + "phone": contact_doc.phone, + "role": contact_doc.role + }) + + # Contact -> Customer & Address + print("#####DEBUG: Linking contacts to customer.") + for contact_doc in contact_docs: + contact_doc.append("links", { + "link_doctype": "Customer", + "link_name": customer_doc.name + }) + contact_doc.append("links", { + "link_doctype": "Address", + "link_name": address_doc.name + }) + contact_doc.custom_customer = customer_doc.name + contact_doc.save(ignore_permissions=True) + address_doc.save(ignore_permissions=True) + customer_doc.save(ignore_permissions=True) + return build_success_response({ "customer": customer_doc.as_dict(), "address": address_doc.as_dict(), - "contact": contact_doc.as_dict() + "contacts": [contact_doc.as_dict() for contact_doc in contact_docs] }) except frappe.ValidationError as ve: return build_error_response(str(ve), 400) diff --git a/custom_ui/api/db/estimates.py b/custom_ui/api/db/estimates.py index d9ad39e..1e9b16a 100644 --- a/custom_ui/api/db/estimates.py +++ b/custom_ui/api/db/estimates.py @@ -1,5 +1,5 @@ import frappe, json -from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response +from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response # =============================================================================== # ESTIMATES & INVOICES API METHODS @@ -47,15 +47,72 @@ def get_estimate_table_data(filters={}, sortings=[], page=1, page_size=10): return build_success_response(table_data_dict) +@frappe.whitelist() +def get_quotation_items(): + """Get all available quotation items.""" + try: + items = frappe.get_all("Item", fields=["*"], filters={"item_group": "SNW-S"}) + return build_success_response(items) + except Exception as e: + return build_error_response(str(e), 500) + +@frappe.whitelist() +def get_estimate(estimate_name): + """Get detailed information for a specific estimate.""" + try: + estimate = frappe.get_doc("Quotation", estimate_name) + return build_success_response(estimate.as_dict()) + except Exception as e: + return build_error_response(str(e), 500) + @frappe.whitelist() def upsert_estimate(data): """Create or update an estimate.""" # TODO: Implement estimate creation/update logic pass +@frappe.whitelist() +def get_estimate_items(): + items = frappe.db.get_all("Quotation Item", fields=["*"]) + return build_success_response(items) @frappe.whitelist() -def upsert_invoice(data): - """Create or update an invoice.""" - # TODO: Implement invoice creation/update logic - pass +def get_estimate_from_address(full_address): + quotation = frappe.db.sql(""" + SELECT q.name, q.custom_installation_address + FROM `tabQuotation` q + JOIN `tabAddress` a + ON q.custom_installation_address = a.name + WHERE a.full_address =%s + """, (full_address,), as_dict=True) + if quotation: + return build_success_response(quotation) + else: + return build_error_response("No quotation found for the given address.", 404) + +@frappe.whitelist() +def upsert_estimate(data): + """Create or update an estimate.""" + print("DOIFJSEOFJISLFK") + try: + data = json.loads(data) if isinstance(data, str) else data + print("DEBUG: Upsert estimate data received:", data) + address_name = frappe.get_value("Address", fieldname="name", filters={"full_address": data.get("address")}) + new_estimate = frappe.get_doc({ + "doctype": "Quotation", + "custom_installation_address": address_name, + "contact_email": data.get("contact_email"), + "party_name": data.get("contact_name"), + "customer_name": data.get("customer_name"), + }) + for item in data.get("items", []): + item = json.loads(item) if isinstance(item, str) else item + new_estimate.append("items", { + "item_code": item.get("item_code"), + "qty": item.get("qty"), + }) + new_estimate.insert() + print("DEBUG: New estimate created with name:", new_estimate.name) + return build_success_response(new_estimate.as_dict()) + except Exception as e: + return build_error_response(str(e), 500) \ No newline at end of file diff --git a/custom_ui/api/db/onsite_meetings.py b/custom_ui/api/db/onsite_meetings.py index d5fc705..fddaf09 100644 --- a/custom_ui/api/db/onsite_meetings.py +++ b/custom_ui/api/db/onsite_meetings.py @@ -15,6 +15,9 @@ def get_week_onsite_meetings(week_start, week_end): ], order_by="start_time asc" ) + for meeting in meetings: + address_doc = frappe.get_doc("Address", meeting["address"]) + meeting["address"] = address_doc.as_dict() return build_success_response(meetings) except Exception as e: frappe.log_error(message=str(e), title="Get Week On-Site Meetings Failed") @@ -34,6 +37,9 @@ def get_onsite_meetings(fields=["*"], filters={}): filters=processed_filters, order_by="creation desc" ) + for meeting in meetings: + address_doc = frappe.get_doc("Address", meeting["address"]) + meeting["address"] = address_doc.as_dict() return build_success_response( meetings diff --git a/custom_ui/install.py b/custom_ui/install.py index cdcc50d..c814eba 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -115,6 +115,22 @@ def add_custom_fields(): insert_after="job_status" ) ], + "Contact": [ + dict( + fieldname="role", + label="Role", + fieldtype="Select", + options="Owner\nProperty Manager\nTenant\nBuilder\nNeighbor\nFamily Member\nRealtor\nOther", + insert_after="designation" + ), + dict( + fieldname="email", + label="Email", + fieldtype="Data", + insert_after="last_name", + options="Email" + ) + ], "On-Site Meeting": [ dict( fieldname="notes", diff --git a/frontend/src/api.js b/frontend/src/api.js index a273954..cc25e51 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -49,6 +49,42 @@ class Api { } } + static async getAddressByFullAddress(fullAddress) { + return await this.request("custom_ui.api.db.addresses.get_address_by_full_address", { + full_address: fullAddress, + }); + } + + static async getQuotationItems() { + return await this.request("custom_ui.api.db.estimates.get_quotation_items"); + } + + static async getEstimateFromAddress(fullAddress) { + return await this.request("custom_ui.api.db.estimates.get_estimate_from_address", { + full_address: fullAddress, + }); + } + + static async getAddress(fullAddress) { + return await this.request("custom_ui.api.db.addresses.get_address", { fullAddress }); + } + + static async getContactsForAddress(fullAddress) { + return await this.request("custom_ui.api.db.addresses.get_contacts_for_address", { + fullAddress, + }); + } + + static async getEstimate(estimateName) { + return await this.request("custom_ui.api.db.estimates.get_estimate", { + estimate_name: estimateName, + }); + } + + static async getEstimateItems() { + return await this.request("custom_ui.api.db.estimates.get_estimate_items"); + } + static async searchAddresses(searchTerm) { const filters = { full_address: ["like", `%${searchTerm}%`], @@ -201,7 +237,6 @@ class Api { const result = await this.request(FRAPPE_GET_ESTIMATES_METHOD, { options }); return result; - } /** @@ -352,9 +387,7 @@ class Api { } static async createEstimate(estimateData) { - const payload = DataUtils.toSnakeCaseObject(estimateData); - const result = await this.request(FRAPPE_UPSERT_ESTIMATE_METHOD, { data: payload }); - console.log("DEBUG: API - Created Estimate: ", result); + const result = await this.request(FRAPPE_UPSERT_ESTIMATE_METHOD, { data: estimateData }); return result; } diff --git a/frontend/src/components/SideBar.vue b/frontend/src/components/SideBar.vue index 4983c10..08750a5 100644 --- a/frontend/src/components/SideBar.vue +++ b/frontend/src/components/SideBar.vue @@ -54,7 +54,7 @@ const createButtons = ref([ label: "Estimate", command: () => { //frappe.new_doc("Estimate"); - router.push("/createEstimate/new"); + router.push("/estimate?new=true"); }, }, { diff --git a/frontend/src/components/clientSubPages/ClientInformationForm.vue b/frontend/src/components/clientSubPages/ClientInformationForm.vue index 04132ae..9be730e 100644 --- a/frontend/src/components/clientSubPages/ClientInformationForm.vue +++ b/frontend/src/components/clientSubPages/ClientInformationForm.vue @@ -2,10 +2,10 @@