fix: update letzshop API to use unified Order model properties
- Change message exceptions (MessageAttachmentException,
InvalidConversationTypeException, InvalidRecipientTypeException)
to extend BusinessLogicException instead of ValidationException
which doesn't accept error_code parameter
- Update vendor letzshop API endpoints:
- Change sync_status query param to status in list_orders()
- Fix response mapping to use unified Order model properties
(external_order_id, external_shipment_id, status, etc.)
- Fix confirm_order() to get inventory unit IDs from order items
- Fix set_tracking() to use external_shipment_id
- Add order_date to test fixtures (required NOT NULL field)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
107
app/api/v1/vendor/letzshop.py
vendored
107
app/api/v1/vendor/letzshop.py
vendored
@@ -267,8 +267,7 @@ def test_api_key(
|
|||||||
def list_orders(
|
def list_orders(
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0),
|
||||||
limit: int = Query(50, ge=1, le=200),
|
limit: int = Query(50, ge=1, le=200),
|
||||||
sync_status: str | None = Query(None, description="Filter by sync status"),
|
status: str | None = Query(None, description="Filter by order status"),
|
||||||
letzshop_state: str | None = Query(None, description="Filter by Letzshop state"),
|
|
||||||
current_user: User = Depends(get_current_vendor_api),
|
current_user: User = Depends(get_current_vendor_api),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
@@ -280,8 +279,7 @@ def list_orders(
|
|||||||
vendor_id=vendor_id,
|
vendor_id=vendor_id,
|
||||||
skip=skip,
|
skip=skip,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
sync_status=sync_status,
|
status=status,
|
||||||
letzshop_state=letzshop_state,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return LetzshopOrderListResponse(
|
return LetzshopOrderListResponse(
|
||||||
@@ -289,24 +287,24 @@ def list_orders(
|
|||||||
LetzshopOrderResponse(
|
LetzshopOrderResponse(
|
||||||
id=order.id,
|
id=order.id,
|
||||||
vendor_id=order.vendor_id,
|
vendor_id=order.vendor_id,
|
||||||
letzshop_order_id=order.letzshop_order_id,
|
order_number=order.order_number,
|
||||||
letzshop_shipment_id=order.letzshop_shipment_id,
|
external_order_id=order.external_order_id,
|
||||||
letzshop_order_number=order.letzshop_order_number,
|
external_shipment_id=order.external_shipment_id,
|
||||||
letzshop_state=order.letzshop_state,
|
external_order_number=order.external_order_number,
|
||||||
|
status=order.status,
|
||||||
customer_email=order.customer_email,
|
customer_email=order.customer_email,
|
||||||
customer_name=order.customer_name,
|
customer_name=f"{order.customer_first_name} {order.customer_last_name}",
|
||||||
|
customer_locale=order.customer_locale,
|
||||||
|
ship_country_iso=order.ship_country_iso,
|
||||||
|
bill_country_iso=order.bill_country_iso,
|
||||||
total_amount=order.total_amount,
|
total_amount=order.total_amount,
|
||||||
currency=order.currency,
|
currency=order.currency,
|
||||||
local_order_id=order.local_order_id,
|
|
||||||
sync_status=order.sync_status,
|
|
||||||
last_synced_at=order.last_synced_at,
|
|
||||||
sync_error=order.sync_error,
|
|
||||||
confirmed_at=order.confirmed_at,
|
|
||||||
rejected_at=order.rejected_at,
|
|
||||||
tracking_set_at=order.tracking_set_at,
|
|
||||||
tracking_number=order.tracking_number,
|
tracking_number=order.tracking_number,
|
||||||
tracking_carrier=order.tracking_carrier,
|
tracking_provider=order.tracking_provider,
|
||||||
inventory_units=order.inventory_units,
|
order_date=order.order_date,
|
||||||
|
confirmed_at=order.confirmed_at,
|
||||||
|
shipped_at=order.shipped_at,
|
||||||
|
cancelled_at=order.cancelled_at,
|
||||||
created_at=order.created_at,
|
created_at=order.created_at,
|
||||||
updated_at=order.updated_at,
|
updated_at=order.updated_at,
|
||||||
)
|
)
|
||||||
@@ -333,29 +331,50 @@ def get_order(
|
|||||||
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
||||||
|
|
||||||
return LetzshopOrderDetailResponse(
|
return LetzshopOrderDetailResponse(
|
||||||
|
# Base fields from LetzshopOrderResponse
|
||||||
id=order.id,
|
id=order.id,
|
||||||
vendor_id=order.vendor_id,
|
vendor_id=order.vendor_id,
|
||||||
letzshop_order_id=order.letzshop_order_id,
|
order_number=order.order_number,
|
||||||
letzshop_shipment_id=order.letzshop_shipment_id,
|
external_order_id=order.external_order_id,
|
||||||
letzshop_order_number=order.letzshop_order_number,
|
external_shipment_id=order.external_shipment_id,
|
||||||
letzshop_state=order.letzshop_state,
|
external_order_number=order.external_order_number,
|
||||||
|
status=order.status,
|
||||||
customer_email=order.customer_email,
|
customer_email=order.customer_email,
|
||||||
customer_name=order.customer_name,
|
customer_name=f"{order.customer_first_name} {order.customer_last_name}",
|
||||||
|
customer_locale=order.customer_locale,
|
||||||
|
ship_country_iso=order.ship_country_iso,
|
||||||
|
bill_country_iso=order.bill_country_iso,
|
||||||
total_amount=order.total_amount,
|
total_amount=order.total_amount,
|
||||||
currency=order.currency,
|
currency=order.currency,
|
||||||
local_order_id=order.local_order_id,
|
|
||||||
sync_status=order.sync_status,
|
|
||||||
last_synced_at=order.last_synced_at,
|
|
||||||
sync_error=order.sync_error,
|
|
||||||
confirmed_at=order.confirmed_at,
|
|
||||||
rejected_at=order.rejected_at,
|
|
||||||
tracking_set_at=order.tracking_set_at,
|
|
||||||
tracking_number=order.tracking_number,
|
tracking_number=order.tracking_number,
|
||||||
tracking_carrier=order.tracking_carrier,
|
tracking_provider=order.tracking_provider,
|
||||||
inventory_units=order.inventory_units,
|
order_date=order.order_date,
|
||||||
raw_order_data=order.raw_order_data,
|
confirmed_at=order.confirmed_at,
|
||||||
|
shipped_at=order.shipped_at,
|
||||||
|
cancelled_at=order.cancelled_at,
|
||||||
created_at=order.created_at,
|
created_at=order.created_at,
|
||||||
updated_at=order.updated_at,
|
updated_at=order.updated_at,
|
||||||
|
# Detail fields from LetzshopOrderDetailResponse
|
||||||
|
customer_first_name=order.customer_first_name,
|
||||||
|
customer_last_name=order.customer_last_name,
|
||||||
|
customer_phone=order.customer_phone,
|
||||||
|
ship_first_name=order.ship_first_name,
|
||||||
|
ship_last_name=order.ship_last_name,
|
||||||
|
ship_company=order.ship_company,
|
||||||
|
ship_address_line_1=order.ship_address_line_1,
|
||||||
|
ship_address_line_2=order.ship_address_line_2,
|
||||||
|
ship_city=order.ship_city,
|
||||||
|
ship_postal_code=order.ship_postal_code,
|
||||||
|
bill_first_name=order.bill_first_name,
|
||||||
|
bill_last_name=order.bill_last_name,
|
||||||
|
bill_company=order.bill_company,
|
||||||
|
bill_address_line_1=order.bill_address_line_1,
|
||||||
|
bill_address_line_2=order.bill_address_line_2,
|
||||||
|
bill_city=order.bill_city,
|
||||||
|
bill_postal_code=order.bill_postal_code,
|
||||||
|
external_data=order.external_data,
|
||||||
|
customer_notes=order.customer_notes,
|
||||||
|
internal_notes=order.internal_notes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -461,12 +480,15 @@ def confirm_order(
|
|||||||
if unresolved_count > 0:
|
if unresolved_count > 0:
|
||||||
raise OrderHasUnresolvedExceptionsException(order_id, unresolved_count)
|
raise OrderHasUnresolvedExceptionsException(order_id, unresolved_count)
|
||||||
|
|
||||||
# Get inventory unit IDs from request or order
|
# Get inventory unit IDs from request or order items
|
||||||
if confirm_request and confirm_request.inventory_unit_ids:
|
if confirm_request and confirm_request.inventory_unit_ids:
|
||||||
inventory_unit_ids = confirm_request.inventory_unit_ids
|
inventory_unit_ids = confirm_request.inventory_unit_ids
|
||||||
elif order.inventory_units:
|
|
||||||
inventory_unit_ids = [u["id"] for u in order.inventory_units]
|
|
||||||
else:
|
else:
|
||||||
|
# Get inventory unit IDs from order items' external_item_id
|
||||||
|
inventory_unit_ids = [
|
||||||
|
item.external_item_id for item in order.items if item.external_item_id
|
||||||
|
]
|
||||||
|
if not inventory_unit_ids:
|
||||||
raise ValidationException("No inventory units to confirm")
|
raise ValidationException("No inventory units to confirm")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -516,12 +538,15 @@ def reject_order(
|
|||||||
except OrderNotFoundError:
|
except OrderNotFoundError:
|
||||||
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
||||||
|
|
||||||
# Get inventory unit IDs from request or order
|
# Get inventory unit IDs from request or order items
|
||||||
if reject_request and reject_request.inventory_unit_ids:
|
if reject_request and reject_request.inventory_unit_ids:
|
||||||
inventory_unit_ids = reject_request.inventory_unit_ids
|
inventory_unit_ids = reject_request.inventory_unit_ids
|
||||||
elif order.inventory_units:
|
|
||||||
inventory_unit_ids = [u["id"] for u in order.inventory_units]
|
|
||||||
else:
|
else:
|
||||||
|
# Get inventory unit IDs from order items' external_item_id
|
||||||
|
inventory_unit_ids = [
|
||||||
|
item.external_item_id for item in order.items if item.external_item_id
|
||||||
|
]
|
||||||
|
if not inventory_unit_ids:
|
||||||
raise ValidationException("No inventory units to reject")
|
raise ValidationException("No inventory units to reject")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -568,13 +593,13 @@ def set_order_tracking(
|
|||||||
except OrderNotFoundError:
|
except OrderNotFoundError:
|
||||||
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
||||||
|
|
||||||
if not order.letzshop_shipment_id:
|
if not order.external_shipment_id:
|
||||||
raise ValidationException("Order does not have a shipment ID")
|
raise ValidationException("Order does not have a shipment ID")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with creds_service.create_client(vendor_id) as client:
|
with creds_service.create_client(vendor_id) as client:
|
||||||
result = client.set_shipment_tracking(
|
result = client.set_shipment_tracking(
|
||||||
shipment_id=order.letzshop_shipment_id,
|
shipment_id=order.external_shipment_id,
|
||||||
tracking_code=tracking_request.tracking_number,
|
tracking_code=tracking_request.tracking_number,
|
||||||
tracking_provider=tracking_request.tracking_carrier,
|
tracking_provider=tracking_request.tracking_carrier,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Messaging specific exceptions.
|
Messaging specific exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .base import BusinessLogicException, ResourceNotFoundException, ValidationException
|
from .base import BusinessLogicException, ResourceNotFoundException
|
||||||
|
|
||||||
|
|
||||||
class ConversationNotFoundException(ResourceNotFoundException):
|
class ConversationNotFoundException(ResourceNotFoundException):
|
||||||
@@ -41,7 +41,7 @@ class ConversationClosedException(BusinessLogicException):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MessageAttachmentException(ValidationException):
|
class MessageAttachmentException(BusinessLogicException):
|
||||||
"""Raised when attachment validation fails."""
|
"""Raised when attachment validation fails."""
|
||||||
|
|
||||||
def __init__(self, message: str, details: dict | None = None):
|
def __init__(self, message: str, details: dict | None = None):
|
||||||
@@ -63,7 +63,7 @@ class UnauthorizedConversationAccessException(BusinessLogicException):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidConversationTypeException(ValidationException):
|
class InvalidConversationTypeException(BusinessLogicException):
|
||||||
"""Raised when conversation type is not valid for the operation."""
|
"""Raised when conversation type is not valid for the operation."""
|
||||||
|
|
||||||
def __init__(self, message: str, allowed_types: list[str] | None = None):
|
def __init__(self, message: str, allowed_types: list[str] | None = None):
|
||||||
@@ -74,7 +74,7 @@ class InvalidConversationTypeException(ValidationException):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidRecipientTypeException(ValidationException):
|
class InvalidRecipientTypeException(BusinessLogicException):
|
||||||
"""Raised when recipient type doesn't match conversation type."""
|
"""Raised when recipient type doesn't match conversation type."""
|
||||||
|
|
||||||
def __init__(self, conversation_type: str, expected_recipient_type: str):
|
def __init__(self, conversation_type: str, expected_recipient_type: str):
|
||||||
|
|||||||
@@ -246,6 +246,8 @@ class TestAdminLetzshopOrdersAPI:
|
|||||||
|
|
||||||
def test_list_vendor_orders_with_data(self, client, db, admin_headers, test_vendor):
|
def test_list_vendor_orders_with_data(self, client, db, admin_headers, test_vendor):
|
||||||
"""Test listing vendor orders with data."""
|
"""Test listing vendor orders with data."""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from models.database.order import Order
|
from models.database.order import Order
|
||||||
|
|
||||||
# Create test order using unified Order model with all required fields
|
# Create test order using unified Order model with all required fields
|
||||||
@@ -256,6 +258,7 @@ class TestAdminLetzshopOrdersAPI:
|
|||||||
channel="letzshop",
|
channel="letzshop",
|
||||||
external_order_id="admin_order_1",
|
external_order_id="admin_order_1",
|
||||||
status="pending",
|
status="pending",
|
||||||
|
order_date=datetime.now(timezone.utc),
|
||||||
customer_first_name="Admin",
|
customer_first_name="Admin",
|
||||||
customer_last_name="Test",
|
customer_last_name="Test",
|
||||||
customer_email="admin-test@example.com",
|
customer_email="admin-test@example.com",
|
||||||
|
|||||||
13
tests/integration/api/v1/vendor/test_letzshop.py
vendored
13
tests/integration/api/v1/vendor/test_letzshop.py
vendored
@@ -238,6 +238,8 @@ class TestVendorLetzshopOrdersAPI:
|
|||||||
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
|
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
|
||||||
):
|
):
|
||||||
"""Test listing orders with status filter."""
|
"""Test listing orders with status filter."""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from models.database.order import Order
|
from models.database.order import Order
|
||||||
|
|
||||||
# Create test orders using unified Order model with all required fields
|
# Create test orders using unified Order model with all required fields
|
||||||
@@ -248,6 +250,7 @@ class TestVendorLetzshopOrdersAPI:
|
|||||||
channel="letzshop",
|
channel="letzshop",
|
||||||
external_order_id="order_1",
|
external_order_id="order_1",
|
||||||
status="pending",
|
status="pending",
|
||||||
|
order_date=datetime.now(timezone.utc),
|
||||||
customer_first_name="Test",
|
customer_first_name="Test",
|
||||||
customer_last_name="User",
|
customer_last_name="User",
|
||||||
customer_email="test1@example.com",
|
customer_email="test1@example.com",
|
||||||
@@ -273,6 +276,7 @@ class TestVendorLetzshopOrdersAPI:
|
|||||||
channel="letzshop",
|
channel="letzshop",
|
||||||
external_order_id="order_2",
|
external_order_id="order_2",
|
||||||
status="processing",
|
status="processing",
|
||||||
|
order_date=datetime.now(timezone.utc),
|
||||||
customer_first_name="Test",
|
customer_first_name="Test",
|
||||||
customer_last_name="User",
|
customer_last_name="User",
|
||||||
customer_email="test2@example.com",
|
customer_email="test2@example.com",
|
||||||
@@ -309,6 +313,8 @@ class TestVendorLetzshopOrdersAPI:
|
|||||||
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
|
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
|
||||||
):
|
):
|
||||||
"""Test getting order detail."""
|
"""Test getting order detail."""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from models.database.order import Order
|
from models.database.order import Order
|
||||||
|
|
||||||
order = Order(
|
order = Order(
|
||||||
@@ -319,6 +325,7 @@ class TestVendorLetzshopOrdersAPI:
|
|||||||
external_order_id="order_detail_test",
|
external_order_id="order_detail_test",
|
||||||
external_shipment_id="shipment_1",
|
external_shipment_id="shipment_1",
|
||||||
status="pending",
|
status="pending",
|
||||||
|
order_date=datetime.now(timezone.utc),
|
||||||
customer_first_name="Test",
|
customer_first_name="Test",
|
||||||
customer_last_name="User",
|
customer_last_name="User",
|
||||||
customer_email="test@example.com",
|
customer_email="test@example.com",
|
||||||
@@ -448,6 +455,8 @@ class TestVendorLetzshopFulfillmentAPI:
|
|||||||
test_vendor_with_vendor_user,
|
test_vendor_with_vendor_user,
|
||||||
):
|
):
|
||||||
"""Test confirming an order."""
|
"""Test confirming an order."""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from models.database.order import Order, OrderItem
|
from models.database.order import Order, OrderItem
|
||||||
|
|
||||||
# Create test order using unified Order model with all required fields
|
# Create test order using unified Order model with all required fields
|
||||||
@@ -459,6 +468,7 @@ class TestVendorLetzshopFulfillmentAPI:
|
|||||||
external_order_id="order_confirm",
|
external_order_id="order_confirm",
|
||||||
external_shipment_id="shipment_1",
|
external_shipment_id="shipment_1",
|
||||||
status="pending",
|
status="pending",
|
||||||
|
order_date=datetime.now(timezone.utc),
|
||||||
customer_first_name="Test",
|
customer_first_name="Test",
|
||||||
customer_last_name="User",
|
customer_last_name="User",
|
||||||
customer_email="test@example.com",
|
customer_email="test@example.com",
|
||||||
@@ -534,6 +544,8 @@ class TestVendorLetzshopFulfillmentAPI:
|
|||||||
test_vendor_with_vendor_user,
|
test_vendor_with_vendor_user,
|
||||||
):
|
):
|
||||||
"""Test setting tracking information."""
|
"""Test setting tracking information."""
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from models.database.order import Order
|
from models.database.order import Order
|
||||||
|
|
||||||
order = Order(
|
order = Order(
|
||||||
@@ -544,6 +556,7 @@ class TestVendorLetzshopFulfillmentAPI:
|
|||||||
external_order_id="order_tracking",
|
external_order_id="order_tracking",
|
||||||
external_shipment_id="shipment_track",
|
external_shipment_id="shipment_track",
|
||||||
status="processing", # confirmed state
|
status="processing", # confirmed state
|
||||||
|
order_date=datetime.now(timezone.utc),
|
||||||
customer_first_name="Test",
|
customer_first_name="Test",
|
||||||
customer_last_name="User",
|
customer_last_name="User",
|
||||||
customer_email="test@example.com",
|
customer_email="test@example.com",
|
||||||
|
|||||||
Reference in New Issue
Block a user