This commit is contained in:
Iliyan Angelov
2025-11-30 22:43:09 +02:00
parent 24b40450dd
commit 39fcfff811
1610 changed files with 5442 additions and 1383 deletions

View File

@@ -0,0 +1,566 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Optional, List
from ...shared.config.database import get_db
from ...security.middleware.auth import get_current_user, authorize_roles
from ...auth.models.user import User
from ..models.guest_preference import GuestPreference
from ..models.guest_note import GuestNote
from ..models.guest_tag import GuestTag
from ..models.guest_communication import GuestCommunication, CommunicationType, CommunicationDirection
from ..models.guest_segment import GuestSegment
from ..services.guest_profile_service import GuestProfileService
from ...shared.utils.role_helpers import is_customer
import json
router = APIRouter(prefix='/guest-profiles', tags=['guest-profiles'])
# Guest Search and List
@router.get('/')
async def search_guests(
search: Optional[str] = Query(None),
is_vip: Optional[bool] = Query(None),
segment_id: Optional[int] = Query(None),
min_lifetime_value: Optional[float] = Query(None),
min_satisfaction_score: Optional[float] = Query(None),
tag_id: Optional[int] = Query(None),
page: int = Query(1, ge=1),
limit: int = Query(10, ge=1, le=100),
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Search and filter guests"""
try:
result = GuestProfileService.search_guests(
db=db,
search=search,
is_vip=is_vip,
segment_id=segment_id,
min_lifetime_value=min_lifetime_value,
min_satisfaction_score=min_satisfaction_score,
tag_id=tag_id,
page=page,
limit=limit
)
guests_data = []
for guest in result['guests']:
guest_dict = {
'id': guest.id,
'full_name': guest.full_name,
'email': guest.email,
'phone': guest.phone,
'is_vip': guest.is_vip,
'lifetime_value': float(guest.lifetime_value) if guest.lifetime_value else 0,
'satisfaction_score': float(guest.satisfaction_score) if guest.satisfaction_score else None,
'total_visits': guest.total_visits,
'last_visit_date': guest.last_visit_date.isoformat() if guest.last_visit_date else None,
'tags': [{'id': tag.id, 'name': tag.name, 'color': tag.color} for tag in guest.guest_tags],
'segments': [{'id': seg.id, 'name': seg.name} for seg in guest.guest_segments]
}
guests_data.append(guest_dict)
return {
'status': 'success',
'data': {
'guests': guests_data,
'pagination': {
'total': result['total'],
'page': result['page'],
'limit': result['limit'],
'total_pages': result['total_pages']
}
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Get Guest Profile Details
@router.get('/{user_id}')
async def get_guest_profile(
user_id: int,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Get comprehensive guest profile"""
try:
# First check if user exists at all
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail=f'User with ID {user_id} not found')
# Check if user is a customer
from ...shared.utils.role_helpers import is_customer
if not is_customer(user, db):
raise HTTPException(status_code=404, detail=f'User with ID {user_id} is not a guest (customer)')
# Get analytics
analytics = GuestProfileService.get_guest_analytics(user_id, db)
# Get preferences
preferences = db.query(GuestPreference).filter(GuestPreference.user_id == user_id).first()
# Get notes
notes = db.query(GuestNote).filter(GuestNote.user_id == user_id).order_by(GuestNote.created_at.desc()).all()
# Get communications
communications = db.query(GuestCommunication).filter(
GuestCommunication.user_id == user_id
).order_by(GuestCommunication.created_at.desc()).limit(20).all()
# Get booking history
bookings = GuestProfileService.get_booking_history(user_id, db, limit=10)
# Safely access relationships
try:
tags = [{'id': tag.id, 'name': tag.name, 'color': tag.color} for tag in (user.guest_tags or [])]
except Exception:
tags = []
try:
segments = [{'id': seg.id, 'name': seg.name, 'description': seg.description} for seg in (user.guest_segments or [])]
except Exception:
segments = []
profile_data = {
'id': user.id,
'full_name': user.full_name,
'email': user.email,
'phone': user.phone,
'address': user.address,
'avatar': user.avatar,
'is_vip': getattr(user, 'is_vip', False),
'lifetime_value': float(user.lifetime_value) if hasattr(user, 'lifetime_value') and user.lifetime_value else 0,
'satisfaction_score': float(user.satisfaction_score) if hasattr(user, 'satisfaction_score') and user.satisfaction_score else None,
'total_visits': getattr(user, 'total_visits', 0),
'last_visit_date': user.last_visit_date.isoformat() if hasattr(user, 'last_visit_date') and user.last_visit_date else None,
'created_at': user.created_at.isoformat() if user.created_at else None,
'analytics': analytics,
'preferences': {
'preferred_room_location': preferences.preferred_room_location if preferences else None,
'preferred_floor': preferences.preferred_floor if preferences else None,
'preferred_amenities': preferences.preferred_amenities if preferences else None,
'special_requests': preferences.special_requests if preferences else None,
'preferred_contact_method': preferences.preferred_contact_method if preferences else None,
'dietary_restrictions': preferences.dietary_restrictions if preferences else None,
} if preferences else None,
'tags': tags,
'segments': segments,
'notes': [{
'id': note.id,
'note': note.note,
'is_important': note.is_important,
'is_private': note.is_private,
'created_by': note.creator.full_name if note.creator else None,
'created_at': note.created_at.isoformat() if note.created_at else None
} for note in notes],
'communications': [{
'id': comm.id,
'communication_type': comm.communication_type.value,
'direction': comm.direction.value,
'subject': comm.subject,
'content': comm.content,
'staff_name': comm.staff.full_name if comm.staff else None,
'created_at': comm.created_at.isoformat() if comm.created_at else None
} for comm in communications],
'recent_bookings': [{
'id': booking.id,
'booking_number': booking.booking_number,
'check_in_date': booking.check_in_date.isoformat() if booking.check_in_date else None,
'check_out_date': booking.check_out_date.isoformat() if booking.check_out_date else None,
'status': booking.status.value if hasattr(booking.status, 'value') else str(booking.status),
'total_price': float(booking.total_price) if booking.total_price else 0
} for booking in bookings]
}
return {'status': 'success', 'data': {'profile': profile_data}}
except HTTPException:
raise
except Exception as e:
import traceback
error_detail = f'Error fetching guest profile: {str(e)}\n{traceback.format_exc()}'
raise HTTPException(status_code=500, detail=error_detail)
# Update Guest Preferences
@router.put('/{user_id}/preferences')
async def update_guest_preferences(
user_id: int,
preferences_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Update guest preferences"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
preferences = db.query(GuestPreference).filter(GuestPreference.user_id == user_id).first()
if not preferences:
preferences = GuestPreference(user_id=user_id)
db.add(preferences)
if 'preferred_room_location' in preferences_data:
preferences.preferred_room_location = preferences_data['preferred_room_location']
if 'preferred_floor' in preferences_data:
preferences.preferred_floor = preferences_data['preferred_floor']
if 'preferred_room_type_id' in preferences_data:
preferences.preferred_room_type_id = preferences_data['preferred_room_type_id']
if 'preferred_amenities' in preferences_data:
preferences.preferred_amenities = preferences_data['preferred_amenities']
if 'special_requests' in preferences_data:
preferences.special_requests = preferences_data['special_requests']
if 'preferred_services' in preferences_data:
preferences.preferred_services = preferences_data['preferred_services']
if 'preferred_contact_method' in preferences_data:
preferences.preferred_contact_method = preferences_data['preferred_contact_method']
if 'preferred_language' in preferences_data:
preferences.preferred_language = preferences_data['preferred_language']
if 'dietary_restrictions' in preferences_data:
preferences.dietary_restrictions = preferences_data['dietary_restrictions']
if 'additional_preferences' in preferences_data:
preferences.additional_preferences = preferences_data['additional_preferences']
db.commit()
db.refresh(preferences)
return {'status': 'success', 'message': 'Preferences updated successfully'}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Create Guest Note
@router.post('/{user_id}/notes')
async def create_guest_note(
user_id: int,
note_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Create a note for a guest"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
note = GuestNote(
user_id=user_id,
created_by=current_user.id,
note=note_data.get('note'),
is_important=note_data.get('is_important', False),
is_private=note_data.get('is_private', False)
)
db.add(note)
db.commit()
db.refresh(note)
return {'status': 'success', 'message': 'Note created successfully', 'data': {'note': {
'id': note.id,
'note': note.note,
'is_important': note.is_important,
'is_private': note.is_private,
'created_at': note.created_at.isoformat() if note.created_at else None
}}}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Delete Guest Note
@router.delete('/{user_id}/notes/{note_id}')
async def delete_guest_note(
user_id: int,
note_id: int,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Delete a guest note"""
try:
note = db.query(GuestNote).filter(GuestNote.id == note_id, GuestNote.user_id == user_id).first()
if not note:
raise HTTPException(status_code=404, detail='Note not found')
db.delete(note)
db.commit()
return {'status': 'success', 'message': 'Note deleted successfully'}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Toggle VIP Status
@router.put('/{user_id}/vip-status')
async def toggle_vip_status(
user_id: int,
vip_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Toggle VIP status for a guest"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
user.is_vip = vip_data.get('is_vip', False)
db.commit()
db.refresh(user)
return {'status': 'success', 'message': 'VIP status updated successfully'}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Add Tag to Guest
@router.post('/{user_id}/tags')
async def add_tag_to_guest(
user_id: int,
tag_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Add a tag to a guest"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
tag_id = tag_data.get('tag_id')
tag = db.query(GuestTag).filter(GuestTag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail='Tag not found')
if tag not in user.guest_tags:
user.guest_tags.append(tag)
db.commit()
return {'status': 'success', 'message': 'Tag added successfully'}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Remove Tag from Guest
@router.delete('/{user_id}/tags/{tag_id}')
async def remove_tag_from_guest(
user_id: int,
tag_id: int,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Remove a tag from a guest"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
tag = db.query(GuestTag).filter(GuestTag.id == tag_id).first()
if not tag:
raise HTTPException(status_code=404, detail='Tag not found')
if tag in user.guest_tags:
user.guest_tags.remove(tag)
db.commit()
return {'status': 'success', 'message': 'Tag removed successfully'}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Create Communication Record
@router.post('/{user_id}/communications')
async def create_communication(
user_id: int,
communication_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Create a communication record"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
comm = GuestCommunication(
user_id=user_id,
staff_id=current_user.id,
communication_type=CommunicationType(communication_data.get('communication_type')),
direction=CommunicationDirection(communication_data.get('direction')),
subject=communication_data.get('subject'),
content=communication_data.get('content'),
booking_id=communication_data.get('booking_id'),
is_automated=communication_data.get('is_automated', False)
)
db.add(comm)
db.commit()
db.refresh(comm)
return {'status': 'success', 'message': 'Communication recorded successfully'}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Get Guest Analytics
@router.get('/{user_id}/analytics')
async def get_guest_analytics(
user_id: int,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Get guest analytics"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
analytics = GuestProfileService.get_guest_analytics(user_id, db)
return {'status': 'success', 'data': {'analytics': analytics}}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Update Guest Metrics
@router.post('/{user_id}/update-metrics')
async def update_guest_metrics(
user_id: int,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Update guest metrics (lifetime value, satisfaction score, etc.)"""
try:
user = db.query(User).filter(User.id == user_id).first()
if not user or not is_customer(user, db):
raise HTTPException(status_code=404, detail='Guest not found')
metrics = GuestProfileService.update_guest_metrics(user_id, db)
return {'status': 'success', 'message': 'Metrics updated successfully', 'data': {'metrics': metrics}}
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Tag Management Routes
@router.get('/tags/all')
async def get_all_tags(
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Get all available tags"""
try:
tags = db.query(GuestTag).all()
tags_data = [{
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description
} for tag in tags]
return {'status': 'success', 'data': {'tags': tags_data}}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post('/tags')
async def create_tag(
tag_data: dict,
current_user: User = Depends(authorize_roles('admin')),
db: Session = Depends(get_db)
):
"""Create a new tag"""
try:
tag = GuestTag(
name=tag_data.get('name'),
color=tag_data.get('color', '#3B82F6'),
description=tag_data.get('description')
)
db.add(tag)
db.commit()
db.refresh(tag)
return {'status': 'success', 'message': 'Tag created successfully', 'data': {'tag': {
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description
}}}
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
# Segment Management Routes
@router.get('/segments/all')
async def get_all_segments(
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Get all available segments"""
try:
segments = db.query(GuestSegment).filter(GuestSegment.is_active == True).all()
segments_data = [{
'id': seg.id,
'name': seg.name,
'description': seg.description,
'criteria': seg.criteria
} for seg in segments]
return {'status': 'success', 'data': {'segments': segments_data}}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post('/{user_id}/segments')
async def assign_segment(
user_id: int,
segment_data: dict,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Assign a guest to a segment"""
try:
segment_id = segment_data.get('segment_id')
success = GuestProfileService.assign_segment(user_id, segment_id, db)
if not success:
raise HTTPException(status_code=404, detail='Guest or segment not found')
return {'status': 'success', 'message': 'Segment assigned successfully'}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete('/{user_id}/segments/{segment_id}')
async def remove_segment(
user_id: int,
segment_id: int,
current_user: User = Depends(authorize_roles('admin', 'staff')),
db: Session = Depends(get_db)
):
"""Remove a guest from a segment"""
try:
success = GuestProfileService.remove_segment(user_id, segment_id, db)
if not success:
raise HTTPException(status_code=404, detail='Guest or segment not found')
return {'status': 'success', 'message': 'Segment removed successfully'}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))