diff --git a/drcr/general_journal/__init__.py b/drcr/journal/__init__.py
similarity index 100%
rename from drcr/general_journal/__init__.py
rename to drcr/journal/__init__.py
diff --git a/drcr/general_journal/models.py b/drcr/journal/models.py
similarity index 53%
rename from drcr/general_journal/models.py
rename to drcr/journal/models.py
index 33fb528..6dee0d5 100644
--- a/drcr/general_journal/models.py
+++ b/drcr/journal/models.py
@@ -1,5 +1,5 @@
# 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
# it under the terms of the GNU Affero General Public License as published by
@@ -15,33 +15,7 @@
# along with this program. If not, see .
from ..database import db
-from ..models import Amount, Posting, Transaction
-
-class GeneralJournalTransaction(db.Model, Transaction):
- __tablename__ = 'general_journal_transactions'
-
- id = db.Column(db.Integer, primary_key=True)
-
- dt = db.Column(db.DateTime)
- description = db.Column(db.String)
-
- postings = db.relationship('GeneralJournalPosting', back_populates='transaction', cascade='all, delete-orphan')
-
-class GeneralJournalPosting(db.Model, Posting):
- __tablename__ = 'general_journal_postings'
-
- id = db.Column(db.Integer, primary_key=True)
- transaction_id = db.Column(db.Integer, db.ForeignKey('general_journal_transactions.id'))
-
- description = db.Column(db.String)
- account = db.Column(db.String)
- quantity = db.Column(db.Integer)
- commodity = db.Column(db.String)
-
- transaction = db.relationship('GeneralJournalTransaction', back_populates='postings')
-
- def amount(self):
- return Amount(self.quantity, self.commodity)
+from ..models import Amount
class BalanceAssertion(db.Model):
__tablename__ = 'balance_assertions'
diff --git a/drcr/general_journal/views.py b/drcr/journal/views.py
similarity index 75%
rename from drcr/general_journal/views.py
rename to drcr/journal/views.py
index 748f1ed..87934ea 100644
--- a/drcr/general_journal/views.py
+++ b/drcr/journal/views.py
@@ -1,5 +1,5 @@
# 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
# it under the terms of the GNU Affero General Public License as published by
@@ -18,27 +18,27 @@ from flask import abort, redirect, render_template, request
from .. import AMOUNT_DPS
from ..database import db
-from ..models import TrialBalancer
+from ..models import Amount, Posting, Transaction, TrialBalancer
from ..webapp import all_transactions, app
-from .models import Amount, BalanceAssertion, GeneralJournalPosting, GeneralJournalTransaction
+from .models import BalanceAssertion
from datetime import datetime
-@app.route('/general-journal')
-def general_journal():
+@app.route('/journal')
+def journal():
return render_template(
- 'general_journal/general_journal.html',
+ 'journal/journal.html',
commodity_detail=request.args.get('commodity-detail', '0') == '1',
- transactions=sorted(GeneralJournalTransaction.query.all(), key=lambda t: t.dt)
+ transactions=sorted(Transaction.query.all(), key=lambda t: t.dt)
)
-@app.route('/general-journal/new', methods=['GET', 'POST'])
-def general_journal_new():
+@app.route('/journal/new-transaction', methods=['GET', 'POST'])
+def journal_new_transaction():
if request.method == 'GET':
- return render_template('general_journal/general_journal_edit.html', transaction=None)
+ return render_template('journal/journal_edit_transaction.html', transaction=None)
# New transaction
- transaction = GeneralJournalTransaction(
+ transaction = Transaction(
dt=datetime.strptime(request.form['dt'], '%Y-%m-%d'),
description=request.form['description'],
postings=[]
@@ -49,7 +49,7 @@ def general_journal_new():
if sign == 'cr':
amount = -amount
- posting = GeneralJournalPosting(
+ posting = Posting(
account=account,
quantity=amount.quantity,
commodity=amount.commodity
@@ -61,28 +61,30 @@ def general_journal_new():
db.session.add(transaction)
db.session.commit()
- return redirect('/general-journal')
+ return redirect('/journal')
-@app.route('/general-journal/edit', methods=['GET', 'POST'])
-def general_journal_edit():
- transaction = db.session.get(GeneralJournalTransaction, request.args['id'])
+@app.route('/journal/edit-transaction', methods=['GET', 'POST'])
+def journal_edit_transaction():
+ transaction = db.session.get(Transaction, request.args['id'])
if not transaction:
abort(404)
if request.method == 'GET':
- return render_template('general_journal/general_journal_edit.html', transaction=transaction)
+ return render_template('journal/journal_edit_transaction.html', transaction=transaction)
# Edit transaction
transaction.dt = datetime.strptime(request.form['dt'], '%Y-%m-%d')
transaction.description = request.form['description']
transaction.postings = []
+ # FIXME: This will orphan StatementLineReconciliations
+
for account, sign, amount_str in zip(request.form.getlist('account'), request.form.getlist('sign'), request.form.getlist('amount')):
amount = Amount.parse(amount_str)
if sign == 'cr':
amount = -amount
- posting = GeneralJournalPosting(
+ posting = Posting(
account=account,
quantity=amount.quantity,
commodity=amount.commodity
@@ -93,7 +95,7 @@ def general_journal_edit():
db.session.commit()
- return redirect('/general-journal')
+ return redirect('/journal')
@app.route('/balance-assertions')
def balance_assertions():
@@ -113,12 +115,12 @@ def balance_assertions():
else:
assertion_status[assertion] = False
- return render_template('general_journal/balance_assertions.html', assertions=assertions, assertion_status=assertion_status)
+ return render_template('journal/balance_assertions.html', assertions=assertions, assertion_status=assertion_status)
@app.route('/balance-assertions/new', methods=['GET', 'POST'])
def balance_assertions_new():
if request.method == 'GET':
- return render_template('general_journal/balance_assertions_edit.html', assertion=None)
+ return render_template('journal/balance_assertions_edit.html', assertion=None)
quantity = round(float(request.form['amount']) * (10**AMOUNT_DPS))
if request.form['sign'] == 'cr':
@@ -144,7 +146,7 @@ def balance_assertions_edit():
abort(404)
if request.method == 'GET':
- return render_template('general_journal/balance_assertions_edit.html', assertion=assertion)
+ return render_template('journal/balance_assertions_edit.html', assertion=assertion)
quantity = round(float(request.form['amount']) * (10**AMOUNT_DPS))
if request.form['sign'] == 'cr':
diff --git a/drcr/models.py b/drcr/models.py
index 9bd1473..6761c11 100644
--- a/drcr/models.py
+++ b/drcr/models.py
@@ -1,5 +1,5 @@
# 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
# it under the terms of the GNU Affero General Public License as published by
@@ -17,8 +17,18 @@
from markupsafe import Markup
from . import AMOUNT_DPS
+from .database import db
-class Transaction:
+class Transaction(db.Model):
+ __tablename__ = 'transactions'
+
+ id = db.Column(db.Integer, primary_key=True)
+
+ dt = db.Column(db.DateTime)
+ description = db.Column(db.String)
+
+ postings = db.relationship('Posting', back_populates='transaction', cascade='all, delete-orphan')
+
def __init__(self, dt=None, description=None, postings=None):
self.dt = dt
self.description = description
@@ -40,7 +50,19 @@ class Transaction:
if total_dr != total_cr:
raise AssertionError('Transaction debits ({}) and credits ({}) do not balance'.format(total_dr, total_cr))
-class Posting:
+class Posting(db.Model):
+ __tablename__ = 'postings'
+
+ id = db.Column(db.Integer, primary_key=True)
+ transaction_id = db.Column(db.Integer, db.ForeignKey('transactions.id'))
+
+ description = db.Column(db.String)
+ account = db.Column(db.String)
+ quantity = db.Column(db.Integer)
+ commodity = db.Column(db.String)
+
+ transaction = db.relationship('Transaction', back_populates='postings')
+
def __init__(self, description=None, account=None, quantity=None, commodity=None):
self.description = description
self.account = account
diff --git a/drcr/statements/models.py b/drcr/statements/models.py
index 951865b..06c8ccc 100644
--- a/drcr/statements/models.py
+++ b/drcr/statements/models.py
@@ -1,5 +1,5 @@
# 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
# it under the terms of the GNU Affero General Public License as published by
@@ -29,15 +29,14 @@ class StatementLine(db.Model):
balance = db.Column(db.Integer)
commodity = db.Column(db.String)
- postings = db.relationship('StatementLinePosting', back_populates='statement_line')
+ reconciliation = db.relationship('StatementLineReconciliation', back_populates='statement_line', uselist=False)
def amount(self):
return Amount(self.quantity, self.commodity)
def into_transaction(self):
- if len(self.postings) > 0:
+ if len(self.reconciliations) > 0:
# Will already be accounted for in a StatementLineTransaction
- #return None
raise Exception('Should not call into_transaction on a StatementLine with associated StatementLinePosting')
# Not classified
@@ -53,53 +52,18 @@ class StatementLine(db.Model):
)
def is_complex(self):
- if len(self.postings) > 1:
- return True
-
- if any(len(p.transaction.postings) > 2 for p in self.postings):
+ if self.reconciliation and len(self.reconciliation.posting.transaction.postings) > 2:
return True
return False
-
- def delete_postings(self):
- """Delete existing StatementLineTransactions with postings for this StatementLine"""
-
- for posting in self.postings:
- # TODO: Will be wonky if transaction covers multiple StatementLines
- db.session.delete(posting.transaction)
-class StatementLineTransaction(db.Model, Transaction):
- __tablename__ = 'statement_line_transactions'
+class StatementLineReconciliation(db.Model):
+ __tablename__ = 'statement_line_reconciliations'
id = db.Column(db.Integer, primary_key=True)
- dt = db.Column(db.DateTime)
- description = db.Column(db.String)
+ statement_line_id = db.Column(db.Integer, db.ForeignKey('statement_lines.id'))
+ posting_id = db.Column(db.Integer, db.ForeignKey('postings.id'))
- postings = db.relationship('StatementLinePosting', back_populates='transaction', cascade='all, delete')
-
- def charge_account(self, source_account):
- if len(self.postings) > 2:
- raise Exception('Cannot call charge_account on StatementLineTransaction with more than 2 postings')
-
- for posting in self.postings:
- if posting.account != source_account:
- return posting.account
-
-class StatementLinePosting(db.Model, Posting):
- __tablename__ = 'statement_line_postings'
-
- id = db.Column(db.Integer, primary_key=True)
- transaction_id = db.Column(db.Integer, db.ForeignKey('statement_line_transactions.id'))
- line_id = db.Column(db.Integer, db.ForeignKey('statement_lines.id'))
-
- description = db.Column(db.String)
- account = db.Column(db.String)
- quantity = db.Column(db.Integer)
- commodity = db.Column(db.String)
-
- transaction = db.relationship('StatementLineTransaction', back_populates='postings')
- statement_line = db.relationship('StatementLine', back_populates='postings')
-
- def amount(self):
- return Amount(self.quantity, self.commodity)
+ statement_line = db.relationship('StatementLine', back_populates='reconciliation')
+ posting = db.relationship('Posting')
diff --git a/drcr/statements/views.py b/drcr/statements/views.py
index 8b3b288..fbfce33 100644
--- a/drcr/statements/views.py
+++ b/drcr/statements/views.py
@@ -1,5 +1,5 @@
# 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
# it under the terms of the GNU Affero General Public License as published by
@@ -19,7 +19,7 @@ from flask import abort, redirect, render_template, request
from .. import AMOUNT_DPS
from ..database import db
from ..webapp import app
-from .models import StatementLine, StatementLinePosting, StatementLineTransaction
+from .models import StatementLine
@app.route('/statement-lines')
def statement_lines():
@@ -53,57 +53,6 @@ def statement_line_charge():
return 'OK'
-@app.route('/statement-lines/edit-transaction', methods=['GET', 'POST'])
-def statement_line_edit_transaction():
- statement_line = db.session.get(StatementLine, request.args['line-id'])
- if not statement_line:
- abort(404)
-
- if request.method == 'GET':
- if len(statement_line.postings) == 0:
- return render_template('statements/statement_line_edit_transaction.html', statement_line=statement_line, transaction=None)
-
- if len(statement_line.postings) > 1:
- # NYI
- raise Exception('Cannot display a StatementLine with >2 postings')
-
- # Get existing transaction
- return render_template('statements/statement_line_edit_transaction.html', statement_line=statement_line, transaction=statement_line.postings[0].transaction)
-
- # Delete existing postings
- statement_line.delete_postings()
-
- if len(request.form.getlist('charge-account')) == 1:
- # Simple transaction
- postings = [
- StatementLinePosting(statement_line=statement_line, account=statement_line.source_account, quantity=statement_line.quantity, commodity=statement_line.commodity),
- StatementLinePosting(account=request.form['charge-account'], quantity=-statement_line.quantity, commodity=statement_line.commodity)
- ]
- else:
- # Complex transaction, multiple postings
- postings = [
- StatementLinePosting(statement_line=statement_line, account=statement_line.source_account, quantity=statement_line.quantity, commodity=statement_line.commodity)
- ]
-
- for charge_account, charge_amount_str in zip(request.form.getlist('charge-account'), request.form.getlist('charge-amount')):
- charge_quantity = round(float(charge_amount_str) * (10**AMOUNT_DPS))
- if statement_line.quantity >= 0:
- # If source is debit, charge is credit
- charge_quantity = -charge_quantity
- postings.append(StatementLinePosting(account=charge_account, quantity=charge_quantity, commodity=statement_line.commodity))
-
- transaction = StatementLineTransaction(
- dt=statement_line.dt,
- description=request.form['description'],
- postings=postings
- )
- transaction.assert_valid()
-
- db.session.add(transaction)
- db.session.commit()
-
- return redirect('/statement-lines')
-
@app.route('/statement-lines/reconcile-transfer', methods=['POST'])
def statement_line_reconcile_transfer():
line_ids = request.form.getlist('sel-line-id')
diff --git a/drcr/templates/base.html b/drcr/templates/base.html
index 6736f15..3720aa4 100644
--- a/drcr/templates/base.html
+++ b/drcr/templates/base.html
@@ -1,5 +1,5 @@
{# 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
it under the terms of the GNU Affero General Public License as published by
@@ -31,7 +31,7 @@
DrCr
diff --git a/drcr/templates/index.html b/drcr/templates/index.html
index f475053..e83dddd 100644
--- a/drcr/templates/index.html
+++ b/drcr/templates/index.html
@@ -1,5 +1,5 @@
{# 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
it under the terms of the GNU Affero General Public License as published by
@@ -21,7 +21,7 @@
{% block content %}
Data sources
diff --git a/drcr/templates/general_journal/balance_assertions.html b/drcr/templates/journal/balance_assertions.html
similarity index 96%
rename from drcr/templates/general_journal/balance_assertions.html
rename to drcr/templates/journal/balance_assertions.html
index 63da64c..bb747a0 100644
--- a/drcr/templates/general_journal/balance_assertions.html
+++ b/drcr/templates/journal/balance_assertions.html
@@ -1,5 +1,5 @@
{# 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
it under the terms of the GNU Affero General Public License as published by
diff --git a/drcr/templates/general_journal/balance_assertions_edit.html b/drcr/templates/journal/balance_assertions_edit.html
similarity index 97%
rename from drcr/templates/general_journal/balance_assertions_edit.html
rename to drcr/templates/journal/balance_assertions_edit.html
index f269309..d2e4d57 100644
--- a/drcr/templates/general_journal/balance_assertions_edit.html
+++ b/drcr/templates/journal/balance_assertions_edit.html
@@ -1,5 +1,5 @@
{# 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
it under the terms of the GNU Affero General Public License as published by
diff --git a/drcr/templates/general_journal/general_journal.html b/drcr/templates/journal/journal.html
similarity index 84%
rename from drcr/templates/general_journal/general_journal.html
rename to drcr/templates/journal/journal.html
index 1ab0308..4510932 100644
--- a/drcr/templates/general_journal/general_journal.html
+++ b/drcr/templates/journal/journal.html
@@ -1,5 +1,5 @@
{# 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
it under the terms of the GNU Affero General Public License as published by
@@ -16,13 +16,13 @@
#}
{% extends 'base.html' %}
-{% block title %}General journal{% endblock %}
+{% block title %}Journal{% endblock %}
{% block content %}
-
General journal
+
Journal
-
New transaction
+ {#
New transaction#}
{% if commodity_detail %}
Hide commodity detail
{% else %}
@@ -43,7 +43,7 @@
{% for transaction in transactions %}
{{ transaction.dt.strftime('%Y-%m-%d') }} |
- {{ transaction.description }} |
+ {{ transaction.description }} {##} |
|
|
diff --git a/drcr/templates/general_journal/general_journal_edit.html b/drcr/templates/journal/journal_edit_transaction.html
similarity index 96%
rename from drcr/templates/general_journal/general_journal_edit.html
rename to drcr/templates/journal/journal_edit_transaction.html
index 7a4f1d9..d215d9e 100644
--- a/drcr/templates/general_journal/general_journal_edit.html
+++ b/drcr/templates/journal/journal_edit_transaction.html
@@ -1,5 +1,5 @@
{# 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
it under the terms of the GNU Affero General Public License as published by
@@ -16,10 +16,10 @@
#}
{% extends 'base.html' %}
-{% block title %}{{ 'Edit' if transaction else 'New' }} general journal transaction{% endblock %}
+{% block title %}{{ 'Edit' if transaction else 'New' }} transaction{% endblock %}
{% block content %}
-
{{ 'Edit' if transaction else 'New' }} general journal transaction
+
{{ 'Edit' if transaction else 'New' }} transaction