update calendar functionality and holidays

This commit is contained in:
Casey 2026-01-20 17:06:28 -06:00
parent 0620060066
commit 7395d3e048
9 changed files with 859 additions and 440 deletions

View File

@ -0,0 +1,49 @@
import frappe, json
from custom_ui.db_utils import build_success_response, build_error_response
# ===============================================================================
# EMPLOYEE API METHODS
# ===============================================================================
@frappe.whitelist()
def get_employees(company: str, roles=[]):
"""Get a list of employees for a given company. Can be filtered by role."""
roles = json.loads(roles) if isinstance(roles, str) else roles
filters = {"company": company}
if roles:
filters["designation"] = ["in", roles]
try:
employee_names = frappe.get_all(
"Employee",
filters=filters,
pluck="name"
)
employees = [frappe.get_doc("Employee", name).as_dict() for name in employee_names]
return build_success_response(employees)
except Exception as e:
return build_error_response(str(e), 500)
@frappe.whitelist()
def get_employees_organized(company: str, roles=[]):
"""Get all employees for a company organized by designation."""
roles = json.loads(roles) if isinstance(roles, str) else roles
try:
filters = {"company": company}
if roles:
filters["designation"] = ["in", roles]
employee_names = frappe.get_all(
"Employee",
filters=filters,
pluck="name"
)
employees = [frappe.get_doc("Employee", name).as_dict() for name in employee_names]
organized = {}
for emp in employees:
designation = emp.get("designation", "Unassigned")
if designation not in organized:
organized[designation] = []
organized[designation].append(emp)
return build_success_response(organized)
except Exception as e:
return build_error_response(str(e), 500)

View File

@ -1,7 +1,7 @@
import frappe, json
from frappe.utils.pdf import get_pdf
from custom_ui.api.db.general import get_doc_history
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 DbUtils, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
from werkzeug.wrappers import Response
from custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer
from custom_ui.services import DbService, ClientService, AddressService, ContactService
@ -10,6 +10,12 @@ from custom_ui.services import DbService, ClientService, AddressService, Contact
# ESTIMATES & INVOICES API METHODS
# ===============================================================================
@frappe.whitelist()
def get_estimate_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
"""Get paginated estimate table data with filtering and sorting."""
print("DEBUG: Raw estimate options received:", filters, sortings, page, page_size)
filters, sortings, page, page_size = DbUtils.process_query_conditions(filters, sortings, page, page_size)
@frappe.whitelist()
def get_estimate_table_data(filters={}, sortings=[], page=1, page_size=10):

View File

@ -1,5 +1,6 @@
import frappe
from custom_ui.db_utils import build_history_entries
from custom_ui.db_utils import build_history_entries, build_success_response, build_error_response
from datetime import datetime, timedelta
def get_doc_history(doctype, docname):
"""Get the history of changes for a specific document."""
@ -56,4 +57,23 @@ def search_any_field(doctype, text):
query,
[like] * len(conditions),
as_dict=True
)
)
@frappe.whitelist()
def get_week_holidays(week_start_date: str):
"""Get holidays within a week starting from the given date."""
start_date = datetime.strptime(week_start_date, "%Y-%m-%d").date()
end_date = start_date + timedelta(days=6)
holidays = frappe.get_all(
"Holiday",
filters={
"holiday_date": ["between", (start_date, end_date)]
},
fields=["holiday_date", "description"],
order_by="holiday_date asc"
)
print(f"DEBUG: Retrieved holidays from {start_date} to {end_date}: {holidays}")
return build_success_response(holidays)

View File

@ -233,3 +233,16 @@ def build_history_entries(comments, versions):
def normalize_name(name: str, split_target: str = "_") -> str:
"""Normalize a name by splitting off anything after and including the split_target."""
return name.split(split_target)[0] if split_target in name else name
class DbUtils:
@staticmethod
def process_datatable_request(filters, sortings, page, page_size):
# turn filters and sortings from json strings to dicts/lists
if isinstance(filters, str):
filters = json.loads(filters)
if isinstance(sortings, str):
sortings = json.loads(sortings)
page = int(page)
page_size = int(page_size)
return filters, sortings,page, page_size

View File

@ -26,6 +26,10 @@ add_to_apps_screen = [
# "has_permission": "custom_ui.api.permission.has_app_permission"
}
]
requires = [
"holidays==0.89"
]
# Apps
# ------------------

View File

@ -4,6 +4,8 @@ import subprocess
import sys
import frappe
from .utils import create_module
import holidays
from datetime import date, timedelta
def after_install():
create_module()
@ -29,6 +31,8 @@ def after_migrate():
for doctype in doctypes_to_refresh:
frappe.clear_cache(doctype=doctype)
frappe.reload_doctype(doctype)
check_and_create_holiday_list()
# update_address_fields()
# build_frontend()
@ -951,4 +955,54 @@ def build_missing_field_specs(custom_fields, missing_fields):
missing_field_specs[doctype].append(field_spec)
break
return missing_field_specs
return missing_field_specs
def check_and_create_holiday_list(year=2026, country="US", weekly_off="Sunday"):
"""Check if Holiday List for the given year exists, if not create it."""
print(f"\n🔧 Checking for Holiday List for {country} in {year}...")
holiday_list_name = f"{country} Holidays {year}"
if frappe.db.exists("Holiday List", holiday_list_name):
print(f"✅ Holiday List '{holiday_list_name}' already exists.")
return
else:
print(f"❌ Holiday List '{holiday_list_name}' does not exist. Creating...")
us_holidays = holidays.US(years=[year])
sundays = get_all_sundays(year)
hl = frappe.get_doc({
"doctype": "Holiday List",
"holiday_list_name": holiday_list_name,
"country": country,
"year": year,
"from_date": f"{year}-01-01",
"to_date": f"{year}-12-31",
"weekly_off": weekly_off,
"holidays": [
{
"holiday_date": holiday_date,
"description": holiday_name
} for holiday_date, holiday_name in us_holidays.items()
]
})
for sunday in sundays:
hl.append("holidays", {
"holiday_date": sunday,
"description": "Sunday"
})
hl.insert()
# hl.make_holiday_entries()
frappe.db.commit()
print(f"✅ Holiday List '{holiday_list_name}' created successfully.")
def get_all_sundays(year):
sundays = []
d = date(year, 1, 1)
while d.weekday() != 6:
d += timedelta(days=1)
while d.year == year:
sundays.append(d)
d += timedelta(days=7)
return sundays

View File

@ -47,7 +47,11 @@ const FRAPPE_GET_CLIENT_TABLE_DATA_METHOD = "custom_ui.api.db.clients.get_client
const FRAPPE_GET_CLIENT_TABLE_DATA_V2_METHOD = "custom_ui.api.db.clients.get_clients_table_data_v2";
const FRAPPE_GET_CLIENT_METHOD = "custom_ui.api.db.clients.get_client_v2";
const FRAPPE_GET_CLIENT_NAMES_METHOD = "custom_ui.api.db.clients.get_client_names";
// Employee methods
const FRAPPE_GET_EMPLOYEES_METHOD = "custom_ui.api.db.employees.get_employees";
const FRAPPE_GET_EMPLOYEES_ORGANIZED_METHOD = "custom_ui.api.db.employees.get_employees_organized";
// Other methods
const FRAPPE_GET_WEEK_HOLIDAYS_METHOD = "custom_ui.api.db.general.get_week_holidays";
class Api {
// ============================================================================
// CORE REQUEST METHOPD
@ -589,6 +593,26 @@ class Api {
return data;
}
// ============================================================================
// EMPLOYEE METHODS
// ============================================================================
static async getEmployees(company, roles = []) {
return await this.request(FRAPPE_GET_EMPLOYEES_METHOD, { company, roles });
}
static async getEmployeesOrganized (company, roles = []) {
return await this.request(FRAPPE_GET_EMPLOYEES_ORGANIZED_METHOD, { company, roles });
}
// ============================================================================
// OTHER METHODS
// ============================================================================
static async getWeekHolidays(startDate) {
return await this.request(FRAPPE_GET_WEEK_HOLIDAYS_METHOD, { weekStartDate: startDate });
}
// ============================================================================
// GENERIC DOCTYPE METHODS
// ============================================================================

View File

@ -1,6 +1,6 @@
<template>
<div class="calendar-navigation">
<Tabs value="0">
<Tabs value="0" v-if="companyStore.currentCompany == 'Sprinklers Northwest'">
<TabList>
<Tab value="0">Bids</Tab>
<Tab value="1">Projects</Tab>
@ -11,8 +11,8 @@
<TabPanel header="Bids" value="0">
<ScheduleBid />
</TabPanel>
<TabPanel header="Install" value="1">
<InstallsCalendar />
<TabPanel header="Projects" value="1">
<SNWProjectCalendar />
</TabPanel>
<TabPanel header="Service" value="2">
<div class="coming-soon">
@ -26,6 +26,9 @@
</TabPanel>
</TabPanels>
</Tabs>
<div v-else class="coming-soon">
<p>Calendar feature coming soon!</p>
</div>
</div>
</template>
@ -38,10 +41,12 @@ import TabPanel from 'primevue/tabpanel';
import TabPanels from 'primevue/tabpanels';
import ScheduleBid from '../calendar/bids/ScheduleBid.vue';
import JobsCalendar from '../calendar/jobs/JobsCalendar.vue';
import InstallsCalendar from './jobs/ProjectsCalendar.vue';
import SNWProjectCalendar from './jobs/SNWProjectCalendar.vue';
import { useNotificationStore } from '../../stores/notifications-primevue';
import { useCompanyStore } from '../../stores/company';
const notifications = useNotificationStore();
const companyStore = useCompanyStore();
</script>
<style scoped>