This commit is contained in:
Casey 2026-02-18 06:56:19 -06:00
parent e2746b83bb
commit 1610905a43
16 changed files with 3303 additions and 2521 deletions

View file

@ -9,6 +9,101 @@ from custom_ui.services import AddressService, ContactService, ClientService
# CLIENT MANAGEMENT API METHODS
# ===============================================================================
@frappe.whitelist()
def add_addresses_contacts(client_name, company_name, addresses=[], contacts=[]):
if isinstance(addresses, str):
addresses = json.loads(addresses)
if isinstance(contacts, str):
contacts = json.loads(contacts)
print(f"DEBUG: add_addresses_contacts called with client_name: {client_name}, addresses: {addresses}, contacts: {contacts}")
try:
client_doc = ClientService.get_client_or_throw(client_name)
if contacts:
contact_docs = [frappe.get_doc("Contact", contact.contact) for contact in client_doc.contacts]
for contact in contacts:
contact_doc = None
if frappe.db.exists("Contact", {"email_id": contact.get("email")}):
contact_doc = frappe.get_doc("Contact", {"email_id": contact.get("email")})
else:
contact_doc = ContactService.create({
"first_name": contact.get("first_name"),
"last_name": contact.get("last_name"),
"email_id": contact.get("email"),
"role": contact.get("role"),
"phone": contact.get("phone"),
"custom_email": contact.get("email"),
"is_primary_contact": 0,
"customer_type": client_doc.doctype,
"customer_name": client_doc.name,
"email_ids": [{
"email": contact.get("email"),
"is_primary": 1
}],
"phone_nos": [{
"phone": contact.get("phone"),
"is_primary_phone": 1,
"is_primary_mobile_no": 1
}]
})
contact_doc.insert()
ClientService.append_link_v2(client_doc.name, "contacts", {"contact": contact_doc.name})
ContactService.link_contact_to_customer(contact_doc, client_doc.doctype, client_doc.name)
contact_docs.append(contact_doc)
address_docs = [frappe.get_doc("Address", link.address) for link in client_doc.properties]
for address in addresses:
address_doc = None
if frappe.db.exists("Address", {
"address_line1": address.get("address_line1"),
"address_line2": address.get("address_line2"),
"city": address.get("city"),
# "state": address.get("state"),
"pincode": address.get("pincode")
}):
address_doc = frappe.get_doc("Address", {
"address_line1": address.get("address_line1"),
"address_line2": address.get("address_line2"),
"city": address.get("city"),
# "state": address.get("state"),
"pincode": address.get("pincode")
})
else:
address_doc = AddressService.create({
"address_title": AddressService.build_address_title(customer_name=client_name, address_data=address),
"address_line1": address.get("address_line1"),
"address_line2": address.get("address_line2"),
"city": address.get("city"),
"state": address.get("state"),
"pincode": address.get("pincode"),
"country": "United States",
"address_type": "Service",
"custom_billing_address": 0,
"is_primary_address": 0,
"is_service_address": 1,
"customer_type": client_doc.doctype,
"customer_name": client_doc.name
})
address_doc.insert()
if company_name not in [company.company for company in address_doc.companies]:
address_doc.append("companies", {"company": company_name})
address_doc.save(ignore_permissions=True)
AddressService.link_address_to_customer(address_doc, client_doc.doctype, client_doc.name)
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)
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)
ClientService.append_link_v2(client_doc.name, "properties", {"address": address_doc.name})
address_docs.append(address_doc)
return build_success_response({
"contacts": [contact.as_dict() for contact in contact_docs],
"addresses": [address.as_dict() for address in address_docs],
"message": "Addresses and contacts added successfully."
})
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def check_client_exists(client_name):
"""Check if a client exists as either a Customer or a Lead.

View file

@ -61,7 +61,7 @@ def get_unscheduled_service_appointments(companies):
return build_error_response(str(e), 500)
@frappe.whitelist()
def update_service_appointment_scheduled_dates(service_appointment_name: str, start_date, end_date, crew_lead_name, start_time=None, end_time=None):
def update_service_appointment_scheduled_dates(service_appointment_name: str, start_date, end_date, crew_lead_name, skipped_days=[], start_time=None, end_time=None):
"""Update scheduled dates for a Service Appointment."""
print(f"DEBUG: Updating scheduled dates for Service Appointment {service_appointment_name} to start: {start_date}, end: {end_date}, crew lead: {crew_lead_name}, start time: {start_time}, end time: {end_time}")
try:
@ -71,7 +71,8 @@ def update_service_appointment_scheduled_dates(service_appointment_name: str, st
start_date,
end_date,
start_time,
end_time
end_time,
skip_days=skipped_days
)
return build_success_response(updated_service_appointment.as_dict())
except Exception as e:

View file

@ -61,7 +61,7 @@
"expenses_included_in_valuation": "Expenses Included In Valuation - VS",
"fax": null,
"is_group": 0,
"modified": "2026-02-15 00:56:31.933618",
"modified": "2026-02-17 03:05:48.497419",
"monthly_sales_target": 0.0,
"name": "Veritas Stone",
"old_parent": "",
@ -149,7 +149,7 @@
"expenses_included_in_valuation": "Expenses Included In Valuation - DL",
"fax": null,
"is_group": 0,
"modified": "2026-02-15 00:56:31.935944",
"modified": "2026-02-17 03:05:48.503038",
"monthly_sales_target": 0.0,
"name": "Daniels Landscape Supplies",
"old_parent": "",
@ -237,7 +237,7 @@
"expenses_included_in_valuation": "Expenses Included In Valuation - SD",
"fax": null,
"is_group": 0,
"modified": "2026-02-15 00:56:31.938072",
"modified": "2026-02-17 03:05:48.505280",
"monthly_sales_target": 0.0,
"name": "sprinklersnorthwest (Demo)",
"old_parent": "",
@ -325,7 +325,7 @@
"expenses_included_in_valuation": "Expenses Included In Valuation - NYC",
"fax": null,
"is_group": 0,
"modified": "2026-02-15 00:56:31.942129",
"modified": "2026-02-17 03:05:48.509153",
"monthly_sales_target": 0.0,
"name": "Nuco Yard Care",
"old_parent": "",
@ -413,7 +413,7 @@
"expenses_included_in_valuation": "Expenses Included In Valuation - LF",
"fax": null,
"is_group": 0,
"modified": "2026-02-15 00:56:31.940118",
"modified": "2026-02-17 03:05:48.507129",
"monthly_sales_target": 0.0,
"name": "Lowe Fencing",
"old_parent": "",
@ -501,7 +501,7 @@
"expenses_included_in_valuation": "Expenses Included In Valuation - S",
"fax": null,
"is_group": 0,
"modified": "2026-02-15 00:56:31.946270",
"modified": "2026-02-17 03:05:48.512257",
"monthly_sales_target": 0.0,
"name": "Sprinklers Northwest",
"old_parent": "",

File diff suppressed because it is too large Load diff

View file

@ -33179,5 +33179,351 @@
"track_views": 0,
"translated_doctype": 0,
"website_search_field": null
},
{
"_assign": null,
"_comments": null,
"_last_update": null,
"_liked_by": null,
"_user_tags": null,
"actions": [],
"allow_auto_repeat": 0,
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"app": null,
"autoname": null,
"beta": 0,
"color": null,
"colour": null,
"custom": 1,
"default_email_template": null,
"default_print_format": null,
"default_view": null,
"description": null,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"documentation": null,
"editable_grid": 1,
"email_append_to": 0,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": "Customer",
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "customer_type",
"fieldtype": "Select",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Customer Type",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Customer\nLead",
"parent": "Address Customer Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "customer_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Customer Name",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "customer_type",
"parent": "Address Customer Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "relation",
"fieldtype": "Select",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Relation",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Owner\nBuilder",
"parent": "Address Customer Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": "1",
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "is_active",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Active",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"parent": "Address Customer Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
}
],
"force_re_route_to_default_view": 0,
"grid_page_length": 50,
"has_web_view": 0,
"hide_toolbar": 0,
"icon": null,
"image_field": null,
"in_create": 0,
"index_web_pages_for_search": 1,
"is_calendar_and_gantt": 0,
"is_published_field": null,
"is_submittable": 0,
"is_tree": 0,
"is_virtual": 0,
"issingle": 0,
"istable": 1,
"links": [],
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": null,
"modified": "2026-02-17 08:14:21.379886",
"module": "Custom UI",
"name": "Address Customer Link",
"naming_rule": "",
"nsm_parent_field": null,
"parent_node": null,
"permissions": [],
"print_outline": null,
"protect_attached_files": 0,
"queue_in_background": 0,
"quick_entry": 0,
"read_only": 0,
"recipient_account_field": null,
"restrict_to_domain": null,
"route": null,
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"search_fields": null,
"sender_field": null,
"sender_name_field": null,
"show_name_in_global_search": 0,
"show_preview_popup": 0,
"show_title_field_in_link": 0,
"smallicon": null,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"subject": null,
"subject_field": null,
"tag_fields": null,
"timeline_field": null,
"title_field": null,
"track_changes": 0,
"track_seen": 0,
"track_views": 0,
"translated_doctype": 0,
"website_search_field": null
}
]

View file

@ -1,13 +1,13 @@
[
{
"bid_meeting_note_form": "SNW Install Bid Meeting Notes",
"calendar_color": "#CB2929",
"calendar_color": "#c1dec5",
"company": "Sprinklers Northwest",
"custom__complete_method": "Task Weight",
"docstatus": 0,
"doctype": "Project Template",
"item_groups": "SNW-I, SNW-S, SNW-LS",
"modified": "2026-02-15 01:31:39.325004",
"modified": "2026-02-16 03:59:53.719382",
"name": "SNW Install",
"project_type": "External",
"tasks": [

View file

@ -95,22 +95,6 @@
"row_name": null,
"value": "[\"weight\", \"description\", \"base_date\", \"offset_days\", \"skip_weekends\", \"skip_holidays\", \"logic_key\", \"offset_direction\", \"title\", \"days\", \"calculate_from\", \"trigger\", \"task_type_calculate_from\", \"work_type\", \"no_due_date\", \"triggering_doctype\", \"custom_completion_trigger\", \"custom_completion_trigger_doctype\", \"target_percent\"]"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"field_name": "email_id",
"is_system_generated": 0,
"modified": "2025-01-06 14:08:26.445944",
"module": null,
"name": "Contact-email_id-options",
"property": "options",
"property_type": "Text",
"row_name": null,
"value": "email_id"
},
{
"default_value": null,
"doc_type": "Pre-Built Routes",
@ -10415,22 +10399,6 @@
"row_name": null,
"value": "None"
},
{
"default_value": null,
"doc_type": "Customer",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2025-02-05 06:03:40.318065",
"module": null,
"name": "Customer-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"basic_info\", \"naming_series\", \"salutation\", \"customer_name\", \"customer_type\", \"customer_group\", \"custom_appointment_date\", \"column_break0\", \"territory\", \"gender\", \"lead_name\", \"opportunity_name\", \"account_manager\", \"image\", \"defaults_tab\", \"default_currency\", \"default_bank_account\", \"column_break_14\", \"default_price_list\", \"custom_previous_year_price\", \"internal_customer_section\", \"is_internal_customer\", \"represents_company\", \"column_break_70\", \"companies\", \"more_info\", \"market_segment\", \"industry\", \"customer_pos_id\", \"website\", \"language\", \"column_break_45\", \"customer_details\", \"contact_and_address_tab\", \"address_contacts\", \"address_html\", \"column_break1\", \"contact_html\", \"custom_related_addresses\", \"custom_select_address\", \"custom_associated_contacts\", \"custom_add_contacts\", \"custom_primary_billing_and_contact_details\", \"custom_billing_address\", \"custom_column_break_q0puw\", \"custom_billing_contact\", \"primary_address_and_contact_detail\", \"column_break_26\", \"primary_address\", \"customer_primary_address\", \"column_break_nwor\", \"customer_primary_contact\", \"mobile_no\", \"email_id\", \"tax_tab\", \"taxation_section\", \"tax_id\", \"column_break_21\", \"tax_category\", \"tax_withholding_category\", \"accounting_tab\", \"credit_limit_section\", \"payment_terms\", \"credit_limits\", \"default_receivable_accounts\", \"accounts\", \"loyalty_points_tab\", \"loyalty_program\", \"column_break_54\", \"loyalty_program_tier\", \"sales_team_tab\", \"sales_team\", \"sales_team_section\", \"default_sales_partner\", \"column_break_66\", \"default_commission_rate\", \"settings_tab\", \"so_required\", \"dn_required\", \"exempt_from_sales_tax\", \"column_break_53\", \"is_frozen\", \"disabled\", \"portal_users_tab\", \"portal_users\", \"dashboard_tab\"]"
},
{
"default_value": null,
"doc_type": "SMS Log",
@ -12751,22 +12719,6 @@
"row_name": null,
"value": "format:{full_address)-#-{MM}-{YYYY}-{####}"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-21 03:31:53.827100",
"module": null,
"name": "Address-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"address_details\", \"custom_column_break_vqa4d\", \"custom_column_break_jw2ty\", \"custom_installationservice_address\", \"custom_billing_address\", \"is_shipping_address\", \"is_primary_address\", \"custom_is_compnay_address\", \"custom_column_break_ky1zo\", \"custom_estimate_sent_status\", \"custom_onsite_meeting_scheduled\", \"custom_job_status\", \"custom_payment_received_status\", \"custom_section_break_fvgdt\", \"address_title\", \"primary_contact\", \"address_type\", \"address_line1\", \"address_line2\", \"custom_linked_city\", \"custom_subdivision\", \"is_your_company_address\", \"custom_column_break_3mo7x\", \"state\", \"city\", \"pincode\", \"county\", \"country\", \"full_address\", \"latitude\", \"longitude\", \"onsite_meeting_scheduled\", \"estimate_sent_status\", \"job_status\", \"payment_received_status\", \"custom_column_break_rrto0\", \"custom_customer_to_bill\", \"lead_name\", \"customer_type\", \"customer_name\", \"contacts\", \"companies\", \"quotations\", \"onsite_meetings\", \"projects\", \"sales_orders\", \"tasks\", \"custom_contact_name\", \"phone\", \"email_id\", \"fax\", \"tax_category\", \"disabled\", \"custom_section_break_aecpx\", \"column_break0\", \"custom_show_irrigation_district\", \"custom_google_map\", \"custom_latitude\", \"custom_longitude\", \"custom_address_for_coordinates\", \"linked_with\", \"custom_linked_contacts\", \"links\", \"custom_column_break_9cbvb\", \"custom_linked_companies\", \"custom_irrigation\", \"custom_upcoming_services\", \"custom_service_type\", \"custom_service_route\", \"custom_confirmation_status\", \"custom_backflow_test_form_filed\", \"custom_column_break_j79td\", \"custom_technician_assigned\", \"custom_scheduled_date\", \"custom_column_break_sqplk\", \"custom_test_route\", \"custom_tech\", \"custom_column_break_wcs7g\", \"custom_section_break_zruvq\", \"custom_irrigation_district\", \"custom_serial_\", \"custom_makemodel_\", \"custom_column_break_djjw3\", \"custom_backflow_location\", \"custom_shutoff_location\", \"custom_valve_boxes\", \"custom_timer_type_and_location\", \"custom_column_break_slusf\", \"custom_section_break_5d1cf\", \"custom_installed_by_sprinklers_nw\", \"custom_column_break_th7rq\", \"custom_installed_for\", \"custom_install_month\", \"custom_install_year\", \"custom_column_break_4itse\", \"custom_section_break_xfdtv\", \"custom_backflow_test_report\", \"custom_column_break_oxppn\", \"custom_photo_attachment\"]"
},
{
"default_value": null,
"doc_type": "Project Template",
@ -15119,38 +15071,6 @@
"row_name": null,
"value": "format:{custom_customer_name}-#-{YYYY}-{MM}-{####}"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:35:09.522811",
"module": null,
"name": "Address-main-links_order",
"property": "links_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"21ddd8462e\", \"c26b89d0d3\", \"ee207f2316\"]"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:35:09.598292",
"module": null,
"name": "Address-main-states_order",
"property": "states_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"62m56h85vo\", \"62m5uugrvr\", \"62m57bgpkf\", \"62m5fgrjb0\"]"
},
{
"default_value": null,
"doc_type": "Contact",
@ -15167,22 +15087,6 @@
"row_name": null,
"value": "Expression"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:40:01.427255",
"module": null,
"name": "Contact-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{full-name}-#-{MM}-{YYYY}-{####}"
},
{
"default_value": null,
"doc_type": "Contact",
@ -15262,5 +15166,117 @@
"property_type": "Data",
"row_name": null,
"value": "[\"custom_column_break_k7sgq\", \"custom_installation_address\", \"naming_series\", \"project_name\", \"job_address\", \"status\", \"custom_warranty_duration_days\", \"custom_warranty_expiration_date\", \"custom_warranty_information\", \"project_type\", \"percent_complete_method\", \"percent_complete\", \"column_break_5\", \"project_template\", \"expected_start_date\", \"expected_start_time\", \"expected_end_date\", \"expected_end_time\", \"requires_half_payment\", \"is_half_down_paid\", \"is_scheduled\", \"invoice_status\", \"custom_completion_date\", \"priority\", \"custom_foreman\", \"custom_hidden_fields\", \"department\", \"service_appointment\", \"tasks\", \"ready_to_schedule\", \"is_active\", \"custom_address\", \"custom_section_break_lgkpd\", \"custom_workflow_related_custom_fields__landry\", \"custom_permit_status\", \"custom_utlity_locate_status\", \"custom_crew_scheduling\", \"customer_details\", \"customer\", \"column_break_14\", \"sales_order\", \"users_section\", \"users\", \"copied_from\", \"section_break0\", \"notes\", \"section_break_18\", \"actual_start_date\", \"actual_start_time\", \"actual_time\", \"column_break_20\", \"actual_end_date\", \"actual_end_time\", \"project_details\", \"estimated_costing\", \"total_costing_amount\", \"total_expense_claim\", \"total_purchase_cost\", \"company\", \"column_break_28\", \"total_sales_amount\", \"total_billable_amount\", \"total_billed_amount\", \"total_consumed_material_cost\", \"cost_center\", \"margin\", \"gross_margin\", \"column_break_37\", \"per_gross_margin\", \"monitor_progress\", \"collect_progress\", \"holiday_list\", \"frequency\", \"from_time\", \"to_time\", \"first_email\", \"second_email\", \"daily_time_to_send\", \"day_to_send\", \"weekly_time_to_send\", \"column_break_45\", \"subject\", \"message\"]"
},
{
"default_value": null,
"doc_type": "Customer",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 03:51:08.179106",
"module": null,
"name": "Customer-main-naming_rule",
"property": "naming_rule",
"property_type": "Data",
"row_name": null,
"value": "Expression"
},
{
"default_value": null,
"doc_type": "Customer",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 03:51:08.313125",
"module": null,
"name": "Customer-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{customer_name}-CUST-{MM}-{YYYY}-{#####}"
},
{
"default_value": null,
"doc_type": "Customer",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 03:51:08.370590",
"module": null,
"name": "Customer-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"basic_info\", \"naming_series\", \"salutation\", \"customer_name\", \"from_lead\", \"properties\", \"contacts\", \"primary_contact\", \"customer_type\", \"customer_group\", \"custom_appointment_date\", \"column_break0\", \"territory\", \"gender\", \"lead_name\", \"opportunity_name\", \"prospect_name\", \"account_manager\", \"image\", \"defaults_tab\", \"default_currency\", \"default_bank_account\", \"column_break_14\", \"default_price_list\", \"custom_previous_year_price\", \"internal_customer_section\", \"is_internal_customer\", \"represents_company\", \"column_break_70\", \"companies\", \"quotations\", \"onsite_meetings\", \"projects\", \"tasks\", \"sales_orders\", \"more_info\", \"market_segment\", \"industry\", \"customer_pos_id\", \"website\", \"language\", \"column_break_45\", \"customer_details\", \"contact_and_address_tab\", \"address_contacts\", \"address_html\", \"column_break1\", \"contact_html\", \"custom_related_addresses\", \"custom_select_address\", \"custom_associated_contacts\", \"custom_add_contacts\", \"custom_primary_billing_and_contact_details\", \"custom_billing_address\", \"custom_column_break_q0puw\", \"custom_billing_contact\", \"primary_address_and_contact_detail\", \"column_break_26\", \"primary_address\", \"customer_primary_address\", \"column_break_nwor\", \"customer_primary_contact\", \"mobile_no\", \"email_id\", \"first_name\", \"last_name\", \"tax_tab\", \"taxation_section\", \"tax_id\", \"column_break_21\", \"tax_category\", \"tax_withholding_category\", \"accounting_tab\", \"credit_limit_section\", \"payment_terms\", \"credit_limits\", \"default_receivable_accounts\", \"accounts\", \"loyalty_points_tab\", \"loyalty_program\", \"column_break_54\", \"loyalty_program_tier\", \"sales_team_tab\", \"sales_team\", \"sales_team_section\", \"default_sales_partner\", \"column_break_66\", \"default_commission_rate\", \"settings_tab\", \"so_required\", \"dn_required\", \"exempt_from_sales_tax\", \"column_break_53\", \"is_frozen\", \"disabled\", \"portal_users_tab\", \"portal_users\", \"dashboard_tab\"]"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 03:53:14.660505",
"module": null,
"name": "Contact-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{full_name}-CONT-{MM}-{YYYY}-{####}"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 08:16:54.459462",
"module": null,
"name": "Address-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"address_details\", \"custom_column_break_vqa4d\", \"custom_column_break_jw2ty\", \"custom_installationservice_address\", \"custom_billing_address\", \"is_shipping_address\", \"is_primary_address\", \"custom_is_compnay_address\", \"custom_column_break_ky1zo\", \"custom_estimate_sent_status\", \"custom_onsite_meeting_scheduled\", \"custom_job_status\", \"custom_payment_received_status\", \"custom_section_break_fvgdt\", \"address_title\", \"primary_contact\", \"address_type\", \"address_line1\", \"address_line2\", \"custom_linked_city\", \"custom_subdivision\", \"is_your_company_address\", \"custom_column_break_3mo7x\", \"state\", \"city\", \"pincode\", \"county\", \"country\", \"full_address\", \"latitude\", \"longitude\", \"onsite_meeting_scheduled\", \"estimate_sent_status\", \"job_status\", \"payment_received_status\", \"custom_column_break_rrto0\", \"custom_customer_to_bill\", \"lead_name\", \"customer_type\", \"customer_name\", \"contacts\", \"companies\", \"quotations\", \"onsite_meetings\", \"projects\", \"sales_orders\", \"tasks\", \"custom_contact_name\", \"phone\", \"email_id\", \"fax\", \"tax_category\", \"disabled\", \"is_service_address\", \"customers\", \"custom_section_break_aecpx\", \"column_break0\", \"custom_show_irrigation_district\", \"custom_google_map\", \"custom_latitude\", \"custom_longitude\", \"custom_address_for_coordinates\", \"linked_with\", \"custom_linked_contacts\", \"links\", \"custom_column_break_9cbvb\", \"custom_linked_companies\", \"custom_irrigation\", \"custom_upcoming_services\", \"custom_service_type\", \"custom_service_route\", \"custom_confirmation_status\", \"custom_backflow_test_form_filed\", \"custom_column_break_j79td\", \"custom_technician_assigned\", \"custom_scheduled_date\", \"custom_column_break_sqplk\", \"custom_test_route\", \"custom_tech\", \"custom_column_break_wcs7g\", \"custom_section_break_zruvq\", \"custom_irrigation_district\", \"custom_serial_\", \"custom_makemodel_\", \"custom_column_break_djjw3\", \"custom_backflow_location\", \"custom_shutoff_location\", \"custom_valve_boxes\", \"custom_timer_type_and_location\", \"custom_column_break_slusf\", \"custom_section_break_5d1cf\", \"custom_installed_by_sprinklers_nw\", \"custom_column_break_th7rq\", \"custom_installed_for\", \"custom_install_month\", \"custom_install_year\", \"custom_column_break_4itse\", \"custom_section_break_xfdtv\", \"custom_backflow_test_report\", \"custom_column_break_oxppn\", \"custom_photo_attachment\"]"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 08:16:54.562064",
"module": null,
"name": "Address-main-links_order",
"property": "links_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"21ddd8462e\", \"c26b89d0d3\", \"ee207f2316\"]"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-02-17 08:16:54.619557",
"module": null,
"name": "Address-main-states_order",
"property": "states_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"62m56h85vo\", \"62m5uugrvr\", \"62m57bgpkf\", \"62m5fgrjb0\"]"
}
]

View file

@ -324,31 +324,6 @@
"weight": 0.0,
"work_type": "Admin"
},
{
"base_date": "Start",
"calculate_from": "Service Address 2",
"custom_completion_trigger": null,
"custom_completion_trigger_doctype": null,
"custom_target_percent": 0,
"days": 0,
"description": "Permits required prior to installation start.",
"docstatus": 0,
"doctype": "Task Type",
"logic_key": null,
"modified": "2026-02-08 01:48:15.012387",
"name": "Permit",
"no_due_date": 0,
"offset_days": 7,
"offset_direction": "Before",
"skip_holidays": 1,
"skip_weekends": 0,
"task_type_calculate_from": null,
"title": "Permit",
"trigger": "Scheduled",
"triggering_doctype": "Service Address 2",
"weight": 7.0,
"work_type": "Admin"
},
{
"base_date": "Completion",
"calculate_from": "Service Address 2",
@ -424,6 +399,31 @@
"weight": 25.0,
"work_type": "Labor"
},
{
"base_date": "Start",
"calculate_from": "Service Address 2",
"custom_completion_trigger": null,
"custom_completion_trigger_doctype": null,
"custom_target_percent": 0,
"days": 0,
"description": "Permits required prior to installation start.",
"docstatus": 0,
"doctype": "Task Type",
"logic_key": null,
"modified": "2026-02-08 01:48:15.012387",
"name": "Permit",
"no_due_date": 0,
"offset_days": 7,
"offset_direction": "Before",
"skip_holidays": 1,
"skip_weekends": 0,
"task_type_calculate_from": null,
"title": "Permit",
"trigger": "Scheduled",
"triggering_doctype": "Service Address 2",
"weight": 7.0,
"work_type": "Admin"
},
{
"base_date": "Project Start",
"calculate_from": "Service Appointment",

View file

@ -0,0 +1,20 @@
import frappe
class DBRestoreService:
@staticmethod
def massage_customer_address_contact_links():
"""Fixes the links between Customer, Address, and Contacts from legacy data that may have been imported without proper linking."""
# use emojis in print statments to make it more fun and visually distinct in the logs
print("DEBUG: 🛠️ Starting to massage customer, address, and contact links")
all_addresses = frappe.get_all("Address", pluck="name")
print(f"DEBUG: Found {len(all_addresses)} addresses to process")
all_customers = frappe.get_all("Customer", pluck="name")
print(f"DEBUG: Found {len(all_customers)} customers to process")
all_leads = frappe.get_all("Lead", pluck="name")
print(f"DEBUG: Found {len(all_leads)} leads to process")
all_contacts = frappe.get_all("Contact", pluck="name")
print(f"DEBUG: Found {len(all_contacts)} contacts to process")
# query all customer doctypes that don't have an empty array for custom_select_address. This field is a child table so get_all cannot be used. We need to use a custom sql query
# the child table is a doctype called "Custom "
# print(f"DEBUG: Found {len(customers_with_addresses)} customers with addresses to process")

View file

@ -24,6 +24,7 @@ class ServiceAppointmentService:
service_appointment["service_address"] = AddressService.get_or_throw(service_appointment["service_address"]).as_dict()
service_appointment["customer"] = ClientService.get_client_or_throw(service_appointment["customer"]).as_dict()
service_appointment["project"] = DbService.get_or_throw("Project", service_appointment["project"]).as_dict()
service_appointment["color"] = frappe.get_value("Project Template", service_appointment["project_template"], "calendar_color")
return service_appointment
@ -43,20 +44,21 @@ class ServiceAppointmentService:
if end_time:
service_appointment.expected_end_time = end_time
if skip_days:
current_skip_days
print(f"DEBUG: Updating skip days for Service Appointment {service_appointment_name}. Current skip days: {[skip_day.date for skip_day in service_appointment.skip_days]}, New skip days: {skip_days}")
# Compare skip_days with the current skip_days and remove/add as needed
current_skip_days = set([skip_day.date for skip_day in service_appointment.skip_days])
new_skip_days = set(skip_days)
current_skip_days = [skip_day.date for skip_day in service_appointment.skip_days]
# Remove skip days that are no longer needed
for skip_day in current_skip_days - new_skip_days:
skip_day_doc = service_appointment.skip_days.find(lambda d: d.date == skip_day)
if skip_day_doc:
service_appointment.skip_days.remove(skip_day_doc.name)
print(f"DEBUG: Removed skip day {skip_day} from Service Appointment {service_appointment_name}")
for skip_day in current_skip_days:
if skip_day not in skip_days:
skip_day_doc = service_appointment.skip_days.find(lambda d: d.date == skip_day)
if skip_day_doc:
service_appointment.skip_days.remove(skip_day_doc.name)
print(f"DEBUG: Removed skip day {skip_day} from Service Appointment {service_appointment_name}")
# Add new skip days
for skip_day in new_skip_days - current_skip_days:
service_appointment.append("skip_days", {"date": skip_day})
print(f"DEBUG: Added new skip day {skip_day} for Service Appointment {service_appointment_name}")
for skip_day in skip_days:
if skip_day not in current_skip_days:
service_appointment.append("skip_days", {"date": skip_day["date"]})
print(f"DEBUG: Added new skip day {skip_day} for Service Appointment {service_appointment_name}")
service_appointment.save()
print(f"DEBUG: Updated scheduled dates for Service Appointment {service_appointment_name}")
return service_appointment