Coverage for src \ syriantaxes \ taxes.py: 10%

78 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-12 14:00 +0200

1from decimal import ROUND_DOWN, Decimal 

2 

3from .cast import cast_to_decimal 

4from .types import Brackets, Number, Rounder, SocialSecurity 

5 

6 

7def calculate_fixed_tax( 

8 amount: Number, 

9 fixed_tax_rate: Number, 

10 rounder: Rounder | None = None, 

11) -> Decimal: 

12 """Calculate the fixed tax for an amount. 

13 

14 Args: 

15 amount (Number): The amount to calculate the tax for. 

16 fixed_tax_rate (Number): The fixed tax rate. 

17 rounder (Rounder, optional): The rounder to use for rounding the result. Defaults to None. 

18 

19 Returns: 

20 Decimal: The calculated fixed tax. 

21 

22 """ # noqa: E501 

23 amount = cast_to_decimal(amount) 

24 fixed_tax_rate = cast_to_decimal( 

25 fixed_tax_rate, lt=Decimal(0), gt=Decimal(1) 

26 ) 

27 

28 if rounder is not None: 

29 return rounder.round(amount * fixed_tax_rate) 

30 

31 return amount * fixed_tax_rate 

32 

33 

34def calculate_gross_compensation( 

35 amount: Number, 

36 fixed_tax_rate: Number, 

37 rounder: Rounder | None = None, 

38) -> Decimal: 

39 """Calculate the gross compensation for an amount. 

40 

41 Args: 

42 amount (Decimal): The amount to calculate the compensation for. 

43 fixed_tax_rate (Decimal | float): The fixed tax rate. 

44 rounder (Rounder, optional): The rounder to use for rounding the result. Defaults to None. 

45 

46 Returns: 

47 Decimal: The calculated gross compensation. 

48 

49 Raises: 

50 TypeIsNotDecimalError: If the amount is not a decimal. 

51 NumberLessThanOrEqualToZeroError: If the amount is less than or equal to zero. 

52 

53 """ # noqa: E501 

54 fixed_tax_rate = cast_to_decimal( 

55 fixed_tax_rate, lt=Decimal(0), gt=Decimal(1) 

56 ) 

57 amount = cast_to_decimal(amount) 

58 

59 if rounder is not None: 

60 return rounder.round(amount / (1 - fixed_tax_rate)) 

61 

62 return amount / (1 - fixed_tax_rate) 

63 

64 

65def calculate_brackets_tax( 

66 amount: Number, 

67 brackets: Brackets, 

68 rounder: Rounder | None = None, 

69 ss_obj: SocialSecurity | None = None, 

70 ss_salary: Number | None = None, 

71) -> Decimal: 

72 """Calculate the tax for an amount based on brackets. 

73 

74 Args: 

75 amount (Decimal): The amount to calculate the tax for. 

76 brackets (Brackets): The brackets to use for calculating the tax. 

77 rounder (Rounder, optional): The rounder to use for rounding the result. Defaults to None. 

78 ss_obj (SocialSecurity, optional): The social security object to use for calculating the deduction. Defaults to None. 

79 ss_salary (Decimal, optional): The social security salary to use for calculating the deduction. Defaults to None. 

80 

81 Returns: 

82 Decimal: The calculated tax. 

83 

84 Raises: 

85 TypeIsNotDecimalError: If the amount is not a decimal. 

86 NumberLessThanOrEqualToZeroError: If the amount is less than or equal to zero. 

87 

88 """ # noqa: E501 

89 amount = cast_to_decimal(amount) 

90 

91 tax = Decimal(0) 

92 

93 if ss_obj is not None: 

94 ss_salary = ss_salary or amount 

95 ss_salary = cast_to_decimal(ss_salary, lt=Decimal(0)) 

96 taxable_salary = amount - ss_obj.calculate_deduction(ss_salary) 

97 else: 

98 taxable_salary = amount 

99 

100 for bracket in brackets: 

101 if isinstance(bracket, dict): 

102 bracket_min = bracket["min"] 

103 bracket_max = bracket["max"] 

104 bracket_rate = bracket["rate"] 

105 else: 

106 bracket_min = bracket.min 

107 bracket_max = bracket.max 

108 bracket_rate = bracket.rate 

109 

110 bracket_min = cast_to_decimal(bracket_min, lt=Decimal(0)) 

111 bracket_max = cast_to_decimal(bracket_max, lte=bracket_min) 

112 bracket_rate = cast_to_decimal( 

113 bracket_rate, lt=Decimal(0), gt=Decimal(1) 

114 ) 

115 

116 if bracket_min <= taxable_salary <= bracket_max: 

117 bracket_tax = bracket_rate * (taxable_salary - bracket_min) 

118 tax += bracket_tax 

119 

120 if rounder is not None: 

121 return rounder.round(tax) 

122 

123 return tax 

124 

125 tax += (bracket_max - bracket_min) * bracket_rate 

126 

127 if rounder is not None: 

128 return rounder.round(tax) 

129 

130 return tax 

131 

132 

133def calculate_gross_fixed_salary( 

134 amount: Number, 

135 brackets: Brackets, 

136 min_allowed_salary: Number, 

137 rounder: Rounder | None = None, 

138) -> Decimal: 

139 """Calculate the gross fixed salary for an amount based on brackets. 

140 

141 Args: 

142 amount (Decimal): The amount to calculate the gross fixed salary for. 

143 brackets (Brackets): The brackets to use for calculating the tax. 

144 min_allowed_salary (Decimal): The minimum allowed salary. 

145 rounder (Rounder, optional): The rounder to use for rounding the result. Defaults to None. 

146 

147 Returns: 

148 Decimal: The calculated gross fixed salary. 

149 

150 Raises: 

151 TypeIsNotDecimalError: If the amount is not a decimal. 

152 NumberLessThanOrEqualToZeroError: If the amount is less than or equal to zero. 

153 ValueError: If the amount is less than the minimum allowed salary. 

154 

155 """ # noqa: E501 

156 amount = cast_to_decimal(amount) 

157 min_allowed_salary = cast_to_decimal(min_allowed_salary) 

158 

159 if amount < min_allowed_salary: 

160 message = ( 

161 f"Can't be calculated for salary less than {min_allowed_salary}." 

162 ) 

163 raise ValueError(message) 

164 

165 if calculate_brackets_tax(amount, brackets, rounder) == 0: 

166 return amount 

167 

168 min_amount = Decimal(amount) 

169 max_amount = (amount * Decimal("1.5")).to_integral(rounding=ROUND_DOWN) 

170 

171 while True: 

172 mid_amount = ((min_amount + max_amount) / 2).to_integral( 

173 rounding=ROUND_DOWN 

174 ) 

175 mid_net = mid_amount - calculate_brackets_tax( 

176 mid_amount, brackets, rounder 

177 ) 

178 

179 if mid_net > amount: 

180 max_amount = mid_amount 

181 elif mid_net < amount: 

182 min_amount = mid_amount 

183 else: 

184 return mid_amount 

185 

186 

187def calculate_gross_components( # noqa: PLR0913 

188 target: Number, 

189 compensations_rate: Number, 

190 brackets: Brackets, 

191 min_allowed_salary: Number, 

192 compensations_tax_rate: Number, 

193 rounder: Rounder | None = None, 

194) -> tuple[Decimal, Decimal]: 

195 """Calculate the gross components for an amount based on brackets. 

196 

197 Args: 

198 target (Decimal): The amount to calculate the gross components for. 

199 compensations_rate (Decimal | float): The compensations rate. 

200 brackets (Brackets): The brackets to use for calculating the tax. 

201 min_allowed_salary (Decimal): The minimum allowed salary. 

202 compensations_tax_rate (Decimal): The compensations tax rate. 

203 rounder (Rounder, optional): The rounder to use for rounding the result. Defaults to None. 

204 

205 Returns: 

206 tuple[Decimal, Decimal]: A tuple containing the gross salary and compensations. 

207 

208 Raises: 

209 TypeIsNotDecimalError: If the amount or min_allowed_salary is not a number. 

210 NumberLessThanOrEqualToZeroError: If the amount is less than or equal to zero. 

211 ValueError: If the amount is less than the minimum allowed salary. 

212 

213 """ # noqa: E501 

214 compensations_rate = cast_to_decimal( 

215 compensations_rate, lt=Decimal(0), gt=Decimal(1) 

216 ) 

217 target = cast_to_decimal(target) 

218 min_allowed_salary = cast_to_decimal(min_allowed_salary) 

219 

220 if target < min_allowed_salary: 

221 message = ( 

222 f"Can't be calculated for salary less than {min_allowed_salary}." 

223 ) 

224 raise ValueError(message) 

225 

226 gross_salary_before = Decimal( 

227 target * (1 - compensations_rate) 

228 ).to_integral(rounding=ROUND_DOWN) 

229 

230 if gross_salary_before < min_allowed_salary: 

231 gross_salary = min_allowed_salary 

232 minium_salary_tax = calculate_brackets_tax( 

233 min_allowed_salary, brackets, rounder 

234 ) 

235 compensations_before = target - min_allowed_salary + minium_salary_tax 

236 else: 

237 gross_salary = calculate_gross_fixed_salary( 

238 gross_salary_before, brackets, min_allowed_salary, rounder 

239 ) 

240 compensations_before = target - gross_salary_before 

241 

242 compensations = calculate_gross_compensation( 

243 compensations_before, compensations_tax_rate, rounder 

244 ) 

245 

246 return (gross_salary, compensations)