From bf86d721754e22c2e2dff0a469ce8d3d5cca7d0e Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Thu, 5 Jan 2023 22:11:55 +1100 Subject: [PATCH] austax: Implement calculation of mandatory study loan repayment --- austax/__init__.py | 20 +++++++++++----- austax/reports.py | 54 +++++++++++++++++++++++++++++++------------ austax/tax_tables.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 austax/tax_tables.py diff --git a/austax/__init__.py b/austax/__init__.py index fbe5315..56f422b 100644 --- a/austax/__init__.py +++ b/austax/__init__.py @@ -21,9 +21,7 @@ from drcr.database import db import drcr.plugins from drcr.webapp import app -from .reports import tax_summary_report - -from datetime import datetime +from .reports import eofy_date, tax_summary_report def plugin_init(): drcr.plugins.advanced_reports.append(('/tax/summary', 'Tax summary')) @@ -46,9 +44,7 @@ def make_tax_transactions(): tax_amount = report.by_id('total_tax').amount # Get EOFY date - dt = datetime.now().replace(month=6, day=30) - if dt < datetime.now(): - dt = dt.replace(year=dt.year + 1) + dt = eofy_date() # Estimated tax payable transactions = [Transaction( @@ -60,6 +56,18 @@ def make_tax_transactions(): ] )] + # Mandatory study loan repayment + loan_repayment = report.by_id('loan_repayment').amount + if loan_repayment.quantity != 0: + transactions.append(Transaction( + dt=dt, + description='Mandatory study loan repayment payable', + postings=[ + Posting(account='HELP', quantity=loan_repayment.quantity, commodity='$'), # FIXME: Correct account + Posting(account='Income Tax Control', quantity=-loan_repayment.quantity, commodity='$') + ] + )) + # Get trial balance balancer = TrialBalancer() balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all()) diff --git a/austax/reports.py b/austax/reports.py index def779f..d0e1b5d 100644 --- a/austax/reports.py +++ b/austax/reports.py @@ -14,22 +14,34 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from drcr import AMOUNT_DPS from drcr.database import db from drcr.models import AccountConfiguration, Amount, Transaction, TrialBalancer from drcr.reports import Calculated, Report, Section, Spacer, Subtotal, entries_for_kind -def base_income_tax(taxable_income): - income = taxable_income.quantity +from .tax_tables import base_tax, repayment_rates + +from datetime import datetime + +def eofy_date(dt=None): + """Get the datetime for the end of the financial year""" - if income <= 1820000: - return Amount(0, '$') - if income <= 4500000: - return Amount(int((income - 1820000) * 0.19), '$') - if income <= 12000000: - return Amount(int(509200 + (income - 4500000) * 0.325), '$') - if income <= 18000000: - return Amount(int(2946700 + (income - 12000000) * 0.37), '$') - return Amount(int(5166700 + (income - 18000000) * 0.45), '$') + if dt is None: + dt = datetime.now() + + dt_eofy = dt.replace(month=6, day=30) + if dt_eofy < dt: + dt_eofy = dt_eofy.replace(year=dt.year + 1) + + return dt_eofy + +def base_income_tax(year, taxable_income): + """Get the amount of base income tax""" + + for i, (upper_limit, flat_amount, marginal_rate) in enumerate(base_tax[year]): + if upper_limit is None or taxable_income.quantity <= upper_limit * (10**AMOUNT_DPS): + lower_limit = base_tax[year][i - 1][0] + return Amount(flat_amount * (10**AMOUNT_DPS) + marginal_rate * (taxable_income.quantity - lower_limit * (10**AMOUNT_DPS)), '$') def medicare_levy(taxable_income): if taxable_income.quantity < 2920700: @@ -37,10 +49,16 @@ def medicare_levy(taxable_income): return Amount(int(taxable_income.quantity * 0.02), '$') +def study_loan_repayment(year, taxable_income): + """Get the amount of mandatory study loan repayment""" + + for upper_limit, rate in repayment_rates[year]: + if upper_limit is None or taxable_income.quantity <= upper_limit * (10**AMOUNT_DPS): + return Amount(rate * taxable_income.quantity, '$') + def tax_summary_report(): # Get trial balance balancer = TrialBalancer() - #balancer.apply_transactions(all_transactions()) balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all()) accounts = dict(sorted(balancer.accounts.items())) @@ -100,7 +118,7 @@ def tax_summary_report(): entries=[ Calculated( 'Base income tax', - lambda _: base_income_tax(report.by_id('taxable').amount) + lambda _: base_income_tax(eofy_date().year, report.by_id('taxable').amount) ), Calculated( 'Medicare levy', @@ -116,8 +134,14 @@ def tax_summary_report(): ), Spacer(), Calculated( - 'Income tax payable (refundable)', - lambda _: report.by_id('total_tax').amount - report.by_id('paygw').amount, + 'Mandatory study loan repayment', + lambda _: study_loan_repayment(eofy_date().year, report.by_id('taxable').amount), + id='loan_repayment' + ), + Spacer(), + Calculated( + 'ATO liability payable (refundable)', + lambda _: report.by_id('total_tax').amount - report.by_id('paygw').amount + report.by_id('loan_repayment').amount, heading=True, bordered=True ) diff --git a/austax/tax_tables.py b/austax/tax_tables.py new file mode 100644 index 0000000..501aa6e --- /dev/null +++ b/austax/tax_tables.py @@ -0,0 +1,55 @@ +# DrCr: Web-based double-entry bookkeeping framework +# Copyright (C) 2022–2023 Lee Yingtong Li (RunasSudo) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# Base income tax +# https://www.ato.gov.au/rates/individual-income-tax-rates/ +# Maps each financial year to list of (upper limit (INclusive), flat amount, marginal rate) +base_tax = { + 2023: [ + (18200, 0, 0), + (45000, 0, 0.19), + (120000, 5092, 0.325), + (180000, 29467, 0.37), + (None, 51667, 0.45) + ] +} + +# Study and training loan (HELP, etc.) repayment thresholds and rates +# https://www.ato.gov.au/Rates/HELP,-TSL-and-SFSS-repayment-thresholds-and-rates/ +# Maps each financial year to list of (upper limit (EXclusive), repayment rate) +repayment_rates = { + 2023: [ + (48361, 0), + (55837, 0.01), + (59187, 0.02), + (62739, 0.025), + (66503, 0.03), + (70493, 0.035), + (74723, 0.04), + (79207, 0.045), + (83959, 0.05), + (88997, 0.055), + (94337, 0.06), + (99997, 0.065), + (105997, 0.07), + (112356, 0.075), + (119098, 0.08), + (126244, 0.085), + (133819, 0.09), + (141848, 0.095), + (None, 0.1) + ] +}