feat: add partial shipment support (Phase 3)
- Add shipped_quantity field to OrderItem for tracking partial fulfillment
- Add partially_shipped order status for orders with partial shipments
- Add fulfill_item method for shipping individual items with quantities
- Add get_shipment_status method for detailed shipment tracking
- Add vendor API endpoints for partial shipment operations:
- GET /orders/{id}/shipment-status - Get item-level shipment status
- POST /orders/{id}/items/{item_id}/ship - Ship specific item quantity
- Automatic status updates: partially_shipped when some items shipped,
shipped when all items fully shipped
- Migration to add shipped_quantity column with upgrade for existing data
- Update documentation with partial shipment usage examples
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -239,6 +239,37 @@ class Order(Base, TimestampMixin):
|
||||
"""Check if this is a marketplace order."""
|
||||
return self.channel != "direct"
|
||||
|
||||
@property
|
||||
def is_fully_shipped(self) -> bool:
|
||||
"""Check if all items are fully shipped."""
|
||||
if not self.items:
|
||||
return False
|
||||
return all(item.is_fully_shipped for item in self.items)
|
||||
|
||||
@property
|
||||
def is_partially_shipped(self) -> bool:
|
||||
"""Check if some items are shipped but not all."""
|
||||
if not self.items:
|
||||
return False
|
||||
has_shipped = any(item.shipped_quantity > 0 for item in self.items)
|
||||
all_shipped = all(item.is_fully_shipped for item in self.items)
|
||||
return has_shipped and not all_shipped
|
||||
|
||||
@property
|
||||
def shipped_item_count(self) -> int:
|
||||
"""Count of fully shipped items."""
|
||||
return sum(1 for item in self.items if item.is_fully_shipped)
|
||||
|
||||
@property
|
||||
def total_shipped_units(self) -> int:
|
||||
"""Total quantity shipped across all items."""
|
||||
return sum(item.shipped_quantity for item in self.items)
|
||||
|
||||
@property
|
||||
def total_ordered_units(self) -> int:
|
||||
"""Total quantity ordered across all items."""
|
||||
return sum(item.quantity for item in self.items)
|
||||
|
||||
|
||||
class OrderItem(Base, TimestampMixin):
|
||||
"""
|
||||
@@ -280,6 +311,9 @@ class OrderItem(Base, TimestampMixin):
|
||||
inventory_reserved = Column(Boolean, default=False)
|
||||
inventory_fulfilled = Column(Boolean, default=False)
|
||||
|
||||
# === Shipment Tracking ===
|
||||
shipped_quantity = Column(Integer, default=0, nullable=False) # Units shipped so far
|
||||
|
||||
# === Exception Tracking ===
|
||||
# True if product was not found by GTIN during import (linked to placeholder)
|
||||
needs_product_match = Column(Boolean, default=False, index=True)
|
||||
@@ -342,3 +376,20 @@ class OrderItem(Base, TimestampMixin):
|
||||
if not self.exception:
|
||||
return False
|
||||
return self.exception.blocks_confirmation
|
||||
|
||||
# === SHIPMENT PROPERTIES ===
|
||||
|
||||
@property
|
||||
def remaining_quantity(self) -> int:
|
||||
"""Quantity not yet shipped."""
|
||||
return max(0, self.quantity - self.shipped_quantity)
|
||||
|
||||
@property
|
||||
def is_fully_shipped(self) -> bool:
|
||||
"""Check if all units have been shipped."""
|
||||
return self.shipped_quantity >= self.quantity
|
||||
|
||||
@property
|
||||
def is_partially_shipped(self) -> bool:
|
||||
"""Check if some but not all units have been shipped."""
|
||||
return 0 < self.shipped_quantity < self.quantity
|
||||
|
||||
Reference in New Issue
Block a user