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
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 14:00 +0200
1from decimal import ROUND_DOWN, Decimal
3from .cast import cast_to_decimal
4from .types import Brackets, Number, Rounder, SocialSecurity
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.
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.
19 Returns:
20 Decimal: The calculated fixed tax.
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 )
28 if rounder is not None:
29 return rounder.round(amount * fixed_tax_rate)
31 return amount * fixed_tax_rate
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.
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.
46 Returns:
47 Decimal: The calculated gross compensation.
49 Raises:
50 TypeIsNotDecimalError: If the amount is not a decimal.
51 NumberLessThanOrEqualToZeroError: If the amount is less than or equal to zero.
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)
59 if rounder is not None:
60 return rounder.round(amount / (1 - fixed_tax_rate))
62 return amount / (1 - fixed_tax_rate)
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.
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.
81 Returns:
82 Decimal: The calculated tax.
84 Raises:
85 TypeIsNotDecimalError: If the amount is not a decimal.
86 NumberLessThanOrEqualToZeroError: If the amount is less than or equal to zero.
88 """ # noqa: E501
89 amount = cast_to_decimal(amount)
91 tax = Decimal(0)
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
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
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 )
116 if bracket_min <= taxable_salary <= bracket_max:
117 bracket_tax = bracket_rate * (taxable_salary - bracket_min)
118 tax += bracket_tax
120 if rounder is not None:
121 return rounder.round(tax)
123 return tax
125 tax += (bracket_max - bracket_min) * bracket_rate
127 if rounder is not None:
128 return rounder.round(tax)
130 return tax
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.
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.
147 Returns:
148 Decimal: The calculated gross fixed salary.
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.
155 """ # noqa: E501
156 amount = cast_to_decimal(amount)
157 min_allowed_salary = cast_to_decimal(min_allowed_salary)
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)
165 if calculate_brackets_tax(amount, brackets, rounder) == 0:
166 return amount
168 min_amount = Decimal(amount)
169 max_amount = (amount * Decimal("1.5")).to_integral(rounding=ROUND_DOWN)
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 )
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
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.
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.
205 Returns:
206 tuple[Decimal, Decimal]: A tuple containing the gross salary and compensations.
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.
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)
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)
226 gross_salary_before = Decimal(
227 target * (1 - compensations_rate)
228 ).to_integral(rounding=ROUND_DOWN)
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
242 compensations = calculate_gross_compensation(
243 compensations_before, compensations_tax_rate, rounder
244 )
246 return (gross_salary, compensations)