updates for company effects

This commit is contained in:
Casey 2026-01-20 00:58:03 -06:00
parent 98ec082394
commit 7710a7c8fe
22 changed files with 941 additions and 186 deletions

View file

@ -4,7 +4,7 @@ from custom_ui.db_utils import build_error_response, build_success_response, pro
from custom_ui.services import DbService, ClientService, AddressService, ContactService
@frappe.whitelist()
def get_week_bid_meetings(week_start, week_end):
def get_week_bid_meetings(week_start, week_end, company):
"""Get On-Site Meetings scheduled within a specific week."""
try:
meetings = frappe.db.get_all(
@ -12,7 +12,8 @@ def get_week_bid_meetings(week_start, week_end):
fields=["*"],
filters=[
["start_time", ">=", week_start],
["start_time", "<=", week_end]
["start_time", "<=", week_end],
["company", "=", company]
],
order_by="start_time asc"
)
@ -27,7 +28,7 @@ def get_week_bid_meetings(week_start, week_end):
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_bid_meetings(fields=["*"], filters={}):
def get_bid_meetings(fields=["*"], filters={}, company=None):
"""Get paginated On-Site Meetings with filtering and sorting support."""
try:
print("DEBUG: Raw bid meeting options received:", filters)
@ -53,13 +54,13 @@ def get_bid_meetings(fields=["*"], filters={}):
@frappe.whitelist()
def get_unscheduled_bid_meetings():
def get_unscheduled_bid_meetings(company):
"""Get On-Site Meetings that are unscheduled."""
try:
meetings = frappe.db.get_all(
"On-Site Meeting",
fields=["*"],
filters={"status": "Unscheduled"},
filters={"status": "Unscheduled", "company": company},
order_by="creation desc"
)
for meeting in meetings:

View file

@ -1,5 +1,5 @@
import frappe, json
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client, build_address_title
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client, build_address_title, normalize_name
from erpnext.crm.doctype.lead.lead import make_customer
from custom_ui.api.db.addresses import address_exists
from custom_ui.api.db.contacts import check_and_get_contact, create_contact, create_contact_links
@ -167,6 +167,81 @@ def get_client_v2(client_name):
return build_error_response(str(ve), 400)
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
"""Get paginated client table data with filtering and sorting support."""
try:
filters = json.loads(filters) if isinstance(filters, str) else filters
sortings = json.loads(sortings) if isinstance(sortings, str) else sortings
page = int(page)
page_size = int(page_size)
print("DEBUG: Raw client table query received:", {
"filters": filters,
"sortings": sortings,
"page": page,
"page_size": page_size
})
where_clauses = []
values = []
if filters.get("company"):
where_clauses.append("c.company = %s")
values.append(filters["company"]["value"])
if filters.get("address"):
where_clauses.append("a.full_address LIKE %s")
values.append(f"%{filters['address']['value']}%")
if filters.get("customer_name"):
where_clauses.append("a.customer_name LIKE %s")
values.append(f"%{filters['customer_name']['value']}%")
where_sql = ""
if where_clauses:
where_sql = "WHERE " + " AND ".join(where_clauses)
offset = (page - 1) * page_size
address_names = frappe.db.sql(f"""
SELECT DISTINCT a.name
FROM `tabAddress` a
LEFT JOIN `tabAddress Company Link` c ON c.parent = a.name
{where_sql}
ORDER BY a.modified DESC
LIMIT %s OFFSET %s
""", values + [page_size, offset], as_dict=True)
print("DEBUG: Address names retrieved:", address_names)
count = frappe.db.sql(f"""
SELECT COUNT(DISTINCT a.name) as count
FROM `tabAddress` a
LEFT JOIN `tabAddress Company Link` c ON c.parent = a.name
{where_sql}
""", values, as_dict=True)[0]["count"]
tableRows = []
for address_name in address_names:
address = AddressService.get_or_throw(address_name["name"])
tableRow = {}
tableRow["id"] = address.name
tableRow["address"] = address.full_address
tableRow["client_type"] = address.customer_type
tableRow["customer_name"] = normalize_name(address.customer_name, "-#-")
tableRow["companies"] = ", ".join([link.company for link in address.get("companies", [])])
tableRows.append(tableRow)
table_data = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
return build_success_response(table_data)
except frappe.ValidationError as ve:
return build_error_response(str(ve), 400)
except Exception as e:
print("ERROR in get_clients_table_data_v2:", str(e))
return build_error_response(str(e), 500)
@frappe.whitelist()

View file

@ -50,6 +50,8 @@ def get_job(job_id=""):
project = project.as_dict()
project["job_address"] = address_doc
project["client"] = ClientService.get_client_or_throw(project.customer)
task_names = frappe.get_all("Task", filters={"project": job_id})
project["tasks"] = [frappe.get_doc("Task", task_name).as_dict() for task_name in task_names]
return build_success_response(project)
except Exception as e:
return build_error_response(str(e), 500)
@ -167,55 +169,32 @@ def upsert_job(data):
return {"status": "error", "message": str(e)}
@frappe.whitelist()
def get_install_projects(start_date=None, end_date=None):
def get_projects_for_calendar(date, company=None, project_templates=[]):
"""Get install projects for the calendar."""
# Parse project_templates if it's a JSON string
if isinstance(project_templates, str):
project_templates = json.loads(project_templates)
# put some emojis in the print to make it stand out
print("📅📅📅", date, "company:", company, "project_templates:", project_templates, "type:", type(project_templates))
try:
filters = {"project_template": "SNW Install"}
filters = {"company": company} if company else {}
if project_templates and len(project_templates) > 0:
filters["project_template"] = ["in", project_templates]
unscheduled_filters = filters.copy()
unscheduled_filters["is_scheduled"] = 0
filters["expected_start_date"] = date
# If date range provided, we could filter, but for now let's fetch all open/active ones
# or maybe filter by status not Closed/Completed if we want active ones.
# The user said "unscheduled" are those with status "Open" (and no date).
# extend filters into unscheduled_filters
projects = frappe.get_all("Project", fields=["*"], filters=filters)
calendar_events = []
for project in projects:
# Determine status
status = "unscheduled"
if project.get("expected_start_date"):
status = "scheduled"
# Map to calendar event format
event = {
"id": project.name,
"serviceType": project.project_name, # Using project name as service type/title
"customer": project.customer,
"status": status,
"scheduledDate": project.expected_start_date,
"scheduledTime": "08:00", # Default time if not specified? Project doesn't seem to have time.
"duration": 480, # Default 8 hours?
"foreman": project.get("custom_install_crew"),
"crew": [], # Need to map crew
"estimatedCost": project.estimated_costing,
"priority": project.priority.lower() if project.priority else "medium",
"notes": project.notes,
"address": project.custom_installation_address
}
calendar_events.append(event)
return {"status": "success", "data": calendar_events}
project_names = frappe.get_all("Project", pluck="name", filters=filters)
print("DEBUG: Found scheduled project names:", project_names)
unscheduled_project_names = frappe.get_all("Project", pluck="name", filters=unscheduled_filters)
print("DEBUG: Found unscheduled project names:", unscheduled_project_names)
projects = [frappe.get_doc("Project", name).as_dict() for name in project_names]
unscheduled_projects = [frappe.get_doc("Project", name).as_dict() for name in unscheduled_project_names]
return build_success_response({ "projects": projects, "unscheduled_projects": unscheduled_projects })
except Exception as e:
return {"status": "error", "message": str(e)}
@frappe.whitelist()
def get_project_templates_for_company(company_name):
"""Get project templates for a specific company."""
try:
templates = frappe.get_all(
"Project Template",
fields=["*"],
filters={"company": company_name}
)
return build_success_response(templates)
except Exception as e:
return build_error_response(str(e), 500),
return build_error_response(str(e), 500)

View file

@ -229,3 +229,7 @@ def build_history_entries(comments, versions):
# Sort by timestamp descending
history.sort(key=lambda x: x["timestamp"], reverse=True)
return history
def normalize_name(name: str, split_target: str = "_") -> str:
"""Normalize a name by splitting off anything after and including the split_target."""
return name.split(split_target)[0] if split_target in name else name

View file

@ -25,4 +25,27 @@ def after_insert(doc, method):
def before_insert(doc, method):
# This is where we will add logic to set tasks and other properties of a job based on it's project_template
pass
pass
def before_save(doc, method):
print("DEBUG: Before Save Triggered for Project:", doc.name)
if doc.expected_start_date and doc.expected_end_date:
doc.is_scheduled = 1
else:
doc.is_scheduled = 0
def after_save(doc, method):
print("DEBUG: After Save Triggered for Project:", doc.name)
if doc.project_template == "SNW Install":
print("DEBUG: Project template is SNW Install, updating Address Job Status based on Project status")
status_mapping = {
"Open": "In Progress",
"Completed": "Completed",
"Closed": "Completed"
}
new_status = status_mapping.get(doc.status, "In Progress")
AddressService.update_value(
doc.job_address,
"job_status",
new_status
)

View file

@ -1,7 +1,24 @@
import frappe
from custom_ui.services import AddressService, ClientService
def before_insert(doc, method):
"""Set values before inserting a Task."""
print("DEBUG: Before Insert Triggered for Task")
project_doc = frappe.get_doc("Project", doc.project)
if project_doc.custom_installation_address:
doc.custom_property = project_doc.custom_installation_address
doc.project_template = project_doc.project_template
if project_doc.job_address:
doc.custom_property = project_doc.job_address
def after_insert(doc, method):
print("DEBUG: After Insert Triggered for Task")
print("DEBUG: Linking Task to Customer and Address")
AddressService.append_link_v2(
doc.custom_property, "tasks", {"task": doc.name, "project_template": doc.project_template }
)
AddressService.append_link_v2(
doc.custom_property, "links", {"link_doctype": "Task", "link_name": doc.name}
)
ClientService.append_link_v2(
doc.customer, "tasks", {"task": doc.name, "project_template": doc.project_template }
)

View file

@ -44,7 +44,7 @@
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldname": "task",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
@ -58,7 +58,7 @@
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Company",
"label": "Task",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
@ -68,8 +68,8 @@
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Company",
"parent": "Lead Company Link",
"options": "Task",
"parent": "Customer Task Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
@ -93,6 +93,70 @@
"trigger": null,
"unique": 0,
"width": null
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "project_template",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Project Template",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Project Template",
"parent": "Customer Task Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
}
],
"force_re_route_to_default_view": 0,
@ -114,10 +178,228 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
"modified": "2026-01-15 00:40:39.197431",
"module": "Custom",
"name": "Lead Company Link",
"migration_hash": null,
"modified": "2026-01-19 18:10:16.782664",
"module": "Custom UI",
"name": "Customer Task Link",
"naming_rule": "",
"nsm_parent_field": null,
"parent_node": null,
"permissions": [],
"print_outline": null,
"protect_attached_files": 0,
"queue_in_background": 0,
"quick_entry": 0,
"read_only": 0,
"recipient_account_field": null,
"restrict_to_domain": null,
"route": null,
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"search_fields": null,
"sender_field": null,
"sender_name_field": null,
"show_name_in_global_search": 0,
"show_preview_popup": 0,
"show_title_field_in_link": 0,
"smallicon": null,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"subject": null,
"subject_field": null,
"tag_fields": null,
"timeline_field": null,
"title_field": null,
"track_changes": 0,
"track_seen": 0,
"track_views": 0,
"translated_doctype": 0,
"website_search_field": null
},
{
"_assign": null,
"_comments": null,
"_last_update": null,
"_liked_by": null,
"_user_tags": null,
"actions": [],
"allow_auto_repeat": 0,
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"app": null,
"autoname": null,
"beta": 0,
"color": null,
"colour": null,
"custom": 1,
"default_email_template": null,
"default_print_format": null,
"default_view": null,
"description": null,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"documentation": null,
"editable_grid": 1,
"email_append_to": 0,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "task",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Task",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Task",
"parent": "Address Task Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"documentation_url": null,
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "project_template",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"is_virtual": 0,
"label": "Project Template",
"length": 0,
"link_filters": null,
"make_attachment_public": 0,
"mandatory_depends_on": null,
"max_height": null,
"no_copy": 0,
"non_negative": 0,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Project Template",
"parent": "Address Task Link",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"show_dashboard": 0,
"show_on_timeline": 0,
"show_preview_popup": 0,
"sort_options": 0,
"translatable": 0,
"trigger": null,
"unique": 0,
"width": null
}
],
"force_re_route_to_default_view": 0,
"grid_page_length": 50,
"has_web_view": 0,
"hide_toolbar": 0,
"icon": null,
"image_field": null,
"in_create": 0,
"index_web_pages_for_search": 1,
"is_calendar_and_gantt": 0,
"is_published_field": null,
"is_submittable": 0,
"is_tree": 0,
"is_virtual": 0,
"issingle": 0,
"istable": 1,
"links": [],
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": null,
"modified": "2026-01-19 18:10:02.359022",
"module": "Custom UI",
"name": "Address Task Link",
"naming_rule": "",
"nsm_parent_field": null,
"parent_node": null,
@ -268,8 +550,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.521684",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.097017",
"module": "Custom",
"name": "Lead Companies Link",
"naming_rule": "",
@ -486,8 +768,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.576521",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.150584",
"module": "Custom",
"name": "Address Project Link",
"naming_rule": "",
@ -704,8 +986,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.628136",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.203403",
"module": "Custom",
"name": "Address Quotation Link",
"naming_rule": "",
@ -922,8 +1204,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.681893",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.255846",
"module": "Custom",
"name": "Address On-Site Meeting Link",
"naming_rule": "",
@ -1140,8 +1422,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.737017",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.309600",
"module": "Custom",
"name": "Address Sales Order Link",
"naming_rule": "",
@ -1294,8 +1576,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.787995",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.361237",
"module": "Custom",
"name": "Contact Address Link",
"naming_rule": "",
@ -1448,8 +1730,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.837721",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.412683",
"module": "Custom",
"name": "Lead On-Site Meeting Link",
"naming_rule": "",
@ -2050,8 +2332,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.906370",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.483924",
"module": "Selling",
"name": "Quotation Template",
"naming_rule": "",
@ -2548,8 +2830,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:34.977831",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.558008",
"module": "Selling",
"name": "Quotation Template Item",
"naming_rule": "",
@ -2702,8 +2984,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.031029",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.609372",
"module": "Custom UI",
"name": "Customer Company Link",
"naming_rule": "",
@ -2856,8 +3138,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.084461",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.660893",
"module": "Custom UI",
"name": "Customer Address Link",
"naming_rule": "",
@ -3010,8 +3292,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.135851",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.712878",
"module": "Custom UI",
"name": "Customer Contact Link",
"naming_rule": "",
@ -3164,8 +3446,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.184768",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.765849",
"module": "Custom",
"name": "Address Contact Link",
"naming_rule": "",
@ -3318,8 +3600,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.236428",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.818352",
"module": "Custom",
"name": "Customer On-Site Meeting Link",
"naming_rule": "",
@ -3472,8 +3754,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.287145",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.870984",
"module": "Custom",
"name": "Customer Project Link",
"naming_rule": "",
@ -3626,8 +3908,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.338967",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.922695",
"module": "Custom",
"name": "Customer Quotation Link",
"naming_rule": "",
@ -3780,8 +4062,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.388711",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:17.975165",
"module": "Custom",
"name": "Customer Sales Order Link",
"naming_rule": "",
@ -3934,8 +4216,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.441876",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:18.027046",
"module": "Custom",
"name": "Lead Address Link",
"naming_rule": "",
@ -4088,8 +4370,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.492936",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:18.078476",
"module": "Custom",
"name": "Lead Contact Link",
"naming_rule": "",
@ -4242,8 +4524,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.545465",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:18.170095",
"module": "Custom",
"name": "Lead Quotation Link",
"naming_rule": "",
@ -4396,8 +4678,8 @@
"make_attachments_public": 0,
"max_attachments": 0,
"menu_index": null,
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
"modified": "2026-01-16 04:11:35.604415",
"migration_hash": "0df0ede31f640435231ba887f40eca91",
"modified": "2026-01-19 20:52:18.238066",
"module": "Custom",
"name": "Address Company Link",
"naming_rule": "",

View file

@ -0,0 +1 @@
[]

View file

@ -181,10 +181,13 @@ doc_events = {
},
"Project": {
"before_insert": "custom_ui.events.jobs.before_insert",
"after_insert": "custom_ui.events.jobs.after_insert"
"after_insert": "custom_ui.events.jobs.after_insert",
"before_save": "custom_ui.events.jobs.before_save",
"on_update": "custom_ui.events.jobs.after_save"
},
"Task": {
"before_insert": "custom_ui.events.task.before_insert"
"before_insert": "custom_ui.events.task.before_insert",
"after_insert": "custom_ui.events.task.after_insert"
}
}
@ -216,6 +219,8 @@ fixtures = [
"Address Contact Link",
"Address Company Link",
"Contact Address Link",
"Address Task Link",
"Customer Task Link"
]]
]
},
@ -249,7 +254,15 @@ fixtures = [
["dt", "=", "Project Template"],
["fieldname", "=", "company"]
]
},
{
"dt": "Property Setter",
"filters": [
["doc_type", "=", "Lead"],
["doc_type", "=", "Project"]
]
}
]

View file

@ -30,7 +30,7 @@ def after_migrate():
frappe.clear_cache(doctype=doctype)
frappe.reload_doctype(doctype)
update_address_fields()
# update_address_fields()
# build_frontend()
@ -146,6 +146,13 @@ def add_custom_fields():
fieldtype="Link",
options="Contact",
insert_after="contacts"
),
dict(
fieldname="tasks",
label="Tasks",
fieldtype="Table",
options="Customer Task Link",
insert_after="projects"
)
],
"Lead": [
@ -327,6 +334,13 @@ def add_custom_fields():
fieldtype="Table",
options="Address Company Link",
insert_after="contacts"
),
dict(
fieldname="tasks",
label="Tasks",
fieldtype="Table",
options="Address Task Link",
insert_after="projects"
)
],
"Contact": [
@ -539,6 +553,37 @@ def add_custom_fields():
options="Customer",
insert_after="job_address",
description="The customer for whom the project is being executed."
),
dict(
fieldname="expected_start_time",
label="Expected Start Time",
fieldtype="Time",
insert_after="expected_start_date"
),
dict(
fieldname="expected_end_time",
label="Expected End Time",
fieldtype="Time",
insert_after="expected_end_date"
),
dict(
fieldname="actual_start_time",
label="Actual Start Time",
fieldtype="Time",
insert_after="actual_start_date"
),
dict(
fieldname="actual_end_time",
label="Actual End Time",
fieldtype="Time",
insert_after="actual_end_date"
),
dict(
fieldname="is_scheduled",
label="Is Scheduled",
fieldtype="Check",
default=0,
insert_after="expected_end_time"
)
],
"Project Template": [
@ -550,6 +595,15 @@ def add_custom_fields():
insert_after="project_type",
description="The company associated with this project template."
)
],
"Task": [
dict(
fieldname="project_template",
label="Project Template",
fieldtype="Link",
options="Project Template",
insert_after="project"
)
]
}