Holy db migrations batman

This commit is contained in:
Jonas Linter
2025-12-03 10:41:34 +01:00
parent 7ff3c44747
commit b572f660a7
19 changed files with 752 additions and 84 deletions

View File

@@ -768,9 +768,9 @@ def _process_single_reservation(
hotel_reservation_id=[hotel_res_id]
)
if reservation.hotel_code is None:
if reservation.hotel_id is None:
raise ValueError("Reservation hotel_code is None")
hotel_code = str(reservation.hotel_code)
hotel_code = str(reservation.hotel_id)
hotel_name = None if reservation.hotel_name is None else str(reservation.hotel_name)
basic_property_info = HotelReservation.ResGlobalInfo.BasicPropertyInfo(

View File

@@ -138,7 +138,7 @@ async def push_listener(customer: DBCustomer, reservation: DBReservation, hotel)
server: AlpineBitsServer = app.state.alpine_bits_server
hotel_id = hotel["hotel_id"]
reservation_hotel_id = reservation.hotel_code
reservation_hotel_id = reservation.hotel_id
# Double-check hotel matching (should be guaranteed by dispatcher)
if hotel_id != reservation_hotel_id:

View File

@@ -642,7 +642,7 @@ class ConversionService:
# Organize by hotel_code for efficient lookup
for reservation in reservations:
hotel_code = reservation.hotel_code
hotel_code = reservation.hotel_id
if hotel_code not in self._reservation_cache:
self._reservation_cache[hotel_code] = []
# Cache the hashed customer - prefer direct relationship, fall back to customer relationship
@@ -1095,7 +1095,7 @@ class ConversionService:
# Add hotel filter if available
if hotel_id:
query = query.where(Reservation.hotel_code == hotel_id)
query = query.where(Reservation.hotel_id == hotel_id)
# Execute query
db_result = await session.execute(query)

View File

@@ -472,7 +472,7 @@ class CSVImporter:
num_adults=num_adults,
num_children=num_children,
children_ages=children_ages,
hotel_code=final_hotel_code,
hotel_id=final_hotel_code,
hotel_name=final_hotel_name,
offer=str(row.get("room_offer", "")).strip() or None,
user_comment=str(row.get("message", "")).strip() or None,

View File

@@ -27,7 +27,7 @@ from sqlalchemy.ext.asyncio import (
async_sessionmaker,
create_async_engine,
)
from sqlalchemy.orm import backref, declarative_base, relationship
from sqlalchemy.orm import backref, declarative_base, foreign, relationship
from .const import WebhookStatus
from .logging_config import get_logger
@@ -435,7 +435,13 @@ class ConversionGuest(Base):
last_seen = Column(DateTime(timezone=True))
# Relationships
conversions = relationship("Conversion", back_populates="guest")
conversions = relationship(
"Conversion",
back_populates="guest",
foreign_keys="[Conversion.hotel_id, Conversion.guest_id]",
primaryjoin="and_(ConversionGuest.hotel_id == foreign(Conversion.hotel_id), "
"ConversionGuest.guest_id == foreign(Conversion.guest_id))",
)
hashed_customer = relationship("HashedCustomer", backref="conversion_guests")
@staticmethod
@@ -541,8 +547,8 @@ class Reservation(Base):
# Advertising account IDs (stored conditionally based on fbclid/gclid presence)
meta_account_id = Column(String)
google_account_id = Column(String)
# Add hotel_code and hotel_name for XML
hotel_code = Column(String)
# Add hotel_id and hotel_name for XML
hotel_id = Column(String, ForeignKey("hotels.hotel_id", ondelete="CASCADE"))
hotel_name = Column(String)
# RoomTypes fields (optional)
room_type_code = Column(String)
@@ -569,7 +575,7 @@ class AckedRequest(Base):
) # Username of the client making the request
unique_id = Column(
String, index=True
) # Should match Reservation.form_id or another unique field
) # Matches the md5_unique_id in Reservation
timestamp = Column(DateTime(timezone=True))
@@ -646,13 +652,10 @@ class Conversion(Base):
created_at = Column(DateTime(timezone=True)) # When this record was imported
updated_at = Column(DateTime(timezone=True)) # When this record was last updated
# Composite foreign key constraint for ConversionGuest (hotel_id, guest_id)
# Table constraints
# Note: The relationship to ConversionGuest is handled via SQLAlchemy ORM
# by matching (hotel_id, guest_id) pairs, no DB-level FK constraint needed
__table_args__ = (
ForeignKeyConstraint(
["hotel_id", "guest_id"],
["conversion_guests.hotel_id", "conversion_guests.guest_id"],
ondelete="SET NULL",
),
UniqueConstraint(
"hotel_id", "pms_reservation_id", name="uq_conversion_hotel_reservation"
),
@@ -662,7 +665,13 @@ class Conversion(Base):
reservation = relationship("Reservation", backref="conversions")
customer = relationship("Customer", backref="conversions")
hashed_customer = relationship("HashedCustomer", backref="conversions")
guest = relationship("ConversionGuest", back_populates="conversions")
guest = relationship(
"ConversionGuest",
back_populates="conversions",
foreign_keys="[Conversion.hotel_id, Conversion.guest_id]",
primaryjoin="and_(Conversion.hotel_id == ConversionGuest.hotel_id, "
"Conversion.guest_id == ConversionGuest.guest_id)",
)
conversion_rooms = relationship(
"ConversionRoom", back_populates="conversion", cascade="all, delete-orphan"
)

View File

@@ -115,7 +115,7 @@ async def backfill_advertising_account_ids(
sql = text(
"UPDATE reservations "
"SET meta_account_id = :meta_account "
"WHERE hotel_code = :hotel_id "
"WHERE hotel_id = :hotel_id "
"AND fbclid IS NOT NULL "
"AND fbclid != '' "
"AND (meta_account_id IS NULL OR meta_account_id = '')"
@@ -141,7 +141,7 @@ async def backfill_advertising_account_ids(
sql = text(
"UPDATE reservations "
"SET google_account_id = :google_account "
"WHERE hotel_code = :hotel_id "
"WHERE hotel_id = :hotel_id "
"AND gclid IS NOT NULL "
"AND gclid != '' "
"AND (google_account_id IS NULL OR google_account_id = '')"
@@ -215,7 +215,7 @@ async def backfill_acked_requests_username(
UPDATE acked_requests
SET username = :username
WHERE unique_id IN (
SELECT md5_unique_id FROM reservations WHERE hotel_code = :hotel_id
SELECT md5_unique_id FROM reservations WHERE hotel_id = :hotel_id
)
AND username IS NULL
"""

View File

@@ -523,10 +523,10 @@ class ReservationStatsCollector:
async with self.async_sessionmaker() as session:
# Query reservations created in the reporting period
result = await session.execute(
select(Reservation.hotel_code, func.count(Reservation.id))
select(Reservation.hotel_id, func.count(Reservation.id))
.where(Reservation.created_at >= period_start)
.where(Reservation.created_at < period_end)
.group_by(Reservation.hotel_code)
.group_by(Reservation.hotel_id)
)
hotel_counts = dict(result.all())

View File

@@ -181,7 +181,7 @@ class ReservationService:
if end_date:
filters.append(Reservation.created_at <= end_date)
if hotel_code:
filters.append(Reservation.hotel_code == hotel_code)
filters.append(Reservation.hotel_id == hotel_code)
if filters:
query = query.where(and_(*filters))

View File

@@ -131,7 +131,7 @@ class ReservationData(BaseModel):
num_adults: int = Field(..., ge=1)
num_children: int = Field(0, ge=0, le=10)
children_ages: list[int] = Field(default_factory=list)
hotel_code: str = Field(..., min_length=1, max_length=50)
hotel_id: str = Field(..., min_length=1, max_length=50)
hotel_name: str | None = Field(None, max_length=200)
offer: str | None = Field(None, max_length=500)
user_comment: str | None = Field(None, max_length=2000)

View File

@@ -306,7 +306,7 @@ async def migrate_data(
user_comment=reservation.user_comment,
fbclid=reservation.fbclid,
gclid=reservation.gclid,
hotel_code=reservation.hotel_code,
hotel_code=reservation.hotel_id,
hotel_name=reservation.hotel_name,
room_type_code=reservation.room_type_code,
room_classification_code=reservation.room_classification_code,

View File

@@ -247,7 +247,7 @@ async def process_wix_form_submission(
num_adults=num_adults,
num_children=num_children,
children_ages=children_ages,
hotel_code=hotel_code,
hotel_id=hotel_code,
hotel_name=hotel_name,
offer=offer,
created_at=submissionTime,
@@ -575,7 +575,7 @@ async def process_generic_webhook_submission(
"num_adults": num_adults,
"num_children": num_children,
"children_ages": children_ages,
"hotel_code": hotel_code,
"hotel_id": hotel_code,
"hotel_name": hotel_name,
"offer": selected_offers_str,
"utm_source": utm_source,