From 541e6c5ed77d02668733b6db3e92a3029d35bf81 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 30 Jan 2026 16:21:57 -0500 Subject: [PATCH] Moved custom fields def to new file, added an after_uninstall hook to remove custom field on uninstall of app. --- custom_ui/custom_fields.py | 566 ++++++++++++++++++++++++++++++++++ custom_ui/hooks.py | 1 + custom_ui/install.py | 617 ++----------------------------------- 3 files changed, 601 insertions(+), 583 deletions(-) create mode 100644 custom_ui/custom_fields.py diff --git a/custom_ui/custom_fields.py b/custom_ui/custom_fields.py new file mode 100644 index 0000000..204ba7b --- /dev/null +++ b/custom_ui/custom_fields.py @@ -0,0 +1,566 @@ +custom_fields = { + "Customer": [ + dict( + fieldname="companies", + label="Companies", + fieldtype="Table", + options="Customer Company Link", + insert_after="customer_type" + ), + dict( + fieldname="quotations", + label="Quotations", + fieldtype="Table", + options="Customer Quotation Link", + insert_after="companies" + ), + dict( + fieldname="onsite_meetings", + label="On-Site Meetings", + fieldtype="Table", + options="Customer On-Site Meeting Link", + insert_after="quotations" + ), + dict( + fieldname="projects", + label="Projects", + fieldtype="Table", + options="Customer Project Link", + insert_after="onsite_meetings" + ), + dict( + fieldname="sales_orders", + label="Sales Orders", + fieldtype="Table", + options="Customer Sales Order Link", + insert_after="projects" + ), + dict( + fieldname="from_lead", + label="From Lead", + fieldtype="Link", + options="Lead", + insert_after="customer_name" + ), + dict( + fieldname="properties", + label="Properties", + fieldtype="Table", + options="Customer Address Link", + insert_after="customer_name" + ), + dict( + fieldname="contacts", + label="Contacts", + fieldtype="Table", + options="Customer Contact Link", + insert_after="properties" + ), + dict( + fieldname="primary_contact", + label="Primary Contact", + fieldtype="Link", + options="Contact", + insert_after="contacts" + ), + dict( + fieldname="tasks", + label="Tasks", + fieldtype="Table", + options="Customer Task Link", + insert_after="projects" + ) + ], + "Lead": [ + dict( + fieldname="onsite_meetings", + label="On-Site Meetings", + fieldtype="Table", + options="Lead On-Site Meeting Link", + insert_after="quotations" + ), + dict( + fieldname="custom_billing_address", + label="Custom Address", + fieldtype="Link", + options="Address", + insert_after="customer_name" + ), + dict( + fieldname="quotations", + label="Quotations", + fieldtype="Table", + options="Lead Quotation Link", + insert_after="companies" + ), + dict( + fieldname="companies", + label="Companies", + fieldtype="Table", + options="Lead Company Link", + insert_after="customer_type" + ), + dict( + fieldname="customer_type", + label="Customer Type", + fieldtype="Select", + options="Individual\nCompany\nPartnership", + insert_after="lead_name" + ), + dict( + fieldname="properties", + label="Properties", + fieldtype="Table", + options="Lead Address Link", + insert_after="customer_name" + ), + dict( + fieldname="contacts", + label="Contacts", + fieldtype="Table", + options="Lead Contact Link", + insert_after="properties" + ), + dict( + fieldname="primary_contact", + label="Primary Contact", + fieldtype="Link", + options="Contact", + insert_after="contacts" + ) + ], + "Address": [ + dict( + fieldname="primary_contact", + label="Primary Contact", + fieldtype="Link", + options="Contact", + insert_after="address_title" + ), + dict( + fieldname="projects", + label="Projects", + fieldtype="Table", + options="Address Project Link", + insert_after="onsite_meetings" + ), + dict( + fieldname="sales_orders", + label="Sales Orders", + fieldtype="Table", + options="Address Sales Order Link", + insert_after="projects" + ), + dict( + fieldname="onsite_meetings", + label="On-Site Meetings", + fieldtype="Table", + options="Address On-Site Meeting Link", + insert_after="quotations" + ), + dict( + fieldname="quotations", + label="Quotations", + fieldtype="Table", + options="Address Quotation Link", + insert_after="companies" + ), + 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" + ), + dict( + fieldname="lead_name", + label="Lead Name", + fieldtype="Data", + insert_after="custom_customer_to_bill" + ), + dict( + fieldname="customer_type", + label="Customer Type", + fieldtype="Select", + options="Customer\nLead", + insert_after="lead_name" + ), + dict( + fieldname="customer_name", + label="Customer Name", + fieldtype="Dynamic Link", + options="customer_type", + insert_after="customer_type" + ), + dict( + fieldname="contacts", + label="Contacts", + fieldtype="Table", + options="Address Contact Link", + insert_after="customer_name" + ), + dict( + fieldname="companies", + label="Companies", + fieldtype="Table", + options="Address Company Link", + insert_after="contacts" + ), + dict( + fieldname="tasks", + label="Tasks", + fieldtype="Table", + options="Address Task Link", + insert_after="projects" + ), + dict( + fieldname="custom_subdivision", + label="Subdivision", + fieldtype="Link", + options="Territory", + insert_after="address_line2" + ), + dict( + fieldname="custom_customer_to_bill", + label="Customer To Bill", + fieldtype="Link", + options="Customer", + insert_after="custom_subdivision" + ) + ], + "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" + ), + dict( + fieldname="customer_type", + label="Customer Type", + fieldtype="Select", + options="Customer\nLead", + insert_after="email" + ), + dict( + fieldname="customer_name", + label="Customer Name", + fieldtype="Dynamic Link", + options="customer_type", + insert_after="customer_type" + ), + dict( + fieldname="addresses", + label="Addresses", + fieldtype="Table", + options="Contact Address Link", + insert_after="customer_name" + ) + ], + "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" + ), + dict( + fieldname="company", + label="Company", + fieldtype="Link", + options="Company", + insert_after="assigned_employee" + ), + dict( + fieldname="party_type", + label="Party Type", + fieldtype="Select", + options="Customer\nLead", + insert_after="company" + ), + dict( + fieldname="party_name", + label="Party Name", + fieldtype="Dynamic Link", + options="party_type", + insert_after="party_type" + ), + dict( + fieldname="contact", + label="Contact", + fieldtype="Link", + options="Contact", + insert_after="party_name" + ), + dict( + fieldname="project_template", + label="Project Template", + fieldtype="Link", + options="Project Template", + insert_after="company" + ) + ], + "Quotation": [ + dict( + fieldname="requires_half_payment", + label="Requires Half Payment", + fieldtype="Check", + default=0, + insert_after="custom_installation_address" + ), + dict( + fieldname="custom_quotation_template", + label="Quotation Template", + fieldtype="Link", + options="Quotation Template", + insert_after="company", + description="The template used for generating this quotation." + ), + dict( + fieldname="custom_project_template", + label="Project Template", + fieldtype="Link", + options="Project Template", + insert_after="custom_quotation_template", + description="The project template to use when creating a project from this quotation.", + allow_on_submit=1 + ), + dict( + fieldname="custom_job_address", + label="Job Address", + fieldtype="Link", + options="Address", + insert_after="custom_installation_address", + description="The address where the job will be performed.", + allow_on_submit=1 + ), + dict( + fieldname="from_onsite_meeting", + label="From On-Site Meeting", + fieldtype="Link", + options="On-Site Meeting", + insert_after="custom_job_address" + ), + dict( + fieldname="from_onsite_meeting", + label="From On-Site Meeting", + fieldtype="Link", + options="On-Site Meeting", + insert_after="custom_job_address" + ), + dict( + fieldname="actual_customer_name", + label="Customer", + fieldtype="Dynamic Link", + options="customer_type", + insert_after="from_onsite_meeting", + allow_on_submit=1 + ), + dict( + fieldname="customer_type", + label="Customer Type", + fieldtype="Select", + options="Customer\nLead", + insert_after="customer_name", + allow_on_submit=1 + ) + ], + "Sales Order": [ + dict( + fieldname="requires_half_payment", + label="Requires Half Payment", + fieldtype="Check", + default=0, + insert_after="custom_installation_address" + ), + dict( + fieldname="custom_project_template", + label="Project Template", + fieldtype="Link", + options="Project Template", + description="The project template to use when creating a project from this sales order.", + insert_after="custom_installation_address", + allow_on_submit=1 + ), + dict( + fieldname="custom_job_address", + label="Job Address", + fieldtype="Link", + options="Address", + insert_after="custom_installation_address", + description="The address where the job will be performed.", + allow_on_submit=1 + ) + ], + "Project": [ + dict( + fieldname="job_address", + label="Job Address", + fieldtype="Link", + options="Address", + insert_after="project_name", + description="The address where the job is being performed." + ), + dict( + fieldname="customer", + label="Customer", + fieldtype="Link", + options="Customer", + insert_after="job_address", + description="The customer for whom the project is being executed." + ), + dict( + fieldname="expected_start_time", + label="Expected Start Time", + fieldtype="Time", + insert_after="expected_start_date" + ), + dict( + fieldname="expected_end_time", + label="Expected End Time", + fieldtype="Time", + insert_after="expected_end_date" + ), + dict( + fieldname="actual_start_time", + label="Actual Start Time", + fieldtype="Time", + insert_after="actual_start_date" + ), + dict( + fieldname="actual_end_time", + label="Actual End Time", + fieldtype="Time", + insert_after="actual_end_date" + ), + dict( + fieldname="is_scheduled", + label="Is Scheduled", + fieldtype="Check", + default=0, + insert_after="is_half_down_paid" + ), + 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" + ), + dict( + fieldname="requires_half_payment", + label="Requires Half Payment", + fieldtype="Check", + default=0, + insert_after="expected_end_time" + ), + dict( + fieldname="is_half_down_paid", + label="Is Half Down Paid", + fieldtype="Check", + default=0, + insert_after="requires_half_payment" + ), + ], + "Project Template": [ + dict( + fieldname="company", + label="Company", + fieldtype="Link", + options="Company", + insert_after="project_type", + description="The company associated with this project template." + ), + dict( + fieldname="calendar_color", + label="Calendar Color", + fieldtype="Color", + insert_after="company" + ) + ], + "Task": [ + dict( + fieldname="project_template", + label="Project Template", + fieldtype="Link", + options="Project Template", + insert_after="project" + ) + ] +} diff --git a/custom_ui/hooks.py b/custom_ui/hooks.py index c8a17c0..606b82b 100644 --- a/custom_ui/hooks.py +++ b/custom_ui/hooks.py @@ -9,6 +9,7 @@ app_license = "mit" after_install = "custom_ui.install.after_install" after_migrate = "custom_ui.install.after_migrate" +after_uninstall = "custom_ui.install.after_uninstall" # on_session_creation = "custom_ui.utils.on_login_redirect" # on_login = "custom_ui.utils.on_login_redirect" page_js = { diff --git a/custom_ui/install.py b/custom_ui/install.py index f05dd03..f1b8249 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -6,29 +6,35 @@ import frappe from .utils import create_module import holidays from datetime import date, timedelta +import custom_fields def after_install(): create_module() - #add_custom_fields() + # add_custom_fields(custom_fields.custom_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_onsite_meeting_fields() - #update_address_fields() - #check_and_create_holiday_list() - #create_project_templates() - #create_task_types() - ## create_tasks() - #create_bid_meeting_note_form_templates() - #build_frontend() + # # 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_onsite_meeting_fields() + # update_address_fields() + # check_and_create_holiday_list() + # create_project_templates() + # create_task_types() + # # create_tasks() + # create_bid_meeting_note_form_templates() + # build_frontend() print("Custom UI After Install hook finished successfully!") + +def after_uninstall(): + remove_custom_fields(custom_fields.custom_fields) + + def after_migrate(): - add_custom_fields() + add_custom_fields(custom_fields.custom_fields) update_onsite_meeting_fields() frappe.db.commit() @@ -80,7 +86,19 @@ def build_frontend(): frappe.log_error(message=str(e), title="Frontend Build Failed") print(f"\n❌ Frontend build failed: {e}\n") -def add_custom_fields(): + +def remove_custom_fields(custom_fields): + for doctype, fields_list in custom_fields.items(): + for field in fields_list: + name = f"{doctype}-{field['fieldname']}" + if frappe.db.exists("Custom Field", name): + frappe.delete_doc("Custom Field", name) + frappe.db.commit() + print("Deleted {name}") + print("Deleted custom fields from database") + + +def add_custom_fields(custom_fields): from frappe.custom.doctype.custom_field.custom_field import create_custom_fields print("\n🔧 Adding custom fields to doctypes...") @@ -96,573 +114,6 @@ def add_custom_fields(): except Exception as e: print(f" ⚠️ Failed to update Address address_type: {e}") - custom_fields = { - "Customer": [ - dict( - fieldname="companies", - label="Companies", - fieldtype="Table", - options="Customer Company Link", - insert_after="customer_type" - ), - dict( - fieldname="quotations", - label="Quotations", - fieldtype="Table", - options="Customer Quotation Link", - insert_after="companies" - ), - dict( - fieldname="onsite_meetings", - label="On-Site Meetings", - fieldtype="Table", - options="Customer On-Site Meeting Link", - insert_after="quotations" - ), - dict( - fieldname="projects", - label="Projects", - fieldtype="Table", - options="Customer Project Link", - insert_after="onsite_meetings" - ), - dict( - fieldname="sales_orders", - label="Sales Orders", - fieldtype="Table", - options="Customer Sales Order Link", - insert_after="projects" - ), - dict( - fieldname="from_lead", - label="From Lead", - fieldtype="Link", - options="Lead", - insert_after="customer_name" - ), - dict( - fieldname="properties", - label="Properties", - fieldtype="Table", - options="Customer Address Link", - insert_after="customer_name" - ), - dict( - fieldname="contacts", - label="Contacts", - fieldtype="Table", - options="Customer Contact Link", - insert_after="properties" - ), - dict( - fieldname="primary_contact", - label="Primary Contact", - fieldtype="Link", - options="Contact", - insert_after="contacts" - ), - dict( - fieldname="tasks", - label="Tasks", - fieldtype="Table", - options="Customer Task Link", - insert_after="projects" - ) - ], - "Lead": [ - dict( - fieldname="onsite_meetings", - label="On-Site Meetings", - fieldtype="Table", - options="Lead On-Site Meeting Link", - insert_after="quotations" - ), - dict( - fieldname="custom_billing_address", - label="Custom Address", - fieldtype="Link", - options="Address", - insert_after="customer_name" - ), - dict( - fieldname="quotations", - label="Quotations", - fieldtype="Table", - options="Lead Quotation Link", - insert_after="companies" - ), - dict( - fieldname="companies", - label="Companies", - fieldtype="Table", - options="Lead Company Link", - insert_after="customer_type" - ), - dict( - fieldname="customer_type", - label="Customer Type", - fieldtype="Select", - options="Individual\nCompany\nPartnership", - insert_after="lead_name" - ), - dict( - fieldname="properties", - label="Properties", - fieldtype="Table", - options="Lead Address Link", - insert_after="customer_name" - ), - dict( - fieldname="contacts", - label="Contacts", - fieldtype="Table", - options="Lead Contact Link", - insert_after="properties" - ), - dict( - fieldname="primary_contact", - label="Primary Contact", - fieldtype="Link", - options="Contact", - insert_after="contacts" - ) - ], - "Address": [ - dict( - fieldname="primary_contact", - label="Primary Contact", - fieldtype="Link", - options="Contact", - insert_after="address_title" - ), - dict( - fieldname="projects", - label="Projects", - fieldtype="Table", - options="Address Project Link", - insert_after="onsite_meetings" - ), - dict( - fieldname="sales_orders", - label="Sales Orders", - fieldtype="Table", - options="Address Sales Order Link", - insert_after="projects" - ), - dict( - fieldname="onsite_meetings", - label="On-Site Meetings", - fieldtype="Table", - options="Address On-Site Meeting Link", - insert_after="quotations" - ), - dict( - fieldname="quotations", - label="Quotations", - fieldtype="Table", - options="Address Quotation Link", - insert_after="companies" - ), - 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" - ), - dict( - fieldname="lead_name", - label="Lead Name", - fieldtype="Data", - insert_after="custom_customer_to_bill" - ), - dict( - fieldname="customer_type", - label="Customer Type", - fieldtype="Select", - options="Customer\nLead", - insert_after="lead_name" - ), - dict( - fieldname="customer_name", - label="Customer Name", - fieldtype="Dynamic Link", - options="customer_type", - insert_after="customer_type" - ), - dict( - fieldname="contacts", - label="Contacts", - fieldtype="Table", - options="Address Contact Link", - insert_after="customer_name" - ), - dict( - fieldname="companies", - label="Companies", - fieldtype="Table", - options="Address Company Link", - insert_after="contacts" - ), - dict( - fieldname="tasks", - label="Tasks", - fieldtype="Table", - options="Address Task Link", - insert_after="projects" - ), - dict( - fieldname="custom_subdivision", - label="Subdivision", - fieldtype="Link", - options="Territory", - insert_after="address_line2" - ), - dict( - fieldname="custom_customer_to_bill", - label="Customer To Bill", - fieldtype="Link", - options="Customer", - insert_after="custom_subdivision" - ) - ], - "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" - ), - dict( - fieldname="customer_type", - label="Customer Type", - fieldtype="Select", - options="Customer\nLead", - insert_after="email" - ), - dict( - fieldname="customer_name", - label="Customer Name", - fieldtype="Dynamic Link", - options="customer_type", - insert_after="customer_type" - ), - dict( - fieldname="addresses", - label="Addresses", - fieldtype="Table", - options="Contact Address Link", - insert_after="customer_name" - ) - ], - "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" - ), - dict( - fieldname="company", - label="Company", - fieldtype="Link", - options="Company", - insert_after="assigned_employee" - ), - dict( - fieldname="party_type", - label="Party Type", - fieldtype="Select", - options="Customer\nLead", - insert_after="company" - ), - dict( - fieldname="party_name", - label="Party Name", - fieldtype="Dynamic Link", - options="party_type", - insert_after="party_type" - ), - dict( - fieldname="contact", - label="Contact", - fieldtype="Link", - options="Contact", - insert_after="party_name" - ), - dict( - fieldname="project_template", - label="Project Template", - fieldtype="Link", - options="Project Template", - insert_after="company" - ) - ], - "Quotation": [ - dict( - fieldname="requires_half_payment", - label="Requires Half Payment", - fieldtype="Check", - default=0, - insert_after="custom_installation_address" - ), - dict( - fieldname="custom_quotation_template", - label="Quotation Template", - fieldtype="Link", - options="Quotation Template", - insert_after="company", - description="The template used for generating this quotation." - ), - dict( - fieldname="custom_project_template", - label="Project Template", - fieldtype="Link", - options="Project Template", - insert_after="custom_quotation_template", - description="The project template to use when creating a project from this quotation.", - allow_on_submit=1 - ), - dict( - fieldname="custom_job_address", - label="Job Address", - fieldtype="Link", - options="Address", - insert_after="custom_installation_address", - description="The address where the job will be performed.", - allow_on_submit=1 - ), - dict( - fieldname="from_onsite_meeting", - label="From On-Site Meeting", - fieldtype="Link", - options="On-Site Meeting", - insert_after="custom_job_address" - ), - dict( - fieldname="from_onsite_meeting", - label="From On-Site Meeting", - fieldtype="Link", - options="On-Site Meeting", - insert_after="custom_job_address" - ), - dict( - fieldname="actual_customer_name", - label="Customer", - fieldtype="Dynamic Link", - options="customer_type", - insert_after="from_onsite_meeting", - allow_on_submit=1 - ), - dict( - fieldname="customer_type", - label="Customer Type", - fieldtype="Select", - options="Customer\nLead", - insert_after="customer_name", - allow_on_submit=1 - ) - ], - "Sales Order": [ - dict( - fieldname="requires_half_payment", - label="Requires Half Payment", - fieldtype="Check", - default=0, - insert_after="custom_installation_address" - ), - dict( - fieldname="custom_project_template", - label="Project Template", - fieldtype="Link", - options="Project Template", - description="The project template to use when creating a project from this sales order.", - insert_after="custom_installation_address", - allow_on_submit=1 - ), - dict( - fieldname="custom_job_address", - label="Job Address", - fieldtype="Link", - options="Address", - insert_after="custom_installation_address", - description="The address where the job will be performed.", - allow_on_submit=1 - ) - ], - "Project": [ - dict( - fieldname="job_address", - label="Job Address", - fieldtype="Link", - options="Address", - insert_after="project_name", - description="The address where the job is being performed." - ), - dict( - fieldname="customer", - label="Customer", - fieldtype="Link", - options="Customer", - insert_after="job_address", - description="The customer for whom the project is being executed." - ), - dict( - fieldname="expected_start_time", - label="Expected Start Time", - fieldtype="Time", - insert_after="expected_start_date" - ), - dict( - fieldname="expected_end_time", - label="Expected End Time", - fieldtype="Time", - insert_after="expected_end_date" - ), - dict( - fieldname="actual_start_time", - label="Actual Start Time", - fieldtype="Time", - insert_after="actual_start_date" - ), - dict( - fieldname="actual_end_time", - label="Actual End Time", - fieldtype="Time", - insert_after="actual_end_date" - ), - dict( - fieldname="is_scheduled", - label="Is Scheduled", - fieldtype="Check", - default=0, - insert_after="is_half_down_paid" - ), - 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" - ), - dict( - fieldname="requires_half_payment", - label="Requires Half Payment", - fieldtype="Check", - default=0, - insert_after="expected_end_time" - ), - dict( - fieldname="is_half_down_paid", - label="Is Half Down Paid", - fieldtype="Check", - default=0, - insert_after="requires_half_payment" - ), - ], - "Project Template": [ - dict( - fieldname="company", - label="Company", - fieldtype="Link", - options="Company", - insert_after="project_type", - description="The company associated with this project template." - ), - dict( - fieldname="calendar_color", - label="Calendar Color", - fieldtype="Color", - insert_after="company" - ) - ], - "Task": [ - dict( - fieldname="project_template", - label="Project Template", - fieldtype="Link", - options="Project Template", - insert_after="project" - ) - ] - } - print("🔧 Custom fields to check per doctype:") for key, value in custom_fields.items(): print(f" • {key}: {len(value)} fields")