big update

This commit is contained in:
Casey 2026-01-15 17:31:53 -06:00
parent 73d235b7bc
commit 0380dd10d8
18 changed files with 951 additions and 490 deletions

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, ClientService, AddressService
from custom_ui.services import DbService, ClientService, AddressService, ContactService
@frappe.whitelist()
def get_week_bid_meetings(week_start, week_end):
@ -17,8 +17,10 @@ def get_week_bid_meetings(week_start, week_end):
order_by="start_time asc"
)
for meeting in meetings:
address_doc = frappe.get_doc("Address", meeting["address"])
address_doc = AddressService.get_or_throw(meeting["address"])
meeting["address"] = address_doc.as_dict()
contact_doc = ContactService.get_or_throw(meeting["contact"]) if meeting.get("contact") else None
meeting["contact"] = contact_doc.as_dict() if contact_doc else None
return build_success_response(meetings)
except Exception as e:
frappe.log_error(message=str(e), title="Get Week On-Site Meetings Failed")
@ -60,6 +62,13 @@ def get_unscheduled_bid_meetings():
filters={"status": "Unscheduled"},
order_by="creation desc"
)
for meeting in meetings:
address_doc = AddressService.get_or_throw(meeting["address"])
meeting["address"] = address_doc.as_dict()
# client_doc = ClientService.get_client_doctype(meeting["party_name"])
# meeting["client"] = client_doc.as_dict() if client_doc else None
contact_doc = ContactService.get_or_throw(meeting["contact"]) if meeting.get("contact") else None
meeting["contact"] = contact_doc.as_dict() if contact_doc else None
return build_success_response(meetings)
except Exception as e:
frappe.log_error(message=str(e), title="Get Unscheduled On-Site Meetings Failed")
@ -75,8 +84,11 @@ def get_bid_meeting(name):
# Get the full address data
if meeting_dict.get("address"):
address_doc = frappe.get_doc("Address", meeting_dict["address"])
address_doc = AddressService.get_or_throw(meeting_dict["address"])
meeting_dict["address"] = address_doc.as_dict()
if meeting_dict.get("contact"):
contact_doc = ContactService.get_or_throw(meeting_dict["contact"])
meeting_dict["contact"] = contact_doc.as_dict()
return build_success_response(meeting_dict)
except frappe.DoesNotExistError:
@ -127,37 +139,30 @@ def create_bid_meeting(data):
@frappe.whitelist()
def update_bid_meeting(name, data):
"""Update an existing On-Site Meeting."""
defualts = {
"address": None,
"start_time": None,
"end_time": None,
"notes": None,
"assigned_employee": None,
"completed_by": None,
"contact": None,
"status": None
}
try:
if isinstance(data, str):
data = json.loads(data)
# Ensure we always have the expected keys so fields can be cleared
data = {**defualts, **(data or {})}
meeting = frappe.get_doc("On-Site Meeting", name)
# Only update fields that are explicitly provided in the data
for key, value in data.items():
# Allow explicitly clearing date/time and assignment fields
if key in ["start_time", "end_time", "assigned_employee", "completed_by"] and value is None:
meeting.set(key, None)
continue
if value is not None:
if key == "address":
value = frappe.db.get_value("Address", {"full_address": value}, "name")
elif key in ["assigned_employee", "completed_by"]:
value = frappe.db.get_value("Employee", {"employee_name": value}, "name")
print(f"DEBUG: Updating field '{key}' to value '{value}'")
if key == "address" and value is not None:
# Convert full address to address name
value = frappe.db.get_value("Address", {"full_address": value}, "name")
meeting.set(key, value)
elif key in ["assigned_employee", "completed_by"] and value is not None:
# Convert employee name to employee ID
value = frappe.db.get_value("Employee", {"employee_name": value}, "name")
meeting.set(key, value)
else:
# For all other fields, set the value as-is (including None to clear fields)
meeting.set(key, value)
print(f"DEBUG: Field '{key}' updated to '{meeting.get(key)}'")
meeting.save()
frappe.db.commit()
return build_success_response(meeting.as_dict())
except frappe.DoesNotExistError:
return build_error_response(f"On-Site Meeting '{name}' does not exist.", 404)

View file

@ -205,19 +205,20 @@ def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10):
addresses = [frappe.get_doc("Address", addr["name"]).as_dict() for addr in address_names]
tableRows = []
for address in addresses:
is_lead = False
is_lead = address.customer_type == "Lead"
print("##########IS LEAD:", is_lead)
tableRow = {}
links = address.links
customer_links = [link for link in links if link.link_doctype == "Customer"] if links else None
customer_name = address.get("custom_customer_to_bill", None)
if not customer_links:
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:
# customer_links = [link for link in links if link.link_doctype == "Customer"] if links else None
customer_name = address.get("customer_name")
# if not customer_links:
# 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:
# customer_name = frappe.get_value("Lead", address.get("customer_name"), "custom_customer_name")
if is_lead:
# print("DEBUG: No customer to bill. Customer links found:", customer_links)
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
tableRow["id"] = address["name"]
tableRow["customer_name"] = customer_name
tableRow["address"] = (
@ -225,6 +226,7 @@ def get_clients_table_data(filters={}, sortings=[], page=1, page_size=10):
f"{' ' + address['address_line2'] if address['address_line2'] else ''} "
f"{address['city']}, {address['state']} {address['pincode']}"
)
print("########IS LEAD @TABLE ROW:", is_lead)
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
@ -308,6 +310,7 @@ def upsert_client(data):
"phone": primary_contact.get("phone_number"),
"custom_customer_name": customer_name,
"customer_type": customer_type,
"address_type": "Billing",
"companies": [{ "company": data.get("company_name")
}]
}

View file

@ -4,7 +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
from custom_ui.services import DbService, ClientService, AddressService, ContactService
# ===============================================================================
# ESTIMATES & INVOICES API METHODS
@ -145,17 +145,18 @@ def send_estimate_email(estimate_name):
print("DEBUG: Sending estimate email for:", estimate_name)
quotation = frappe.get_doc("Quotation", estimate_name)
party_exists = frappe.db.exists(quotation.quotation_to, quotation.party_name)
if not party_exists:
if not DbService.exists("Contact", quotation.contact_person):
return build_error_response("No email found for the customer.", 400)
party = frappe.get_doc(quotation.quotation_to, quotation.party_name)
party = ContactService.get_or_throw(quotation.contact_person)
email = None
if (getattr(party, 'email_id', None)):
email = party.email_id
elif (getattr(party, 'contact_ids', None) and len(party.email_ids) > 0):
primary = next((e for e in party.email_ids if e.is_primary), None)
email = primary.email_id if primary else party.email_ids[0].email_id
email = quotation.contact_email or None
if not email:
if (getattr(party, 'email_id', None)):
email = party.email_id
elif (getattr(party, 'email_ids', None) and len(party.email_ids) > 0):
primary = next((e for e in party.email_ids if e.is_primary), None)
email = primary.email_id if primary else party.email_ids[0].email_id
if not email and quotation.custom_job_address:
address = frappe.get_doc("Address", quotation.custom_job_address)
@ -383,9 +384,10 @@ def upsert_estimate(data):
try:
data = json.loads(data) if isinstance(data, str) else data
print("DEBUG: Upsert estimate data:", data)
address_doc = AddressService.get_or_throw(data.get("address_name"))
estimate_name = data.get("estimate_name")
client_doctype = ClientService.get_client_doctype(data.get("customer"))
client_doctype = ClientService.get_client_doctype(address_doc.customer_name)
print("DEBUG: Retrieved client doctype:", client_doctype)
project_template = data.get("project_template", None)
# If estimate_name exists, update existing estimate
@ -430,17 +432,16 @@ 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),
"custom_job_address": data.get("address_name"),
"custom_current_status": "Draft",
"contact_email": data.get("contact_email"),
"party_name": data.get("customer"),
"quotation_to": client_doctype,
"party_name": data.get("contact_name"),
"quotation_to": "Contact",
"company": data.get("company"),
"customer": data.get("customer"),
"actual_customer_name": address_doc.customer_name,
"customer_type": client_doctype,
"customer_address": data.get("address_name"),
"contact_person": data.get("contact_name"),
@ -457,9 +458,12 @@ def upsert_estimate(data):
"discount_amount": item.get("discount_amount") or item.get("discountAmount", 0),
"discount_percentage": item.get("discount_percentage") or item.get("discountPercentage", 0)
})
# Iterate through every field and print it out, I need to see if there is any field that is a Dynamic link saying Customer
for fieldname, value in new_estimate.as_dict().items():
print(f"DEBUG: Field '{fieldname}': {value}")
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)
# 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

View file

@ -5,8 +5,10 @@ from custom_ui.services import DbService, AddressService, ClientService
def after_insert(doc, method):
print("DEBUG: After insert hook triggered for Quotation:", doc.name)
AddressService.append_link_v2(
doc.custom_job_address,
{"quotations": {"quotation": doc.name, "project_template": doc.custom_project_template}}
doc.custom_job_address, "quotations", {"quotation": doc.name, "project_template": doc.custom_project_template}
)
ClientService.append_link_v2(
doc.actual_customer_name, "quotations", {"quotation": doc.name, "project_template": doc.custom_project_template}
)
template = doc.custom_project_template or "Other"
if template == "Other":
@ -20,18 +22,24 @@ def after_insert(doc, method):
)
def before_insert(doc, method):
print("DEBUG: Before insert hook triggered for Quotation:", doc.name)
# if doc.custom_project_template == "SNW Install":
# print("DEBUG: Quotation uses SNW Install template, setting initial Address status to 'In Progress'.")
# address_doc = AddressService.get_or_throw(doc.custom_job_address)
# if "SNW Install" in [link.project_template for link in address_doc.quotations]:
# raise frappe.ValidationError("An Estimate with project template 'SNW Install' is already linked to this address.")
print("DEBUG: Before insert hook triggered for Quotation:", doc)
print("DEBUG: CHECKING CUSTOMER TYPE")
print(doc.customer_type)
print("DEBUG: CHECKING CUSTOMER NAME")
print(doc.actual_customer_name)
print("Quotation_to:", doc.quotation_to)
# print("Party_type:", doc.party_type)
if doc.custom_project_template == "SNW Install":
print("DEBUG: Quotation uses SNW Install template, making sure no duplicate linked estimates.")
address_doc = AddressService.get_or_throw(doc.custom_job_address)
if "SNW Install" in [link.project_template for link in address_doc.quotations]:
raise frappe.ValidationError("An Estimate with project template 'SNW Install' is already linked to this address.")
def before_submit(doc, method):
print("DEBUG: Before submit hook triggered for Quotation:", doc.name)
if doc.custom_project_template == "SNW Install":
print("DEBUG: Quotation uses SNW Install template.")
if doc.custom_current_status == "Estimate Sent":
if doc.custom_sent == 1:
print("DEBUG: Current status is 'Estimate Sent', updating Address status to 'Sent'.")
AddressService.update_value(
doc.custom_job_address,
@ -47,14 +55,18 @@ def on_update_after_submit(doc, method):
print("DEBUG: Quotation marked as Won, updating current status.")
if doc.customer_type == "Lead":
print("DEBUG: Customer is a Lead, converting to Customer and updating Quotation.")
new_customer = ClientService.convert_lead_to_customer(doc.customer, update_quotations=False)
doc.customer = new_customer.name
new_customer = ClientService.convert_lead_to_customer(doc.actual_customer_name, update_quotations=False)
doc.actual_customer_name = new_customer.name
doc.customer_type = "Customer"
new_customer.reload()
ClientService.append_link_v2(
new_customer.name, "quotations", {"quotation": doc.name}
)
doc.save()
print("DEBUG: Creating Sales Order from accepted Estimate")
new_sales_order = make_sales_order(doc.name)
new_sales_order.custom_requires_half_payment = doc.requires_half_payment
new_sales_order.customer = doc.customer
new_sales_order.customer = doc.actual_customer_name
# new_sales_order.custom_installation_address = doc.custom_installation_address
# new_sales_order.custom_job_address = doc.custom_job_address
new_sales_order.payment_schedule = []
@ -62,8 +74,11 @@ def on_update_after_submit(doc, method):
new_sales_order.set_payment_schedule()
print("DEBUG: Inserting Sales Order:", new_sales_order.as_dict())
new_sales_order.delivery_date = new_sales_order.transaction_date
backup = new_sales_order.customer_address
new_sales_order.customer_address = None
new_sales_order.insert()
print("DEBUG: Submitting Sales Order")
new_sales_order.customer_address = backup
new_sales_order.submit()
frappe.db.commit()
print("DEBUG: Sales Order created successfully:", new_sales_order.name)

View file

@ -22,7 +22,7 @@ def after_insert(doc, method):
def before_save(doc, method):
print("DEBUG: Before Save Triggered for On-Site Meeting")
if doc.status != "Scheduled" and doc.start_time and doc.end_time:
if doc.status != "Scheduled" and doc.start_time and doc.end_time and doc.status != "Completed":
print("DEBUG: Meeting has start and end time, setting status to Scheduled")
doc.status = "Scheduled"
if doc.project_template == "SNW Install":

View file

@ -114,8 +114,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "084d85c4c6bd8aa7a867582454f1fc78",
"modified": "2026-01-13 11:55:00.125952",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.065211",
"module": "Custom",
"name": "Lead Companies Link",
"naming_rule": "",
@ -332,8 +332,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 03:13:47.584878",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.117683",
"module": "Custom",
"name": "Address Project Link",
"naming_rule": "",
@ -550,8 +550,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 03:12:40.401170",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.173958",
"module": "Custom",
"name": "Address Quotation Link",
"naming_rule": "",
@ -768,8 +768,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 03:08:11.881037",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.229451",
"module": "Custom",
"name": "Address On-Site Meeting Link",
"naming_rule": "",
@ -922,8 +922,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.392595",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.280251",
"module": "Custom",
"name": "Address Sales Order Link",
"naming_rule": "",
@ -1076,8 +1076,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.443423",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.332720",
"module": "Custom",
"name": "Contact Address Link",
"naming_rule": "",
@ -1230,8 +1230,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.493149",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.385343",
"module": "Custom",
"name": "Lead On-Site Meeting Link",
"naming_rule": "",
@ -1832,8 +1832,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.563912",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.457103",
"module": "Selling",
"name": "Quotation Template",
"naming_rule": "",
@ -2330,8 +2330,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.635350",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.529451",
"module": "Selling",
"name": "Quotation Template Item",
"naming_rule": "",
@ -2484,8 +2484,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.685490",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.580626",
"module": "Custom UI",
"name": "Customer Company Link",
"naming_rule": "",
@ -2638,8 +2638,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.735935",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.630918",
"module": "Custom UI",
"name": "Customer Address Link",
"naming_rule": "",
@ -2792,8 +2792,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.785648",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.681698",
"module": "Custom UI",
"name": "Customer Contact Link",
"naming_rule": "",
@ -2946,8 +2946,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.837258",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.731378",
"module": "Custom",
"name": "Address Contact Link",
"naming_rule": "",
@ -3100,8 +3100,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.887850",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.783154",
"module": "Custom",
"name": "Customer On-Site Meeting Link",
"naming_rule": "",
@ -3254,8 +3254,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.938600",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.832605",
"module": "Custom",
"name": "Customer Project Link",
"naming_rule": "",
@ -3408,8 +3408,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:38.989639",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.882511",
"module": "Custom",
"name": "Customer Quotation Link",
"naming_rule": "",
@ -3562,8 +3562,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:39.042414",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.934072",
"module": "Custom",
"name": "Customer Sales Order Link",
"naming_rule": "",
@ -3716,8 +3716,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:39.092979",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:57.984126",
"module": "Custom",
"name": "Lead Address Link",
"naming_rule": "",
@ -3870,8 +3870,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:39.144013",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:58.034758",
"module": "Custom",
"name": "Lead Contact Link",
"naming_rule": "",
@ -4024,8 +4024,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:39.251239",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:58.086245",
"module": "Custom",
"name": "Lead Quotation Link",
"naming_rule": "",
@ -4178,8 +4178,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:39.304711",
"migration_hash": "717d2b4e3d605ae371d8c05bb650b364",
"modified": "2026-01-15 10:05:58.139052",
"module": "Custom",
"name": "Address Company Link",
"naming_rule": "",

View file

@ -462,18 +462,20 @@ def add_custom_fields():
insert_after="custom_job_address"
),
dict(
fieldname="customer",
fieldname="actual_customer_name",
label="Customer",
fieldtype="Dynamic Link",
options="customer_type",
insert_after="from_onsite_meeting"
insert_after="from_onsite_meeting",
allow_on_submit=1
),
dict(
fieldname="customer_type",
label="Customer Type",
fieldtype="Select",
options="Customer\nLead",
insert_after="customer_name"
insert_after="customer_name",
allow_on_submit=1
)
],
"Sales Order": [

View file

@ -1,4 +1,5 @@
import frappe
import requests
from .contact_service import ContactService, DbService
class AddressService:
@ -164,6 +165,38 @@ class AddressService:
"""Set a link field for an address using a link dictionary."""
print(f"DEBUG: Setting link field {field} for Address {address_name} with link data {link}")
address_doc = AddressService.get_or_throw(address_name)
print("DEBUG: Appending link:", link)
address_doc.append(field, link)
print("DEBUG: Saving address document after appending link.")
address_doc.save(ignore_permissions=True)
print(f"DEBUG: Set link field {field} for Address {address_name} with link data {link}")
print(f"DEBUG: Set link field {field} for Address {address_name} with link data {link}")
@staticmethod
def get_county_and_set(address_doc, save: bool = False):
"""Get the county from the address document and set it if not already set."""
if not address_doc.county:
print(f"DEBUG: Getting county for Address {address_doc.name}")
# Example logic to determine county from address fields
# This is a placeholder; actual implementation may vary
url = "https://geocoding.geo.cencus.gov/geocoder/geographies/coordinates"
params = {
"x": address_doc.longitude,
"y": address_doc.latitude,
"benchmark": "Public_AR_Current",
"vintage": "Current_Current",
"format": "json"
}
r = requests.get(url, params=params, timeout=10)
data = r.json()
try:
county = data['result']['geographies']['Counties'][0]['NAME']
county_fips = data['result']['geographies']['Counties'][0]['GEOID']
except (KeyError, IndexError):
return None
return {
"county": county,
"county_fips": county_fips
}

View file

@ -37,7 +37,19 @@ class ClientService:
})
client_doc.save(ignore_permissions=True)
print(f"DEBUG: Set link field {field} for client {client_doc.get('name')} to {link_doctype} {link_name}")
@staticmethod
def append_link_v2(client_name: str, field: str, link: dict):
"""Set a link field for a client (Customer or Lead) using a link dictionary."""
print(f"DEBUG: Setting link field {field} for client {client_name} with link data {link}")
client_doctype = ClientService.get_client_doctype(client_name)
client_doc = DbService.get_or_throw(client_doctype, client_name)
print("DEBUG: Appending link:", link)
client_doc.append(field, link)
print("DEBUG: Saving client document after appending link.")
client_doc.save(ignore_permissions=True)
print(f"DEBUG: Set link field {field} for client {client_doc.get('name')} with link data {link}")
@staticmethod
def convert_lead_to_customer(
lead_name: str,
@ -48,24 +60,78 @@ class ClientService:
):
"""Convert a Lead to a Customer."""
print(f"DEBUG: Converting Lead {lead_name} to Customer")
lead_doc = DbService.get_or_throw("Lead", lead_name)
customer_doc = make_customer(lead_doc.name)
customer_doc.insert(ignore_permissions=True)
if update_addresses:
for address in lead_doc.get("addresses", []):
address_doc = AddressService.get_or_throw(address.get("address"))
AddressService.link_address_to_customer(address_doc, "Customer", customer_doc.name)
if update_contacts:
for contact in lead_doc.get("contacts", []):
contact_doc = ContactService.get_or_throw(contact.get("contact"))
ContactService.link_contact_to_customer(contact_doc, "Customer", customer_doc.name)
if update_quotations:
for quotation in lead_doc.get("quotations", []):
quotation_doc = EstimateService.get_or_throw(quotation.get("quotation"))
EstimateService.link_estimate_to_customer(quotation_doc, "Customer", customer_doc.name)
if update_onsite_meetings:
for meeting in lead_doc.get("onsite_meetings", []):
meeting_doc = OnSiteMeetingService.get_or_throw(meeting.get("onsite_meeting"))
OnSiteMeetingService.link_onsite_meeting_to_customer(meeting_doc, "Customer", customer_doc.name)
print(f"DEBUG: Converted Lead {lead_name} to Customer {customer_doc.name}")
return customer_doc
try:
lead_doc = DbService.get_or_throw("Lead", lead_name)
print(f"DEBUG: Retrieved Lead document: {lead_doc.name}")
print("DEBUG: RUNNING make_customer()")
customer_doc = make_customer(lead_doc.name)
print(f"DEBUG: make_customer() returned document type: {type(customer_doc)}")
print(f"DEBUG: Customer doc name: {customer_doc.name if hasattr(customer_doc, 'name') else 'NO NAME'}")
print("DEBUG: Calling customer_doc.insert()")
customer_doc.insert(ignore_permissions=True)
print(f"DEBUG: Customer inserted successfully: {customer_doc.name}")
frappe.db.commit()
print("DEBUG: Database committed after customer insert")
print("DEBUG: CREATED CUSTOMER:", customer_doc.as_dict())
if update_addresses:
print("DEBUG: Lead_doc addresses:", lead_doc.get("addresses", []))
print(f"DEBUG: Updating addresses. Count: {len(lead_doc.get('properties', []))}")
for address in lead_doc.get("properties", []):
try:
print(f"DEBUG: Processing address: {address.get('address')}")
ClientService.append_link_v2(customer_doc.name, "properties", {"address": address.get("address")})
address_doc = AddressService.get_or_throw(address.get("address"))
AddressService.link_address_to_customer(address_doc, "Customer", customer_doc.name)
print(f"DEBUG: Linked address {address.get('address')} to customer")
except Exception as e:
print(f"ERROR: Failed to link address {address.get('address')}: {str(e)}")
frappe.log_error(f"Address linking error: {str(e)}", "convert_lead_to_customer")
if update_contacts:
print(f"DEBUG: Updating contacts. Count: {len(lead_doc.get('contacts', []))}")
for contact in lead_doc.get("contacts", []):
try:
print(f"DEBUG: Processing contact: {contact.get('contact')}")
ClientService.append_link_v2(customer_doc.name, "contacts", {"contact": contact.get("contact")})
contact_doc = ContactService.get_or_throw(contact.get("contact"))
ContactService.link_contact_to_customer(contact_doc, "Customer", customer_doc.name)
print(f"DEBUG: Linked contact {contact.get('contact')} to customer")
except Exception as e:
print(f"ERROR: Failed to link contact {contact.get('contact')}: {str(e)}")
frappe.log_error(f"Contact linking error: {str(e)}", "convert_lead_to_customer")
if update_quotations:
print(f"DEBUG: Updating quotations. Count: {len(lead_doc.get('quotations', []))}")
for quotation in lead_doc.get("quotations", []):
try:
print(f"DEBUG: Processing quotation: {quotation.get('quotation')}")
ClientService.append_link_v2(customer_doc.name, "quotations", {"quotation": quotation.get("quotation")})
quotation_doc = EstimateService.get_or_throw(quotation.get("quotation"))
EstimateService.link_estimate_to_customer(quotation_doc, "Customer", customer_doc.name)
print(f"DEBUG: Linked quotation {quotation.get('quotation')} to customer")
except Exception as e:
print(f"ERROR: Failed to link quotation {quotation.get('quotation')}: {str(e)}")
frappe.log_error(f"Quotation linking error: {str(e)}", "convert_lead_to_customer")
if update_onsite_meetings:
print(f"DEBUG: Updating onsite meetings. Count: {len(lead_doc.get('onsite_meetings', []))}")
for meeting in lead_doc.get("onsite_meetings", []):
try:
print(f"DEBUG: Processing onsite meeting: {meeting.get('onsite_meeting')}")
meeting_doc = DbService.get_or_throw("On-Site Meeting",meeting.get("onsite_meeting"))
ClientService.append_link_v2(customer_doc.name, "onsite_meetings", {"onsite_meeting": meeting.get("onsite_meeting")})
OnSiteMeetingService.link_onsite_meeting_to_customer(meeting_doc, "Customer", customer_doc.name)
print(f"DEBUG: Linked onsite meeting {meeting.get('onsite_meeting')} to customer")
except Exception as e:
print(f"ERROR: Failed to link onsite meeting {meeting.get('onsite_meeting')}: {str(e)}")
frappe.log_error(f"Onsite meeting linking error: {str(e)}", "convert_lead_to_customer")
print(f"DEBUG: Converted Lead {lead_name} to Customer {customer_doc.name}")
return customer_doc
except Exception as e:
print(f"ERROR: Exception in convert_lead_to_customer: {str(e)}")
frappe.log_error(f"convert_lead_to_customer failed: {str(e)}", "ClientService")
raise