Migration to guest_table for conversion works
This commit is contained in:
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user