Added Invoice Status Select to Project DocType.

This commit is contained in:
rocketdebris 2026-01-24 16:43:43 -05:00
parent 2fb82917b4
commit 91e4d47d48

View file

@ -31,7 +31,7 @@ def after_migrate():
for doctype in doctypes_to_refresh:
frappe.clear_cache(doctype=doctype)
frappe.reload_doctype(doctype)
check_and_create_holiday_list()
# update_address_fields()
@ -56,7 +56,7 @@ def build_frontend():
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
@ -69,10 +69,10 @@ def build_frontend():
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 doctypes...")
try:
@ -85,7 +85,7 @@ def add_custom_fields():
print(" ✅ Added 'Service' to Address address_type options.")
except Exception as e:
print(f" ⚠️ Failed to update Address address_type: {e}")
custom_fields = {
"Customer": [
dict(
@ -267,7 +267,7 @@ def add_custom_fields():
insert_after="full_address"
),
dict(
fieldname="longitude",
fieldname="longitude",
label="Longitude",
fieldtype="Float",
precision=8,
@ -277,7 +277,7 @@ def add_custom_fields():
fieldname="onsite_meeting_scheduled",
label="On-Site Meeting Scheduled",
fieldtype="Select",
options="Not Started\nIn Progress\nCompleted",
options="Not Started\nIn Progress\nCompleted",
default="Not Started",
insert_after="longitude"
),
@ -291,7 +291,7 @@ def add_custom_fields():
),
dict(
fieldname="job_status",
label="Job Status",
label="Job Status",
fieldtype="Select",
options="Not Started\nIn Progress\nCompleted",
default="Not Started",
@ -302,7 +302,7 @@ def add_custom_fields():
label="Payment Received Status",
fieldtype="Select",
options="Not Started\nIn Progress\nCompleted",
default="Not Started",
default="Not Started",
insert_after="job_status"
),
dict(
@ -588,7 +588,15 @@ def add_custom_fields():
fieldtype="Check",
default=0,
insert_after="expected_end_time"
)
),
dict(
fieldname="invoice_status",
label="Invoice Status",
fieldtype="Select",
default="Not Ready",
options="Not Ready\nReady to Invoice\nInvoice Created\nInvoice Sent",
insert_after="is_scheduled"
),
],
"Project Template": [
dict(
@ -616,15 +624,15 @@ def add_custom_fields():
)
]
}
print("🔧 Custom fields to check per doctype:")
for key, value in custom_fields.items():
print(f"{key}: {len(value)} fields")
print(f" Total fields to check: {sum(len(v) for v in custom_fields.values())}\n")
missing_fields = []
fields_to_update = []
for doctype, field_options in custom_fields.items():
meta = frappe.get_meta(doctype)
for field_spec in field_options:
@ -637,7 +645,7 @@ def add_custom_fields():
if frappe.db.exists("Custom Field", custom_field_name):
custom_field_doc = frappe.get_doc("Custom Field", custom_field_name)
needs_update = False
# Compare important properties
for key, desired_value in field_spec.items():
if key == "fieldname":
@ -646,10 +654,10 @@ def add_custom_fields():
if current_value != desired_value:
needs_update = True
break
if needs_update:
fields_to_update.append((doctype, fieldname, field_spec))
if missing_fields:
print("\n❌ Missing custom fields:")
for entry in missing_fields:
@ -658,36 +666,36 @@ def add_custom_fields():
missing_field_specs = build_missing_field_specs(custom_fields, missing_fields)
create_custom_fields(missing_field_specs)
print("✅ Missing custom fields created.")
if fields_to_update:
print("\n🔧 Updating custom fields with mismatched specs:")
for doctype, fieldname, field_spec in fields_to_update:
print(f"{doctype}: {fieldname}")
custom_field_name = f"{doctype}-{fieldname}"
custom_field_doc = frappe.get_doc("Custom Field", custom_field_name)
# Update all properties from field_spec
for key, value in field_spec.items():
if key != "fieldname":
setattr(custom_field_doc, key, value)
custom_field_doc.save(ignore_permissions=True)
frappe.db.commit()
print("✅ Custom fields updated.")
if not missing_fields and not fields_to_update:
print("✅ All custom fields verified.")
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:
@ -695,20 +703,20 @@ def update_onsite_meeting_fields():
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():
quotations = frappe.get_all("Quotation", pluck="name")
addresses = frappe.get_all("Address", pluck="name")
@ -728,9 +736,9 @@ def update_address_fields():
combined_doctypes.append({"doctype": "Address", "name": address})
for task in tasks:
combined_doctypes.append({"doctype": "Task", "name": task})
print(f"\n📍 Updating field values for {total_addresses} addresses, {total_quotations} quotations, {total_sales_orders} sales orders, and {total_tasks} tasks...")
# Field update counters
field_counters = {
'quotation_addresses_updated': 0,
@ -751,7 +759,7 @@ def update_address_fields():
'contacts_updated': 0,
'tasks_updated': 0
}
onsite_meta = frappe.get_meta("On-Site Meeting")
onsite_status_field = "custom_status" if onsite_meta.has_field("custom_status") else "status"
@ -761,7 +769,7 @@ def update_address_fields():
bar_length = 30
filled_length = int(bar_length * index // total_doctypes)
bar = '' * filled_length + '' * (bar_length - filled_length)
# 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'] + field_counters['customers_updated']}"
@ -792,7 +800,7 @@ def update_address_fields():
custom_installation_address = getattr(quotation_doc, 'custom_installation_address', None)
custom_job_address = getattr(quotation_doc, 'custom_job_address', None)
custom_project_template = getattr(quotation_doc, 'custom_project_template', None)
updates = {}
if custom_installation_address and not custom_job_address:
updates['custom_job_address'] = custom_installation_address
@ -802,15 +810,15 @@ def update_address_fields():
updates['custom_project_template'] = "SNW Install"
field_counters[f"{dict_field}_project_templates_updated"] += 1
field_counters['total_field_updates'] += 1
if updates:
frappe.db.set_value(doc['doctype'], doc['name'], updates)
field_counters[f"{dict_field}s_updated"] += 1
if doc['doctype'] == "Address":
address_doc = frappe.get_doc("Address", doc['name'])
updates = {}
# Use getattr with default values instead of direct attribute access
if not getattr(address_doc, 'full_address', None):
address_parts_1 = [
@ -822,57 +830,57 @@ def update_address_fields():
address_doc.state or "",
address_doc.pincode or "",
]
full_address = ", ".join([
" ".join(filter(None, address_parts_1)),
" ".join(filter(None, address_parts_2))
]).strip()
]).strip()
updates['full_address'] = full_address
field_counters['full_address'] += 1
field_counters['total_field_updates'] += 1
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=[onsite_status_field], filters={"address": address_doc.address_title})
if onsite_meetings and onsite_meetings[0]:
status_value = onsite_meetings[0].get(onsite_status_field)
onsite_meeting = "Completed" if status_value == "Completed" else "In Progress"
estimates = frappe.get_all("Quotation", fields=["custom_sent", "docstatus", "custom_response"], filters={"custom_job_address": address_doc.address_title})
if estimates and estimates[0] and estimates[0]["custom_sent"] == 1 and estimates[0]["custom_response"]:
estimate_sent = "Completed"
elif estimates and estimates[0] and not (estimates[0]["custom_sent"] == 1 and estimates[0]["custom_response"]):
estimate_sent = "In Progress"
jobs = frappe.get_all("Project", fields=["status"], filters={"custom_installation_address": address_doc.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_doc.address_title})
# payments = frappe.get_all("Payment Entry", filters={"custom_installation_address": address_doc.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"
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
@ -880,7 +888,7 @@ def update_address_fields():
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
@ -897,11 +905,11 @@ def update_address_fields():
updates['custom_payment_received_status'] = payment_received
field_counters['custom_payment_received_status'] += 1
field_counters['total_field_updates'] += 1
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
@ -922,13 +930,13 @@ def update_address_fields():
frappe.db.set_value("Task", doc["name"], "custom_property", project_address if project_address else alt_project_address)
field_counters['tasks_updated'] += 1
field_counters['total_field_updates'] += 1
# Commit every 100 records to avoid long transactions
if index % 100 == 0:
frappe.db.commit()
# Print completion summary
print(f"\n\n✅ DocType field value update completed!")
print(f"📊 Summary:")
@ -950,7 +958,7 @@ def update_address_fields():
print(f" • Sales Order Addresses Updated: {field_counters['sales_order_addresses_updated']:,}")
print(f" • Sales Order Project Templates Updated: {field_counters['sales_order_project_templates_updated']:,}")
print("📍 DocType field value updates complete.\n")
def build_missing_field_specs(custom_fields, missing_fields):
missing_field_specs = {}
for entry in missing_fields:
@ -960,14 +968,14 @@ def build_missing_field_specs(custom_fields, missing_fields):
if field_spec["fieldname"] == fieldname:
missing_field_specs[doctype].append(field_spec)
break
return missing_field_specs
def check_and_create_holiday_list(year=2026, country="US", weekly_off="Sunday"):
"""Check if Holiday List for the given year exists, if not create it."""
print(f"\n🔧 Checking for Holiday List for {country} in {year}...")
holiday_list_name = f"{country} Holidays {year}"
if frappe.db.exists("Holiday List", holiday_list_name):
print(f"✅ Holiday List '{holiday_list_name}' already exists.")
return
@ -999,16 +1007,16 @@ def check_and_create_holiday_list(year=2026, country="US", weekly_off="Sunday"):
# hl.make_holiday_entries()
frappe.db.commit()
print(f"✅ Holiday List '{holiday_list_name}' created successfully.")
def get_all_sundays(year):
sundays = []
d = date(year, 1, 1)
while d.weekday() != 6:
d += timedelta(days=1)
while d.year == year:
sundays.append(d)
d += timedelta(days=7)
return sundays
return sundays