358 lines
No EOL
14 KiB
Python
358 lines
No EOL
14 KiB
Python
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") |