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)