Compare commits

...

26 Commits

Author SHA1 Message Date
rocketdebris
0c900618b8 Disabled a client script breaking things by setting dates in the past. 2026-02-10 19:57:20 -05:00
rocketdebris
b0deab7662 Lots of missing fields/tweaks to custom fields. 2026-02-10 19:56:41 -05:00
rocketdebris
c5e4f3f72a Set billing address to correct field in upsert_estimate. 2026-02-10 19:55:52 -05:00
rocketdebris
860bd69531 Updated ordering of territories to import parents before children. 2026-02-10 19:54:34 -05:00
rocketdebris
0bacb2046a Added missing customer groups to fixtures. 2026-02-10 19:21:49 -05:00
rocketdebris
e7f3796f85 Added Selling Settings to fixtures to fix default Idaho territory bug. 2026-02-10 19:13:29 -05:00
rocketdebris
e2780ea905 Added Territory to fixtures. 2026-02-10 18:21:33 -05:00
rocketdebris
a88afdfe71 Added Payment Term and Payment Terms Templates to fixtures. 2026-02-10 18:18:24 -05:00
rocketdebris
47d36b1582 Revert "Added missing email account for sending quotations to fixtures."
This reverts commit 90b42170e7975f0f7185b45fb83e896269ccd5c5.
2026-02-10 17:53:50 -05:00
rocketdebris
90b42170e7 Added missing email account for sending quotations to fixtures. 2026-02-10 17:51:57 -05:00
rocketdebris
a5b025715d Revert "Added missing Quotation Template doctype to the codebase."
This reverts commit a06c4cf35646851697350447586defd098239b0c.
2026-02-10 15:59:07 -05:00
rocketdebris
a06c4cf356 Added missing Quotation Template doctype to the codebase. 2026-02-10 15:47:02 -05:00
rocketdebris
58a06614f0 Added Letter Head to Fixtures. 2026-02-10 15:40:55 -05:00
rocketdebris
e93281b33d Revert "Added Item Groups to fixtures."
This reverts commit b0a5ddfbed0ee7a3a421738439bb5ff882936b62.
2026-02-10 15:33:19 -05:00
rocketdebris
82e8086436 Item groups to project template. 2026-02-10 15:32:49 -05:00
rocketdebris
b0a5ddfbed Added Item Groups to fixtures. 2026-02-10 15:08:57 -05:00
rocketdebris
f66ceb36d6 Removed unused empty project template 2026-02-10 14:57:02 -05:00
rocketdebris
273e507e77 Bid Meeting Note Form auto-connection to Project Template. 2026-02-10 14:51:47 -05:00
rocketdebris
0e895b0c17 Update service address 2 doctype. 2026-02-10 12:40:50 -05:00
rocketdebris
758e8f2b71 Added missing Bid Meeting Note Form and Item Groups fields on Project Template. 2026-02-10 10:54:47 -05:00
rocketdebris
0c0a5599f0 Added property setter fixtures back. 2026-02-10 10:32:33 -05:00
rocketdebris
a034b15c62 Changed old custom fields names. 2026-02-10 10:08:31 -05:00
rocketdebris
d6f7800f20 Added missing customer name field to Lead. 2026-02-09 16:05:52 -05:00
rocketdebris
205f71e7b9 Added all missing doctypes to the codebase 2026-02-09 15:48:25 -05:00
rocketdebris
120693b62b Added missing Bid Meeting Note Doctype to codebase. 2026-02-09 15:26:12 -05:00
rocketdebris
c5da0a090d Removed demo company 2026-02-09 15:11:12 -05:00
71 changed files with 18425 additions and 114 deletions

View File

@ -28,30 +28,30 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
onsite_meeting_scheduled_status_counts = {
"label": "On-Site Meeting Scheduled",
"not_started": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("custom_onsite_meeting_scheduled", "Completed"))
"not_started": frappe.db.count("Address", filters=get_filters("onsite_meeting_scheduled", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("onsite_meeting_scheduled", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("onsite_meeting_scheduled", "Completed"))
}
estimate_sent_status_counts = {
"label": "Estimate Sent",
"not_started": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("custom_estimate_sent_status", "Completed"))
"not_started": frappe.db.count("Address", filters=get_filters("estimate_sent_status", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("estimate_sent_status", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("estimate_sent_status", "Completed"))
}
job_status_counts = {
"label": "Job Status",
"not_started": frappe.db.count("Address", filters=get_filters("custom_job_status", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("custom_job_status", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("custom_job_status", "Completed"))
"not_started": frappe.db.count("Address", filters=get_filters("job_status", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("job_status", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("job_status", "Completed"))
}
payment_received_status_counts = {
"label": "Payment Received",
"not_started": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("custom_payment_received_status", "Completed"))
"not_started": frappe.db.count("Address", filters=get_filters("payment_received_status", "Not Started")),
"in_progress": frappe.db.count("Address", filters=get_filters("payment_received_status", "In Progress")),
"completed": frappe.db.count("Address", filters=get_filters("payment_received_status", "Completed"))
}
status_dicts = [
@ -135,7 +135,7 @@ def get_client(client_name):
clientData["contacts"].append(linked_doc.as_dict())
elif link["link_doctype"] == "Address":
clientData["addresses"].append(linked_doc.as_dict())
# TODO: Continue getting other linked docs like jobs, invoices, etc.
print("DEBUG: Final client data prepared:", clientData)
return build_success_response(clientData)
@ -143,7 +143,7 @@ def get_client(client_name):
return build_error_response(str(ve), 400)
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_client_v2(client_name):
"""Get detailed information for a specific client including address, customer, and projects."""
@ -159,7 +159,7 @@ def get_client_v2(client_name):
clientData["addresses"] = [AddressService.get_or_throw(link.address) for link in clientData["properties"]]
if clientData["doctype"] == "Lead":
clientData["customer_name"] = customer.custom_customer_name
# TODO: Continue getting other linked docs like jobs, invoices, etc.
print("DEBUG: Final client data prepared:", clientData)
return build_success_response(clientData)
@ -167,9 +167,9 @@ def get_client_v2(client_name):
return build_error_response(str(ve), 400)
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
"""Get paginated client table data with filtering and sorting support."""
@ -189,21 +189,21 @@ def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
if filters.get("company"):
where_clauses.append("c.company = %s")
values.append(filters["company"]["value"])
if filters.get("address"):
where_clauses.append("a.full_address LIKE %s")
values.append(f"%{filters['address']['value']}%")
if filters.get("customer_name"):
where_clauses.append("a.customer_name LIKE %s")
values.append(f"%{filters['customer_name']['value']}%")
where_sql = ""
if where_clauses:
where_sql = "WHERE " + " AND ".join(where_clauses)
offset = (page - 1) * page_size
address_names = frappe.db.sql(f"""
SELECT DISTINCT a.name
FROM `tabAddress` a
@ -213,7 +213,7 @@ def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
LIMIT %s OFFSET %s
""", values + [page_size, offset], as_dict=True)
print("DEBUG: Address names retrieved:", address_names)
count = frappe.db.sql(f"""
SELECT COUNT(DISTINCT a.name) as count
FROM `tabAddress` a
@ -230,18 +230,18 @@ def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
tableRow["customer_name"] = normalize_name(address.customer_name, "-#-")
tableRow["companies"] = ", ".join([link.company for link in address.get("companies", [])])
tableRows.append(tableRow)
table_data = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
return build_success_response(table_data)
except frappe.ValidationError as ve:
return build_error_response(str(ve), 400)
except Exception as e:
print("ERROR in get_clients_table_data_v2:", str(e))
return build_error_response(str(e), 500)
@frappe.whitelist()
@ -354,11 +354,11 @@ def upsert_client(data):
try:
data = json.loads(data)
print("#####DEBUG: Create client data received:", data)
customer_name = data.get("customer_name")
contacts = data.get("contacts", [])
addresses = data.get("addresses", [])
# Check for existing address
client_doc = check_and_get_client_doc(customer_name)
if client_doc:
@ -374,7 +374,7 @@ def upsert_client(data):
return build_error_response("This address already exists. Please use a different address or search for the address to find the associated client.", 400)
# Handle customer creation/update
print("#####DEBUG: Creating new lead.")
customer_type = data.get("customer_type", "Individual")
primary_contact = find_primary_contact_or_throw(contacts)
@ -392,7 +392,7 @@ def upsert_client(data):
lead_data["company_name"] = data.get("customer_name")
client_doc = create_lead(lead_data)
print(f"#####DEBUG: {client_doc.doctype}:", client_doc.as_dict())
#Handle contact creation
contact_docs = []
for contact_data in contacts:
@ -427,7 +427,7 @@ def upsert_client(data):
})
ContactService.link_contact_to_customer(contact_doc, "Lead", client_doc.name)
contact_docs.append(contact_doc)
# Link all contacts to client after creating them
client_doc.reload()
for idx, contact_data in enumerate(contacts):
@ -476,7 +476,7 @@ def upsert_client(data):
primary_contact = contact_docs[address.get("primary_contact)", 0)]
AddressService.set_primary_contact(address_doc.name, primary_contact.name)
address_docs.append(address_doc)
# Link all addresses to client after creating them
client_doc.reload()
for address_doc in address_docs:
@ -487,7 +487,7 @@ def upsert_client(data):
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(client_dict)
except frappe.ValidationError as ve:
@ -535,8 +535,8 @@ def convert_lead_to_customer(lead_name):
lead = frappe.get_doc("Lead", lead_name)
customer = make_customer(lead)
customer.insert(ignore_permissions=True)
def create_lead(lead_data):
lead = frappe.get_doc({
"doctype": "Lead",
@ -551,10 +551,10 @@ def get_customer_or_lead(client_name):
else:
lead_name = frappe.db.get_all("Lead", pluck="name", filters={"lead_name": client_name})[0]
return frappe.get_doc("Lead", lead_name)
def find_primary_contact_or_throw(contacts):
for contact in contacts:
if contact.get("is_primary"):
print("#####DEBUG: Primary contact found:", contact)
return contact
raise ValueError("No primary contact found in contacts list.")
raise ValueError("No primary contact found in contacts list.")

View File

@ -26,7 +26,7 @@ def get_estimate_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
start=(page) * page_size,
order_by=sortings
)
estimates = [frappe.get_doc("Quotation", name).as_dict() for name in estimate_names]
tableRows = []
for estimate in estimates:
@ -41,7 +41,7 @@ def get_estimate_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
tableRows.append(tableRow)
table_data_dict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
return build_success_response(table_data_dict)
@frappe.whitelist()
def get_estimate_table_data(filters={}, sortings=[], page=1, page_size=10):
@ -283,14 +283,14 @@ def send_estimate_email(estimate_name):
attachments=[{"fname": f"{quotation.name}.pdf", "fcontent": pdf}]
)
print(f"DEBUG: Email sent to {email} successfully.")
# Update quotation status
quotation.custom_current_status = "Submitted"
quotation.custom_sent = 1
quotation.save()
quotation.submit()
frappe.db.commit()
updated_quotation = frappe.get_doc("Quotation", estimate_name)
return build_success_response(updated_quotation.as_dict())
except Exception as e:
@ -510,7 +510,7 @@ def upsert_estimate(data):
"company": data.get("company"),
"actual_customer_name": client_doc.name,
"customer_type": address_doc.customer_type,
"customer_address": client_doc.custom_billing_address,
"customer_address": client_doc.primary_address,
"contact_person": data.get("contact_name"),
"letter_head": data.get("company"),
"custom_project_template": data.get("project_template", None),

View File

@ -127,6 +127,12 @@ custom_fields = {
fieldtype="Link",
options="Contact",
insert_after="contacts"
),
dict(
fieldname="custom_customer_name",
label="Customer Name",
fieldtype="Data",
insert_after="last_name"
)
],
"Address": [
@ -376,6 +382,12 @@ custom_fields = {
)
],
"Quotation": [
dict(
fieldname="custom_installation_address",
label="Installation Address",
fieldtype="Link",
options="Address"
),
dict(
fieldname="requires_half_payment",
label="Requires Half Payment",
@ -383,20 +395,12 @@ custom_fields = {
default=0,
insert_after="custom_installation_address"
),
dict(
fieldname="custom_quotation_template",
label="Quotation Template",
fieldtype="Link",
options="Quotation Template",
insert_after="company",
description="The template used for generating this quotation."
),
dict(
fieldname="custom_project_template",
label="Project Template",
fieldtype="Link",
options="Project Template",
insert_after="custom_quotation_template",
insert_after="requires_half_payment",
description="The project template to use when creating a project from this quotation.",
allow_on_submit=1
),
@ -416,13 +420,6 @@ custom_fields = {
options="On-Site Meeting",
insert_after="custom_job_address"
),
dict(
fieldname="from_onsite_meeting",
label="From On-Site Meeting",
fieldtype="Link",
options="On-Site Meeting",
insert_after="custom_job_address"
),
dict(
fieldname="actual_customer_name",
label="Customer",
@ -438,9 +435,39 @@ custom_fields = {
options="Customer\nLead",
insert_after="customer_name",
allow_on_submit=1
)
),
dict(
fieldname="custom_current_status",
label="Status",
fieldtype="Select",
options="Draft\nSubmitted\nEstimate Accepted\nLost\nWon",
insert_after="Date",
allow_on_submit=1
),
dict(
fieldname="custom_sent",
label="Sent",
fieldtype="Check",
insert_after="custom_current_status",
default=0,
allow_on_submit=1
),
dict(
fieldname="custom_followup_needed",
label="Follow-up Needed",
fieldtype="Check",
insert_after="custom_sent",
default=0,
allow_on_submit=1
),
],
"Sales Order": [
dict(
fieldname="custom_installation_address",
label="Installation Address",
fieldtype="Link",
options="Address"
),
dict(
fieldname="requires_half_payment",
label="Requires Half Payment",
@ -465,7 +492,14 @@ custom_fields = {
insert_after="custom_installation_address",
description="The address where the job will be performed.",
allow_on_submit=1
)
),
dict(
fieldname="custom_department_type",
label="Department Type",
fieldtype="Select",
options="\nFencing Install\nWarranty\nSprinkler Service\nLandscape Installation\nLawn Maintenance",
insert_after="delivery_date",
),
],
"Project": [
dict(
@ -552,6 +586,19 @@ custom_fields = {
label="Calendar Color",
fieldtype="Color",
insert_after="company"
),
dict(
fieldname="bid_meeting_note_form",
label="Bid Meeting Note Form",
fieldtype="Link",
options="Bid Meeting Note Form",
insert_after="calendar_color"
),
dict(
fieldname="item_groups",
label="Item Groups",
fieldtype="Data",
insert_after="bid_meeting_note_form"
)
],
"Task": [
@ -562,5 +609,13 @@ custom_fields = {
options="Project Template",
insert_after="project"
)
],
"Territory": [
dict(
fieldname="custom_map",
label="Map",
fieldtype="Attach Image",
insert_after="is_group"
)
]
}

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.675964",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:47:47.764072",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Address Company Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AddressCompanyLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.178750",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"contact"
],
"fields": [
{
"fieldname": "contact",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Contact",
"options": "Contact",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:46:22.292044",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Address Contact Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AddressContactLink(Document):
pass

View File

@ -0,0 +1,43 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.602083",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"onsite_meeting",
"project_template"
],
"fields": [
{
"fieldname": "onsite_meeting",
"fieldtype": "Link",
"in_list_view": 1,
"label": "On-Site Meeting",
"options": "On-Site Meeting",
"reqd": 1
},
{
"fieldname": "project_template",
"fieldtype": "Link",
"label": "Project Template",
"options": "Project Template"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:45:27.206492",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Address On-Site Meeting Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AddressOnSiteMeetingLink(Document):
pass

View File

@ -0,0 +1,43 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.478227",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"project",
"project_template"
],
"fields": [
{
"fieldname": "project",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Project",
"options": "Project",
"reqd": 1
},
{
"fieldname": "project_template",
"fieldtype": "Link",
"label": "Project Template",
"options": "Project Template"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:45:01.988468",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Address Project Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AddressProjectLink(Document):
pass

View File

@ -0,0 +1,43 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.538472",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"quotation",
"project_template"
],
"fields": [
{
"fieldname": "quotation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Quotation",
"options": "Quotation",
"reqd": 1
},
{
"fieldname": "project_template",
"fieldtype": "Link",
"label": "Project Template",
"options": "Project Template"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:45:14.139329",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Address Quotation Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AddressQuotationLink(Document):
pass

View File

@ -0,0 +1,43 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.663857",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"sales_order",
"project_template"
],
"fields": [
{
"fieldname": "sales_order",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Sales Order",
"options": "Sales Order",
"reqd": 1
},
{
"fieldname": "project_template",
"fieldtype": "Link",
"label": "Project Template",
"options": "Project Template"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:45:48.574123",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Address Sales Order Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AddressSalesOrderLink(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2026, Shiloh Code LLC and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Bid Meeting Note", {
// refresh(frm) {
// },
// });

View File

@ -0,0 +1,76 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:26.035639",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"form_template",
"bid_meeting",
"notes",
"fields",
"quantities"
],
"fields": [
{
"fieldname": "form_template",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Form Template",
"options": "Bid Meeting Note Form",
"reqd": 1
},
{
"fieldname": "bid_meeting",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Bid Meeting",
"options": "On-Site Meeting",
"reqd": 1
},
{
"fieldname": "notes",
"fieldtype": "Small Text",
"label": "Notes"
},
{
"fieldname": "fields",
"fieldtype": "Table",
"label": "Fields",
"options": "Bid Meeting Note Field"
},
{
"fieldname": "quantities",
"fieldtype": "Table",
"label": "Quantities",
"options": "Bid Meeting Note Field Quantity"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-02-09 10:22:58.938177",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Bid Meeting Note",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class BidMeetingNote(Document):
pass

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestBidMeetingNote(FrappeTestCase):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.726392",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"address"
],
"fields": [
{
"fieldname": "address",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Address",
"options": "Address",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:45:58.906807",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Contact Address Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class ContactAddressLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.247241",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"onsite_meeting"
],
"fields": [
{
"fieldname": "onsite_meeting",
"fieldtype": "Link",
"in_list_view": 1,
"label": "On-Site Meeting",
"options": "On-Site Meeting",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:46:32.725338",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer On-Site Meeting Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CustomerOnSiteMeetingLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.309059",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"project"
],
"fields": [
{
"fieldname": "project",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Project",
"options": "Project",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:46:43.360282",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Project Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CustomerProjectLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.371266",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"quotation"
],
"fields": [
{
"fieldname": "quotation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Quotation",
"options": "Quotation",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:46:52.392918",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Quotation Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CustomerQuotationLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.433195",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"sales_order"
],
"fields": [
{
"fieldname": "sales_order",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Sales Order",
"options": "Sales Order",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:47:06.349253",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Customer Sales Order Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CustomerSalesOrderLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.492405",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"address"
],
"fields": [
{
"fieldname": "address",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Address",
"options": "Address",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:47:14.644540",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Lead Address Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LeadAddressLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.418372",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:44:26.753244",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Lead Companies Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LeadCompaniesLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.237855",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:44:00.147844",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Lead Company Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LeadCompanyLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.552289",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"contact"
],
"fields": [
{
"fieldname": "contact",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Contact",
"options": "Contact",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:47:26.440348",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Lead Contact Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LeadContactLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:24.789839",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"onsite_meeting"
],
"fields": [
{
"fieldname": "onsite_meeting",
"fieldtype": "Link",
"in_list_view": 1,
"label": "On-Site Meeting",
"options": "On-Site Meeting",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:46:11.939521",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Lead On-Site Meeting Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LeadOnSiteMeetingLink(Document):
pass

View File

@ -0,0 +1,36 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-02-09 03:55:25.613799",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"quotation"
],
"fields": [
{
"fieldname": "quotation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Quotation",
"options": "Quotation",
"reqd": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-09 10:47:35.963947",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Lead Quotation Link",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2026, Shiloh Code LLC and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LeadQuotationLink(Document):
pass

View File

@ -1,7 +1,7 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2026-01-30 07:01:57.571003",
"creation": "2026-02-09 03:55:26.299198",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
@ -19,7 +19,8 @@
"customer",
"company",
"service_address",
"foreman"
"foreman",
"ready_to_schedule"
],
"fields": [
{
@ -113,12 +114,18 @@
"fieldtype": "Link",
"label": "Foreman",
"options": "Employee"
},
{
"default": "1",
"fieldname": "ready_to_schedule",
"fieldtype": "Check",
"label": "Ready To Schedule"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-01-30 07:15:39.410145",
"modified": "2026-02-10 07:36:48.544719",
"modified_by": "Administrator",
"module": "Custom UI",
"name": "Service Address 2",

View File

@ -69,7 +69,7 @@
"docstatus": 0,
"doctype": "Client Script",
"dt": "Quotation",
"enabled": 1,
"enabled": 0,
"modified": "2025-01-08 05:04:26.743210",
"module": null,
"name": "Quotation - Set Same Valid Until Date",

View File

@ -0,0 +1,251 @@
[
{
"accounts": [],
"credit_limits": [],
"customer_group_name": "All Customer Groups",
"default_price_list": null,
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 1,
"modified": "2024-04-03 13:52:56.001588",
"name": "All Customer Groups",
"old_parent": "",
"parent_customer_group": null,
"payment_terms": null
},
{
"accounts": [],
"credit_limits": [],
"customer_group_name": "Non Profit",
"default_price_list": null,
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2024-04-03 13:52:56.071669",
"name": "Non Profit",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": null
},
{
"accounts": [],
"credit_limits": [],
"customer_group_name": "Government",
"default_price_list": null,
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2024-04-03 13:52:56.106475",
"name": "Government",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": null
},
{
"accounts": [
{
"account": "Debtors - NYC",
"advance_account": null,
"company": "Nuco Yard Care",
"parent": "Residential - Lawn Care",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Residential - Lawn Care",
"default_price_list": "Standard Selling",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2025-01-30 14:09:03.788424",
"name": "Residential - Lawn Care",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": "Due on receipt"
},
{
"accounts": [
{
"account": "Debtors - NYC",
"advance_account": null,
"company": "Nuco Yard Care",
"parent": "Commercial - Lawn Care",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Commercial - Lawn Care",
"default_price_list": "Commerical Installation",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2025-01-30 14:09:56.564106",
"name": "Commercial - Lawn Care",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": "Net 10th"
},
{
"accounts": [],
"credit_limits": [],
"customer_group_name": "HOA",
"default_price_list": null,
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2024-12-13 11:06:00.939794",
"name": "HOA",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": null
},
{
"accounts": [
{
"account": "Debtors - S",
"advance_account": null,
"company": "Sprinklers Northwest",
"parent": "Residential - Installation",
"parentfield": "accounts",
"parenttype": "Customer Group"
},
{
"account": "Debtors - LF",
"advance_account": null,
"company": "Lowe Fencing",
"parent": "Residential - Installation",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Residential - Installation",
"default_price_list": "Residential",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2025-01-30 14:07:58.523303",
"name": "Residential - Installation",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": "Half Down"
},
{
"accounts": [
{
"account": "Debtors - S",
"advance_account": null,
"company": "Sprinklers Northwest",
"parent": "Builder",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Builder",
"default_price_list": "Standard Selling",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2024-12-19 12:23:59.682344",
"name": "Builder",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": ""
},
{
"accounts": [
{
"account": "Debtors - S",
"advance_account": null,
"company": "Sprinklers Northwest",
"parent": "Commercial - Irrigation service",
"parentfield": "accounts",
"parenttype": "Customer Group"
},
{
"account": "Debtors - NYC",
"advance_account": null,
"company": "Nuco Yard Care",
"parent": "Commercial - Irrigation service",
"parentfield": "accounts",
"parenttype": "Customer Group"
},
{
"account": "Debtors - LF",
"advance_account": null,
"company": "Lowe Fencing",
"parent": "Commercial - Irrigation service",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Commercial - Irrigation service",
"default_price_list": "Standard Selling",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2025-01-30 14:05:52.955168",
"name": "Commercial - Irrigation service",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": "Due on receipt"
},
{
"accounts": [
{
"account": "Debtors - S",
"advance_account": null,
"company": "Sprinklers Northwest",
"parent": "Commercial - Installation",
"parentfield": "accounts",
"parenttype": "Customer Group"
},
{
"account": "Debtors - LF",
"advance_account": null,
"company": "Lowe Fencing",
"parent": "Commercial - Installation",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Commercial - Installation",
"default_price_list": "Commerical Installation",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2025-01-30 14:06:42.132018",
"name": "Commercial - Installation",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": "Net 10th"
},
{
"accounts": [
{
"account": "Debtors - S",
"advance_account": null,
"company": "Sprinklers Northwest",
"parent": "Residential - Irrigation service",
"parentfield": "accounts",
"parenttype": "Customer Group"
}
],
"credit_limits": [],
"customer_group_name": "Residential - Irrigation service",
"default_price_list": "Residential",
"docstatus": 0,
"doctype": "Customer Group",
"is_group": 0,
"modified": "2025-01-30 14:03:51.259924",
"name": "Residential - Irrigation service",
"old_parent": "All Customer Groups",
"parent_customer_group": "All Customer Groups",
"payment_terms": "Due on receipt"
}
]

View File

@ -0,0 +1,48 @@
[
{
"align": "Right",
"content": "\n\t\t\t<div style=\"text-align: right;\">\n\t\t\t\t<img src=\"/files/SNW logo31d841.jpg\" alt=\"Lowe Fencing\" width=\"200\" style=\"width: 200px;\">\n\t\t\t</div>\n\t\t",
"disabled": 0,
"docstatus": 0,
"doctype": "Letter Head",
"footer": null,
"footer_align": "Left",
"footer_image": null,
"footer_image_height": 0.0,
"footer_image_width": 0.0,
"footer_script": null,
"footer_source": "HTML",
"header_script": null,
"image": "/files/SNW logo31d841.jpg",
"image_height": 67.889908257,
"image_width": 200.0,
"is_default": 1,
"letter_head_name": "Lowe Fencing",
"modified": "2025-01-29 16:54:36.546007",
"name": "Lowe Fencing",
"source": "HTML"
},
{
"align": "Right",
"content": "<div style=\"text-align: right;\">\n<img src=\"/files/sprinklers-northwest.jpg\" alt=\"Sprinklers Northwest\" width=\"100.0\" style=\"width: 100.0px;\">\n</div>",
"disabled": 0,
"docstatus": 0,
"doctype": "Letter Head",
"footer": null,
"footer_align": "Left",
"footer_image": null,
"footer_image_height": 0.0,
"footer_image_width": 0.0,
"footer_script": null,
"footer_source": "HTML",
"header_script": null,
"image": "/files/sprinklers-northwest.jpg",
"image_height": 33.582089552,
"image_width": 100.0,
"is_default": 0,
"letter_head_name": "Sprinklers Northwest",
"modified": "2025-01-27 12:07:23.200816",
"name": "Sprinklers Northwest",
"source": "Image"
}
]

View File

@ -0,0 +1,223 @@
[
{
"credit_days": 0,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 20.0,
"mode_of_payment": "Bank Draft",
"modified": "2025-01-03 09:11:38.199028",
"name": "Deposit for Spring",
"payment_term_name": "Deposit for Spring"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Milestone #1: 30% is due upon project initiation",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"modified": "2024-12-13 13:55:57.588606",
"name": "Milestone Payment",
"payment_term_name": "Milestone Payment"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Milestone Payment 2 is due upon completion of design phase.",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"modified": "2024-12-13 14:01:09.387776",
"name": "Milestone Payment #2",
"payment_term_name": "Milestone Payment #2"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Milestone payment #3 is due upon completion of installation",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"modified": "2024-12-13 14:02:12.045732",
"name": "milestone Payment #3",
"payment_term_name": "milestone Payment #3"
},
{
"credit_days": 30,
"credit_months": 0,
"description": "Payment Terms: Installments\nDue Date: Monthly payments on the [X]th of each month\nInstallment Amount: [Amount Per Installment]\nTotal Amount: [Total Invoice Amount]\n\nDescription: Payment is split into multiple installments over a period of time.",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 0.0,
"mode_of_payment": null,
"modified": "2024-12-13 14:05:47.451623",
"name": "Installments",
"payment_term_name": "Installments"
},
{
"credit_days": 30,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after the end of the invoice month",
"invoice_portion": 95.0,
"mode_of_payment": null,
"modified": "2024-12-13 13:44:21.019236",
"name": "Payment in full, less retention",
"payment_term_name": "Payment in full, less retention"
},
{
"credit_days": 90,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 5.0,
"mode_of_payment": null,
"modified": "2024-12-13 13:44:54.391772",
"name": "Retention Payment",
"payment_term_name": "Retention Payment"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Prepaid services require payment in full prior to the service date.",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"modified": "2024-12-27 12:45:32.841916",
"name": "Prepaid",
"payment_term_name": "Prepaid"
},
{
"credit_days": 30,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"modified": "2024-12-27 14:47:23.733775",
"name": "Net 30",
"payment_term_name": "Net 30"
},
{
"credit_days": -50,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 50.0,
"mode_of_payment": null,
"modified": "2024-11-01 13:15:13.698534",
"name": "50% Commitment Payment",
"payment_term_name": "50% Commitment Payment"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Remaining Balance Due upon completion of project",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 50.0,
"mode_of_payment": "",
"modified": "2024-11-01 13:18:23.029229",
"name": "Final Payment",
"payment_term_name": "Final Payment"
},
{
"credit_days": 10,
"credit_months": 0,
"description": "Payment due in full on the 10th of next month\n",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after the end of the invoice month",
"invoice_portion": 100.0,
"mode_of_payment": "",
"modified": "2024-10-30 12:14:45.411032",
"name": "Net 10th",
"payment_term_name": "Net 10th"
},
{
"credit_days": 1,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"docstatus": 0,
"doctype": "Payment Term",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"modified": "2024-12-16 17:33:50.732656",
"name": "Payment due",
"payment_term_name": "Payment due"
}
]

View File

@ -0,0 +1,348 @@
[
{
"allocate_payment_based_on_payment_terms": 1,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-13 13:49:50.909253",
"name": "Retainage withheld",
"template_name": "Retainage withheld",
"terms": [
{
"credit_days": 30,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after the end of the invoice month",
"invoice_portion": 95.0,
"mode_of_payment": null,
"parent": "Retainage withheld",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Payment in full, less retention"
},
{
"credit_days": 90,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 5.0,
"mode_of_payment": null,
"parent": "Retainage withheld",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Retention Payment"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-13 14:02:40.410369",
"name": "Progress Payments",
"template_name": "Progress Payments",
"terms": [
{
"credit_days": 0,
"credit_months": 0,
"description": "Milestone #1: 30% is due upon project initiation",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"parent": "Progress Payments",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Milestone Payment"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Milestone Payment 2 is due upon completion of design phase.",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"parent": "Progress Payments",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Milestone Payment #2"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Milestone payment #3 is due upon completion of installation",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"parent": "Progress Payments",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "milestone Payment #3"
},
{
"credit_days": 0,
"credit_months": 0,
"description": "Remaining Balance Due upon completion of project",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 10.0,
"mode_of_payment": "",
"parent": "Progress Payments",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Final Payment"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-13 14:07:00.773030",
"name": "Installment Payments",
"template_name": "Installment Payments",
"terms": [
{
"credit_days": 30,
"credit_months": 0,
"description": "Payment Terms: Installments\nDue Date: Monthly payments on the [X]th of each month\nInstallment Amount: [Amount Per Installment]\nTotal Amount: [Total Invoice Amount]\n\nDescription: Payment is split into multiple installments over a period of time.",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"parent": "Installment Payments",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Installments"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2025-01-03 09:12:35.254621",
"name": "20 - 30 - 50",
"template_name": "20 - 30 - 50",
"terms": [
{
"credit_days": 0,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 20.0,
"mode_of_payment": "Bank Draft",
"parent": "20 - 30 - 50",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Deposit for Spring"
},
{
"credit_days": 180,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 30.0,
"mode_of_payment": null,
"parent": "20 - 30 - 50",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "50% Commitment Payment"
},
{
"credit_days": 200,
"credit_months": 0,
"description": "Remaining Balance Due upon completion of project",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 50.0,
"mode_of_payment": "",
"parent": "20 - 30 - 50",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Final Payment"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-27 14:46:06.969722",
"name": "Prepaid",
"template_name": "Prepaid",
"terms": [
{
"credit_days": 0,
"credit_months": 0,
"description": "Prepaid services require payment in full prior to the service date.",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"parent": "Prepaid",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Prepaid"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-11-01 13:20:36.757196",
"name": "Half Down",
"template_name": "Half Down",
"terms": [
{
"credit_days": 1,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 50.0,
"mode_of_payment": null,
"parent": "Half Down",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "50% Commitment Payment"
},
{
"credit_days": 1,
"credit_months": 0,
"description": "Remaining Balance Due upon completion of project",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 50.0,
"mode_of_payment": "",
"parent": "Half Down",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Final Payment"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-27 14:47:41.338972",
"name": "Net 30",
"template_name": "Net 30",
"terms": [
{
"credit_days": 30,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"parent": "Net 30",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Net 30"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-16 17:35:10.724856",
"name": "Net 10th",
"template_name": "Net 10th",
"terms": [
{
"credit_days": 15,
"credit_months": 0,
"description": "Payment due in full on the 10th of next month\n",
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after the end of the invoice month",
"invoice_portion": 100.0,
"mode_of_payment": "",
"parent": "Net 10th",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Net 10th"
}
]
},
{
"allocate_payment_based_on_payment_terms": 0,
"docstatus": 0,
"doctype": "Payment Terms Template",
"modified": "2024-12-27 14:45:32.560693",
"name": "Due on receipt",
"template_name": "Due on receipt",
"terms": [
{
"credit_days": 1,
"credit_months": 0,
"description": null,
"discount": 0.0,
"discount_type": "Percentage",
"discount_validity": 0,
"discount_validity_based_on": "Day(s) after invoice date",
"due_date_based_on": "Day(s) after invoice date",
"invoice_portion": 100.0,
"mode_of_payment": null,
"parent": "Due on receipt",
"parentfield": "terms",
"parenttype": "Payment Terms Template",
"payment_term": "Payment due"
}
]
}
]

View File

@ -5,8 +5,8 @@
"company": "Sprinklers Northwest",
"docstatus": 0,
"doctype": "Project Template",
"item_groups": null,
"modified": "2026-01-29 09:51:46.681553",
"item_groups": "SNW-I, SNW-S, SNW-LS",
"modified": "2026-02-10 10:29:37.435176",
"name": "SNW Install",
"project_type": "External",
"tasks": [
@ -53,25 +53,5 @@
"task": "TASK-2025-00006"
}
]
},
{
"bid_meeting_note_form": null,
"calendar_color": null,
"company": "Sprinklers Northwest",
"docstatus": 0,
"doctype": "Project Template",
"item_groups": null,
"modified": "2026-01-08 10:36:39.245470",
"name": "Other",
"project_type": null,
"tasks": [
{
"parent": "Other",
"parentfield": "tasks",
"parenttype": "Project Template",
"subject": "Primary Job",
"task": "TASK-2025-00004"
}
]
}
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
[
{
"allow_against_multiple_purchase_orders": 1,
"allow_multiple_items": 1,
"allow_negative_rates_for_items": 0,
"allow_sales_order_creation_for_expired_quotation": 1,
"allow_zero_qty_in_quotation": 0,
"allow_zero_qty_in_sales_order": 0,
"blanket_order_allowance": 5.0,
"cust_master_name": "Customer Name",
"customer_group": "Residential - Irrigation service",
"dn_required": "No",
"docstatus": 0,
"doctype": "Selling Settings",
"dont_reserve_sales_order_qty_on_sales_return": 0,
"editable_bundle_item_rates": 1,
"editable_price_list_rate": 1,
"enable_discount_accounting": 0,
"fallback_to_default_price_list": 0,
"hide_tax_id": 0,
"maintain_same_rate_action": "Stop",
"maintain_same_sales_rate": 0,
"modified": "2025-01-30 14:03:19.895633",
"name": "Selling Settings",
"role_to_override_stop_action": null,
"sales_update_frequency": "Each Transaction",
"selling_price_list": "Standard Selling",
"so_required": "No",
"territory": "Idaho",
"validate_selling_price": 1
}
]

File diff suppressed because it is too large Load Diff

View File

@ -232,8 +232,15 @@ fixtures = [
{
"dt": "Project Template"
},
{"dt": "Property Setter"},
{"dt": "Client Script"},
{"dt": "Server Script"},
{"dt": "Letter Head"},
{"dt": "Payment Term"},
{"dt": "Payment Terms Template"},
{"dt": "Territory"},
{"dt": "Selling Settings"},
{"dt": "Customer Group"},
]

View File

@ -48,9 +48,9 @@ def after_migrate():
# create_project_templates()
create_task_types()
# create_tasks()
create_bid_meeting_note_form_templates()
create_accounts()
create_companies()
create_accounts()
create_bid_meeting_note_form_templates()
# init_stripe_accounts()
# update_address_fields()
@ -725,15 +725,23 @@ def create_project_templates():
"""Create default Project Templates if they do not exist."""
print("\n🔧 Checking for default Project Templates...")
templates = {
"snw_templates": [
"Sprinklers Northwest": [
{
"name": "SNW Install",
"project_type": "Service",
"company": "Sprinklers Northwest",
"calendar_color": "#FF5733" # Example color
}
]
}
"calendar_color": "#FF5733", # Example color
"item_groups": "SNW-I, SNW-S, SNW-LS"
}
]
}
for company, template_list in templates.items():
for template in template_list:
if frappe.db.exists("Project Template", template["name"]):
continue
doc = frappe.get_doc(template)
doc.insert(ignore_permissions=True)
def create_bid_meeting_note_form_templates():
@ -811,10 +819,17 @@ def create_bid_meeting_note_form_templates():
for company, form_list in forms.items():
for form in form_list:
# Idempotency check
if frappe.db.exists(
"Bid Meeting Note Form",
{"title": form["title"], "company": company},
):
bid_meeting_filter = {"title": form["title"], "company": company}
# Get the id of the Bid Meeting Note Form if it exists
bid_meeting_notes_id = frappe.db.exists("Bid Meeting Note Form", bid_meeting_filter)
if bid_meeting_notes_id:
# Check if the project template is connected to this Bid Meetings Note Form
template_connection = frappe.db.get_value("Project Template",
form["project_template"], "bid_meeting_note_form")
if not template_connection:
# Connect the Bid Meeting Note Form and Project Template
frappe.db.set_value("Project Template", form["project_template"],
"bid_meeting_note_form", bid_meeting_notes_id)
continue
doc = frappe.new_doc("Bid Meeting Note Form")
@ -847,6 +862,8 @@ def create_bid_meeting_note_form_templates():
)
doc.insert(ignore_permissions=True)
frappe.db.set_value("Project Template", form["project_template"],
"bid_meeting_note_form", doc.name)
def create_companies():
@ -876,17 +893,6 @@ def create_companies():
'chart_of_accounts': 'Standard',
'enable_perpetual_inventory': 1
},
{
'company_name': 'sprinklersnorthwest (Demo)',
'abbr': 'SD',
'default_currency': 'USD',
'country': 'United States',
'is_group': 0,
'parent_company': None,
'create_chart_of_accounts_based_on': 'Standard Template',
'chart_of_accounts': 'Standard',
'enable_perpetual_inventory': 1
},
{
'company_name': 'Lowe Fencing',
'abbr': 'LF',
@ -931,7 +937,7 @@ def create_companies():
doc = frappe.get_doc(data)
doc.insert(ignore_permissions=True)
set_default_accounts(doc)
def set_default_accounts(company_doc):
"""Set default accounts for a company after creation."""
abbr = company_doc.abbr
@ -942,8 +948,8 @@ def set_default_accounts(company_doc):
company_doc.default_expense_account = f"Cost of Goods Sold - {abbr}"
company_doc.cost_center = f"Main - {abbr}"
company_doc.save(ignore_permissions=True)
def create_accounts():