Migration to guest_table for conversion works

This commit is contained in:
Jonas Linter
2025-11-19 12:05:38 +01:00
parent c0e5c3c4db
commit 2cf4071c2f
4 changed files with 43096 additions and 1 deletions

View File

@@ -364,6 +364,116 @@ class HashedCustomer(Base):
customer = relationship("Customer", backref="hashed_version")
class ConversionGuest(Base):
"""Guest information from hotel PMS conversions, with hashed fields for privacy.
Stores both unhashed (for reference during transition) and hashed (SHA256 per Meta API)
versions of guest PII. Multiple conversions can reference the same guest if they have
the same hotel_id and guest_id (PMS guest identifier).
When multiple conversions for the same guest arrive with different guest info,
the most recent (by creation_time) data is kept as the canonical version.
"""
__tablename__ = "conversion_guests"
id = Column(Integer, primary_key=True)
# Natural keys from PMS (composite unique constraint)
hotel_id = Column(String, nullable=False, index=True)
guest_id = Column(String, index=True) # PMS guest ID (nullable for unidentified guests)
# Unhashed guest information (for reference/transition period)
guest_first_name = Column(String)
guest_last_name = Column(String)
guest_email = Column(String)
guest_country_code = Column(String)
guest_birth_date = Column(Date)
# Hashed guest information (SHA256, for privacy compliance)
hashed_first_name = Column(String(64), index=True)
hashed_last_name = Column(String(64), index=True)
hashed_email = Column(String(64), index=True)
hashed_country_code = Column(String(64))
hashed_birth_date = Column(String(64))
# Metadata
first_seen = Column(DateTime(timezone=True))
last_seen = Column(DateTime(timezone=True))
# Relationships
conversions = relationship("Conversion", back_populates="guest")
@staticmethod
def _normalize_and_hash(value):
"""Normalize and hash a value according to Meta Conversion API requirements."""
if not value:
return None
# Normalize: lowercase, strip whitespace
normalized = str(value).lower().strip()
# SHA256 hash
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
@classmethod
def create_from_conversion_data(
cls,
hotel_id: str,
guest_id: str | None,
guest_first_name: str | None,
guest_last_name: str | None,
guest_email: str | None,
guest_country_code: str | None,
guest_birth_date: Date | None,
now: DateTime,
):
"""Create a ConversionGuest from conversion guest data."""
return cls(
hotel_id=hotel_id,
guest_id=guest_id,
guest_first_name=guest_first_name,
guest_last_name=guest_last_name,
guest_email=guest_email,
guest_country_code=guest_country_code,
guest_birth_date=guest_birth_date,
hashed_first_name=cls._normalize_and_hash(guest_first_name),
hashed_last_name=cls._normalize_and_hash(guest_last_name),
hashed_email=cls._normalize_and_hash(guest_email),
hashed_country_code=cls._normalize_and_hash(guest_country_code),
hashed_birth_date=cls._normalize_and_hash(
guest_birth_date.isoformat() if guest_birth_date else None
),
first_seen=now,
last_seen=now,
)
def update_from_conversion_data(
self,
guest_first_name: str | None,
guest_last_name: str | None,
guest_email: str | None,
guest_country_code: str | None,
guest_birth_date: Date | None,
now: DateTime,
):
"""Update ConversionGuest with newer guest data, preferring non-null values."""
# Only update if new data is provided (not null)
if guest_first_name:
self.guest_first_name = guest_first_name
self.hashed_first_name = self._normalize_and_hash(guest_first_name)
if guest_last_name:
self.guest_last_name = guest_last_name
self.hashed_last_name = self._normalize_and_hash(guest_last_name)
if guest_email:
self.guest_email = guest_email
self.hashed_email = self._normalize_and_hash(guest_email)
if guest_country_code:
self.guest_country_code = guest_country_code
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.last_seen = now
class Reservation(Base):
__tablename__ = "reservations"
id = Column(Integer, primary_key=True)
@@ -445,6 +555,9 @@ class Conversion(Base):
hashed_customer_id = Column(
Integer, ForeignKey("hashed_customers.id"), nullable=True, index=True
)
conversion_guest_id = Column(
Integer, ForeignKey("conversion_guests.id"), nullable=True, index=True
)
# Reservation metadata from XML
hotel_id = Column(String, index=True) # hotelID attribute
@@ -482,6 +595,7 @@ 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")
conversion_rooms = relationship(
"ConversionRoom", back_populates="conversion", cascade="all, delete-orphan"
)