Compare commits

..

3 Commits

Author SHA1 Message Date
56fa6f3633
Allow splitting API transactions into multiple stages 2024-11-11 16:28:59 +11:00
97893a9c12
Provide running list of transactions to transaction providers
This will enable transaction providers to act iteratively on the results of previous transaction providers
2024-11-11 15:32:17 +11:00
541086d346
More efficient implementation for all_accounts 2024-11-11 15:26:17 +11:00
8 changed files with 104 additions and 65 deletions

View File

@ -18,7 +18,7 @@ from flask import render_template, url_for
from drcr.models import AccountConfiguration, Posting, Transaction, TrialBalancer, reporting_commodity from drcr.models import AccountConfiguration, Posting, Transaction, TrialBalancer, reporting_commodity
from drcr.database import db from drcr.database import db
from drcr.webapp import eofy_date from drcr.webapp import eofy_date, sofy_date
import drcr.plugins import drcr.plugins
from . import views # Load routes from . import views # Load routes
@ -49,25 +49,32 @@ def plugin_init():
drcr.plugins.transaction_providers.append(make_tax_transactions) drcr.plugins.transaction_providers.append(make_tax_transactions)
@assert_aud @assert_aud
def make_tax_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 # Get EOFY date
dt = 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): if (start_date is not None and start_date > dt) or (end_date is not None and end_date < dt):
return [] 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 tax_amount = report.by_id('total_tax').amount - report.by_id('offsets').amount
# Estimated tax payable # Estimated tax payable
transactions = [Transaction( transactions.append(Transaction(
dt=dt, dt=dt,
description='Estimated income tax', description='Estimated income tax',
postings=[ postings=[
Posting(account='Income Tax', quantity=tax_amount.quantity, commodity=reporting_commodity()), Posting(account='Income Tax', quantity=tax_amount.quantity, commodity=reporting_commodity()),
Posting(account='Income Tax Control', quantity=-tax_amount.quantity, commodity=reporting_commodity()) Posting(account='Income Tax Control', quantity=-tax_amount.quantity, commodity=reporting_commodity())
] ]
)] ))
# Mandatory study loan repayment # Mandatory study loan repayment
loan_repayment = report.by_id('loan_repayment').amount loan_repayment = report.by_id('loan_repayment').amount
@ -81,9 +88,6 @@ def make_tax_transactions(start_date=None, end_date=None):
] ]
)) ))
# Get trial balance
balancer = TrialBalancer.from_cached()
accounts = dict(sorted(balancer.accounts.items())) accounts = dict(sorted(balancer.accounts.items()))
# Get account configurations # Get account configurations

View File

@ -82,10 +82,7 @@ def study_loan_repayment(year, taxable_income, rfb_grossedup):
return Amount(rate * repayment_income.quantity, reporting_commodity()) return Amount(rate * repayment_income.quantity, reporting_commodity())
@assert_aud @assert_aud
def tax_summary_report(): def tax_summary_report(balancer):
# Get trial balance
balancer = TrialBalancer.from_cached(start_date=sofy_date(), end_date=eofy_date())
accounts = dict(sorted(balancer.accounts.items())) accounts = dict(sorted(balancer.accounts.items()))
# Get account configurations # Get account configurations

View File

@ -16,10 +16,11 @@
from flask import redirect, render_template, request, url_for 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.database import db
from drcr.plugins import render_plugin_template 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 .models import CGTAsset, CGTCostAdjustment
from .reports import tax_summary_report from .reports import tax_summary_report
@ -237,5 +238,9 @@ def cgt_assets():
@app.route('/tax/summary') @app.route('/tax/summary')
@assert_aud @assert_aud
def tax_summary(): 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) return render_template('report.html', report=report)

View File

@ -19,7 +19,8 @@ from flask import abort, redirect, render_template, request, url_for
from .. import AMOUNT_DPS from .. import AMOUNT_DPS
from ..database import db from ..database import db
from ..models import Amount, Posting, Transaction, TrialBalancer, queue_invalidate_running_balances, reporting_commodity 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 .models import BalanceAssertion
from ..statements.models import StatementLineReconciliation from ..statements.models import StatementLineReconciliation

View File

@ -17,7 +17,8 @@
from flask import url_for from flask import url_for
from .models import AccountConfiguration, Amount, TrialBalancer, reporting_commodity 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 from datetime import datetime, timedelta

72
drcr/transactions.py Normal file
View File

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

View File

@ -20,7 +20,8 @@ from .database import db
from .models import AccountConfiguration, Amount, Balance, Posting, TrialBalancer, reporting_commodity from .models import AccountConfiguration, Amount, Balance, Posting, TrialBalancer, reporting_commodity
from .plugins import account_kinds, advanced_reports, data_sources from .plugins import account_kinds, advanced_reports, data_sources
from .reports import balance_sheet_report, income_statement_report 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 datetime import datetime
from itertools import groupby from itertools import groupby

View File

@ -23,8 +23,7 @@ from flask_sqlalchemy.record_queries import get_recorded_queries
from .database import db from .database import db
from .models import Amount, Metadata, Transaction, reporting_commodity from .models import Amount, Metadata, Transaction, reporting_commodity
from .plugins import init_plugins, transaction_providers from .plugins import init_plugins
from .statements.models import StatementLine
from datetime import datetime, timedelta from datetime import datetime, timedelta
import time import time
@ -32,51 +31,10 @@ import time
app.config['SQLALCHEMY_RECORD_QUERIES'] = app.debug app.config['SQLALCHEMY_RECORD_QUERIES'] = app.debug
db.init_app(app) 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.extend(transaction_provider(start_date=start_date, end_date=end_date))
return transactions
def all_accounts(): def all_accounts():
# TODO: Can this be cached? """Return all accounts in alphabetical order"""
return sorted(list(set(p.account for t in all_transactions() for p in t.postings)))
return db.session.scalars('SELECT DISTINCT account FROM postings ORDER BY account').all()
def eofy_date(): def eofy_date():
"""Get the datetime for the end of the financial year""" """Get the datetime for the end of the financial year"""