This commit is contained in:
Casey 2026-02-20 10:56:40 -06:00
parent 6c703c2c3b
commit 6ff5a09ad4
24 changed files with 270688 additions and 237967 deletions

View File

@ -317,7 +317,7 @@ def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
where_sql = "WHERE " + " AND ".join(where_clauses)
offset = (page - 1) * page_size
print("DEBUG: Constructed SQL where clause:", where_sql)
address_names = frappe.db.sql(f"""
SELECT DISTINCT a.name
FROM `tabAddress` a

View File

@ -112,10 +112,21 @@ def setup_custom_ui():
@click.option("--dry-run", is_flag=True, default=False, help="Print what would be done without inserting")
def import_aspire_migration(site, path, dry_run):
"""Import Aspire migration JSON files into ERPNext in dependency order."""
import sys
import time
frappe.init(site=site)
frappe.connect()
def print_progress(step_label, current, total, success, skipped, failed):
"""Print an in-place progress line using \\r."""
sys.stdout.write(f"\r {step_label}: {current}/{total} (✓ {success}{skipped}{failed})")
sys.stdout.flush()
def finish_progress(step_label, success, skipped, failed, elapsed):
"""Clear the progress line and print the final summary."""
sys.stdout.write("\r\033[K") # clear the line
click.echo(f"{step_label} — inserted: {success}, skipped: {skipped}, failed: {failed} ({elapsed:.1f}s)")
# Resolve path relative to the app if not absolute
if not os.path.isabs(path):
path = os.path.join(frappe.get_app_path("custom_ui"), os.path.basename(path))
@ -123,9 +134,12 @@ def import_aspire_migration(site, path, dry_run):
customers_file = os.path.join(path, "customers.json")
contacts_file = os.path.join(path, "contacts.json")
addresses_file = os.path.join(path, "addresses.json")
updates_file = os.path.join(path, "customer_updates.json")
customer_updates_file = os.path.join(path, "customer_updates.json")
address_updates_file = os.path.join(path, "address_updates.json")
contacts_updates_file = os.path.join(path, "contacts_updates.json")
for f in [customers_file, contacts_file, addresses_file, updates_file]:
for f in [customers_file, contacts_file, addresses_file, customer_updates_file, address_updates_file, contacts_updates_file]:
if not os.path.exists(f):
click.echo(f"❌ Missing file: {f}")
return
@ -140,13 +154,39 @@ def import_aspire_migration(site, path, dry_run):
def fast_insert(rec, label="record"):
"""Insert a doc skipping hooks, validations, and permissions."""
doc = frappe.get_doc(rec)
company_links = rec.pop("companies", [])
doc.flags.ignore_permissions = True
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.flags.ignore_mandatory = True
doc.db_insert()
doc.flags.ignore_links = True
for link in company_links:
doc.append("companies", link)
doc.insert()
return doc
def fast_update(rec, doctype):
filters = {}
if doctype == "Customer":
filters = {"customer_name": rec.get("customer_name")}
rec.pop("customer_name", None)
elif doctype == "Address":
filters = {"address_title": rec.get("address_title")}
rec.pop("address_title", None)
elif doctype == "Contact":
filters = {"full_name": rec.get("full_name")}
rec.pop("full_name", None)
name = frappe.db.get_value(doctype, filters)
if not name:
raise ValueError(f"No {doctype} found matching {filters}")
doc = frappe.get_doc(doctype, name)
for key, value in rec.items():
if isinstance(value, list):
for item in value:
doc.append(key, item)
elif value:
doc.set(key, value)
doc.flags.ignore_permissions = True
doc.save()
# --- Step 1: Insert Customers ---
click.echo("📦 Step 1: Inserting Customers...")
t0 = time.time()
@ -157,6 +197,7 @@ def import_aspire_migration(site, path, dry_run):
existing_customers = set(frappe.get_all("Customer", pluck="name"))
success, skipped, failed = 0, 0, 0
total = len(customers)
for i, rec in enumerate(customers):
if dry_run:
click.echo(f" [DRY RUN] Would insert Customer: {rec.get('customer_name')}")
@ -164,20 +205,20 @@ def import_aspire_migration(site, path, dry_run):
try:
if rec.get("customer_name") in existing_customers:
skipped += 1
continue
fast_insert(rec, "Customer")
existing_customers.add(rec.get("customer_name"))
success += 1
else:
fast_insert(rec, "Customer")
existing_customers.add(rec.get("customer_name"))
success += 1
except Exception as e:
failed += 1
click.echo(f" ⚠️ Customer '{rec.get('customer_name')}': {e}")
print_progress("Customers", i + 1, total, success, skipped, failed)
if (i + 1) % BATCH_SIZE == 0:
frappe.db.commit()
click.echo(f" ... committed {i + 1}/{len(customers)}")
frappe.db.commit()
click.echo(f" ✅ Customers — inserted: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
finish_progress("Customers", success, skipped, failed, time.time() - t0)
# --- Step 2: Insert Contacts ---
click.echo("📦 Step 2: Inserting Contacts...")
@ -186,7 +227,12 @@ def import_aspire_migration(site, path, dry_run):
contacts = json.load(f)
success, skipped, failed = 0, 0, 0
total = len(contacts)
for i, rec in enumerate(contacts):
full_name = f"{rec.get('first_name', '')} {rec.get('last_name', '')}".strip()
if frappe.db.exists("Contact", {"full_name": full_name}):
skipped += 1
continue
if dry_run:
click.echo(f" [DRY RUN] Would insert Contact: {rec.get('first_name')} {rec.get('last_name')}")
continue
@ -195,15 +241,17 @@ def import_aspire_migration(site, path, dry_run):
success += 1
except Exception as e:
failed += 1
name = f"{rec.get('first_name', '')} {rec.get('last_name', '')}"
click.echo(f" ⚠️ Contact '{name}': {e}")
print_progress("Contacts", i + 1, total, success, skipped, failed)
if (i + 1) % BATCH_SIZE == 0:
frappe.db.commit()
click.echo(f" ... committed {i + 1}/{len(contacts)}")
frappe.db.commit()
click.echo(f" ✅ Contacts — inserted: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
finish_progress("Contacts", success, skipped, failed, time.time() - t0)
# Clear cache so link validation can find the newly inserted contacts
frappe.clear_cache()
# --- Step 3: Insert Addresses ---
click.echo("📦 Step 3: Inserting Addresses...")
@ -212,7 +260,11 @@ def import_aspire_migration(site, path, dry_run):
addresses = json.load(f)
success, skipped, failed = 0, 0, 0
total = len(addresses)
for i, rec in enumerate(addresses):
if frappe.db.exists("Address", {"address_title": rec.get("address_title")}):
skipped += 1
continue
if dry_run:
click.echo(f" [DRY RUN] Would insert Address: {rec.get('address_line1')}")
continue
@ -220,20 +272,21 @@ def import_aspire_migration(site, path, dry_run):
fast_insert(rec, "Address")
success += 1
except Exception as e:
print(f"Error inserting Address {rec.get('address_title')}: {e}")
failed += 1
click.echo(f" ⚠️ Address '{rec.get('address_line1', '?')}': {e}")
print_progress("Addresses", i + 1, total, success, skipped, failed)
if (i + 1) % BATCH_SIZE == 0:
frappe.db.commit()
click.echo(f" ... committed {i + 1}/{len(addresses)}")
frappe.db.commit()
click.echo(f" ✅ Addresses — inserted: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
finish_progress("Addresses", success, skipped, failed, time.time() - t0)
# --- Step 4: Update Customers with child tables ---
click.echo("📦 Step 4: Updating Customers with contact/property links...")
t0 = time.time()
with open(updates_file) as f:
with open(customer_updates_file) as f:
updates = json.load(f)
# Get child doctype names from Customer meta once
@ -247,6 +300,7 @@ def import_aspire_migration(site, path, dry_run):
click.echo(f" → properties child doctype: {properties_doctype}")
success, skipped, failed = 0, 0, 0
total = len(updates)
for i, rec in enumerate(updates):
customer_name = rec.get("customer_name")
if dry_run:
@ -255,42 +309,76 @@ def import_aspire_migration(site, path, dry_run):
try:
if customer_name not in existing_customers:
skipped += 1
continue
# Directly insert child rows without loading/saving parent doc
for contact_row in rec.get("contacts", []):
if not contacts_doctype:
break
contact_row.update({
"doctype": contacts_doctype,
"parent": customer_name,
"parenttype": "Customer",
"parentfield": "contacts",
})
fast_insert(contact_row, "contact link")
for property_row in rec.get("properties", []):
if not properties_doctype:
break
property_row.update({
"doctype": properties_doctype,
"parent": customer_name,
"parenttype": "Customer",
"parentfield": "properties",
})
fast_insert(property_row, "property link")
success += 1
else:
fast_update(rec, "Customer")
success += 1
except Exception as e:
failed += 1
click.echo(f" ⚠️ Update '{customer_name}': {e}")
print_progress("Customer updates", i + 1, total, success, skipped, failed)
if (i + 1) % BATCH_SIZE == 0:
frappe.db.commit()
click.echo(f" ... committed {i + 1}/{len(updates)}")
frappe.db.commit()
click.echo(f" ✅ Updates — applied: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
finish_progress("Customer updates", success, skipped, failed, time.time() - t0)
# --- Step 5: Update Addresses with customer links ---
click.echo("📦 Step 5: Updating Addresses with customer links...")
t0 = time.time()
with open(address_updates_file) as f:
updates = json.load(f)
success, skipped, failed = 0, 0, 0
total = len(updates)
for i, rec in enumerate(updates):
address_title = rec.get("address_title")
if dry_run:
click.echo(f" [DRY RUN] Would update Address: {address_title}")
continue
try:
fast_update(rec, "Address")
success += 1
except Exception as e:
failed += 1
print(f"Error updating Address {address_title}: {e}")
print_progress("Address updates", i + 1, total, success, skipped, failed)
if (i + 1) % BATCH_SIZE == 0:
frappe.db.commit()
frappe.db.commit()
finish_progress("Address updates", success, skipped, failed, time.time() - t0)
# --- Step 6: Update Contacts with customer links ---
click.echo("📦 Step 6: Updating Contacts with customer links...")
t0 = time.time()
with open(contacts_updates_file) as f:
updates = json.load(f)
success, skipped, failed = 0, 0, 0
total = len(updates)
for i, rec in enumerate(updates):
contact_name = f"{rec.get('first_name', '')} {rec.get('last_name', '')}"
if dry_run:
click.echo(f" [DRY RUN] Would update Contact: {contact_name}")
continue
try:
fast_update(rec, "Contact")
success += 1
except Exception as e:
failed += 1
print(f"Error updating Contact {contact_name}: {e}")
print_progress("Contact updates", i + 1, total, success, skipped, failed)
if (i + 1) % BATCH_SIZE == 0:
frappe.db.commit()
frappe.db.commit()
finish_progress("Contact updates", success, skipped, failed, time.time() - t0)
click.echo("🎉 Migration complete!")
frappe.flags.in_import = False

View File

@ -0,0 +1,57 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-18 05:53:07.818441",
"custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"contact",
"role",
"email",
"phone"
],
"fields": [
{
"fieldname": "contact",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Contact",
"options": "Contact",
"reqd": 1
},
{
"fieldname": "role",
"fieldtype": "Select",
"label": "Role",
"options": "Owner\nProperty Manager\nTenant\nBuilder\nNeighbor\nFamily member\nRealtor\nOther"
},
{
"fieldname": "email",
"fieldtype": "Data",
"label": "Email",
"read_only": 1
},
{
"fieldname": "phone",
"fieldtype": "Data",
"label": "Phone",
"read_only": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-20 03:21:47.159037",
"modified_by": "casey@shilohcode.com",
"module": "Custom UI",
"name": "Address Contact Role",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,65 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-18 05:53:08.441123",
"custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"address_name",
"address_line_1",
"city",
"state",
"pincode",
"address_type"
],
"fields": [
{
"fieldname": "address_name",
"fieldtype": "Link",
"label": "Address",
"options": "Address"
},
{
"fieldname": "address_line_1",
"fieldtype": "Data",
"label": "Address Line 1"
},
{
"fieldname": "city",
"fieldtype": "Data",
"label": "City"
},
{
"fieldname": "state",
"fieldtype": "Data",
"label": "State"
},
{
"fieldname": "pincode",
"fieldtype": "Data",
"label": "Pincode"
},
{
"fieldname": "address_type",
"fieldtype": "Select",
"label": "Address Type",
"options": "Billing\nService\nBuilder\nInstallation"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-20 03:39:57.967418",
"modified_by": "casey@shilohcode.com",
"module": "Custom UI",
"name": "Custom Customer Address Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,59 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:check_list_item_name",
"creation": "2026-02-18 05:53:07.008038",
"custom": 1,
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"permit_inspection_complete_section",
"check_list_item_name",
"description"
],
"fields": [
{
"fieldname": "permit_inspection_complete_section",
"fieldtype": "Section Break",
"label": "Check List Item"
},
{
"fieldname": "check_list_item_name",
"fieldtype": "Data",
"label": "Name",
"unique": 1
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-02-20 03:14:58.875000",
"modified_by": "casey@shilohcode.com",
"module": "Custom UI",
"name": "Follow Check List Fields",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,527 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:address",
"creation": "2026-02-18 05:53:06.908262",
"custom": 1,
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"15_day_warranty_check_list",
"address",
"column_break_qkir",
"name1",
"inspector_date",
"section_break_qcgd",
"form_date",
"install_start_date",
"install_end_date",
"column_break_lxgp",
"builder_and_bft",
"development",
"column_break_wlcx",
"has_customer_taken_over",
"homeowner",
"phone",
"email",
"section_break_wjee",
"permit_inspection_complete_yes",
"permit_inspection_complete_no",
"permit_notes",
"permit_notes_office",
"section_break_etlc",
"backflow_certified_yes",
"backflow_certified_no",
"backflow_notes",
"backflow_notes_office",
"any_state_repairs_required_to_pass_section",
"any_state_repairs_yes",
"any_state_repairs_no",
"state_repairs_notes",
"state_repairs_notes_office",
"turn_timer_down_or_up_section",
"turn_timer_yes",
"turn_timer_no",
"turn_timer_notes",
"turn_timer_notes_office",
"hydroseed_or_sod_section",
"hydro_sod_yes",
"hydro_sod_no",
"hydro_sod_notes",
"hydro_sod_notes_office",
"fertilized_lawn_with_16x16x16_section",
"fertilized_yes",
"fertilized_no",
"fertilized_notes",
"fertilized_notes_office",
"requirement_for_turn_on_or_blowout_posted_section",
"turn_on_blowout_yes",
"turn_on_blowout_no",
"turn_on_blowout_notes",
"turn_on_blowout_notes_office",
"print_out_of_hydroseed__sod_care_guide_posted_section",
"hydroseed_sod_care_yes",
"hydroseed_sod_care_no",
"hydroseed_sod_care_notes",
"hydroseed_sod_care_notes_office",
"irrigation_zone_numbers_labeled_inside_of_timer_section",
"irrigation_zones_yes",
"irrigation_zones_no",
"irrigation_zones_notes",
"irrigation_zones_notes_office",
"section_break_hlnz",
"did_lowe_fencing_do_install_column",
"lowe_install_yes",
"lowe_install_no",
"lowe_install_notes",
"lowe_install_notes_office",
"if_so_does_the_gate_swing_correctly_section",
"swings_yes",
"swings_no",
"swings_notes",
"swings_notes_office",
"section_break_muil",
"things_for_install_crew_next_time",
"column_break_ghug",
"general_customer_comments_to_install"
],
"fields": [
{
"fieldname": "15_day_warranty_check_list",
"fieldtype": "Heading",
"label": "15 Day Warranty Check List"
},
{
"fieldname": "address",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Address",
"options": "Properties",
"reqd": 1,
"unique": 1
},
{
"fieldname": "column_break_qkir",
"fieldtype": "Column Break"
},
{
"fieldname": "name1",
"fieldtype": "Data",
"label": "SNW Representative"
},
{
"fieldname": "inspector_date",
"fieldtype": "Date",
"label": "Date of Appt"
},
{
"fieldname": "section_break_qcgd",
"fieldtype": "Section Break"
},
{
"fieldname": "form_date",
"fieldtype": "Date",
"label": "Expected Date"
},
{
"fieldname": "install_start_date",
"fieldtype": "Date",
"label": "Install Start Date"
},
{
"fieldname": "install_end_date",
"fieldtype": "Date",
"label": "Install End Date"
},
{
"fieldname": "column_break_lxgp",
"fieldtype": "Column Break"
},
{
"fieldname": "builder_and_bft",
"fieldtype": "Data",
"label": "builder_and_bft"
},
{
"fieldname": "development",
"fieldtype": "Link",
"label": "Development",
"options": "Territory"
},
{
"fieldname": "column_break_wlcx",
"fieldtype": "Column Break"
},
{
"fieldname": "has_customer_taken_over",
"fieldtype": "Heading",
"label": "Has customer taken over?"
},
{
"fieldname": "homeowner",
"fieldtype": "Link",
"label": "Homeowner",
"options": "Contact"
},
{
"fetch_from": "homeowner.phone",
"fieldname": "phone",
"fieldtype": "Phone",
"label": "Phone",
"read_only": 1
},
{
"fetch_from": "homeowner.email_id",
"fieldname": "email",
"fieldtype": "Data",
"label": "Email",
"read_only": 1
},
{
"fieldname": "section_break_wjee",
"fieldtype": "Section Break",
"label": "Permit inspection complete"
},
{
"default": "0",
"fieldname": "permit_inspection_complete_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "permit_inspection_complete_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "permit_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "permit_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "section_break_etlc",
"fieldtype": "Section Break",
"label": "Backflow Certified"
},
{
"default": "0",
"fieldname": "backflow_certified_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "backflow_certified_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "backflow_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "backflow_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "any_state_repairs_required_to_pass_section",
"fieldtype": "Section Break",
"label": "Any State Repairs Required to pass?"
},
{
"default": "0",
"fieldname": "any_state_repairs_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "any_state_repairs_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "state_repairs_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "state_repairs_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "turn_timer_down_or_up_section",
"fieldtype": "Section Break",
"label": "Turn timer down or up"
},
{
"default": "0",
"fieldname": "turn_timer_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "turn_timer_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "turn_timer_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "turn_timer_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "hydroseed_or_sod_section",
"fieldtype": "Section Break",
"label": "Hydroseed or Sod"
},
{
"default": "0",
"fieldname": "hydro_sod_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "hydro_sod_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "hydro_sod_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "hydro_sod_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "fertilized_lawn_with_16x16x16_section",
"fieldtype": "Section Break",
"label": "Fertilized Lawn with 16x16x16"
},
{
"default": "0",
"fieldname": "fertilized_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "fertilized_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "fertilized_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "fertilized_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "requirement_for_turn_on_or_blowout_posted_section",
"fieldtype": "Section Break",
"label": "Requirement for Turn On / Blowout Posted"
},
{
"default": "0",
"fieldname": "turn_on_blowout_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "turn_on_blowout_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "turn_on_blowout_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "turn_on_blowout_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "print_out_of_hydroseed__sod_care_guide_posted_section",
"fieldtype": "Section Break",
"label": "Print out of HydroSeed / Sod Care Guide Posted"
},
{
"default": "0",
"fieldname": "hydroseed_sod_care_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "hydroseed_sod_care_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "hydroseed_sod_care_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "hydroseed_sod_care_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "irrigation_zone_numbers_labeled_inside_of_timer_section",
"fieldtype": "Section Break",
"label": "Irrigation zone numbers labeled inside of timer"
},
{
"default": "0",
"fieldname": "irrigation_zones_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "irrigation_zones_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "irrigation_zones_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "irrigation_zones_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "section_break_hlnz",
"fieldtype": "Section Break",
"label": "Did Lowe fencing do install?"
},
{
"fieldname": "did_lowe_fencing_do_install_column",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "lowe_install_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "lowe_install_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "lowe_install_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "lowe_install_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "if_so_does_the_gate_swing_correctly_section",
"fieldtype": "Section Break",
"label": "If so does the gate swing correctly?"
},
{
"default": "0",
"fieldname": "swings_yes",
"fieldtype": "Check",
"label": "Yes"
},
{
"default": "0",
"fieldname": "swings_no",
"fieldtype": "Check",
"label": "No"
},
{
"fieldname": "swings_notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "swings_notes_office",
"fieldtype": "Small Text",
"label": "Notes for Office"
},
{
"fieldname": "section_break_muil",
"fieldtype": "Section Break"
},
{
"fieldname": "things_for_install_crew_next_time",
"fieldtype": "Small Text",
"label": "Things for Install crew next time"
},
{
"fieldname": "column_break_ghug",
"fieldtype": "Column Break"
},
{
"fieldname": "general_customer_comments_to_install",
"fieldtype": "Small Text",
"label": "General customer comments to install"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-02-20 03:15:16.026719",
"modified_by": "casey@shilohcode.com",
"module": "Custom UI",
"name": "Follow Up Checklist",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-18 05:53:07.766014",
"custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-20 03:19:20.452597",
"modified_by": "casey@shilohcode.com",
"module": "Custom UI",
"name": "Linked Companies",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -2,7 +2,12 @@ import frappe
from custom_ui.db_utils import build_full_address
def before_insert(doc, method):
print("DEBUG: Before Insert Triggered for Address")
# print("DEBUG: Before Insert Triggered for Address")
if not doc.full_address:
doc.full_address = build_full_address(doc)
def before_delete(doc, method):
print("DEBUG: Before Delete Triggered for Address")
# Clear the full_address field before deletion to prevent orphaned data

View File

@ -3,4 +3,5 @@ import frappe
def attach_bid_note_form_to_project_template(doc, method):
"""Attatch Bid Meeting Note Form to Project Template on insert."""
print("DEBUG: Attaching Bid Meeting Note Form to Project Template")
frappe.set_value("Project Template", doc.project_template, "bid_meeting_note_form", doc.name)
# frappe.set_value("Project Template", doc.project_template, "bid_meeting_note_form", doc.name)

View File

@ -0,0 +1,153 @@
[
{
"company": "Sprinklers Northwest",
"docstatus": 0,
"doctype": "Bid Meeting Note Form",
"fields": [
{
"column": 1,
"conditional_on_field": null,
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": null,
"doctype_label_field": null,
"help_text": "Indicate if a locate is needed for this project.",
"include_options": 0,
"label": "Locate Needed",
"options": null,
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 1,
"type": "Check"
},
{
"column": 2,
"conditional_on_field": null,
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": null,
"doctype_label_field": null,
"help_text": "Indicate if a permit is needed for this project.",
"include_options": 0,
"label": "Permit Needed",
"options": null,
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 1,
"type": "Check"
},
{
"column": 3,
"conditional_on_field": null,
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": null,
"doctype_label_field": null,
"help_text": "Indicate if a backflow test is required after installation.",
"include_options": 0,
"label": "Back Flow Test Required",
"options": null,
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 1,
"type": "Check"
},
{
"column": 1,
"conditional_on_field": null,
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": null,
"doctype_label_field": null,
"help_text": null,
"include_options": 0,
"label": "Machine Access",
"options": null,
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 2,
"type": "Check"
},
{
"column": 2,
"conditional_on_field": "Machine Access",
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": null,
"doctype_label_field": null,
"help_text": null,
"include_options": 1,
"label": "Machines",
"options": "MT, Skip Steer, Excavator-E-50, Link Belt, Tre?, Forks, Auger, Backhoe, Loader, Duzer",
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 2,
"type": "Multi-Select"
},
{
"column": 0,
"conditional_on_field": null,
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": null,
"doctype_label_field": null,
"help_text": null,
"include_options": 0,
"label": "Materials Required",
"options": null,
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 3,
"type": "Check"
},
{
"column": 0,
"conditional_on_field": "Materials Required",
"conditional_on_value": null,
"default_value": null,
"doctype_for_select": "Item",
"doctype_label_field": "itemName",
"help_text": null,
"include_options": 0,
"label": "Materials",
"options": null,
"order": 0,
"parent": "SNW Install Bid Meeting Notes",
"parentfield": "fields",
"parenttype": "Bid Meeting Note Form",
"read_only": 0,
"required": 0,
"row": 4,
"type": "Multi-Select w/ Quantity"
}
],
"modified": "2026-02-18 05:52:37.304228",
"name": "SNW Install Bid Meeting Notes",
"notes": null,
"title": "SNW Install Bid Meeting Notes"
}
]

View File

@ -1,61 +1,4 @@
[
{
"allow_in_quick_entry": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": "contact.mobile_no",
"fetch_if_empty": 1,
"fieldname": "custom_phone_number",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 0,
"insert_after": "contact",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Phone Number",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:50:41.564255",
"module": null,
"name": "Service Appointment-custom_phone_number",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -227,63 +170,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": "Kris Sims",
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": null,
"fetch_if_empty": 1,
"fieldname": "custom_assigned_to",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 0,
"insert_after": "service_details",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Assigned to",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:56:08.644464",
"module": null,
"name": "Service Appointment-custom_assigned_to",
"no_copy": 0,
"non_negative": 0,
"options": "Sales Person",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -569,63 +455,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": "contact.custom_service_address",
"fetch_if_empty": 0,
"fieldname": "custom_location_of_meeting",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 1,
"in_standard_filter": 0,
"insert_after": "section_break_toee",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Service Address",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:50:41.472790",
"module": null,
"name": "Service Appointment-custom_location_of_meeting",
"no_copy": 0,
"non_negative": 0,
"options": "Address",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 1,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -6611,63 +6440,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_sms_optin",
"fieldtype": "Check",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_phone_number",
"is_system_generated": 0,
"is_virtual": 0,
"label": "SMS Opt-In",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-09-02 10:21:59.145925",
"module": null,
"name": "Service Appointment-custom_sms_optin",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -6839,63 +6611,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": "contact.email_id",
"fetch_if_empty": 1,
"fieldname": "custom_email_address",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 1,
"in_standard_filter": 0,
"insert_after": "custom_sms_optin",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Email Address",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:50:41.658096",
"module": null,
"name": "Service Appointment-custom_email_address",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -7181,63 +6896,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_column_break_dsqvk",
"fieldtype": "Column Break",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_email_address",
"is_system_generated": 0,
"is_virtual": 0,
"label": "",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:50:41.381550",
"module": null,
"name": "Service Appointment-custom_column_break_dsqvk",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 1,
"allow_on_submit": 0,
@ -7523,63 +7181,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_internal_company",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "status",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Internal Company",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:53:41.538535",
"module": null,
"name": "Service Appointment-custom_internal_company",
"no_copy": 0,
"non_negative": 0,
"options": "Company",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -8777,63 +8378,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_section_break_gndxh",
"fieldtype": "Section Break",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_internal_company",
"is_system_generated": 0,
"is_virtual": 0,
"label": "",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-06 16:50:41.747787",
"module": null,
"name": "Service Appointment-custom_section_break_gndxh",
"no_copy": 0,
"non_negative": 0,
"options": null,
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
@ -9062,63 +8606,6 @@
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Service Appointment",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "auto_repeat",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_assigned_to",
"is_system_generated": 1,
"is_virtual": 0,
"label": "Auto Repeat",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2025-01-07 12:01:01.971054",
"module": null,
"name": "Service Appointment-auto_repeat",
"no_copy": 1,
"non_negative": 0,
"options": "Auto Repeat",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,

View File

@ -0,0 +1,72 @@
[
{
"bid_meeting_note_form": "SNW Install Bid Meeting Notes",
"calendar_color": "#c1dec5",
"company": "Sprinklers Northwest",
"custom__complete_method": "Task Weight",
"docstatus": 0,
"doctype": "Project Template",
"item_groups": "SNW-I, SNW-S, SNW-LS",
"modified": "2026-02-16 03:59:53.719382",
"name": "SNW Install",
"project_type": "External",
"tasks": [
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Send customer 3-5 day window for start date",
"task": "TASK-2025-00001"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "811/Locate call in",
"task": "TASK-2025-00002"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Permit(s) call in and pay",
"task": "TASK-2025-00003"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Primary Job",
"task": "TASK-2025-00004"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Hydroseeding",
"task": "TASK-2025-00005"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Curbing",
"task": "TASK-2025-00006"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "15-Day QA",
"task": "TASK-2025-00007"
},
{
"parent": "SNW Install",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Permit Close-out",
"task": "TASK-2025-00008"
}
]
}
]

View File

@ -227,7 +227,7 @@ def remove_fencing_job_queue_links(doc):
fixtures = [
{
"dt": "Custom Field",
"filters": [["module", "in", ["Custom UI"]], ["dt", "not in", ["Service Appointment"]]]
"filters": [["dt", "not in", ["Service Appointment"]]]
},
# {
# "dt": "Email Template",
@ -255,23 +255,23 @@ fixtures = [
# { "dt": "Holiday List" },
# These don't have reliable flags → export all
{"dt": "Custom Field"},
# {"dt": "Custom Field"},
{"dt": "Property Setter",
"filters": [["name", "not in", ["Service Appointment-service_address", "Service Appointment-customer", "Service Appointment-project", "Service Appointment-project_template", "Service Appointment"]],]},
{"dt": "Client Script"},
{"dt": "Server Script"},
{
"dt": "User",
"filters": [
["name", "not in", ["Administrator", "Guest"]]
]
},
{
"dt": "Role Profile"
},
{
"dt": "Role"
},
# {
# "dt": "User",
# "filters": [
# ["name", "not in", ["Administrator", "Guest"]]
# ]
# },
# {
# "dt": "Role Profile"
# },
# {
# "dt": "Role"
# },
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -117,8 +117,7 @@
:id="`contacts-${index}`"
v-model="address.contacts"
:options="contactOptions"
optionLabel="label"
dataKey="value"
optionLabel="label" optionValue="value" dataKey="value"
:disabled="isSubmitting || contactOptions.length === 0"
placeholder="Select contacts"
class="w-full"
@ -130,8 +129,9 @@
<Select
:id="`primaryContact-${index}`"
v-model="address.primaryContact"
:options="address.contacts"
optionLabel="label"
:options="getPrimaryContactOptions(address.contacts)"
optionLabel="label"
optionValue="value"
dataKey="value"
:disabled="isSubmitting || !address.contacts || address.contacts.length === 0"
placeholder="Select primary contact"
@ -256,6 +256,11 @@ const formatAddressLine = (index, field, event) => {
localFormData.value.addresses[index][field] = formatted;
};
const getPrimaryContactOptions = (selectedContactIndexes) => {
if (!selectedContactIndexes || selectedContactIndexes.length === 0) return [];
return contactOptions.value.filter(opt => selectedContactIndexes.includes(opt.value));
};
const handleBillingChange = (selectedIndex) => {
// If the selected address is now checked as billing
if (localFormData.value.addresses[selectedIndex].isBillingAddress) {
@ -267,12 +272,12 @@ const handleBillingChange = (selectedIndex) => {
}
});
// Auto-select all contacts (store full objects)
// Auto-select all contacts (store indexes only)
if (contactOptions.value.length > 0) {
localFormData.value.addresses[selectedIndex].contacts = [...contactOptions.value];
localFormData.value.addresses[selectedIndex].contacts = contactOptions.value.map(o => o.value);
}
// Auto-select primary contact (store full object)
// Auto-select primary contact (store index only)
const allOpts = contactOptions.value;
if (allOpts.length > 0) {
// Try to find the primary from localFormData contacts
@ -280,12 +285,12 @@ const handleBillingChange = (selectedIndex) => {
const primaryIndex = localFormData.value.contacts.findIndex((c) => c.isPrimary);
if (primaryIndex !== -1) {
const primaryOpt = allOpts.find((o) => o.value === primaryIndex);
localFormData.value.addresses[selectedIndex].primaryContact = primaryOpt || allOpts[0];
localFormData.value.addresses[selectedIndex].primaryContact = (primaryOpt || allOpts[0]).value;
} else {
localFormData.value.addresses[selectedIndex].primaryContact = allOpts[0];
localFormData.value.addresses[selectedIndex].primaryContact = allOpts[0].value;
}
} else {
localFormData.value.addresses[selectedIndex].primaryContact = allOpts[0];
localFormData.value.addresses[selectedIndex].primaryContact = allOpts[0].value;
}
}
} else {