big update

This commit is contained in:
Casey 2026-01-15 08:36:08 -06:00
parent d53ebf9ecd
commit 5c7e93fcc7
26 changed files with 1890 additions and 423 deletions

View file

@ -1,31 +1,15 @@
import frappe
import json
from custom_ui.db_utils import build_error_response, build_success_response
from custom_ui.services import ClientService, AddressService
@frappe.whitelist()
def get_address_by_full_address(full_address):
"""Get address by full_address, including associated contacts."""
print(f"DEBUG: get_address_by_full_address called with full_address: {full_address}")
try:
address = frappe.get_doc("Address", {"full_address": full_address}).as_dict()
customer_exists = frappe.db.exists("Customer", address.get("custom_customer_to_bill"))
doctype = "Customer" if customer_exists else "Lead"
name = ""
if doctype == "Customer":
name = address.get("custom_customer_to_bill")
else:
## filter through links for one with doctype Lead
lead_links = address.get("links", [])
print(f"DEBUG: lead_links: {lead_links}")
lead_name = [link.link_name for link in lead_links if link.link_doctype == "Lead"]
name = lead_name[0] if lead_name else ""
address["customer"] = frappe.get_doc(doctype, name).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)
address = AddressService.get_address_by_full_address(full_address)
return build_success_response(AddressService.build_full_dict(address))
except Exception as e:
return build_error_response(str(e), 500)
@ -33,23 +17,23 @@ def get_address_by_full_address(full_address):
def get_address(address_name):
"""Get a specific address by name."""
try:
address = frappe.get_doc("Address", address_name)
address = AddressService.get_or_throw(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() #### DEPRECATED FUNCTION
# def get_contacts_for_address(address_name):
# """Get contacts linked to a specific address."""
# try:
# address = AddressService.get_or_throw(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={}):
@ -74,16 +58,6 @@ def get_addresses(fields=["*"], filters={}):
except Exception as e:
frappe.log_error(message=str(e), title="Get Addresses Failed")
return build_error_response(str(e), 500)
def create_address(address_data):
"""Create a new address."""
address = frappe.get_doc({
"doctype": "Address",
**address_data
})
address.insert(ignore_permissions=True)
return address
def update_address(address_data):
"""Update an existing address."""
@ -106,19 +80,10 @@ def address_exists(address_line1, address_line2, city, state, pincode):
}
return frappe.db.exists("Address", filters) is not None
def check_and_get_address_by_name(address_name):
"""Check if an address exists by name and return the address document if found."""
if frappe.db.exists("Address", address_name):
return frappe.get_doc("Address", address_name)
raise ValueError(f"Address with name {address_name} does not exist.")
def address_exists_by_name(address_name):
"""Check if an address with the given name exists."""
return frappe.db.exists("Address", address_name) is not None
def calculate_address_title(customer_name, address_data):
return f"{customer_name} - {address_data.get('address_line1', '')}, {address_data.get('city', '')} - {address_data.get('type', '')}"
def create_address_links(address_doc, client_doc, contact_docs):
print("#####DEBUG: Linking customer to address.")
print("#####DEBUG: Client Doc:", client_doc.as_dict(), "Address Doc:", address_doc.as_dict(), "Contact Docs:", [c.as_dict() for c in contact_docs])

View file

@ -1,7 +1,7 @@
import frappe
import json
from custom_ui.db_utils import build_error_response, build_success_response, process_filters, process_sorting
from custom_ui.services import DbService
from custom_ui.services import DbService, ClientService, AddressService
@frappe.whitelist()
def get_week_bid_meetings(week_start, week_end):
@ -67,38 +67,51 @@ def get_unscheduled_bid_meetings():
@frappe.whitelist()
def create_bid_meeting(address, notes="", company=None, contact=None):
"""Create a new On-Site Meeting with Unscheduled status."""
def get_bid_meeting(name):
"""Get a specific On-Site Meeting by name."""
try:
print(f"DEBUG: Creating meeting with address='{address}', notes='{notes}', company='{company}'")
# Validate address parameter
if not address or address == "None" or not address.strip():
return build_error_response("Address is required and cannot be empty.", 400)
# Get the address document name from the full address string
address_name = frappe.db.get_value("Address", filters={"full_address": address}, fieldname="name")
meeting = frappe.get_doc("On-Site Meeting", name)
meeting_dict = meeting.as_dict()
print(f"DEBUG: Address lookup result: address_name='{address_name}'")
# Get the full address data
if meeting_dict.get("address"):
address_doc = frappe.get_doc("Address", meeting_dict["address"])
meeting_dict["address"] = address_doc.as_dict()
if not address_name:
return build_error_response(f"Address '{address}' not found in the system.", 404)
address_doc = DbService.get("Address", address_name)
return build_success_response(meeting_dict)
except frappe.DoesNotExistError:
return build_error_response(f"On-Site Meeting '{name}' does not exist.", 404)
except Exception as e:
frappe.log_error(message=str(e), title="Get On-Site Meeting Failed")
return build_error_response(str(e), 500)
@frappe.whitelist()
def create_bid_meeting(data):
"""Create a new On-Site Meeting with Unscheduled status."""
if isinstance(data, str):
data = json.loads(data)
try:
print(f"DEBUG: Creating meeting with data='{data}'")
address_doc = DbService.get_or_throw("Address", data.get("address"))
# Create the meeting with Unscheduled status
meeting = frappe.get_doc({
"doctype": "On-Site Meeting",
"address": address_doc.name,
"notes": notes or "",
"notes": data.get("notes") or "",
"status": "Unscheduled",
"company": company,
"contact": contact,
"company": data.get("company"),
"contact": data.get("contact"),
"party_type": address_doc.customer_type,
"party_name": address_doc.customer_name
"party_name": address_doc.customer_name,
"project_template": data.get("project_template")
})
meeting.flags.ignore_permissions = True
meeting.insert(ignore_permissions=True)
# ClientService.append_link(address_doc.customer_name, "onsite_meetings", "onsite_meeting", meeting.name)
# AddressService.append_link(address_doc.name, "onsite_meetings", "onsite_meeting", meeting.name)
meeting.flags.ignore_permissions = True
frappe.db.commit()
# Clear any auto-generated messages from Frappe

View file

@ -1,8 +1,9 @@
import frappe, json
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client, build_address_title
from erpnext.crm.doctype.lead.lead import make_customer
from custom_ui.api.db.addresses import address_exists, create_address, create_address_links
from custom_ui.api.db.addresses import address_exists
from custom_ui.api.db.contacts import check_and_get_contact, create_contact, create_contact_links
from custom_ui.services import AddressService, ContactService, ClientService
# ===============================================================================
# CLIENT MANAGEMENT API METHODS
@ -96,7 +97,7 @@ def get_client(client_name):
"""Get detailed information for a specific client including address, customer, and projects."""
print("DEBUG: get_client called with client_name:", client_name)
try:
clientData = {"addresses": [], "contacts": [], "jobs": [], "sales_invoices": [], "payment_entries": [], "sales_orders": [], "tasks": []}
clientData = {"addresses": [], "contacts": [], "jobs": [], "sales_invoices": [], "payment_entries": [], "tasks": []}
customer = check_and_get_client_doc(client_name)
if not customer:
return build_error_response(f"Client with name '{client_name}' does not exist.", 404)
@ -142,6 +143,30 @@ def get_client(client_name):
return build_error_response(str(ve), 400)
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_client_v2(client_name):
"""Get detailed information for a specific client including address, customer, and projects."""
print("DEBUG: get_client_v2 called with client_name:", client_name)
try:
clientData = {"addresses": [], "jobs": [], "payment_entries": [], "tasks": []}
customer = check_and_get_client_doc(client_name)
if not customer:
return build_error_response(f"Client with name '{client_name}' does not exist.", 404)
print("DEBUG: Retrieved customer/lead document:", customer.as_dict())
clientData = {**clientData, **customer.as_dict()}
clientData["contacts"] = [ContactService.get_or_throw(link.contact) for link in clientData["contacts"]]
clientData["addresses"] = [AddressService.get_or_throw(link.address) for link in clientData["properties"]]
if clientData["doctype"] == "Lead":
clientData["customer_name"] = customer.custom_customer_name
# TODO: Continue getting other linked docs like jobs, invoices, etc.
print("DEBUG: Final client data prepared:", clientData)
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)
@frappe.whitelist()
@ -189,8 +214,7 @@ def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10):
customer_links = [link for link in links if link.link_doctype == "Lead"] if links else None
is_lead = True if customer_links else False
if not customer_name and not customer_links:
print("DEBUG: No customer links found and no customer to bill.")
customer_name = "N/A"
customer_name = frappe.get_value("Lead", address.get("customer_name"), "custom_customer_name")
elif not customer_name and customer_links:
print("DEBUG: No customer to bill. Customer links found:", customer_links)
customer_name = frappe.get_value("Lead", customer_links[0].link_name, "custom_customer_name") if is_lead else customer_links[0].link_name
@ -202,9 +226,9 @@ def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10):
f"{address['city']}, {address['state']} {address['pincode']}"
)
tableRow["client_type"] = "Lead" if is_lead else "Customer"
tableRow["appointment_scheduled_status"] = address.custom_onsite_meeting_scheduled
tableRow["estimate_sent_status"] = address.custom_estimate_sent_status
tableRow["job_status"] = address.custom_job_status
# tableRow["appointment_scheduled_status"] = address.custom_onsite_meeting_scheduled
# tableRow["estimate_sent_status"] = address.custom_estimate_sent_status
# tableRow["job_status"] = address.custom_job_status
tableRow["payment_received_status"] = address.custom_payment_received_status
tableRows.append(tableRow)
tableDataDict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
@ -256,19 +280,21 @@ def upsert_client(data):
customer_name = data.get("customer_name")
contacts = data.get("contacts", [])
addresses = data.get("addresses", [])
# Check for existing address
client_doc = check_and_get_client_doc(customer_name)
if client_doc:
return build_error_response(f"Client with name '{customer_name}' already exists.", 400)
if address_exists(
data.get("address_line1"),
data.get("address_line2"),
data.get("city"),
data.get("state"),
data.get("pincode")
):
return build_error_response("This address already exists. Please use a different address or search for the address to find the associated client.", 400)
for address in addresses:
if address_exists(
address.get("address_line1"),
address.get("address_line2"),
address.get("city"),
address.get("state"),
address.get("pincode")
):
return build_error_response("This address already exists. Please use a different address or search for the address to find the associated client.", 400)
# Handle customer creation/update
@ -280,28 +306,16 @@ def upsert_client(data):
"last_name": primary_contact.get("last_name"),
"email_id": primary_contact.get("email"),
"phone": primary_contact.get("phone_number"),
"company": data.get("company"),
"custom_customer_name": customer_name,
"customer_type": customer_type
"customer_type": customer_type,
"companies": [{ "company": data.get("company_name")
}]
}
if customer_type == "Company":
lead_data["company_name"] = data.get("customer_name")
client_doc = create_lead(lead_data)
print(f"#####DEBUG: {client_doc.doctype}:", client_doc.as_dict())
# Handle address creation
address_doc = create_address({
"address_title": build_address_title(customer_name, data),
"address_line1": data.get("address_line1"),
"address_line2": data.get("address_line2"),
"city": data.get("city"),
"state": data.get("state"),
"country": "United States",
"pincode": data.get("pincode"),
"customer_type": "Lead",
"customer_name": client_doc.name
})
#Handle contact creation
contact_docs = []
for contact_data in contacts:
@ -316,12 +330,14 @@ def upsert_client(data):
)
if not contact_doc:
print("#####DEBUG: No existing contact found. Creating new contact.")
contact_doc = create_contact({
contact_doc = ContactService.create({
"first_name": contact_data.get("first_name"),
"last_name": contact_data.get("last_name"),
"role": contact_data.get("contact_role", "Other"),
"custom_email": contact_data.get("email"),
"is_primary_contact":1 if contact_data.get("is_primary", False) else 0,
"customer_type": "Lead",
"customer_name": client_doc.name,
"email_ids": [{
"email_id": contact_data.get("email"),
"is_primary": 1
@ -332,37 +348,61 @@ def upsert_client(data):
"is_primary_phone": 1
}]
})
ContactService.link_contact_to_customer(contact_doc, "Lead", client_doc.name)
contact_docs.append(contact_doc)
# Link all contacts to client after creating them
client_doc.reload()
for idx, contact_data in enumerate(contacts):
if isinstance(contact_data, str):
contact_data = json.loads(contact_data)
contact_doc = contact_docs[idx]
client_doc.append("contacts", {
"contact": contact_doc.name
})
if contact_data.get("is_primary", False):
client_doc.primary_contact = contact_doc.name
client_doc.save(ignore_permissions=True)
# ##### Create links
# # Customer -> Address
# if client_doc.doctype == "Customer":
# print("#####DEBUG: Linking address to customer.")
# client_doc.append("custom_select_address", {
# "address_name": address_doc.name,
# })
# # Customer -> Contact
# print("#####DEBUG: Linking contacts to customer.")
# for contact_doc in contact_docs:
# client_doc.append("custom_add_contacts", {
# "contact": contact_doc.name,
# "email": contact_doc.custom_email,
# "phone": contact_doc.phone,
# "role": contact_doc.role
# })
# client_doc.save(ignore_permissions=True)
# Address -> Customer/Lead
create_address_links(address_doc, client_doc, contact_docs)
# Contact -> Customer/Lead & Address
create_contact_links(contact_docs, client_doc, address_doc)
# Handle address creation
address_docs = []
for address in addresses:
print("#####DEBUG: Creating address with data:", address)
address_doc = AddressService.create_address({
"address_title": build_address_title(customer_name, address),
"address_line1": address.get("address_line1"),
"address_line2": address.get("address_line2"),
"city": address.get("city"),
"state": address.get("state"),
"country": "United States",
"pincode": address.get("pincode"),
"customer_type": "Lead",
"customer_name": client_doc.name,
"companies": [{ "company": data.get("company_name") }]
})
AddressService.link_address_to_customer(address_doc, "Lead", client_doc.name)
address_doc.reload()
for contact_to_link_idx in address.get("contacts", []):
contact_doc = contact_docs[contact_to_link_idx]
AddressService.link_address_to_contact(address_doc, contact_doc.name)
address_doc.reload()
ContactService.link_contact_to_address(contact_doc, address_doc.name)
primary_contact = contact_docs[address.get("primary_contact)", 0)]
AddressService.set_primary_contact(address_doc.name, primary_contact.name)
address_docs.append(address_doc)
# Link all addresses to client after creating them
client_doc.reload()
for address_doc in address_docs:
client_doc.append("properties", {
"address": address_doc.name
})
client_doc.save(ignore_permissions=True)
frappe.local.message_log = []
return build_success_response({
"customer": client_doc.as_dict(),
"address": address_doc.as_dict(),
"address": [address_doc.as_dict() for address_doc in address_docs],
"contacts": [contact_doc.as_dict() for contact_doc in contact_docs]
})
except frappe.ValidationError as ve:

View file

@ -4,6 +4,7 @@ from custom_ui.api.db.general import get_doc_history
from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
from werkzeug.wrappers import Response
from custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer
from custom_ui.services import DbService, ClientService, AddressService
# ===============================================================================
# ESTIMATES & INVOICES API METHODS
@ -244,11 +245,11 @@ def update_response(name, response):
if accepted:
template = "custom_ui/templates/estimates/accepted.html"
if check_if_customer(estimate.party_name):
print("DEBUG: Party is already a customer:", estimate.party_name)
else:
print("DEBUG: Converting lead to customer for party:", estimate.party_name)
convert_lead_to_customer(estimate.party_name)
# if check_if_customer(estimate.party_name):
# print("DEBUG: Party is already a customer:", estimate.party_name)
# else:
# print("DEBUG: Converting lead to customer for party:", estimate.party_name)
# convert_lead_to_customer(estimate.party_name)
elif response == "Requested call":
template = "custom_ui/templates/estimates/request-call.html"
else:
@ -384,7 +385,8 @@ def upsert_estimate(data):
print("DEBUG: Upsert estimate data:", data)
estimate_name = data.get("estimate_name")
is_customer = True if frappe.db.exists("Customer", data.get("customer")) else False
client_doctype = ClientService.get_client_doctype(data.get("customer"))
project_template = data.get("project_template", None)
# If estimate_name exists, update existing estimate
if estimate_name:
@ -392,11 +394,21 @@ def upsert_estimate(data):
estimate = frappe.get_doc("Quotation", estimate_name)
# Update fields
estimate.custom_installation_address = data.get("address")
estimate.party_name = data.get("customer")
estimate.contact_person = data.get("contact_name")
# estimate.custom_installation_address = data.get("address")
# estimate.custom_job_address = data.get("address_name")
# estimate.party_name = data.get("customer")
# estimate.contact_person = data.get("contact_name")
estimate.custom_requires_half_payment = data.get("requires_half_payment", 0)
estimate.custom_project_template = project_template
estimate.custom_quotation_template = data.get("quotation_template", None)
# estimate.company = data.get("company")
# estimate.contact_email = data.get("contact_email")
# estimate.quotation_to = client_doctype
# estimate.customer_name = data.get("customer")
# estimate.customer_address = data.get("address_name")
# estimate.letter_head = data.get("company")
# estimate.from_onsite_meeting = data.get("onsite_meeting", None)
# Clear existing items and add new ones
estimate.items = []
for item in data.get("items", []):
@ -418,6 +430,7 @@ def upsert_estimate(data):
else:
print("DEBUG: Creating new estimate")
print("DEBUG: Retrieved address name:", data.get("address_name"))
client_doctype = ClientService.get_client_doctype(data.get("customer"))
new_estimate = frappe.get_doc({
"doctype": "Quotation",
"custom_requires_half_payment": data.get("requires_half_payment", 0),
@ -425,13 +438,15 @@ def upsert_estimate(data):
"custom_current_status": "Draft",
"contact_email": data.get("contact_email"),
"party_name": data.get("customer"),
"quotation_to": "Customer" if is_customer else "Lead",
"quotation_to": client_doctype,
"company": data.get("company"),
"customer_name": data.get("customer"),
"customer": data.get("customer"),
"customer_type": client_doctype,
"customer_address": data.get("address_name"),
"contact_person": data.get("contact_name"),
"letter_head": data.get("company"),
"custom_project_template": data.get("project_template", None),
"custom_quotation_template": data.get("quotation_template", None),
"from_onsite_meeting": data.get("onsite_meeting", None)
})
for item in data.get("items", []):
@ -443,6 +458,8 @@ def upsert_estimate(data):
"discount_percentage": item.get("discount_percentage") or item.get("discountPercentage", 0)
})
new_estimate.insert()
AddressService.append_link(data.get("address_name"), "quotations", "quotation", new_estimate.name)
ClientService.append_link(data.get("customer"), "quotations", "quotation", new_estimate.name)
print("DEBUG: New estimate created with name:", new_estimate.name)
return build_success_response(new_estimate.as_dict())
except Exception as e:

View file

@ -201,3 +201,16 @@ def get_install_projects(start_date=None, end_date=None):
return {"status": "success", "data": calendar_events}
except Exception as e:
return {"status": "error", "message": str(e)}
@frappe.whitelist()
def get_project_templates_for_company(company_name):
"""Get project templates for a specific company."""
try:
templates = frappe.get_all(
"Project Template",
fields=["*"],
filters={"company": company_name}
)
return build_success_response(templates)
except Exception as e:
return build_error_response(str(e), 500),