DrCr/austax/views.py

111 lines
4.4 KiB
Python

# 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 redirect, render_template, request
from drcr.models import AccountConfiguration, Amount, Posting, Transaction
from drcr.database import db
from drcr.plugins import render_plugin_template
from drcr.webapp import app
from .models import CGTAsset, CGTCostAdjustment
from .reports import eofy_date, tax_summary_report
from datetime import datetime
@app.route('/tax/cgt-adjustments')
def cgt_adjustments():
adjustments = db.session.scalars(db.select(CGTCostAdjustment)).all()
return render_plugin_template('austax', 'cgt_adjustments.html', cgt_adjustments=adjustments)
@app.route('/tax/cgt-adjustments/new', methods=['GET', 'POST'])
def cgt_adjustment_new():
if request.method == 'GET':
return render_plugin_template('austax', 'cgt_adjustments_edit.html', adjustment=None)
asset = Amount.parse(request.form['asset'])
adjustment = CGTCostAdjustment(
quantity=asset.quantity,
commodity=asset.commodity,
account=request.form['account'],
acquisition_date=datetime.strptime(request.form['acquisition_date'], '%Y-%m-%d'),
dt=datetime.strptime(request.form['dt'], '%Y-%m-%d'),
description=request.form['description'],
cost_adjustment=Amount.parse(request.form['cost_adjustment']).quantity
)
db.session.add(adjustment)
db.session.commit()
return redirect('/tax/cgt-adjustments')
@app.route('/tax/cgt-assets')
def cgt_assets():
# Find all CGT asset accounts
cgt_accounts = []
account_configurations = AccountConfiguration.get_all_kinds()
for account_name, kinds in account_configurations.items():
if 'austax.cgtasset' in kinds:
cgt_accounts.append(account_name)
# Get all postings to CGT asset accounts
cgt_postings = db.session.scalars(
db.select(Posting)
.where(Posting.account.in_(cgt_accounts))
.join(Posting.transaction)
.order_by(Transaction.dt)
).all()
# Process postings to determine final balances
assets = []
for posting in cgt_postings:
if posting.quantity >= 0:
assets.append(CGTAsset(posting.quantity, posting.commodity, posting.account, posting.transaction.dt))
elif posting.quantity < 0:
asset = next((a for a in assets if a.commodity == posting.commodity and a.account == posting.account), None)
if asset is None:
raise Exception('Attempted credit {} without preceding debit balance'.quantity(posting.amount()))
if asset.quantity + posting.quantity < 0:
raise Exception('Attempted credit {} with insufficient debit balance {}'.quantity(posting.amount(), asset.amount()))
if asset.quantity + posting.quantity != 0:
raise NotImplementedError('Partial disposal of CGT asset not implemented')
asset.disposal_date = posting.transaction.dt
# Calculate disposal value by searching for matching asset postings
asset.disposal_value = Amount(0, '$')
for other_posting in posting.transaction.postings:
if posting != other_posting and 'drcr.asset' in account_configurations.get(other_posting.account, []):
asset.disposal_value.quantity += other_posting.amount().as_cost().quantity
# Process CGT adjustments
for cost_adjustment in db.session.scalars(db.select(CGTCostAdjustment)).all():
asset = next((a for a in assets if a.quantity == cost_adjustment.quantity and a.commodity == cost_adjustment.commodity and a.account == cost_adjustment.account and a.acquisition_date == cost_adjustment.acquisition_date), None)
if asset is None:
raise Exception('No matching CGT asset for {}'.format(repr(cost_adjustment.asset())))
asset.cost_adjustments.append(cost_adjustment)
return render_plugin_template('austax', 'cgt_assets.html', assets=assets, eofy_date=eofy_date())
@app.route('/tax/summary')
def tax_summary():
report = tax_summary_report()
return render_template('report.html', report=report)