123 lines
3.3 KiB
Python
Executable File
123 lines
3.3 KiB
Python
Executable File
#!/usr/bin/python3
|
|
''' Calculate SARS tax on payments '''
|
|
|
|
import locale
|
|
import math
|
|
import pprint
|
|
import re
|
|
import sys
|
|
|
|
'''
|
|
Rates from https://www.sars.gov.za/Tax-Rates/Income-Tax/Pages/Rates%20of%20Tax%20for%20Individuals.aspx
|
|
low is the lowest value for that bracket; rate is the tax percentage applied to that bracket
|
|
'''
|
|
tax_rates = [
|
|
dict(
|
|
rate = 18,
|
|
high = 205_900,
|
|
), dict(
|
|
rate = 26,
|
|
high = 321_600,
|
|
), dict(
|
|
rate = 31,
|
|
high = 445_100,
|
|
), dict(
|
|
rate = 36,
|
|
high = 584_200,
|
|
), dict(
|
|
rate = 39,
|
|
high = 744_800,
|
|
), dict(
|
|
rate = 41,
|
|
high = 1_577_300,
|
|
), dict(
|
|
rate = 45,
|
|
),
|
|
]
|
|
for r in tax_rates:
|
|
idx = tax_rates.index(r)
|
|
if not r.get('high'):
|
|
if idx < len(tax_rates) - 1:
|
|
high = tax_rates[idx+1].get('low')
|
|
r['high'] = high - 1
|
|
if not r.get('low'):
|
|
if idx > 0:
|
|
low = tax_rates[idx-1].get('high')
|
|
r['low'] = low + 1
|
|
else:
|
|
r['low'] = 1
|
|
#pprint.pprint(tax_rates)
|
|
|
|
locale.setlocale( locale.LC_MONETARY, 'en_ZA.UTF-8' )
|
|
locale._override_localeconv = {'mon_thousands_sep': ' ', 'mon_decimal_point': '.'}
|
|
currency = locale.localeconv()
|
|
|
|
class colour:
|
|
PURPLE = '\033[95m'
|
|
CYAN = '\033[96m'
|
|
DARKCYAN = '\033[36m'
|
|
BLUE = '\033[94m'
|
|
GREEN = '\033[92m'
|
|
YELLOW = '\033[93m'
|
|
RED = '\033[91m'
|
|
BOLD = '\033[1m'
|
|
UNDERLINE = '\033[4m'
|
|
END = '\033[0m'
|
|
|
|
if len(sys.argv) < 2:
|
|
error()
|
|
elif isinstance(sys.argv[1], str):
|
|
try:
|
|
amount = "".join(sys.argv[1:]).strip(currency.get('currency_symbol'))
|
|
amount = re.sub('[,.](\d{2})$','.\g<1>', amount) # convert decimal separator to .
|
|
amount = re.sub('[ ,](\d{3})','\g<1>', amount) # convert thousands separator to ''
|
|
amount = locale.atof(amount)
|
|
amount = int(amount)
|
|
except Exception as e:
|
|
error(e)
|
|
else:
|
|
error()
|
|
|
|
''' Are we dealing with a monthly amount or annual amount?
|
|
Assume anything over R100k is annual.
|
|
If earning more than 100k per month, convert to --month/--year commandline option
|
|
'''
|
|
if amount > 100_000:
|
|
is_annual = True
|
|
else:
|
|
is_annual = False
|
|
print("Notice: value {} determined to be per month, dividing tax rates by 12".format(locale.currency(amount, grouping=True)))
|
|
|
|
total_tax = 0
|
|
for r in tax_rates:
|
|
low = (r.get('low') - 1)
|
|
high = r.get('high')
|
|
rate = r.get('rate')
|
|
if not is_annual:
|
|
low = math.ceil(low/12)
|
|
high = math.ceil(high/12)
|
|
if amount >= low:
|
|
bracket = amount
|
|
if high and amount > high:
|
|
bracket = high
|
|
current = math.ceil(bracket - low)
|
|
total_tax += math.ceil(current * rate / 100)
|
|
print(" {:>6} - {:>6} = {:>6} / {} = {:>6}".format(bracket, low, current, rate, total_tax))
|
|
if amount < high:
|
|
break
|
|
|
|
remainder = amount - total_tax
|
|
print("\nTotal tax on {} is {}{}{}, leaving {}".format(
|
|
locale.currency(amount, grouping=True),
|
|
colour.BOLD,
|
|
locale.currency(total_tax, grouping=True),
|
|
colour.END,
|
|
locale.currency(remainder, grouping=True)
|
|
))
|
|
|
|
def error(e):
|
|
if e:
|
|
print(e);
|
|
print("{0}: Please enter the annual or monthly amount before tax".format(sys.argv[0]))
|
|
sys.exit()
|