Only update updated_at timestamps if something actually changes.

This commit is contained in:
Jonas Linter
2025-12-09 14:06:00 +01:00
parent c4bb9c524d
commit 13e404d07c

View File

@@ -119,6 +119,28 @@ class ConversionService:
f"session must be AsyncSession or SessionMaker, got {type(session)}" f"session must be AsyncSession or SessionMaker, got {type(session)}"
) )
@staticmethod
def _update_timestamp_if_modified(
obj: Conversion | ConversionRoom, session: AsyncSession
) -> bool:
"""Update the updated_at timestamp only if the object has been modified.
Uses SQLAlchemy's change tracking to determine if any scalar attributes
have changed. Only updates the timestamp if actual changes were detected.
Args:
obj: The ORM object to check and potentially update
session: The session managing this object
Returns:
True if the object was modified and timestamp was updated, False otherwise
"""
if session.is_modified(obj, include_collections=False):
obj.updated_at = datetime.now()
return True
return False
@staticmethod @staticmethod
def _parse_required_int(value: str | None, field_name: str) -> int: def _parse_required_int(value: str | None, field_name: str) -> int:
"""Parse an integer attribute that must be present.""" """Parse an integer attribute that must be present."""
@@ -897,26 +919,39 @@ class ConversionService:
existing_conversion = existing_result.scalar_one_or_none() existing_conversion = existing_result.scalar_one_or_none()
if existing_conversion: if existing_conversion:
# Update existing conversion - only update reservation metadata and advertising data # Update existing conversion using Pydantic validation
# Guest info is stored in ConversionGuest table, not here # Guest info is stored in ConversionGuest table, not here
# Don't clear reservation/customer links (matching logic will update if needed) # Preserve reservation/customer links (matching logic will update if needed)
existing_conversion.reservation_number = ( conversion_data = ConversionData(
parsed_reservation.reservation_number hotel_id=hotel_id,
pms_reservation_id=pms_reservation_id,
guest_id=parsed_reservation.guest_id,
reservation_number=parsed_reservation.reservation_number,
reservation_date=parsed_reservation.reservation_date,
creation_time=parsed_reservation.creation_time,
reservation_type=parsed_reservation.reservation_type,
booking_channel=parsed_reservation.booking_channel,
advertising_medium=parsed_reservation.advertising_medium,
advertising_partner=parsed_reservation.advertising_partner,
advertising_campagne=parsed_reservation.advertising_campagne,
# Preserve existing values (managed separately)
created_at=existing_conversion.created_at,
reservation_id=existing_conversion.reservation_id,
customer_id=existing_conversion.customer_id,
directly_attributable=existing_conversion.directly_attributable,
guest_matched=existing_conversion.guest_matched,
) )
existing_conversion.reservation_date = parsed_reservation.reservation_date
existing_conversion.creation_time = parsed_reservation.creation_time # Apply validated data, excluding managed fields
existing_conversion.reservation_type = parsed_reservation.reservation_type validated_dict = conversion_data.model_dump(
existing_conversion.booking_channel = parsed_reservation.booking_channel exclude={'created_at', 'updated_at', 'reservation_id', 'customer_id',
existing_conversion.advertising_medium = ( 'directly_attributable', 'guest_matched'}
parsed_reservation.advertising_medium
) )
existing_conversion.advertising_partner = ( for key, value in validated_dict.items():
parsed_reservation.advertising_partner setattr(existing_conversion, key, value)
)
existing_conversion.advertising_campagne = ( # Only update timestamp if something actually changed
parsed_reservation.advertising_campagne self._update_timestamp_if_modified(existing_conversion, session)
)
existing_conversion.updated_at = datetime.now()
conversion = existing_conversion conversion = existing_conversion
_LOGGER.debug( _LOGGER.debug(
"Updated conversion %s (pms_id=%s)", "Updated conversion %s (pms_id=%s)",
@@ -998,7 +1033,8 @@ class ConversionService:
else None else None
) )
existing_room_reservation.total_revenue = room_reservation.total_revenue existing_room_reservation.total_revenue = room_reservation.total_revenue
existing_room_reservation.updated_at = datetime.now() # Only update timestamp if something actually changed
self._update_timestamp_if_modified(existing_room_reservation, session)
_LOGGER.debug( _LOGGER.debug(
"Updated room reservation %s (pms_id=%s, room=%s)", "Updated room reservation %s (pms_id=%s, room=%s)",
existing_room_reservation.id, existing_room_reservation.id,
@@ -1358,8 +1394,8 @@ class ConversionService:
# ID-based matches are always directly attributable # ID-based matches are always directly attributable
conversion.directly_attributable = True conversion.directly_attributable = True
conversion.guest_matched = False conversion.guest_matched = False
# Only update timestamp if something actually changed
conversion.updated_at = datetime.now() self._update_timestamp_if_modified(conversion, session)
# Update stats if provided # Update stats if provided
if stats is not None: if stats is not None:
@@ -1506,7 +1542,8 @@ class ConversionService:
elif conversion.reservation_id is None: elif conversion.reservation_id is None:
conversion.directly_attributable = False conversion.directly_attributable = False
conversion.updated_at = datetime.now() # Only update timestamp if something actually changed
self._update_timestamp_if_modified(conversion, session)
return matched_reservation, matched_customer return matched_reservation, matched_customer