# 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 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)