Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions rest/python/server/routes/ucp_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 40 additions & 7 deletions rest/python/server/services/checkout_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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:
Expand Down
36 changes: 36 additions & 0 deletions rest/python/server/services/fulfillment_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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,
Expand Down