Free-Rooms implementation #17
@@ -842,4 +842,4 @@ class AlpineBitsServer:
|
||||
|
||||
|
||||
# Ensure FreeRoomsAction is registered with ServerCapabilities discovery
|
||||
#from .free_rooms_action import FreeRoomsAction
|
||||
from .free_rooms_action import FreeRoomsAction
|
||||
|
||||
@@ -119,6 +119,28 @@ class ConversionService:
|
||||
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
|
||||
def _parse_required_int(value: str | None, field_name: str) -> int:
|
||||
"""Parse an integer attribute that must be present."""
|
||||
@@ -897,26 +919,39 @@ class ConversionService:
|
||||
existing_conversion = existing_result.scalar_one_or_none()
|
||||
|
||||
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
|
||||
# Don't clear reservation/customer links (matching logic will update if needed)
|
||||
existing_conversion.reservation_number = (
|
||||
parsed_reservation.reservation_number
|
||||
# Preserve reservation/customer links (matching logic will update if needed)
|
||||
conversion_data = ConversionData(
|
||||
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
|
||||
existing_conversion.reservation_type = parsed_reservation.reservation_type
|
||||
existing_conversion.booking_channel = parsed_reservation.booking_channel
|
||||
existing_conversion.advertising_medium = (
|
||||
parsed_reservation.advertising_medium
|
||||
|
||||
# Apply validated data, excluding managed fields
|
||||
validated_dict = conversion_data.model_dump(
|
||||
exclude={'created_at', 'updated_at', 'reservation_id', 'customer_id',
|
||||
'directly_attributable', 'guest_matched'}
|
||||
)
|
||||
existing_conversion.advertising_partner = (
|
||||
parsed_reservation.advertising_partner
|
||||
)
|
||||
existing_conversion.advertising_campagne = (
|
||||
parsed_reservation.advertising_campagne
|
||||
)
|
||||
existing_conversion.updated_at = datetime.now()
|
||||
for key, value in validated_dict.items():
|
||||
setattr(existing_conversion, key, value)
|
||||
|
||||
# Only update timestamp if something actually changed
|
||||
self._update_timestamp_if_modified(existing_conversion, session)
|
||||
conversion = existing_conversion
|
||||
_LOGGER.debug(
|
||||
"Updated conversion %s (pms_id=%s)",
|
||||
@@ -998,7 +1033,8 @@ class ConversionService:
|
||||
else None
|
||||
)
|
||||
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(
|
||||
"Updated room reservation %s (pms_id=%s, room=%s)",
|
||||
existing_room_reservation.id,
|
||||
@@ -1358,8 +1394,8 @@ class ConversionService:
|
||||
# ID-based matches are always directly attributable
|
||||
conversion.directly_attributable = True
|
||||
conversion.guest_matched = False
|
||||
|
||||
conversion.updated_at = datetime.now()
|
||||
# Only update timestamp if something actually changed
|
||||
self._update_timestamp_if_modified(conversion, session)
|
||||
|
||||
# Update stats if provided
|
||||
if stats is not None:
|
||||
@@ -1506,7 +1542,8 @@ class ConversionService:
|
||||
elif conversion.reservation_id is None:
|
||||
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
|
||||
|
||||
|
||||
@@ -290,19 +290,30 @@ class FreeRoomsAction(AlpineBitsAction):
|
||||
# Validate standard inventory entries
|
||||
inv_type_code = (sac.inv_type_code or "").strip()
|
||||
if not inv_type_code:
|
||||
error_message = "InvTypeCode is required unless AllInvCode=\"true\" or similar truthy values"
|
||||
_LOGGER.info(error_message)
|
||||
raise FreeRoomsProcessingError(
|
||||
"InvTypeCode is required unless AllInvCode=\"true\"",
|
||||
error_message,
|
||||
HttpStatusCode.BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Validate date range
|
||||
start_date, end_date = self._parse_date_range(sac.start, sac.end)
|
||||
|
||||
# Check if this inventory entry has any counts (available rooms)
|
||||
# Entries without counts represent unavailable rooms
|
||||
has_availability = inventory.inv_counts is not None and inventory.inv_counts.inv_count
|
||||
|
||||
# Check for overlap with closing seasons
|
||||
# Only entries with availability (counts) cannot overlap with closing seasons
|
||||
# Entries without counts (unavailable rooms) can overlap with closing seasons
|
||||
if has_availability:
|
||||
for closing_start, closing_end in closing_season_ranges:
|
||||
if self._date_ranges_overlap(start_date, end_date, closing_start, closing_end):
|
||||
error_message = f"Inventory entry ({start_date} to {end_date}) overlaps with closing season ({closing_start} to {closing_end})"
|
||||
_LOGGER.info(error_message)
|
||||
raise FreeRoomsProcessingError(
|
||||
f"Inventory entry ({start_date} to {end_date}) overlaps with closing season ({closing_start} to {closing_end})",
|
||||
error_message,
|
||||
HttpStatusCode.BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -586,7 +597,12 @@ class FreeRoomsAction(AlpineBitsAction):
|
||||
self,
|
||||
sac: OtaHotelInvCountNotifRq.Inventories.Inventory.StatusApplicationControl,
|
||||
) -> bool:
|
||||
return (sac.all_inv_code or "").strip().lower() == "true"
|
||||
"""Check if AllInvCode is a truthy boolean value.
|
||||
|
||||
Accepts: "true", "True", "TRUE", "1", "yes", "Yes", "YES", etc.
|
||||
"""
|
||||
value = (sac.all_inv_code or "").strip().lower()
|
||||
return value in ("true", "1", "yes")
|
||||
|
||||
def _extract_counts(
|
||||
self,
|
||||
|
||||
549
tests/test_data/ClosingSeasons.xml
Normal file
549
tests/test_data/ClosingSeasons.xml
Normal file
@@ -0,0 +1,549 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!--
|
||||
Bespielfile von Sebastian zum testen der Closing Seasons Funktionalität
|
||||
-->
|
||||
<OTA_HotelInvCountNotifRQ xmlns='http://www.opentravel.org/OTA/2003/05' Version='3.000' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='http://www.opentravel.org/OTA/2003/05 OTA_HotelInvCountNotifRQ.xsd'>
|
||||
<UniqueID Type='16' ID='1' Instance='CompleteSet'/>
|
||||
<Inventories HotelCode='TESTHOTEL'>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' AllInvCode='1'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='106' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='106' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='106' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-09' InvCode='107' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-10' End='2025-12-19' InvCode='107' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='107' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2025-12-28' InvCode='107' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-29' End='2026-01-04' InvCode='107' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2026-01-05' End='2026-01-31' InvCode='107' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='108' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='108' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='108' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='206' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='206' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='206' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='207' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='207' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='207' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='208' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='208' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='208' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='306' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='306' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='306' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='307' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='307' InvTypeCode='EZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='307' InvTypeCode='EZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='101' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='101' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='101' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2026-01-31' InvCode='102' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='103' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='103' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='103' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='104' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='104' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-04' InvCode='104' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2026-01-05' End='2026-01-05' InvCode='104' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2026-01-06' End='2026-01-31' InvCode='104' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='105' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='105' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='105' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='201' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='201' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='201' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='202' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='202' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='202' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='203' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='203' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='203' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='204' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='204' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='204' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='205' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2026-01-05' InvCode='205' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2026-01-06' End='2026-01-31' InvCode='205' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='301' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='301' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='301' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='302' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='302' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='302' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='303' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='303' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='303' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='304' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-25' InvCode='304' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2026-01-31' InvCode='304' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='305' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='305' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='305' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='501' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='501' InvTypeCode='DZ'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='501' InvTypeCode='DZ'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-11' InvCode='109' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-12' End='2025-12-24' InvCode='109' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-25' End='2025-12-25' InvCode='109' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-26' End='2025-12-26' InvCode='109' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-27' End='2026-01-13' InvCode='109' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2026-01-14' End='2026-01-14' InvCode='109' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2026-01-15' End='2026-01-31' InvCode='109' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-16' InvCode='110' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-17' End='2025-12-23' InvCode='110' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='110' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-17' InvCode='209' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-18' End='2025-12-23' InvCode='209' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='209' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='210' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='210' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='210' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='309' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='309' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='309' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='310' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='310' InvTypeCode='SUI'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='310' InvTypeCode='SUI'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='401' InvTypeCode='FW'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='401' InvTypeCode='FW'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='401' InvTypeCode='FW'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='402' InvTypeCode='FW'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='402' InvTypeCode='FW'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='402' InvTypeCode='FW'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='403' InvTypeCode='FW'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='403' InvTypeCode='FW'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='403' InvTypeCode='FW'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-09' End='2025-12-19' InvCode='308' InvTypeCode='COD'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-20' End='2025-12-23' InvCode='308' InvTypeCode='COD'/>
|
||||
</Inventory>
|
||||
<Inventory>
|
||||
<StatusApplicationControl Start='2025-12-24' End='2026-01-31' InvCode='308' InvTypeCode='COD'/>
|
||||
<InvCounts>
|
||||
<InvCount CountType='2' Count='1'/>
|
||||
</InvCounts>
|
||||
</Inventory>
|
||||
</Inventories>
|
||||
</OTA_HotelInvCountNotifRQ>
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
@@ -231,6 +232,36 @@ async def test_closing_season_entries_marked_correctly(db_session: AsyncSession)
|
||||
assert len(closing_rows) == 2
|
||||
assert all(row.bookable_type_2 is None for row in closing_rows)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_closing_seasons_test_file(db_session: AsyncSession):
|
||||
await insert_test_hotel(db_session)
|
||||
action = make_action()
|
||||
|
||||
Path(__file__).parent / "test_data" / "ClosingSeasons.xml"
|
||||
|
||||
xml = (Path(__file__).parent / "test_data" / "ClosingSeasons.xml").read_text()
|
||||
|
||||
response = await action.handle(
|
||||
"OTA_HotelInvCountNotif:FreeRooms",
|
||||
xml,
|
||||
Version.V2024_10,
|
||||
make_client_info(),
|
||||
db_session,
|
||||
)
|
||||
assert response.status_code == HttpStatusCode.OK, f"Response was not OK {response.xml_content}"
|
||||
|
||||
inventories = (await db_session.execute(select(HotelInventory))).scalars().all()
|
||||
closing_inventory = next(inv for inv in inventories if inv.inv_type_code == "__CLOSE")
|
||||
assert closing_inventory.inv_code is None
|
||||
|
||||
rows = (
|
||||
await db_session.execute(select(RoomAvailability).order_by(RoomAvailability.date))
|
||||
).scalars().all()
|
||||
closing_rows = [row for row in rows if row.is_closing_season]
|
||||
# Closing season from 2025-12-20 to 2025-12-23 = 4 days
|
||||
assert len(closing_rows) == 4
|
||||
assert all(row.bookable_type_2 is None for row in closing_rows)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_closing_season_not_allowed_in_delta(db_session: AsyncSession):
|
||||
|
||||
Reference in New Issue
Block a user