diff --git a/rest/python/server/routes/ucp_implementation.py b/rest/python/server/routes/ucp_implementation.py index dd61490..c2bdf58 100644 --- a/rest/python/server/routes/ucp_implementation.py +++ b/rest/python/server/routes/ucp_implementation.py @@ -176,8 +176,25 @@ async def update_checkout( platform_config = None webhook_url = await extract_webhook_url(common_headers.ucp_agent) - if webhook_url: - platform_config = PlatformConfig(webhook_url=webhook_url) + + # Check for platform config in request body (extras) + body_platform = req_dict.get("platform") + + if webhook_url or body_platform: + platform_data = {} + if body_platform and isinstance(body_platform, dict): + platform_data.update(body_platform) + + if webhook_url: + platform_data["webhook_url"] = webhook_url + + try: + platform_config = PlatformConfig(**platform_data) + except Exception as e: + logger.warning("Failed to construct PlatformConfig: %s", e) + # Fallback to just webhook if possible + if webhook_url: + platform_config = PlatformConfig(webhook_url=webhook_url) result = await checkout_service.update_checkout( checkout_id, unified_req, idempotency_key, platform_config diff --git a/rest/python/server/services/checkout_service.py b/rest/python/server/services/checkout_service.py index 3769aef..609d68c 100644 --- a/rest/python/server/services/checkout_service.py +++ b/rest/python/server/services/checkout_service.py @@ -1012,6 +1012,26 @@ async def _recalculate_totals( # Fetch promotions once for the loop promotions = await db.get_active_promotions(self.products_session) + # Calculate available methods (inventory hints) + available_methods_wrapped = ( + await self.fulfillment_service.calculate_available_methods( + self.transactions_session, checkout.line_items + ) + ) + checkout.fulfillment.root.available_methods = [ + m.root for m in available_methods_wrapped + ] + + # Check for split shipment support (simulated via platform config) + supports_multi_group = False + if checkout.platform: + # PlatformConfig in models.py is extra='allow' + # We check if supports_multi_group is set to True + # In a real implementation this would be typed. + supports_multi_group = getattr( + checkout.platform, "supports_multi_group", False + ) + for method in checkout.fulfillment.root.methods: # 1. Identify Destination and Calculate Options calculated_options = [] @@ -1082,13 +1102,26 @@ async def _recalculate_totals( # 2. Generate or Update Groups if method.selected_destination_id and not method.groups: - # Generate new group - group = FulfillmentGroupResponse( - id=f"group_{uuid.uuid4()}", - line_item_ids=method.line_item_ids, - options=calculated_options, - ) - method.groups = [group] + # If split shipment is supported, we can simulate multiple groups. + # For testing, we split if there are multiple items and + # supports_multi_group is True. + if supports_multi_group and len(method.line_item_ids) > 1: + method.groups = [] + for li_id in method.line_item_ids: + group = FulfillmentGroupResponse( + id=f"group_{uuid.uuid4()}", + line_item_ids=[li_id], + options=calculated_options, + ) + method.groups.append(group) + else: + # Generate single group + group = FulfillmentGroupResponse( + id=f"group_{uuid.uuid4()}", + line_item_ids=method.line_item_ids, + options=calculated_options, + ) + method.groups = [group] elif method.groups: # Update existing groups with fresh options for group in method.groups: diff --git a/rest/python/server/services/fulfillment_service.py b/rest/python/server/services/fulfillment_service.py index 7e2fdc5..717eb1d 100644 --- a/rest/python/server/services/fulfillment_service.py +++ b/rest/python/server/services/fulfillment_service.py @@ -18,9 +18,17 @@ and costs based on the provided shipping address. """ +from typing import Any + import db from sqlalchemy.ext.asyncio import AsyncSession +from ucp_sdk.models.schemas.shopping.fulfillment_resp import ( + FulfillmentAvailableMethod, +) from ucp_sdk.models.schemas.shopping.fulfillment_resp import FulfillmentOption +from ucp_sdk.models.schemas.shopping.types import ( + fulfillment_available_method_resp as avail_mod, +) from ucp_sdk.models.schemas.shopping.types.fulfillment_option_resp import ( FulfillmentOptionResponse, ) @@ -31,6 +39,34 @@ class FulfillmentService: """Service for handling fulfillment logic.""" + async def calculate_available_methods( + self, + session: AsyncSession, + line_items: list[Any], + ) -> list[FulfillmentAvailableMethod]: + """Calculate available fulfillment methods based on inventory.""" + # This sample implementation assumes everything is fulfillable via shipping + # but we can simulate unavailability for items with low stock. + available_li_ids = [] + for li in line_items: + qty_avail = await db.get_inventory(session, li.item.id) + if qty_avail and qty_avail >= li.quantity: + available_li_ids.append(li.id) + + if not available_li_ids: + return [] + + return [ + FulfillmentAvailableMethod( + root=avail_mod.FulfillmentAvailableMethodResponse( + type="shipping", + line_item_ids=available_li_ids, + fulfillable_on="now", + description="Available for immediate shipping", + ) + ) + ] + async def calculate_options( self, session: AsyncSession,