2023-01-04 18:03:08 +11:00
|
|
|
# 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/>.
|
|
|
|
|
2023-01-05 22:11:55 +11:00
|
|
|
from drcr import AMOUNT_DPS
|
2023-01-04 18:03:08 +11:00
|
|
|
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
|
|
|
|
|
2023-01-05 22:11:55 +11:00
|
|
|
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 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"""
|
2023-01-04 18:03:08 +11:00
|
|
|
|
2023-01-05 22:11:55 +11:00
|
|
|
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)), '$')
|
2023-01-04 18:03:08 +11:00
|
|
|
|
|
|
|
def medicare_levy(taxable_income):
|
|
|
|
if taxable_income.quantity < 2920700:
|
|
|
|
raise NotImplementedError('Medicare levy reduction is not implemented')
|
|
|
|
|
|
|
|
return Amount(int(taxable_income.quantity * 0.02), '$')
|
|
|
|
|
2023-01-05 22:11:55 +11:00
|
|
|
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, '$')
|
|
|
|
|
2023-01-04 18:03:08 +11:00
|
|
|
def tax_summary_report():
|
|
|
|
# Get trial balance
|
|
|
|
balancer = TrialBalancer()
|
2023-01-05 01:15:39 +11:00
|
|
|
balancer.apply_transactions(db.session.scalars(db.select(Transaction).options(db.selectinload(Transaction.postings))).all())
|
2023-01-04 18:03:08 +11:00
|
|
|
|
|
|
|
accounts = dict(sorted(balancer.accounts.items()))
|
|
|
|
|
|
|
|
# Get account configurations
|
|
|
|
account_configurations = AccountConfiguration.get_all_kinds()
|
|
|
|
|
|
|
|
report = Report(title='Tax summary')
|
|
|
|
report.entries = [
|
|
|
|
Section(
|
|
|
|
entries=[
|
|
|
|
Section(
|
|
|
|
title='Salary or wages (1)',
|
|
|
|
entries=entries_for_kind(account_configurations, accounts, 'austax.income1', neg=True, floor=100) + [Subtotal('Total item 1', id='income1')]
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Section(
|
|
|
|
title='Australian Government allowances and payments (5)',
|
|
|
|
entries=entries_for_kind(account_configurations, accounts, 'austax.income5', neg=True) + [Subtotal('Total item 5', id='income5', floor=100)]
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Calculated(
|
|
|
|
'Total assessable income',
|
|
|
|
lambda r: r.by_id('income1').amount + r.by_id('income5').amount,
|
|
|
|
id='assessable',
|
|
|
|
heading=True,
|
|
|
|
bordered=True
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Section(
|
|
|
|
title='Work-related self-education expenses (D4)',
|
|
|
|
entries=entries_for_kind(account_configurations, accounts, 'austax.d4') + [Subtotal('Total item D4', id='d4', floor=100)]
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Section(
|
|
|
|
title='Other work-related expenses (D5)',
|
|
|
|
entries=entries_for_kind(account_configurations, accounts, 'austax.d5') + [Subtotal('Total item D5', id='d5', floor=100)]
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Calculated(
|
|
|
|
'Total deductions',
|
|
|
|
lambda r: r.by_id('d4').amount + r.by_id('d5').amount,
|
|
|
|
id='deductions',
|
|
|
|
heading=True,
|
|
|
|
bordered=True
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Calculated(
|
2023-01-05 18:36:01 +11:00
|
|
|
'Net taxable income',
|
2023-01-04 18:03:08 +11:00
|
|
|
lambda r: r.by_id('assessable').amount - r.by_id('deductions').amount,
|
|
|
|
id='taxable',
|
|
|
|
heading=True,
|
|
|
|
bordered=True
|
|
|
|
),
|
2023-01-05 18:36:01 +11:00
|
|
|
Spacer(),
|
2023-01-04 18:03:08 +11:00
|
|
|
Section(
|
|
|
|
entries=[
|
|
|
|
Calculated(
|
2023-01-05 18:36:01 +11:00
|
|
|
'Base income tax',
|
2023-01-05 22:11:55 +11:00
|
|
|
lambda _: base_income_tax(eofy_date().year, report.by_id('taxable').amount)
|
2023-01-04 18:03:08 +11:00
|
|
|
),
|
|
|
|
Calculated(
|
|
|
|
'Medicare levy',
|
|
|
|
lambda _: medicare_levy(report.by_id('taxable').amount)
|
|
|
|
),
|
2023-01-05 18:36:01 +11:00
|
|
|
Subtotal('Total income tax', id='total_tax', bordered=True)
|
2023-01-04 18:03:08 +11:00
|
|
|
]
|
2023-01-05 18:36:01 +11:00
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Section(
|
|
|
|
title='PAYG withheld amounts',
|
|
|
|
entries=entries_for_kind(account_configurations, accounts, 'austax.paygw') + [Subtotal('Total withheld amounts', id='paygw')]
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Calculated(
|
2023-01-05 22:11:55 +11:00
|
|
|
'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,
|
2023-01-05 18:36:01 +11:00
|
|
|
heading=True,
|
|
|
|
bordered=True
|
2023-01-04 18:03:08 +11:00
|
|
|
)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
]
|
|
|
|
report.calculate()
|
|
|
|
|
|
|
|
return report
|