multiple bid meetings for one cell, final invoice flow.
This commit is contained in:
parent
b3e6e4f6a2
commit
caa4bc2dca
@ -534,7 +534,7 @@ def upsert_estimate(data):
|
||||
# ClientService.append_link(data.get("customer"), "quotations", "quotation", new_estimate.name)
|
||||
print("DEBUG: New estimate created with name:", new_estimate.name)
|
||||
dict = new_estimate.as_dict()
|
||||
dict["items"] = [ItemService.get_full_dict(item.item_code) for item in new_estimate.items]
|
||||
dict["items"] = [{**ItemService.get_full_dict(item.item_code), **item} for item in new_estimate.items]
|
||||
return build_success_response(dict)
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error in upsert_estimate: {str(e)}")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import frappe, json
|
||||
from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||
from custom_ui.services import SalesOrderService, EmailService
|
||||
|
||||
# ===============================================================================
|
||||
# INVOICES API METHODS
|
||||
@ -10,15 +11,40 @@ from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||
@frappe.whitelist()
|
||||
def create_invoice_for_job(job_name):
|
||||
"""Create the invoice from a sales order of a job."""
|
||||
print("DEBUG: create_invoice_for_job called with job_name:", job_name)
|
||||
try:
|
||||
project = frappe.get_doc("Project", job_name)
|
||||
sales_order = project.sales_order
|
||||
invoice = make_sales_invoice(sales_order)
|
||||
invoice.save()
|
||||
sales_order = frappe.get_value("Project", job_name, "sales_order")
|
||||
if not sales_order:
|
||||
return build_error_response("No sales order found for this job.", 404)
|
||||
invoice = SalesOrderService.create_sales_invoice_from_sales_order(sales_order)
|
||||
return build_success_response(invoice.as_dict())
|
||||
except Exception as e:
|
||||
return build_error_response(str(e), 500)
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_and_send_invoice(invoice_name):
|
||||
"""Submit the invoice and send email to customer."""
|
||||
print("DEBUG: submit_and_send_invoice called with invoice_name:", invoice_name)
|
||||
try:
|
||||
invoice_doc = frappe.get_doc("Sales Invoice", invoice_name)
|
||||
if invoice_doc.docstatus == 0:
|
||||
print("DEBUG: Submitting invoice:", invoice_name)
|
||||
invoice_doc.submit()
|
||||
else:
|
||||
print("DEBUG: Invoice already submitted:", invoice_name)
|
||||
# Send invoice email to customer
|
||||
try:
|
||||
print("DEBUG: Preparing to send invoice email for", invoice_name)
|
||||
EmailService.send_invoice_email(invoice_name)
|
||||
print("DEBUG: Invoice email sent successfully for", invoice_name)
|
||||
frappe.set_value("Project", invoice_doc.project, "invoice_status", "Invoice Sent")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to send invoice email: {str(e)}")
|
||||
# Don't raise the exception - we don't want to block the invoice submission
|
||||
frappe.log_error(f"Failed to send invoice email for {invoice_name}: {str(e)}", "Invoice Email Error")
|
||||
return build_success_response(invoice_doc.as_dict())
|
||||
except Exception as e:
|
||||
return build_error_response(str(e), 500)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices_late_count():
|
||||
|
||||
@ -4,6 +4,7 @@ from datetime import datetime
|
||||
from frappe.utils.data import flt
|
||||
from custom_ui.services import DbService, StripeService, PaymentService
|
||||
from custom_ui.models import PaymentData
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def half_down_stripe_payment(sales_order):
|
||||
@ -36,7 +37,11 @@ def invoice_stripe_payment(sales_invoice):
|
||||
if si.docstatus != 1:
|
||||
frappe.throw("Sales Invoice must be submitted to proceed with payment.")
|
||||
if si.outstanding_amount <= 0:
|
||||
frappe.throw("This invoice has already been paid.")
|
||||
html = frappe.render_template("custom_ui/templates/invoices/already_paid.html", {
|
||||
"invoice_number": si.name,
|
||||
"company": frappe.get_doc("Company", si.company)
|
||||
})
|
||||
return Response(html, content_type='text/html')
|
||||
stripe_session = StripeService.create_checkout_session(
|
||||
company=si.company,
|
||||
amount=si.outstanding_amount,
|
||||
|
||||
@ -75,9 +75,9 @@ def on_update_after_submit(doc, method):
|
||||
new_sales_order.customer_address = doc.customer_address
|
||||
# new_sales_order.custom_installation_address = doc.custom_installation_address
|
||||
new_sales_order.custom_job_address = doc.custom_job_address
|
||||
new_sales_order.payment_schedule = []
|
||||
new_sales_order.set("payment_schedule", [])
|
||||
print("DEBUG: Setting payment schedule for Sales Order")
|
||||
new_sales_order.set_payment_schedule()
|
||||
# new_sales_order.set_payment_schedule()
|
||||
print("DEBUG: Inserting Sales Order:", new_sales_order.as_dict())
|
||||
new_sales_order.delivery_date = new_sales_order.transaction_date
|
||||
# backup = new_sales_order.customer_address
|
||||
|
||||
@ -31,6 +31,8 @@ def after_insert(doc, method):
|
||||
"project_template": doc.project_template
|
||||
})
|
||||
doc.service_appointment = service_apt.name
|
||||
if doc.requires_half_payment:
|
||||
service_apt.ready_to_schedule = 0
|
||||
doc.save(ignore_permissions=True)
|
||||
print("DEBUG: Created Service Appointment:", service_apt.name)
|
||||
except Exception as e:
|
||||
@ -63,6 +65,9 @@ def before_insert(doc, method):
|
||||
def before_save(doc, method):
|
||||
print("DEBUG: Before Save Triggered for Project:", doc.name)
|
||||
print("DEBUG: Checking status: ", doc.status)
|
||||
if doc.percent_complete == 100.0 and doc.invoice_status == "Not Ready":
|
||||
print("DEBUG: Project is 100% complete and invoice status is Not Ready, setting invoice status to Ready to Invoice")
|
||||
doc.invoice_status = "Ready to Invoice"
|
||||
if doc.expected_start_date and doc.expected_end_date:
|
||||
print("DEBUG: Project has expected start and end dates, marking as scheduled")
|
||||
doc.is_scheduled = 1
|
||||
@ -82,11 +87,18 @@ def before_save(doc, method):
|
||||
|
||||
def after_save(doc, method):
|
||||
print("DEBUG: After Save Triggered for Project:", doc.name)
|
||||
if doc.status == "Completed":
|
||||
print("DEBUG: Project marked as Completed. Generating and sending final invoice.")
|
||||
sales_order_status = frappe.get_value("Sales Order", doc.sales_order, "billing_status")
|
||||
if sales_order_status == "Not Billed":
|
||||
SalesOrderService.create_sales_invoice_from_sales_order(doc.sales_order)
|
||||
if doc.ready_to_schedule and doc.service_appointment:
|
||||
service_apt_ready_to_schedule = frappe.get_value("Service Address 2", doc.service_appointment, "ready_to_schedule")
|
||||
if not service_apt_ready_to_schedule:
|
||||
print("DEBUG: Project is ready to schedule, setting Service Appointment to ready to schedule.")
|
||||
service_apt_doc = frappe.get_doc("Service Address 2", doc.service_appointment)
|
||||
service_apt_doc.ready_to_schedule = 1
|
||||
service_apt_doc.save(ignore_permissions=True)
|
||||
# if doc.status == "Completed":
|
||||
|
||||
# sales_order_status = frappe.get_value("Sales Order", doc.sales_order, "billing_status")
|
||||
# if sales_order_status == "Not Billed":
|
||||
# SalesOrderService.create_sales_invoice_from_sales_order(doc.sales_order)
|
||||
if doc.ready_to_schedule:
|
||||
service_apt_ready_to_schedule = frappe.get_value("Service Address 2", doc.service_appointment, "ready_to_schedule")
|
||||
if not service_apt_ready_to_schedule:
|
||||
|
||||
@ -1,22 +1,23 @@
|
||||
import frappe
|
||||
from custom_ui.services.email_service import EmailService
|
||||
from custom_ui.services import ProjectService
|
||||
|
||||
def on_submit(doc, method):
|
||||
print("DEBUG: On Submit Triggered for Sales Invoice:", doc.name)
|
||||
|
||||
# Send invoice email to customer
|
||||
try:
|
||||
print("DEBUG: Preparing to send invoice email for", doc.name)
|
||||
EmailService.send_invoice_email(doc.name)
|
||||
print("DEBUG: Invoice email sent successfully for", doc.name)
|
||||
frappe.set_value("Project", doc.project, "invoice_status", "Invoice Sent")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to send invoice email: {str(e)}")
|
||||
# Don't raise the exception - we don't want to block the invoice submission
|
||||
frappe.log_error(f"Failed to send invoice email for {doc.name}: {str(e)}", "Invoice Email Error")
|
||||
# # Send invoice email to customer
|
||||
# try:
|
||||
# print("DEBUG: Preparing to send invoice email for", doc.name)
|
||||
# EmailService.send_invoice_email(doc.name)
|
||||
# print("DEBUG: Invoice email sent successfully for", doc.name)
|
||||
# frappe.set_value("Project", doc.project, "invoice_status", "Invoice Sent")
|
||||
# except Exception as e:
|
||||
# print(f"ERROR: Failed to send invoice email: {str(e)}")
|
||||
# # Don't raise the exception - we don't want to block the invoice submission
|
||||
# frappe.log_error(f"Failed to send invoice email for {doc.name}: {str(e)}", "Invoice Email Error")
|
||||
|
||||
def after_insert(doc, method):
|
||||
print("DEBUG: After Insert Triggered for Sales Invoice:", doc.name)
|
||||
# Additional logic can be added here if needed after invoice creation
|
||||
frappe.set_value("Project", doc.project, "invoice_status", "Invoice Created")
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import frappe
|
||||
from custom_ui.services import DbService, AddressService, ClientService
|
||||
from custom_ui.services import DbService, AddressService, ClientService, EmailService
|
||||
|
||||
|
||||
def before_save(doc, method):
|
||||
@ -76,7 +76,6 @@ def after_insert(doc, method):
|
||||
if doc.requires_half_payment:
|
||||
try:
|
||||
print("DEBUG: Sales Order requires half payment, preparing to send down payment email")
|
||||
from custom_ui.services.email_service import EmailService
|
||||
|
||||
# Use EmailService to send the down payment email
|
||||
EmailService.send_downpayment_email(doc.name)
|
||||
|
||||
@ -4,6 +4,10 @@ from custom_ui.services import AddressService, ClientService, TaskService
|
||||
def before_insert(doc, method):
|
||||
"""Set values before inserting a Task."""
|
||||
print("DEBUG: Before Insert Triggered for Task")
|
||||
if doc.type:
|
||||
task_type_weight = frappe.get_value("Task Type", doc.type, "weight") or 0
|
||||
print(f"DEBUG: Setting Task weight to {task_type_weight} based on Task Type {doc.type}")
|
||||
doc.task_weight = task_type_weight
|
||||
if doc.status == "Template":
|
||||
print("DEBUG: Task is a Template, skipping project linking")
|
||||
return
|
||||
@ -52,11 +56,13 @@ def after_save(doc, method):
|
||||
if doc.project and doc.status == "Completed":
|
||||
print("DEBUG: Task is completed, checking if project has calculated 100% Progress.")
|
||||
project_doc = frappe.get_doc("Project", doc.project)
|
||||
if project_doc.percent_complete == 100:
|
||||
print("DEBUG: Current Project percent_complete:", project_doc.percent_complete)
|
||||
if project_doc.percent_complete == 100.0:
|
||||
print("DEBUG: Project percent_complete is 100%, checking if we need to update project status or invoice status.")
|
||||
project_update_required = False
|
||||
if project_doc.status == "Completed" and project_doc.customCompletionDate is None:
|
||||
if project_doc.status == "Completed" and project_doc.custom_completion_date is None:
|
||||
print("DEBUG: Project is marked as Completed but customCompletionDate is not set, updating customCompletionDate.")
|
||||
project_doc.customCompletionDate = frappe.utils.nowdate()
|
||||
project_doc.custom_completion_date = frappe.utils.nowdate()
|
||||
project_update_required = True
|
||||
if project_doc.invoice_status == "Not Ready":
|
||||
project_doc.invoice_status = "Ready to Invoice"
|
||||
|
||||
@ -2963,120 +2963,6 @@
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "On-Site Meeting",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "company",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Party Type",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-13 03:06:47.189806",
|
||||
"module": null,
|
||||
"name": "On-Site Meeting-party_type",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "Customer\nLead",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Customer",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "from_lead",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "customer_name",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "From Lead",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-13 04:14:35.978839",
|
||||
"module": null,
|
||||
"name": "Customer-from_lead",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "Lead",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -3419,6 +3305,120 @@
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "On-Site Meeting",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "company",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Party Type",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-13 03:06:47.189806",
|
||||
"module": null,
|
||||
"name": "On-Site Meeting-party_type",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "Customer\nLead",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Customer",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "from_lead",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "customer_name",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "From Lead",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-02-13 03:39:42.161305",
|
||||
"module": null,
|
||||
"name": "Customer-from_lead",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "Lead",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -8070,7 +8070,7 @@
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-02-12 02:52:42.307849",
|
||||
"modified": "2026-02-12 04:10:06.712703",
|
||||
"module": null,
|
||||
"name": "Project-requires_half_payment",
|
||||
"no_copy": 0,
|
||||
@ -8469,7 +8469,7 @@
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-02-12 02:52:42.394762",
|
||||
"modified": "2026-02-12 04:10:06.808397",
|
||||
"module": null,
|
||||
"name": "Project-is_half_down_paid",
|
||||
"no_copy": 0,
|
||||
@ -8754,7 +8754,7 @@
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-02-12 02:52:42.235665",
|
||||
"modified": "2026-02-12 04:10:06.643135",
|
||||
"module": null,
|
||||
"name": "Project-is_scheduled",
|
||||
"no_copy": 0,
|
||||
@ -10179,7 +10179,7 @@
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-02-12 02:52:41.808876",
|
||||
"modified": "2026-02-12 04:10:06.196780",
|
||||
"module": null,
|
||||
"name": "Address-latitude",
|
||||
"no_copy": 0,
|
||||
@ -10293,7 +10293,7 @@
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-02-12 02:52:41.924704",
|
||||
"modified": "2026-02-12 04:10:06.320979",
|
||||
"module": null,
|
||||
"name": "Address-longitude",
|
||||
"no_copy": 0,
|
||||
@ -13858,15 +13858,15 @@
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": "accept_payment",
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Web Form",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "payment_button_help",
|
||||
"fieldtype": "Text",
|
||||
"fieldname": "payments_cb",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
@ -13877,16 +13877,16 @@
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "payment_button_label",
|
||||
"insert_after": "payment_button_help",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Button Help",
|
||||
"label": null,
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-27 11:11:05.256345",
|
||||
"modified": "2026-01-27 11:11:05.288776",
|
||||
"module": null,
|
||||
"name": "Web Form-payment_button_help",
|
||||
"name": "Web Form-payments_cb",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
@ -13907,63 +13907,6 @@
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": "accept_payment",
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Web Form",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "amount",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Currency",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-27 11:11:05.420470",
|
||||
"module": null,
|
||||
"name": "Web Form-currency",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "Currency",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -14021,120 +13964,6 @@
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "accept_payment",
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Web Form",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "amount_based_on_field",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "payments_cb",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Amount Based On Field",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-27 11:11:05.321231",
|
||||
"module": null,
|
||||
"name": "Web Form-amount_based_on_field",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Sales Order",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "exempt_from_sales_tax",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "taxes_and_charges",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Is customer exempted from sales tax?",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2024-04-03 13:53:07.674608",
|
||||
"module": null,
|
||||
"name": "Sales Order-exempt_from_sales_tax",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -14249,6 +14078,120 @@
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": "accept_payment",
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Web Form",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "amount",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Currency",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-27 11:11:05.420470",
|
||||
"module": null,
|
||||
"name": "Web Form-currency",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "Currency",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Sales Order",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "exempt_from_sales_tax",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "taxes_and_charges",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Is customer exempted from sales tax?",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2024-04-03 13:53:07.674608",
|
||||
"module": null,
|
||||
"name": "Sales Order-exempt_from_sales_tax",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -14306,6 +14249,63 @@
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "accept_payment",
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Web Form",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "amount_based_on_field",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "payments_cb",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": "Amount Based On Field",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-27 11:11:05.321231",
|
||||
"module": null,
|
||||
"name": "Web Form-amount_based_on_field",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"show_dashboard": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -14371,15 +14371,15 @@
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"depends_on": "accept_payment",
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Web Form",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "payments_cb",
|
||||
"fieldtype": "Column Break",
|
||||
"fieldname": "payment_button_help",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
@ -14390,16 +14390,16 @@
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "payment_button_help",
|
||||
"insert_after": "payment_button_label",
|
||||
"is_system_generated": 1,
|
||||
"is_virtual": 0,
|
||||
"label": null,
|
||||
"label": "Button Help",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2026-01-27 11:11:05.288776",
|
||||
"modified": "2026-01-27 11:11:05.256345",
|
||||
"module": null,
|
||||
"name": "Web Form-payments_cb",
|
||||
"name": "Web Form-payment_button_help",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": null,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -15199,38 +15199,6 @@
|
||||
"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\"]"
|
||||
},
|
||||
{
|
||||
"default_value": null,
|
||||
"doc_type": "Sales Order",
|
||||
@ -15262,5 +15230,37 @@
|
||||
"property_type": "Data",
|
||||
"row_name": null,
|
||||
"value": "[\"gateway_name\", \"publishable_key\", \"custom_webhook_secret\", \"column_break_3\", \"secret_key\", \"custom_company\", \"custom_account\", \"section_break_5\", \"header_img\", \"column_break_7\", \"redirect_url\"]"
|
||||
},
|
||||
{
|
||||
"default_value": null,
|
||||
"doc_type": "Project",
|
||||
"docstatus": 0,
|
||||
"doctype": "Property Setter",
|
||||
"doctype_or_field": "DocType",
|
||||
"field_name": null,
|
||||
"is_system_generated": 0,
|
||||
"modified": "2026-02-12 12:21:16.095851",
|
||||
"module": null,
|
||||
"name": "Project-main-autoname",
|
||||
"property": "autoname",
|
||||
"property_type": "Data",
|
||||
"row_name": null,
|
||||
"value": "format:{project_template}-{customer}-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-02-12 12:21:16.160749",
|
||||
"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\", \"requires_half_payment\", \"is_half_down_paid\", \"is_scheduled\", \"invoice_status\", \"custom_completion_date\", \"priority\", \"custom_foreman\", \"custom_hidden_fields\", \"department\", \"service_appointment\", \"tasks\", \"ready_to_schedule\", \"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\"]"
|
||||
}
|
||||
]
|
||||
@ -59,7 +59,7 @@
|
||||
"department": null,
|
||||
"depends_on": [],
|
||||
"depends_on_tasks": "",
|
||||
"description": null,
|
||||
"description": "Utility locate request prior to installation start.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Task",
|
||||
"duration": 0,
|
||||
@ -70,7 +70,7 @@
|
||||
"is_milestone": 0,
|
||||
"is_template": 1,
|
||||
"issue": null,
|
||||
"modified": "2025-04-24 14:57:03.402721",
|
||||
"modified": "2026-02-12 12:31:42.805351",
|
||||
"name": "TASK-2025-00002",
|
||||
"old_parent": "",
|
||||
"parent_task": null,
|
||||
@ -82,12 +82,12 @@
|
||||
"start": 0,
|
||||
"status": "Template",
|
||||
"subject": "811/Locate call in",
|
||||
"task_weight": 0.0,
|
||||
"task_weight": 7.0,
|
||||
"template_task": null,
|
||||
"total_billing_amount": 0.0,
|
||||
"total_costing_amount": 0.0,
|
||||
"total_expense_claim": 0.0,
|
||||
"type": "Admin"
|
||||
"type": "811/Locate"
|
||||
},
|
||||
{
|
||||
"act_end_date": null,
|
||||
@ -284,7 +284,7 @@
|
||||
"department": null,
|
||||
"depends_on": [],
|
||||
"depends_on_tasks": "",
|
||||
"description": null,
|
||||
"description": "Task tracking for the main service job.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Task",
|
||||
"duration": 0,
|
||||
@ -295,7 +295,7 @@
|
||||
"is_milestone": 0,
|
||||
"is_template": 1,
|
||||
"issue": null,
|
||||
"modified": "2025-05-10 05:06:24.653035",
|
||||
"modified": "2026-02-12 12:32:42.996899",
|
||||
"name": "TASK-2025-00004",
|
||||
"old_parent": "",
|
||||
"parent_task": null,
|
||||
@ -307,12 +307,12 @@
|
||||
"start": 0,
|
||||
"status": "Template",
|
||||
"subject": "Primary Job",
|
||||
"task_weight": 0.0,
|
||||
"task_weight": 25.0,
|
||||
"template_task": null,
|
||||
"total_billing_amount": 0.0,
|
||||
"total_costing_amount": 0.0,
|
||||
"total_expense_claim": 0.0,
|
||||
"type": "Labor"
|
||||
"type": "Main Job"
|
||||
},
|
||||
{
|
||||
"act_end_date": null,
|
||||
|
||||
@ -196,7 +196,7 @@ doc_events = {
|
||||
"before_insert": "custom_ui.events.task.before_insert",
|
||||
"after_insert": "custom_ui.events.task.after_insert",
|
||||
"before_save": "custom_ui.events.task.before_save",
|
||||
"after_save": "custom_ui.events.task.after_save"
|
||||
"on_update": "custom_ui.events.task.after_save"
|
||||
},
|
||||
"Bid Meeting Note Form": {
|
||||
"after_insert": "custom_ui.events.general.attach_bid_note_form_to_project_template"
|
||||
@ -210,7 +210,8 @@ doc_events = {
|
||||
"on_submit": "custom_ui.events.payments.on_submit"
|
||||
},
|
||||
"Sales Invoice": {
|
||||
"on_submit": "custom_ui.events.sales_invoice.on_submit"
|
||||
"on_submit": "custom_ui.events.sales_invoice.on_submit",
|
||||
"after_insert": "custom_ui.events.sales_invoice.after_insert"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -485,6 +485,12 @@ def add_custom_fields():
|
||||
default=0,
|
||||
insert_after="custom_installation_address"
|
||||
),
|
||||
dict(
|
||||
fieldname="remarks",
|
||||
label="Remarks",
|
||||
fieldtype="Small Text",
|
||||
insert_after="grand_total"
|
||||
),
|
||||
dict(
|
||||
fieldname="custom_quotation_template",
|
||||
label="Quotation Template",
|
||||
|
||||
@ -8,7 +8,8 @@ class SalesOrderService:
|
||||
def create_sales_invoice_from_sales_order(sales_order_name):
|
||||
try:
|
||||
sales_order_doc = frappe.get_doc("Sales Order", sales_order_name)
|
||||
sales_invoice = make_sales_invoice(sales_order_doc.name)
|
||||
sales_invoice = make_sales_invoice(sales_order_doc.name, ignore_permissions=True)
|
||||
print("DEBUG: Sales Invoice created from Sales Order:", sales_invoice.name)
|
||||
sales_invoice.project = sales_order_doc.project
|
||||
sales_invoice.posting_date = today()
|
||||
sales_invoice.due_date = today()
|
||||
@ -21,8 +22,9 @@ class SalesOrderService:
|
||||
sales_invoice.calculate_taxes_and_totals()
|
||||
|
||||
sales_invoice.insert()
|
||||
sales_invoice.submit()
|
||||
return sales_invoice.name
|
||||
print("DEBUG: Sales Invoice: ", sales_invoice.as_dict())
|
||||
# sales_invoice.submit()
|
||||
return sales_invoice
|
||||
except Exception as e:
|
||||
print("ERROR creating Sales Invoice from Sales Order:", str(e))
|
||||
return None
|
||||
@ -56,14 +56,17 @@ class StripeService:
|
||||
Returns:
|
||||
stripe.checkout.Session object
|
||||
"""
|
||||
print(f"DEBUG: Creating Stripe Checkout Session for company with details - Company: {company}, Amount: {amount}, Service: {service}, Order Num: {order_num}, Currency: {currency}, For Advance Payment: {for_advance_payment}, Sales Invoice: {sales_invoice}")
|
||||
stripe.api_key = StripeService.get_api_key(company)
|
||||
|
||||
# Determine payment description
|
||||
if for_advance_payment:
|
||||
description = f"Advance payment for {company}{' - ' + service if service else ''}"
|
||||
else:
|
||||
print("DEBUG: Creating description for full payment")
|
||||
description = f"Invoice payment for {company}{' - ' + service if service else ''}"
|
||||
if sales_invoice:
|
||||
print(f"DEBUG: Sales Invoice provided for full payment: {sales_invoice}")
|
||||
description = f"Invoice {sales_invoice} - {company}"
|
||||
|
||||
# Use custom line items if provided and not an advance payment, otherwise create default line item
|
||||
@ -88,7 +91,7 @@ class StripeService:
|
||||
if for_advance_payment:
|
||||
metadata["sales_order"] = order_num
|
||||
else:
|
||||
metadata["sales_invoice"] = sales_invoice or order_num
|
||||
metadata["sales_invoice"] = sales_invoice if sales_invoice else order_num
|
||||
if sales_invoice:
|
||||
# Check if there's a related sales order
|
||||
invoice_doc = frappe.get_doc("Sales Invoice", sales_invoice)
|
||||
|
||||
191
custom_ui/templates/invoices/already_paid.html
Normal file
191
custom_ui/templates/invoices/already_paid.html
Normal file
@ -0,0 +1,191 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Invoice Already Paid</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.message-container {
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
padding: 50px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
position: relative;
|
||||
}
|
||||
.message-icon {
|
||||
font-size: 5rem;
|
||||
color: #00b894;
|
||||
margin-bottom: 30px;
|
||||
animation: checkmarkAnimation 1.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes checkmarkAnimation {
|
||||
0% {
|
||||
transform: scale(0) rotate(-180deg);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
transform: scale(0.9) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.invoice-number {
|
||||
background-color: #e3f2fd;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #2196f3;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.contact-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-top: 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.contact-section h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #333;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.contact-section > p {
|
||||
margin: 0 0 15px 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.contact-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.contact-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.contact-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-label {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contact-value {
|
||||
font-weight: 400;
|
||||
color: #6c757d;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.contact-value a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.contact-value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="message-container">
|
||||
<div class="message-icon">✓</div>
|
||||
<h1 class="message-title">Invoice Already Paid</h1>
|
||||
<p class="message-text">This invoice has already been paid in full.</p>
|
||||
|
||||
{% if invoice_number %}
|
||||
<div class="invoice-number">
|
||||
Invoice #{{ invoice_number }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if company %}
|
||||
<div class="contact-section">
|
||||
<h3>Have Questions?</h3>
|
||||
<p>We're here to help! Contact us if you need assistance.</p>
|
||||
<div class="contact-details">
|
||||
{% if company.company_name %}
|
||||
<div class="contact-row">
|
||||
<span class="contact-label">Company:</span>
|
||||
<span class="contact-value">{{ company.company_name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if company.phone_no %}
|
||||
<div class="contact-row">
|
||||
<span class="contact-label">Phone:</span>
|
||||
<span class="contact-value">{{ company.phone_no }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if company.email %}
|
||||
<div class="contact-row">
|
||||
<span class="contact-label">Email:</span>
|
||||
<span class="contact-value"><a href="mailto:{{ company.email }}">{{ company.email }}</a></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if company.website %}
|
||||
<div class="contact-row">
|
||||
<span class="contact-label">Website:</span>
|
||||
<span class="contact-value"><a href="{{ company.website }}" target="_blank">{{ company.website }}</a></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -5,4 +5,5 @@ services:
|
||||
ports:
|
||||
- "8025:8025" # MailHog web UI
|
||||
- "1025:1025" # SMTP server
|
||||
restart: unless-stopped
|
||||
restart: unless-stopped
|
||||
|
||||
@ -46,6 +46,7 @@ const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_
|
||||
const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice";
|
||||
const FRAPPE_GET_INVOICES_LATE_METHOD = "custom_ui.api.db.invoices.get_invoices_late_count";
|
||||
const FRAPPE_CREATE_INVOICE_FOR_JOB = "custom_ui.api.db.invoices.create_invoice_for_job";
|
||||
const FRAPPE_SUBMIT_AND_SEND_INVOICE_METHOD = "custom_ui.api.db.invoices.submit_and_send_invoice";
|
||||
// Warranty methods
|
||||
const FRAPPE_GET_WARRANTY_CLAIMS_METHOD = "custom_ui.api.db.warranties.get_warranty_claims";
|
||||
// On-Site Meeting methods
|
||||
@ -574,8 +575,12 @@ class Api {
|
||||
|
||||
}
|
||||
|
||||
static async sendInvoice(invoiceName) {
|
||||
return await this.request(FRAPPE_SUBMIT_AND_SEND_INVOICE_METHOD, { invoiceName });
|
||||
}
|
||||
|
||||
static async createInvoice(invoiceData) {
|
||||
const payload = DataUtils.toSnakeCaseObject(invoiceData);
|
||||
// const payload = DataUtils.toSnakeCaseObject(invoiceData);
|
||||
const result = await this.request(FRAPPE_UPSERT_INVOICE_METHOD, { data: payload });
|
||||
console.log("DEBUG: API - Created Invoice: ", result);
|
||||
return result;
|
||||
|
||||
@ -101,11 +101,13 @@
|
||||
dragOverSlot?.date === day.date &&
|
||||
dragOverSlot?.time === timeSlot.time,
|
||||
weekend: day.isWeekend,
|
||||
'has-multiple-meetings': getMeetingsForTimeSlot(day.date, timeSlot.time).length > 1,
|
||||
}"
|
||||
@click="selectTimeSlot(day.date, timeSlot.time)"
|
||||
@dragover="handleDragOver($event, day.date, timeSlot.time)"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop($event, day.date, timeSlot.time)"
|
||||
:ref="el => dayCells[`${day.date}-${timeSlot.time}`] = el"
|
||||
>
|
||||
<!-- Meetings in this time slot -->
|
||||
<div
|
||||
@ -119,7 +121,7 @@
|
||||
:draggable="meeting.status !== 'Completed'"
|
||||
@dragstart="handleMeetingDragStart($event, meeting)"
|
||||
@dragend="handleDragEnd($event)"
|
||||
@click.stop="showMeetingDetails(meeting)"
|
||||
@drop="handleDrop($event, day.date, timeSlot.time)"
|
||||
>
|
||||
<div class="event-time">
|
||||
{{ formatTimeDisplay(meeting.scheduledTime) }}
|
||||
@ -135,6 +137,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slot Popup for Multiple Meetings -->
|
||||
<div v-if="showSlotPopup" class="slot-popup-overlay" @click="showSlotPopup = false">
|
||||
<div class="slot-popup" :style="popupStyle">
|
||||
<div class="popup-header">
|
||||
{{ formatTimeDisplay(popupPosition.time) }} - {{ formatDate(popupPosition.date) }}
|
||||
</div>
|
||||
<div
|
||||
v-for="meeting in popupMeetings"
|
||||
:key="meeting.id"
|
||||
class="popup-meeting-item"
|
||||
@click.stop="showMeetingDetails(meeting); showSlotPopup = false"
|
||||
>
|
||||
<div class="meeting-address">{{ meeting.address?.fullAddress || meeting.address }}</div>
|
||||
<div class="meeting-client">{{ meeting.client }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Sidebar for Unscheduled Meetings -->
|
||||
<div class="sidebar sidebar-right" :class="{ collapsed: isSidebarCollapsed }">
|
||||
<div class="sidebar-header">
|
||||
@ -274,6 +294,13 @@ const draggedMeeting = ref(null);
|
||||
const originalMeetingForReschedule = ref(null); // Store original meeting data for reschedules
|
||||
const isUnscheduledDragOver = ref(false);
|
||||
|
||||
// Slot popup state
|
||||
const showSlotPopup = ref(false);
|
||||
const popupMeetings = ref([]);
|
||||
const popupPosition = ref({});
|
||||
const popupStyle = ref({});
|
||||
const dayCells = ref({});
|
||||
|
||||
// Sidebar state
|
||||
const isSidebarCollapsed = ref(false);
|
||||
|
||||
@ -313,7 +340,7 @@ const weekDays = computed(() => {
|
||||
const isWeekend = day.getDay() === 0 || day.getDay() === 6;
|
||||
|
||||
days.push({
|
||||
date: day.toISOString().split("T")[0],
|
||||
date: `${day.getFullYear()}-${String(day.getMonth() + 1).padStart(2, '0')}-${String(day.getDate()).padStart(2, '0')}`,
|
||||
dayName: day.toLocaleDateString("en-US", { weekday: "short" }),
|
||||
displayDate: day.toLocaleDateString("en-US", { month: "short", day: "numeric" }),
|
||||
isToday,
|
||||
@ -472,7 +499,25 @@ const getAddressText = (address) => {
|
||||
};
|
||||
|
||||
const selectTimeSlot = (date, time) => {
|
||||
console.log("Selected time slot:", date, time);
|
||||
const meetingsInSlot = getMeetingsForTimeSlot(date, time);
|
||||
if (meetingsInSlot.length === 1) {
|
||||
showMeetingDetails(meetingsInSlot[0]);
|
||||
} else if (meetingsInSlot.length > 1) {
|
||||
// Show popup with list of meetings
|
||||
const cellKey = `${date}-${time}`;
|
||||
const cell = dayCells.value[cellKey];
|
||||
if (cell) {
|
||||
const rect = cell.getBoundingClientRect();
|
||||
popupStyle.value = {
|
||||
top: `${rect.bottom + window.scrollY + 5}px`,
|
||||
left: `${rect.left + window.scrollX}px`,
|
||||
};
|
||||
}
|
||||
popupMeetings.value = meetingsInSlot;
|
||||
popupPosition.value = { date, time };
|
||||
showSlotPopup.value = true;
|
||||
}
|
||||
// If no meetings, do nothing for now
|
||||
};
|
||||
|
||||
const showMeetingDetails = (meeting) => {
|
||||
@ -964,15 +1009,8 @@ const loadWeekMeetings = async () => {
|
||||
meetings.value = apiResult
|
||||
.map((meeting) => {
|
||||
// Extract date and time from startTime
|
||||
const startDateTime = meeting.startTime
|
||||
? new Date(meeting.startTime)
|
||||
: null;
|
||||
const date = startDateTime
|
||||
? startDateTime.toISOString().split("T")[0]
|
||||
: null;
|
||||
const time = startDateTime
|
||||
? `${startDateTime.getHours().toString().padStart(2, "0")}:${startDateTime.getMinutes().toString().padStart(2, "0")}`
|
||||
: null;
|
||||
const date = meeting.startTime ? meeting.startTime.split(' ')[0] : null;
|
||||
const time = meeting.startTime ? meeting.startTime.split(' ')[1].substring(0,5) : null;
|
||||
|
||||
// Return the full meeting object with calendar-specific fields added
|
||||
return {
|
||||
@ -1108,8 +1146,8 @@ const findAndDisplayMeetingByName = async () => {
|
||||
|
||||
// Parse the start time to get date and time
|
||||
const startDateTime = new Date(meetingData.startTime);
|
||||
const meetingDate = startDateTime.toISOString().split("T")[0];
|
||||
const meetingTime = `${startDateTime.getHours().toString().padStart(2, "0")}:${startDateTime.getMinutes().toString().padStart(2, "0")}`;
|
||||
const meetingDate = meetingData.startTime.split(' ')[0];
|
||||
const meetingTime = meetingData.startTime.split(' ')[1].substring(0,5);
|
||||
|
||||
// Navigate to the week containing this meeting
|
||||
currentWeekStart.value = new Date(
|
||||
@ -1636,24 +1674,60 @@ watch(
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.main-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
.day-column.has-multiple-meetings {
|
||||
border: 2px solid #ff9800;
|
||||
background-color: #fff3e0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 20px;
|
||||
max-height: 200px;
|
||||
}
|
||||
.slot-popup-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.calendar-header-row,
|
||||
.time-row {
|
||||
grid-template-columns: 60px repeat(7, 1fr);
|
||||
}
|
||||
.slot-popup {
|
||||
position: absolute;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
padding: 8px;
|
||||
min-width: 250px;
|
||||
max-width: 300px;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.popup-meeting-item {
|
||||
padding: 4px 0;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-meeting-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.popup-meeting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.meeting-address {
|
||||
font-size: 0.8em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meeting-client {
|
||||
font-size: 0.7em;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -26,10 +26,30 @@
|
||||
<span class="btn-icon">📧</span>
|
||||
Email Customer
|
||||
</button>
|
||||
<button class="btn btn-primary" @click="createInvoiceForJob()">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="createInvoiceForJob()"
|
||||
:disabled="!canCreateInvoice"
|
||||
>
|
||||
<span class="btn-icon">💰</span>
|
||||
Create Invoice
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="viewInvoice()"
|
||||
:disabled="!canViewInvoice"
|
||||
>
|
||||
<span class="btn-icon">👁️</span>
|
||||
View Invoice
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="sendInvoice()"
|
||||
:disabled="!canSendInvoice"
|
||||
>
|
||||
<span class="btn-icon">📤</span>
|
||||
{{ sendInvoiceButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -446,6 +466,26 @@ const filteredTasks = computed(() => {
|
||||
return job.value.tasks;
|
||||
});
|
||||
|
||||
const canCreateInvoice = computed(() => {
|
||||
const status = job.value?.invoiceStatus;
|
||||
return status === 'Ready to Invoice';
|
||||
});
|
||||
|
||||
const canViewInvoice = computed(() => {
|
||||
const status = job.value?.invoiceStatus;
|
||||
return status === 'Invoice Created' || status === 'Invoice Sent';
|
||||
});
|
||||
|
||||
const canSendInvoice = computed(() => {
|
||||
const status = job.value?.invoiceStatus;
|
||||
return status === 'Invoice Created' || status === 'Invoice Sent';
|
||||
});
|
||||
|
||||
const sendInvoiceButtonText = computed(() => {
|
||||
const status = job.value?.invoiceStatus;
|
||||
return status === 'Invoice Sent' ? 'Resend Invoice' : 'Send Invoice';
|
||||
});
|
||||
|
||||
const getProgressOffset = (percent) => {
|
||||
const circumference = 2 * Math.PI * 45;
|
||||
const offset = circumference - (percent / 100) * circumference;
|
||||
@ -537,6 +577,7 @@ const createInvoiceForJob = async () => {
|
||||
if (!job.value) return;
|
||||
try {
|
||||
await Api.createInvoiceForJob(job.value.name);
|
||||
job.value.invoiceStatus = "Invoice Created";
|
||||
notifications.addSuccess("Invoice created successfully");
|
||||
} catch (error) {
|
||||
console.error("Error creating invoice:", error);
|
||||
@ -544,6 +585,19 @@ const createInvoiceForJob = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const viewInvoice = () => {
|
||||
// Placeholder method for viewing invoice
|
||||
console.log("View Invoice clicked");
|
||||
notifications.addInfo("View Invoice functionality - Coming soon!");
|
||||
};
|
||||
|
||||
const sendInvoice = async () => {
|
||||
// Placeholder method for sending invoice
|
||||
await Api.sendInvoice(job.value.invoice.name)
|
||||
job.value.invoiceStatus = "Invoice Sent";
|
||||
console.log("Send Invoice clicked");
|
||||
};
|
||||
|
||||
const navigateToCalendar = () => {
|
||||
router.push('/calendar?tab=projects');
|
||||
};
|
||||
@ -771,8 +825,20 @@ onMounted(async () => {
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #f8f9fa;
|
||||
.btn:disabled {
|
||||
background: #e9ecef;
|
||||
color: #adb5bd;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn:disabled:hover {
|
||||
background: #e9ecef;
|
||||
color: #adb5bd;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
@ -989,24 +1055,24 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.financial-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 6px 12px;
|
||||
margin-top: 12px;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.financial-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
@ -1014,6 +1080,7 @@ onMounted(async () => {
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.item-value.paid {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user