diff --git a/drcr/config.py.example b/drcr/config.py.example
index 8c34cca..9fa0f3f 100644
--- a/drcr/config.py.example
+++ b/drcr/config.py.example
@@ -20,3 +20,8 @@ INCOME_STATEMENT_MAPPING = {
'Income': ['Sales'],
'Expenses': ['Operating costs', 'Purchases']
}
+
+TAX_MAPPING = {
+ 'Assessable income': [],
+ 'Deductions': []
+}
diff --git a/drcr/reports/views.py b/drcr/reports/views.py
index d42e2bc..5b8c8c6 100644
--- a/drcr/reports/views.py
+++ b/drcr/reports/views.py
@@ -1,5 +1,5 @@
# DrCr: Web-based double-entry bookkeeping framework
-# Copyright (C) 2022 Lee Yingtong Li (RunasSudo)
+# 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
@@ -29,8 +29,6 @@ def get_trial_balance():
for source_account, destination_account in COA_MAPPING.items():
balancer.transfer_balance(source_account, destination_account)
- return balancer
-
# Validate COA
for account in balancer.accounts:
if account in BALANCE_SHEET_MAPPING['Current assets']:
@@ -47,6 +45,8 @@ def get_trial_balance():
continue
if account == 'Accumulated surplus (deficit)':
continue
+ if account == 'Income tax':
+ continue
raise Exception('Account "{}" is not mapped to a report'.format(account))
@@ -66,7 +66,7 @@ def balance_sheet():
balancer = get_trial_balance()
# Transfer surplus to balance sheet
- for account in INCOME_STATEMENT_MAPPING['Income'] + INCOME_STATEMENT_MAPPING['Expenses']:
+ for account in INCOME_STATEMENT_MAPPING['Income'] + INCOME_STATEMENT_MAPPING['Expenses'] + ['Income tax']:
balancer.transfer_balance(account, 'Current year surplus (deficit)')
return render_template('reports/balance_sheet.html', accounts=balancer.accounts, running_total=Amount(0, '$'), BALANCE_SHEET_MAPPING=BALANCE_SHEET_MAPPING)
diff --git a/drcr/tax/__init__.py b/drcr/tax/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/drcr/tax/aus_tax.py b/drcr/tax/aus_tax.py
new file mode 100644
index 0000000..3c5eff0
--- /dev/null
+++ b/drcr/tax/aus_tax.py
@@ -0,0 +1,59 @@
+# 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 .
+
+from ..config import TAX_MAPPING
+from ..models import Amount, Posting, Transaction, TrialBalancer
+
+from datetime import datetime
+
+def taxable_income():
+ balancer = TrialBalancer()
+ balancer.apply_transactions(Transaction.query.all())
+
+ result = Amount(0, '$')
+ for account in TAX_MAPPING['Assessable income']:
+ result.quantity += balancer.accounts[account].quantity
+ return result
+
+def calculate_tax(taxable_income):
+ taxable_income = -taxable_income.as_cost().quantity
+
+ if taxable_income <= 1820000:
+ return Amount(0, '$')
+ if taxable_income <= 4500000:
+ return Amount(int((taxable_income - 1820000) * 0.19), '$')
+ if taxable_income <= 12000000:
+ return Amount(int(509200 + (taxable_income - 4500000) * 0.325), '$')
+ if taxable_income <= 18000000:
+ return Amount(int(2946700 + (taxable_income - 12000000) * 0.37), '$')
+ return Amount(int(5166700 + (taxable_income - 18000000) * 0.45), '$')
+
+def tax_transaction(taxable_income):
+ tax = calculate_tax(taxable_income)
+
+ # Get EOFY date
+ dt = datetime.now().replace(month=6, day=30)
+ if dt < datetime.now():
+ dt = dt.replace(year=dt.year + 1)
+
+ return Transaction(
+ dt=dt,
+ description='Estimated tax payable',
+ postings=[
+ Posting(account='Income Tax', quantity=tax.quantity, commodity='$'),
+ Posting(account='Income Tax Control', quantity=-tax.quantity, commodity='$')
+ ]
+ )
diff --git a/drcr/tax/views.py b/drcr/tax/views.py
new file mode 100644
index 0000000..ee72ca7
--- /dev/null
+++ b/drcr/tax/views.py
@@ -0,0 +1,36 @@
+# 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 .
+
+from flask import render_template
+
+from ..config import TAX_MAPPING
+from ..models import Amount, TrialBalancer
+from ..webapp import all_transactions, app
+from .aus_tax import calculate_tax
+
+@app.route('/tax/summary')
+def tax_summary():
+ # Get trial balance and validate COA
+ balancer = TrialBalancer()
+ balancer.apply_transactions(all_transactions())
+
+ return render_template(
+ 'tax/summary.html',
+ accounts=balancer.accounts,
+ calculate_tax=calculate_tax,
+ running_total=Amount(0, '$'),
+ TAX_MAPPING=TAX_MAPPING
+ )
diff --git a/drcr/templates/index.html b/drcr/templates/index.html
index e83dddd..4073922 100644
--- a/drcr/templates/index.html
+++ b/drcr/templates/index.html
@@ -36,5 +36,6 @@
{% endblock %}
diff --git a/drcr/templates/journal/journal.html b/drcr/templates/journal/journal.html
index b5394f0..0c251c9 100644
--- a/drcr/templates/journal/journal.html
+++ b/drcr/templates/journal/journal.html
@@ -22,7 +22,7 @@
Journal
- {#
New transaction#}
+
New transaction
{% if commodity_detail %}
Hide commodity detail
{% else %}
diff --git a/drcr/templates/reports/income_statement.html b/drcr/templates/reports/income_statement.html
index 76bf50a..97a626b 100644
--- a/drcr/templates/reports/income_statement.html
+++ b/drcr/templates/reports/income_statement.html
@@ -1,5 +1,5 @@
{# DrCr: Web-based double-entry bookkeeping framework
- Copyright (C) 2022 Lee Yingtong Li (RunasSudo)
+ 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
@@ -76,8 +76,18 @@
|
+
+ Net surplus (deficit) before income tax |
+ {% set _ = running_total.__setattr__('quantity', surplus) %}
+ {{ fmtbal(running_total, -1) }} |
+
+
+ Income tax expense |
+ {{ fmtbal(accounts['Income tax']) }} |
+ {% set surplus = surplus + accounts['Income tax'].quantity %}
+
- Net surplus (deficit) |
+ Net surplus (deficit) after income tax |
{% set _ = running_total.__setattr__('quantity', surplus) %}
{{ fmtbal(running_total, -1) }} |
diff --git a/drcr/templates/tax/summary.html b/drcr/templates/tax/summary.html
new file mode 100644
index 0000000..f29c2f7
--- /dev/null
+++ b/drcr/templates/tax/summary.html
@@ -0,0 +1,90 @@
+{# 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
.
+#}
+
+{% extends 'base.html' %}
+{% block title %}Tax summary{% endblock %}
+
+{% macro fmtbal(amount, mul=1) %}
+ {# FIXME: Honour AMOUNT_DPS #}
+ {% if amount.quantity * mul >= 0 %}
+ {{ '{:,.2f}'.format(amount.quantity|abs / 100) }}
+ {% else %}
+ ({{ '{:,.2f}'.format(amount.quantity|abs / 100) }})
+ {% endif %}
+{% endmacro %}
+
+{% macro acctrow(name, mul=1) %}
+
+ {{ name }} |
+ {{ fmtbal(accounts[name], mul) }} |
+
+ {% set _ = running_total.__setattr__('quantity', running_total.quantity + accounts[name].quantity) %}
+{% endmacro %}
+
+{% block content %}
+
Tax summary
+
+
+
+
+ |
+ $ |
+
+
+
+
+ Assessable income |
+
+ {% set _ = running_total.__setattr__('quantity', 0) %}
+ {% for account in TAX_MAPPING['Assessable income'] if account in accounts %}
+ {{ acctrow(account, -1) }}
+ {% endfor %}
+
+ Total assessable income |
+ {{ fmtbal(running_total, -1) }} |
+
+ {% set taxable_income = running_total.quantity %}
+
+ |
+
+
+ Deductions |
+
+ {% set _ = running_total.__setattr__('quantity', 0) %}
+ {% for account in TAX_MAPPING['Deductions'] if account in accounts %}
+ {{ acctrow(account) }}
+ {% endfor %}
+
+ Total deductions |
+ {{ fmtbal(running_total) }} |
+
+ {% set taxable_income = taxable_income + running_total.quantity %}
+
+ |
+
+ {% set _ = running_total.__setattr__('quantity', taxable_income) %}
+
+ Taxable income |
+ {{ fmtbal(running_total, -1) }} |
+
+
+ Tax on taxable income |
+ {{ fmtbal(calculate_tax(running_total)) }} |
+
+
+
+{% endblock %}
diff --git a/drcr/webapp.py b/drcr/webapp.py
index 7692090..465f2b8 100644
--- a/drcr/webapp.py
+++ b/drcr/webapp.py
@@ -20,6 +20,7 @@ from flask_sqlalchemy.record_queries import get_recorded_queries
from .database import db
from .models import Transaction
from .statements.models import StatementLine
+from .tax import aus_tax
import time
@@ -32,13 +33,15 @@ db.init_app(app)
def all_transactions():
return (
Transaction.query.all() +
- [line.into_transaction() for line in StatementLine.query.filter(StatementLine.reconciliation == None)]
+ [line.into_transaction() for line in StatementLine.query.filter(StatementLine.reconciliation == None)] +
+ [aus_tax.tax_transaction(aus_tax.taxable_income())]
)
from . import views
from .journal import views
from .reports import views
from .statements import views
+from .tax import views
@app.cli.command('initdb')
def initdb():