bmc_hub/app/models/schemas.py
Christian adc4fb5876 Implement tests for sag module, add knowledge base templates, and enhance internet connection migrations
- Added multiple test cases for the sag module to ensure proper functionality and data handling.
- Created new templates for knowledge detail and knowledge index pages to display articles and solutions.
- Introduced migrations to enhance the internet connections schema, including new columns for manual sharing and SLA subscriptions.
- Added a script to reconcile known internet connections with verified data.
- Planned the implementation of a new website content administration module for managing customer references and operational status.
2026-08-30 14:34:43 +02:00

421 lines
12 KiB
Python

"""
Pydantic Models and Schemas
"""
from enum import Enum
from pydantic import BaseModel, ConfigDict
from typing import Optional, List
from datetime import datetime, date
class CustomerBase(BaseModel):
"""Base customer schema"""
name: str
email: Optional[str] = None
phone: Optional[str] = None
address: Optional[str] = None
class CustomerCreate(CustomerBase):
"""Schema for creating a customer"""
pass
class CustomerUpdate(BaseModel):
"""Schema for updating a customer"""
name: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
address: Optional[str] = None
cvr_number: Optional[str] = None
city: Optional[str] = None
postal_code: Optional[str] = None
country: Optional[str] = None
website: Optional[str] = None
mobile_phone: Optional[str] = None
invoice_email: Optional[str] = None
is_active: Optional[bool] = None
standard_margin_percent: Optional[float] = None
standard_hourly_rate: Optional[float] = None
special_freight_price: Optional[float] = None
supplier_service_enrolled: Optional[bool] = None
invoice_fee_amount: Optional[float] = None
class Customer(CustomerBase):
"""Full customer schema"""
id: int
created_at: str # Changed from datetime to str for serialization
updated_at: Optional[str] = None # Changed from datetime to str for serialization
model_config = ConfigDict(from_attributes=True)
class HardwareBase(BaseModel):
"""Base hardware schema"""
serial_number: str
model: str
customer_id: int
class HardwareCreate(HardwareBase):
"""Schema for creating hardware"""
pass
class Hardware(HardwareBase):
"""Full hardware schema"""
id: int
created_at: datetime
class Config:
from_attributes = True
class VendorBase(BaseModel):
"""Base vendor schema"""
name: str
cvr_number: Optional[str] = None
domain: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
address: Optional[str] = None
postal_code: Optional[str] = None
city: Optional[str] = None
website: Optional[str] = None
email_pattern: Optional[str] = None
contact_person: Optional[str] = None
category: Optional[str] = None
priority: Optional[int] = 100
notes: Optional[str] = None
is_active: bool = True
is_internet_provider: bool = False
class VendorCreate(VendorBase):
"""Schema for creating a vendor"""
pass
class VendorUpdate(BaseModel):
"""Schema for updating a vendor"""
name: Optional[str] = None
cvr_number: Optional[str] = None
domain: Optional[str] = None
email: Optional[str] = None
phone: Optional[str] = None
address: Optional[str] = None
postal_code: Optional[str] = None
city: Optional[str] = None
website: Optional[str] = None
economic_supplier_number: Optional[int] = None
contact_person: Optional[str] = None
category: Optional[str] = None
notes: Optional[str] = None
is_active: Optional[bool] = None
is_internet_provider: Optional[bool] = None
class Vendor(VendorBase):
"""Full vendor schema"""
id: int
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class ConversationBase(BaseModel):
title: str
transcript: Optional[str] = None
summary: Optional[str] = None
is_private: bool = False
customer_id: Optional[int] = None
ticket_id: Optional[int] = None
category: str = "General"
class ConversationCreate(ConversationBase):
audio_file_path: str
duration_seconds: int = 0
email_message_id: Optional[int] = None
class ConversationUpdate(BaseModel):
title: Optional[str] = None
is_private: Optional[bool] = None
ticket_id: Optional[int] = None
customer_id: Optional[int] = None
category: Optional[str] = None
# For soft delete via update if needed, though usually strict API endpoint
class Conversation(ConversationBase):
id: int
audio_file_path: str
duration_seconds: int
user_id: Optional[int] = None
source: str
created_at: datetime
updated_at: Optional[datetime] = None
deleted_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class SolutionBase(BaseModel):
"""Base schema for Case Solutions"""
title: str
description: Optional[str] = None
solution_type: Optional[str] = None # Support, Drift, Konsulent, etc.
result: Optional[str] = None # Løst, Delvist, Workaround, Ej løst
problem: Optional[str] = None
root_cause: Optional[str] = None
investigation: Optional[str] = None
workaround: Optional[str] = None
visibility: str = "internal"
approval_status: str = "draft"
is_final: bool = True
tags: list[str] = Field(default_factory=list)
products: list[str] = Field(default_factory=list)
class SolutionCreate(SolutionBase):
"""Schema for creating a solution"""
sag_id: int
created_by_user_id: Optional[int] = None
class SolutionUpdate(BaseModel):
"""Schema for updating a solution"""
title: Optional[str] = None
description: Optional[str] = None
solution_type: Optional[str] = None
result: Optional[str] = None
problem: Optional[str] = None
root_cause: Optional[str] = None
investigation: Optional[str] = None
workaround: Optional[str] = None
visibility: Optional[str] = None
approval_status: Optional[str] = None
is_final: Optional[bool] = None
tags: Optional[list[str]] = None
products: Optional[list[str]] = None
change_note: Optional[str] = None
class Solution(SolutionBase):
"""Full solution schema"""
id: int
sag_id: int
created_by_user_id: Optional[int] = None
created_at: datetime
updated_at: Optional[datetime] = None
updated_by_user_id: Optional[int] = None
approved_by_user_id: Optional[int] = None
approved_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class UserAdminCreate(BaseModel):
username: str
email: str
password: str
full_name: Optional[str] = None
is_superadmin: bool = False
is_active: bool = True
group_ids: Optional[List[int]] = None
class UserGroupsUpdate(BaseModel):
group_ids: List[int]
class GroupCreate(BaseModel):
name: str
description: Optional[str] = None
class GroupPermissionsUpdate(BaseModel):
permission_ids: List[int]
class UserTwoFactorResetRequest(BaseModel):
reason: Optional[str] = None
# =====================================================
# AnyDesk Remote Support Integration Schemas
# =====================================================
class AnyDeskSessionCreate(BaseModel):
"""Schema for creating a new AnyDesk session"""
customer_id: int
contact_id: Optional[int] = None
sag_id: Optional[int] = None # Case/ticket ID
description: Optional[str] = None
created_by_user_id: Optional[int] = None
class AnyDeskSession(BaseModel):
"""Full AnyDesk session schema"""
id: int
anydesk_session_id: str
customer_id: int
contact_id: Optional[int] = None
sag_id: Optional[int] = None
session_link: Optional[str] = None
status: str # active, completed, failed, cancelled
started_at: str
ended_at: Optional[str] = None
duration_minutes: Optional[int] = None
created_by_user_id: Optional[int] = None
created_at: str
updated_at: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class AnyDeskSessionDetail(AnyDeskSession):
"""AnyDesk session with additional details"""
contact_name: Optional[str] = None
customer_name: Optional[str] = None
sag_title: Optional[str] = None
created_by_user_name: Optional[str] = None
device_info: Optional[dict] = None
metadata: Optional[dict] = None
class AnyDeskWorklogSuggestion(BaseModel):
"""Suggested worklog entry from a completed session"""
session_id: int
duration_hours: float
duration_minutes: int
description: str
start_time: str
end_time: str
billable: bool = True
work_type: str = "remote_support"
class AnyDeskSessionWithWorklog(BaseModel):
"""AnyDesk session with suggested worklog entry"""
session: AnyDeskSession
suggested_worklog: AnyDeskWorklogSuggestion
class TodoStepBase(BaseModel):
"""Base schema for case todo steps"""
title: str
description: Optional[str] = None
due_date: Optional[date] = None
class TodoStepCreate(TodoStepBase):
"""Schema for creating a todo step"""
pass
class TodoStepUpdate(BaseModel):
"""Schema for updating a todo step"""
is_done: Optional[bool] = None
is_next: Optional[bool] = None
class TodoStep(TodoStepBase):
"""Full todo step schema"""
id: int
sag_id: int
is_done: bool
is_next: bool = False
created_by_user_id: Optional[int] = None
created_by_name: Optional[str] = None
created_at: datetime
completed_by_user_id: Optional[int] = None
completed_by_name: Optional[str] = None
completed_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
class AnyDeskSessionHistory(BaseModel):
"""Session history response"""
sessions: List[AnyDeskSessionDetail]
total: int
limit: int
offset: int
class AnyDeskSessionUpdate(BaseModel):
"""Schema for updating a session (mainly status updates)"""
status: str
ended_at: Optional[str] = None
duration_minutes: Optional[int] = None
# ============================================================================
# SAG MODULE (Cases) - QuickCreate and Priority Support
# ============================================================================
class SagPriority(str, Enum):
"""Case priority levels matching database enum"""
LOW = "low"
NORMAL = "normal"
HIGH = "high"
URGENT = "urgent"
class QuickCreateAnalysis(BaseModel):
"""AI analysis result for QuickCreate feature"""
suggested_title: str
suggested_description: str
suggested_priority: SagPriority = SagPriority.NORMAL
suggested_customer_id: Optional[int] = None
suggested_customer_name: Optional[str] = None
suggested_technician_id: Optional[int] = None
suggested_technician_name: Optional[str] = None
suggested_group_id: Optional[int] = None
suggested_group_name: Optional[str] = None
suggested_tags: List[str] = []
hardware_references: List[dict] = [] # [{id, brand, model, serial_number}]
confidence: float = 0.0
ai_reasoning: Optional[str] = None # Debug info for low confidence
model_config = ConfigDict(from_attributes=True)
class SagBase(BaseModel):
"""Base schema for SAG (cases)"""
titel: str
beskrivelse: Optional[str] = None
priority: SagPriority = SagPriority.NORMAL
customer_id: Optional[int] = None
ansvarlig_bruger_id: Optional[int] = None
assigned_group_id: Optional[int] = None
deadline: Optional[datetime] = None
class SagCreate(SagBase):
"""Schema for creating a case"""
template_key: Optional[str] = None
tags: Optional[List[str]] = None
class SagUpdate(BaseModel):
"""Schema for updating a case"""
titel: Optional[str] = None
beskrivelse: Optional[str] = None
priority: Optional[SagPriority] = None
status: Optional[str] = None
ansvarlig_bruger_id: Optional[int] = None
assigned_group_id: Optional[int] = None
deadline: Optional[datetime] = None
class Sag(SagBase):
"""Full case schema"""
id: int
status: str
template_key: Optional[str] = None
created_by_user_id: int
created_at: datetime
updated_at: datetime
deleted_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)