feat: add order item exception system for graceful product matching
Replaces the "fail on missing product" behavior with graceful handling: - Orders import even when products aren't found by GTIN - Unmatched items link to a per-vendor placeholder product - Exceptions tracked in order_item_exceptions table for QC resolution - Order confirmation blocked until exceptions are resolved - Auto-matching when products are imported via catalog sync New files: - OrderItemException model and migration - OrderItemExceptionService with CRUD and resolution logic - Admin and vendor API endpoints for exception management - Domain exceptions for error handling Modified: - OrderItem: added needs_product_match flag and exception relationship - OrderService: graceful handling with placeholder products - MarketplaceProductService: auto-match on product import - Letzshop confirm endpoints: blocking check for unresolved exceptions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,12 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import ResourceNotFoundException, ValidationException
|
||||
from app.exceptions import (
|
||||
OrderHasUnresolvedExceptionsException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from app.services.order_item_exception_service import order_item_exception_service
|
||||
from app.services.letzshop import (
|
||||
CredentialsNotFoundError,
|
||||
LetzshopClientError,
|
||||
@@ -783,6 +788,9 @@ def confirm_order(
|
||||
Confirm all inventory units for a Letzshop order.
|
||||
|
||||
Sends confirmInventoryUnits mutation with isAvailable=true for all items.
|
||||
|
||||
Raises:
|
||||
OrderHasUnresolvedExceptionsException: If order has unresolved product exceptions
|
||||
"""
|
||||
order_service = get_order_service(db)
|
||||
creds_service = get_credentials_service(db)
|
||||
@@ -792,6 +800,13 @@ def confirm_order(
|
||||
except OrderNotFoundError:
|
||||
raise ResourceNotFoundException("Order", str(order_id))
|
||||
|
||||
# Check for unresolved exceptions (blocks confirmation)
|
||||
unresolved_count = order_item_exception_service.get_unresolved_exception_count(
|
||||
db, order_id
|
||||
)
|
||||
if unresolved_count > 0:
|
||||
raise OrderHasUnresolvedExceptionsException(order_id, unresolved_count)
|
||||
|
||||
# Get inventory unit IDs from order items
|
||||
items = order_service.get_order_items(order)
|
||||
if not items:
|
||||
@@ -933,6 +948,9 @@ def confirm_single_item(
|
||||
Confirm a single inventory unit in an order.
|
||||
|
||||
Sends confirmInventoryUnits mutation with isAvailable=true for one item.
|
||||
|
||||
Raises:
|
||||
OrderHasUnresolvedExceptionsException: If the specific item has an unresolved exception
|
||||
"""
|
||||
order_service = get_order_service(db)
|
||||
creds_service = get_credentials_service(db)
|
||||
@@ -942,6 +960,15 @@ def confirm_single_item(
|
||||
except OrderNotFoundError:
|
||||
raise ResourceNotFoundException("Order", str(order_id))
|
||||
|
||||
# Check if this specific item has an unresolved exception
|
||||
# Find the order item by external_item_id
|
||||
item = next(
|
||||
(i for i in order.items if i.external_item_id == item_id),
|
||||
None
|
||||
)
|
||||
if item and item.needs_product_match:
|
||||
raise OrderHasUnresolvedExceptionsException(order_id, 1)
|
||||
|
||||
try:
|
||||
with creds_service.create_client(vendor_id) as client:
|
||||
result = client.confirm_inventory_units([item_id])
|
||||
|
||||
Reference in New Issue
Block a user