build meeting notes form in install on migrate
This commit is contained in:
parent
6cd3d138ad
commit
c024e7fd86
12 changed files with 227 additions and 124 deletions
|
|
@ -445,12 +445,16 @@ def upsert_client(data):
|
||||||
address_docs = []
|
address_docs = []
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
is_billing = True if address.get("is_billing_address") else False
|
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)
|
print("#####DEBUG: Creating address with data:", address)
|
||||||
address_doc = AddressService.create_address({
|
address_doc = AddressService.create_address({
|
||||||
"address_title": AddressService.build_address_title(customer_name, address),
|
"address_title": AddressService.build_address_title(customer_name, address),
|
||||||
"address_line1": address.get("address_line1"),
|
"address_line1": address.get("address_line1"),
|
||||||
"address_line2": address.get("address_line2"),
|
"address_line2": address.get("address_line2"),
|
||||||
"address_type": "Billing" if is_billing else "Service",
|
"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"),
|
"city": address.get("city"),
|
||||||
"state": address.get("state"),
|
"state": address.get("state"),
|
||||||
"country": "United States",
|
"country": "United States",
|
||||||
|
|
@ -480,13 +484,12 @@ def upsert_client(data):
|
||||||
"address": address_doc.name
|
"address": address_doc.name
|
||||||
})
|
})
|
||||||
client_doc.save(ignore_permissions=True)
|
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 = []
|
frappe.local.message_log = []
|
||||||
return build_success_response({
|
return build_success_response(client_dict)
|
||||||
"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]
|
|
||||||
})
|
|
||||||
except frappe.ValidationError as ve:
|
except frappe.ValidationError as ve:
|
||||||
return build_error_response(str(ve), 400)
|
return build_error_response(str(ve), 400)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -485,7 +485,7 @@ def upsert_estimate(data):
|
||||||
"letter_head": data.get("company"),
|
"letter_head": data.get("company"),
|
||||||
"custom_project_template": data.get("project_template", None),
|
"custom_project_template": data.get("project_template", None),
|
||||||
"custom_quotation_template": data.get("quotation_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", []):
|
for item in data.get("items", []):
|
||||||
item = json.loads(item) if isinstance(item, str) else item
|
item = json.loads(item) if isinstance(item, str) else item
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,31 @@ def get_service_appointments(companies, filters={}):
|
||||||
filters = json.loads(filters)
|
filters = json.loads(filters)
|
||||||
filters["company"] = ["in", companies]
|
filters["company"] = ["in", companies]
|
||||||
service_appointment_names = frappe.get_all(
|
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,
|
filters=filters,
|
||||||
pluck="name"
|
pluck="name"
|
||||||
)
|
)
|
||||||
|
|
@ -27,9 +51,11 @@ def get_service_appointments(companies, filters={}):
|
||||||
@frappe.whitelist()
|
@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):
|
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."""
|
"""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:
|
try:
|
||||||
updated_service_appointment = ServiceAppointmentService.update_scheduled_dates(
|
updated_service_appointment = ServiceAppointmentService.update_scheduled_dates(
|
||||||
service_appointment_name,
|
service_appointment_name,
|
||||||
|
crew_lead_name,
|
||||||
start_date,
|
start_date,
|
||||||
end_date,
|
end_date,
|
||||||
start_time,
|
start_time,
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ def on_update_after_submit(doc, method):
|
||||||
print("DEBUG: Quotation marked as Won, updating current status.")
|
print("DEBUG: Quotation marked as Won, updating current status.")
|
||||||
if doc.customer_type == "Lead":
|
if doc.customer_type == "Lead":
|
||||||
print("DEBUG: Customer is a Lead, converting to Customer and updating Quotation.")
|
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.actual_customer_name = new_customer.name
|
||||||
doc.customer_type = "Customer"
|
doc.customer_type = "Customer"
|
||||||
new_customer.reload()
|
new_customer.reload()
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ def after_insert(doc, method):
|
||||||
)
|
)
|
||||||
if doc.project_template == "SNW Install":
|
if doc.project_template == "SNW Install":
|
||||||
print("DEBUG: Project template is SNW Install, creating Service Appointment")
|
print("DEBUG: Project template is SNW Install, creating Service Appointment")
|
||||||
# AddressService.update_value(
|
AddressService.update_value(
|
||||||
# doc.job_address,
|
doc.job_address,
|
||||||
# "job_status",
|
"job_status",
|
||||||
# "In Progress"
|
"In Progress"
|
||||||
# )
|
)
|
||||||
try:
|
try:
|
||||||
service_apt = ServiceAppointmentService.create({
|
service_apt = ServiceAppointmentService.create({
|
||||||
"project": doc.name,
|
"project": doc.name,
|
||||||
|
|
@ -50,7 +50,7 @@ def after_insert(doc, method):
|
||||||
)
|
)
|
||||||
if task_names:
|
if task_names:
|
||||||
doc.save(ignore_permissions=True)
|
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:
|
elif not doc.expected_start_date or not doc.expected_end_date:
|
||||||
print("DEBUG: Project missing expected start or end date, marking as unscheduled")
|
print("DEBUG: Project missing expected start or end date, marking as unscheduled")
|
||||||
doc.is_scheduled = 0
|
doc.is_scheduled = 0
|
||||||
|
|
||||||
def after_save(doc, method):
|
|
||||||
print("DEBUG: After Save Triggered for Project:", doc.name)
|
|
||||||
event = TaskService.determine_event(doc)
|
event = TaskService.determine_event(doc)
|
||||||
if event:
|
if event:
|
||||||
TaskService.calculate_and_set_due_dates(
|
TaskService.calculate_and_set_due_dates(
|
||||||
[task.task for task in doc.tasks],
|
[task.task for task in doc.tasks],
|
||||||
event,
|
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":
|
if doc.project_template == "SNW Install":
|
||||||
print("DEBUG: Project template is SNW Install, updating Address Job Status based on Project status")
|
print("DEBUG: Project template is SNW Install, updating Address Job Status based on Project status")
|
||||||
status_mapping = {
|
status_mapping = {
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,28 @@ from custom_ui.services import TaskService
|
||||||
|
|
||||||
def on_update(doc, method):
|
def on_update(doc, method):
|
||||||
print("DEBUG: On Update Triggered for Service Appointment")
|
print("DEBUG: On Update Triggered for Service Appointment")
|
||||||
event = TaskService.determine_event(doc)
|
# event = TaskService.determine_event(doc)
|
||||||
if event:
|
# if event:
|
||||||
tasks = TaskService.get_tasks_by_project(doc.project)
|
# tasks = TaskService.get_tasks_by_project(doc.project)
|
||||||
task_names = [task.name for task in tasks]
|
# 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")
|
# TaskService.calculate_and_set_due_dates(task_names=task_names, event=event, triggering_doctype="Service Address 2")
|
||||||
|
|
||||||
def after_insert(doc, method):
|
def after_insert(doc, method):
|
||||||
print("DEBUG: After Insert Triggered for Service Appointment")
|
print("DEBUG: After Insert Triggered for Service Appointment")
|
||||||
task_names = [task.name for task in TaskService.get_tasks_by_project(doc.project)]
|
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")
|
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())
|
||||||
|
|
@ -23,12 +23,12 @@ def after_insert(doc, method):
|
||||||
doc.customer, "tasks", {"task": doc.name, "project_template": doc.project_template }
|
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)]
|
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):
|
def before_save(doc, method):
|
||||||
print("DEBUG: After Save Triggered for Task:", doc.name)
|
print("DEBUG: Before Save Triggered for Task:", doc.name)
|
||||||
event = TaskService.determine_event(doc)
|
event = TaskService.determine_event(doc)
|
||||||
if event:
|
if event:
|
||||||
task_names = [task.name for task in TaskService.get_tasks_by_project(doc.project)]
|
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())
|
||||||
|
|
||||||
|
|
@ -191,10 +191,16 @@ doc_events = {
|
||||||
},
|
},
|
||||||
"Task": {
|
"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"
|
"after_insert": "custom_ui.events.task.after_insert",
|
||||||
|
"before_save": "custom_ui.events.task.before_save"
|
||||||
},
|
},
|
||||||
"Bid Meeting Note Form": {
|
"Bid Meeting Note Form": {
|
||||||
"after_insert": "custom_ui.events.general.attach_bid_note_form_to_project_template"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ def after_migrate():
|
||||||
# create_project_templates()
|
# create_project_templates()
|
||||||
create_task_types()
|
create_task_types()
|
||||||
# create_tasks()
|
# create_tasks()
|
||||||
# create_bid_meeting_note_form_templates()
|
create_bid_meeting_note_form_templates()
|
||||||
|
|
||||||
# update_address_fields()
|
# update_address_fields()
|
||||||
# build_frontend()
|
# build_frontend()
|
||||||
|
|
@ -1252,23 +1252,27 @@ def create_project_templates():
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
def create_bid_meeting_note_form_templates():
|
def create_bid_meeting_note_form_templates():
|
||||||
"""Create Bid Meeting Note Forms if they do not exist."""
|
"""Create Bid Meeting Note Forms if they do not exist."""
|
||||||
print("\n🔧 Checking for Bid Meeting Note Forms...")
|
print("\n🔧 Checking for Bid Meeting Note Forms...")
|
||||||
|
|
||||||
forms = {
|
forms = {
|
||||||
"Sprinklers Northwest": [
|
"Sprinklers Northwest": [
|
||||||
{
|
{
|
||||||
"name": "SNW Install Bid Meeting Notes",
|
|
||||||
"title": "SNW Install Bid Meeting Notes",
|
"title": "SNW Install Bid Meeting Notes",
|
||||||
"description": "Notes form for SNW Install bid meetings.",
|
"description": "Notes form for SNW Install bid meetings.",
|
||||||
"project_template": "SNW Install",
|
"project_template": "SNW Install",
|
||||||
"fields": [{
|
"fields": [
|
||||||
|
{
|
||||||
"label": "Locate Needed",
|
"label": "Locate Needed",
|
||||||
"type": "Check",
|
"type": "Check",
|
||||||
"required": 1,
|
"required": 1,
|
||||||
"help_text": "Indicate if a locate is needed for this project.",
|
"help_text": "Indicate if a locate is needed for this project.",
|
||||||
"row": 1,
|
"row": 1,
|
||||||
"column": 1
|
"column": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Permit Needed",
|
"label": "Permit Needed",
|
||||||
|
|
@ -1276,7 +1280,7 @@ def create_bid_meeting_note_form_templates():
|
||||||
"required": 1,
|
"required": 1,
|
||||||
"help_text": "Indicate if a permit is needed for this project.",
|
"help_text": "Indicate if a permit is needed for this project.",
|
||||||
"row": 1,
|
"row": 1,
|
||||||
"column": 2
|
"column": 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Back Flow Test Required",
|
"label": "Back Flow Test Required",
|
||||||
|
|
@ -1284,53 +1288,81 @@ def create_bid_meeting_note_form_templates():
|
||||||
"required": 1,
|
"required": 1,
|
||||||
"help_text": "Indicate if a backflow test is required after installation.",
|
"help_text": "Indicate if a backflow test is required after installation.",
|
||||||
"row": 1,
|
"row": 1,
|
||||||
"column": 3
|
"column": 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Machine Access",
|
"label": "Machine Access",
|
||||||
"type": "Check",
|
"type": "Check",
|
||||||
"required": 1,
|
"required": 1,
|
||||||
"row": 2,
|
"row": 2,
|
||||||
"column": 1
|
"column": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Machines",
|
"label": "Machines",
|
||||||
"type": "Multi-Select",
|
"type": "Multi-Select",
|
||||||
"options": "MT, Skip Steer, Excavator-E-50, Link Belt, Tre?, Forks, Auger, Backhoe, Loader, Duzer",
|
"options": "MT, Skip Steer, Excavator-E-50, Link Belt, Tre?, Forks, Auger, Backhoe, Loader, Duzer",
|
||||||
"required": 0,
|
|
||||||
"include_options": 1,
|
"include_options": 1,
|
||||||
"conditional_on_field": "Machine Access",
|
"conditional_on_field": "Machine Access",
|
||||||
"row": 2,
|
"row": 2,
|
||||||
"column": 2
|
"column": 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Materials Required",
|
"label": "Materials Required",
|
||||||
"type": "Check",
|
"type": "Check",
|
||||||
"required": 1,
|
"required": 1,
|
||||||
"row": 3,
|
"row": 3,
|
||||||
"column": 0
|
"column": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Materials",
|
"label": "Materials",
|
||||||
"type": "Multi-Select w/ Quantity",
|
"type": "Multi-Select w/ Quantity",
|
||||||
"required": 0,
|
|
||||||
"doctype_for_select": "Item",
|
"doctype_for_select": "Item",
|
||||||
"conditional_on_field": "Materials Required",
|
|
||||||
"doctype_label_field": "itemName",
|
"doctype_label_field": "itemName",
|
||||||
|
"conditional_on_field": "Materials Required",
|
||||||
"row": 4,
|
"row": 4,
|
||||||
"column": 0
|
"column": 0,
|
||||||
}]
|
},
|
||||||
}]}
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
for company, form_list in forms.items():
|
for company, form_list in forms.items():
|
||||||
for form in form_list:
|
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
|
continue
|
||||||
doc = frappe.get_doc({
|
|
||||||
"doctype": "Bid Meeting Note Form",
|
doc = frappe.new_doc("Bid Meeting Note Form")
|
||||||
"company": company,
|
doc.company = company
|
||||||
"title": form["title"],
|
doc.title = form["title"]
|
||||||
"description": form["description"],
|
doc.description = form.get("description")
|
||||||
"project_template": form["project_template"],
|
doc.project_template = form.get("project_template")
|
||||||
"fields": form["fields"]
|
|
||||||
})
|
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)
|
doc.insert(ignore_permissions=True)
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,6 @@ class ClientService:
|
||||||
print(f"DEBUG: Linked quotation {quotation.get('quotation')} to customer")
|
print(f"DEBUG: Linked quotation {quotation.get('quotation')} to customer")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to link quotation {quotation.get('quotation')}: {str(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:
|
if update_onsite_meetings:
|
||||||
print(f"DEBUG: Updating onsite meetings. Count: {len(lead_doc.get('onsite_meetings', []))}")
|
print(f"DEBUG: Updating onsite meetings. Count: {len(lead_doc.get('onsite_meetings', []))}")
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,18 @@ class ServiceAppointmentService:
|
||||||
service_appointment = frappe.get_doc("Service Address 2", service_appointment_name).as_dict()
|
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["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["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
|
return service_appointment
|
||||||
|
|
||||||
@staticmethod
|
@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."""
|
"""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}")
|
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 = DbService.get_or_throw("Service Address 2", service_appointment_name)
|
||||||
service_appointment.expected_start_date = start_date
|
service_appointment.expected_start_date = start_date
|
||||||
service_appointment.expected_end_date = end_date
|
service_appointment.expected_end_date = end_date
|
||||||
|
service_appointment.foreman = crew_lead_name
|
||||||
if start_time:
|
if start_time:
|
||||||
service_appointment.expected_start_time = start_time
|
service_appointment.expected_start_time = start_time
|
||||||
if end_time:
|
if end_time:
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils.safe_exec import safe_eval
|
from frappe.utils.safe_exec import safe_eval
|
||||||
from datetime import timedelta, datetime, date
|
from datetime import timedelta, datetime, date
|
||||||
|
from frappe.utils import getdate
|
||||||
class TaskService:
|
class TaskService:
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@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."""
|
"""Calculate the due date for a list of tasks based on their expected end dates."""
|
||||||
for task_name in task_names:
|
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
|
@staticmethod
|
||||||
|
|
@ -19,14 +20,14 @@ class TaskService:
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
@staticmethod
|
@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."""
|
"""Determine the triggering configuration for a given task."""
|
||||||
task_type_doc = TaskService.get_task_type_doc(task_name)
|
task_type_doc = TaskService.get_task_type_doc(task_name)
|
||||||
if task_type_doc.no_due_date:
|
if task_type_doc.no_due_date:
|
||||||
print(f"DEBUG: Task {task_name} is marked as no due date, skipping calculation.")
|
print(f"DEBUG: Task {task_name} is marked as no due date, skipping calculation.")
|
||||||
return
|
return
|
||||||
if task_type_doc.triggering_doctype != triggering_doctype:
|
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 {triggering_doctype}, skipping calculation.")
|
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
|
return
|
||||||
if task_type_doc.trigger != event:
|
if task_type_doc.trigger != event:
|
||||||
print(f"DEBUG: Task {task_name} trigger {task_type_doc.trigger} does not match event {event}, skipping calculation.")
|
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}")
|
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(
|
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]:
|
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_direction = task_type_doc.offset_direction
|
||||||
offset_days = task_type_doc.offset_days
|
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":
|
if offset_direction == "Before":
|
||||||
offset_days = -offset_days
|
offset_days = -offset_days
|
||||||
|
|
||||||
base_date_field_value = triggering_doc_dict.get(base_date_field)
|
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):
|
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_due_date = base_date_field_value + timedelta(days=offset_days)
|
||||||
calculated_start_date = None
|
calculated_start_date = None
|
||||||
|
|
@ -91,8 +95,8 @@ class TaskService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def determine_update_required(task_name: str, calculated_due_date: date | None, calculated_start_date: date | None) -> bool:
|
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_due_date = frappe.get_value("Task", task_name, "exp_end_date")
|
||||||
current_start_date = frappe.get_value("Task", task_name, "expected_start_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:
|
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}")
|
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
|
return True
|
||||||
|
|
@ -104,30 +108,31 @@ class TaskService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_triggering_doc_dict(task_name: str, task_type_doc) -> dict | None:
|
def get_triggering_doc_dict(task_name: str, task_type_doc) -> dict | None:
|
||||||
project_name = frappe.get_value("Task", task_name, "project")
|
project_name = frappe.get_value("Task", task_name, "project")
|
||||||
|
print(f"DEBUG: Project name: {project_name}")
|
||||||
dict = None
|
dict = None
|
||||||
if task_type_doc.calculate_from == "Project":
|
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":
|
if task_type_doc.calculate_from == "Service Address 2":
|
||||||
service_name = frappe.get_value("Project", project_name, "service_appointment")
|
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":
|
if task_type_doc.calculate_from == "Task":
|
||||||
project_doc = frappe.get_doc("Project", project_name)
|
project_doc = frappe.get_doc("Project", project_name)
|
||||||
for task in project_doc.tasks:
|
for task in project_doc.tasks:
|
||||||
if task.task_type == task_type_doc.task_type_calculate_from:
|
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}")
|
print(f"DEBUG: Triggering doc dict for Task {task_name}: {dict}")
|
||||||
return dict
|
return dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_task_dates(task_name: str, calculated_due_date: date | None, calculated_start_date: date | None):
|
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 = frappe.get_doc("Task", task_name)
|
||||||
task_doc.expected_end_date = calculated_due_date
|
task_doc.exp_end_date = calculated_due_date
|
||||||
task_doc.expected_start_date = calculated_start_date
|
task_doc.exp_start_date = calculated_start_date
|
||||||
task_doc.save(ignore_permissions=True)
|
task_doc.save(ignore_permissions=True)
|
||||||
print(f"DEBUG: Updated Task {task_name} with new dates - Start: {calculated_start_date}, End: {calculated_due_date}")
|
print(f"DEBUG: Updated Task {task_name} with new dates - Start: {calculated_start_date}, End: {calculated_due_date}")
|
||||||
|
|
||||||
@staticmethod
|
@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."""
|
"""Map a base date configuration to a corresponding field name."""
|
||||||
base_date_field_map = {
|
base_date_field_map = {
|
||||||
"Start": "expected_start_date",
|
"Start": "expected_start_date",
|
||||||
|
|
@ -135,12 +140,29 @@ class TaskService:
|
||||||
"Creation": "creation",
|
"Creation": "creation",
|
||||||
"Completion": "actual_end_date"
|
"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")
|
return base_date_field_map.get(base_date, "expected_end_date")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def determine_event(triggering_doc) -> str | None:
|
def determine_event(triggering_doc) -> str | None:
|
||||||
prev_doc = triggering_doc.get_doc_before_save()
|
print("DEBUG: Current Document:", triggering_doc.as_dict())
|
||||||
if prev_doc.expected_end_date != triggering_doc.expected_end_date or prev_doc.expected_start_date != triggering_doc.expected_start_date:
|
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"
|
return "Scheduled"
|
||||||
elif prev_doc.status != triggering_doc.status and triggering_doc.status == "Completed":
|
elif prev_doc.status != triggering_doc.status and triggering_doc.status == "Completed":
|
||||||
return "Completed"
|
return "Completed"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue