From 91e4d47d48b5090dc68065b86adc4e54ebe5a80a Mon Sep 17 00:00:00 2001 From: rocketdebris Date: Sat, 24 Jan 2026 16:43:43 -0500 Subject: [PATCH] Added Invoice Status Select to Project DocType. --- custom_ui/install.py | 132 +++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/custom_ui/install.py b/custom_ui/install.py index ab74967..fa53153 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -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 \ No newline at end of file + + return sundays