update calendar functionality and holidays
This commit is contained in:
parent
0620060066
commit
7395d3e048
9 changed files with 859 additions and 440 deletions
49
custom_ui/api/db/employees.py
Normal file
49
custom_ui/api/db/employees.py
Normal 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)
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import frappe, json
|
import frappe, json
|
||||||
from frappe.utils.pdf import get_pdf
|
from frappe.utils.pdf import get_pdf
|
||||||
from custom_ui.api.db.general import get_doc_history
|
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 werkzeug.wrappers import Response
|
||||||
from custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer
|
from custom_ui.api.db.clients import check_if_customer, convert_lead_to_customer
|
||||||
from custom_ui.services import DbService, ClientService, AddressService, ContactService
|
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
|
# 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()
|
@frappe.whitelist()
|
||||||
def get_estimate_table_data(filters={}, sortings=[], page=1, page_size=10):
|
def get_estimate_table_data(filters={}, sortings=[], page=1, page_size=10):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import frappe
|
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):
|
def get_doc_history(doctype, docname):
|
||||||
"""Get the history of changes for a specific document."""
|
"""Get the history of changes for a specific document."""
|
||||||
|
|
@ -56,4 +57,23 @@ def search_any_field(doctype, text):
|
||||||
query,
|
query,
|
||||||
[like] * len(conditions),
|
[like] * len(conditions),
|
||||||
as_dict=True
|
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)
|
||||||
|
|
@ -233,3 +233,16 @@ def build_history_entries(comments, versions):
|
||||||
def normalize_name(name: str, split_target: str = "_") -> str:
|
def normalize_name(name: str, split_target: str = "_") -> str:
|
||||||
"""Normalize a name by splitting off anything after and including the split_target."""
|
"""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
|
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
|
||||||
|
|
@ -26,6 +26,10 @@ add_to_apps_screen = [
|
||||||
# "has_permission": "custom_ui.api.permission.has_app_permission"
|
# "has_permission": "custom_ui.api.permission.has_app_permission"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
requires = [
|
||||||
|
"holidays==0.89"
|
||||||
|
]
|
||||||
# Apps
|
# Apps
|
||||||
# ------------------
|
# ------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import frappe
|
import frappe
|
||||||
from .utils import create_module
|
from .utils import create_module
|
||||||
|
import holidays
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
def after_install():
|
def after_install():
|
||||||
create_module()
|
create_module()
|
||||||
|
|
@ -29,6 +31,8 @@ def after_migrate():
|
||||||
for doctype in doctypes_to_refresh:
|
for doctype in doctypes_to_refresh:
|
||||||
frappe.clear_cache(doctype=doctype)
|
frappe.clear_cache(doctype=doctype)
|
||||||
frappe.reload_doctype(doctype)
|
frappe.reload_doctype(doctype)
|
||||||
|
|
||||||
|
check_and_create_holiday_list()
|
||||||
|
|
||||||
# update_address_fields()
|
# update_address_fields()
|
||||||
# build_frontend()
|
# build_frontend()
|
||||||
|
|
@ -951,4 +955,54 @@ def build_missing_field_specs(custom_fields, missing_fields):
|
||||||
missing_field_specs[doctype].append(field_spec)
|
missing_field_specs[doctype].append(field_spec)
|
||||||
break
|
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
|
||||||
|
|
@ -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_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_METHOD = "custom_ui.api.db.clients.get_client_v2";
|
||||||
const FRAPPE_GET_CLIENT_NAMES_METHOD = "custom_ui.api.db.clients.get_client_names";
|
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 {
|
class Api {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CORE REQUEST METHOPD
|
// CORE REQUEST METHOPD
|
||||||
|
|
@ -589,6 +593,26 @@ class Api {
|
||||||
return data;
|
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
|
// GENERIC DOCTYPE METHODS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="calendar-navigation">
|
<div class="calendar-navigation">
|
||||||
<Tabs value="0">
|
<Tabs value="0" v-if="companyStore.currentCompany == 'Sprinklers Northwest'">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value="0">Bids</Tab>
|
<Tab value="0">Bids</Tab>
|
||||||
<Tab value="1">Projects</Tab>
|
<Tab value="1">Projects</Tab>
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
<TabPanel header="Bids" value="0">
|
<TabPanel header="Bids" value="0">
|
||||||
<ScheduleBid />
|
<ScheduleBid />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel header="Install" value="1">
|
<TabPanel header="Projects" value="1">
|
||||||
<InstallsCalendar />
|
<SNWProjectCalendar />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel header="Service" value="2">
|
<TabPanel header="Service" value="2">
|
||||||
<div class="coming-soon">
|
<div class="coming-soon">
|
||||||
|
|
@ -26,6 +26,9 @@
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
<div v-else class="coming-soon">
|
||||||
|
<p>Calendar feature coming soon!</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -38,10 +41,12 @@ import TabPanel from 'primevue/tabpanel';
|
||||||
import TabPanels from 'primevue/tabpanels';
|
import TabPanels from 'primevue/tabpanels';
|
||||||
import ScheduleBid from '../calendar/bids/ScheduleBid.vue';
|
import ScheduleBid from '../calendar/bids/ScheduleBid.vue';
|
||||||
import JobsCalendar from '../calendar/jobs/JobsCalendar.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 { useNotificationStore } from '../../stores/notifications-primevue';
|
||||||
|
import { useCompanyStore } from '../../stores/company';
|
||||||
|
|
||||||
const notifications = useNotificationStore();
|
const notifications = useNotificationStore();
|
||||||
|
const companyStore = useCompanyStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue