diff --git a/custom_ui/api/db/clients.py b/custom_ui/api/db/clients.py
index 5c8aae8..dd0839c 100644
--- a/custom_ui/api/db/clients.py
+++ b/custom_ui/api/db/clients.py
@@ -127,83 +127,83 @@ def check_client_exists(client_name):
def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=None):
"""Get counts of clients by status categories with optional weekly filtering."""
# Build base filters for date range if weekly filtering is enabled
- try:
- base_filters = {}
- if weekly and week_start_date and week_end_date:
- # Assuming you have a date field to filter by - adjust the field name as needed
- # Common options: creation, modified, custom_date_field, etc.
- base_filters["creation"] = ["between", [week_start_date, week_end_date]]
+ # try:
+ # base_filters = {}
+ # if weekly and week_start_date and week_end_date:
+ # # Assuming you have a date field to filter by - adjust the field name as needed
+ # # Common options: creation, modified, custom_date_field, etc.
+ # base_filters["creation"] = ["between", [week_start_date, week_end_date]]
- # Helper function to merge base filters with status filters
- def get_filters(status_field, status_value):
- filters = {status_field: status_value}
- filters.update(base_filters)
- return filters
+ # # Helper function to merge base filters with status filters
+ # def get_filters(status_field, status_value):
+ # filters = {status_field: status_value}
+ # filters.update(base_filters)
+ # return filters
- onsite_meeting_scheduled_status_counts = {
- "label": "On-Site Meeting Scheduled",
- "not_started": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Not Started")),
- "in_progress": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "In Progress")),
- "completed": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Completed"))
- }
+ # onsite_meeting_scheduled_status_counts = {
+ # "label": "On-Site Meeting Scheduled",
+ # "not_started": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Not Started")),
+ # "in_progress": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "In Progress")),
+ # "completed": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Completed"))
+ # }
- estimate_sent_status_counts = {
- "label": "Estimate Sent",
- "not_started": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Not Started")),
- "in_progress": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "In Progress")),
- "completed": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Completed"))
- }
+ # estimate_sent_status_counts = {
+ # "label": "Estimate Sent",
+ # "not_started": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Not Started")),
+ # "in_progress": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "In Progress")),
+ # "completed": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Completed"))
+ # }
- job_status_counts = {
- "label": "Job Status",
- "not_started": frappe.db.count("Address", filters=get_filters("custom_job_status", "Not Started")),
- "in_progress": frappe.db.count("Address", filters=get_filters("custom_job_status", "In Progress")),
- "completed": frappe.db.count("Address", filters=get_filters("custom_job_status", "Completed"))
- }
+ # job_status_counts = {
+ # "label": "Job Status",
+ # "not_started": frappe.db.count("Address", filters=get_filters("custom_job_status", "Not Started")),
+ # "in_progress": frappe.db.count("Address", filters=get_filters("custom_job_status", "In Progress")),
+ # "completed": frappe.db.count("Address", filters=get_filters("custom_job_status", "Completed"))
+ # }
- payment_received_status_counts = {
- "label": "Payment Received",
- "not_started": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Not Started")),
- "in_progress": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "In Progress")),
- "completed": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Completed"))
- }
+ # payment_received_status_counts = {
+ # "label": "Payment Received",
+ # "not_started": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Not Started")),
+ # "in_progress": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "In Progress")),
+ # "completed": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Completed"))
+ # }
- status_dicts = [
- onsite_meeting_scheduled_status_counts,
- estimate_sent_status_counts,
- job_status_counts,
- payment_received_status_counts
- ]
+ # status_dicts = [
+ # onsite_meeting_scheduled_status_counts,
+ # estimate_sent_status_counts,
+ # job_status_counts,
+ # payment_received_status_counts
+ # ]
- categories = []
- for status_dict in status_dicts:
- category = {
- "label": status_dict["label"],
- "statuses": [
- {
- "color": "red",
- "label": "Not Started",
- "count": status_dict["not_started"]
- },
- {
- "color": "yellow",
- "label": "In Progress",
- "count": status_dict["in_progress"]
- },
- {
- "color": "green",
- "label": "Completed",
- "count": status_dict["completed"]
- }
- ]
- }
- categories.append(category)
+ # categories = []
+ # for status_dict in status_dicts:
+ # category = {
+ # "label": status_dict["label"],
+ # "statuses": [
+ # {
+ # "color": "red",
+ # "label": "Not Started",
+ # "count": status_dict["not_started"]
+ # },
+ # {
+ # "color": "yellow",
+ # "label": "In Progress",
+ # "count": status_dict["in_progress"]
+ # },
+ # {
+ # "color": "green",
+ # "label": "Completed",
+ # "count": status_dict["completed"]
+ # }
+ # ]
+ # }
+ # categories.append(category)
- return build_success_response(categories)
- except frappe.ValidationError as ve:
- return build_error_response(str(ve), 400)
- except Exception as e:
- return build_error_response(str(e), 500)
+ return build_success_response("success")
+ # except frappe.ValidationError as ve:
+ # return build_error_response(str(ve), 400)
+ # except Exception as e:
+ # return build_error_response(str(e), 500)
@frappe.whitelist()
@@ -682,4 +682,142 @@ def find_primary_contact_or_throw(contacts):
if contact.get("is_primary"):
print("#####DEBUG: Primary contact found:", contact)
return contact
- raise ValueError("No primary contact found in contacts list.")
\ No newline at end of file
+ raise ValueError("No primary contact found in contacts list.")
+
+
+def find_contact_in_list(contact_docs, contact_ref):
+ """Find a contact document in a list by matching first_name, last_name, and email."""
+ if not isinstance(contact_ref, dict):
+ return None
+ ref_first = contact_ref.get("first_name", "")
+ ref_last = contact_ref.get("last_name", "")
+ ref_email = contact_ref.get("email", "")
+ for doc in contact_docs:
+ if (doc.first_name == ref_first and
+ doc.last_name == ref_last and
+ (doc.email_id == ref_email or doc.custom_email == ref_email)):
+ return doc
+ return None
+
+
+@frappe.whitelist()
+def create_client_contacts_addresses(client_name, company, contacts=[], addresses=[]):
+ """Create or link contacts and addresses for an existing client.
+
+ If a contact or address already exists, it will be linked to the client
+ instead of creating a duplicate.
+ """
+ if isinstance(contacts, str):
+ contacts = json.loads(contacts)
+ if isinstance(addresses, str):
+ addresses = json.loads(addresses)
+ print(f"DEBUG: create_client_contacts_addresses called with client_name: {client_name}, company: {company}")
+ try:
+ client_doc = ClientService.get_client_or_throw(client_name)
+
+ # Build list of existing client contacts (preserves frontend index order)
+ existing_contact_docs = [frappe.get_doc("Contact", link.contact) for link in client_doc.contacts]
+
+ # Process new contacts
+ new_contact_docs = []
+ for contact in contacts:
+ contact_doc = check_and_get_contact(
+ contact.get("first_name"),
+ contact.get("last_name"),
+ contact.get("email"),
+ contact.get("phone_number")
+ )
+ if not contact_doc:
+ contact_doc = ContactService.create({
+ "first_name": contact.get("first_name"),
+ "last_name": contact.get("last_name"),
+ "role": contact.get("contact_role", "Other"),
+ "custom_email": contact.get("email"),
+ "is_primary_contact": 1 if contact.get("is_primary") else 0,
+ "customer_type": client_doc.doctype,
+ "customer_name": client_doc.name,
+ "email_ids": [{
+ "email_id": contact.get("email"),
+ "is_primary": 1
+ }],
+ "phone_nos": [{
+ "phone": contact.get("phone_number"),
+ "is_primary_phone": 1,
+ "is_primary_mobile_no": 1
+ }]
+ })
+ ContactService.link_contact_to_customer(contact_doc, client_doc.doctype, client_doc.name)
+ ClientService.append_link_v2(client_doc.name, "contacts", {"contact": contact_doc.name})
+ new_contact_docs.append(contact_doc)
+
+ # Combined contact list: existing client contacts + newly created/linked contacts
+ # Address contact indices reference this combined list
+ all_contact_docs = existing_contact_docs + new_contact_docs
+
+ # Process addresses
+ address_docs = []
+ for address in addresses:
+ filters = {
+ "address_line1": address.get("address_line1"),
+ "city": address.get("city"),
+ "pincode": address.get("pincode")
+ }
+ if address.get("address_line2"):
+ filters["address_line2"] = address.get("address_line2")
+
+ existing_address = frappe.db.exists("Address", filters)
+ if existing_address:
+ address_doc = frappe.get_doc("Address", existing_address)
+ else:
+ address_doc = AddressService.create({
+ "address_title": AddressService.build_address_title(client_name, address),
+ "address_line1": address.get("address_line1"),
+ "address_line2": address.get("address_line2"),
+ "city": address.get("city"),
+ "state": address.get("state"),
+ "pincode": address.get("pincode"),
+ "country": "United States",
+ "address_type": "Service",
+ "custom_billing_address": 0,
+ "is_primary_address": 0,
+ "is_service_address": 1,
+ "customer_type": client_doc.doctype,
+ "customer_name": client_doc.name
+ })
+
+ # Add company if not already present
+ if company not in [c.company for c in address_doc.companies]:
+ address_doc.append("companies", {"company": company})
+ address_doc.save(ignore_permissions=True)
+
+ # Link address to customer
+ AddressService.link_address_to_customer(address_doc, client_doc.doctype, client_doc.name)
+
+ # Link selected contacts to address
+ for contact_ref in address.get("contacts", []):
+ if not contact_ref:
+ continue
+ # Contact references are dicts with first_name, last_name, email
+ contact_doc = find_contact_in_list(all_contact_docs, contact_ref)
+ if contact_doc:
+ AddressService.link_address_to_contact(address_doc, contact_doc.name)
+ ContactService.link_contact_to_address(contact_doc, address_doc.name)
+
+ # Set primary contact for address
+ primary_ref = address.get("primary_contact")
+ if primary_ref:
+ primary_contact = find_contact_in_list(all_contact_docs, primary_ref)
+ if primary_contact:
+ AddressService.set_primary_contact(address_doc.name, primary_contact.name)
+
+ # Link address to client
+ ClientService.append_link_v2(client_doc.name, "properties", {"address": address_doc.name})
+ address_docs.append(address_doc)
+
+ return build_success_response({
+ "contacts": [c.as_dict() for c in new_contact_docs],
+ "addresses": [a.as_dict() for a in address_docs],
+ "message": "Contacts and addresses created/linked successfully."
+ })
+ except Exception as e:
+ return build_error_response(str(e), 500)
\ No newline at end of file
diff --git a/custom_ui/commands.py b/custom_ui/commands.py
index 5910c90..985a503 100644
--- a/custom_ui/commands.py
+++ b/custom_ui/commands.py
@@ -107,12 +107,19 @@ def setup_custom_ui():
pass
@click.command("import-aspire-migration")
+@click.option("--site", required=True, help="Site to import data into")
@click.option("--path", required=True, help="Path to the migration output directory containing JSON files")
@click.option("--dry-run", is_flag=True, default=False, help="Print what would be done without inserting")
-def import_aspire_migration(path, dry_run):
+def import_aspire_migration(site, path, dry_run):
"""Import Aspire migration JSON files into ERPNext in dependency order."""
+ import time
+ frappe.init(site=site)
frappe.connect()
+ # 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))
+
customers_file = os.path.join(path, "customers.json")
contacts_file = os.path.join(path, "contacts.json")
addresses_file = os.path.join(path, "addresses.json")
@@ -123,36 +130,58 @@ def import_aspire_migration(path, dry_run):
click.echo(f"❌ Missing file: {f}")
return
+ BATCH_SIZE = 1000
+
+ # Set flags to skip hooks, validations, and link checks for speed
+ frappe.flags.in_import = True
+ frappe.flags.mute_emails = True
+ frappe.flags.mute_notifications = True
+
+ def fast_insert(rec, label="record"):
+ """Insert a doc skipping hooks, validations, and permissions."""
+ doc = frappe.get_doc(rec)
+ doc.flags.ignore_permissions = True
+ doc.flags.ignore_links = True
+ doc.flags.ignore_validate = True
+ doc.flags.ignore_mandatory = True
+ doc.db_insert()
+ return doc
+
# --- Step 1: Insert Customers ---
click.echo("📦 Step 1: Inserting Customers...")
+ t0 = time.time()
with open(customers_file) as f:
customers = json.load(f)
+ # Pre-fetch existing customers in one query for fast duplicate check
+ existing_customers = set(frappe.get_all("Customer", pluck="name"))
+
success, skipped, failed = 0, 0, 0
for i, rec in enumerate(customers):
if dry_run:
click.echo(f" [DRY RUN] Would insert Customer: {rec.get('customer_name')}")
continue
try:
- if frappe.db.exists("Customer", rec.get("customer_name")):
+ if rec.get("customer_name") in existing_customers:
skipped += 1
continue
- doc = frappe.get_doc(rec)
- doc.insert(ignore_permissions=True)
+ 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}")
- if (i + 1) % 500 == 0:
+ 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}")
+ click.echo(f" ✅ Customers — inserted: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
# --- Step 2: Insert Contacts ---
click.echo("📦 Step 2: Inserting Contacts...")
+ t0 = time.time()
with open(contacts_file) as f:
contacts = json.load(f)
@@ -162,23 +191,23 @@ def import_aspire_migration(path, dry_run):
click.echo(f" [DRY RUN] Would insert Contact: {rec.get('first_name')} {rec.get('last_name')}")
continue
try:
- doc = frappe.get_doc(rec)
- doc.insert(ignore_permissions=True)
+ fast_insert(rec, "Contact")
success += 1
except Exception as e:
failed += 1
name = f"{rec.get('first_name', '')} {rec.get('last_name', '')}"
click.echo(f" ⚠️ Contact '{name}': {e}")
- if (i + 1) % 500 == 0:
+ 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}")
+ click.echo(f" ✅ Contacts — inserted: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
# --- Step 3: Insert Addresses ---
click.echo("📦 Step 3: Inserting Addresses...")
+ t0 = time.time()
with open(addresses_file) as f:
addresses = json.load(f)
@@ -188,25 +217,35 @@ def import_aspire_migration(path, dry_run):
click.echo(f" [DRY RUN] Would insert Address: {rec.get('address_line1')}")
continue
try:
- doc = frappe.get_doc(rec)
- doc.insert(ignore_permissions=True)
+ fast_insert(rec, "Address")
success += 1
except Exception as e:
failed += 1
click.echo(f" ⚠️ Address '{rec.get('address_line1', '?')}': {e}")
- if (i + 1) % 500 == 0:
+ 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}")
+ click.echo(f" ✅ Addresses — inserted: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
# --- 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:
updates = json.load(f)
+ # Get child doctype names from Customer meta once
+ customer_meta = frappe.get_meta("Customer")
+ contacts_doctype = customer_meta.get_field("contacts").options if customer_meta.has_field("contacts") else None
+ properties_doctype = customer_meta.get_field("properties").options if customer_meta.has_field("properties") else None
+
+ if contacts_doctype:
+ click.echo(f" → contacts child doctype: {contacts_doctype}")
+ if properties_doctype:
+ click.echo(f" → properties child doctype: {properties_doctype}")
+
success, skipped, failed = 0, 0, 0
for i, rec in enumerate(updates):
customer_name = rec.get("customer_name")
@@ -214,32 +253,49 @@ def import_aspire_migration(path, dry_run):
click.echo(f" [DRY RUN] Would update Customer: {customer_name}")
continue
try:
- if not frappe.db.exists("Customer", customer_name):
+ if customer_name not in existing_customers:
skipped += 1
continue
- doc = frappe.get_doc("Customer", customer_name)
-
+ # Directly insert child rows without loading/saving parent doc
for contact_row in rec.get("contacts", []):
- doc.append("contacts", contact_row)
+ 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", []):
- doc.append("properties", property_row)
+ if not properties_doctype:
+ break
+ property_row.update({
+ "doctype": properties_doctype,
+ "parent": customer_name,
+ "parenttype": "Customer",
+ "parentfield": "properties",
+ })
+ fast_insert(property_row, "property link")
- doc.save(ignore_permissions=True)
success += 1
except Exception as e:
failed += 1
click.echo(f" ⚠️ Update '{customer_name}': {e}")
- if (i + 1) % 500 == 0:
+ 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}")
+ click.echo(f" ✅ Updates — applied: {success}, skipped: {skipped}, failed: {failed} ({time.time() - t0:.1f}s)")
click.echo("🎉 Migration complete!")
+ frappe.flags.in_import = False
+ frappe.flags.mute_emails = False
+ frappe.flags.mute_notifications = False
frappe.destroy()
diff --git a/custom_ui/custom_ui/doctype/address_contact_link/__init__.py b/custom_ui/custom_ui/doctype/address_contact_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/address_contact_link/address_contact_link.json b/custom_ui/custom_ui/doctype/address_contact_link/address_contact_link.json
new file mode 100644
index 0000000..a318e4b
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/address_contact_link/address_contact_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.512268",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "contact"
+ ],
+ "fields": [
+ {
+ "fieldname": "contact",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Contact",
+ "options": "Contact",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-18 13:27:02.691142",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Address Contact Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/address_on_site_meeting_link/__init__.py b/custom_ui/custom_ui/doctype/address_on_site_meeting_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/address_on_site_meeting_link/address_on_site_meeting_link.json b/custom_ui/custom_ui/doctype/address_on_site_meeting_link/address_on_site_meeting_link.json
new file mode 100644
index 0000000..4712dc3
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/address_on_site_meeting_link/address_on_site_meeting_link.json
@@ -0,0 +1,44 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:08.988990",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "onsite_meeting",
+ "project_template"
+ ],
+ "fields": [
+ {
+ "fieldname": "onsite_meeting",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "On-Site Meeting",
+ "options": "On-Site Meeting",
+ "reqd": 1
+ },
+ {
+ "fieldname": "project_template",
+ "fieldtype": "Link",
+ "label": "Project Template",
+ "options": "Project Template"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:15:47.019375",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Address On-Site Meeting Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/address_project_link/__init__.py b/custom_ui/custom_ui/doctype/address_project_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/address_project_link/address_project_link.json b/custom_ui/custom_ui/doctype/address_project_link/address_project_link.json
new file mode 100644
index 0000000..a8ec83c
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/address_project_link/address_project_link.json
@@ -0,0 +1,44 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:08.879871",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "project",
+ "project_template"
+ ],
+ "fields": [
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Project",
+ "options": "Project",
+ "reqd": 1
+ },
+ {
+ "fieldname": "project_template",
+ "fieldtype": "Link",
+ "label": "Project Template",
+ "options": "Project Template"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:15:57.211249",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Address Project Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/address_quotation_link/__init__.py b/custom_ui/custom_ui/doctype/address_quotation_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/address_quotation_link/address_quotation_link.json b/custom_ui/custom_ui/doctype/address_quotation_link/address_quotation_link.json
new file mode 100644
index 0000000..b9fdf94
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/address_quotation_link/address_quotation_link.json
@@ -0,0 +1,44 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:08.933748",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "quotation",
+ "project_template"
+ ],
+ "fields": [
+ {
+ "fieldname": "quotation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Quotation",
+ "options": "Quotation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "project_template",
+ "fieldtype": "Link",
+ "label": "Project Template",
+ "options": "Project Template"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:16:06.875841",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Address Quotation Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/address_sales_order_link/__init__.py b/custom_ui/custom_ui/doctype/address_sales_order_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/address_sales_order_link/address_sales_order_link.json b/custom_ui/custom_ui/doctype/address_sales_order_link/address_sales_order_link.json
new file mode 100644
index 0000000..fec5fd5
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/address_sales_order_link/address_sales_order_link.json
@@ -0,0 +1,44 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.040022",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "sales_order",
+ "project_template"
+ ],
+ "fields": [
+ {
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Order",
+ "options": "Sales Order",
+ "reqd": 1
+ },
+ {
+ "fieldname": "project_template",
+ "fieldtype": "Link",
+ "label": "Project Template",
+ "options": "Project Template"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:16:15.139526",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Address Sales Order Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/bid_meeting_note/__init__.py b/custom_ui/custom_ui/doctype/bid_meeting_note/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/bid_meeting_note/bid_meeting_note.json b/custom_ui/custom_ui/doctype/bid_meeting_note/bid_meeting_note.json
new file mode 100644
index 0000000..74e9c3d
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/bid_meeting_note/bid_meeting_note.json
@@ -0,0 +1,77 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:10.182623",
+ "custom": 1,
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "form_template",
+ "bid_meeting",
+ "notes",
+ "fields",
+ "quantities"
+ ],
+ "fields": [
+ {
+ "fieldname": "form_template",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Form Template",
+ "options": "Bid Meeting Note Form",
+ "reqd": 1
+ },
+ {
+ "fieldname": "bid_meeting",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Bid Meeting",
+ "options": "On-Site Meeting",
+ "reqd": 1
+ },
+ {
+ "fieldname": "notes",
+ "fieldtype": "Small Text",
+ "label": "Notes"
+ },
+ {
+ "fieldname": "fields",
+ "fieldtype": "Table",
+ "label": "Fields",
+ "options": "Bid Meeting Note Field"
+ },
+ {
+ "fieldname": "quantities",
+ "fieldtype": "Table",
+ "label": "Quantities",
+ "options": "Bid Meeting Note Field Quantity"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2026-02-18 05:53:10.230323",
+ "modified_by": "Administrator",
+ "module": "Custom UI",
+ "name": "Bid Meeting Note",
+ "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",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/bid_meeting_note_field/bid_meeting_note_field.json b/custom_ui/custom_ui/doctype/bid_meeting_note_field/bid_meeting_note_field.json
index fcf353d..77dd2e7 100644
--- a/custom_ui/custom_ui/doctype/bid_meeting_note_field/bid_meeting_note_field.json
+++ b/custom_ui/custom_ui/doctype/bid_meeting_note_field/bid_meeting_note_field.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:50.095868",
+ "creation": "2026-02-18 05:53:10.121229",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -94,7 +95,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:52:08.063602",
+ "modified": "2026-02-18 05:53:10.166711",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Bid Meeting Note Field",
diff --git a/custom_ui/custom_ui/doctype/bid_meeting_note_field_quantity/bid_meeting_note_field_quantity.json b/custom_ui/custom_ui/doctype/bid_meeting_note_field_quantity/bid_meeting_note_field_quantity.json
index 7d35865..2af45fe 100644
--- a/custom_ui/custom_ui/doctype/bid_meeting_note_field_quantity/bid_meeting_note_field_quantity.json
+++ b/custom_ui/custom_ui/doctype/bid_meeting_note_field_quantity/bid_meeting_note_field_quantity.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:50.423957",
+ "creation": "2026-02-18 05:53:10.353383",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -36,7 +37,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:51:49.006161",
+ "modified": "2026-02-18 05:53:10.394181",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Bid Meeting Note Field Quantity",
diff --git a/custom_ui/custom_ui/doctype/bid_meeting_note_form/bid_meeting_note_form.json b/custom_ui/custom_ui/doctype/bid_meeting_note_form/bid_meeting_note_form.json
index 55c7294..be1b8d7 100644
--- a/custom_ui/custom_ui/doctype/bid_meeting_note_form/bid_meeting_note_form.json
+++ b/custom_ui/custom_ui/doctype/bid_meeting_note_form/bid_meeting_note_form.json
@@ -1,12 +1,13 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:01:57.052796",
+ "autoname": "format:{title}",
+ "creation": "2026-02-18 05:53:10.057094",
+ "custom": 1,
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"title",
- "project_template",
"notes",
"fields",
"company"
@@ -17,15 +18,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
- "reqd": 1
- },
- {
- "fieldname": "project_template",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Project Template",
- "options": "Project Template",
- "reqd": 0
+ "reqd": 1,
+ "unique": 1
},
{
"fieldname": "notes",
@@ -49,10 +43,11 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2026-01-30 07:17:51.934698",
+ "modified": "2026-02-18 05:53:10.102840",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Bid Meeting Note Form",
+ "naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
diff --git a/custom_ui/custom_ui/doctype/bid_meeting_note_form_field/bid_meeting_note_form_field.json b/custom_ui/custom_ui/doctype/bid_meeting_note_form_field/bid_meeting_note_form_field.json
index ab9a536..8285cba 100644
--- a/custom_ui/custom_ui/doctype/bid_meeting_note_form_field/bid_meeting_note_form_field.json
+++ b/custom_ui/custom_ui/doctype/bid_meeting_note_form_field/bid_meeting_note_form_field.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:49.918704",
+ "creation": "2026-02-18 05:53:09.994325",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -135,7 +136,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:52:16.305665",
+ "modified": "2026-02-18 05:53:10.041502",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Bid Meeting Note Form Field",
diff --git a/custom_ui/custom_ui/doctype/condition/condition.json b/custom_ui/custom_ui/doctype/condition/condition.json
index bb10d4f..8cc6266 100644
--- a/custom_ui/custom_ui/doctype/condition/condition.json
+++ b/custom_ui/custom_ui/doctype/condition/condition.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:01:57.401662",
+ "creation": "2026-02-18 05:53:10.294998",
+ "custom": 1,
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
@@ -16,7 +17,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2026-01-30 07:16:50.657332",
+ "modified": "2026-02-18 05:53:10.338801",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Condition",
diff --git a/custom_ui/custom_ui/doctype/contact_address_link/__init__.py b/custom_ui/custom_ui/doctype/contact_address_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/contact_address_link/contact_address_link.json b/custom_ui/custom_ui/doctype/contact_address_link/contact_address_link.json
new file mode 100644
index 0000000..14cde86
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/contact_address_link/contact_address_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.096760",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "address"
+ ],
+ "fields": [
+ {
+ "fieldname": "address",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Address",
+ "options": "Address",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:14:50.291119",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Contact Address Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/customer_address_link/customer_address_link.json b/custom_ui/custom_ui/doctype/customer_address_link/customer_address_link.json
index 31ded93..50c2cc0 100644
--- a/custom_ui/custom_ui/doctype/customer_address_link/customer_address_link.json
+++ b/custom_ui/custom_ui/doctype/customer_address_link/customer_address_link.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:48.972109",
+ "creation": "2026-02-18 05:53:09.407100",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -20,7 +21,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:52:31.110075",
+ "modified": "2026-02-18 05:53:09.446933",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Address Link",
diff --git a/custom_ui/custom_ui/doctype/customer_company_link/customer_company_link.json b/custom_ui/custom_ui/doctype/customer_company_link/customer_company_link.json
index 7c91601..085db4c 100644
--- a/custom_ui/custom_ui/doctype/customer_company_link/customer_company_link.json
+++ b/custom_ui/custom_ui/doctype/customer_company_link/customer_company_link.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:48.896768",
+ "creation": "2026-02-18 05:53:09.354310",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -18,7 +19,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:52:38.531992",
+ "modified": "2026-02-18 05:53:09.393557",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Company Link",
diff --git a/custom_ui/custom_ui/doctype/customer_contact_link/customer_contact_link.json b/custom_ui/custom_ui/doctype/customer_contact_link/customer_contact_link.json
index 4299c50..5eb179d 100644
--- a/custom_ui/custom_ui/doctype/customer_contact_link/customer_contact_link.json
+++ b/custom_ui/custom_ui/doctype/customer_contact_link/customer_contact_link.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:49.052039",
+ "creation": "2026-02-18 05:53:09.460094",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -20,7 +21,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:52:24.170798",
+ "modified": "2026-02-18 05:53:09.498323",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Contact Link",
diff --git a/custom_ui/custom_ui/doctype/customer_on_site_meeting_link/__init__.py b/custom_ui/custom_ui/doctype/customer_on_site_meeting_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/customer_on_site_meeting_link/customer_on_site_meeting_link.json b/custom_ui/custom_ui/doctype/customer_on_site_meeting_link/customer_on_site_meeting_link.json
new file mode 100644
index 0000000..179f647
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/customer_on_site_meeting_link/customer_on_site_meeting_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.563147",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "onsite_meeting"
+ ],
+ "fields": [
+ {
+ "fieldname": "onsite_meeting",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "On-Site Meeting",
+ "options": "On-Site Meeting",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:28:36.179299",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Customer On-Site Meeting Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/customer_project_link/__init__.py b/custom_ui/custom_ui/doctype/customer_project_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/customer_project_link/customer_project_link.json b/custom_ui/custom_ui/doctype/customer_project_link/customer_project_link.json
new file mode 100644
index 0000000..cebc97f
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/customer_project_link/customer_project_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.617767",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "project"
+ ],
+ "fields": [
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Project",
+ "options": "Project",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:28:47.305053",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Customer Project Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/customer_quotation_link/__init__.py b/custom_ui/custom_ui/doctype/customer_quotation_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/customer_quotation_link/customer_quotation_link.json b/custom_ui/custom_ui/doctype/customer_quotation_link/customer_quotation_link.json
new file mode 100644
index 0000000..add929a
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/customer_quotation_link/customer_quotation_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.670908",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "quotation"
+ ],
+ "fields": [
+ {
+ "fieldname": "quotation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Quotation",
+ "options": "Quotation",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:28:57.466997",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Customer Quotation Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/customer_sales_order_link/__init__.py b/custom_ui/custom_ui/doctype/customer_sales_order_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/customer_sales_order_link/customer_sales_order_link.json b/custom_ui/custom_ui/doctype/customer_sales_order_link/customer_sales_order_link.json
new file mode 100644
index 0000000..f1628fd
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/customer_sales_order_link/customer_sales_order_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.727425",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "sales_order"
+ ],
+ "fields": [
+ {
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Order",
+ "options": "Sales Order",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:29:06.649786",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Customer Sales Order Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/customer_task_link/customer_task_link.json b/custom_ui/custom_ui/doctype/customer_task_link/customer_task_link.json
index 37263a4..c2d5413 100644
--- a/custom_ui/custom_ui/doctype/customer_task_link/customer_task_link.json
+++ b/custom_ui/custom_ui/doctype/customer_task_link/customer_task_link.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:48.120856",
+ "creation": "2026-02-18 05:53:08.727283",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -29,7 +30,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:52:52.271939",
+ "modified": "2026-02-18 05:53:08.766401",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Task Link",
diff --git a/custom_ui/custom_ui/doctype/lead_address_link/__init__.py b/custom_ui/custom_ui/doctype/lead_address_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/lead_address_link/lead_address_link.json b/custom_ui/custom_ui/doctype/lead_address_link/lead_address_link.json
new file mode 100644
index 0000000..a08a35a
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/lead_address_link/lead_address_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.779109",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "address"
+ ],
+ "fields": [
+ {
+ "fieldname": "address",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Address",
+ "options": "Address",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:16:22.611831",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Lead Address Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/lead_companies_link/__init__.py b/custom_ui/custom_ui/doctype/lead_companies_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/lead_companies_link/lead_companies_link.json b/custom_ui/custom_ui/doctype/lead_companies_link/lead_companies_link.json
new file mode 100644
index 0000000..4858867
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/lead_companies_link/lead_companies_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:08.828617",
+ "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-19 12:29:19.514404",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Lead Companies Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/lead_company_link/__init__.py b/custom_ui/custom_ui/doctype/lead_company_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/lead_company_link/lead_company_link.json b/custom_ui/custom_ui/doctype/lead_company_link/lead_company_link.json
new file mode 100644
index 0000000..e8090ec
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/lead_company_link/lead_company_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:08.671692",
+ "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-19 12:29:30.435977",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Lead Company Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/lead_contact_link/__init__.py b/custom_ui/custom_ui/doctype/lead_contact_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/lead_contact_link/lead_contact_link.json b/custom_ui/custom_ui/doctype/lead_contact_link/lead_contact_link.json
new file mode 100644
index 0000000..68cf0e8
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/lead_contact_link/lead_contact_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.831349",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "contact"
+ ],
+ "fields": [
+ {
+ "fieldname": "contact",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Contact",
+ "options": "Contact",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:29:41.971971",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Lead Contact Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/lead_on_site_meeting_link/__init__.py b/custom_ui/custom_ui/doctype/lead_on_site_meeting_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/lead_on_site_meeting_link/lead_on_site_meeting_link.json b/custom_ui/custom_ui/doctype/lead_on_site_meeting_link/lead_on_site_meeting_link.json
new file mode 100644
index 0000000..0dd6cbc
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/lead_on_site_meeting_link/lead_on_site_meeting_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.149765",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "onsite_meeting"
+ ],
+ "fields": [
+ {
+ "fieldname": "onsite_meeting",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "On-Site Meeting",
+ "options": "On-Site Meeting",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:29:50.898270",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Lead On-Site Meeting Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/lead_quotation_link/__init__.py b/custom_ui/custom_ui/doctype/lead_quotation_link/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/lead_quotation_link/lead_quotation_link.json b/custom_ui/custom_ui/doctype/lead_quotation_link/lead_quotation_link.json
new file mode 100644
index 0000000..6a95a3e
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/lead_quotation_link/lead_quotation_link.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:09.883408",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "quotation"
+ ],
+ "fields": [
+ {
+ "fieldname": "quotation",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Quotation",
+ "options": "Quotation",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-19 12:30:01.642314",
+ "modified_by": "casey@shilohcode.com",
+ "module": "Custom UI",
+ "name": "Lead Quotation Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/custom_ui/doctype/project_task_link/project_task_link.json b/custom_ui/custom_ui/doctype/project_task_link/project_task_link.json
index 14a618a..5ae6e61 100644
--- a/custom_ui/custom_ui/doctype/project_task_link/project_task_link.json
+++ b/custom_ui/custom_ui/doctype/project_task_link/project_task_link.json
@@ -1,7 +1,8 @@
{
"actions": [],
"allow_rename": 1,
- "creation": "2026-01-30 07:21:50.267662",
+ "creation": "2026-02-18 05:53:10.243616",
+ "custom": 1,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -22,7 +23,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-01-30 07:51:59.777431",
+ "modified": "2026-02-18 05:53:10.281244",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Project Task Link",
diff --git a/custom_ui/custom_ui/doctype/skip_day/__init__.py b/custom_ui/custom_ui/doctype/skip_day/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/custom_ui/custom_ui/doctype/skip_day/skip_day.json b/custom_ui/custom_ui/doctype/skip_day/skip_day.json
new file mode 100644
index 0000000..fbdc607
--- /dev/null
+++ b/custom_ui/custom_ui/doctype/skip_day/skip_day.json
@@ -0,0 +1,36 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2026-02-18 05:53:10.480897",
+ "custom": 1,
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "date"
+ ],
+ "fields": [
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Date",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-02-18 05:53:10.518277",
+ "modified_by": "Administrator",
+ "module": "Custom UI",
+ "name": "Skip Day",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/custom_ui/fixtures/bid_meeting_note_form.json b/custom_ui/fixtures/bid_meeting_note_form.json
deleted file mode 100644
index a1cab74..0000000
--- a/custom_ui/fixtures/bid_meeting_note_form.json
+++ /dev/null
@@ -1,153 +0,0 @@
-[
- {
- "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"
- }
-]
\ No newline at end of file
diff --git a/custom_ui/fixtures/project_template.json b/custom_ui/fixtures/project_template.json
deleted file mode 100644
index 5c095ce..0000000
--- a/custom_ui/fixtures/project_template.json
+++ /dev/null
@@ -1,72 +0,0 @@
-[
- {
- "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"
- }
- ]
- }
-]
\ No newline at end of file
diff --git a/frontend/src/api.js b/frontend/src/api.js
index 82c2d06..6b3671c 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -69,6 +69,7 @@ const FRAPPE_GET_CLIENT_METHOD = "custom_ui.api.db.clients.get_client_v2";
const FRAPPE_GET_CLIENT_NAMES_METHOD = "custom_ui.api.db.clients.get_client_names";
const FRAPPE_CHECK_CLIENT_EXISTS_METHOD = "custom_ui.api.db.clients.check_client_exists";
const FRAPPE_ADD_ADDRESSES_CONTACTS_METHOD = "custom_ui.api.db.clients.add_addresses_contacts";
+const FRAPPE_CREATE_CLIENT_CONTACTS_ADDRESSES_METHOD = "custom_ui.api.db.clients.create_client_contacts_addresses";
// Employee methods
const FRAPPE_GET_EMPLOYEES_METHOD = "custom_ui.api.db.employees.get_employees";
const FRAPPE_GET_EMPLOYEES_ORGANIZED_METHOD = "custom_ui.api.db.employees.get_employees_organized";
@@ -188,6 +189,10 @@ class Api {
return await this.request(FRAPPE_ADD_ADDRESSES_CONTACTS_METHOD, { clientName, companyName, addresses, contacts });
}
+ static async createClientContactsAddresses(clientName, company, contacts = [], addresses = []) {
+ return await this.request(FRAPPE_CREATE_CLIENT_CONTACTS_ADDRESSES_METHOD, { clientName, company, contacts, addresses });
+ }
+
// ============================================================================
// ON-SITE MEETING METHODS
// ============================================================================
diff --git a/frontend/src/components/clientView/AddContactAddressModal.vue b/frontend/src/components/clientView/AddContactAddressModal.vue
index 19ccc58..525b100 100644
--- a/frontend/src/components/clientView/AddContactAddressModal.vue
+++ b/frontend/src/components/clientView/AddContactAddressModal.vue
@@ -66,18 +66,23 @@ import Dialog from 'primevue/dialog';
import Button from 'primevue/button';
import ModalContactForm from './ModalContactForm.vue';
import ModalAddressForm from './ModalAddressForm.vue';
+import Api from '../../api';
+import { useCompanyStore } from '../../stores/company';
+
+const companyStore = useCompanyStore();
const props = defineProps({
visible: Boolean,
+ clientName: { type: String, default: '' },
clientContacts: { type: Array, default: () => [] },
existingContacts: { type: Array, default: () => [] },
existingAddresses: { type: Array, default: () => [] },
- isSubmitting: { type: Boolean, default: false },
});
const emit = defineEmits(['update:visible', 'created']);
const showContacts = ref(false);
const showAddresses = ref(false);
+const isSubmitting = ref(false);
// Direct arrays instead of wrapping in formData objects
const newContacts = ref([
@@ -136,16 +141,57 @@ function close() {
emit('update:visible', false);
}
-function create() {
- const payload = {};
- if (showContacts.value) {
- payload.contacts = newContacts.value;
+async function create() {
+ isSubmitting.value = true;
+ try {
+ const contactsToSend = showContacts.value ? newContacts.value : [];
+ const addressesToSend = showAddresses.value ? newAddresses.value : [];
+
+ // Check if any contacts or addresses already exist
+ const existingMessages = [];
+
+ if (contactsToSend.length > 0) {
+ const existingContactsResult = await Api.checkContactsExist(contactsToSend);
+ if (existingContactsResult && existingContactsResult.length > 0) {
+ const names = existingContactsResult.map(c => `${c.firstName || ''} ${c.lastName || ''}`.trim()).join(', ');
+ existingMessages.push(`Contact(s) already exist: ${names}`);
+ }
+ }
+
+ if (addressesToSend.length > 0) {
+ const existingAddressesResult = await Api.checkAddressesExist(addressesToSend);
+ if (existingAddressesResult && existingAddressesResult.length > 0) {
+ const addrs = existingAddressesResult.map(a => `${a.addressLine1 || ''} ${a.city || ''}`).join(', ');
+ existingMessages.push(`Address(es) already exist: ${addrs}`);
+ }
+ }
+
+ // If any exist, prompt the user for confirmation
+ if (existingMessages.length > 0) {
+ const message = existingMessages.join('\n') + '\n\nWould you like to proceed anyway? Existing records will be linked instead of duplicated.';
+ if (!window.confirm(message)) {
+ isSubmitting.value = false;
+ return;
+ }
+ }
+
+ // Call API to create/link contacts and addresses
+ // Address contacts/primaryContact are dicts with firstName, lastName, email
+ // that the backend matches against created/existing contact docs
+ const result = await Api.createClientContactsAddresses(
+ props.clientName,
+ companyStore.currentCompany,
+ contactsToSend,
+ addressesToSend
+ );
+
+ emit('created', result);
+ close();
+ } catch (error) {
+ console.error('Error creating contacts/addresses:', error);
+ } finally {
+ isSubmitting.value = false;
}
- if (showAddresses.value) {
- payload.addresses = newAddresses.value;
- }
- emit('created', payload);
- close();
}
diff --git a/frontend/src/components/clientView/GeneralClientInfo.vue b/frontend/src/components/clientView/GeneralClientInfo.vue
index e5efd04..b5cf1a6 100644
--- a/frontend/src/components/clientView/GeneralClientInfo.vue
+++ b/frontend/src/components/clientView/GeneralClientInfo.vue
@@ -98,9 +98,11 @@
@@ -126,6 +128,8 @@ const props = defineProps({
},
});
+const emit = defineEmits(['refresh']);
+
// Check if client is a Lead
const isLead = computed(() => props.clientData.doctype === "Lead");
@@ -173,6 +177,11 @@ const formattedCreationDate = computed(() => {
});
});
+// Handle successful contact/address creation - emit refresh so parent reloads client data
+const onContactsAddressesCreated = () => {
+ emit('refresh');
+};
+
diff --git a/frontend/src/components/pages/Client.vue b/frontend/src/components/pages/Client.vue
index 5416acf..6d19837 100644
--- a/frontend/src/components/pages/Client.vue
+++ b/frontend/src/components/pages/Client.vue
@@ -29,6 +29,7 @@
@@ -461,6 +462,12 @@ const handleCustomerSelected = (clientData) => {
// Handle customer selected from search
client.value = { ...client.value, ...clientData };
};
+
+const refreshClient = async () => {
+ if (clientName) {
+ await getClient(clientName);
+ }
+};