diff --git a/custom_ui/api/db/clients.py b/custom_ui/api/db/clients.py index b306528..c1c6194 100644 --- a/custom_ui/api/db/clients.py +++ b/custom_ui/api/db/clients.py @@ -445,12 +445,16 @@ def upsert_client(data): address_docs = [] for address in addresses: is_billing = True if address.get("is_billing_address") else False + is_service = True if address.get("is_service_address") else False print("#####DEBUG: Creating address with data:", address) address_doc = AddressService.create_address({ "address_title": AddressService.build_address_title(customer_name, address), "address_line1": address.get("address_line1"), "address_line2": address.get("address_line2"), "address_type": "Billing" if is_billing else "Service", + "custom_billing_address": is_billing, + "is_service_address": is_service, + "is_primary_address": is_billing, "city": address.get("city"), "state": address.get("state"), "country": "United States", @@ -480,13 +484,12 @@ def upsert_client(data): "address": address_doc.name }) client_doc.save(ignore_permissions=True) + client_dict = client_doc.as_dict() + client_dict["contacts"] = [contact.as_dict() for contact in contact_docs] + client_dict["addresses"] = [address.as_dict() for address in address_docs] frappe.local.message_log = [] - return build_success_response({ - "customer": client_doc.as_dict(), - "address": [address_doc.as_dict() for address_doc in address_docs], - "contacts": [contact_doc.as_dict() for contact_doc in contact_docs] - }) + return build_success_response(client_dict) except frappe.ValidationError as ve: return build_error_response(str(ve), 400) except Exception as e: diff --git a/custom_ui/api/db/estimates.py b/custom_ui/api/db/estimates.py index 720bc4e..678200b 100644 --- a/custom_ui/api/db/estimates.py +++ b/custom_ui/api/db/estimates.py @@ -485,7 +485,7 @@ def upsert_estimate(data): "letter_head": data.get("company"), "custom_project_template": data.get("project_template", None), "custom_quotation_template": data.get("quotation_template", None), - "from_onsite_meeting": data.get("onsite_meeting", None) + "from_onsite_meeting": data.get("from_onsite_meeting", None) }) for item in data.get("items", []): item = json.loads(item) if isinstance(item, str) else item diff --git a/custom_ui/api/db/service_appointments.py b/custom_ui/api/db/service_appointments.py index fc2d5e7..6cb864c 100644 --- a/custom_ui/api/db/service_appointments.py +++ b/custom_ui/api/db/service_appointments.py @@ -12,7 +12,31 @@ def get_service_appointments(companies, filters={}): filters = json.loads(filters) filters["company"] = ["in", companies] service_appointment_names = frappe.get_all( - "Service Appointment", + "Service Address 2", + filters=filters, + pluck="name" + ) + service_appointments = [ + ServiceAppointmentService.get_full_dict(name) + for name in service_appointment_names + ] + return build_success_response(service_appointments) + except Exception as e: + return build_error_response(str(e), 500) + +@frappe.whitelist() +def get_unscheduled_service_appointments(companies): + """Get unscheduled Service Appointments for given companies.""" + try: + if isinstance(companies, str): + companies = json.loads(companies) + filters = { + "company": ["in", companies], + "expected_start_date": None, + "status": "Open" + } + service_appointment_names = frappe.get_all( + "Service Address 2", filters=filters, pluck="name" ) @@ -27,9 +51,11 @@ def get_service_appointments(companies, filters={}): @frappe.whitelist() def update_service_appointment_scheduled_dates(service_appointment_name: str, start_date, end_date, crew_lead_name, start_time=None, end_time=None): """Update scheduled dates for a Service Appointment.""" + print(f"DEBUG: Updating scheduled dates for Service Appointment {service_appointment_name} to start: {start_date}, end: {end_date}, crew lead: {crew_lead_name}, start time: {start_time}, end time: {end_time}") try: updated_service_appointment = ServiceAppointmentService.update_scheduled_dates( service_appointment_name, + crew_lead_name, start_date, end_date, start_time, diff --git a/custom_ui/events/estimate.py b/custom_ui/events/estimate.py index 83195de..cfae092 100644 --- a/custom_ui/events/estimate.py +++ b/custom_ui/events/estimate.py @@ -58,7 +58,7 @@ def on_update_after_submit(doc, method): print("DEBUG: Quotation marked as Won, updating current status.") if doc.customer_type == "Lead": print("DEBUG: Customer is a Lead, converting to Customer and updating Quotation.") - new_customer = ClientService.convert_lead_to_customer(doc.actual_customer_name, update_quotations=False) + new_customer = ClientService.convert_lead_to_customer(doc.actual_customer_name) doc.actual_customer_name = new_customer.name doc.customer_type = "Customer" new_customer.reload() diff --git a/custom_ui/events/jobs.py b/custom_ui/events/jobs.py index bf81223..378018e 100644 --- a/custom_ui/events/jobs.py +++ b/custom_ui/events/jobs.py @@ -17,11 +17,11 @@ def after_insert(doc, method): ) if doc.project_template == "SNW Install": print("DEBUG: Project template is SNW Install, creating Service Appointment") - # AddressService.update_value( - # doc.job_address, - # "job_status", - # "In Progress" - # ) + AddressService.update_value( + doc.job_address, + "job_status", + "In Progress" + ) try: service_apt = ServiceAppointmentService.create({ "project": doc.name, @@ -50,7 +50,7 @@ def after_insert(doc, method): ) if task_names: doc.save(ignore_permissions=True) - TaskService.calculate_and_set_due_dates(task_names, "Created", "Project") + TaskService.calculate_and_set_due_dates(task_names, "Created", current_triggering_dict=doc.as_dict()) @@ -69,16 +69,16 @@ def before_save(doc, method): elif not doc.expected_start_date or not doc.expected_end_date: print("DEBUG: Project missing expected start or end date, marking as unscheduled") doc.is_scheduled = 0 - -def after_save(doc, method): - print("DEBUG: After Save Triggered for Project:", doc.name) event = TaskService.determine_event(doc) if event: TaskService.calculate_and_set_due_dates( [task.task for task in doc.tasks], event, - "Project" + current_triggering_dict=doc.as_dict() ) + +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 = { diff --git a/custom_ui/events/service_appointment.py b/custom_ui/events/service_appointment.py index ab449ea..d0f04de 100644 --- a/custom_ui/events/service_appointment.py +++ b/custom_ui/events/service_appointment.py @@ -4,14 +4,28 @@ from custom_ui.services import TaskService def on_update(doc, method): print("DEBUG: On Update Triggered for Service Appointment") - event = TaskService.determine_event(doc) - if event: - tasks = TaskService.get_tasks_by_project(doc.project) - task_names = [task.name for task in tasks] - TaskService.calculate_and_set_due_dates(task_names=task_names, event=event, triggering_doctype="Service Address 2") + # event = TaskService.determine_event(doc) + # if event: + # tasks = TaskService.get_tasks_by_project(doc.project) + # task_names = [task.name for task in tasks] + # TaskService.calculate_and_set_due_dates(task_names=task_names, event=event, triggering_doctype="Service Address 2") def after_insert(doc, method): print("DEBUG: After Insert Triggered for Service Appointment") task_names = [task.name for task in TaskService.get_tasks_by_project(doc.project)] - TaskService.calculate_and_set_due_dates(task_names=task_names, event="Created", triggering_doctype="Service Address 2") - \ No newline at end of file + TaskService.calculate_and_set_due_dates(task_names=task_names, event="Created", current_triggering_dict=doc.as_dict()) + +def before_save(doc, method): + print("DEBUG: Before Save Triggered for Service Appointment") + if doc.status == "Open" and doc.expected_start_date: + doc.status = "Scheduled" + elif doc.status == "Scheduled" and not doc.expected_start_date: + doc.status = "Open" + if doc.status == "Scheduled" and doc.actual_start_date: + doc.status = "Started" + elif doc.status != "Completed" and doc.status != "Canceled" and doc.actual_end_date: + doc.status = "Completed" + event = TaskService.determine_event(doc) + if event: + task_names = [task.name for task in TaskService.get_tasks_by_project(doc.project)] + TaskService.calculate_and_set_due_dates(task_names=task_names, event=event, current_triggering_dict=doc.as_dict()) \ No newline at end of file diff --git a/custom_ui/events/task.py b/custom_ui/events/task.py index 5a614dc..a5e24df 100644 --- a/custom_ui/events/task.py +++ b/custom_ui/events/task.py @@ -23,12 +23,12 @@ def after_insert(doc, method): doc.customer, "tasks", {"task": doc.name, "project_template": doc.project_template } ) task_names = [task.name for task in TaskService.get_tasks_by_project(doc.project)] - TaskService.calculate_and_set_due_dates(task_names, "Created", "Task") + TaskService.calculate_and_set_due_dates(task_names, "Created", current_triggering_dict=doc.as_dict()) -def after_save(doc, method): - print("DEBUG: After Save Triggered for Task:", doc.name) +def before_save(doc, method): + print("DEBUG: Before Save Triggered for Task:", doc.name) event = TaskService.determine_event(doc) if event: task_names = [task.name for task in TaskService.get_tasks_by_project(doc.project)] - TaskService.calculate_and_set_due_dates(task_names, event, "Task") + TaskService.calculate_and_set_due_dates(task_names, event, current_triggering_dict=doc.as_dict()) \ No newline at end of file diff --git a/custom_ui/hooks.py b/custom_ui/hooks.py index 9f3c45c..925c302 100644 --- a/custom_ui/hooks.py +++ b/custom_ui/hooks.py @@ -191,10 +191,16 @@ doc_events = { }, "Task": { "before_insert": "custom_ui.events.task.before_insert", - "after_insert": "custom_ui.events.task.after_insert" + "after_insert": "custom_ui.events.task.after_insert", + "before_save": "custom_ui.events.task.before_save" }, "Bid Meeting Note Form": { "after_insert": "custom_ui.events.general.attach_bid_note_form_to_project_template" + }, + "Service Address 2": { + "before_save": "custom_ui.events.service_appointment.before_save", + "after_insert": "custom_ui.events.service_appointment.after_insert", + "on_update": "custom_ui.events.service_appointment.on_update" } } diff --git a/custom_ui/install.py b/custom_ui/install.py index fd40423..f1e2041 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -41,7 +41,7 @@ def after_migrate(): # create_project_templates() create_task_types() # create_tasks() - # create_bid_meeting_note_form_templates() + create_bid_meeting_note_form_templates() # update_address_fields() # build_frontend() @@ -1252,85 +1252,117 @@ def create_project_templates(): ] } +import frappe + + def create_bid_meeting_note_form_templates(): """Create Bid Meeting Note Forms if they do not exist.""" print("\n🔧 Checking for Bid Meeting Note Forms...") + forms = { "Sprinklers Northwest": [ - { - "name": "SNW Install Bid Meeting Notes", + { "title": "SNW Install Bid Meeting Notes", "description": "Notes form for SNW Install bid meetings.", "project_template": "SNW Install", - "fields": [{ - "label": "Locate Needed", - "type": "Check", - "required": 1, - "help_text": "Indicate if a locate is needed for this project.", - "row": 1, - "column": 1 - }, - { - "label": "Permit Needed", - "type": "Check", - "required": 1, - "help_text": "Indicate if a permit is needed for this project.", - "row": 1, - "column": 2 - }, - { - "label": "Back Flow Test Required", - "type": "Check", - "required": 1, - "help_text": "Indicate if a backflow test is required after installation.", - "row": 1, - "column": 3 - }, - { - "label": "Machine Access", - "type": "Check", - "required": 1, - "row": 2, - "column": 1 - }, - { - "label": "Machines", - "type": "Multi-Select", - "options": "MT, Skip Steer, Excavator-E-50, Link Belt, Tre?, Forks, Auger, Backhoe, Loader, Duzer", - "required": 0, - "include_options": 1, - "conditional_on_field": "Machine Access", - "row": 2, - "column": 2 - }, - { - "label": "Materials Required", - "type": "Check", - "required": 1, - "row": 3, - "column": 0 - }, - { - "label": "Materials", - "type": "Multi-Select w/ Quantity", - "required": 0, - "doctype_for_select": "Item", - "conditional_on_field": "Materials Required", - "doctype_label_field": "itemName", - "row": 4, - "column": 0 - }] - }]} + "fields": [ + { + "label": "Locate Needed", + "type": "Check", + "required": 1, + "help_text": "Indicate if a locate is needed for this project.", + "row": 1, + "column": 1, + }, + { + "label": "Permit Needed", + "type": "Check", + "required": 1, + "help_text": "Indicate if a permit is needed for this project.", + "row": 1, + "column": 2, + }, + { + "label": "Back Flow Test Required", + "type": "Check", + "required": 1, + "help_text": "Indicate if a backflow test is required after installation.", + "row": 1, + "column": 3, + }, + { + "label": "Machine Access", + "type": "Check", + "required": 1, + "row": 2, + "column": 1, + }, + { + "label": "Machines", + "type": "Multi-Select", + "options": "MT, Skip Steer, Excavator-E-50, Link Belt, Tre?, Forks, Auger, Backhoe, Loader, Duzer", + "include_options": 1, + "conditional_on_field": "Machine Access", + "row": 2, + "column": 2, + }, + { + "label": "Materials Required", + "type": "Check", + "required": 1, + "row": 3, + "column": 0, + }, + { + "label": "Materials", + "type": "Multi-Select w/ Quantity", + "doctype_for_select": "Item", + "doctype_label_field": "itemName", + "conditional_on_field": "Materials Required", + "row": 4, + "column": 0, + }, + ], + } + ] + } + for company, form_list in forms.items(): for form in form_list: - if frappe.db.exists("Bid Meeting Note Form", form["name"]): + # Idempotency check + if frappe.db.exists( + "Bid Meeting Note Form", + {"title": form["title"], "company": company}, + ): continue - doc = frappe.get_doc({ - "doctype": "Bid Meeting Note Form", - "company": company, - "title": form["title"], - "description": form["description"], - "project_template": form["project_template"], - "fields": form["fields"] - }) + + doc = frappe.new_doc("Bid Meeting Note Form") + doc.company = company + doc.title = form["title"] + doc.description = form.get("description") + doc.project_template = form.get("project_template") + + for idx, field in enumerate(form.get("fields", []), start=1): + doc.append( + "fields", + { + "label": field["label"], + "type": field["type"], + "options": field.get("options"), + "required": field.get("required", 0), + "default_value": field.get("default_value"), + "read_only": field.get("read_only", 0), + "order": field.get("order", 0), + "help_text": field.get("help_text"), + "doctype_for_select": field.get("doctype_for_select"), + "include_options": field.get("include_options", 0), + "conditional_on_field": field.get("conditional_on_field"), + "conditional_on_value": field.get("conditional_on_value"), + "doctype_label_field": field.get("doctype_label_field"), + "row": field.get("row"), + "column": field.get("column"), + "idx": idx, + }, + ) + doc.insert(ignore_permissions=True) diff --git a/custom_ui/services/client_service.py b/custom_ui/services/client_service.py index 3207dd2..099b4d2 100644 --- a/custom_ui/services/client_service.py +++ b/custom_ui/services/client_service.py @@ -122,7 +122,6 @@ class ClientService: print(f"DEBUG: Linked quotation {quotation.get('quotation')} to customer") except Exception as e: print(f"ERROR: Failed to link quotation {quotation.get('quotation')}: {str(e)}") - frappe.log_error(f"Quotation linking error: {str(e)}", "convert_lead_to_customer") if update_onsite_meetings: print(f"DEBUG: Updating onsite meetings. Count: {len(lead_doc.get('onsite_meetings', []))}") diff --git a/custom_ui/services/service_appointment_service.py b/custom_ui/services/service_appointment_service.py index 55e395f..a4d45d0 100644 --- a/custom_ui/services/service_appointment_service.py +++ b/custom_ui/services/service_appointment_service.py @@ -22,17 +22,18 @@ class ServiceAppointmentService: service_appointment = frappe.get_doc("Service Address 2", service_appointment_name).as_dict() service_appointment["service_address"] = AddressService.get_or_throw(service_appointment["service_address"]).as_dict() service_appointment["customer"] = ClientService.get_client_or_throw(service_appointment["customer"]).as_dict() - service_appointment["project"] = DbService.get_doc_or_throw("Project", service_appointment["project"]).as_dict() + service_appointment["project"] = DbService.get_or_throw("Project", service_appointment["project"]).as_dict() return service_appointment @staticmethod - def update_scheduled_dates(service_appointment_name: str, start_date, end_date, start_time=None, end_time=None): + def update_scheduled_dates(service_appointment_name: str, crew_lead_name: str,start_date, end_date, start_time=None, end_time=None): """Update the scheduled start and end dates of a Service Appointment.""" print(f"DEBUG: Updating scheduled dates for Service Appointment {service_appointment_name} to start: {start_date}, end: {end_date}") service_appointment = DbService.get_or_throw("Service Address 2", service_appointment_name) service_appointment.expected_start_date = start_date service_appointment.expected_end_date = end_date + service_appointment.foreman = crew_lead_name if start_time: service_appointment.expected_start_time = start_time if end_time: diff --git a/custom_ui/services/task_service.py b/custom_ui/services/task_service.py index b9c2bfa..f03028b 100644 --- a/custom_ui/services/task_service.py +++ b/custom_ui/services/task_service.py @@ -1,14 +1,15 @@ import frappe from frappe.utils.safe_exec import safe_eval from datetime import timedelta, datetime, date +from frappe.utils import getdate class TaskService: @staticmethod - def calculate_and_set_due_dates(task_names: list[str], event: str, triggering_doctype: str): + def calculate_and_set_due_dates(task_names: list[str], event: str, current_triggering_dict=None): """Calculate the due date for a list of tasks based on their expected end dates.""" for task_name in task_names: - TaskService.check_and_update_task_due_date(task_name, event, triggering_doctype) + TaskService.check_and_update_task_due_date(task_name, event, current_triggering_dict) @staticmethod @@ -19,14 +20,14 @@ class TaskService: return tasks @staticmethod - def check_and_update_task_due_date(task_name: str, event: str, triggering_doctype: str): + def check_and_update_task_due_date(task_name: str, event: str, current_triggering_dict=None): """Determine the triggering configuration for a given task.""" task_type_doc = TaskService.get_task_type_doc(task_name) if task_type_doc.no_due_date: print(f"DEBUG: Task {task_name} is marked as no due date, skipping calculation.") return - if task_type_doc.triggering_doctype != triggering_doctype: - print(f"DEBUG: Task {task_name} triggering doctype {task_type_doc.triggering_doctype} does not match triggering doctype {triggering_doctype}, skipping calculation.") + if task_type_doc.triggering_doctype != current_triggering_dict.get("doctype") and current_triggering_dict: + print(f"DEBUG: Task {task_name} triggering doctype {task_type_doc.triggering_doctype} does not match triggering doctype {current_triggering_dict.get('doctype')}, skipping calculation.") return if task_type_doc.trigger != event: print(f"DEBUG: Task {task_name} trigger {task_type_doc.trigger} does not match event {event}, skipping calculation.") @@ -42,7 +43,7 @@ class TaskService: print(f"DEBUG: Calculating triggering data for Task {task_name} from {calculate_from} on trigger {trigger}") - triggering_doc_dict = TaskService.get_triggering_doc_dict(task_name=task_name, task_type_doc=task_type_doc) + triggering_doc_dict = current_triggering_dict if current_triggering_dict else TaskService.get_triggering_doc_dict(task_name=task_name, task_type_doc=task_type_doc) calculated_due_date, calculated_start_date = TaskService.calculate_dates( @@ -72,15 +73,18 @@ class TaskService: def calculate_dates(task_name: str, triggering_doc_dict: dict, task_type_doc) -> tuple[date | None, date | None]: offset_direction = task_type_doc.offset_direction offset_days = task_type_doc.offset_days - base_date_field = TaskService.map_base_date_to_field(task_type_doc.base_date) - + base_date_field = TaskService.map_base_date_to_field(task_type_doc.base_date, task_type_doc.triggering_doctype) + print(f"DEBUG: base_date_field for Task {task_name} is {base_date_field}") if offset_direction == "Before": offset_days = -offset_days base_date_field_value = triggering_doc_dict.get(base_date_field) + print(f"DEBUG: base_date_field_value for Task {task_name} is {base_date_field_value}") if isinstance(base_date_field_value, datetime): - base_date_field_value = base_date_field_value.date() + base_date_field_value = base_date_field_value + else: + base_date_field_value = getdate(base_date_field_value) calculated_due_date = base_date_field_value + timedelta(days=offset_days) calculated_start_date = None @@ -91,8 +95,8 @@ class TaskService: @staticmethod def determine_update_required(task_name: str, calculated_due_date: date | None, calculated_start_date: date | None) -> bool: - current_due_date = frappe.get_value("Task", task_name, "expected_end_date") - current_start_date = frappe.get_value("Task", task_name, "expected_start_date") + current_due_date = frappe.get_value("Task", task_name, "exp_end_date") + current_start_date = frappe.get_value("Task", task_name, "exp_start_date") if current_due_date != calculated_due_date or current_start_date != calculated_start_date: print(f"DEBUG: Update required for Task {task_name}. Current due date: {current_due_date}, Calculated due date: {calculated_due_date}. Current start date: {current_start_date}, Calculated start date: {calculated_start_date}") return True @@ -104,30 +108,31 @@ class TaskService: @staticmethod def get_triggering_doc_dict(task_name: str, task_type_doc) -> dict | None: project_name = frappe.get_value("Task", task_name, "project") + print(f"DEBUG: Project name: {project_name}") dict = None if task_type_doc.calculate_from == "Project": - dict = frappe.get_doc("Project", project_name).to_dict() + dict = frappe.get_doc("Project", project_name).as_dict() if task_type_doc.calculate_from == "Service Address 2": service_name = frappe.get_value("Project", project_name, "service_appointment") - dict = frappe.get_doc("Service Address 2", service_name).to_dict() + dict = frappe.get_doc("Service Address 2", service_name).as_dict() if task_type_doc.calculate_from == "Task": project_doc = frappe.get_doc("Project", project_name) for task in project_doc.tasks: if task.task_type == task_type_doc.task_type_calculate_from: - dict = frappe.get_doc("Task", task.task).to_dict() + dict = frappe.get_doc("Task", task.task).as_dict() print(f"DEBUG: Triggering doc dict for Task {task_name}: {dict}") return dict @staticmethod def update_task_dates(task_name: str, calculated_due_date: date | None, calculated_start_date: date | None): task_doc = frappe.get_doc("Task", task_name) - task_doc.expected_end_date = calculated_due_date - task_doc.expected_start_date = calculated_start_date + task_doc.exp_end_date = calculated_due_date + task_doc.exp_start_date = calculated_start_date task_doc.save(ignore_permissions=True) print(f"DEBUG: Updated Task {task_name} with new dates - Start: {calculated_start_date}, End: {calculated_due_date}") @staticmethod - def map_base_date_to_field(base_date: str) -> str: + def map_base_date_to_field(base_date: str, triggering_doctype: str) -> str: """Map a base date configuration to a corresponding field name.""" base_date_field_map = { "Start": "expected_start_date", @@ -135,12 +140,29 @@ class TaskService: "Creation": "creation", "Completion": "actual_end_date" } + task_date_field_map = { + "Start": "exp_start_date", + "End": "exp_end_date", + "Creation": "creation", + "Completion": "actual_end_date" + } + if triggering_doctype == "Task": + return task_date_field_map.get(base_date, "exp_end_date") return base_date_field_map.get(base_date, "expected_end_date") @staticmethod - def determine_event(triggering_doc) -> str | None: - prev_doc = triggering_doc.get_doc_before_save() - if prev_doc.expected_end_date != triggering_doc.expected_end_date or prev_doc.expected_start_date != triggering_doc.expected_start_date: + def determine_event(triggering_doc) -> str | None: + print("DEBUG: Current Document:", triggering_doc.as_dict()) + if not frappe.db.exists(triggering_doc.doctype, triggering_doc.name): + print("DEBUG: Document does not exist in database, returning None for event.") + return None + prev_doc = frappe.get_doc(triggering_doc.doctype, triggering_doc.name, as_dict=False, ignore_if_missing=True) + start_date_field = "expected_start_date" if triggering_doc.doctype != "Task" else "exp_start_date" + end_date_field = "expected_end_date" if triggering_doc.doctype != "Task" else "exp_end_date" + print("DEBUG: Previous Document:", prev_doc.as_dict() if prev_doc else "None") + if not prev_doc: + return None + if getattr(prev_doc, end_date_field) != getattr(triggering_doc, end_date_field) or getattr(prev_doc, start_date_field) != getattr(triggering_doc, start_date_field): return "Scheduled" elif prev_doc.status != triggering_doc.status and triggering_doc.status == "Completed": return "Completed"