From 56fa6f3633c38ec6541331f7a36e4e6cc20f3394 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 11 Nov 2024 16:28:59 +1100 Subject: [PATCH] Allow splitting API transactions into multiple stages --- austax/__init__.py | 17 +++++----- austax/reports.py | 5 +-- austax/views.py | 11 +++++-- drcr/journal/views.py | 3 +- drcr/reports.py | 3 +- drcr/transactions.py | 72 +++++++++++++++++++++++++++++++++++++++++++ drcr/views.py | 3 +- drcr/webapp.py | 45 +-------------------------- 8 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 drcr/transactions.py diff --git a/austax/__init__.py b/austax/__init__.py index 04b6dde..902dde4 100644 --- a/austax/__init__.py +++ b/austax/__init__.py @@ -18,7 +18,7 @@ from flask import render_template, url_for from drcr.models import AccountConfiguration, Posting, Transaction, TrialBalancer, reporting_commodity from drcr.database import db -from drcr.webapp import eofy_date +from drcr.webapp import eofy_date, sofy_date import drcr.plugins from . import views # Load routes @@ -49,14 +49,21 @@ def plugin_init(): drcr.plugins.transaction_providers.append(make_tax_transactions) @assert_aud -def make_tax_transactions(transactions, start_date=None, end_date=None): +def make_tax_transactions(transactions, start_date=None, end_date=None, api_stage_until=None): + if api_stage_until is not None and api_stage_until < 300: + return transactions + # Get EOFY date dt = eofy_date() if (start_date is not None and start_date > dt) or (end_date is not None and end_date < dt): return transactions - report = tax_summary_report() + # Get trial balance + balancer = TrialBalancer.from_cached(start_date=sofy_date(), end_date=dt) + balancer.apply_transactions(transactions) + + report = tax_summary_report(balancer) tax_amount = report.by_id('total_tax').amount - report.by_id('offsets').amount # Estimated tax payable @@ -81,10 +88,6 @@ def make_tax_transactions(transactions, start_date=None, end_date=None): ] )) - # Get trial balance - balancer = TrialBalancer.from_cached() - balancer.apply_transactions(transactions) - accounts = dict(sorted(balancer.accounts.items())) # Get account configurations diff --git a/austax/reports.py b/austax/reports.py index d415ab5..29bce67 100644 --- a/austax/reports.py +++ b/austax/reports.py @@ -82,10 +82,7 @@ def study_loan_repayment(year, taxable_income, rfb_grossedup): return Amount(rate * repayment_income.quantity, reporting_commodity()) @assert_aud -def tax_summary_report(): - # Get trial balance - balancer = TrialBalancer.from_cached(start_date=sofy_date(), end_date=eofy_date()) - +def tax_summary_report(balancer): accounts = dict(sorted(balancer.accounts.items())) # Get account configurations diff --git a/austax/views.py b/austax/views.py index b097399..958477b 100644 --- a/austax/views.py +++ b/austax/views.py @@ -16,10 +16,11 @@ from flask import redirect, render_template, request, url_for -from drcr.models import AccountConfiguration, Amount, Posting, Transaction, reporting_commodity +from drcr.models import AccountConfiguration, Amount, Posting, Transaction, TrialBalancer, reporting_commodity from drcr.database import db from drcr.plugins import render_plugin_template -from drcr.webapp import all_accounts, app, eofy_date +from drcr.transactions import api_transactions +from drcr.webapp import all_accounts, app, eofy_date, sofy_date from .models import CGTAsset, CGTCostAdjustment from .reports import tax_summary_report @@ -237,5 +238,9 @@ def cgt_assets(): @app.route('/tax/summary') @assert_aud def tax_summary(): - report = tax_summary_report() + # Get trial balance + balancer = TrialBalancer.from_cached(start_date=sofy_date(), end_date=eofy_date()) + balancer.apply_transactions(api_transactions(api_stage_until=299)) + + report = tax_summary_report(balancer) return render_template('report.html', report=report) diff --git a/drcr/journal/views.py b/drcr/journal/views.py index 627d3fc..3621354 100644 --- a/drcr/journal/views.py +++ b/drcr/journal/views.py @@ -19,7 +19,8 @@ from flask import abort, redirect, render_template, request, url_for from .. import AMOUNT_DPS from ..database import db from ..models import Amount, Posting, Transaction, TrialBalancer, queue_invalidate_running_balances, reporting_commodity -from ..webapp import all_accounts, all_transactions, app +from ..transactions import all_transactions +from ..webapp import all_accounts, app from .models import BalanceAssertion from ..statements.models import StatementLineReconciliation diff --git a/drcr/reports.py b/drcr/reports.py index 70872a9..7170ec1 100644 --- a/drcr/reports.py +++ b/drcr/reports.py @@ -17,7 +17,8 @@ from flask import url_for from .models import AccountConfiguration, Amount, TrialBalancer, reporting_commodity -from .webapp import all_transactions, api_transactions, eofy_date, sofy_date +from .transactions import all_transactions, api_transactions +from .webapp import eofy_date, sofy_date from datetime import datetime, timedelta diff --git a/drcr/transactions.py b/drcr/transactions.py new file mode 100644 index 0000000..c3f7969 --- /dev/null +++ b/drcr/transactions.py @@ -0,0 +1,72 @@ +# DrCr: Web-based double-entry bookkeeping framework +# Copyright (C) 2022–2024 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 .database import db +from .models import Transaction +from .plugins import transaction_providers +from .statements.models import StatementLine + +def limit_query_dt(query, field, start_date=None, end_date=None): + """Helper function to limit the query between the start and end dates""" + + if start_date and end_date: + return query.where((field >= start_date) & (field <= end_date)) + if start_date: + return query.where(field >= start_date) + if end_date: + return query.where(field <= end_date) + return query + +def all_transactions(start_date=None, end_date=None, api_stage_until=None, join_postings=True): + """Return all transactions, including from DB and API""" + + transactions = db_transactions(start_date=start_date, end_date=end_date, join_postings=join_postings) + transactions.extend(api_transactions(start_date=start_date, end_date=end_date, api_stage_until=api_stage_until)) + return transactions + +def db_transactions(start_date=None, end_date=None, join_postings=True): + """Return only transactions from DB""" + + # All Transactions in database between start_date and end_date + query = db.select(Transaction) + query = limit_query_dt(query, Transaction.dt, start_date, end_date) + if join_postings: + query = query.options(db.selectinload(Transaction.postings)) + + transactions = db.session.scalars(query).all() + return transactions + +def api_transactions(start_date=None, end_date=None, api_stage_until=None): + """Return only transactions from API""" + + # Stage 0: DB transactions + # Stage 100: Ordinary transactions + # Stage 200: Closing entries prior to tax + # Stage 300: Tax entries + # Stage 400: Closing entries after tax + + transactions = [] + + # Unreconciled StatementLines + query = db.select(StatementLine).where(StatementLine.reconciliation == None) + query = limit_query_dt(query, StatementLine.dt, start_date, end_date) + transactions.extend(line.into_transaction() for line in db.session.scalars(query).all()) + + # Plugins + for transaction_provider in transaction_providers: + transactions = transaction_provider(transactions, start_date=start_date, end_date=end_date, api_stage_until=api_stage_until) + + return transactions diff --git a/drcr/views.py b/drcr/views.py index a187372..183a620 100644 --- a/drcr/views.py +++ b/drcr/views.py @@ -20,7 +20,8 @@ from .database import db from .models import AccountConfiguration, Amount, Balance, Posting, TrialBalancer, reporting_commodity from .plugins import account_kinds, advanced_reports, data_sources from .reports import balance_sheet_report, income_statement_report -from .webapp import all_transactions, api_transactions, app +from .transactions import all_transactions, api_transactions +from .webapp import app from datetime import datetime from itertools import groupby diff --git a/drcr/webapp.py b/drcr/webapp.py index 6bf7007..2b287e7 100644 --- a/drcr/webapp.py +++ b/drcr/webapp.py @@ -23,8 +23,7 @@ from flask_sqlalchemy.record_queries import get_recorded_queries from .database import db from .models import Amount, Metadata, Transaction, reporting_commodity -from .plugins import init_plugins, transaction_providers -from .statements.models import StatementLine +from .plugins import init_plugins from datetime import datetime, timedelta import time @@ -32,48 +31,6 @@ import time app.config['SQLALCHEMY_RECORD_QUERIES'] = app.debug db.init_app(app) -def limit_query_dt(query, field, start_date=None, end_date=None): - """Helper function to limit the query between the start and end dates""" - - if start_date and end_date: - return query.where((field >= start_date) & (field <= end_date)) - if start_date: - return query.where(field >= start_date) - if end_date: - return query.where(field <= end_date) - return query - -def all_transactions(start_date=None, end_date=None, join_postings=True): - """Return all transactions, including from DB and API""" - - # All Transactions in database between start_date and end_date - query = db.select(Transaction) - query = limit_query_dt(query, Transaction.dt, start_date, end_date) - if join_postings: - query = query.options(db.selectinload(Transaction.postings)) - - transactions = db.session.scalars(query).all() - - transactions.extend(api_transactions(start_date=start_date, end_date=end_date)) - - return transactions - -def api_transactions(start_date=None, end_date=None): - """Return only transactions from API""" - - transactions = [] - - # Unreconciled StatementLines - query = db.select(StatementLine).where(StatementLine.reconciliation == None) - query = limit_query_dt(query, StatementLine.dt, start_date, end_date) - transactions.extend(line.into_transaction() for line in db.session.scalars(query).all()) - - # Plugins - for transaction_provider in transaction_providers: - transactions = transaction_provider(transactions, start_date=start_date, end_date=end_date) - - return transactions - def all_accounts(): """Return all accounts in alphabetical order"""