Basic implementation of tax estimation

This commit is contained in:
RunasSudo 2023-01-02 21:00:09 +11:00
parent 131e818a4e
commit 9d5873376f
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
10 changed files with 212 additions and 8 deletions

View File

@ -20,3 +20,8 @@ INCOME_STATEMENT_MAPPING = {
'Income': ['Sales'], 'Income': ['Sales'],
'Expenses': ['Operating costs', 'Purchases'] 'Expenses': ['Operating costs', 'Purchases']
} }
TAX_MAPPING = {
'Assessable income': [],
'Deductions': []
}

View File

@ -1,5 +1,5 @@
# DrCr: Web-based double-entry bookkeeping framework # 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 # 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 # 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(): for source_account, destination_account in COA_MAPPING.items():
balancer.transfer_balance(source_account, destination_account) balancer.transfer_balance(source_account, destination_account)
return balancer
# Validate COA # Validate COA
for account in balancer.accounts: for account in balancer.accounts:
if account in BALANCE_SHEET_MAPPING['Current assets']: if account in BALANCE_SHEET_MAPPING['Current assets']:
@ -47,6 +45,8 @@ def get_trial_balance():
continue continue
if account == 'Accumulated surplus (deficit)': if account == 'Accumulated surplus (deficit)':
continue continue
if account == 'Income tax':
continue
raise Exception('Account "{}" is not mapped to a report'.format(account)) raise Exception('Account "{}" is not mapped to a report'.format(account))
@ -66,7 +66,7 @@ def balance_sheet():
balancer = get_trial_balance() balancer = get_trial_balance()
# Transfer surplus to balance sheet # 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)') 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) return render_template('reports/balance_sheet.html', accounts=balancer.accounts, running_total=Amount(0, '$'), BALANCE_SHEET_MAPPING=BALANCE_SHEET_MAPPING)

0
drcr/tax/__init__.py Normal file
View File

59
drcr/tax/aus_tax.py Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
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='$')
]
)

36
drcr/tax/views.py Normal file
View File

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

View File

@ -36,5 +36,6 @@
<ul> <ul>
<li><a href="/reports/balance-sheet">Balance sheet</a></li> <li><a href="/reports/balance-sheet">Balance sheet</a></li>
<li><a href="/reports/income-statement">Income statement</a></li> <li><a href="/reports/income-statement">Income statement</a></li>
<li><a href="/tax/summary">Tax summary</a></li>
</ul> </ul>
{% endblock %} {% endblock %}

View File

@ -22,7 +22,7 @@
<h1 class="h2 my-4">Journal</h1> <h1 class="h2 my-4">Journal</h1>
<div class="mb-2"> <div class="mb-2">
{#<a href="/journal/new-transaction" class="btn btn-primary"><i class="bi bi-plus-lg"></i> New transaction</a>#} <a href="/journal/new-transaction" class="btn btn-primary"><i class="bi bi-plus-lg"></i> New transaction</a>
{% if commodity_detail %} {% if commodity_detail %}
<a href="?commodity-detail=0" class="btn btn-outline-secondary">Hide commodity detail</a> <a href="?commodity-detail=0" class="btn btn-outline-secondary">Hide commodity detail</a>
{% else %} {% else %}

View File

@ -1,5 +1,5 @@
{# DrCr: Web-based double-entry bookkeeping framework {# 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 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 it under the terms of the GNU Affero General Public License as published by
@ -76,8 +76,18 @@
<tr><td colspan="2">&nbsp;</td></tr> <tr><td colspan="2">&nbsp;</td></tr>
<tr>
<th>Net surplus (deficit) before income tax</th>
{% set _ = running_total.__setattr__('quantity', surplus) %}
<th class="text-end">{{ fmtbal(running_total, -1) }}</th>
</tr>
<tr>
<td>Income tax expense</td>
<td class="text-end">{{ fmtbal(accounts['Income tax']) }}</td>
{% set surplus = surplus + accounts['Income tax'].quantity %}
</tr>
<tr style="border-width:1px 0"> <tr style="border-width:1px 0">
<th>Net surplus (deficit)</th> <th>Net surplus (deficit) after income tax</th>
{% set _ = running_total.__setattr__('quantity', surplus) %} {% set _ = running_total.__setattr__('quantity', surplus) %}
<th class="text-end">{{ fmtbal(running_total, -1) }}</th> <th class="text-end">{{ fmtbal(running_total, -1) }}</th>
</tr> </tr>

View File

@ -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 <https://www.gnu.org/licenses/>.
#}
{% 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) }}&nbsp;
{% else %}
({{ '{:,.2f}'.format(amount.quantity|abs / 100) }})
{% endif %}
{% endmacro %}
{% macro acctrow(name, mul=1) %}
<tr>
<td>{{ name }}</td>
<td class="text-end">{{ fmtbal(accounts[name], mul) }}</td>
</tr>
{% set _ = running_total.__setattr__('quantity', running_total.quantity + accounts[name].quantity) %}
{% endmacro %}
{% block content %}
<h1 class="h2 mt-4">Tax summary</h1>
<table class="table table-borderless">
<thead>
<tr style="border-width:0 0 1px 0">
<th></th>
<th class="text-end">$&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="2">Assessable income</th>
</tr>
{% set _ = running_total.__setattr__('quantity', 0) %}
{% for account in TAX_MAPPING['Assessable income'] if account in accounts %}
{{ acctrow(account, -1) }}
{% endfor %}
<tr style="border-width:1px 0">
<th>Total assessable income</th>
<th class="text-end">{{ fmtbal(running_total, -1) }}</th>
</tr>
{% set taxable_income = running_total.quantity %}
<tr><td colspan="2">&nbsp;</td></tr>
<tr>
<th colspan="2">Deductions</th>
</tr>
{% set _ = running_total.__setattr__('quantity', 0) %}
{% for account in TAX_MAPPING['Deductions'] if account in accounts %}
{{ acctrow(account) }}
{% endfor %}
<tr style="border-width:1px 0">
<th>Total deductions</th>
<th class="text-end">{{ fmtbal(running_total) }}</th>
</tr>
{% set taxable_income = taxable_income + running_total.quantity %}
<tr><td colspan="2">&nbsp;</td></tr>
{% set _ = running_total.__setattr__('quantity', taxable_income) %}
<tr style="border-width:1px 0">
<th>Taxable income</th>
<th class="text-end">{{ fmtbal(running_total, -1) }}</th>
</tr>
<tr>
<td>Tax on taxable income</td>
<td class="text-end">{{ fmtbal(calculate_tax(running_total)) }}</td>
</tr>
</tbody>
</table>
{% endblock %}

View File

@ -20,6 +20,7 @@ from flask_sqlalchemy.record_queries import get_recorded_queries
from .database import db from .database import db
from .models import Transaction from .models import Transaction
from .statements.models import StatementLine from .statements.models import StatementLine
from .tax import aus_tax
import time import time
@ -32,13 +33,15 @@ db.init_app(app)
def all_transactions(): def all_transactions():
return ( return (
Transaction.query.all() + 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 . import views
from .journal import views from .journal import views
from .reports import views from .reports import views
from .statements import views from .statements import views
from .tax import views
@app.cli.command('initdb') @app.cli.command('initdb')
def initdb(): def initdb():