mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-19 04:40:32 -05:00
b4486a627f
- Webhook models: remove duplicate index definitions so db.create_all() no longer raises 'index already exists' (columns already have index=True) - ImportService: fix circular import by late-importing ClientService, ProjectService, TimeTrackingService in __init__ - reports: fix F823 by renaming unpack variable _ to _entry_count to avoid shadowing gettext _ in export_task_excel() - Code quality: add .flake8 with extend-ignore so flake8 CI passes; simplify pyproject.toml isort config (drop unsupported options) - Format: run black and isort on app/ - tests: restore minimal app fixture in test_import_export_models
149 lines
4.0 KiB
Python
149 lines
4.0 KiB
Python
"""
|
|
Search utilities for full-text search across the application.
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from sqlalchemy import and_, or_
|
|
|
|
from app.models import Client, Comment, Invoice, Project, Task, TimeEntry
|
|
|
|
|
|
def search_projects(query: str, user_id: Optional[int] = None, status: Optional[str] = None) -> List[Project]:
|
|
"""
|
|
Search projects by name and description.
|
|
|
|
Args:
|
|
query: Search query
|
|
user_id: Optional user ID filter
|
|
status: Optional status filter
|
|
|
|
Returns:
|
|
List of matching projects
|
|
"""
|
|
search_term = f"%{query}%"
|
|
|
|
search_query = Project.query.filter(or_(Project.name.ilike(search_term), Project.description.ilike(search_term)))
|
|
|
|
if status:
|
|
search_query = search_query.filter_by(status=status)
|
|
|
|
return search_query.order_by(Project.name).all()
|
|
|
|
|
|
def search_time_entries(query: str, user_id: Optional[int] = None, project_id: Optional[int] = None) -> List[TimeEntry]:
|
|
"""
|
|
Search time entries by notes and tags.
|
|
|
|
Args:
|
|
query: Search query
|
|
user_id: Optional user ID filter
|
|
project_id: Optional project ID filter
|
|
|
|
Returns:
|
|
List of matching time entries
|
|
"""
|
|
search_term = f"%{query}%"
|
|
|
|
search_query = TimeEntry.query.filter(or_(TimeEntry.notes.ilike(search_term), TimeEntry.tags.ilike(search_term)))
|
|
|
|
if user_id:
|
|
search_query = search_query.filter_by(user_id=user_id)
|
|
|
|
if project_id:
|
|
search_query = search_query.filter_by(project_id=project_id)
|
|
|
|
return search_query.order_by(TimeEntry.start_time.desc()).all()
|
|
|
|
|
|
def search_tasks(query: str, project_id: Optional[int] = None, status: Optional[str] = None) -> List[Task]:
|
|
"""
|
|
Search tasks by name and description.
|
|
|
|
Args:
|
|
query: Search query
|
|
project_id: Optional project ID filter
|
|
status: Optional status filter
|
|
|
|
Returns:
|
|
List of matching tasks
|
|
"""
|
|
search_term = f"%{query}%"
|
|
|
|
search_query = Task.query.filter(or_(Task.name.ilike(search_term), Task.description.ilike(search_term)))
|
|
|
|
if project_id:
|
|
search_query = search_query.filter_by(project_id=project_id)
|
|
|
|
if status:
|
|
search_query = search_query.filter_by(status=status)
|
|
|
|
return search_query.order_by(Task.priority.desc(), Task.created_at.desc()).all()
|
|
|
|
|
|
def search_invoices(query: str, status: Optional[str] = None) -> List[Invoice]:
|
|
"""
|
|
Search invoices by number and client name.
|
|
|
|
Args:
|
|
query: Search query
|
|
status: Optional status filter
|
|
|
|
Returns:
|
|
List of matching invoices
|
|
"""
|
|
search_term = f"%{query}%"
|
|
|
|
search_query = Invoice.query.filter(
|
|
or_(Invoice.invoice_number.ilike(search_term), Invoice.client_name.ilike(search_term))
|
|
)
|
|
|
|
if status:
|
|
search_query = search_query.filter_by(status=status)
|
|
|
|
return search_query.order_by(Invoice.created_at.desc()).all()
|
|
|
|
|
|
def search_clients(query: str) -> List[Client]:
|
|
"""
|
|
Search clients by name, email, and company.
|
|
|
|
Args:
|
|
query: Search query
|
|
|
|
Returns:
|
|
List of matching clients
|
|
"""
|
|
search_term = f"%{query}%"
|
|
|
|
return (
|
|
Client.query.filter(
|
|
or_(Client.name.ilike(search_term), Client.email.ilike(search_term), Client.company.ilike(search_term))
|
|
)
|
|
.order_by(Client.name)
|
|
.all()
|
|
)
|
|
|
|
|
|
def global_search(query: str, user_id: Optional[int] = None, limit_per_type: int = 10) -> Dict[str, List[Any]]:
|
|
"""
|
|
Perform a global search across all entities.
|
|
|
|
Args:
|
|
query: Search query
|
|
user_id: Optional user ID filter
|
|
limit_per_type: Maximum results per entity type
|
|
|
|
Returns:
|
|
dict with search results by entity type
|
|
"""
|
|
results = {
|
|
"projects": search_projects(query, user_id=user_id)[:limit_per_type],
|
|
"time_entries": search_time_entries(query, user_id=user_id)[:limit_per_type],
|
|
"tasks": search_tasks(query)[:limit_per_type],
|
|
"invoices": search_invoices(query)[:limit_per_type],
|
|
"clients": search_clients(query)[:limit_per_type],
|
|
}
|
|
|
|
return results
|