diff --git a/custom_ui/api/db/clients.py b/custom_ui/api/db/clients.py index f8024f2..a88f742 100644 --- a/custom_ui/api/db/clients.py +++ b/custom_ui/api/db/clients.py @@ -106,8 +106,12 @@ def get_client(client_name): clientData.update(map_lead_client(clientData)) links = [] if customer.doctype == "Customer": - clientData["addresses"] = customer.custom_select_address or [] - clientData["contacts"] = customer.custom_add_contacts or [] + for address_link in customer.custom_select_address: + address_doc = frappe.get_doc("Address", address_link.address_name) + clientData["addresses"].append(address_doc.as_dict()) + for contact_link in customer.custom_add_contacts: + contact_doc = frappe.get_doc("Contact", contact_link.contact) + clientData["contacts"].append(contact_doc.as_dict()) else: links = frappe.get_all( @@ -130,7 +134,7 @@ def get_client(client_name): clientData["contacts"].append(linked_doc.as_dict()) elif link["link_doctype"] == "Address": clientData["addresses"].append(linked_doc.as_dict()) - doctypes_to_fetch_history = [] + # TODO: Continue getting other linked docs like jobs, invoices, etc. print("DEBUG: Final client data prepared:", clientData) return build_success_response(clientData) diff --git a/custom_ui/events/jobs.py b/custom_ui/events/jobs.py new file mode 100644 index 0000000..2039480 --- /dev/null +++ b/custom_ui/events/jobs.py @@ -0,0 +1,5 @@ +import frappe + +def after_insert(doc, method): + # This is where we will add logic to set tasks and other properties of a job based on it's project_template + pass \ No newline at end of file diff --git a/custom_ui/fixtures/doctype.json b/custom_ui/fixtures/doctype.json index 5ffb4a3..ca1ae52 100644 --- a/custom_ui/fixtures/doctype.json +++ b/custom_ui/fixtures/doctype.json @@ -477,6 +477,70 @@ "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": "The project that this Quotation will eventually generate.", + "documentation_url": null, + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "project_template", + "fieldtype": "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": 0, + "in_preview": 0, + "in_standard_filter": 0, + "is_virtual": 0, + "label": "Project Template", + "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": "Project Template", + "parent": "Quotation Template", + "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, @@ -498,8 +562,8 @@ "make_attachments_public": 0, "max_attachments": 0, "menu_index": null, - "migration_hash": "2521636464aadbebbd70dfbf13252950", - "modified": "2026-01-07 11:10:03.317996", + "migration_hash": "d5c5525a76e0cba00a479f83d71ac74c", + "modified": "2026-01-09 09:08:21.281081", "module": "Selling", "name": "Quotation Template", "naming_rule": "", @@ -996,8 +1060,8 @@ "make_attachments_public": 0, "max_attachments": 0, "menu_index": null, - "migration_hash": "2521636464aadbebbd70dfbf13252950", - "modified": "2026-01-07 11:10:03.406758", + "migration_hash": "d5c5525a76e0cba00a479f83d71ac74c", + "modified": "2026-01-09 08:52:19.818789", "module": "Selling", "name": "Quotation Template Item", "naming_rule": "", diff --git a/custom_ui/install.py b/custom_ui/install.py index c7b000f..f19b702 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -383,10 +383,13 @@ def update_address_fields(): 'custom_estimate_sent_status': 0, 'custom_job_status': 0, 'custom_payment_received_status': 0, + 'address_linked_to_customer': 0, 'total_field_updates': 0, 'addresses_updated': 0, 'quotations_updated': 0, - 'sales_orders_updated': 0 + 'sales_orders_updated': 0, + 'customers_updated': 0, + 'contacts_updated': 0 } onsite_meta = frappe.get_meta("On-Site Meeting") @@ -401,7 +404,7 @@ def update_address_fields(): # Print a three-line, refreshing progress block without adding new lines each loop progress_line = f"šŸ“Š Progress: [{bar}] {progress_percentage:3d}% ({index}/{total_doctypes})" - counters_line = f" Fields updated: {field_counters['total_field_updates']} | DocTypes updated: {field_counters['addresses_updated'] + field_counters['quotations_updated'] + field_counters['sales_orders_updated']}" + counters_line = f" Fields updated: {field_counters['total_field_updates']} | DocTypes updated: {field_counters['addresses_updated'] + field_counters['quotations_updated'] + field_counters['sales_orders_updated'] + field_counters['customers_updated']}" detail_line = f" Processing: {doc['name'][:40]}..." if index == 1: @@ -498,6 +501,26 @@ def update_address_fields(): elif sales_invoices and sales_invoices[0]: payment_received = "In Progress" + customer_name = getattr(address_doc, 'custom_customer_to_bill', None) + links = address_doc.get("links", []) + customer_links = [link for link in links if link.link_doctype == "Customer"] + needs_link_update = False + + if customer_name and frappe.db.exists("Customer", customer_name): + customer_doc = frappe.get_doc("Customer", customer_name) + + # Check if address needs link update + if not customer_links: + needs_link_update = True + + if not address_doc.name in [link.address_name for link in customer_doc.get("custom_select_address", [])]: + customer_doc.append("custom_select_address", { + "address_name": address_doc.name + }) + customer_doc.save(ignore_permissions=True) + field_counters['customers_updated'] += 1 + field_counters['total_field_updates'] += 1 + if getattr(address_doc, 'custom_onsite_meeting_scheduled', None) != onsite_meeting: updates['custom_onsite_meeting_scheduled'] = onsite_meeting field_counters['custom_onsite_meeting_scheduled'] += 1 @@ -518,6 +541,21 @@ def update_address_fields(): if updates: frappe.db.set_value("Address", doc['name'], updates) field_counters['addresses_updated'] += 1 + + # Handle address links after db.set_value to avoid timestamp mismatch + if needs_link_update: + # Reload the document to get the latest version + address_doc = frappe.get_doc("Address", doc['name']) + address_doc.append("links", { + "link_doctype": "Customer", + "link_name": customer_name + }) + address_doc.save(ignore_permissions=True) + field_counters['address_linked_to_customer'] += 1 + field_counters['total_field_updates'] += 1 + + + # Commit every 100 records to avoid long transactions if index % 100 == 0: @@ -530,6 +568,7 @@ def update_address_fields(): print(f" • Addresses updated: {field_counters['addresses_updated']:,}") print(f" • Quotations updated: {field_counters['quotations_updated']:,}") print(f" • Sales Orders updated: {field_counters['sales_orders_updated']:,}") + print(f" • Customers updated: {field_counters['customers_updated']:,}") print(f" • Total field updates: {field_counters['total_field_updates']:,}") print(f"\nšŸ“ Field-specific updates:") print(f" • Full Address: {field_counters['full_address']:,}")