merge_db_fixes_to_main #16
@@ -471,7 +471,6 @@ class ConversionService:
|
||||
"total_daily_sales": 0,
|
||||
"matched_to_reservation": 0,
|
||||
"matched_to_customer": 0,
|
||||
"matched_to_hashed_customer": 0,
|
||||
"unmatched": 0,
|
||||
"errors": 0,
|
||||
}
|
||||
@@ -1471,41 +1470,40 @@ class ConversionService:
|
||||
session: AsyncSession for database queries
|
||||
|
||||
"""
|
||||
# Get all ConversionGuests that have ANY customer link
|
||||
# This includes:
|
||||
# 1. Guests matched via guest-details (hashed_customer_id is not null)
|
||||
# 2. Guests matched via ID-based matching (customer_id is not null via conversion)
|
||||
# Collect every guest/customer pair derived from conversions.
|
||||
result = await session.execute(
|
||||
select(ConversionGuest).where(
|
||||
ConversionGuest.hashed_customer_id.isnot(None)
|
||||
select(Conversion.guest_id, Conversion.customer_id).where(
|
||||
Conversion.guest_id.isnot(None), Conversion.customer_id.isnot(None)
|
||||
)
|
||||
)
|
||||
matched_guests = result.scalars().all()
|
||||
guest_customer_rows = result.all()
|
||||
|
||||
if not matched_guests:
|
||||
if not guest_customer_rows:
|
||||
_LOGGER.debug("Phase 3d: No matched guests to check for regularity")
|
||||
return
|
||||
|
||||
# Deduplicate by guest_id to avoid recalculating when multiple conversions share the same guest.
|
||||
guest_to_customer: dict[int, int] = {}
|
||||
for guest_id, customer_id in guest_customer_rows:
|
||||
if guest_id is None or customer_id is None:
|
||||
continue
|
||||
if guest_id not in guest_to_customer:
|
||||
guest_to_customer[guest_id] = customer_id
|
||||
elif guest_to_customer[guest_id] != customer_id:
|
||||
_LOGGER.warning(
|
||||
"Guest %s linked to multiple customers (%s, %s); keeping first match",
|
||||
guest_id,
|
||||
guest_to_customer[guest_id],
|
||||
customer_id,
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Phase 3d: Checking regularity for %d matched guests", len(matched_guests)
|
||||
"Phase 3d: Checking regularity for %d matched guests",
|
||||
len(guest_to_customer),
|
||||
)
|
||||
|
||||
for conversion_guest in matched_guests:
|
||||
if not conversion_guest.hashed_customer_id:
|
||||
continue
|
||||
|
||||
# Get the customer ID from the hashed_customer
|
||||
hashed_customer_result = await session.execute(
|
||||
select(Customer).where(
|
||||
Customer.id == conversion_guest.hashed_customer_id
|
||||
)
|
||||
)
|
||||
hashed_customer = hashed_customer_result.scalar_one_or_none()
|
||||
|
||||
if hashed_customer and hashed_customer.id:
|
||||
await self._check_if_guest_is_regular(
|
||||
conversion_guest.guest_id, hashed_customer.id, session
|
||||
)
|
||||
for guest_id, customer_id in guest_to_customer.items():
|
||||
await self._check_if_guest_is_regular(guest_id, customer_id, session)
|
||||
|
||||
async def _match_conversions_from_db_sequential(
|
||||
self, pms_reservation_ids: list[str], stats: dict[str, int]
|
||||
@@ -1721,7 +1719,6 @@ class ConversionService:
|
||||
# Guest detail matching is deferred to Phase 3b/3c
|
||||
matched_reservation = None
|
||||
matched_customer = None
|
||||
matched_hashed_customer = None
|
||||
|
||||
if conversion.advertising_campagne:
|
||||
matched_reservation = await self._match_by_advertising(
|
||||
@@ -1755,10 +1752,6 @@ class ConversionService:
|
||||
conversion.directly_attributable = True
|
||||
conversion.guest_matched = False
|
||||
|
||||
# Update conversion_guest with hashed_customer reference if matched
|
||||
if conversion_guest and matched_hashed_customer:
|
||||
conversion_guest.hashed_customer_id = matched_hashed_customer.id
|
||||
|
||||
conversion.updated_at = datetime.now()
|
||||
|
||||
# Update stats if provided
|
||||
@@ -1767,8 +1760,6 @@ class ConversionService:
|
||||
stats["matched_to_reservation"] += 1
|
||||
elif matched_customer:
|
||||
stats["matched_to_customer"] += 1
|
||||
elif matched_hashed_customer:
|
||||
stats["matched_to_hashed_customer"] += 1
|
||||
else:
|
||||
stats["unmatched"] += 1
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user