Compare commits

..

11 commits

14 changed files with 420 additions and 156 deletions

View file

@ -481,6 +481,31 @@ def upsert_estimate(data):
print(f"DEBUG: Error in upsert_estimate: {str(e)}") print(f"DEBUG: Error in upsert_estimate: {str(e)}")
return build_error_response(str(e), 500) return build_error_response(str(e), 500)
@frappe.whitelist()
def get_unapproved_estimates_count(company):
"""Get the number of unapproved estimates."""
try:
draft_filters = {'status': "Draft", "company": company}
submitted_filters = {'status': "Submitted", "company": company}
draft_count = frappe.db.count("Quotation", filters=draft_filters)
submitted_count = frappe.db.count("Quotation", filters=submitted_filters)
return build_success_response([draft_count, submitted_count])
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_estimates_half_down_count(company):
"""Get the number unpaid half-down estimates."""
try:
filters = {'requires_half_payment': True, 'company': company}
count = frappe.db.count("Quotation", filters=filters)
return build_success_response([count])
except Exception as e:
return build_error_response(str(e), 500)
def get_estimate_history(estimate_name): def get_estimate_history(estimate_name):
"""Get the history of changes for a specific estimate.""" """Get the history of changes for a specific estimate."""
pass pass

View file

@ -1,11 +1,36 @@
import frappe, json 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 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
# =============================================================================== # ===============================================================================
# INVOICES API METHODS # INVOICES API METHODS
# =============================================================================== # ===============================================================================
@frappe.whitelist()
def create_invoice_for_job(job_name):
"""Create the invoice from a sales order of a job."""
try:
project = frappe.get_doc("Project", job_name)
sales_order = project.sales_order
invoice = make_sales_invoice(sales_order)
invoice.save()
return build_success_response(invoice.as_dict())
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_invoices_late_count():
"""Return Due, 30-day late, 90-day late, and Lien-worthy late accounts."""
try:
dummy_result = [10, 4, 5, 1]
print("DEBUG: DUMMY RESULT:", dummy_result)
return build_success_response(dummy_result)
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist() @frappe.whitelist()
def get_invoice_table_data(filters={}, sortings=[], page=1, page_size=10): def get_invoice_table_data(filters={}, sortings=[], page=1, page_size=10):
"""Get paginated invoice table data with filtering and sorting support.""" """Get paginated invoice table data with filtering and sorting support."""
@ -18,7 +43,7 @@ def get_invoice_table_data(filters={}, sortings=[], page=1, page_size=10):
else: else:
count = frappe.db.count("Sales Invoice", filters=processed_filters) count = frappe.db.count("Sales Invoice", filters=processed_filters)
print(f"DEBUG: Number of invoice returned: {count}") print(f"DEBUG: Number of invoices returned: {count}")
invoices = frappe.db.get_all( invoices = frappe.db.get_all(
"Sales Invoice", "Sales Invoice",

View file

@ -7,6 +7,64 @@ from frappe.utils import getdate
# JOB MANAGEMENT API METHODS # JOB MANAGEMENT API METHODS
# =============================================================================== # ===============================================================================
@frappe.whitelist()
def get_jobs_in_queue_count(company):
try:
filters = {
'company': company,
'is_scheduled': True,
}
count = frappe.db.count("Project", filters=filters)
return build_success_response([count])
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_jobs_in_progress_count(company):
try:
today = getdate()
filters = {
'company': company,
'invoice_status': 'Not Ready',
'expected_start_date': ['<=', today],
'expected_end_date': ['>=', today],
}
count = frappe.db.count("Project", filters=filters)
return build_success_response([count])
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_jobs_late_count(company):
try:
today = getdate()
filters = {
'company': company,
'invoice_status': 'Not Ready',
'expected_end_date': ['<', today]
}
count = frappe.db.count("Project", filters=filters)
return build_success_response([count])
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_jobs_to_invoice_count(company):
try:
filters = {
'company': company,
'invoice_status': 'Ready to Invoice',
}
count = frappe.db.count("Project", filters=filters)
return build_success_response([count])
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist() @frappe.whitelist()
def get_job_templates(company=None): def get_job_templates(company=None):
"""Get list of job (project) templates.""" """Get list of job (project) templates."""
@ -19,6 +77,7 @@ def get_job_templates(company=None):
except Exception as e: except Exception as e:
return build_error_response(str(e), 500) return build_error_response(str(e), 500)
@frappe.whitelist() @frappe.whitelist()
def create_job_from_sales_order(sales_order_name): def create_job_from_sales_order(sales_order_name):
"""Create a Job (Project) from a given Sales Order""" """Create a Job (Project) from a given Sales Order"""
@ -152,9 +211,10 @@ def get_jobs_table_data(filters={}, sortings=[], page=1, page_size=10):
tableRow = {} tableRow = {}
tableRow["id"] = project["name"] tableRow["id"] = project["name"]
tableRow["name"] = project["name"] tableRow["name"] = project["name"]
tableRow["installation_address"] = project.get("custom_installation_address", "") tableRow["job_address"] = project["job_address"]
tableRow["customer"] = project.get("customer", "") tableRow["customer"] = project.get("customer", "")
tableRow["status"] = project.get("status", "") tableRow["status"] = project.get("status", "")
tableRow["invoice_status"] = project.get("invoice_status")
tableRow["percent_complete"] = project.get("percent_complete", 0) tableRow["percent_complete"] = project.get("percent_complete", 0)
tableRows.append(tableRow) tableRows.append(tableRow)

View file

@ -0,0 +1,15 @@
import frappe
from custom_ui.db_utils import build_success_response, build_error_response
@frappe.whitelist()
def get_incomplete_bids(company):
print("Getting Incomplete Bids")
try:
filters = {'status': "Unscheduled", 'company': company}
count = frappe.db.count("On-Site Meeting", filters=filters)
print("Incomplete Bids:", count)
return build_success_response([count])
except Exception as e:
return build_error_response(str(e), 500)

View file

@ -1,4 +1,5 @@
import frappe import frappe
import datetime
from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
from custom_ui.services import DbService from custom_ui.services import DbService
@ -46,23 +47,26 @@ def get_task_status_options():
def get_tasks_due(subject_filter, current_company): def get_tasks_due(subject_filter, current_company):
"""Return the number of items due today of the type of subject_filter""" """Return the number of items due today of the type of subject_filter"""
try: try:
today = datetime.date.today()
due_filters = { due_filters = {
'subject': ['like', f'%{subject_filter}%'], 'subject': ['like', f'%{subject_filter}%'],
'status': ['not in', ["Template", "Completed", "Cancelled"]], 'status': ['not in', ["Template", "Completed", "Cancelled"]],
'company': current_company, 'company': current_company,
'exp_end_date': today,
# Add due date filter here # Add due date filter here
} }
completed_filters = { completed_filters = {
'subject': ['like', f'%{subject_filter}%'], 'subject': ['like', f'%{subject_filter}%'],
'status': ['not in', ["Template", "Cancelled"]], 'status': ['not in', ["Template", "Cancelled"]],
'company': current_company, 'company': current_company,
'exp_end_date': today,
# Add due date filter here # Add due date filter here
} }
overdue_filters = { overdue_filters = {
'subject': ['like', f'%{subject_filter}%'], 'subject': ['like', f'%{subject_filter}%'],
'status': ['not in', ["Template", "Completed", "Cancelled"]], 'status': ['not in', ["Template", "Completed", "Cancelled"]],
'company': current_company, 'company': current_company,
'exp_end_date': ["<", today]
# Add overdue date filtering here # Add overdue date filtering here
} }
due_count = frappe.db.count("Task", filters=due_filters) due_count = frappe.db.count("Task", filters=due_filters)

View file

@ -178,8 +178,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:23.661458", "modified": "2026-01-24 11:42:38.816045",
"module": "Custom UI", "module": "Custom UI",
"name": "Customer Task Link", "name": "Customer Task Link",
"naming_rule": "", "naming_rule": "",
@ -396,8 +396,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:23.730085", "modified": "2026-01-24 11:42:38.886150",
"module": "Custom UI", "module": "Custom UI",
"name": "Address Task Link", "name": "Address Task Link",
"naming_rule": "", "naming_rule": "",
@ -550,8 +550,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:23.799282", "modified": "2026-01-24 11:42:38.953132",
"module": "Custom", "module": "Custom",
"name": "Lead Companies Link", "name": "Lead Companies Link",
"naming_rule": "", "naming_rule": "",
@ -768,8 +768,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:23.867486", "modified": "2026-01-24 11:42:39.023393",
"module": "Custom", "module": "Custom",
"name": "Address Project Link", "name": "Address Project Link",
"naming_rule": "", "naming_rule": "",
@ -986,8 +986,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:23.934979", "modified": "2026-01-24 11:42:39.093123",
"module": "Custom", "module": "Custom",
"name": "Address Quotation Link", "name": "Address Quotation Link",
"naming_rule": "", "naming_rule": "",
@ -1204,8 +1204,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.001835", "modified": "2026-01-24 11:42:39.163081",
"module": "Custom", "module": "Custom",
"name": "Address On-Site Meeting Link", "name": "Address On-Site Meeting Link",
"naming_rule": "", "naming_rule": "",
@ -1422,8 +1422,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.069255", "modified": "2026-01-24 11:42:39.236784",
"module": "Custom", "module": "Custom",
"name": "Address Sales Order Link", "name": "Address Sales Order Link",
"naming_rule": "", "naming_rule": "",
@ -1576,8 +1576,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.137117", "modified": "2026-01-24 11:42:39.305461",
"module": "Custom", "module": "Custom",
"name": "Contact Address Link", "name": "Contact Address Link",
"naming_rule": "", "naming_rule": "",
@ -1730,8 +1730,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.201857", "modified": "2026-01-24 11:42:39.376523",
"module": "Custom", "module": "Custom",
"name": "Lead On-Site Meeting Link", "name": "Lead On-Site Meeting Link",
"naming_rule": "", "naming_rule": "",
@ -2332,8 +2332,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.288443", "modified": "2026-01-24 11:42:39.472141",
"module": "Selling", "module": "Selling",
"name": "Quotation Template", "name": "Quotation Template",
"naming_rule": "", "naming_rule": "",
@ -2830,8 +2830,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.372116", "modified": "2026-01-24 11:42:39.556506",
"module": "Selling", "module": "Selling",
"name": "Quotation Template Item", "name": "Quotation Template Item",
"naming_rule": "", "naming_rule": "",
@ -2984,8 +2984,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.439961", "modified": "2026-01-24 11:42:39.624602",
"module": "Custom UI", "module": "Custom UI",
"name": "Customer Company Link", "name": "Customer Company Link",
"naming_rule": "", "naming_rule": "",
@ -3138,8 +3138,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.506463", "modified": "2026-01-24 11:42:39.690715",
"module": "Custom UI", "module": "Custom UI",
"name": "Customer Address Link", "name": "Customer Address Link",
"naming_rule": "", "naming_rule": "",
@ -3292,8 +3292,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.575135", "modified": "2026-01-24 11:42:39.758626",
"module": "Custom UI", "module": "Custom UI",
"name": "Customer Contact Link", "name": "Customer Contact Link",
"naming_rule": "", "naming_rule": "",
@ -3446,8 +3446,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.644548", "modified": "2026-01-24 11:42:39.825642",
"module": "Custom", "module": "Custom",
"name": "Address Contact Link", "name": "Address Contact Link",
"naming_rule": "", "naming_rule": "",
@ -3600,8 +3600,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.712407", "modified": "2026-01-24 11:42:39.896330",
"module": "Custom", "module": "Custom",
"name": "Customer On-Site Meeting Link", "name": "Customer On-Site Meeting Link",
"naming_rule": "", "naming_rule": "",
@ -3754,8 +3754,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.786735", "modified": "2026-01-24 11:42:39.967073",
"module": "Custom", "module": "Custom",
"name": "Customer Project Link", "name": "Customer Project Link",
"naming_rule": "", "naming_rule": "",
@ -3908,8 +3908,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.856324", "modified": "2026-01-24 11:42:40.037175",
"module": "Custom", "module": "Custom",
"name": "Customer Quotation Link", "name": "Customer Quotation Link",
"naming_rule": "", "naming_rule": "",
@ -4062,8 +4062,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.924254", "modified": "2026-01-24 11:42:40.106626",
"module": "Custom", "module": "Custom",
"name": "Customer Sales Order Link", "name": "Customer Sales Order Link",
"naming_rule": "", "naming_rule": "",
@ -4216,8 +4216,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:24.990458", "modified": "2026-01-24 11:42:40.174670",
"module": "Custom", "module": "Custom",
"name": "Lead Address Link", "name": "Lead Address Link",
"naming_rule": "", "naming_rule": "",
@ -4370,8 +4370,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:25.058368", "modified": "2026-01-24 11:42:40.246628",
"module": "Custom", "module": "Custom",
"name": "Lead Contact Link", "name": "Lead Contact Link",
"naming_rule": "", "naming_rule": "",
@ -4524,8 +4524,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:25.124946", "modified": "2026-01-24 11:42:40.318639",
"module": "Custom", "module": "Custom",
"name": "Lead Quotation Link", "name": "Lead Quotation Link",
"naming_rule": "", "naming_rule": "",
@ -4678,8 +4678,8 @@
"make_attachments_public": 0, "make_attachments_public": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": null, "menu_index": null,
"migration_hash": "5a00edceb2f575ccfc93ed99c01bc0b7", "migration_hash": "c40d7d206c7d4601cdb57fde7ab57c12",
"modified": "2026-01-22 11:05:25.193024", "modified": "2026-01-24 11:42:40.390663",
"module": "Custom", "module": "Custom",
"name": "Address Company Link", "name": "Address Company Link",
"naming_rule": "", "naming_rule": "",

View file

@ -588,7 +588,15 @@ def add_custom_fields():
fieldtype="Check", fieldtype="Check",
default=0, default=0,
insert_after="expected_end_time" 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": [ "Project Template": [
dict( dict(

View file

@ -5,6 +5,8 @@ import { useErrorStore } from "./stores/errors";
const ZIPPOPOTAMUS_BASE_URL = "https://api.zippopotam.us/us"; const ZIPPOPOTAMUS_BASE_URL = "https://api.zippopotam.us/us";
// Proxy method for external API calls // Proxy method for external API calls
const FRAPPE_PROXY_METHOD = "custom_ui.api.proxy.request"; const FRAPPE_PROXY_METHOD = "custom_ui.api.proxy.request";
// On-Site Meeting methods
const FRAPPE_GET_INCOMPLETE_BIDS_METHOD = "custom_ui.api.db.on_site_meetings.get_incomplete_bids";
// Estimate methods // Estimate methods
const FRAPPE_UPSERT_ESTIMATE_METHOD = "custom_ui.api.db.estimates.upsert_estimate"; const FRAPPE_UPSERT_ESTIMATE_METHOD = "custom_ui.api.db.estimates.upsert_estimate";
const FRAPPE_GET_ESTIMATES_METHOD = "custom_ui.api.db.estimates.get_estimate_table_data"; const FRAPPE_GET_ESTIMATES_METHOD = "custom_ui.api.db.estimates.get_estimate_table_data";
@ -14,7 +16,7 @@ const FRAPPE_LOCK_ESTIMATE_METHOD = "custom_ui.api.db.estimates.lock_estimate";
const FRAPPE_ESTIMATE_UPDATE_RESPONSE_METHOD = "custom_ui.api.db.estimates.manual_response"; const FRAPPE_ESTIMATE_UPDATE_RESPONSE_METHOD = "custom_ui.api.db.estimates.manual_response";
const FRAPPE_GET_ESTIMATE_TEMPLATES_METHOD = "custom_ui.api.db.estimates.get_estimate_templates"; const FRAPPE_GET_ESTIMATE_TEMPLATES_METHOD = "custom_ui.api.db.estimates.get_estimate_templates";
const FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD = "custom_ui.api.db.estimates.create_estimate_template"; const FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD = "custom_ui.api.db.estimates.create_estimate_template";
const FRAPPE_GET_UNAPPROVED_ESTIMATES_COUNT_METHOD = "custom_ui.api.db.estimates.get_unnaproved_estimates_count"; const FRAPPE_GET_UNAPPROVED_ESTIMATES_COUNT_METHOD = "custom_ui.api.db.estimates.get_unapproved_estimates_count";
const FRAPPE_GET_ESTIMATES_HALF_DOWN_COUNT_METHOD = "custom_ui.api.db.estimates.get_estimates_half_down_count"; const FRAPPE_GET_ESTIMATES_HALF_DOWN_COUNT_METHOD = "custom_ui.api.db.estimates.get_estimates_half_down_count";
// Job methods // Job methods
const FRAPPE_GET_JOB_METHOD = "custom_ui.api.db.jobs.get_job"; const FRAPPE_GET_JOB_METHOD = "custom_ui.api.db.jobs.get_job";
@ -26,6 +28,10 @@ const FRAPPE_GET_INSTALL_PROJECTS_METHOD = "custom_ui.api.db.jobs.get_install_pr
const FRAPPE_GET_JOBS_FOR_CALENDAR_METHOD = "custom_ui.api.db.jobs.get_projects_for_calendar"; const FRAPPE_GET_JOBS_FOR_CALENDAR_METHOD = "custom_ui.api.db.jobs.get_projects_for_calendar";
const FRAPPE_GET_JOB_TEMPLATES_METHOD = "custom_ui.api.db.jobs.get_job_templates"; const FRAPPE_GET_JOB_TEMPLATES_METHOD = "custom_ui.api.db.jobs.get_job_templates";
const FRAPPE_UPDATE_JOB_SCHEDULED_DATES_METHOD = "custom_ui.api.db.jobs.update_job_scheduled_dates"; const FRAPPE_UPDATE_JOB_SCHEDULED_DATES_METHOD = "custom_ui.api.db.jobs.update_job_scheduled_dates";
const FRAPPE_GET_JOBS_IN_QUEUE_METHOD = "custom_ui.api.db.jobs.get_jobs_in_queue_count";
const FRAPPE_GET_JOBS_IN_PROGRESS_METHOD = "custom_ui.api.db.jobs.get_jobs_in_progress_count";
const FRAPPE_GET_JOBS_LATE_METHOD = "custom_ui.api.db.jobs.get_jobs_late_count";
const FRAPPE_GET_JOBS_TO_INVOICE_METHOD = "custom_ui.api.db.jobs.get_jobs_to_invoice_count";
// Task methods // Task methods
const FRAPPE_GET_TASKS_METHOD = "custom_ui.api.db.tasks.get_tasks_table_data"; const FRAPPE_GET_TASKS_METHOD = "custom_ui.api.db.tasks.get_tasks_table_data";
const FRAPPE_GET_TASKS_STATUS_OPTIONS = "custom_ui.api.db.tasks.get_task_status_options"; const FRAPPE_GET_TASKS_STATUS_OPTIONS = "custom_ui.api.db.tasks.get_task_status_options";
@ -34,6 +40,8 @@ const FRAPPE_GET_TASKS_DUE_METHOD = "custom_ui.api.db.tasks.get_tasks_due";
// Invoice methods // Invoice methods
const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data"; const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data";
const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice"; 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";
// Warranty methods // Warranty methods
const FRAPPE_GET_WARRANTY_CLAIMS_METHOD = "custom_ui.api.db.warranties.get_warranty_claims"; const FRAPPE_GET_WARRANTY_CLAIMS_METHOD = "custom_ui.api.db.warranties.get_warranty_claims";
// On-Site Meeting methods // On-Site Meeting methods
@ -240,6 +248,11 @@ class Api {
return result; return result;
} }
static async getIncompleteBidsCount(currentCompany) {
const result = await this.request(FRAPPE_GET_INCOMPLETE_BIDS_METHOD, { company: currentCompany });
return result;
}
static async createEstimate(estimateData) { static async createEstimate(estimateData) {
const result = await this.request(FRAPPE_UPSERT_ESTIMATE_METHOD, { data: estimateData }); const result = await this.request(FRAPPE_UPSERT_ESTIMATE_METHOD, { data: estimateData });
return result; return result;
@ -265,12 +278,12 @@ class Api {
return await this.request(FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD, { data }); return await this.request(FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD, { data });
} }
static async getUnapprovedEstimatesCount() { static async getUnapprovedEstimatesCount(currentCompany) {
return await this.request(FRAPPE_GET_UNAPPROVED_ESTIMATES_COUNT_METHOD, {}); return await this.request(FRAPPE_GET_UNAPPROVED_ESTIMATES_COUNT_METHOD, {company: currentCompany});
} }
static async getEstimatesHalfDownCount() { static async getEstimatesHalfDownCount(currentCompany) {
return await this.request(FRAPPE_GET_ESTIMATES_HALF_DOWN_COUNT_METHOD, {}); return await this.request(FRAPPE_GET_ESTIMATES_HALF_DOWN_COUNT_METHOD, {company: currentCompany});
} }
// ============================================================================ // ============================================================================
@ -342,6 +355,26 @@ class Api {
}); });
} }
static async getJobsInQueueCount(currentCompany) {
return await this.request(FRAPPE_GET_JOBS_IN_QUEUE_METHOD, {company: currentCompany});
}
static async getJobsInProgressCount(currentCompany) {
return await this.request(FRAPPE_GET_JOBS_IN_PROGRESS_METHOD, {company: currentCompany});
}
static async getJobsLateCount(currentCompany) {
return await this.request(FRAPPE_GET_JOBS_LATE_METHOD, {company: currentCompany});
}
static async getJobsToInvoiceCount(currentCompany) {
return await this.request(FRAPPE_GET_JOBS_TO_INVOICE_METHOD, {company: currentCompany});
}
static async setJobCompleted(jobName) {
return await this.request(FRAPPE_SET_JOB_COMPLETE_METHOD, {jobName: jobName});
}
static async getJob(jobName) { static async getJob(jobName) {
if (frappe.db.exists("Project", jobName)) { if (frappe.db.exists("Project", jobName)) {
const result = await this.request(FRAPPE_GET_JOB_METHOD, { jobId: jobName }) const result = await this.request(FRAPPE_GET_JOB_METHOD, { jobId: jobName })
@ -491,6 +524,16 @@ class Api {
return result; return result;
} }
static async getInvoicesLateCount(currentCompany) {
const result = await this.request(FRAPPE_GET_INVOICES_LATE_METHOD, { company: currentCompany });
return result;
}
static async createInvoiceForJob(jobName) {
const result = await this.request(FRAPPE_CREATE_INVOICE_FOR_JOB, { jobName: jobName });
return result;
}
// ============================================================================ // ============================================================================
// WARRANTY METHODS // WARRANTY METHODS
// ============================================================================ // ============================================================================

View file

@ -36,9 +36,6 @@ const props = defineProps({
}, },
}); });
//Constants
//const categories = ["To-do", "Completed"];
//Reactive data //Reactive data
const centerData = ref(null); const centerData = ref(null);
const hoveredSegment = ref(null); const hoveredSegment = ref(null);
@ -113,7 +110,7 @@ const updateCenterData = () => {
}; };
} else { } else {
centerData.value = { centerData.value = {
label: "To-do", label: props.categories.labels[0],
value: todos, value: todos,
percentage: null, percentage: null,
}; };

View file

@ -15,8 +15,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Incomplete Bids" title="Incomplete Bids"
:todoNumber="bidsTodoNumber" :categories="chartData.bids"
:completedNumber="bidsCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -38,8 +37,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Unapproved Estimates" title="Unapproved Estimates"
:todoNumber="estimatesTodoNumber" :categories="chartData.estimates"
:completedNumber="estimatesCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -61,8 +59,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Half Down Payments" title="Half Down Payments"
:todoNumber="halfDownTodoNumber" :categories="chartData.halfDown"
:completedNumber="halfDownCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -84,8 +81,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Jobs In Queue" title="Jobs In Queue"
:todoNumber="jobQueueTodoNumber" :categories="chartData.jobsInQueue"
:completedNumber="jobQueueCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -132,22 +128,30 @@ import Card from "../common/Card.vue";
import DataTable from "../common/DataTable.vue"; import DataTable from "../common/DataTable.vue";
import TodoChart from "../common/TodoChart.vue"; import TodoChart from "../common/TodoChart.vue";
import { Edit, ChatBubbleQuestion, CreditCard, Hammer } from "@iconoir/vue"; import { Edit, ChatBubbleQuestion, CreditCard, Hammer } from "@iconoir/vue";
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import Api from "../../api"; import Api from "../../api";
import { useLoadingStore } from "../../stores/loading"; import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination"; import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters"; import { useFiltersStore } from "../../stores/filters";
import { useCompanyStore } from "../../stores/company.js";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
const loadingStore = useLoadingStore(); const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore(); const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore(); const filtersStore = useFiltersStore();
const companyStore = useCompanyStore();
const router = useRouter(); const router = useRouter();
const tableData = ref([]); const tableData = ref([]);
const totalRecords = ref(0); const totalRecords = ref(0);
const isLoading = ref(false); const isLoading = ref(false);
const showSubmitEstimateModal = ref(true); const showSubmitEstimateModal = ref(true);
const chartData = ref({
bids: {labels: ["Unscheduled"], data: [0], colors: ['red']},
estimates: {labels: ["Draft", "Submitted"], data: [0, 0], colors: ['orange', 'blue']},
halfDown: {labels: ["Unpaid"], data: [0], colors: ['red']},
jobsInQueue: {labels: ["Queued"], data: [0], colors: ['blue']}
});
//Junk //Junk
const filteredItems= [] const filteredItems= []
@ -277,6 +281,13 @@ const handleLazyLoad = async (event) => {
} }
}; };
const loadChartData = async () => {
chartData.value.bids.data = await Api.getIncompleteBidsCount(companyStore.currentCompany);
chartData.value.estimates.data = await Api.getUnapprovedEstimatesCount(companyStore.currentCompany);
chartData.value.halfDown.data = await Api.getEstimatesHalfDownCount(companyStore.currentCompany);
chartData.value.jobsInQueue.data = await Api.getJobsInQueueCount(companyStore.currentCompany);
};
// Load initial data // Load initial data
onMounted(async () => { onMounted(async () => {
// Initialize pagination and filters // Initialize pagination and filters
@ -297,6 +308,13 @@ onMounted(async () => {
sortOrder: initialSorting.order || initialPagination.sortOrder, sortOrder: initialSorting.order || initialPagination.sortOrder,
filters: initialFilters, filters: initialFilters,
}); });
// Chart Data
await loadChartData();
});
watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => {
await loadChartData();
}); });
</script> </script>

View file

@ -350,14 +350,14 @@ const chartData = ref({
hydroseed: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, hydroseed: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
permitFinalizations: {labels: ["Todo", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, permitFinalizations: {labels: ["Todo", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
warranties: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, warranties: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
bids: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
estimates: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
halfDown: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
fifteenDayFollowups: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, fifteenDayFollowups: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
lateBalances: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, lateBalances: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
backflows: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, backflows: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
machines: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, machines: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
deliveries: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors}, deliveries: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
bids: {labels: ["Unscheduled"], data: [0], colors: ['red']},
estimates: {labels: ["Draft", "Submitted"], data: [0, 0], colors: ['orange', 'blue']},
halfDown: {labels: ["Unpaid"], data: [0], colors: ['red']},
}); });
const notifications = useNotificationStore(); const notifications = useNotificationStore();
@ -376,11 +376,17 @@ const navigateTo = (path) => {
const loadChartData = async() => { const loadChartData = async() => {
chartData.value.locates.data = await Api.getTasksDue("Locate", companyStore.currentCompany); chartData.value.locates.data = await Api.getTasksDue("Locate", companyStore.currentCompany);
chartData.value.permits.data = await Api.getTasksDue("Permit", companyStore.currentCompany); chartData.value.permits.data = await Api.getTasksDue("Permit(s)", companyStore.currentCompany);
chartData.value.curbing.data = await Api.getTasksDue("Curbing", companyStore.currentCompany); chartData.value.curbing.data = await Api.getTasksDue("Curbing", companyStore.currentCompany);
chartData.value.hydroseed.data = await Api.getTasksDue("Hydroseed", companyStore.currentCompany); chartData.value.hydroseed.data = await Api.getTasksDue("Hydroseed", companyStore.currentCompany);
chartData.value.permitFinalizations.data = await Api.getTasksDue("Permit Close-out", companyStore.currentCompany);
chartData.value.warranties.data = await Api.getTasksDue("Warranty", companyStore.currentCompany);
chartData.value.fifteenDayFollowups.data = await Api.getTasksDue("15-Day QA", companyStore.currentCompany);
chartData.value.backflows.data = await Api.getTasksDue("Backflow", companyStore.currentCompany);
//Uncomment below when we can check if half-down payments have/can been paid //Uncomment below when we can check if half-down payments have/can been paid
//chartData.value.estimates.data = await Api.getEstimatesHalfDownCount(); //chartData.value.estimates.data = await Api.getEstimatesHalfDownCount();
chartData.value.bids.data = await Api.getIncompleteBidsCount(companyStore.currentCompany);
chartData.value.estimates.data = await Api.getUnapprovedEstimatesCount(companyStore.currentCompany);
}; };
onMounted(async() => { onMounted(async() => {
@ -389,7 +395,6 @@ onMounted(async() => {
}); });
watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => { watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => {
console.log("Wathcing for new Company");
await loadChartData(); await loadChartData();
}); });

View file

@ -15,8 +15,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Ready To Invoice" title="Ready To Invoice"
:todoNumber="invoiceTodoNumber" :categories="chartData.jobsToInvoice"
:completedNumber="invoiceCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -38,8 +37,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Late Balances" title="Late Balances"
:todoNumber="balancesTodoNumber" :categories="chartData.invoicesLate"
:completedNumber="balancesCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -65,21 +63,28 @@
import Card from "../common/Card.vue"; import Card from "../common/Card.vue";
import DataTable from "../common/DataTable.vue"; import DataTable from "../common/DataTable.vue";
import TodoChart from "../common/TodoChart.vue"; import TodoChart from "../common/TodoChart.vue";
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import Api from "../../api"; import Api from "../../api";
import { useLoadingStore } from "../../stores/loading"; import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination"; import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters"; import { useFiltersStore } from "../../stores/filters";
import { useCompanyStore } from "../../stores/company.js";
import { CardNoAccess, CalendarCheck } from "@iconoir/vue"; import { CardNoAccess, CalendarCheck } from "@iconoir/vue";
const loadingStore = useLoadingStore(); const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore(); const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore(); const filtersStore = useFiltersStore();
const companyStore = useCompanyStore();
const tableData = ref([]); const tableData = ref([]);
const totalRecords = ref(0); const totalRecords = ref(0);
const isLoading = ref(false); const isLoading = ref(false);
const chartData = ref({
jobsToInvoice: {labels: ["Ready To Invoice"], data: [0], colors: ['green']},
invoicesLate: {labels: ["Due", "30 Days", "60 Days", "80 Days"], data: [0, 0, 0, 0], colors: ["blue", "yellow", "orange", "red"]}
})
const columns = [ const columns = [
{ label: "Customer Address", fieldName: "address", type: "text", sortable: true }, { label: "Customer Address", fieldName: "address", type: "text", sortable: true },
{ label: "Customer", fieldName: "customer", type: "text", sortable: true, filterable: true }, { label: "Customer", fieldName: "customer", type: "text", sortable: true, filterable: true },
@ -168,6 +173,12 @@ const handleLazyLoad = async (event) => {
} }
}; };
// Load Chart Data
const loadChartData = async () => {
chartData.value.jobsToInvoice.data = await Api.getJobsToInvoiceCount(companyStore.currentCompany);
chartData.value.invoicesLate.data = await Api.getInvoicesLateCount(companyStore.currentCompany);
};
// Load initial data // Load initial data
onMounted(async () => { onMounted(async () => {
// Initialize pagination and filters // Initialize pagination and filters
@ -188,6 +199,12 @@ onMounted(async () => {
sortOrder: initialSorting.order || initialPagination.sortOrder, sortOrder: initialSorting.order || initialPagination.sortOrder,
filters: initialFilters, filters: initialFilters,
}); });
await loadChartData();
});
watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => {
await loadChartData();
}); });
</script> </script>

View file

@ -13,7 +13,7 @@
</template> </template>
<template #content> <template #content>
<div class="widget-content"> <div class="widget-content">
{{ job.customInstallationAddress || "" }} {{ job.jobAddress["fullAddress"] || "" }}
</div> </div>
</template> </template>
</Card> </Card>
@ -32,6 +32,26 @@
</template> </template>
</Card> </Card>
</div> </div>
<div class="job-info">
<Card>
<template #header>
<div class="widget-header">
<h3>Job Status</h3>
</div>
</template>
<template #content>
<div class="widget-content">
Job is {{ job.status }}.
<button
class="sidebar-button"
@click="createInvoiceForJob()"
>
Create Invoice
</button>
</div>
</template>
</Card>
</div>
</div> </div>
<div class="task-list"> <div class="task-list">
<DataTable <DataTable
@ -124,6 +144,11 @@ const tableActions = computed(() => [
}, },
]); ]);
const createInvoiceForJob = async () => {
console.log(job);
await Api.createInvoiceForJob(job.value.name);
}
const handleLazyLoad = async (event) => { const handleLazyLoad = async (event) => {
console.log("Task list on Job Page - handling lazy load:", event); console.log("Task list on Job Page - handling lazy load:", event);
try { try {

View file

@ -15,8 +15,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Jobs In Queue" title="Jobs In Queue"
:todoNumber="jobQueueTodoNumber" :categories="chartData.jobsInQueue"
:completedNumber="jobQueueCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -38,8 +37,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Jobs in Progress" title="Jobs in Progress"
:todoNumber="progressTodoNumber" :categories="chartData.jobsInProgress"
:completedNumber="progressCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -61,8 +59,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Late Jobs" title="Late Jobs"
:todoNumber="lateTodoNumber" :categories="chartData.jobsLate"
:completedNumber="lateCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -84,8 +81,7 @@
<div class="widget-content"> <div class="widget-content">
<TodoChart <TodoChart
title="Ready To Invoice" title="Ready To Invoice"
:todoNumber="invoiceTodoNumber" :categories="chartData.jobsToInvoice"
:completedNumber="invoiceCompletedNumber"
> >
</TodoChart> </TodoChart>
<button class="sidebar-button" <button class="sidebar-button"
@ -112,29 +108,39 @@
import Card from "../common/Card.vue"; import Card from "../common/Card.vue";
import DataTable from "../common/DataTable.vue"; import DataTable from "../common/DataTable.vue";
import TodoChart from "../common/TodoChart.vue"; import TodoChart from "../common/TodoChart.vue";
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import Api from "../../api"; import Api from "../../api";
import { useLoadingStore } from "../../stores/loading"; import { useLoadingStore } from "../../stores/loading";
import { usePaginationStore } from "../../stores/pagination"; import { usePaginationStore } from "../../stores/pagination";
import { useFiltersStore } from "../../stores/filters"; import { useFiltersStore } from "../../stores/filters";
import { useCompanyStore } from "../../stores/company.js";
import { useNotificationStore } from "../../stores/notifications-primevue"; import { useNotificationStore } from "../../stores/notifications-primevue";
import { Alarm, CalendarCheck, Hammer } from "@iconoir/vue"; import { Alarm, CalendarCheck, Hammer } from "@iconoir/vue";
const loadingStore = useLoadingStore(); const loadingStore = useLoadingStore();
const paginationStore = usePaginationStore(); const paginationStore = usePaginationStore();
const filtersStore = useFiltersStore(); const filtersStore = useFiltersStore();
const companyStore = useCompanyStore();
const notifications = useNotificationStore(); const notifications = useNotificationStore();
const tableData = ref([]); const tableData = ref([]);
const totalRecords = ref(0); const totalRecords = ref(0);
const isLoading = ref(false); const isLoading = ref(false);
const chartData = ref({
jobsInQueue: {labels: ["Queued"], data: [0], colors: ['blue']},
jobsInProgress: {labels: ["In Progress"], data: [0], colors: ['blue']},
jobsLate: {labels: ["Late"], data: [0], colors: ['red']},
jobsToInvoice: {labels: ["Ready To Invoice"], data: [0], colors: ['green']},
})
const columns = [ const columns = [
{ label: "Job ID", fieldName: "name", type: "text", sortable: true, filterable: true }, { label: "Job ID", fieldName: "name", type: "text", sortable: true, filterable: true },
{ label: "Address", fieldName: "customInstallationAddress", type: "text", sortable: true }, { label: "Address", fieldName: "jobAddress", type: "text", sortable: true },
{ label: "Customer", fieldName: "customer", type: "text", sortable: true, filterable: true }, { label: "Customer", fieldName: "customer", type: "text", sortable: true, filterable: true },
{ label: "Overall Status", fieldName: "status", type: "status", sortable: true }, { label: "Overall Status", fieldName: "status", type: "status", sortable: true },
{ label: "Invoice Status", fieldName: "invoiceStatus", type: "text", sortable: true },
{ label: "Progress", fieldName: "percentComplete", type: "text", sortable: true } { label: "Progress", fieldName: "percentComplete", type: "text", sortable: true }
]; ];
@ -260,6 +266,13 @@ const handleRowClick = (event) => {
router.push(`/job?name=${rowData.name}`); router.push(`/job?name=${rowData.name}`);
} }
const loadChartData = async () => {
chartData.value.jobsInQueue.data = await Api.getJobsInQueueCount(companyStore.currentCompany);
chartData.value.jobsInProgress.data = await Api.getJobsInProgressCount(companyStore.currentCompany);
chartData.value.jobsLate.data = await Api.getJobsLateCount(companyStore.currentCompany);
chartData.value.jobsToInvoice.data = await Api.getJobsToInvoiceCount(companyStore.currentCompany);
}
// Load initial data // Load initial data
onMounted(async () => { onMounted(async () => {
notifications.addWarning("Jobs page coming soon"); notifications.addWarning("Jobs page coming soon");
@ -280,7 +293,16 @@ onMounted(async () => {
sortField: initialSorting.field || initialPagination.sortField, sortField: initialSorting.field || initialPagination.sortField,
sortOrder: initialSorting.order || initialPagination.sortOrder, sortOrder: initialSorting.order || initialPagination.sortOrder,
}); });
// Chart Data
await loadChartData();
}); });
watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => {
await loadChartData();
});
</script> </script>
<style lang="css"> <style lang="css">
.widgets-grid { .widgets-grid {