austax: Implement calculation of mandatory study loan repayment

This commit is contained in:
RunasSudo 2023-01-05 22:11:55 +11:00
parent 83a43a1bef
commit bf86d72175
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
3 changed files with 108 additions and 21 deletions

View File

@ -21,9 +21,7 @@ from drcr.database import db
import drcr.plugins import drcr.plugins
from drcr.webapp import app from drcr.webapp import app
from .reports import tax_summary_report from .reports import eofy_date, tax_summary_report
from datetime import datetime
def plugin_init(): def plugin_init():
drcr.plugins.advanced_reports.append(('/tax/summary', 'Tax summary')) 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 tax_amount = report.by_id('total_tax').amount
# Get EOFY date # Get EOFY date
dt = datetime.now().replace(month=6, day=30) dt = eofy_date()
if dt < datetime.now():
dt = dt.replace(year=dt.year + 1)
# Estimated tax payable # Estimated tax payable
transactions = [Transaction( 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 # Get trial balance
balancer = TrialBalancer() balancer = TrialBalancer()
balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all()) balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all())

View File

@ -14,22 +14,34 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from drcr import AMOUNT_DPS
from drcr.database import db from drcr.database import db
from drcr.models import AccountConfiguration, Amount, Transaction, TrialBalancer from drcr.models import AccountConfiguration, Amount, Transaction, TrialBalancer
from drcr.reports import Calculated, Report, Section, Spacer, Subtotal, entries_for_kind from drcr.reports import Calculated, Report, Section, Spacer, Subtotal, entries_for_kind
def base_income_tax(taxable_income): from .tax_tables import base_tax, repayment_rates
income = taxable_income.quantity
from datetime import datetime
def eofy_date(dt=None):
"""Get the datetime for the end of the financial year"""
if income <= 1820000: if dt is None:
return Amount(0, '$') dt = datetime.now()
if income <= 4500000:
return Amount(int((income - 1820000) * 0.19), '$') dt_eofy = dt.replace(month=6, day=30)
if income <= 12000000: if dt_eofy < dt:
return Amount(int(509200 + (income - 4500000) * 0.325), '$') dt_eofy = dt_eofy.replace(year=dt.year + 1)
if income <= 18000000:
return Amount(int(2946700 + (income - 12000000) * 0.37), '$') return dt_eofy
return Amount(int(5166700 + (income - 18000000) * 0.45), '$')
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): def medicare_levy(taxable_income):
if taxable_income.quantity < 2920700: if taxable_income.quantity < 2920700:
@ -37,10 +49,16 @@ def medicare_levy(taxable_income):
return Amount(int(taxable_income.quantity * 0.02), '$') 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(): def tax_summary_report():
# Get trial balance # Get trial balance
balancer = TrialBalancer() balancer = TrialBalancer()
#balancer.apply_transactions(all_transactions())
balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all()) balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all())
accounts = dict(sorted(balancer.accounts.items())) accounts = dict(sorted(balancer.accounts.items()))
@ -100,7 +118,7 @@ def tax_summary_report():
entries=[ entries=[
Calculated( Calculated(
'Base income tax', '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( Calculated(
'Medicare levy', 'Medicare levy',
@ -116,8 +134,14 @@ def tax_summary_report():
), ),
Spacer(), Spacer(),
Calculated( Calculated(
'Income tax payable (refundable)', 'Mandatory study loan repayment',
lambda _: report.by_id('total_tax').amount - report.by_id('paygw').amount, 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, heading=True,
bordered=True bordered=True
) )

55
austax/tax_tables.py Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
# 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)
]
}