Coverage for app/delivery_fee/fee_calculation_steps.py: 97%
61 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-11-27 09:26 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-11-27 09:26 +0000
1from abc import ABC, abstractmethod
2from app.delivery_fee.models import OrderInfo, DeliveryFee
3from pydantic import BaseModel
4from math import ceil
7class DeliveryFeeCalculationStep(ABC):
8 """Abstract class for all the calculation steps.
9 These steps are used to calculate the delivery fee.
10 Any subclass of this class is essentially one of the rules
11 to calculate surcharge for the total delivery fee."""
13 class ConfigOptions(BaseModel):
14 """Configuration options for DeliveryFeeTransformer."""
16 @abstractmethod
17 def calculate(cls, order_info: OrderInfo, delivery_fee_configs) -> DeliveryFee:
18 """Calculate the delivery fee for the given order info."""
21class CartValueFee(DeliveryFeeCalculationStep):
22 """Calculates delivery fee on cart value with the following rule:
23 If the cart value is less than 10€, a small order surcharge is added to
24 the delivery price. The surcharge is the difference between the cart value
25 and 10€. For example if the cart value is 8.90€, the surcharge will be 1.10€."""
27 class ConfigOptions(BaseModel):
28 """Configuration options for CartValueFee.
30 Default for surcharge threshold is 10€.
31 >>> cart_value_surcharge_threshold: int = 10e2 # 10€ (inclusive)
32 """
33 cart_value_surcharge_threshold: int = 10e2
35 def __init__(self, config_options: ConfigOptions | None = None) -> None:
36 super().__init__()
37 self.config_options = config_options
38 if config_options is None:
39 self.config_options = self.ConfigOptions()
41 def calculate(self, order_info: OrderInfo) -> DeliveryFee:
42 delivery_fee = DeliveryFee(delivery_fee=0)
44 # Apply surcharge if cart value is less than 10€.
45 if (order_info.cart_value < self.config_options.cart_value_surcharge_threshold):
46 delivery_fee += (self.config_options.cart_value_surcharge_threshold -
47 order_info.cart_value)
49 return delivery_fee
52class DeliveryDistanceFee(DeliveryFeeCalculationStep):
53 """Calculates delivery fee on the delivery distance using the following rule:
54 A delivery fee for the first 1000 meters (=1km) is 2€. If the delivery distance
55 is longer than that, 1€ is added for every additional 500 meters that the courier
56 needs to travel before reaching the destination. Even if the distance would be
57 shorter than 500 meters, the minimum fee is always 1€.
59 Example 1: If the delivery distance is 1499 meters,
60 the delivery fee is: 2€ base fee + 1€ for the additional 500 m => 3€
62 Example 2: If the delivery distance is 1500 meters,
63 the delivery fee is: 2€ base fee + 1€ for the additional 500 m => 3€
65 Example 3: If the delivery distance is 1501 meters,
66 the delivery fee is: 2€ base fee + 1€ for the first 500 m + 1€ for the second 500 m => 4€
67 """
69 class ConfigOptions(BaseModel):
70 """Configuration options for DeliveryDistanceFee.
72 Default options are:
73 >>> delivery_distance_low_threshold = 1e3 # 1km (inclusive)
74 >>> delivery_distance_surcharge_for_low_threshold = 2e2 # 2€
75 >>> additional_fee = 1e2 # 1€
76 >>> additional_fee_applied_per_meters_traveled = 500 # 500m (inclusive)
77 """
78 delivery_distance_low_threshold: int = 1e3
79 delivery_distance_surcharge_for_low_threshold: int = 2e2
80 additional_fee: int = 1e2
81 additional_fee_applied_per_meters_traveled: int = 500
83 def __init__(self, config_options: ConfigOptions | None = None) -> None:
84 super().__init__()
85 self.config_options = config_options
86 if config_options is None:
87 self.config_options = self.ConfigOptions()
89 def calculate(self, order_info: OrderInfo) -> DeliveryFee:
90 delivery_fee = DeliveryFee(delivery_fee=0)
92 # Apply surcharge for first 1km.
93 if order_info.delivery_distance > 0:
94 delivery_fee += self.config_options.delivery_distance_surcharge_for_low_threshold
96 # Apply additional fee for every 500m traveled.
97 if order_info.delivery_distance > self.config_options.delivery_distance_low_threshold:
98 additional_distance_to_travel = order_info.delivery_distance - \
99 self.config_options.delivery_distance_low_threshold
100 additional_fee_multiplier = ceil(
101 additional_distance_to_travel / self.config_options.additional_fee_applied_per_meters_traveled)
103 delivery_fee += (additional_fee_multiplier *
104 self.config_options.additional_fee)
106 return delivery_fee
109class NumberOfItemsFee(DeliveryFeeCalculationStep):
110 """Calculates delivery fee on number of items using the following rule:
111 If the number of items is five or more, an additional 50 cent surcharge
112 is added for each item above and including the fifth item. An extra
113 "bulk" fee applies for more than 12 items of 1,20€
115 Example 1: If the number of items is 4, no extra surcharge
117 Example 2: If the number of items is 5, 50 cents surcharge is added
119 Example 3: If the number of items is 10, 3€ surcharge (6 x 50 cents) is added
121 Example 4: If the number of items is 13, 5,
122 70€ surcharge is added ((9 * 50 cents) + 1,20€)
124 Example 5: If the number of items is 14,
125 6,20€ surcharge is added ((10 * 50 cents) + 1,20€)"""
127 class ConfigOptions(BaseModel):
128 """Configuration options for NumberOfItemsFee.
130 Default options are:
131 >>> number_of_items_surcharge_threshold = 4 # 4 items (exclusive)
132 >>> surcharge_per_item_over_threshold = 50 # 50 cents
133 >>> bulk_charge = 1.2e2 # 1.20€
134 >>> bulk_charge_threshold = 12 (exclusive)
135 """
136 number_of_items_surcharge_threshold: int = 4
137 surcharge_per_item_over_threshold: int = 50
138 bulk_charge: int = 1.2e2
139 bulk_charge_threshold: int = 12
141 def __init__(self, config_options: ConfigOptions | None = None) -> None:
142 super().__init__()
143 self.config_options = config_options
144 if config_options is None:
145 self.config_options = self.ConfigOptions()
147 def calculate(self, order_info: OrderInfo) -> DeliveryFee:
148 delivery_fee = DeliveryFee(delivery_fee=0)
150 # Apply surcharge if number of items is more than the threshold.
151 if order_info.number_of_items > self.config_options.number_of_items_surcharge_threshold:
152 items_over_threshold = order_info.number_of_items - \
153 self.config_options.number_of_items_surcharge_threshold
155 surcharge = items_over_threshold * self.config_options.surcharge_per_item_over_threshold
156 delivery_fee += surcharge
158 # Apply bulk charge for items.
159 if order_info.number_of_items > self.config_options.bulk_charge_threshold:
160 delivery_fee += self.config_options.bulk_charge
162 return delivery_fee