feat: add automatic stock synchronization for orders

Implements order-inventory integration that automatically manages stock
when order status changes:
- processing: reserves inventory for order items
- shipped: fulfills (deducts) from stock
- cancelled: releases reserved inventory

Creates OrderInventoryService to orchestrate operations between
OrderService and InventoryService. Placeholder products (unmatched
Letzshop items) are skipped. Inventory errors are logged but don't
block order status updates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 18:05:44 +01:00
parent abeacbe25a
commit 871f52da80
4 changed files with 649 additions and 1 deletions

View File

@@ -31,6 +31,7 @@ from app.exceptions import (
ValidationException,
)
from app.services.order_item_exception_service import order_item_exception_service
from app.services.order_inventory_service import order_inventory_service
from app.services.subscription_service import (
subscription_service,
TierLimitExceededException,
@@ -963,6 +964,11 @@ class OrderService:
"""
Update order status and tracking information.
This method now includes automatic inventory management:
- processing: Reserves inventory for order items
- shipped: Fulfills (deducts) inventory
- cancelled: Releases reserved inventory
Args:
db: Database session
vendor_id: Vendor ID
@@ -975,9 +981,9 @@ class OrderService:
order = self.get_order(db, vendor_id, order_id)
now = datetime.now(UTC)
old_status = order.status
if order_update.status:
old_status = order.status
order.status = order_update.status
# Update timestamps based on status
@@ -990,6 +996,29 @@ class OrderService:
elif order_update.status == "cancelled" and not order.cancelled_at:
order.cancelled_at = now
# Handle inventory operations based on status change
try:
inventory_result = order_inventory_service.handle_status_change(
db=db,
vendor_id=vendor_id,
order_id=order_id,
old_status=old_status,
new_status=order_update.status,
)
if inventory_result:
logger.info(
f"Order {order.order_number} inventory update: "
f"{inventory_result.get('reserved_count', 0)} reserved, "
f"{inventory_result.get('fulfilled_count', 0)} fulfilled, "
f"{inventory_result.get('released_count', 0)} released"
)
except Exception as e:
# Log inventory errors but don't fail the status update
# Inventory can be adjusted manually if needed
logger.warning(
f"Order {order.order_number} inventory operation failed: {e}"
)
if order_update.tracking_number:
order.tracking_number = order_update.tracking_number