mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:11:09 -05:00
229 lines
6.7 KiB
Python
229 lines
6.7 KiB
Python
"""
|
|
Services for park-related business logic.
|
|
Following Django styleguide pattern for business logic encapsulation.
|
|
"""
|
|
|
|
from typing import Optional, Dict, Any, TYPE_CHECKING
|
|
from django.db import transaction
|
|
from django.db.models import Q
|
|
|
|
if TYPE_CHECKING:
|
|
from django.contrib.auth.models import AbstractUser
|
|
|
|
from ..models import Park, ParkArea
|
|
from .location_service import ParkLocationService
|
|
|
|
|
|
class ParkService:
|
|
"""Service for managing park operations."""
|
|
|
|
@staticmethod
|
|
def create_park(
|
|
*,
|
|
name: str,
|
|
description: str = "",
|
|
status: str = "OPERATING",
|
|
operator_id: Optional[int] = None,
|
|
property_owner_id: Optional[int] = None,
|
|
opening_date: Optional[str] = None,
|
|
closing_date: Optional[str] = None,
|
|
operating_season: str = "",
|
|
size_acres: Optional[float] = None,
|
|
website: str = "",
|
|
location_data: Optional[Dict[str, Any]] = None,
|
|
created_by: Optional["AbstractUser"] = None,
|
|
) -> Park:
|
|
"""
|
|
Create a new park with validation and location handling.
|
|
|
|
Args:
|
|
name: Park name
|
|
description: Park description
|
|
status: Operating status
|
|
operator_id: ID of operating company
|
|
property_owner_id: ID of property owner company
|
|
opening_date: Opening date
|
|
closing_date: Closing date
|
|
operating_season: Operating season description
|
|
size_acres: Park size in acres
|
|
website: Park website URL
|
|
location_data: Dictionary containing location information
|
|
created_by: User creating the park
|
|
|
|
Returns:
|
|
Created Park instance
|
|
|
|
Raises:
|
|
ValidationError: If park data is invalid
|
|
"""
|
|
with transaction.atomic():
|
|
# Create park instance
|
|
park = Park(
|
|
name=name,
|
|
description=description,
|
|
status=status,
|
|
opening_date=opening_date,
|
|
closing_date=closing_date,
|
|
operating_season=operating_season,
|
|
size_acres=size_acres,
|
|
website=website,
|
|
)
|
|
|
|
# Set foreign key relationships if provided
|
|
if operator_id:
|
|
from apps.parks.models import Company
|
|
|
|
park.operator = Company.objects.get(id=operator_id)
|
|
|
|
if property_owner_id:
|
|
from apps.parks.models import Company
|
|
|
|
park.property_owner = Company.objects.get(id=property_owner_id)
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
park.full_clean()
|
|
park.save()
|
|
|
|
# Handle location if provided
|
|
if location_data:
|
|
ParkLocationService.create_park_location(park=park, **location_data)
|
|
|
|
return park
|
|
|
|
@staticmethod
|
|
def update_park(
|
|
*,
|
|
park_id: int,
|
|
updates: Dict[str, Any],
|
|
updated_by: Optional["AbstractUser"] = None,
|
|
) -> Park:
|
|
"""
|
|
Update an existing park with validation.
|
|
|
|
Args:
|
|
park_id: ID of park to update
|
|
updates: Dictionary of field updates
|
|
updated_by: User performing the update
|
|
|
|
Returns:
|
|
Updated Park instance
|
|
|
|
Raises:
|
|
Park.DoesNotExist: If park doesn't exist
|
|
ValidationError: If update data is invalid
|
|
"""
|
|
with transaction.atomic():
|
|
park = Park.objects.select_for_update().get(id=park_id)
|
|
|
|
# Apply updates
|
|
for field, value in updates.items():
|
|
if hasattr(park, field):
|
|
setattr(park, field, value)
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
park.full_clean()
|
|
park.save()
|
|
|
|
return park
|
|
|
|
@staticmethod
|
|
def delete_park(
|
|
*, park_id: int, deleted_by: Optional["AbstractUser"] = None
|
|
) -> bool:
|
|
"""
|
|
Soft delete a park by setting status to DEMOLISHED.
|
|
|
|
Args:
|
|
park_id: ID of park to delete
|
|
deleted_by: User performing the deletion
|
|
|
|
Returns:
|
|
True if successfully deleted
|
|
|
|
Raises:
|
|
Park.DoesNotExist: If park doesn't exist
|
|
"""
|
|
with transaction.atomic():
|
|
park = Park.objects.select_for_update().get(id=park_id)
|
|
park.status = "DEMOLISHED"
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
park.full_clean()
|
|
park.save()
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def create_park_area(
|
|
*,
|
|
park_id: int,
|
|
name: str,
|
|
description: str = "",
|
|
created_by: Optional["AbstractUser"] = None,
|
|
) -> ParkArea:
|
|
"""
|
|
Create a new area within a park.
|
|
|
|
Args:
|
|
park_id: ID of the parent park
|
|
name: Area name
|
|
description: Area description
|
|
created_by: User creating the area
|
|
|
|
Returns:
|
|
Created ParkArea instance
|
|
|
|
Raises:
|
|
Park.DoesNotExist: If park doesn't exist
|
|
ValidationError: If area data is invalid
|
|
"""
|
|
park = Park.objects.get(id=park_id)
|
|
|
|
area = ParkArea(park=park, name=name, description=description)
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
area.full_clean()
|
|
area.save()
|
|
|
|
return area
|
|
|
|
@staticmethod
|
|
def update_park_statistics(*, park_id: int) -> Park:
|
|
"""
|
|
Recalculate and update park statistics (ride counts, ratings).
|
|
|
|
Args:
|
|
park_id: ID of park to update statistics for
|
|
|
|
Returns:
|
|
Updated Park instance with fresh statistics
|
|
"""
|
|
from apps.rides.models import Ride
|
|
from apps.parks.models import ParkReview
|
|
from django.db.models import Count, Avg
|
|
|
|
with transaction.atomic():
|
|
park = Park.objects.select_for_update().get(id=park_id)
|
|
|
|
# Calculate ride counts
|
|
ride_stats = Ride.objects.filter(park=park).aggregate(
|
|
total_rides=Count("id"),
|
|
coaster_count=Count("id", filter=Q(category__in=["RC", "WC"])),
|
|
)
|
|
|
|
# Calculate average rating
|
|
avg_rating = ParkReview.objects.filter(
|
|
park=park, is_published=True
|
|
).aggregate(avg_rating=Avg("rating"))["avg_rating"]
|
|
|
|
# Update park fields
|
|
park.ride_count = ride_stats["total_rides"] or 0
|
|
park.coaster_count = ride_stats["coaster_count"] or 0
|
|
park.average_rating = avg_rating
|
|
|
|
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
|
|
park.full_clean()
|
|
park.save()
|
|
|
|
return park
|