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

1from abc import ABC, abstractmethod 

2from app.delivery_fee.models import OrderInfo, DeliveryFee 

3from pydantic import BaseModel 

4from math import ceil 

5 

6 

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.""" 

12 

13 class ConfigOptions(BaseModel): 

14 """Configuration options for DeliveryFeeTransformer.""" 

15 

16 @abstractmethod 

17 def calculate(cls, order_info: OrderInfo, delivery_fee_configs) -> DeliveryFee: 

18 """Calculate the delivery fee for the given order info.""" 

19 

20 

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€.""" 

26 

27 class ConfigOptions(BaseModel): 

28 """Configuration options for CartValueFee. 

29 

30 Default for surcharge threshold is 10€. 

31 >>> cart_value_surcharge_threshold: int = 10e2 # 10€ (inclusive) 

32 """ 

33 cart_value_surcharge_threshold: int = 10e2 

34 

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() 

40 

41 def calculate(self, order_info: OrderInfo) -> DeliveryFee: 

42 delivery_fee = DeliveryFee(delivery_fee=0) 

43 

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) 

48 

49 return delivery_fee 

50 

51 

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€. 

58 

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€ 

61 

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€ 

64 

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 """ 

68 

69 class ConfigOptions(BaseModel): 

70 """Configuration options for DeliveryDistanceFee. 

71 

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 

82 

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() 

88 

89 def calculate(self, order_info: OrderInfo) -> DeliveryFee: 

90 delivery_fee = DeliveryFee(delivery_fee=0) 

91 

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 

95 

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) 

102 

103 delivery_fee += (additional_fee_multiplier * 

104 self.config_options.additional_fee) 

105 

106 return delivery_fee 

107 

108 

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€ 

114 

115 Example 1: If the number of items is 4, no extra surcharge 

116 

117 Example 2: If the number of items is 5, 50 cents surcharge is added 

118 

119 Example 3: If the number of items is 10, 3€ surcharge (6 x 50 cents) is added 

120 

121 Example 4: If the number of items is 13, 5, 

122 70€ surcharge is added ((9 * 50 cents) + 1,20€) 

123 

124 Example 5: If the number of items is 14,  

125 6,20€ surcharge is added ((10 * 50 cents) + 1,20€)""" 

126 

127 class ConfigOptions(BaseModel): 

128 """Configuration options for NumberOfItemsFee. 

129 

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 

140 

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() 

146 

147 def calculate(self, order_info: OrderInfo) -> DeliveryFee: 

148 delivery_fee = DeliveryFee(delivery_fee=0) 

149 

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 

154 

155 surcharge = items_over_threshold * self.config_options.surcharge_per_item_over_threshold 

156 delivery_fee += surcharge 

157 

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 

161 

162 return delivery_fee