mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-19 12:50:11 -05:00
ef9b66f5e7
Global search referenced Client.company, which is not a column on Client, so client matches failed at runtime. Legacy and v1 search, plus search_clients(), now filter on name, email, description, and contact_person; result descriptions use the same fields. Legacy /api/search returns count: 0 for queries shorter than two characters so responses stay consistent. OpenAPI info.version is taken from get_version_from_setup(), with a config fallback when the resolved version is unknown. get_version_from_setup() also honors TIMETRACKER_VERSION and APP_VERSION for CI and container builds. Client.__init__ accepts custom_fields. ClientService no longer passes status= into Client(), which the initializer does not support. Tests add HTTP route contract checks and OpenAPI version alignment, fix subcontractor search fixtures (Client/Task construction and v1 client fixture naming), and update related API integration tests.
107 lines
3.2 KiB
Python
107 lines
3.2 KiB
Python
"""
|
|
Service for client business logic.
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from app import db
|
|
from app.models import Client
|
|
from app.repositories import ClientRepository
|
|
from app.utils.db import safe_commit
|
|
|
|
|
|
class ClientService:
|
|
"""Service for client operations"""
|
|
|
|
def __init__(self):
|
|
self.client_repo = ClientRepository()
|
|
|
|
def get_by_id(self, client_id: int) -> Optional[Client]:
|
|
"""
|
|
Get a client by its ID.
|
|
|
|
Returns:
|
|
Client instance or None if not found
|
|
"""
|
|
return self.client_repo.get_by_id(client_id)
|
|
|
|
def get_by_name(self, name: str) -> Optional[Client]:
|
|
"""
|
|
Get a client by name.
|
|
|
|
Returns:
|
|
Client instance or None if not found
|
|
"""
|
|
return self.client_repo.get_by_name(name)
|
|
|
|
def create_client(
|
|
self,
|
|
name: str,
|
|
created_by: int,
|
|
email: Optional[str] = None,
|
|
company: Optional[str] = None,
|
|
phone: Optional[str] = None,
|
|
address: Optional[str] = None,
|
|
default_hourly_rate: Optional[Decimal] = None,
|
|
custom_fields: Optional[Dict[str, Any]] = None,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Create a new client.
|
|
|
|
Returns:
|
|
dict with 'success', 'message', and 'client' keys
|
|
"""
|
|
# Check for duplicate name
|
|
existing = self.client_repo.get_by_name(name)
|
|
if existing:
|
|
return {"success": False, "message": "A client with this name already exists", "error": "duplicate_client"}
|
|
|
|
# Create client
|
|
client = self.client_repo.create(
|
|
name=name,
|
|
email=email,
|
|
company=company,
|
|
phone=phone,
|
|
address=address,
|
|
default_hourly_rate=default_hourly_rate,
|
|
custom_fields=custom_fields,
|
|
)
|
|
|
|
if not safe_commit("create_client", {"name": name, "created_by": created_by}):
|
|
return {
|
|
"success": False,
|
|
"message": "Could not create client due to a database error",
|
|
"error": "database_error",
|
|
}
|
|
|
|
return {"success": True, "message": "Client created successfully", "client": client}
|
|
|
|
def update_client(self, client_id: int, user_id: int, **kwargs) -> Dict[str, Any]:
|
|
"""
|
|
Update a client.
|
|
|
|
Returns:
|
|
dict with 'success', 'message', and 'client' keys
|
|
"""
|
|
client = self.client_repo.get_by_id(client_id)
|
|
|
|
if not client:
|
|
return {"success": False, "message": "Client not found", "error": "not_found"}
|
|
|
|
# Update fields
|
|
self.client_repo.update(client, **kwargs)
|
|
|
|
if not safe_commit("update_client", {"client_id": client_id, "user_id": user_id}):
|
|
return {
|
|
"success": False,
|
|
"message": "Could not update client due to a database error",
|
|
"error": "database_error",
|
|
}
|
|
|
|
return {"success": True, "message": "Client updated successfully", "client": client}
|
|
|
|
def get_active_clients(self) -> List[Client]:
|
|
"""Get all active clients"""
|
|
return self.client_repo.get_active_clients()
|