Merge branch 'db_fixes_plus_free_rooms' of https://gitea.99tales.net/jonas/alpinebits_python into db_fixes_plus_free_rooms

This commit is contained in:
Jonas Linter
2025-12-03 22:37:08 +01:00
27 changed files with 1998 additions and 957 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

@@ -28,7 +28,7 @@ from fastapi.security import (
from pydantic import BaseModel
from slowapi.errors import RateLimitExceeded
from sqlalchemy import and_, select, update
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
from sqlalchemy.orm import selectinload
from alpine_bits_python.hotel_service import HotelService
@@ -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:
@@ -719,7 +719,7 @@ async def validate_basic_auth(
async def handle_webhook_unified(
request: Request,
webhook_secret: str,
db_session=Depends(get_async_session),
db_session: AsyncSession = Depends(get_async_session),
):
"""Unified webhook handler with deduplication and routing.
@@ -846,6 +846,9 @@ async def handle_webhook_unified(
if not webhook_endpoint:
raise HTTPException(status_code=404, detail="Webhook not found")
webhook_endpoint_id = webhook_endpoint.id
webhook_hotel_id = webhook_endpoint.hotel_id
# Verify hotel is active
if not webhook_endpoint.hotel.is_active:
raise HTTPException(status_code=404, detail="Hotel is not active")
@@ -860,8 +863,8 @@ async def handle_webhook_unified(
webhook_request_data = WebhookRequestData(
payload_json=payload,
webhook_endpoint_id=webhook_endpoint.id,
hotel_id=webhook_endpoint.hotel_id,
webhook_endpoint_id=webhook_endpoint_id,
hotel_id=webhook_hotel_id,
status=WebhookStatus.PROCESSING,
processing_started_at=timestamp,
created_at=timestamp,
@@ -923,12 +926,17 @@ async def handle_webhook_unified(
db_session.add(webhook_request)
await db_session.flush()
webhook_request_id = webhook_request.id
try:
# 6. Get processor for webhook_type
processor = webhook_registry.get_processor(webhook_endpoint.webhook_type)
if not processor:
raise ValueError(f"No processor for type: {webhook_endpoint.webhook_type}")
# Persist the webhook row before handing off to processors
await db_session.commit()
# 7. Process webhook with simplified interface
result = await processor.process(
webhook_request=webhook_request,
@@ -937,34 +945,50 @@ async def handle_webhook_unified(
event_dispatcher=request.app.state.event_dispatcher,
)
# 8. Update status and link created entities when available
webhook_request.status = WebhookStatus.COMPLETED
webhook_request.processing_completed_at = datetime.now(UTC)
if not db_session.in_transaction():
await db_session.begin()
created_customer_id = result.get("customer_id") if isinstance(result, dict) else None
created_reservation_id = (
result.get("reservation_id") if isinstance(result, dict) else None
completion_values = {
"status": WebhookStatus.COMPLETED,
"processing_completed_at": datetime.now(UTC),
}
if isinstance(result, dict):
created_customer_id = result.get("customer_id")
created_reservation_id = result.get("reservation_id")
if created_customer_id:
completion_values["created_customer_id"] = created_customer_id
if created_reservation_id:
completion_values["created_reservation_id"] = created_reservation_id
await db_session.execute(
update(WebhookRequest)
.where(WebhookRequest.id == webhook_request_id)
.values(**completion_values)
)
if created_customer_id:
webhook_request.created_customer_id = created_customer_id
if created_reservation_id:
webhook_request.created_reservation_id = created_reservation_id
await db_session.commit()
return {
**result,
"webhook_id": webhook_request.id,
"hotel_id": webhook_endpoint.hotel_id,
"webhook_id": webhook_request_id,
"hotel_id": webhook_hotel_id,
}
except Exception as e:
_LOGGER.exception("Error processing webhook: %s", e)
webhook_request.status = WebhookStatus.FAILED
webhook_request.last_error = str(e)[:2000]
webhook_request.processing_completed_at = datetime.now(UTC)
await db_session.rollback()
if not db_session.in_transaction():
await db_session.begin()
await db_session.execute(
update(WebhookRequest)
.where(WebhookRequest.id == webhook_request_id)
.values(
status=WebhookStatus.FAILED,
last_error=str(e)[:2000],
processing_completed_at=datetime.now(UTC),
)
)
await db_session.commit()
raise HTTPException(status_code=500, detail="Error processing webhook")
@@ -1188,7 +1212,9 @@ async def _process_conversion_xml_background(
# Now process the conversion XML
_LOGGER.info("Starting database processing of %s", filename)
conversion_service = ConversionService(session_maker, hotel.hotel_id)
processing_stats = await conversion_service.process_conversion_xml(xml_content)
processing_stats = await conversion_service.process_conversion_xml(xml_content, run_full_guest_matching=True)
await conversion_service.classify_regular_guests(24)
_LOGGER.info(
"Conversion processing complete for %s: %s", filename, processing_stats

File diff suppressed because it is too large Load Diff

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

@@ -6,7 +6,7 @@ from pydantic import ValidationError
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from .db import Customer, HashedCustomer
from .db import Customer
from .logging_config import get_logger
from .schemas import CustomerData
@@ -53,13 +53,13 @@ class CustomerService:
if "phone" in customer_data:
customer.phone = customer_data["phone"]
self.session.add(customer)
await self.session.flush() # Flush to get the customer.id
# Set creation timestamp
customer.created_at = datetime.now(UTC)
# Create hashed version
hashed_customer = customer.create_hashed_customer()
hashed_customer.created_at = datetime.now(UTC)
self.session.add(hashed_customer)
# Update hashed fields
customer.update_hashed_fields()
self.session.add(customer)
if auto_commit:
await self.session.commit()
@@ -130,29 +130,8 @@ class CustomerService:
if "phone" in update_data:
customer.phone = update_data["phone"]
# Update or create hashed version
result = await self.session.execute(
select(HashedCustomer).where(HashedCustomer.customer_id == customer.id)
)
hashed_customer = result.scalar_one_or_none()
if hashed_customer:
# Update existing hashed customer
new_hashed = customer.create_hashed_customer()
hashed_customer.hashed_email = new_hashed.hashed_email
hashed_customer.hashed_phone = new_hashed.hashed_phone
hashed_customer.hashed_given_name = new_hashed.hashed_given_name
hashed_customer.hashed_surname = new_hashed.hashed_surname
hashed_customer.hashed_city = new_hashed.hashed_city
hashed_customer.hashed_postal_code = new_hashed.hashed_postal_code
hashed_customer.hashed_country_code = new_hashed.hashed_country_code
hashed_customer.hashed_gender = new_hashed.hashed_gender
hashed_customer.hashed_birth_date = new_hashed.hashed_birth_date
else:
# Create new hashed customer if it doesn't exist
hashed_customer = customer.create_hashed_customer()
hashed_customer.created_at = datetime.now(UTC)
self.session.add(hashed_customer)
# Update hashed fields
customer.update_hashed_fields()
if auto_commit:
await self.session.commit()
@@ -200,26 +179,27 @@ class CustomerService:
# Create new customer (either no contact_id or customer doesn't exist)
return await self.create_customer(customer_data, auto_commit=auto_commit)
async def get_hashed_customer(self, customer_id: int) -> HashedCustomer | None:
async def get_customer(self, customer_id: int) -> Customer | None:
"""Get the hashed version of a customer.
Args:
customer_id: The customer ID
Returns:
HashedCustomer instance if found, None otherwise
Customer instance if found, None otherwise
"""
result = await self.session.execute(
select(HashedCustomer).where(HashedCustomer.customer_id == customer_id)
select(Customer).where(Customer.id == customer_id)
)
return result.scalar_one_or_none()
async def hash_existing_customers(self) -> int:
"""Hash all existing customers that don't have a hashed version yet.
"""Hash all existing customers that don't have hashed fields populated yet.
This is useful for backfilling hashed data for customers created
before the hashing system was implemented.
before the hashing system was implemented, or after migrating from
the separate hashed_customers table.
Also validates and sanitizes customer data (e.g., normalizes country
codes to uppercase). Customers with invalid data that cannot be fixed
@@ -229,62 +209,64 @@ class CustomerService:
Number of customers that were hashed
"""
# Get all customers
result = await self.session.execute(select(Customer))
# Get all customers without hashed data
result = await self.session.execute(
select(Customer).where(Customer.hashed_email.is_(None))
)
customers = result.scalars().all()
hashed_count = 0
skipped_count = 0
for customer in customers:
# Check if this customer already has a hashed version
existing_hashed = await self.get_hashed_customer(customer.id)
if not existing_hashed:
# Validate and sanitize customer data before hashing
customer_dict = {
"given_name": customer.given_name,
"surname": customer.surname,
"name_prefix": customer.name_prefix,
"email_address": customer.email_address,
"phone": customer.phone,
"email_newsletter": customer.email_newsletter,
"address_line": customer.address_line,
"city_name": customer.city_name,
"postal_code": customer.postal_code,
"country_code": customer.country_code,
"gender": customer.gender,
"birth_date": customer.birth_date,
"language": customer.language,
"address_catalog": customer.address_catalog,
"name_title": customer.name_title,
}
# Validate and sanitize customer data before hashing
customer_dict = {
"given_name": customer.given_name,
"surname": customer.surname,
"name_prefix": customer.name_prefix,
"email_address": customer.email_address,
"phone": customer.phone,
"email_newsletter": customer.email_newsletter,
"address_line": customer.address_line,
"city_name": customer.city_name,
"postal_code": customer.postal_code,
"country_code": customer.country_code,
"gender": customer.gender,
"birth_date": customer.birth_date,
"language": customer.language,
"address_catalog": customer.address_catalog,
"name_title": customer.name_title,
}
try:
# Validate through Pydantic (normalizes country code)
validated = CustomerData(**customer_dict)
try:
# Validate through Pydantic (normalizes country code)
validated = CustomerData(**customer_dict)
# Update customer with sanitized data
# Exclude 'phone_numbers' as Customer model uses 'phone' field
for key, value in validated.model_dump(
exclude_none=True, exclude={"phone_numbers"}
).items():
if hasattr(customer, key):
setattr(customer, key, value)
# Update customer with sanitized data
# Exclude 'phone_numbers' as Customer model uses 'phone' field
for key, value in validated.model_dump(
exclude_none=True, exclude={"phone_numbers"}
).items():
if hasattr(customer, key):
setattr(customer, key, value)
# Create hashed version with sanitized data
hashed_customer = customer.create_hashed_customer()
hashed_customer.created_at = datetime.now(UTC)
self.session.add(hashed_customer)
hashed_count += 1
# Update hashed fields with sanitized data
customer.update_hashed_fields()
except ValidationError as e:
# Skip customers with invalid data and log
skipped_count += 1
_LOGGER.warning(
"Skipping customer ID %s due to validation error: %s",
customer.id,
e,
)
# Set created_at if not already set
if not customer.created_at:
customer.created_at = datetime.now(UTC)
hashed_count += 1
except ValidationError as e:
# Skip customers with invalid data and log
skipped_count += 1
_LOGGER.warning(
"Skipping customer ID %s due to validation error: %s",
customer.id,
e,
)
if hashed_count > 0:
await self.session.commit()

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
@@ -311,6 +311,20 @@ class Customer(Base):
language = Column(String)
address_catalog = Column(Boolean) # Added for XML
name_title = Column(String) # Added for XML
# Hashed fields for Meta Conversion API (SHA256)
hashed_email = Column(String(64))
hashed_phone = Column(String(64))
hashed_given_name = Column(String(64))
hashed_surname = Column(String(64))
hashed_city = Column(String(64))
hashed_postal_code = Column(String(64))
hashed_country_code = Column(String(64))
hashed_gender = Column(String(64))
hashed_birth_date = Column(String(64))
created_at = Column(DateTime(timezone=True))
reservations = relationship("Reservation", back_populates="customer")
def __repr__(self):
@@ -335,53 +349,19 @@ class Customer(Base):
# SHA256 hash
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
def create_hashed_customer(self):
"""Create a HashedCustomer instance from this Customer."""
return HashedCustomer(
customer_id=self.id,
contact_id=self.contact_id,
hashed_email=self._normalize_and_hash(self.email_address),
hashed_phone=self._normalize_and_hash(self.phone),
hashed_given_name=self._normalize_and_hash(self.given_name),
hashed_surname=self._normalize_and_hash(self.surname),
hashed_city=self._normalize_and_hash(self.city_name),
hashed_postal_code=self._normalize_and_hash(self.postal_code),
hashed_country_code=self._normalize_and_hash(self.country_code),
hashed_gender=self._normalize_and_hash(self.gender),
hashed_birth_date=self._normalize_and_hash(self.birth_date),
)
def update_hashed_fields(self):
"""Update the hashed fields based on current plaintext values."""
self.hashed_email = self._normalize_and_hash(self.email_address)
self.hashed_phone = self._normalize_and_hash(self.phone)
self.hashed_given_name = self._normalize_and_hash(self.given_name)
self.hashed_surname = self._normalize_and_hash(self.surname)
self.hashed_city = self._normalize_and_hash(self.city_name)
self.hashed_postal_code = self._normalize_and_hash(self.postal_code)
self.hashed_country_code = self._normalize_and_hash(self.country_code)
self.hashed_gender = self._normalize_and_hash(self.gender)
self.hashed_birth_date = self._normalize_and_hash(self.birth_date)
class HashedCustomer(Base):
"""Hashed customer data for Meta Conversion API.
Stores SHA256 hashed versions of customer PII according to Meta's requirements.
This allows sending conversion events without exposing raw customer data.
"""
__tablename__ = "hashed_customers"
id = Column(Integer, primary_key=True)
customer_id = Column(
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
hashed_phone = Column(String(64))
hashed_given_name = Column(String(64))
hashed_surname = Column(String(64))
hashed_city = Column(String(64))
hashed_postal_code = Column(String(64))
hashed_country_code = Column(String(64))
hashed_gender = Column(String(64))
hashed_birth_date = Column(String(64))
created_at = Column(DateTime(timezone=True))
customer = relationship(
"Customer", backref=backref("hashed_version", uselist=False, lazy="joined")
)
class ConversionGuest(Base):
@@ -420,23 +400,30 @@ class ConversionGuest(Base):
hashed_country_code = Column(String(64))
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
)
# Guest classification
is_regular = Column(
Boolean, default=False
) # True if guest has many prior stays before appearing in our reservations
# Guest classification
is_awareness_guest = Column(
Boolean, default=False
) # True if guests first stay was from our campaigns
# Metadata
first_seen = Column(DateTime(timezone=True))
last_seen = Column(DateTime(timezone=True))
# Relationships
conversions = relationship("Conversion", back_populates="guest")
hashed_customer = relationship("HashedCustomer", backref="conversion_guests")
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))",
)
@staticmethod
def _normalize_and_hash(value):
@@ -517,9 +504,6 @@ 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")
)
unique_id = Column(String, unique=True)
md5_unique_id = Column(String(32), unique=True) # max length 32 guaranteed
start_date = Column(Date)
@@ -541,15 +525,14 @@ 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)
room_classification_code = Column(String)
room_type = Column(String)
customer = relationship("Customer", back_populates="reservations")
hashed_customer = relationship("HashedCustomer", backref="reservations")
# Table for tracking acknowledged requests by client
@@ -569,7 +552,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))
@@ -601,9 +584,6 @@ class Conversion(Base):
Integer, ForeignKey("reservations.id"), nullable=True, index=True
)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True, index=True)
hashed_customer_id = Column(
Integer, ForeignKey("hashed_customers.id"), nullable=True, index=True
)
# Reservation metadata from XML
hotel_id = Column(
@@ -646,23 +626,28 @@ 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
__table_args__ = (
UniqueConstraint(
"hotel_id", "pms_reservation_id", name="uq_conversion_hotel_reservation"
),
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"
name="fk_conversions_guest",
),
)
# Relationships
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

@@ -7,7 +7,7 @@ from typing import Optional
from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession
from .db import AckedRequest, Customer, HashedCustomer, Reservation
from .db import AckedRequest, Customer, Reservation
from .schemas import ReservationData
@@ -64,17 +64,6 @@ class ReservationService:
reservation_data, customer_id
)
# Automatically populate hashed_customer_id from the customer
# Since hashed_customer is always created when a customer is created,
# we can get it by querying for the hashed_customer with matching customer_id
hashed_customer_result = await self.session.execute(
select(HashedCustomer).where(
HashedCustomer.customer_id == customer_id
)
)
hashed_customer = hashed_customer_result.scalar_one_or_none()
if hashed_customer:
reservation.hashed_customer_id = hashed_customer.id
self.session.add(reservation)
@@ -181,7 +170,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)
@@ -562,7 +562,6 @@ class ConversionData(BaseModel):
# Foreign key references (nullable - matched after creation)
reservation_id: int | None = Field(None, gt=0)
customer_id: int | None = Field(None, gt=0)
hashed_customer_id: int | None = Field(None, gt=0)
# Required reservation metadata from PMS
hotel_id: str = Field(..., min_length=1, max_length=50)
@@ -591,7 +590,7 @@ class ConversionData(BaseModel):
@field_validator(
"pms_reservation_id", "guest_id", "reservation_id", "customer_id",
"hashed_customer_id", mode="before"
mode="before"
)
@classmethod
def convert_int_fields(cls, v: Any) -> int | None:

View File

@@ -51,7 +51,6 @@ from alpine_bits_python.db import (
AckedRequest,
Base,
Customer,
HashedCustomer,
Reservation,
get_database_url,
)
@@ -306,7 +305,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

@@ -203,7 +203,7 @@ async def process_wix_form_submission(
"name_title": None,
}
# This automatically creates/updates both Customer and HashedCustomer
# This automatically creates/updates Customer
db_customer = await customer_service.get_or_create_customer(customer_data)
# Determine hotel_code and hotel_name
@@ -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,