import frappe, json from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response from custom_ui.services import AddressService, ClientService # =============================================================================== # JOB MANAGEMENT API METHODS # =============================================================================== @frappe.whitelist() def get_job_templates(company=None): """Get list of job (project) templates.""" filters = {} if company: filters["company"] = company try: templates = frappe.get_all("Project Template", fields=["*"], filters=filters) return build_success_response(templates) except Exception as e: return build_error_response(str(e), 500) @frappe.whitelist() def create_job_from_sales_order(sales_order_name): """Create a Job (Project) from a given Sales Order""" try: sales_order = frappe.get_doc("Sales Order", sales_order_name) project_template = frappe.get_doc("Project Template", "SNW Install") new_job = frappe.get_doc({ "doctype": "Project", "custom_address": sales_order.custom_job_address, # "custom_installation_address": sales_order.custom_installation_address, "project_name": sales_order.custom_job_address, "project_template": project_template, "custom_warranty_duration_days": 90, "sales_order": sales_order, "custom_company": sales_order.company }) new_job.insert() return build_success_response(new_job.as_dict()) except Exception as e: return build_error_response(str(e), 500) @frappe.whitelist() def get_job(job_id=""): """Get particular Job from DB""" print("DEBUG: Loading Job from database:", job_id) try: project = frappe.get_doc("Project", job_id) address_doc = AddressService.get_or_throw(project.job_address) project = project.as_dict() project["job_address"] = address_doc project["client"] = ClientService.get_client_or_throw(project.customer) return build_success_response(project) except Exception as e: return build_error_response(str(e), 500) @frappe.whitelist() def get_job_task_table_data(filters={}, sortings={}, page=1, page_size=10): """Get paginated job tasks table data with filtering and sorting support.""" print("DEBUG: raw task options received:", filters, sortings, page, page_size) processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size) print("DEBUG: Processed Filters:", processed_filters) if is_or: count = frappe.db.sql(*get_count_or_filters("Task", filters))[0][0] else: count = frappe.db.count("Task", filters=filters) print(f"DEBUG: Number of tasks returned: {count}") tasks = frappe.db.get_all( "Task", fields=["*"], filters=filters, limit=page_size, start=(page - 1) * page_size, order_by=processed_sortings ) tableRows = [] for task in tasks: tableRow = {} tableRow["id"] = task["name"] tableRow["subject"] = task["subject"] tableRow["address"] = task.get("custom_property", "") tableRow["status"] = task.get("status", "") tableRows.append(tableRow) table_data_dict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size) return build_success_response(table_data_dict) @frappe.whitelist() def get_job_task_list(job_id=""): if job_id: try: tasks = frappe.get_all('Task', filters={"project": job_id}) task_docs = {task_id: frappe.get_doc(task_id) for task_id in tasks} return build_success_response(task_docs) except Exception as e: return build_error_response(str(e), 500) @frappe.whitelist() def get_jobs_table_data(filters={}, sortings=[], page=1, page_size=10): """Get paginated job table data with filtering and sorting support.""" print("DEBUG: Raw job options received:", filters, sortings, page, page_size) processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size) # Handle count with proper OR filter support if is_or: count = frappe.db.sql(*get_count_or_filters("Project", processed_filters))[0][0] else: count = frappe.db.count("Project", filters=processed_filters) projects = frappe.db.get_all( "Project", fields=["*"], filters=processed_filters if not is_or else None, or_filters=processed_filters if is_or else None, limit=page_size, start=page * page_size, order_by=processed_sortings ) tableRows = [] for project in projects: tableRow = {} tableRow["id"] = project["name"] tableRow["name"] = project["name"] tableRow["installation_address"] = project.get("custom_installation_address", "") tableRow["customer"] = project.get("customer", "") tableRow["status"] = project.get("status", "") tableRow["percent_complete"] = project.get("percent_complete", 0) tableRows.append(tableRow) data_table_dict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size) return build_success_response(data_table_dict) @frappe.whitelist() def upsert_job(data): """Create or update a job (project).""" try: if isinstance(data, str): data = json.loads(data) project_id = data.get("id") if not project_id: return {"status": "error", "message": "Project ID is required"} project = frappe.get_doc("Project", project_id) if "scheduledDate" in data: project.expected_start_date = data["scheduledDate"] if "foreman" in data: project.custom_install_crew = data["foreman"] project.save() return {"status": "success", "data": project.as_dict()} except Exception as e: return {"status": "error", "message": str(e)} @frappe.whitelist() def get_install_projects(start_date=None, end_date=None): """Get install projects for the calendar.""" try: filters = {"project_template": "SNW Install"} # 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). 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} 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),