Added reservation_service aswell
This commit is contained in:
263
src/alpine_bits_python/reservation_service.py
Normal file
263
src/alpine_bits_python/reservation_service.py
Normal file
@@ -0,0 +1,263 @@
|
||||
"""Reservation service layer for handling reservation database operations."""
|
||||
|
||||
import hashlib
|
||||
from datetime import UTC, datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import and_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from .db import AckedRequest, Customer, Reservation
|
||||
from .schemas import ReservationData
|
||||
|
||||
|
||||
class ReservationService:
|
||||
"""Service for managing reservations and related operations.
|
||||
|
||||
Handles all database operations for reservations including creation,
|
||||
retrieval, and acknowledgement tracking.
|
||||
"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
def _convert_reservation_data_to_db(
|
||||
self, reservation_model: ReservationData, customer_id: int
|
||||
) -> Reservation:
|
||||
"""Convert ReservationData to Reservation model.
|
||||
|
||||
Args:
|
||||
reservation_model: ReservationData instance
|
||||
customer_id: Customer ID to link to
|
||||
|
||||
Returns:
|
||||
Reservation instance ready for database insertion
|
||||
"""
|
||||
data = reservation_model.model_dump(exclude_none=True)
|
||||
|
||||
# Convert children_ages list to CSV string
|
||||
children_list = data.pop("children_ages", [])
|
||||
children_csv = (
|
||||
",".join(str(int(a)) for a in children_list) if children_list else ""
|
||||
)
|
||||
data["children_ages"] = children_csv
|
||||
|
||||
# Inject foreign key
|
||||
data["customer_id"] = customer_id
|
||||
|
||||
return Reservation(**data)
|
||||
|
||||
async def create_reservation(
|
||||
self, reservation_data: ReservationData, customer_id: int
|
||||
) -> Reservation:
|
||||
"""Create a new reservation.
|
||||
|
||||
Args:
|
||||
reservation_data: ReservationData containing reservation details
|
||||
customer_id: ID of the customer making the reservation
|
||||
|
||||
Returns:
|
||||
Created Reservation instance
|
||||
"""
|
||||
reservation = self._convert_reservation_data_to_db(
|
||||
reservation_data, customer_id
|
||||
)
|
||||
self.session.add(reservation)
|
||||
await self.session.commit()
|
||||
await self.session.refresh(reservation)
|
||||
return reservation
|
||||
|
||||
async def get_reservation_by_unique_id(
|
||||
self, unique_id: str
|
||||
) -> Optional[Reservation]:
|
||||
"""Get a reservation by unique_id.
|
||||
|
||||
Args:
|
||||
unique_id: The unique_id to search for
|
||||
|
||||
Returns:
|
||||
Reservation instance if found, None otherwise
|
||||
"""
|
||||
result = await self.session.execute(
|
||||
select(Reservation).where(Reservation.unique_id == unique_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_reservation_by_md5_unique_id(
|
||||
self, md5_unique_id: str
|
||||
) -> Optional[Reservation]:
|
||||
"""Get a reservation by md5_unique_id.
|
||||
|
||||
Args:
|
||||
md5_unique_id: The MD5 hash of unique_id
|
||||
|
||||
Returns:
|
||||
Reservation instance if found, None otherwise
|
||||
"""
|
||||
result = await self.session.execute(
|
||||
select(Reservation).where(
|
||||
Reservation.md5_unique_id == md5_unique_id
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def check_duplicate_reservation(
|
||||
self, unique_id: str, md5_unique_id: str
|
||||
) -> bool:
|
||||
"""Check if a reservation already exists.
|
||||
|
||||
Args:
|
||||
unique_id: The unique_id to check
|
||||
md5_unique_id: The MD5 hash to check
|
||||
|
||||
Returns:
|
||||
True if reservation exists, False otherwise
|
||||
"""
|
||||
existing = await self.get_reservation_by_unique_id(unique_id)
|
||||
if existing:
|
||||
return True
|
||||
|
||||
existing_md5 = await self.get_reservation_by_md5_unique_id(md5_unique_id)
|
||||
return existing_md5 is not None
|
||||
|
||||
async def get_reservations_for_customer(
|
||||
self, customer_id: int
|
||||
) -> list[Reservation]:
|
||||
"""Get all reservations for a customer.
|
||||
|
||||
Args:
|
||||
customer_id: The customer ID
|
||||
|
||||
Returns:
|
||||
List of Reservation instances
|
||||
"""
|
||||
result = await self.session.execute(
|
||||
select(Reservation).where(Reservation.customer_id == customer_id)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_reservations_with_filters(
|
||||
self,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
hotel_code: Optional[str] = None,
|
||||
) -> list[tuple[Reservation, Customer]]:
|
||||
"""Get reservations with optional filters, joined with customers.
|
||||
|
||||
Args:
|
||||
start_date: Filter by created_at >= this value
|
||||
end_date: Filter by created_at <= this value
|
||||
hotel_code: Filter by hotel code
|
||||
|
||||
Returns:
|
||||
List of (Reservation, Customer) tuples
|
||||
"""
|
||||
query = select(Reservation, Customer).join(
|
||||
Customer, Reservation.customer_id == Customer.id
|
||||
)
|
||||
|
||||
filters = []
|
||||
if start_date:
|
||||
filters.append(Reservation.created_at >= start_date)
|
||||
if end_date:
|
||||
filters.append(Reservation.created_at <= end_date)
|
||||
if hotel_code:
|
||||
filters.append(Reservation.hotel_code == hotel_code)
|
||||
|
||||
if filters:
|
||||
query = query.where(and_(*filters))
|
||||
|
||||
result = await self.session.execute(query)
|
||||
return list(result.all())
|
||||
|
||||
async def get_unacknowledged_reservations(
|
||||
self,
|
||||
client_id: str,
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
hotel_code: Optional[str] = None,
|
||||
) -> list[tuple[Reservation, Customer]]:
|
||||
"""Get reservations that haven't been acknowledged by a client.
|
||||
|
||||
Args:
|
||||
client_id: The client ID to check acknowledgements for
|
||||
start_date: Filter by start date >= this value
|
||||
end_date: Filter by end date <= this value
|
||||
hotel_code: Filter by hotel code
|
||||
|
||||
Returns:
|
||||
List of (Reservation, Customer) tuples that are unacknowledged
|
||||
"""
|
||||
# Get all acknowledged md5_unique_ids for this client
|
||||
acked_result = await self.session.execute(
|
||||
select(AckedRequest.unique_id).where(
|
||||
AckedRequest.client_id == client_id
|
||||
)
|
||||
)
|
||||
acked_md5_ids = {row[0] for row in acked_result.all()}
|
||||
|
||||
# Get all reservations with filters
|
||||
all_reservations = await self.get_reservations_with_filters(
|
||||
start_date, end_date, hotel_code
|
||||
)
|
||||
|
||||
# Filter out acknowledged ones (comparing md5_unique_id)
|
||||
return [
|
||||
(res, cust)
|
||||
for res, cust in all_reservations
|
||||
if res.md5_unique_id not in acked_md5_ids
|
||||
]
|
||||
|
||||
async def record_acknowledgement(
|
||||
self, client_id: str, unique_id: str
|
||||
) -> AckedRequest:
|
||||
"""Record that a client has acknowledged a reservation.
|
||||
|
||||
Args:
|
||||
client_id: The client ID
|
||||
unique_id: The unique_id of the reservation
|
||||
|
||||
Returns:
|
||||
Created AckedRequest instance
|
||||
"""
|
||||
acked = AckedRequest(
|
||||
client_id=client_id,
|
||||
unique_id=unique_id,
|
||||
timestamp=datetime.now(UTC),
|
||||
)
|
||||
self.session.add(acked)
|
||||
await self.session.commit()
|
||||
await self.session.refresh(acked)
|
||||
return acked
|
||||
|
||||
async def is_acknowledged(self, client_id: str, unique_id: str) -> bool:
|
||||
"""Check if a reservation has been acknowledged by a client.
|
||||
|
||||
Args:
|
||||
client_id: The client ID
|
||||
unique_id: The reservation unique_id
|
||||
|
||||
Returns:
|
||||
True if acknowledged, False otherwise
|
||||
"""
|
||||
result = await self.session.execute(
|
||||
select(AckedRequest).where(
|
||||
and_(
|
||||
AckedRequest.client_id == client_id,
|
||||
AckedRequest.unique_id == unique_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none() is not None
|
||||
|
||||
@staticmethod
|
||||
def generate_md5_unique_id(unique_id: str) -> str:
|
||||
"""Generate MD5 hash of unique_id.
|
||||
|
||||
Args:
|
||||
unique_id: The unique_id to hash
|
||||
|
||||
Returns:
|
||||
MD5 hash as hex string
|
||||
"""
|
||||
return hashlib.md5(unique_id.encode("utf-8")).hexdigest()
|
||||
Reference in New Issue
Block a user