Added Invoice Status Select to Project DocType.
This commit is contained in:
parent
2fb82917b4
commit
91e4d47d48
1 changed files with 70 additions and 62 deletions
|
|
@ -31,7 +31,7 @@ def after_migrate():
|
|||
for doctype in doctypes_to_refresh:
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
frappe.reload_doctype(doctype)
|
||||
|
||||
|
||||
check_and_create_holiday_list()
|
||||
|
||||
# update_address_fields()
|
||||
|
|
@ -56,7 +56,7 @@ def build_frontend():
|
|||
frappe.log_error(message="No frontend directory found for custom_ui", title="Frontend Build Skipped")
|
||||
print(f"⚠️ Frontend directory does not exist. Skipping build. Path was {frontend_path}")
|
||||
return
|
||||
|
||||
|
||||
dist_path = os.path.join(app_root, "custom_ui", "public", "dist")
|
||||
should_build = True
|
||||
|
||||
|
|
@ -69,10 +69,10 @@ def build_frontend():
|
|||
except subprocess.CalledProcessError as e:
|
||||
frappe.log_error(message=str(e), title="Frontend Build Failed")
|
||||
print(f"\n❌ Frontend build failed: {e}\n")
|
||||
|
||||
|
||||
def add_custom_fields():
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
print("\n🔧 Adding custom fields to doctypes...")
|
||||
|
||||
try:
|
||||
|
|
@ -85,7 +85,7 @@ def add_custom_fields():
|
|||
print(" ✅ Added 'Service' to Address address_type options.")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Failed to update Address address_type: {e}")
|
||||
|
||||
|
||||
custom_fields = {
|
||||
"Customer": [
|
||||
dict(
|
||||
|
|
@ -267,7 +267,7 @@ def add_custom_fields():
|
|||
insert_after="full_address"
|
||||
),
|
||||
dict(
|
||||
fieldname="longitude",
|
||||
fieldname="longitude",
|
||||
label="Longitude",
|
||||
fieldtype="Float",
|
||||
precision=8,
|
||||
|
|
@ -277,7 +277,7 @@ def add_custom_fields():
|
|||
fieldname="onsite_meeting_scheduled",
|
||||
label="On-Site Meeting Scheduled",
|
||||
fieldtype="Select",
|
||||
options="Not Started\nIn Progress\nCompleted",
|
||||
options="Not Started\nIn Progress\nCompleted",
|
||||
default="Not Started",
|
||||
insert_after="longitude"
|
||||
),
|
||||
|
|
@ -291,7 +291,7 @@ def add_custom_fields():
|
|||
),
|
||||
dict(
|
||||
fieldname="job_status",
|
||||
label="Job Status",
|
||||
label="Job Status",
|
||||
fieldtype="Select",
|
||||
options="Not Started\nIn Progress\nCompleted",
|
||||
default="Not Started",
|
||||
|
|
@ -302,7 +302,7 @@ def add_custom_fields():
|
|||
label="Payment Received Status",
|
||||
fieldtype="Select",
|
||||
options="Not Started\nIn Progress\nCompleted",
|
||||
default="Not Started",
|
||||
default="Not Started",
|
||||
insert_after="job_status"
|
||||
),
|
||||
dict(
|
||||
|
|
@ -588,7 +588,15 @@ def add_custom_fields():
|
|||
fieldtype="Check",
|
||||
default=0,
|
||||
insert_after="expected_end_time"
|
||||
)
|
||||
),
|
||||
dict(
|
||||
fieldname="invoice_status",
|
||||
label="Invoice Status",
|
||||
fieldtype="Select",
|
||||
default="Not Ready",
|
||||
options="Not Ready\nReady to Invoice\nInvoice Created\nInvoice Sent",
|
||||
insert_after="is_scheduled"
|
||||
),
|
||||
],
|
||||
"Project Template": [
|
||||
dict(
|
||||
|
|
@ -616,15 +624,15 @@ def add_custom_fields():
|
|||
)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
print("🔧 Custom fields to check per doctype:")
|
||||
for key, value in custom_fields.items():
|
||||
print(f" • {key}: {len(value)} fields")
|
||||
print(f" Total fields to check: {sum(len(v) for v in custom_fields.values())}\n")
|
||||
|
||||
|
||||
missing_fields = []
|
||||
fields_to_update = []
|
||||
|
||||
|
||||
for doctype, field_options in custom_fields.items():
|
||||
meta = frappe.get_meta(doctype)
|
||||
for field_spec in field_options:
|
||||
|
|
@ -637,7 +645,7 @@ def add_custom_fields():
|
|||
if frappe.db.exists("Custom Field", custom_field_name):
|
||||
custom_field_doc = frappe.get_doc("Custom Field", custom_field_name)
|
||||
needs_update = False
|
||||
|
||||
|
||||
# Compare important properties
|
||||
for key, desired_value in field_spec.items():
|
||||
if key == "fieldname":
|
||||
|
|
@ -646,10 +654,10 @@ def add_custom_fields():
|
|||
if current_value != desired_value:
|
||||
needs_update = True
|
||||
break
|
||||
|
||||
|
||||
if needs_update:
|
||||
fields_to_update.append((doctype, fieldname, field_spec))
|
||||
|
||||
|
||||
if missing_fields:
|
||||
print("\n❌ Missing custom fields:")
|
||||
for entry in missing_fields:
|
||||
|
|
@ -658,36 +666,36 @@ def add_custom_fields():
|
|||
missing_field_specs = build_missing_field_specs(custom_fields, missing_fields)
|
||||
create_custom_fields(missing_field_specs)
|
||||
print("✅ Missing custom fields created.")
|
||||
|
||||
|
||||
if fields_to_update:
|
||||
print("\n🔧 Updating custom fields with mismatched specs:")
|
||||
for doctype, fieldname, field_spec in fields_to_update:
|
||||
print(f" • {doctype}: {fieldname}")
|
||||
custom_field_name = f"{doctype}-{fieldname}"
|
||||
custom_field_doc = frappe.get_doc("Custom Field", custom_field_name)
|
||||
|
||||
|
||||
# Update all properties from field_spec
|
||||
for key, value in field_spec.items():
|
||||
if key != "fieldname":
|
||||
setattr(custom_field_doc, key, value)
|
||||
|
||||
|
||||
custom_field_doc.save(ignore_permissions=True)
|
||||
|
||||
|
||||
frappe.db.commit()
|
||||
print("✅ Custom fields updated.")
|
||||
|
||||
|
||||
if not missing_fields and not fields_to_update:
|
||||
print("✅ All custom fields verified.")
|
||||
|
||||
|
||||
|
||||
def update_onsite_meeting_fields():
|
||||
"""Update On-Site Meeting doctype fields to make start_time and end_time optional."""
|
||||
print("\n🔧 Updating On-Site Meeting doctype fields...")
|
||||
|
||||
|
||||
try:
|
||||
# Get the doctype
|
||||
doctype = frappe.get_doc("DocType", "On-Site Meeting")
|
||||
|
||||
|
||||
# Find and update start_time and end_time fields
|
||||
updated_fields = []
|
||||
for field in doctype.fields:
|
||||
|
|
@ -695,20 +703,20 @@ def update_onsite_meeting_fields():
|
|||
if field.reqd == 1:
|
||||
field.reqd = 0
|
||||
updated_fields.append(field.fieldname)
|
||||
|
||||
|
||||
if updated_fields:
|
||||
# Save the doctype
|
||||
doctype.save(ignore_permissions=True)
|
||||
print(f"✅ Updated fields: {', '.join(updated_fields)} (set to not required)")
|
||||
else:
|
||||
print("✅ Fields already configured correctly")
|
||||
|
||||
|
||||
print("🔧 On-Site Meeting field update complete.\n")
|
||||
except Exception as e:
|
||||
print(f"❌ Error updating On-Site Meeting fields: {str(e)}")
|
||||
frappe.log_error(message=str(e), title="On-Site Meeting Field Update Failed")
|
||||
# Don't raise - this is not critical enough to stop migration
|
||||
|
||||
|
||||
def update_address_fields():
|
||||
quotations = frappe.get_all("Quotation", pluck="name")
|
||||
addresses = frappe.get_all("Address", pluck="name")
|
||||
|
|
@ -728,9 +736,9 @@ def update_address_fields():
|
|||
combined_doctypes.append({"doctype": "Address", "name": address})
|
||||
for task in tasks:
|
||||
combined_doctypes.append({"doctype": "Task", "name": task})
|
||||
|
||||
|
||||
print(f"\n📍 Updating field values for {total_addresses} addresses, {total_quotations} quotations, {total_sales_orders} sales orders, and {total_tasks} tasks...")
|
||||
|
||||
|
||||
# Field update counters
|
||||
field_counters = {
|
||||
'quotation_addresses_updated': 0,
|
||||
|
|
@ -751,7 +759,7 @@ def update_address_fields():
|
|||
'contacts_updated': 0,
|
||||
'tasks_updated': 0
|
||||
}
|
||||
|
||||
|
||||
onsite_meta = frappe.get_meta("On-Site Meeting")
|
||||
onsite_status_field = "custom_status" if onsite_meta.has_field("custom_status") else "status"
|
||||
|
||||
|
|
@ -761,7 +769,7 @@ def update_address_fields():
|
|||
bar_length = 30
|
||||
filled_length = int(bar_length * index // total_doctypes)
|
||||
bar = '█' * filled_length + '░' * (bar_length - filled_length)
|
||||
|
||||
|
||||
# Print a three-line, refreshing progress block without adding new lines each loop
|
||||
progress_line = f"📊 Progress: [{bar}] {progress_percentage:3d}% ({index}/{total_doctypes})"
|
||||
counters_line = f" Fields updated: {field_counters['total_field_updates']} | DocTypes updated: {field_counters['addresses_updated'] + field_counters['quotations_updated'] + field_counters['sales_orders_updated'] + field_counters['customers_updated']}"
|
||||
|
|
@ -792,7 +800,7 @@ def update_address_fields():
|
|||
custom_installation_address = getattr(quotation_doc, 'custom_installation_address', None)
|
||||
custom_job_address = getattr(quotation_doc, 'custom_job_address', None)
|
||||
custom_project_template = getattr(quotation_doc, 'custom_project_template', None)
|
||||
|
||||
|
||||
updates = {}
|
||||
if custom_installation_address and not custom_job_address:
|
||||
updates['custom_job_address'] = custom_installation_address
|
||||
|
|
@ -802,15 +810,15 @@ def update_address_fields():
|
|||
updates['custom_project_template'] = "SNW Install"
|
||||
field_counters[f"{dict_field}_project_templates_updated"] += 1
|
||||
field_counters['total_field_updates'] += 1
|
||||
|
||||
|
||||
if updates:
|
||||
frappe.db.set_value(doc['doctype'], doc['name'], updates)
|
||||
field_counters[f"{dict_field}s_updated"] += 1
|
||||
|
||||
|
||||
if doc['doctype'] == "Address":
|
||||
address_doc = frappe.get_doc("Address", doc['name'])
|
||||
updates = {}
|
||||
|
||||
|
||||
# Use getattr with default values instead of direct attribute access
|
||||
if not getattr(address_doc, 'full_address', None):
|
||||
address_parts_1 = [
|
||||
|
|
@ -822,57 +830,57 @@ def update_address_fields():
|
|||
address_doc.state or "",
|
||||
address_doc.pincode or "",
|
||||
]
|
||||
|
||||
|
||||
full_address = ", ".join([
|
||||
" ".join(filter(None, address_parts_1)),
|
||||
" ".join(filter(None, address_parts_2))
|
||||
]).strip()
|
||||
]).strip()
|
||||
updates['full_address'] = full_address
|
||||
field_counters['full_address'] += 1
|
||||
field_counters['total_field_updates'] += 1
|
||||
|
||||
|
||||
onsite_meeting = "Not Started"
|
||||
estimate_sent = "Not Started"
|
||||
job_status = "Not Started"
|
||||
payment_received = "Not Started"
|
||||
|
||||
|
||||
|
||||
|
||||
onsite_meetings = frappe.get_all("On-Site Meeting", fields=[onsite_status_field], filters={"address": address_doc.address_title})
|
||||
if onsite_meetings and onsite_meetings[0]:
|
||||
status_value = onsite_meetings[0].get(onsite_status_field)
|
||||
onsite_meeting = "Completed" if status_value == "Completed" else "In Progress"
|
||||
|
||||
|
||||
estimates = frappe.get_all("Quotation", fields=["custom_sent", "docstatus", "custom_response"], filters={"custom_job_address": address_doc.address_title})
|
||||
if estimates and estimates[0] and estimates[0]["custom_sent"] == 1 and estimates[0]["custom_response"]:
|
||||
estimate_sent = "Completed"
|
||||
elif estimates and estimates[0] and not (estimates[0]["custom_sent"] == 1 and estimates[0]["custom_response"]):
|
||||
estimate_sent = "In Progress"
|
||||
|
||||
|
||||
jobs = frappe.get_all("Project", fields=["status"], filters={"custom_installation_address": address_doc.address_title, "project_template": "SNW Install"})
|
||||
if jobs and jobs[0] and jobs[0]["status"] == "Completed":
|
||||
job_status = "Completed"
|
||||
elif jobs and jobs[0]:
|
||||
job_status = "In Progress"
|
||||
|
||||
|
||||
sales_invoices = frappe.get_all("Sales Invoice", fields=["outstanding_amount"], filters={"custom_installation_address": address_doc.address_title})
|
||||
# payments = frappe.get_all("Payment Entry", filters={"custom_installation_address": address_doc.address_title})
|
||||
if sales_invoices and sales_invoices[0] and sales_invoices[0]["outstanding_amount"] == 0:
|
||||
payment_received = "Completed"
|
||||
elif sales_invoices and sales_invoices[0]:
|
||||
payment_received = "In Progress"
|
||||
|
||||
|
||||
customer_name = getattr(address_doc, 'custom_customer_to_bill', None)
|
||||
links = address_doc.get("links", [])
|
||||
customer_links = [link for link in links if link.link_doctype == "Customer"]
|
||||
needs_link_update = False
|
||||
|
||||
|
||||
if customer_name and frappe.db.exists("Customer", customer_name):
|
||||
customer_doc = frappe.get_doc("Customer", customer_name)
|
||||
|
||||
|
||||
# Check if address needs link update
|
||||
if not customer_links:
|
||||
needs_link_update = True
|
||||
|
||||
|
||||
if not address_doc.name in [link.address_name for link in customer_doc.get("custom_select_address", [])]:
|
||||
customer_doc.append("custom_select_address", {
|
||||
"address_name": address_doc.name
|
||||
|
|
@ -880,7 +888,7 @@ def update_address_fields():
|
|||
customer_doc.save(ignore_permissions=True)
|
||||
field_counters['customers_updated'] += 1
|
||||
field_counters['total_field_updates'] += 1
|
||||
|
||||
|
||||
if getattr(address_doc, 'custom_onsite_meeting_scheduled', None) != onsite_meeting:
|
||||
updates['custom_onsite_meeting_scheduled'] = onsite_meeting
|
||||
field_counters['custom_onsite_meeting_scheduled'] += 1
|
||||
|
|
@ -897,11 +905,11 @@ def update_address_fields():
|
|||
updates['custom_payment_received_status'] = payment_received
|
||||
field_counters['custom_payment_received_status'] += 1
|
||||
field_counters['total_field_updates'] += 1
|
||||
|
||||
|
||||
if updates:
|
||||
frappe.db.set_value("Address", doc['name'], updates)
|
||||
field_counters['addresses_updated'] += 1
|
||||
|
||||
|
||||
# Handle address links after db.set_value to avoid timestamp mismatch
|
||||
if needs_link_update:
|
||||
# Reload the document to get the latest version
|
||||
|
|
@ -922,13 +930,13 @@ def update_address_fields():
|
|||
frappe.db.set_value("Task", doc["name"], "custom_property", project_address if project_address else alt_project_address)
|
||||
field_counters['tasks_updated'] += 1
|
||||
field_counters['total_field_updates'] += 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Commit every 100 records to avoid long transactions
|
||||
if index % 100 == 0:
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
# Print completion summary
|
||||
print(f"\n\n✅ DocType field value update completed!")
|
||||
print(f"📊 Summary:")
|
||||
|
|
@ -950,7 +958,7 @@ def update_address_fields():
|
|||
print(f" • Sales Order Addresses Updated: {field_counters['sales_order_addresses_updated']:,}")
|
||||
print(f" • Sales Order Project Templates Updated: {field_counters['sales_order_project_templates_updated']:,}")
|
||||
print("📍 DocType field value updates complete.\n")
|
||||
|
||||
|
||||
def build_missing_field_specs(custom_fields, missing_fields):
|
||||
missing_field_specs = {}
|
||||
for entry in missing_fields:
|
||||
|
|
@ -960,14 +968,14 @@ def build_missing_field_specs(custom_fields, missing_fields):
|
|||
if field_spec["fieldname"] == fieldname:
|
||||
missing_field_specs[doctype].append(field_spec)
|
||||
break
|
||||
|
||||
|
||||
return missing_field_specs
|
||||
|
||||
def check_and_create_holiday_list(year=2026, country="US", weekly_off="Sunday"):
|
||||
"""Check if Holiday List for the given year exists, if not create it."""
|
||||
print(f"\n🔧 Checking for Holiday List for {country} in {year}...")
|
||||
holiday_list_name = f"{country} Holidays {year}"
|
||||
|
||||
|
||||
if frappe.db.exists("Holiday List", holiday_list_name):
|
||||
print(f"✅ Holiday List '{holiday_list_name}' already exists.")
|
||||
return
|
||||
|
|
@ -999,16 +1007,16 @@ def check_and_create_holiday_list(year=2026, country="US", weekly_off="Sunday"):
|
|||
# hl.make_holiday_entries()
|
||||
frappe.db.commit()
|
||||
print(f"✅ Holiday List '{holiday_list_name}' created successfully.")
|
||||
|
||||
|
||||
def get_all_sundays(year):
|
||||
sundays = []
|
||||
d = date(year, 1, 1)
|
||||
|
||||
|
||||
while d.weekday() != 6:
|
||||
d += timedelta(days=1)
|
||||
|
||||
|
||||
while d.year == year:
|
||||
sundays.append(d)
|
||||
d += timedelta(days=7)
|
||||
|
||||
return sundays
|
||||
|
||||
return sundays
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue