From 9d5873376fcad93784ba7b3a4d2d141d0925e0ee Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 2 Jan 2023 21:00:09 +1100 Subject: [PATCH] Basic implementation of tax estimation --- drcr/config.py.example | 5 ++ drcr/reports/views.py | 8 +- drcr/tax/__init__.py | 0 drcr/tax/aus_tax.py | 59 +++++++++++++ drcr/tax/views.py | 36 ++++++++ drcr/templates/index.html | 1 + drcr/templates/journal/journal.html | 2 +- drcr/templates/reports/income_statement.html | 14 ++- drcr/templates/tax/summary.html | 90 ++++++++++++++++++++ drcr/webapp.py | 5 +- 10 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 drcr/tax/__init__.py create mode 100644 drcr/tax/aus_tax.py create mode 100644 drcr/tax/views.py create mode 100644 drcr/templates/tax/summary.html 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

+ + + + + + + + + + + + + {% set _ = running_total.__setattr__('quantity', 0) %} + {% for account in TAX_MAPPING['Assessable income'] if account in accounts %} + {{ acctrow(account, -1) }} + {% endfor %} + + + + + {% set taxable_income = running_total.quantity %} + + + + + + + {% set _ = running_total.__setattr__('quantity', 0) %} + {% for account in TAX_MAPPING['Deductions'] if account in accounts %} + {{ acctrow(account) }} + {% endfor %} + + + + + {% set taxable_income = taxable_income + running_total.quantity %} + + + + {% set _ = running_total.__setattr__('quantity', taxable_income) %} + + + + + + + + + +
Assessable income
Total assessable income{{ fmtbal(running_total, -1) }}
 
Deductions
Total deductions{{ fmtbal(running_total) }}
 
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():