Source code for app.shipping_strategy
from abc import ABC, abstractmethod
from app.inventory import InventoryManager
[docs]
class ShippingStrategy(ABC):
"""Abstract base class for shipping cost calculation strategies."""
[docs]
@abstractmethod
def calculate_shipping(self, subtotal: float, items: list[tuple[int, int]]) -> float:
"""
Calculate the shipping cost.
Args:
subtotal: The subtotal before shipping
items: List of (product_id, quantity) tuples
Returns:
float: The shipping cost
"""
...
[docs]
class FlatRate(ShippingStrategy):
"""Apply a flat rate shipping cost."""
def __init__(self, amount: float):
"""
Initialize the flat rate shipping strategy.
Args:
amount: Fixed shipping cost
Raises:
ValueError: If amount is negative
"""
if amount < 0:
raise ValueError("Amount must be non-negative")
self.amount = amount
[docs]
def calculate_shipping(self, subtotal: float, items: list[tuple[int, int]]) -> float:
return round(self.amount, 2)
[docs]
class FreeOver(ShippingStrategy):
"""Free shipping if subtotal exceeds threshold, otherwise flat rate."""
def __init__(self, threshold: float, flat_rate: float):
"""
Initialize the free-over-threshold shipping strategy.
Args:
threshold: Minimum subtotal for free shipping
flat_rate: Shipping cost if threshold not met
Raises:
ValueError: If threshold or flat_rate is negative
"""
if threshold < 0:
raise ValueError("Threshold must be non-negative")
if flat_rate < 0:
raise ValueError("Flat rate must be non-negative")
self.threshold = threshold
self.flat_rate = flat_rate
[docs]
def calculate_shipping(self, subtotal: float, items: list[tuple[int, int]]) -> float:
if subtotal >= self.threshold:
return 0.0
return round(self.flat_rate, 2)
[docs]
class WeightBased(ShippingStrategy):
"""Calculate shipping based on total weight from product data."""
def __init__(self, rate_per_kg: float):
"""
Initialize the weight-based shipping strategy.
Args:
rate_per_kg: Cost per kilogram
Raises:
ValueError: If rate_per_kg is negative
"""
if rate_per_kg < 0:
raise ValueError("Rate per kg must be non-negative")
self.rate_per_kg = rate_per_kg
[docs]
def calculate_shipping(self, subtotal: float, items: list[tuple[int, int]]) -> float:
"""Calculate shipping cost based on total weight of items.
Args:
subtotal: The subtotal before shipping (not used in weight-based calc)
items: List of (product_id, quantity) tuples
Returns:
float: The shipping cost
Raises:
ValueError: If any product_id is not found
"""
total_weight = 0.0
for product_id, quantity in items:
product = InventoryManager.get_instance().get_product(product_id)
if product is None:
raise ValueError(f"Product {product_id} not found")
total_weight += product.weight * quantity
shipping_cost = total_weight * self.rate_per_kg
return round(shipping_cost, 2)