Migration successfull. Does not cause any problems and new foreign keys work

This commit is contained in:
Jonas Linter
2025-12-02 11:27:07 +01:00
parent 0f00b4508e
commit b1c867ca93
2 changed files with 257 additions and 32 deletions

View File

@@ -4,8 +4,6 @@ import os
from collections.abc import AsyncGenerator, Callable
from typing import TypeVar
from .const import WebhookStatus
from sqlalchemy import (
JSON,
Boolean,
@@ -17,6 +15,7 @@ from sqlalchemy import (
ForeignKeyConstraint,
Index,
Integer,
MetaData,
String,
UniqueConstraint,
func,
@@ -30,6 +29,7 @@ from sqlalchemy.ext.asyncio import (
)
from sqlalchemy.orm import backref, declarative_base, relationship
from .const import WebhookStatus
from .logging_config import get_logger
_LOGGER = get_logger(__name__)
@@ -58,7 +58,16 @@ class Base:
# __table_args__ = {"schema": _SCHEMA}
Base = declarative_base(cls=Base)
# Define naming convention for constraints
metadata = MetaData(naming_convention={
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
})
Base = declarative_base(cls=Base, metadata=metadata)
# Type variable for async functions
T = TypeVar("T")
@@ -353,7 +362,10 @@ class HashedCustomer(Base):
__tablename__ = "hashed_customers"
id = Column(Integer, primary_key=True)
customer_id = Column(
Integer, ForeignKey("customers.id", ondelete="SET NULL"), unique=True, nullable=True
Integer,
ForeignKey("customers.id", ondelete="SET NULL"),
unique=True,
nullable=True,
)
contact_id = Column(String, unique=True) # Keep unhashed for reference
hashed_email = Column(String(64)) # SHA256 produces 64 hex chars
@@ -367,7 +379,9 @@ class HashedCustomer(Base):
hashed_birth_date = Column(String(64))
created_at = Column(DateTime(timezone=True))
customer = relationship("Customer", backref=backref("hashed_version", uselist=False, lazy="joined"))
customer = relationship(
"Customer", backref=backref("hashed_version", uselist=False, lazy="joined")
)
class ConversionGuest(Base):
@@ -383,7 +397,13 @@ class ConversionGuest(Base):
__tablename__ = "conversion_guests"
# Natural keys from PMS - composite primary key
hotel_id = Column(String(50), ForeignKey("hotels.hotel_id", ondelete="CASCADE"), 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)
@@ -401,10 +421,14 @@ class ConversionGuest(Base):
hashed_birth_date = Column(String(64))
# Matched customer reference (nullable, filled after matching)
hashed_customer_id = Column(Integer, ForeignKey("hashed_customers.id"), nullable=True, index=True)
hashed_customer_id = Column(
Integer, ForeignKey("hashed_customers.id"), nullable=True, index=True
)
# Guest classification
is_regular = Column(Boolean, default=False) # True if guest has many prior stays before appearing in our reservations
is_regular = Column(
Boolean, default=False
) # True if guest has many prior stays before appearing in our reservations
# Metadata
first_seen = Column(DateTime(timezone=True))
@@ -428,7 +452,7 @@ class ConversionGuest(Base):
def create_from_conversion_data(
cls,
hotel_id: str,
guest_id: str | None,
guest_id: int | None,
guest_first_name: str | None,
guest_last_name: str | None,
guest_email: str | None,
@@ -483,7 +507,9 @@ class ConversionGuest(Base):
self.hashed_country_code = self._normalize_and_hash(guest_country_code)
if guest_birth_date:
self.guest_birth_date = guest_birth_date
self.hashed_birth_date = self._normalize_and_hash(guest_birth_date.isoformat())
self.hashed_birth_date = self._normalize_and_hash(
guest_birth_date.isoformat()
)
self.last_seen = now
@@ -491,7 +517,9 @@ class Reservation(Base):
__tablename__ = "reservations"
id = Column(Integer, primary_key=True)
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="SET NULL"))
hashed_customer_id = Column(Integer, ForeignKey("hashed_customers.id", ondelete="CASCADE"))
hashed_customer_id = Column(
Integer, ForeignKey("hashed_customers.id", ondelete="CASCADE")
)
unique_id = Column(String, unique=True)
md5_unique_id = Column(String(32), unique=True) # max length 32 guaranteed
start_date = Column(Date)
@@ -578,9 +606,18 @@ class Conversion(Base):
)
# Reservation metadata from XML
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
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)
@@ -588,9 +625,6 @@ class Conversion(Base):
reservation_type = Column(String) # type attribute (e.g., "reservation")
booking_channel = Column(String) # bookingChannel attribute
# Advertising/tracking data - used for matching to existing reservations
advertising_medium = Column(
String, index=True
@@ -603,7 +637,9 @@ class Conversion(Base):
) # advertisingCampagne (contains fbclid/gclid)
# Attribution flags - track how this conversion was matched
directly_attributable = Column(Boolean, default=False) # Matched by ID (high confidence)
directly_attributable = Column(
Boolean, default=False
) # Matched by ID (high confidence)
guest_matched = Column(Boolean, default=False) # Matched by guest details only
# Metadata
@@ -617,7 +653,9 @@ 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"),
UniqueConstraint(
"hotel_id", "pms_reservation_id", name="uq_conversion_hotel_reservation"
),
)
# Relationships
@@ -690,7 +728,10 @@ class HotelInventory(Base):
id = Column(Integer, primary_key=True)
hotel_id = Column(
String(50), ForeignKey("hotels.hotel_id", ondelete="CASCADE"), nullable=False, index=True
String(50),
ForeignKey("hotels.hotel_id", ondelete="CASCADE"),
nullable=False,
index=True,
)
inv_type_code = Column(String(8), nullable=False, index=True)
inv_code = Column(String(16), nullable=True, index=True)
@@ -726,7 +767,10 @@ class RoomAvailability(Base):
id = Column(Integer, primary_key=True)
inventory_id = Column(
Integer, ForeignKey("hotel_inventory.id", ondelete="CASCADE"), nullable=False, index=True
Integer,
ForeignKey("hotel_inventory.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
date = Column(Date, nullable=False, index=True)
count_type_2 = Column(Integer, nullable=True)
@@ -739,7 +783,9 @@ class RoomAvailability(Base):
inventory_item = relationship("HotelInventory", back_populates="availability")
__table_args__ = (
UniqueConstraint("inventory_id", "date", name="uq_room_availability_unique_key"),
UniqueConstraint(
"inventory_id", "date", name="uq_room_availability_unique_key"
),
)
@@ -787,7 +833,9 @@ class WebhookEndpoint(Base):
id = Column(Integer, primary_key=True)
# Hotel association
hotel_id = Column(String(50), ForeignKey("hotels.hotel_id"), nullable=False, index=True)
hotel_id = Column(
String(50), ForeignKey("hotels.hotel_id"), nullable=False, index=True
)
# Webhook configuration
webhook_secret = Column(String(64), unique=True, nullable=False, index=True)
@@ -803,7 +851,7 @@ class WebhookEndpoint(Base):
webhook_requests = relationship("WebhookRequest", back_populates="webhook_endpoint")
__table_args__ = (
Index('idx_webhook_endpoint_hotel_type', 'hotel_id', 'webhook_type'),
Index("idx_webhook_endpoint_hotel_type", "hotel_id", "webhook_type"),
)
@@ -816,11 +864,17 @@ class WebhookRequest(Base):
# Request identification
payload_hash = Column(String(64), unique=True, nullable=False, index=True) # SHA256
webhook_endpoint_id = Column(Integer, ForeignKey("webhook_endpoints.id"), nullable=True, index=True)
hotel_id = Column(String(50), ForeignKey("hotels.hotel_id"), nullable=True, index=True)
webhook_endpoint_id = Column(
Integer, ForeignKey("webhook_endpoints.id"), nullable=True, index=True
)
hotel_id = Column(
String(50), ForeignKey("hotels.hotel_id"), nullable=True, index=True
)
# Processing tracking
status = Column(String(20), nullable=False, default=WebhookStatus.PENDING.value, index=True)
status = Column(
String(20), nullable=False, default=WebhookStatus.PENDING.value, index=True
)
# Status values: 'pending', 'processing', 'completed', 'failed' set by Enum WebhookStatus
processing_started_at = Column(DateTime(timezone=True), nullable=True)
@@ -841,16 +895,20 @@ class WebhookRequest(Base):
# Result tracking
created_customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True)
created_reservation_id = Column(Integer, ForeignKey("reservations.id"), nullable=True)
created_reservation_id = Column(
Integer, ForeignKey("reservations.id"), nullable=True
)
# Relationships
webhook_endpoint = relationship("WebhookEndpoint", back_populates="webhook_requests")
webhook_endpoint = relationship(
"WebhookEndpoint", back_populates="webhook_requests"
)
hotel = relationship("Hotel")
customer = relationship("Customer")
reservation = relationship("Reservation")
__table_args__ = (
Index('idx_webhook_status_created', 'status', 'created_at'),
Index('idx_webhook_hotel_created', 'hotel_id', 'created_at'),
Index('idx_webhook_purge_candidate', 'status', 'purged_at', 'created_at'),
Index("idx_webhook_status_created", "status", "created_at"),
Index("idx_webhook_hotel_created", "hotel_id", "created_at"),
Index("idx_webhook_purge_candidate", "status", "purged_at", "created_at"),
)