update with migration data
This commit is contained in:
parent
d84c9fd20c
commit
30031c4c56
BIN
csv/Aspire_GridExport_Users.xlsx
Normal file
BIN
csv/Aspire_GridExport_Users.xlsx
Normal file
Binary file not shown.
@ -2,6 +2,7 @@ import click
|
||||
import os
|
||||
import subprocess
|
||||
import frappe
|
||||
import json
|
||||
from custom_ui.utils import create_module
|
||||
from custom_ui.api.db.general import search_any_field
|
||||
from custom_ui.install import create_companies, create_project_templates, create_task_types, create_tasks, create_bid_meeting_note_form_templates
|
||||
@ -104,6 +105,142 @@ def create_module_command():
|
||||
|
||||
def setup_custom_ui():
|
||||
pass
|
||||
|
||||
@click.command("import-aspire-migration")
|
||||
@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):
|
||||
"""Import Aspire migration JSON files into ERPNext in dependency order."""
|
||||
frappe.connect()
|
||||
|
||||
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")
|
||||
|
||||
for f in [customers_file, contacts_file, addresses_file, updates_file]:
|
||||
if not os.path.exists(f):
|
||||
click.echo(f"❌ Missing file: {f}")
|
||||
return
|
||||
|
||||
# --- Step 1: Insert Customers ---
|
||||
click.echo("📦 Step 1: Inserting Customers...")
|
||||
with open(customers_file) as f:
|
||||
customers = json.load(f)
|
||||
|
||||
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")):
|
||||
skipped += 1
|
||||
continue
|
||||
doc = frappe.get_doc(rec)
|
||||
doc.insert(ignore_permissions=True)
|
||||
success += 1
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
click.echo(f" ⚠️ Customer '{rec.get('customer_name')}': {e}")
|
||||
|
||||
if (i + 1) % 500 == 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}")
|
||||
|
||||
# --- Step 2: Insert Contacts ---
|
||||
click.echo("📦 Step 2: Inserting Contacts...")
|
||||
with open(contacts_file) as f:
|
||||
contacts = json.load(f)
|
||||
|
||||
success, skipped, failed = 0, 0, 0
|
||||
for i, rec in enumerate(contacts):
|
||||
if 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)
|
||||
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:
|
||||
frappe.db.commit()
|
||||
click.echo(f" ... committed {i + 1}/{len(contacts)}")
|
||||
|
||||
frappe.db.commit()
|
||||
click.echo(f" ✅ Contacts — inserted: {success}, skipped: {skipped}, failed: {failed}")
|
||||
|
||||
# --- Step 3: Insert Addresses ---
|
||||
click.echo("📦 Step 3: Inserting Addresses...")
|
||||
with open(addresses_file) as f:
|
||||
addresses = json.load(f)
|
||||
|
||||
success, skipped, failed = 0, 0, 0
|
||||
for i, rec in enumerate(addresses):
|
||||
if 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)
|
||||
success += 1
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
click.echo(f" ⚠️ Address '{rec.get('address_line1', '?')}': {e}")
|
||||
|
||||
if (i + 1) % 500 == 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}")
|
||||
|
||||
# --- Step 4: Update Customers with child tables ---
|
||||
click.echo("📦 Step 4: Updating Customers with contact/property links...")
|
||||
with open(updates_file) as f:
|
||||
updates = json.load(f)
|
||||
|
||||
success, skipped, failed = 0, 0, 0
|
||||
for i, rec in enumerate(updates):
|
||||
customer_name = rec.get("customer_name")
|
||||
if dry_run:
|
||||
click.echo(f" [DRY RUN] Would update Customer: {customer_name}")
|
||||
continue
|
||||
try:
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
doc = frappe.get_doc("Customer", customer_name)
|
||||
|
||||
for contact_row in rec.get("contacts", []):
|
||||
doc.append("contacts", contact_row)
|
||||
|
||||
for property_row in rec.get("properties", []):
|
||||
doc.append("properties", property_row)
|
||||
|
||||
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:
|
||||
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("🎉 Migration complete!")
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
commands = [build_frontend, create_module_command]
|
||||
@ -1,795 +0,0 @@
|
||||
[
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:22.939672",
|
||||
"name": "System Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "System Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:22.949185",
|
||||
"name": "Guest",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Guest",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:22.957171",
|
||||
"name": "Administrator",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Administrator",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:22.966723",
|
||||
"name": "All",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "All",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:22.974828",
|
||||
"name": "Desk User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Desk User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:23.925708",
|
||||
"name": "Website Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Website Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:24.685089",
|
||||
"name": "Dashboard Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Dashboard Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:26.798862",
|
||||
"name": "Workspace Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Workspace Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:27.293623",
|
||||
"name": "Report Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Report Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:29.149829",
|
||||
"name": "Script Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Script Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:36.945427",
|
||||
"name": "Inbox User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Inbox User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:39.761649",
|
||||
"name": "Prepared Report User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Prepared Report User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:43.947516",
|
||||
"name": "Blogger",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Blogger",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:46.346917",
|
||||
"name": "Newsletter Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Newsletter Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:47.243381",
|
||||
"name": "Knowledge Base Contributor",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Knowledge Base Contributor",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:47.262953",
|
||||
"name": "Knowledge Base Editor",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Knowledge Base Editor",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:56.661394",
|
||||
"name": "Accounts Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Accounts Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:56.674846",
|
||||
"name": "Purchase User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Purchase User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:56.683274",
|
||||
"name": "Sales User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Sales User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:14:56.701284",
|
||||
"name": "Accounts User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Accounts User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:07.963749",
|
||||
"name": "Sales Master Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Sales Master Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:07.979912",
|
||||
"name": "Maintenance Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Maintenance Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:08.013321",
|
||||
"name": "Sales Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Sales Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:08.040169",
|
||||
"name": "Maintenance User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Maintenance User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:08.049510",
|
||||
"name": "Purchase Master Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Purchase Master Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:08.058859",
|
||||
"name": "Purchase Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Purchase Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:16.260699",
|
||||
"name": "Translator",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Translator",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:24.541181",
|
||||
"name": "Auditor",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Auditor",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:28.563445",
|
||||
"name": "Employee",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Employee",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:28.593109",
|
||||
"name": "Stock User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Stock User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:15:54.780680",
|
||||
"name": "HR Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "HR Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:00.248577",
|
||||
"name": "Manufacturing Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Manufacturing Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:01.617491",
|
||||
"name": "Stock Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Stock Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:04.521100",
|
||||
"name": "Projects User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Projects User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:05.499661",
|
||||
"name": "Projects Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Projects Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:06.888198",
|
||||
"name": "Manufacturing User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Manufacturing User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:06.962029",
|
||||
"name": "HR User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "HR User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:15.058111",
|
||||
"name": "Item Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Item Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:17:19.247810",
|
||||
"name": "Customer",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Customer",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:19.053592",
|
||||
"name": "Delivery Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Delivery Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:19.110307",
|
||||
"name": "Delivery User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Delivery User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:19.124541",
|
||||
"name": "Fleet Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Fleet Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:19.602602",
|
||||
"name": "Academics User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Academics User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:38.054879",
|
||||
"name": "Fulfillment User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Fulfillment User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:41.401518",
|
||||
"name": "Quality Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Quality Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:51.029966",
|
||||
"name": "Support Team",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Support Team",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:58.882176",
|
||||
"name": "Agriculture User",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Agriculture User",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:16:58.966355",
|
||||
"name": "Agriculture Manager",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Agriculture Manager",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:17:19.249024",
|
||||
"name": "Supplier",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Supplier",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2024-04-04 04:17:16.072081",
|
||||
"name": "Analytics",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Analytics",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": "/app",
|
||||
"is_custom": 0,
|
||||
"modified": "2025-01-28 15:46:59.075095",
|
||||
"name": "Technician",
|
||||
"restrict_to_domain": "Service",
|
||||
"role_name": "Technician",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-01-13 10:13:13.163560",
|
||||
"name": "Interviewer",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Interviewer",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-01-13 10:13:15.161152",
|
||||
"name": "Expense Approver",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Expense Approver",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-01-13 10:13:16.149250",
|
||||
"name": "Leave Approver",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Leave Approver",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 1,
|
||||
"modified": "2025-01-13 10:13:27.355987",
|
||||
"name": "Employee Self Service",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Employee Self Service",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": "/app/snw-foreman",
|
||||
"is_custom": 0,
|
||||
"modified": "2025-04-17 11:54:33.174189",
|
||||
"name": "SNW Foreman",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "SNW Foreman",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": "/app/snw-front-office",
|
||||
"is_custom": 0,
|
||||
"modified": "2025-05-02 04:52:34.365177",
|
||||
"name": "SNW Front Office",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "SNW Front Office",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-05-02 06:37:19.711082",
|
||||
"name": "SNW Install Admin",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "SNW Install Admin",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-05-02 09:02:42.978358",
|
||||
"name": "Nuco Admin",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Nuco Admin",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-05-02 09:02:53.410754",
|
||||
"name": "Lowe Admin",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "Lowe Admin",
|
||||
"two_factor_auth": 0
|
||||
},
|
||||
{
|
||||
"desk_access": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Role",
|
||||
"home_page": null,
|
||||
"is_custom": 0,
|
||||
"modified": "2025-05-02 09:07:40.352031",
|
||||
"name": "SNW Service Admin",
|
||||
"restrict_to_domain": null,
|
||||
"role_name": "SNW Service Admin",
|
||||
"two_factor_auth": 0
|
||||
}
|
||||
]
|
||||
@ -1,725 +0,0 @@
|
||||
[
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2024-04-04 04:17:19.023597",
|
||||
"name": "Inventory",
|
||||
"role_profile": "Inventory",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Inventory",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"parent": "Inventory",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Inventory",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Item Manager"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2024-04-04 04:17:19.035761",
|
||||
"name": "Manufacturing",
|
||||
"role_profile": "Manufacturing",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Manufacturing",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"parent": "Manufacturing",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Manufacturing User"
|
||||
},
|
||||
{
|
||||
"parent": "Manufacturing",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Manufacturing Manager"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2024-12-30 09:14:46.881813",
|
||||
"name": "Accounts",
|
||||
"role_profile": "Accounts",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Accounts",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"parent": "Accounts",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Accounts",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Academics User"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2024-04-04 04:17:19.070987",
|
||||
"name": "Purchase",
|
||||
"role_profile": "Purchase",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Purchase",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Item Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Purchase",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"parent": "Purchase",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"parent": "Purchase",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase Manager"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2025-02-10 14:08:13.619290",
|
||||
"name": "System Manager",
|
||||
"role_profile": "System Manager",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "System Manager",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "System Manager"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2025-01-28 13:40:31.163924",
|
||||
"name": "HR",
|
||||
"role_profile": "HR & Admin",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "HR User"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "HR Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Leave Approver"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Expense Approver"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Dashboard Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Agriculture Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Analytics"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Auditor"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Delivery Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Fleet Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Inbox User"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Interviewer"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Item Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Maintenance Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Manufacturing Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Newsletter Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Prepared Report User"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Projects Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase Master Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Quality Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Report Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Sales Master Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Workspace Manager"
|
||||
},
|
||||
{
|
||||
"parent": "HR",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Website Manager"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2025-01-28 15:49:20.863461",
|
||||
"name": "Technician",
|
||||
"role_profile": "Technician",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Technician",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Projects User"
|
||||
},
|
||||
{
|
||||
"parent": "Technician",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Maintenance User"
|
||||
},
|
||||
{
|
||||
"parent": "Technician",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2025-02-10 14:33:01.847181",
|
||||
"name": "Admin",
|
||||
"role_profile": "Admin",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Academics User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Agriculture Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Analytics"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Auditor"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Blogger"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Dashboard Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Delivery Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Fleet Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Fulfillment User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "HR User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Inbox User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Item Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Knowledge Base Editor"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Maintenance Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Maintenance User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Manufacturing Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Newsletter Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Prepared Report User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Projects Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase Master Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Quality Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Report Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Sales Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Sales Master Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Script Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Supplier"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Support Team"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Translator"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Website Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Workspace Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Agriculture User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Delivery User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Employee"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Projects User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Manufacturing User"
|
||||
},
|
||||
{
|
||||
"parent": "Admin",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Knowledge Base Contributor"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Role Profile",
|
||||
"modified": "2025-02-12 10:37:54.633409",
|
||||
"name": "Sales",
|
||||
"role_profile": "Sales",
|
||||
"roles": [
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Projects User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Maintenance User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Delivery User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Fulfillment User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Agriculture User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Support Team"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Prepared Report User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Manufacturing User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Inbox User"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Employee Self Service"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Employee"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Blogger"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Workspace Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Website Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"parent": "Sales",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Role Profile",
|
||||
"role": "Accounts Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
206954
custom_ui/migration_data/addresses.json
Normal file
206954
custom_ui/migration_data/addresses.json
Normal file
File diff suppressed because it is too large
Load Diff
149252
custom_ui/migration_data/contacts.json
Normal file
149252
custom_ui/migration_data/contacts.json
Normal file
File diff suppressed because it is too large
Load Diff
57440
custom_ui/migration_data/customer_updates.json
Normal file
57440
custom_ui/migration_data/customer_updates.json
Normal file
File diff suppressed because it is too large
Load Diff
74664
custom_ui/migration_data/customers.json
Normal file
74664
custom_ui/migration_data/customers.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -118,7 +118,7 @@
|
||||
v-model="address.contacts"
|
||||
:options="contactOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
dataKey="value"
|
||||
:disabled="isSubmitting || contactOptions.length === 0"
|
||||
placeholder="Select contacts"
|
||||
class="w-full"
|
||||
@ -130,10 +130,10 @@
|
||||
<Select
|
||||
:id="`primaryContact-${index}`"
|
||||
v-model="address.primaryContact"
|
||||
:options="address.contacts.map(c => contactOptions.find(opt => opt.value === c))"
|
||||
:options="address.contacts"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
:disabled="isSubmitting || contactOptions.length === 0"
|
||||
dataKey="value"
|
||||
:disabled="isSubmitting || !address.contacts || address.contacts.length === 0"
|
||||
placeholder="Select primary contact"
|
||||
class="w-full"
|
||||
/>
|
||||
@ -208,8 +208,13 @@ const localFormData = computed({
|
||||
});
|
||||
|
||||
const contactOptions = computed(() => {
|
||||
// When contactOptions prop is provided (e.g. from modal with merged contacts), use it
|
||||
if (props.contactOptions && props.contactOptions.length > 0) {
|
||||
return props.contactOptions;
|
||||
}
|
||||
// Fallback: derive from localFormData.contacts (e.g. client creation wizard)
|
||||
if (!localFormData.value.contacts || localFormData.value.contacts.length === 0) {
|
||||
return props.contactOptions;
|
||||
return [];
|
||||
}
|
||||
return localFormData.value.contacts.map((contact, index) => ({
|
||||
label: `${contact.firstName || ""} ${contact.lastName || ""}`.trim() || `Contact ${index + 1}`,
|
||||
@ -262,21 +267,25 @@ const handleBillingChange = (selectedIndex) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-select all contacts
|
||||
// Auto-select all contacts (store full objects)
|
||||
if (contactOptions.value.length > 0) {
|
||||
localFormData.value.addresses[selectedIndex].contacts = contactOptions.value.map(
|
||||
(opt) => opt.value,
|
||||
);
|
||||
localFormData.value.addresses[selectedIndex].contacts = [...contactOptions.value];
|
||||
}
|
||||
|
||||
// Auto-select primary contact
|
||||
if (localFormData.value.contacts && localFormData.value.contacts.length > 0) {
|
||||
const primaryIndex = localFormData.value.contacts.findIndex((c) => c.isPrimary);
|
||||
if (primaryIndex !== -1) {
|
||||
localFormData.value.addresses[selectedIndex].primaryContact = primaryIndex;
|
||||
// Auto-select primary contact (store full object)
|
||||
const allOpts = contactOptions.value;
|
||||
if (allOpts.length > 0) {
|
||||
// Try to find the primary from localFormData contacts
|
||||
if (localFormData.value.contacts && localFormData.value.contacts.length > 0) {
|
||||
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];
|
||||
} else {
|
||||
localFormData.value.addresses[selectedIndex].primaryContact = allOpts[0];
|
||||
}
|
||||
} else {
|
||||
// Fallback to first contact if no primary found
|
||||
localFormData.value.addresses[selectedIndex].primaryContact = 0;
|
||||
localFormData.value.addresses[selectedIndex].primaryContact = allOpts[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -1,18 +1,47 @@
|
||||
<template>
|
||||
<Dialog :visible="visible" @update:visible="val => emit('update:visible', val)" modal :closable="false" :style="{ width: '700px', maxWidth: '95vw' }">
|
||||
<template #header>
|
||||
<span class="modal-title">Add Contact/Address</span>
|
||||
<span class="modal-title">Add Contact / Address</span>
|
||||
</template>
|
||||
<div class="modal-body">
|
||||
<ContactInformationForm
|
||||
:formData="contactFormData.value"
|
||||
@update:formData="val => contactFormData.value = val"
|
||||
<!-- Toggle buttons -->
|
||||
<div class="toggle-buttons">
|
||||
<Button
|
||||
:label="showContacts ? 'Hide Contacts' : 'Add Contacts'"
|
||||
:icon="showContacts ? 'pi pi-minus' : 'pi pi-user-plus'"
|
||||
:severity="showContacts ? 'secondary' : 'primary'"
|
||||
:outlined="!showContacts"
|
||||
@click="showContacts = !showContacts"
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
:label="showAddresses ? 'Hide Addresses' : 'Add Addresses'"
|
||||
:icon="showAddresses ? 'pi pi-minus' : 'pi pi-map-marker'"
|
||||
:severity="showAddresses ? 'secondary' : 'primary'"
|
||||
:outlined="!showAddresses"
|
||||
@click="showAddresses = !showAddresses"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Hint when nothing is selected -->
|
||||
<div v-if="!showContacts && !showAddresses" class="empty-hint">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span>Click a button above to add contacts or addresses to this client.</span>
|
||||
</div>
|
||||
|
||||
<!-- Contact form -->
|
||||
<ModalContactForm
|
||||
v-if="showContacts"
|
||||
:contacts="newContacts"
|
||||
:isSubmitting="isSubmitting"
|
||||
:existingContacts="existingContacts"
|
||||
/>
|
||||
<AddressInformationForm
|
||||
:formData="addressFormData.value"
|
||||
@update:formData="val => addressFormData.value = val"
|
||||
|
||||
<!-- Address form -->
|
||||
<ModalAddressForm
|
||||
v-if="showAddresses"
|
||||
:addresses="newAddresses"
|
||||
:isSubmitting="isSubmitting"
|
||||
:contactOptions="allContactOptions"
|
||||
:existingAddresses="existingAddresses"
|
||||
@ -20,7 +49,13 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="Cancel" @click="close" severity="secondary" />
|
||||
<Button label="Create" @click="create" severity="primary" :loading="isSubmitting" />
|
||||
<Button
|
||||
label="Create"
|
||||
@click="create"
|
||||
severity="primary"
|
||||
:loading="isSubmitting"
|
||||
:disabled="!showContacts && !showAddresses"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@ -29,8 +64,8 @@
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import Button from 'primevue/button';
|
||||
import ContactInformationForm from '../clientSubPages/ContactInformationForm.vue';
|
||||
import AddressInformationForm from '../clientSubPages/AddressInformationForm.vue';
|
||||
import ModalContactForm from './ModalContactForm.vue';
|
||||
import ModalAddressForm from './ModalAddressForm.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: Boolean,
|
||||
@ -41,46 +76,75 @@ const props = defineProps({
|
||||
});
|
||||
const emit = defineEmits(['update:visible', 'created']);
|
||||
|
||||
const contactFormData = ref({ contacts: [] });
|
||||
const addressFormData = ref({ addresses: [], contacts: [] });
|
||||
const showContacts = ref(false);
|
||||
const showAddresses = ref(false);
|
||||
|
||||
// Keep addressFormData.contacts in sync with new contacts
|
||||
watch(
|
||||
() => contactFormData.value.contacts,
|
||||
(newContacts) => {
|
||||
addressFormData.value.contacts = newContacts || [];
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
// Direct arrays instead of wrapping in formData objects
|
||||
const newContacts = ref([
|
||||
{ firstName: '', lastName: '', phoneNumber: '', email: '', contactRole: '', isPrimary: true },
|
||||
]);
|
||||
const newAddresses = ref([
|
||||
{ addressLine1: '', addressLine2: '', pincode: '', city: '', state: '', contacts: [], primaryContact: null, zipcodeLookupDisabled: true },
|
||||
]);
|
||||
|
||||
// All contact options = clientContacts + new contacts
|
||||
// Reset forms when modal opens
|
||||
watch(() => props.visible, (isVisible) => {
|
||||
if (isVisible) {
|
||||
showContacts.value = false;
|
||||
showAddresses.value = false;
|
||||
newContacts.value = [
|
||||
{ firstName: '', lastName: '', phoneNumber: '', email: '', contactRole: '', isPrimary: true },
|
||||
];
|
||||
newAddresses.value = [
|
||||
{ addressLine1: '', addressLine2: '', pincode: '', city: '', state: '', contacts: [], primaryContact: null, zipcodeLookupDisabled: true },
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
// All contact options = existing client contacts + new contacts being created in this modal
|
||||
const allContactOptions = computed(() => {
|
||||
const clientOpts = (props.clientContacts || []).map((c, idx) => ({
|
||||
label: `${c.firstName || ''} ${c.lastName || ''}`.trim() || `Contact ${idx + 1}`,
|
||||
value: `client-${idx}`,
|
||||
...c,
|
||||
}));
|
||||
const newOpts = (contactFormData.value.contacts || []).map((c, idx) => ({
|
||||
label: `${c.firstName || ''} ${c.lastName || ''}`.trim() || `Contact ${idx + 1}`,
|
||||
value: `new-${idx}`,
|
||||
...c,
|
||||
}));
|
||||
const clientOpts = (props.clientContacts || []).map((c, idx) => {
|
||||
const firstName = c.firstName || c.first_name || '';
|
||||
const lastName = c.lastName || c.last_name || '';
|
||||
const email = c.email || c.emailId || c.email_id || c.customEmail || '';
|
||||
return {
|
||||
label: `${firstName} ${lastName}`.trim() || c.fullName || c.name || `Contact ${idx + 1}`,
|
||||
value: `client-${idx}`,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
};
|
||||
});
|
||||
const newOpts = showContacts.value
|
||||
? (newContacts.value || []).map((c, idx) => {
|
||||
const firstName = c.firstName || '';
|
||||
const lastName = c.lastName || '';
|
||||
const email = c.email || '';
|
||||
return {
|
||||
label: `${firstName} ${lastName}`.trim() || `New Contact ${idx + 1}`,
|
||||
value: `new-${idx}`,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
return [...clientOpts, ...newOpts];
|
||||
});
|
||||
|
||||
function close() {
|
||||
emit('update:visible', false);
|
||||
}
|
||||
|
||||
function create() {
|
||||
// Dummy create handler
|
||||
console.log('Create clicked', {
|
||||
contacts: contactFormData.value.contacts,
|
||||
addresses: addressFormData.value.addresses,
|
||||
});
|
||||
emit('created', {
|
||||
contacts: contactFormData.value.contacts,
|
||||
addresses: addressFormData.value.addresses,
|
||||
});
|
||||
const payload = {};
|
||||
if (showContacts.value) {
|
||||
payload.contacts = newContacts.value;
|
||||
}
|
||||
if (showAddresses.value) {
|
||||
payload.addresses = newAddresses.value;
|
||||
}
|
||||
emit('created', payload);
|
||||
close();
|
||||
}
|
||||
</script>
|
||||
@ -93,7 +157,25 @@ function create() {
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
.toggle-buttons {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.empty-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
background: var(--surface-ground);
|
||||
border-radius: 6px;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.empty-hint i {
|
||||
font-size: 1rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
362
frontend/src/components/clientView/ModalAddressForm.vue
Normal file
362
frontend/src/components/clientView/ModalAddressForm.vue
Normal file
@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<i class="pi pi-map-marker" style="color: var(--primary-color); font-size: 1.2rem;"></i>
|
||||
<h3>Add Addresses</h3>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div
|
||||
v-for="(address, index) in addresses"
|
||||
:key="index"
|
||||
class="address-item"
|
||||
:class="{ 'existing-highlight': isExistingAddress(address) }"
|
||||
>
|
||||
<div class="address-header">
|
||||
<div class="address-title">
|
||||
<i class="pi pi-home" style="font-size: 0.9rem; color: var(--primary-color);"></i>
|
||||
<h4>Address {{ index + 1 }}</h4>
|
||||
</div>
|
||||
<Button
|
||||
v-if="addresses.length > 1"
|
||||
@click="removeAddress(index)"
|
||||
size="small"
|
||||
severity="danger"
|
||||
icon="pi pi-trash"
|
||||
class="remove-btn"
|
||||
/>
|
||||
</div>
|
||||
<div class="address-fields">
|
||||
<div class="form-field full-width">
|
||||
<label :for="`modal-addr-line1-${index}`">
|
||||
<i class="pi pi-map" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Address Line 1 <span class="required">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
:id="`modal-addr-line1-${index}`"
|
||||
v-model="address.addressLine1"
|
||||
:disabled="isSubmitting"
|
||||
placeholder="Street address"
|
||||
class="w-full"
|
||||
@input="formatAddressLine(index, 'addressLine1', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field full-width">
|
||||
<label :for="`modal-addr-line2-${index}`"><i class="pi pi-map" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Address Line 2</label>
|
||||
<InputText
|
||||
:id="`modal-addr-line2-${index}`"
|
||||
v-model="address.addressLine2"
|
||||
:disabled="isSubmitting"
|
||||
placeholder="Apt, suite, unit, etc."
|
||||
class="w-full"
|
||||
@input="formatAddressLine(index, 'addressLine2', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-field">
|
||||
<label :for="`modal-zip-${index}`">
|
||||
<i class="pi pi-hashtag" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Zip Code <span class="required">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
:id="`modal-zip-${index}`"
|
||||
v-model="address.pincode"
|
||||
:disabled="isSubmitting"
|
||||
@input="handleZipcodeInput(index, $event)"
|
||||
maxlength="5"
|
||||
placeholder="12345"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label :for="`modal-city-${index}`">
|
||||
<i class="pi pi-building" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>City <span class="required">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
:id="`modal-city-${index}`"
|
||||
v-model="address.city"
|
||||
:disabled="isSubmitting || address.zipcodeLookupDisabled"
|
||||
placeholder="City"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label :for="`modal-state-${index}`">
|
||||
<i class="pi pi-flag" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>State <span class="required">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
:id="`modal-state-${index}`"
|
||||
v-model="address.state"
|
||||
:disabled="isSubmitting || address.zipcodeLookupDisabled"
|
||||
placeholder="State"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-field">
|
||||
<label :for="`modal-contacts-${index}`"><i class="pi pi-users" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Assigned Contacts</label>
|
||||
<MultiSelect
|
||||
:id="`modal-contacts-${index}`"
|
||||
v-model="address.contacts"
|
||||
:options="contactOptions"
|
||||
optionLabel="label"
|
||||
dataKey="value"
|
||||
:disabled="isSubmitting || contactOptions.length === 0"
|
||||
placeholder="Select contacts"
|
||||
class="w-full"
|
||||
display="chip"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label :for="`modal-primary-${index}`"><i class="pi pi-star-fill" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Primary Contact</label>
|
||||
<Select
|
||||
:id="`modal-primary-${index}`"
|
||||
v-model="address.primaryContact"
|
||||
:options="address.contacts || []"
|
||||
optionLabel="label"
|
||||
dataKey="value"
|
||||
:disabled="isSubmitting || !address.contacts || address.contacts.length === 0"
|
||||
placeholder="Select primary contact"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field full-width">
|
||||
<Button label="Add another address" @click="addAddress" :disabled="isSubmitting" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import InputText from "primevue/inputtext";
|
||||
import Select from "primevue/select";
|
||||
import MultiSelect from "primevue/multiselect";
|
||||
import Button from "primevue/button";
|
||||
import Api from "../../api";
|
||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||
|
||||
const props = defineProps({
|
||||
addresses: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
contactOptions: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
existingAddresses: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
const createEmptyAddress = () => ({
|
||||
addressLine1: "",
|
||||
addressLine2: "",
|
||||
pincode: "",
|
||||
city: "",
|
||||
state: "",
|
||||
contacts: [],
|
||||
primaryContact: null,
|
||||
zipcodeLookupDisabled: true,
|
||||
});
|
||||
|
||||
const addAddress = () => {
|
||||
props.addresses.push(createEmptyAddress());
|
||||
};
|
||||
|
||||
const removeAddress = (index) => {
|
||||
if (props.addresses.length > 1) {
|
||||
props.addresses.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const formatAddressLine = (index, field, event) => {
|
||||
const value = event.target.value;
|
||||
if (!value) return;
|
||||
const formatted = value
|
||||
.split(" ")
|
||||
.map((word) => {
|
||||
if (!word) return word;
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
||||
})
|
||||
.join(" ");
|
||||
props.addresses[index][field] = formatted;
|
||||
};
|
||||
|
||||
const handleZipcodeInput = async (index, event) => {
|
||||
const value = event.target.value;
|
||||
props.addresses[index].pincode = value;
|
||||
if (value.length === 5) {
|
||||
try {
|
||||
const zipInfo = await Api.getCityStateByZip(value);
|
||||
if (zipInfo && zipInfo.length > 0) {
|
||||
props.addresses[index].city = zipInfo[0].city;
|
||||
props.addresses[index].state = zipInfo[0].state;
|
||||
props.addresses[index].zipcodeLookupDisabled = false;
|
||||
} else {
|
||||
throw new Error("No data returned");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Zipcode lookup failed:", error);
|
||||
props.addresses[index].zipcodeLookupDisabled = true;
|
||||
props.addresses[index].city = "";
|
||||
props.addresses[index].state = "";
|
||||
notificationStore.addError("Invalid zipcode or lookup failed");
|
||||
}
|
||||
} else {
|
||||
props.addresses[index].zipcodeLookupDisabled = true;
|
||||
props.addresses[index].city = "";
|
||||
props.addresses[index].state = "";
|
||||
}
|
||||
};
|
||||
|
||||
const getFullAddress = (address) => {
|
||||
return `${address.addressLine1 || ""} ${address.addressLine2 || ""} ${address.city || ""} ${address.state || ""} ${address.pincode || ""}`
|
||||
.trim()
|
||||
.replace(/\s+/g, " ");
|
||||
};
|
||||
|
||||
const normalizeAddressString = (s = "") => {
|
||||
return (s || "")
|
||||
.toString()
|
||||
.replace(/,/g, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
const isExistingAddress = (address) => {
|
||||
const fullAddr = getFullAddress(address);
|
||||
const normFull = normalizeAddressString(fullAddr);
|
||||
if (!props.existingAddresses || props.existingAddresses.length === 0) return false;
|
||||
return props.existingAddresses.some((ea) => normalizeAddressString(ea) === normFull);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-section {
|
||||
background: var(--surface-card);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--surface-border);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.form-section:hover {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.address-item {
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background: var(--surface-section);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.address-item:hover {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.address-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.address-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.address-header h4 {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.address-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-field.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: var(--red-500);
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.address-item.existing-highlight {
|
||||
border-color: var(--red-500);
|
||||
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
</style>
|
||||
357
frontend/src/components/clientView/ModalContactForm.vue
Normal file
357
frontend/src/components/clientView/ModalContactForm.vue
Normal file
@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<div class="form-section">
|
||||
<div class="section-header">
|
||||
<i class="pi pi-users" style="color: var(--primary-color); font-size: 1.2rem;"></i>
|
||||
<h3>Add Contacts</h3>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="index"
|
||||
class="contact-item"
|
||||
:class="{ 'existing-highlight': isExistingContact(contact) }"
|
||||
>
|
||||
<div class="contact-header">
|
||||
<div class="contact-title">
|
||||
<i class="pi pi-user" style="font-size: 0.9rem; color: var(--primary-color);"></i>
|
||||
<h4>Contact {{ index + 1 }}</h4>
|
||||
</div>
|
||||
<div class="interactables">
|
||||
<div class="form-field header-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="contact-checkbox"
|
||||
:id="`modal-check-${index}`"
|
||||
v-model="contact.isPrimary"
|
||||
:disabled="isSubmitting"
|
||||
@change="setPrimary(index)"
|
||||
/>
|
||||
<label :for="`modal-check-${index}`">
|
||||
<i class="pi pi-star-fill" style="font-size: 0.7rem; margin-right: 0.25rem;"></i>Primary
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
v-if="contacts.length > 1"
|
||||
@click="removeContact(index)"
|
||||
size="small"
|
||||
severity="danger"
|
||||
icon="pi pi-trash"
|
||||
class="remove-btn"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-rows">
|
||||
<div class="form-row">
|
||||
<div class="form-field">
|
||||
<label :for="`modal-first-name-${index}`">
|
||||
<i class="pi pi-user" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>First Name <span class="required">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
:id="`modal-first-name-${index}`"
|
||||
v-model="contact.firstName"
|
||||
:disabled="isSubmitting"
|
||||
placeholder="First name"
|
||||
class="w-full"
|
||||
@input="formatName(index, 'firstName', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label :for="`modal-last-name-${index}`">
|
||||
<i class="pi pi-user" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Last Name <span class="required">*</span>
|
||||
</label>
|
||||
<InputText
|
||||
:id="`modal-last-name-${index}`"
|
||||
v-model="contact.lastName"
|
||||
:disabled="isSubmitting"
|
||||
placeholder="Last name"
|
||||
class="w-full"
|
||||
@input="formatName(index, 'lastName', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label :for="`modal-contact-role-${index}`"><i class="pi pi-briefcase" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Role</label>
|
||||
<Select
|
||||
:id="`modal-contact-role-${index}`"
|
||||
v-model="contact.contactRole"
|
||||
:options="roleOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
:disabled="isSubmitting"
|
||||
placeholder="Select role"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-field">
|
||||
<label :for="`modal-email-${index}`"><i class="pi pi-envelope" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Email</label>
|
||||
<InputText
|
||||
:id="`modal-email-${index}`"
|
||||
v-model="contact.email"
|
||||
:disabled="isSubmitting"
|
||||
type="email"
|
||||
placeholder="email@example.com"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label :for="`modal-phone-${index}`"><i class="pi pi-phone" style="font-size: 0.75rem; margin-right: 0.25rem;"></i>Phone</label>
|
||||
<InputText
|
||||
:id="`modal-phone-${index}`"
|
||||
v-model="contact.phoneNumber"
|
||||
:disabled="isSubmitting"
|
||||
placeholder="(555) 123-4567"
|
||||
class="w-full"
|
||||
@input="formatPhone(index, $event)"
|
||||
@keydown="handlePhoneKeydown($event, index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field full-width">
|
||||
<Button label="Add another contact" @click="addContact" :disabled="isSubmitting" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import InputText from "primevue/inputtext";
|
||||
import Select from "primevue/select";
|
||||
import Button from "primevue/button";
|
||||
|
||||
const props = defineProps({
|
||||
contacts: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
existingContacts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:contacts"]);
|
||||
|
||||
const roleOptions = ref([
|
||||
{ label: "Owner", value: "Owner" },
|
||||
{ label: "Property Manager", value: "Property Manager" },
|
||||
{ label: "Tenant", value: "Tenant" },
|
||||
{ label: "Builder", value: "Builder" },
|
||||
{ label: "Neighbor", value: "Neighbor" },
|
||||
{ label: "Family Member", value: "Family Member" },
|
||||
{ label: "Realtor", value: "Realtor" },
|
||||
{ label: "Other", value: "Other" },
|
||||
]);
|
||||
|
||||
const addContact = () => {
|
||||
props.contacts.push({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
phoneNumber: "",
|
||||
email: "",
|
||||
contactRole: "",
|
||||
isPrimary: false,
|
||||
});
|
||||
};
|
||||
|
||||
const removeContact = (index) => {
|
||||
if (props.contacts.length > 1) {
|
||||
const wasPrimary = props.contacts[index].isPrimary;
|
||||
props.contacts.splice(index, 1);
|
||||
if (wasPrimary && props.contacts.length > 0) {
|
||||
props.contacts[0].isPrimary = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setPrimary = (index) => {
|
||||
props.contacts.forEach((contact, i) => {
|
||||
contact.isPrimary = i === index;
|
||||
});
|
||||
};
|
||||
|
||||
const formatName = (index, field, event) => {
|
||||
const value = event.target.value;
|
||||
if (!value) return;
|
||||
const formatted = value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
|
||||
props.contacts[index][field] = formatted;
|
||||
};
|
||||
|
||||
const formatPhoneNumber = (value) => {
|
||||
const digits = value.replace(/\D/g, "").slice(0, 10);
|
||||
if (digits.length <= 3) return digits;
|
||||
if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
|
||||
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
||||
};
|
||||
|
||||
const formatPhone = (index, event) => {
|
||||
const value = event.target.value;
|
||||
props.contacts[index].phoneNumber = formatPhoneNumber(value);
|
||||
};
|
||||
|
||||
const handlePhoneKeydown = (event, index) => {
|
||||
const allowedKeys = [
|
||||
"Backspace", "Delete", "Tab", "Escape", "Enter",
|
||||
"ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End",
|
||||
];
|
||||
if (allowedKeys.includes(event.key)) return;
|
||||
if (event.ctrlKey || event.metaKey) return;
|
||||
if (!/\d/.test(event.key)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
const currentDigits = props.contacts[index].phoneNumber.replace(/\D/g, "").length;
|
||||
if (currentDigits >= 10) event.preventDefault();
|
||||
};
|
||||
|
||||
const getFullName = (contact) => {
|
||||
return `${contact.firstName || ""} ${contact.lastName || ""}`.trim();
|
||||
};
|
||||
|
||||
const isExistingContact = (contact) => {
|
||||
const fullName = getFullName(contact);
|
||||
return props.existingContacts.includes(fullName);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-section {
|
||||
background: var(--surface-card);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--surface-border);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.form-section:hover {
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background: var(--surface-section);
|
||||
min-width: 33%;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.contact-item:hover {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.contact-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.contact-header h4 {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.interactables {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.form-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-field.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-weight: 500;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.form-field.header-row {
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.contact-checkbox {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: var(--red-500);
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.contact-item.existing-highlight {
|
||||
border-color: var(--red-500);
|
||||
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user