From 0f00b4508eb542787b4195bde970fa5ed1d87d22 Mon Sep 17 00:00:00 2001 From: Jonas Linter Date: Tue, 2 Dec 2025 09:45:27 +0000 Subject: [PATCH] Small db improvements. Still needs migration for alembic --- src/alpine_bits_python/conversion_service.py | 38 ++++++++++++++------ src/alpine_bits_python/db.py | 12 ++++--- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/alpine_bits_python/conversion_service.py b/src/alpine_bits_python/conversion_service.py index 679d3e7..2249271 100644 --- a/src/alpine_bits_python/conversion_service.py +++ b/src/alpine_bits_python/conversion_service.py @@ -37,7 +37,7 @@ class ConversionService: 2. Concurrent mode: SessionMaker passed in, creates independent sessions per task """ - def __init__(self, session: AsyncSession | SessionMaker | None = None): + def __init__(self, session: AsyncSession | SessionMaker | None = None, hotel_id: str | None = None): """Initialize the ConversionService. Args: @@ -45,11 +45,13 @@ class ConversionService: - AsyncSession: Single session for sequential processing - SessionMaker: Factory for creating sessions in concurrent mode - None: Not recommended, but allowed for subclassing + hotel_id: Hotel ID for this conversion service context (from authenticated user) """ self.session = None self.session_maker = None self.supports_concurrent = False + self.hotel_id = hotel_id # Cache for reservation and customer data within a single XML processing run # Maps hotel_code -> list of (reservation, hashed_customer) tuples @@ -248,8 +250,12 @@ class ConversionService: # Process deleted reservations for deleted_res in root.findall("Deletedreservation"): stats["deleted_reservations"] += 1 - pms_reservation_id = deleted_res.get("ID") + pms_reservation_id_str = deleted_res.get("ID") try: + pms_reservation_id = int(pms_reservation_id_str) if pms_reservation_id_str else None + if pms_reservation_id is None: + _LOGGER.warning("Deleted reservation missing ID attribute, skipping") + continue await self._handle_deleted_reservation(pms_reservation_id, session) await session.commit() except Exception as e: @@ -552,7 +558,7 @@ class ConversionService: await session.close() async def _handle_deleted_reservation( - self, pms_reservation_id: str, session: AsyncSession + self, pms_reservation_id: int, session: AsyncSession ): """Handle deleted reservation by marking conversions as deleted or removing them. @@ -561,13 +567,16 @@ class ConversionService: session: AsyncSession to use for the operation """ - # For now, we'll just log it. You could add a 'deleted' flag to the Conversion table - # or actually delete the conversion records - _LOGGER.info("Processing deleted reservation: PMS ID %s", pms_reservation_id) + if not self.hotel_id: + _LOGGER.error("Cannot delete reservation: hotel_id not set in ConversionService") + return - # Option 1: Delete conversion records + _LOGGER.info("Processing deleted reservation: Hotel %s, PMS ID %s", self.hotel_id, pms_reservation_id) + + # Delete conversion records for this hotel + pms_reservation_id result = await session.execute( select(Conversion).where( + Conversion.hotel_id == self.hotel_id, Conversion.pms_reservation_id == pms_reservation_id ) ) @@ -578,8 +587,9 @@ class ConversionService: if conversions: _LOGGER.info( - "Deleted %d conversion records for PMS reservation %s", + "Deleted %d conversion records for hotel %s, PMS reservation %s", len(conversions), + self.hotel_id, pms_reservation_id, ) @@ -673,6 +683,7 @@ class ConversionService: # Check if conversion already exists (upsert logic) existing_result = await session.execute( select(Conversion).where( + Conversion.hotel_id == hotel_id, Conversion.pms_reservation_id == pms_reservation_id ) ) @@ -1498,7 +1509,7 @@ class ConversionService: async def _match_conversion_using_db_data( self, - pms_reservation_id: str, + pms_reservation_id: int, session: AsyncSession | None = None, stats: dict[str, int] | None = None, ) -> None: @@ -1521,10 +1532,17 @@ class ConversionService: if session is None: session = self.session + if not self.hotel_id: + _LOGGER.error("Cannot match conversion: hotel_id not set in ConversionService") + return + # Get the conversion from the database with related data result = await session.execute( select(Conversion) - .where(Conversion.pms_reservation_id == pms_reservation_id) + .where( + Conversion.hotel_id == self.hotel_id, + Conversion.pms_reservation_id == pms_reservation_id + ) .options(selectinload(Conversion.guest), selectinload(Conversion.conversion_rooms)) ) conversion = result.scalar_one_or_none() diff --git a/src/alpine_bits_python/db.py b/src/alpine_bits_python/db.py index c71fecc..a332b31 100644 --- a/src/alpine_bits_python/db.py +++ b/src/alpine_bits_python/db.py @@ -383,8 +383,8 @@ class ConversionGuest(Base): __tablename__ = "conversion_guests" # Natural keys from PMS - composite primary key - hotel_id = Column(String, nullable=False, primary_key=True, index=True) - guest_id = Column(String, nullable=False, primary_key=True, index=True) + hotel_id = Column(String(50), ForeignKey("hotels.hotel_id", ondelete="CASCADE"), nullable=False, primary_key=True, index=True) + guest_id = Column(Integer, nullable=False, primary_key=True, index=True) # Unhashed guest information (for reference/transition period) guest_first_name = Column(String) @@ -578,9 +578,10 @@ class Conversion(Base): ) # Reservation metadata from XML - hotel_id = Column(String, index=True) # hotelID attribute - guest_id = Column(String, nullable=True, index=True) # PMS guest ID, FK to conversion_guests - pms_reservation_id = Column(String, index=True) # id attribute from reservation + hotel_id = Column(String(50), ForeignKey("hotels.hotel_id", ondelete="CASCADE"), nullable=False, index=True) # hotelID attribute + pms_reservation_id = Column(Integer, nullable=False, index=True) # id attribute from reservation + guest_id = Column(Integer, nullable=True, index=True) # PMS guest ID, FK to conversion_guests + reservation_number = Column(String) # number attribute reservation_date = Column(Date) # date attribute (when reservation was made) creation_time = Column(DateTime(timezone=True)) # creationTime attribute @@ -616,6 +617,7 @@ class Conversion(Base): ["conversion_guests.hotel_id", "conversion_guests.guest_id"], ondelete="SET NULL", ), + UniqueConstraint("hotel_id", "pms_reservation_id", name="uq_conversion_hotel_reservation"), ) # Relationships