Big update

This commit is contained in:
Casey 2026-01-26 17:20:49 -06:00
parent 124b8775fb
commit b400be3f1a
29 changed files with 31703 additions and 2443 deletions

View file

@ -11,6 +11,7 @@ def get_week_bid_meetings(week_start, week_end, company):
"On-Site Meeting",
fields=["*"],
filters=[
["status", "!=", "Cancelled"],
["start_time", ">=", week_start],
["start_time", "<=", week_end],
["company", "=", company]
@ -169,6 +170,9 @@ def get_bid_meeting(name):
if meeting_dict.get("contact"):
contact_doc = ContactService.get_or_throw(meeting_dict["contact"])
meeting_dict["contact"] = contact_doc.as_dict()
if meeting_dict.get("bid_notes"):
bid_meeting_note_doc = frappe.get_doc("Bid Meeting Note", meeting_dict["bid_notes"])
meeting_dict["bid_notes"] = bid_meeting_note_doc.as_dict()
return build_success_response(meeting_dict)
except frappe.DoesNotExistError:

View file

@ -92,7 +92,7 @@ def get_doc_list(doctype, fields=["*"], filters={}, pluck=None):
fields=fields,
filters=filters,
order_by="creation desc",
pluck=pluck
# pluck=pluck
)
print(f"DEBUG: Retrieved documents for {doctype} with filters {filters}: {docs}")
return build_success_response(docs)

View file

@ -0,0 +1,6 @@
import frappe
@frappe.whitelist(allow_guest=True)
def start_payment(invoice_name: str):
pass

View file

@ -83,5 +83,5 @@ def on_update_after_submit(doc, method):
print("DEBUG: Submitting Sales Order")
# new_sales_order.customer_address = backup
new_sales_order.submit()
frappe.db.commit()
# frappe.db.commit()
print("DEBUG: Sales Order created successfully:", new_sales_order.name)

View file

@ -1,6 +1,7 @@
import frappe
from custom_ui.services import AddressService, ClientService, ServiceAppointmentService, TaskService
from datetime import timedelta
import traceback
def after_insert(doc, method):
print("DEBUG: After Insert Triggered for Project")
@ -15,26 +16,41 @@ def after_insert(doc, method):
doc.customer, "projects", {"project": doc.name, "project_template": doc.project_template}
)
if doc.project_template == "SNW Install":
print("DEBUG: Project template is SNW Install, updating Address status to In Progress")
AddressService.update_value(
doc.job_address,
"job_status",
"In Progress"
)
service_apt = ServiceAppointmentService.create({
"project": doc.name,
"customer": doc.customer,
"address": doc.job_address,
"company": doc.company,
"project_template": doc.project_template
})
frappe.db.set_value("Project", doc.name, "service_appointment", service_apt.name)
print("DEBUG: Project template is SNW Install, creating Service Appointment")
# AddressService.update_value(
# doc.job_address,
# "job_status",
# "In Progress"
# )
try:
service_apt = ServiceAppointmentService.create({
"project": doc.name,
"customer": doc.customer,
"service_address": doc.job_address,
"company": doc.company,
"project_template": doc.project_template
})
doc.service_appointment = service_apt.name
doc.save(ignore_permissions=True)
print("DEBUG: Created Service Appointment:", service_apt.name)
except Exception as e:
print("ERROR: Failed to create Service Appointment for Project:", e)
print(traceback.format_exc())
raise e
task_names = [task.name for task in TaskService.get_tasks_by_project(doc.name)]
for task_name in task_names:
doc.append("tasks", {
"task": task_name
})
TaskService.calculate_and_set_due_dates(task_names, "Created")
AddressService.append_link_v2(
doc.job_address, "tasks", {"task": task_name}
)
ClientService.append_link_v2(
doc.customer, "tasks", {"task": task_name}
)
if task_names:
doc.save(ignore_permissions=True)
TaskService.calculate_and_set_due_dates(task_names, "Created", "Project")
@ -60,7 +76,8 @@ def after_save(doc, method):
if event:
TaskService.calculate_and_set_due_dates(
[task.task for task in doc.tasks],
event
event,
"Project"
)
if doc.project_template == "SNW Install":
print("DEBUG: Project template is SNW Install, updating Address Job Status based on Project status")

View file

@ -8,7 +8,8 @@ def before_insert(doc, method):
# Address.onsite_meetings is a child table with two fields: onsite_meeting (Link) and project_template (Link). Iterate through to see if there is already an SNW Install meeting linked.
for link in address_doc.onsite_meetings:
if link.project_template == "SNW Install":
raise frappe.ValidationError("An On-Site Meeting with project template 'SNW Install' is already linked to this address.")
if frappe.db.get_value("On-Site Meeting", link.onsite_meeting, "status") != "Cancelled":
raise frappe.ValidationError("An On-Site Meeting with project template 'SNW Install' is already linked to this address.")
def after_insert(doc, method):
print("DEBUG: After Insert Triggered for On-Site Meeting")
@ -22,17 +23,19 @@ def after_insert(doc, method):
def before_save(doc, method):
print("DEBUG: Before Save Triggered for On-Site Meeting")
if doc.status != "Scheduled" and doc.start_time and doc.end_time and doc.status != "Completed":
if doc.status != "Scheduled" and doc.start_time and doc.end_time and doc.status != "Completed" and doc.status != "Cancelled":
print("DEBUG: Meeting has start and end time, setting status to Scheduled")
doc.status = "Scheduled"
if doc.project_template == "SNW Install":
print("DEBUG: Project template is SNW Install")
if doc.status == "Completed":
print("DEBUG: Meeting marked as Completed, updating Address status")
current_status = AddressService.get_value(doc.address, "onsite_meeting_scheduled")
if current_status != doc.status:
AddressService.update_value(doc.address, "onsite_meeting_scheduled", "Completed")
AddressService.update_value(doc.address, "onsite_meeting_scheduled", "Completed")
if doc.status == "Cancelled":
print("DEBUG: Meeting marked as Cancelled, updating Address status")
AddressService.update_value(doc.address, "onsite_meeting_scheduled", "Not Started")
def validate_address_link(doc, method):
print("DEBUG: Validating Address link for On-Site Meeting")

View file

@ -2,12 +2,12 @@ import frappe
from custom_ui.services import DbService, AddressService, ClientService
def before_insert(doc, method):
print("DEBUG: before_insert hook triggered for Sales Order:", doc.name)
if doc.custom_project_template == "SNW Install":
print("DEBUG: Sales Order uses SNW Install template, checking for duplicate linked sales orders.")
address_doc = AddressService.get_or_throw(doc.custom_job_address)
if "SNW Install" in [link.project_template for link in address_doc.sales_orders]:
raise frappe.ValidationError("A Sales Order with project template 'SNW Install' is already linked to this address.")
print("DEBUG: before_insert hook triggered for Sales Order")
# if doc.custom_project_template == "SNW Install":
# print("DEBUG: Sales Order uses SNW Install template, checking for duplicate linked sales orders.")
# address_doc = AddressService.get_or_throw(doc.custom_job_address)
# if "SNW Install" in [link.project_template for link in address_doc.sales_orders]:
# raise frappe.ValidationError("A Sales Order with project template 'SNW Install' is already linked to this address.")
def on_submit(doc, method):
print("DEBUG: Info from Sales Order")
@ -33,7 +33,7 @@ def on_submit(doc, method):
})
# attatch the job to the sales_order links
new_job.insert()
frappe.db.commit()
# frappe.db.commit()
except Exception as e:
print("ERROR creating Project from Sales Order:", str(e))
@ -50,40 +50,41 @@ def after_insert(doc, method):
)
def create_sales_invoice_from_sales_order(doc, method):
try:
print("DEBUG: after_submit hook triggered for Sales Order:", doc.name)
invoice_ammount = doc.grand_total / 2 if doc.requires_half_payment else doc.grand_total
items = []
for so_item in doc.items:
# proportionally reduce rate if half-payment
rate = so_item.rate / 2 if doc.requires_half_payment else so_item.rate
qty = so_item.qty # usually full qty, but depends on half-payment rules
items.append({
"item_code": so_item.item_code,
"qty": qty,
"rate": rate,
"income_account": so_item.income_account,
"cost_center": so_item.cost_center,
"so_detail": so_item.name # links item to Sales Order
})
invoice = frappe.get_doc({
"doctype": "Sales Invoice",
"customer": doc.customer,
"company": doc.company,
"posting_date": frappe.utils.nowdate(),
"due_date": frappe.utils.nowdate(), # or calculate from payment terms
"currency": doc.currency,
"update_stock": 0,
"items": items,
"sales_order": doc.name, # link invoice to Sales Order
"ignore_pricing_rule": 1,
"payment_schedule": doc.payment_schedule if not half_payment else [] # optional
})
pass
# try:
# print("DEBUG: after_submit hook triggered for Sales Order:", doc.name)
# invoice_ammount = doc.grand_total / 2 if doc.requires_half_payment else doc.grand_total
# items = []
# for so_item in doc.items:
# # proportionally reduce rate if half-payment
# rate = so_item.rate / 2 if doc.requires_half_payment else so_item.rate
# qty = so_item.qty # usually full qty, but depends on half-payment rules
# items.append({
# "item_code": so_item.item_code,
# "qty": qty,
# "rate": rate,
# "income_account": so_item.income_account,
# "cost_center": so_item.cost_center,
# "so_detail": so_item.name # links item to Sales Order
# })
# invoice = frappe.get_doc({
# "doctype": "Sales Invoice",
# "customer": doc.customer,
# "company": doc.company,
# "posting_date": frappe.utils.nowdate(),
# "due_date": frappe.utils.nowdate(), # or calculate from payment terms
# "currency": doc.currency,
# "update_stock": 0,
# "items": items,
# "sales_order": doc.name, # link invoice to Sales Order
# "ignore_pricing_rule": 1,
# "payment_schedule": doc.payment_schedule if not half_payment else [] # optional
# })
invoice.insert()
invoice.submit()
frappe.db.commit()
return invoice
except Exception as e:
print("ERROR creating Sales Invoice from Sales Order:", str(e))
frappe.log_error(f"Error creating Sales Invoice from Sales Order {doc.name}: {str(e)}", "Sales Order after_submit Error")
# invoice.insert()
# invoice.submit()
# frappe.db.commit()
# return invoice
# except Exception as e:
# print("ERROR creating Sales Invoice from Sales Order:", str(e))
# frappe.log_error(f"Error creating Sales Invoice from Sales Order {doc.name}: {str(e)}", "Sales Order after_submit Error")

View file

@ -8,10 +8,10 @@ def on_update(doc, method):
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)
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")
TaskService.calculate_and_set_due_dates(task_names=task_names, event="Created", triggering_doctype="Service Address 2")

View file

@ -6,6 +6,7 @@ def before_insert(doc, method):
print("DEBUG: Before Insert Triggered for Task")
project_doc = frappe.get_doc("Project", doc.project)
doc.project_template = project_doc.project_template
doc.customer = project_doc.customer
if project_doc.job_address:
doc.custom_property = project_doc.job_address
@ -22,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")
TaskService.calculate_and_set_due_dates(task_names, "Created", "Task")
def after_save(doc, method):
print("DEBUG: After 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)
TaskService.calculate_and_set_due_dates(task_names, event, "Task")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1 @@
[
{
"docstatus": 0,
"doctype": "Email Template",
"modified": "2026-01-24 07:18:15.939258",
"name": "Customer Invoice",
"response": "<div class=\"ql-editor read-mode\"><p>-- Copywriting goes here --</p><p>-- Customized Payment Link goes here --</p><p>-- In the meantime --</p><p>Invoice number: {{ name }}</p><p>Amount: {{ grand_total }}</p><p>https://sprinklersnorthwest.com/product/bill-pay/</p></div>",
"response_html": null,
"subject": "Your Invoice is Ready",
"use_html": 0
}
]
[]

View file

@ -7343,22 +7343,6 @@
"row_name": null,
"value": "1"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2024-12-23 13:33:08.995392",
"module": null,
"name": "Contact-main-naming_rule",
"property": "naming_rule",
"property_type": "Data",
"row_name": null,
"value": "Set by user"
},
{
"default_value": null,
"doc_type": "Contact",
@ -7391,22 +7375,6 @@
"row_name": null,
"value": "creation"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2024-12-23 16:11:50.106128",
"module": null,
"name": "Contact-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "full_name"
},
{
"default_value": null,
"doc_type": "Contact",
@ -12575,22 +12543,6 @@
"row_name": null,
"value": "ISS-.YYYY.-"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2025-11-26 03:43:13.493067",
"module": null,
"name": "Contact-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"sb_01\", \"custom_column_break_g4zvy\", \"first_name\", \"custom_column_break_hpz5b\", \"middle_name\", \"custom_column_break_3pehb\", \"last_name\", \"contact_section\", \"links\", \"phone_nos\", \"email_ids\", \"custom_column_break_nfqbi\", \"is_primary_contact\", \"is_billing_contact\", \"custom_service_address\", \"user\", \"unsubscribed\", \"more_info\", \"custom_column_break_sn9hu\", \"full_name\", \"address\", \"company_name\", \"designation\", \"department\", \"image\", \"sb_00\", \"custom_column_break_kmlkz\", \"email_id\", \"mobile_no\", \"phone\", \"status\", \"gender\", \"salutation\", \"contact_details\", \"cb_00\", \"custom_test_label\", \"google_contacts\", \"google_contacts_id\", \"sync_with_google_contacts\", \"cb00\", \"pulled_from_google_contacts\", \"custom_column_break_ejxjz\"]"
},
{
"default_value": null,
"doc_type": "Contact",
@ -12671,54 +12623,6 @@
"row_name": null,
"value": "[\"naming_series\", \"salutation\", \"first_name\", \"middle_name\", \"last_name\", \"custom_customer_name\", \"column_break_1\", \"lead_name\", \"customer_type\", \"companies\", \"quotations\", \"onsite_meetings\", \"job_title\", \"gender\", \"source\", \"col_break123\", \"lead_owner\", \"status\", \"customer\", \"type\", \"request_type\", \"contact_info_tab\", \"email_id\", \"website\", \"column_break_20\", \"mobile_no\", \"whatsapp_no\", \"column_break_16\", \"phone\", \"phone_ext\", \"organization_section\", \"company_name\", \"no_of_employees\", \"column_break_28\", \"annual_revenue\", \"industry\", \"market_segment\", \"column_break_31\", \"territory\", \"fax\", \"address_section\", \"address_html\", \"column_break_38\", \"city\", \"state\", \"country\", \"column_break2\", \"contact_html\", \"qualification_tab\", \"qualification_status\", \"column_break_64\", \"qualified_by\", \"qualified_on\", \"other_info_tab\", \"campaign_name\", \"company\", \"column_break_22\", \"language\", \"image\", \"title\", \"column_break_50\", \"disabled\", \"unsubscribed\", \"blog_subscriber\", \"activities_tab\", \"open_activities_html\", \"all_activities_section\", \"all_activities_html\", \"notes_tab\", \"notes_html\", \"notes\", \"dashboard_tab\", \"contacts\", \"primary_contact\", \"properties\", \"custom_billing_address\"]"
},
{
"default_value": null,
"doc_type": "Lead",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-19 16:24:58.030843",
"module": null,
"name": "Lead-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{lead_name}-#-{YYYY}-{MM}-{####}"
},
{
"default_value": null,
"doc_type": "Project",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-19 17:52:08.608481",
"module": null,
"name": "Project-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{job_address}-{project_template}-#-PRO-{#####}-{YYYY}"
},
{
"default_value": null,
"doc_type": "Project",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-19 17:52:08.652993",
"module": null,
"name": "Project-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"custom_column_break_k7sgq\", \"custom_installation_address\", \"naming_series\", \"project_name\", \"job_address\", \"status\", \"custom_warranty_duration_days\", \"custom_warranty_expiration_date\", \"custom_warranty_information\", \"project_type\", \"percent_complete_method\", \"percent_complete\", \"column_break_5\", \"project_template\", \"expected_start_date\", \"expected_end_date\", \"custom_completion_date\", \"priority\", \"custom_foreman\", \"custom_hidden_fields\", \"department\", \"is_active\", \"custom_address\", \"custom_section_break_lgkpd\", \"custom_workflow_related_custom_fields__landry\", \"custom_permit_status\", \"custom_utlity_locate_status\", \"custom_crew_scheduling\", \"customer_details\", \"customer\", \"column_break_14\", \"sales_order\", \"users_section\", \"users\", \"copied_from\", \"section_break0\", \"notes\", \"section_break_18\", \"actual_start_date\", \"actual_time\", \"column_break_20\", \"actual_end_date\", \"project_details\", \"estimated_costing\", \"total_costing_amount\", \"total_expense_claim\", \"total_purchase_cost\", \"company\", \"column_break_28\", \"total_sales_amount\", \"total_billable_amount\", \"total_billed_amount\", \"total_consumed_material_cost\", \"cost_center\", \"margin\", \"gross_margin\", \"column_break_37\", \"per_gross_margin\", \"monitor_progress\", \"collect_progress\", \"holiday_list\", \"frequency\", \"from_time\", \"to_time\", \"first_email\", \"second_email\", \"daily_time_to_send\", \"day_to_send\", \"weekly_time_to_send\", \"column_break_45\", \"subject\", \"message\"]"
},
{
"default_value": null,
"doc_type": "Address",
@ -12767,38 +12671,6 @@
"row_name": null,
"value": "[\"address_details\", \"custom_column_break_vqa4d\", \"custom_column_break_jw2ty\", \"custom_installationservice_address\", \"custom_billing_address\", \"is_shipping_address\", \"is_primary_address\", \"custom_is_compnay_address\", \"custom_column_break_ky1zo\", \"custom_estimate_sent_status\", \"custom_onsite_meeting_scheduled\", \"custom_job_status\", \"custom_payment_received_status\", \"custom_section_break_fvgdt\", \"address_title\", \"primary_contact\", \"address_type\", \"address_line1\", \"address_line2\", \"custom_linked_city\", \"custom_subdivision\", \"is_your_company_address\", \"custom_column_break_3mo7x\", \"state\", \"city\", \"pincode\", \"county\", \"country\", \"full_address\", \"latitude\", \"longitude\", \"onsite_meeting_scheduled\", \"estimate_sent_status\", \"job_status\", \"payment_received_status\", \"custom_column_break_rrto0\", \"custom_customer_to_bill\", \"lead_name\", \"customer_type\", \"customer_name\", \"contacts\", \"companies\", \"quotations\", \"onsite_meetings\", \"projects\", \"sales_orders\", \"tasks\", \"custom_contact_name\", \"phone\", \"email_id\", \"fax\", \"tax_category\", \"disabled\", \"custom_section_break_aecpx\", \"column_break0\", \"custom_show_irrigation_district\", \"custom_google_map\", \"custom_latitude\", \"custom_longitude\", \"custom_address_for_coordinates\", \"linked_with\", \"custom_linked_contacts\", \"links\", \"custom_column_break_9cbvb\", \"custom_linked_companies\", \"custom_irrigation\", \"custom_upcoming_services\", \"custom_service_type\", \"custom_service_route\", \"custom_confirmation_status\", \"custom_backflow_test_form_filed\", \"custom_column_break_j79td\", \"custom_technician_assigned\", \"custom_scheduled_date\", \"custom_column_break_sqplk\", \"custom_test_route\", \"custom_tech\", \"custom_column_break_wcs7g\", \"custom_section_break_zruvq\", \"custom_irrigation_district\", \"custom_serial_\", \"custom_makemodel_\", \"custom_column_break_djjw3\", \"custom_backflow_location\", \"custom_shutoff_location\", \"custom_valve_boxes\", \"custom_timer_type_and_location\", \"custom_column_break_slusf\", \"custom_section_break_5d1cf\", \"custom_installed_by_sprinklers_nw\", \"custom_column_break_th7rq\", \"custom_installed_for\", \"custom_install_month\", \"custom_install_year\", \"custom_column_break_4itse\", \"custom_section_break_xfdtv\", \"custom_backflow_test_report\", \"custom_column_break_oxppn\", \"custom_photo_attachment\"]"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-21 03:31:53.933678",
"module": null,
"name": "Address-main-links_order",
"property": "links_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"21ddd8462e\", \"c26b89d0d3\", \"ee207f2316\"]"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-21 03:31:53.991156",
"module": null,
"name": "Address-main-states_order",
"property": "states_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"62m56h85vo\", \"62m5uugrvr\", \"62m57bgpkf\", \"62m5fgrjb0\"]"
},
{
"default_value": null,
"doc_type": "Project Template",
@ -15134,5 +15006,133 @@
"property_type": "Data",
"row_name": null,
"value": "eval:doc.calculate_from == \"Task\""
},
{
"default_value": null,
"doc_type": "Lead",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 01:51:02.536818",
"module": null,
"name": "Lead-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{custom_customer_name}-#-{YYYY}-{MM}-{####}"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:35:09.522811",
"module": null,
"name": "Address-main-links_order",
"property": "links_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"21ddd8462e\", \"c26b89d0d3\", \"ee207f2316\"]"
},
{
"default_value": null,
"doc_type": "Address",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:35:09.598292",
"module": null,
"name": "Address-main-states_order",
"property": "states_order",
"property_type": "Small Text",
"row_name": null,
"value": "[\"62m56h85vo\", \"62m5uugrvr\", \"62m57bgpkf\", \"62m5fgrjb0\"]"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:40:01.394710",
"module": null,
"name": "Contact-main-naming_rule",
"property": "naming_rule",
"property_type": "Data",
"row_name": null,
"value": "Expression"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:40:01.427255",
"module": null,
"name": "Contact-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{full-name}-#-{MM}-{YYYY}-{####}"
},
{
"default_value": null,
"doc_type": "Contact",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 02:40:01.458831",
"module": null,
"name": "Contact-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"sb_01\", \"custom_column_break_g4zvy\", \"first_name\", \"custom_column_break_hpz5b\", \"middle_name\", \"custom_column_break_3pehb\", \"last_name\", \"email\", \"customer_type\", \"customer_name\", \"addresses\", \"contact_section\", \"links\", \"phone_nos\", \"email_ids\", \"custom_column_break_nfqbi\", \"is_primary_contact\", \"is_billing_contact\", \"custom_service_address\", \"user\", \"unsubscribed\", \"more_info\", \"custom_column_break_sn9hu\", \"full_name\", \"address\", \"company_name\", \"designation\", \"role\", \"department\", \"image\", \"sb_00\", \"custom_column_break_kmlkz\", \"email_id\", \"mobile_no\", \"phone\", \"status\", \"gender\", \"salutation\", \"contact_details\", \"cb_00\", \"custom_test_label\", \"google_contacts\", \"google_contacts_id\", \"sync_with_google_contacts\", \"cb00\", \"pulled_from_google_contacts\", \"custom_column_break_ejxjz\"]"
},
{
"default_value": null,
"doc_type": "Project",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 10:42:06.682515",
"module": null,
"name": "Project-main-autoname",
"property": "autoname",
"property_type": "Data",
"row_name": null,
"value": "format:{project_template}-#-PRO-{#####}-{YYYY}"
},
{
"default_value": null,
"doc_type": "Project",
"docstatus": 0,
"doctype": "Property Setter",
"doctype_or_field": "DocType",
"field_name": null,
"is_system_generated": 0,
"modified": "2026-01-26 10:42:06.862234",
"module": null,
"name": "Project-main-field_order",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"custom_column_break_k7sgq\", \"custom_installation_address\", \"naming_series\", \"project_name\", \"job_address\", \"status\", \"custom_warranty_duration_days\", \"custom_warranty_expiration_date\", \"custom_warranty_information\", \"project_type\", \"percent_complete_method\", \"percent_complete\", \"column_break_5\", \"project_template\", \"expected_start_date\", \"expected_start_time\", \"expected_end_date\", \"expected_end_time\", \"is_scheduled\", \"invoice_status\", \"custom_completion_date\", \"priority\", \"custom_foreman\", \"custom_hidden_fields\", \"department\", \"service_appointment\", \"tasks\", \"is_active\", \"custom_address\", \"custom_section_break_lgkpd\", \"custom_workflow_related_custom_fields__landry\", \"custom_permit_status\", \"custom_utlity_locate_status\", \"custom_crew_scheduling\", \"customer_details\", \"customer\", \"column_break_14\", \"sales_order\", \"users_section\", \"users\", \"copied_from\", \"section_break0\", \"notes\", \"section_break_18\", \"actual_start_date\", \"actual_start_time\", \"actual_time\", \"column_break_20\", \"actual_end_date\", \"actual_end_time\", \"project_details\", \"estimated_costing\", \"total_costing_amount\", \"total_expense_claim\", \"total_purchase_cost\", \"company\", \"column_break_28\", \"total_sales_amount\", \"total_billable_amount\", \"total_billed_amount\", \"total_consumed_material_cost\", \"cost_center\", \"margin\", \"gross_margin\", \"column_break_37\", \"per_gross_margin\", \"monitor_progress\", \"collect_progress\", \"holiday_list\", \"frequency\", \"from_time\", \"to_time\", \"first_email\", \"second_email\", \"daily_time_to_send\", \"day_to_send\", \"weekly_time_to_send\", \"column_break_45\", \"subject\", \"message\"]"
}
]

View file

@ -8,7 +8,7 @@ class ServiceAppointmentService:
"""Create a new Service Appointment document."""
print("DEBUG: Creating Service Appointment with data:", data)
service_appointment_doc = frappe.get_doc({
"doctype": "Service Appointment",
"doctype": "Service Address 2",
**data
})
service_appointment_doc.insert()
@ -19,7 +19,7 @@ class ServiceAppointmentService:
def get_full_dict(service_appointment_name: str) -> dict:
"""Retrieve a Service Appointment document as a full dictionary."""
print(f"DEBUG: Retrieving Service Appointment document with name: {service_appointment_name}")
service_appointment = frappe.get_doc("Service Appointment", 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["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()
@ -30,7 +30,7 @@ class ServiceAppointmentService:
def update_scheduled_dates(service_appointment_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 Appointment", 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_end_date = end_date
if start_time:
@ -45,7 +45,7 @@ class ServiceAppointmentService:
def update_field(service_appointment_name: str, updates: list[tuple[str, any]]):
"""Update specific fields of a Service Appointment."""
print(f"DEBUG: Updating fields for Service Appointment {service_appointment_name} with updates: {updates}")
service_appointment = DbService.get_or_throw("Service Appointment", service_appointment_name)
service_appointment = DbService.get_or_throw("Service Address 2", service_appointment_name)
for field, value in updates:
setattr(service_appointment, field, value)
service_appointment.save()

View file

@ -5,10 +5,10 @@ class TaskService:
@staticmethod
def calculate_and_set_due_dates(task_names: list[str], event: str):
def calculate_and_set_due_dates(task_names: list[str], event: str, triggering_doctype: str):
"""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)
TaskService.check_and_update_task_due_date(task_name, event, triggering_doctype)
@staticmethod
@ -19,9 +19,15 @@ class TaskService:
return tasks
@staticmethod
def check_and_update_task_due_date(task_name: str, event: str):
def check_and_update_task_due_date(task_name: str, event: str, triggering_doctype: str):
"""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.")
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.")
return
@ -31,9 +37,9 @@ class TaskService:
if task_type_doc.no_due_date:
print(f"DEBUG: Task {task_name} is marked as no due date, skipping calculation.")
return
calculated_from = task_type_doc.calculated_from
calculate_from = task_type_doc.calculate_from
trigger = task_type_doc.trigger
print(f"DEBUG: Calculating triggering data for Task {task_name} from {calculated_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)
@ -59,7 +65,7 @@ class TaskService:
@staticmethod
def get_task_type_doc(task_name: str):
task_type_name = frappe.get_value("Task", task_name, "task_type")
task_type_name = frappe.get_value("Task", task_name, "type")
return frappe.get_doc("Task Type", task_type_name)
@staticmethod
@ -99,12 +105,12 @@ class TaskService:
def get_triggering_doc_dict(task_name: str, task_type_doc) -> dict | None:
project_name = frappe.get_value("Task", task_name, "project")
dict = None
if task_type_doc.calculated_from == "Project":
if task_type_doc.calculate_from == "Project":
dict = frappe.get_doc("Project", project_name).to_dict()
if task_type_doc.calculated_from == "Service Appointment":
if task_type_doc.calculate_from == "Service Address 2":
service_name = frappe.get_value("Project", project_name, "service_appointment")
dict = frappe.get_doc("Service Appointment", service_name).to_dict()
if task_type_doc.calculated_from == "Task":
dict = frappe.get_doc("Service Address 2", service_name).to_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: