import os import subprocess import frappe from .utils import create_module def after_install(): create_module() add_custom_fields() build_frontend() def after_migrate(): add_custom_fields() update_onsite_meeting_fields() frappe.db.commit() # Proper way to refresh metadata frappe.clear_cache(doctype="Address") frappe.reload_doctype("Address") frappe.clear_cache(doctype="On-Site Meeting") frappe.reload_doctype("On-Site Meeting") update_address_fields() build_frontend() def build_frontend(): app_package_path = frappe.get_app_path("custom_ui") app_root = os.path.dirname(app_package_path) candidates = [ os.path.join(app_root, "frontend"), os.path.join(app_package_path, "frontend") ] frontend_path = None for p in candidates: if os.path.exists(p): frontend_path = p break if not frontend_path: frappe.log_error(message="No frontend directory found for custom_ui", title="Frontend Build Skipped") print(f"āš ļø Frontend directory does not exist. Skipping build. Path was {frontend_path}") return dist_path = os.path.join(app_root, "custom_ui", "public", "dist") should_build = True if should_build: print("\nšŸ“¦ Building frontend for custom_ui...\n") try: subprocess.check_call(["npm", "install"], cwd=frontend_path) subprocess.check_call(["npm", "run", "build"], cwd=frontend_path) print("\nāœ… Frontend build completed successfully.\n") except subprocess.CalledProcessError as e: frappe.log_error(message=str(e), title="Frontend Build Failed") print(f"\nāŒ Frontend build failed: {e}\n") def add_custom_fields(): from frappe.custom.doctype.custom_field.custom_field import create_custom_fields print("\nšŸ”§ Adding custom fields to Address doctype...") custom_fields = { "Address": [ dict( fieldname="full_address", label="Full Address", fieldtype="Data", insert_after="country" ), dict( fieldname="latitude", label="Latitude", fieldtype="Float", precision=8, insert_after="full_address" ), dict( fieldname="longitude", label="Longitude", fieldtype="Float", precision=8, insert_after="latitude" ), dict( fieldname="onsite_meeting_scheduled", label="On-Site Meeting Scheduled", fieldtype="Select", options="Not Started\nIn Progress\nCompleted", default="Not Started", insert_after="longitude" ), dict( fieldname="estimate_sent_status", label="Estimate Sent Status", fieldtype="Select", options="Not Started\nIn Progress\nCompleted", default="Not Started", insert_after="onsite_meeting_scheduled" ), dict( fieldname="job_status", label="Job Status", fieldtype="Select", options="Not Started\nIn Progress\nCompleted", default="Not Started", insert_after="estimate_sent_status" ), dict( fieldname="payment_received_status", label="Payment Received Status", fieldtype="Select", options="Not Started\nIn Progress\nCompleted", default="Not Started", insert_after="job_status" ) ], "Contact": [ dict( fieldname="role", label="Role", fieldtype="Select", options="Owner\nProperty Manager\nTenant\nBuilder\nNeighbor\nFamily Member\nRealtor\nOther", insert_after="designation" ), dict( fieldname="email", label="Email", fieldtype="Data", insert_after="last_name", options="Email" ) ], "On-Site Meeting": [ dict( fieldname="notes", label="Notes", fieldtype="Small Text", insert_after="address" ), dict( fieldname="assigned_employee", label="Assigned Employee", fieldtype="Link", options="Employee", insert_after="notes" ), dict( fieldname="status", label="Status", fieldtype="Select", options="Unscheduled\nScheduled\nCompleted\nCancelled", default="Unscheduled", insert_after="start_time" ), dict( fieldname="completed_by", label="Completed By", fieldtype="Link", options="Employee", insert_after="status" ) ] } field_count = len(custom_fields["Address"]) print(f"šŸ“ Creating {field_count} custom fields for Address doctype...") try: create_custom_fields(custom_fields) print("āœ… Custom fields added successfully!") print(" • full_address (Data)") print(" • latitude (Float)") print(" • longitude (Float)") print(" • onsite_meeting_scheduled (Select)") print(" • estimate_sent_status (Select)") print(" • job_status (Select)") print(" • payment_received_status (Select)") print("šŸ”§ Custom fields installation complete.\n") except Exception as e: print(f"āŒ Error creating custom fields: {str(e)}") frappe.log_error(message=str(e), title="Custom Fields Creation Failed") raise def update_onsite_meeting_fields(): """Update On-Site Meeting doctype fields to make start_time and end_time optional.""" print("\nšŸ”§ Updating On-Site Meeting doctype fields...") try: # Get the doctype doctype = frappe.get_doc("DocType", "On-Site Meeting") # Find and update start_time and end_time fields updated_fields = [] for field in doctype.fields: if field.fieldname in ['start_time', 'end_time']: if field.reqd == 1: field.reqd = 0 updated_fields.append(field.fieldname) if updated_fields: # Save the doctype doctype.save(ignore_permissions=True) print(f"āœ… Updated fields: {', '.join(updated_fields)} (set to not required)") else: print("āœ… Fields already configured correctly") print("šŸ”§ On-Site Meeting field update complete.\n") except Exception as e: print(f"āŒ Error updating On-Site Meeting fields: {str(e)}") frappe.log_error(message=str(e), title="On-Site Meeting Field Update Failed") # Don't raise - this is not critical enough to stop migration def update_address_fields(): addresses = frappe.get_all("Address", pluck="name") total_addresses = len(addresses) if total_addresses == 0: print("šŸ“ No addresses found to update.") return print(f"\nšŸ“ Updating fields for {total_addresses} addresses...") # Verify custom fields exist by checking the meta address_meta = frappe.get_meta("Address") required_fields = ['full_address', 'custom_onsite_meeting_scheduled', 'custom_estimate_sent_status', 'custom_job_status', 'custom_payment_received_status'] missing_fields = [] for field in required_fields: if not address_meta.has_field(field): missing_fields.append(field) if missing_fields: print(f"\nāŒ Missing custom fields: {', '.join(missing_fields)}") print(" Custom fields creation may have failed. Skipping address updates.") return print("āœ… All custom fields verified. Proceeding with address updates...") # Field update counters field_counters = { 'full_address': 0, 'latitude': 0, 'longitude': 0, 'custom_onsite_meeting_scheduled': 0, 'custom_estimate_sent_status': 0, 'custom_job_status': 0, 'custom_payment_received_status': 0 } total_field_updates = 0 addresses_updated = 0 for index, name in enumerate(addresses, 1): # Calculate progress progress_percentage = int((index / total_addresses) * 100) bar_length = 30 filled_length = int(bar_length * index // total_addresses) bar = 'ā–ˆ' * filled_length + 'ā–‘' * (bar_length - filled_length) # Print progress bar with field update count print(f"\ršŸ“Š Progress: [{bar}] {progress_percentage:3d}% ({index}/{total_addresses}) | Fields Updated: {total_field_updates} - Processing: {name[:25]}...", end='', flush=True) should_update = False address = frappe.get_doc("Address", name) current_address_updates = 0 current_address_updates = 0 # Use getattr with default values instead of direct attribute access if not getattr(address, 'full_address', None): address_parts_1 = [ address.address_line1 or "", address.address_line2 or "", address.city or "", ] address_parts_2 = [ address.state or "", address.pincode or "", ] full_address = ", ".join([ " ".join(filter(None, address_parts_1)), " ".join(filter(None, address_parts_2)) ]).strip() address.full_address = full_address field_counters['full_address'] += 1 current_address_updates += 1 should_update = True onsite_meeting = "Not Started" estimate_sent = "Not Started" job_status = "Not Started" payment_received = "Not Started" onsite_meetings = frappe.get_all("On-Site Meeting", fields=["docstatus"],filters={"address": address.address_title}) if onsite_meetings and onsite_meetings[0]: onsite_meeting = "Completed" if onsite_meetings[0]["docstatus"] == 1 else "In Progress" estimates = frappe.get_all("Quotation", fields=["custom_sent", "docstatus"], filters={"custom_installation_address": address.address_title}) if estimates and estimates[0] and estimates[0]["custom_sent"] == 1 and estimates[0]["docstatus"] == 1: estimate_sent = "Completed" elif estimates and estimates[0] and estimates[0]["docstatus"] != 1: estimate_sent = "In Progress" jobs = frappe.get_all("Project", fields=["status"], filters={"custom_installation_address": address.address_title, "project_template": "SNW Install"}) if jobs and jobs[0] and jobs[0]["status"] == "Completed": job_status = "Completed" elif jobs and jobs[0]: job_status = "In Progress" sales_invoices = frappe.get_all("Sales Invoice", fields=["outstanding_amount"], filters={"custom_installation_address": address.address_title}) # payments = frappe.get_all("Payment Entry", filters={"custom_installation_address": address.address_title}) if sales_invoices and sales_invoices[0] and sales_invoices[0]["outstanding_amount"] == 0: payment_received = "Completed" elif sales_invoices and sales_invoices[0]: payment_received = "In Progress" if getattr(address, 'custom_onsite_meeting_scheduled', None) != onsite_meeting: address.custom_onsite_meeting_scheduled = onsite_meeting field_counters['custom_onsite_meeting_scheduled'] += 1 current_address_updates += 1 should_update = True if getattr(address, 'custom_estimate_sent_status', None) != estimate_sent: address.custom_estimate_sent_status = estimate_sent field_counters['custom_estimate_sent_status'] += 1 current_address_updates += 1 should_update = True if getattr(address, 'custom_job_status', None) != job_status: address.custom_job_status = job_status field_counters['custom_job_status'] += 1 current_address_updates += 1 should_update = True if getattr(address, 'custom_payment_received_status', None) != payment_received: address.custom_payment_received_status = payment_received field_counters['custom_payment_received_status'] += 1 current_address_updates += 1 should_update = True if should_update: address.save(ignore_permissions=True) addresses_updated += 1 total_field_updates += current_address_updates # Print completion summary print(f"\n\nāœ… Address field update completed!") print(f"šŸ“Š Summary:") print(f" • Total addresses processed: {total_addresses:,}") print(f" • Addresses updated: {addresses_updated:,}") print(f" • Total field updates: {total_field_updates:,}") print(f"\nšŸ“ Field-specific updates:") print(f" • Full Address: {field_counters['full_address']:,}") print(f" • Latitude: {field_counters['latitude']:,}") print(f" • Longitude: {field_counters['longitude']:,}") print(f" • On-Site Meeting Status: {field_counters['custom_onsite_meeting_scheduled']:,}") print(f" • Estimate Sent Status: {field_counters['custom_estimate_sent_status']:,}") print(f" • Job Status: {field_counters['custom_job_status']:,}") print(f" • Payment Received Status: {field_counters['custom_payment_received_status']:,}") print("šŸ“ Address field updates complete.\n")