monopyly 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monopyly/CHANGELOG.md +27 -0
- monopyly/README.md +3 -3
- monopyly/__init__.py +22 -27
- monopyly/_version.py +2 -2
- monopyly/auth/blueprint.py +2 -0
- monopyly/auth/routes.py +2 -3
- monopyly/banking/accounts.py +7 -7
- monopyly/banking/actions.py +20 -17
- monopyly/banking/banks.py +1 -1
- monopyly/banking/blueprint.py +2 -0
- monopyly/banking/filters.py +6 -6
- monopyly/banking/forms.py +3 -5
- monopyly/banking/routes.py +72 -10
- monopyly/banking/transactions.py +15 -7
- monopyly/common/forms/__init__.py +8 -0
- monopyly/common/forms/_forms.py +1 -2
- monopyly/common/forms/fields.py +0 -2
- monopyly/common/forms/utils.py +1 -1
- monopyly/common/transactions.py +89 -14
- monopyly/core/actions.py +2 -8
- monopyly/core/blueprint.py +2 -0
- monopyly/core/filters.py +0 -2
- monopyly/core/routes.py +1 -1
- monopyly/credit/accounts.py +1 -1
- monopyly/credit/actions.py +4 -5
- monopyly/credit/blueprint.py +2 -0
- monopyly/credit/cards.py +7 -3
- monopyly/credit/forms.py +3 -5
- monopyly/credit/routes.py +65 -87
- monopyly/credit/statements.py +1 -1
- monopyly/credit/transactions/__init__.py +2 -0
- monopyly/credit/transactions/_transactions.py +18 -8
- monopyly/credit/transactions/activity/__init__.py +6 -0
- monopyly/credit/transactions/activity/parser.py +0 -1
- monopyly/credit/transactions/activity/reconciliation.py +25 -4
- monopyly/database/__init__.py +1 -59
- monopyly/database/models.py +198 -276
- monopyly/database/preloads.sql +6 -1
- monopyly/scripts/screenshot_application.py +100 -0
- monopyly/static/chartist-1.5.0.min.js +8 -0
- monopyly/static/css/style.css +35 -14
- monopyly/static/img/about/bank-account-details.png +0 -0
- monopyly/static/img/about/bank-account-summaries.png +0 -0
- monopyly/static/img/about/bank-accounts.png +0 -0
- monopyly/static/img/about/credit-account-details.png +0 -0
- monopyly/static/img/about/credit-statement-details.png +0 -0
- monopyly/static/img/about/credit-transactions.png +0 -0
- monopyly/static/img/about/homepage-user.png +0 -0
- monopyly/static/img/about/homepage.png +0 -0
- monopyly/static/jquery-3.7.1.min.js +2 -0
- monopyly/static/js/add-transfer.js +8 -9
- monopyly/static/js/bind-tag-actions.js +6 -0
- monopyly/static/js/create-balance-chart.js +2 -2
- monopyly/static/js/create-category-chart.js +1 -1
- monopyly/static/js/load-more-transactions.js +27 -0
- monopyly/static/js/modules/expand-transaction.js +7 -6
- monopyly/static/js/modules/update-display-ajax.js +20 -1
- monopyly/static/js/update-transactions-display.js +8 -2
- monopyly/templates/banking/account_page.html +15 -16
- monopyly/templates/banking/account_summaries.html +2 -2
- monopyly/templates/banking/account_summary.html +1 -1
- monopyly/templates/banking/accounts_page.html +2 -2
- monopyly/templates/banking/transactions_table/table.html +3 -0
- monopyly/templates/banking/transactions_table/transactions.html +0 -1
- monopyly/templates/common/tag_tree.html +25 -0
- monopyly/templates/{credit → common}/tags_page.html +7 -3
- monopyly/templates/common/transactions_table/linked_bank_transaction.html +2 -2
- monopyly/templates/common/transactions_table/table.html +6 -0
- monopyly/templates/common/transactions_table/transactions.html +9 -15
- monopyly/templates/core/index.html +112 -101
- monopyly/templates/core/profile.html +1 -1
- monopyly/templates/credit/statement_page.html +2 -2
- monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +1 -1
- monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +3 -3
- monopyly/templates/credit/statement_summary.html +2 -2
- monopyly/templates/credit/transaction_submission_page.html +3 -3
- monopyly/templates/credit/transactions_page.html +19 -3
- monopyly/templates/credit/transactions_table/condensed_row_content.html +2 -3
- monopyly/templates/credit/transactions_table/expanded_row_content.html +5 -5
- monopyly/templates/credit/transactions_table/table.html +3 -0
- monopyly/templates/credit/transactions_table/transactions.html +0 -1
- monopyly/templates/layout.html +9 -4
- {monopyly-1.5.1.dist-info → monopyly-1.6.0.dist-info}/METADATA +12 -13
- {monopyly-1.5.1.dist-info → monopyly-1.6.0.dist-info}/RECORD +88 -87
- monopyly-1.6.0.dist-info/entry_points.txt +3 -0
- monopyly/cli/apps.py +0 -108
- monopyly/cli/launch.py +0 -135
- monopyly/config/__init__.py +0 -1
- monopyly/config/default_settings.py +0 -56
- monopyly/config/settings.py +0 -59
- monopyly/static/jquery-3.7.0.min.js +0 -2
- monopyly/templates/credit/tag_tree/subtag_tree.html +0 -22
- monopyly/templates/credit/tag_tree/tag_tree.html +0 -13
- monopyly-1.5.1.dist-info/entry_points.txt +0 -2
- {monopyly-1.5.1.dist-info → monopyly-1.6.0.dist-info}/WHEEL +0 -0
- {monopyly-1.5.1.dist-info → monopyly-1.6.0.dist-info}/licenses/COPYING +0 -0
- {monopyly-1.5.1.dist-info → monopyly-1.6.0.dist-info}/licenses/LICENSE +0 -0
monopyly/common/transactions.py
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
Tools for building a common transaction interface.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from authanor.database.handler import DatabaseHandler, DatabaseViewHandler
|
|
8
|
-
from flask import current_app
|
|
5
|
+
from dry_foundation.database.handler import DatabaseHandler, DatabaseViewHandler
|
|
6
|
+
from flask import abort, current_app
|
|
9
7
|
|
|
10
8
|
from ..database.models import (
|
|
11
9
|
BankAccountTypeView,
|
|
@@ -31,17 +29,27 @@ class TransactionHandler(DatabaseViewHandler):
|
|
|
31
29
|
"""
|
|
32
30
|
|
|
33
31
|
@classmethod
|
|
34
|
-
def _customize_entries_query(
|
|
32
|
+
def _customize_entries_query(
|
|
33
|
+
cls, query, criteria, column_orders, offset=None, limit=None
|
|
34
|
+
):
|
|
35
35
|
# Group transactions and order by transaction date
|
|
36
36
|
query = query.group_by(cls.model.id)
|
|
37
|
-
return super()._customize_entries_query(
|
|
37
|
+
return super()._customize_entries_query(
|
|
38
|
+
query, criteria, column_orders, offset=offset, limit=limit
|
|
39
|
+
)
|
|
38
40
|
|
|
39
41
|
@classmethod
|
|
40
|
-
def _get_transactions(
|
|
42
|
+
def _get_transactions(
|
|
43
|
+
cls, criteria=None, sort_order="DESC", offset=None, limit=None
|
|
44
|
+
):
|
|
41
45
|
# Specify transaction order
|
|
42
46
|
column_orders = {cls.model.transaction_date: sort_order}
|
|
43
47
|
entries = cls.get_entries(
|
|
44
|
-
entry_ids=None,
|
|
48
|
+
entry_ids=None,
|
|
49
|
+
criteria=criteria,
|
|
50
|
+
column_orders=column_orders,
|
|
51
|
+
offset=offset,
|
|
52
|
+
limit=limit,
|
|
45
53
|
)
|
|
46
54
|
return entries
|
|
47
55
|
|
|
@@ -76,7 +84,26 @@ class TransactionHandler(DatabaseViewHandler):
|
|
|
76
84
|
|
|
77
85
|
@classmethod
|
|
78
86
|
def update_entry(cls, entry_id, **field_values):
|
|
79
|
-
"""
|
|
87
|
+
"""
|
|
88
|
+
Update a transaction in the database.
|
|
89
|
+
|
|
90
|
+
Accept a mapping relating given inputs to database fields. This
|
|
91
|
+
mapping is used to update an existing transaction in the
|
|
92
|
+
database. All fields are sanitized prior to updating, and any
|
|
93
|
+
subtransactions are identified for individual processing.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
entry_id : int
|
|
98
|
+
The ID of the transaction to be updated.
|
|
99
|
+
**field_values :
|
|
100
|
+
Values for the fields to update in the transaction.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
transaction : database.models.BankTransaction
|
|
105
|
+
The saved transaction.
|
|
106
|
+
"""
|
|
80
107
|
# Extend the default method to account for subtransactions
|
|
81
108
|
subtransactions_data = field_values.pop("subtransactions", None)
|
|
82
109
|
transaction = super().update_entry(entry_id, **field_values)
|
|
@@ -100,6 +127,30 @@ class TransactionHandler(DatabaseViewHandler):
|
|
|
100
127
|
# Flush to the database after all subtransactions have been added
|
|
101
128
|
cls._db.session.flush()
|
|
102
129
|
|
|
130
|
+
@classmethod
|
|
131
|
+
def delete_entry(cls, entry_id):
|
|
132
|
+
"""
|
|
133
|
+
Delete a transaction in the database given its ID.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
entry_id : int
|
|
138
|
+
The ID of the transaction to be deleted.
|
|
139
|
+
|
|
140
|
+
Notes
|
|
141
|
+
-----
|
|
142
|
+
This will also delete any internal transactions associated with
|
|
143
|
+
this transaction, since the internal transaction link no longer
|
|
144
|
+
exists.
|
|
145
|
+
"""
|
|
146
|
+
internal_transaction = cls.get_entry(entry_id).internal_transaction
|
|
147
|
+
super().delete_entry(entry_id)
|
|
148
|
+
if internal_transaction:
|
|
149
|
+
cls._db.session.refresh(internal_transaction)
|
|
150
|
+
if len(internal_transaction.transaction_views) <= 1:
|
|
151
|
+
cls._db.session.delete(internal_transaction)
|
|
152
|
+
cls._db.session.flush()
|
|
153
|
+
|
|
103
154
|
|
|
104
155
|
def get_linked_transaction(transaction):
|
|
105
156
|
"""
|
|
@@ -232,10 +283,10 @@ class TransactionTagHandler(DatabaseHandler, model=TransactionTag):
|
|
|
232
283
|
return tags
|
|
233
284
|
|
|
234
285
|
@classmethod
|
|
235
|
-
def _filter_entries(cls, query, criteria):
|
|
286
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
236
287
|
# Only get distinct tag entries
|
|
237
288
|
query = query.distinct()
|
|
238
|
-
return super()._filter_entries(query, criteria)
|
|
289
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
239
290
|
|
|
240
291
|
@classmethod
|
|
241
292
|
def get_subtags(cls, tag):
|
|
@@ -359,6 +410,25 @@ class TransactionTagHandler(DatabaseHandler, model=TransactionTag):
|
|
|
359
410
|
tag = cls._db.session.execute(query).scalar_one_or_none()
|
|
360
411
|
return tag
|
|
361
412
|
|
|
413
|
+
@classmethod
|
|
414
|
+
def delete_entry(cls, entry_id):
|
|
415
|
+
"""
|
|
416
|
+
Delete the tag in the database given its ID.
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
entry_id : int
|
|
421
|
+
The ID of the tag to be deleted.
|
|
422
|
+
"""
|
|
423
|
+
super().delete_entry(entry_id)
|
|
424
|
+
|
|
425
|
+
@classmethod
|
|
426
|
+
def _retrieve_authorized_manipulable_entry(cls, entry_id):
|
|
427
|
+
tag = super()._retrieve_authorized_manipulable_entry(entry_id)
|
|
428
|
+
if tag.user_id != cls.user_id:
|
|
429
|
+
abort(403, "The current user is not authorized to manipulate this tag.")
|
|
430
|
+
return tag
|
|
431
|
+
|
|
362
432
|
|
|
363
433
|
def categorize(transactions):
|
|
364
434
|
"""
|
|
@@ -402,6 +472,11 @@ class CategoryTree:
|
|
|
402
472
|
"""
|
|
403
473
|
Store a tree of categories.
|
|
404
474
|
|
|
475
|
+
The category tree is a tree of categorized subtransactions. Each
|
|
476
|
+
leaf of the tree represents a transaction tag and the
|
|
477
|
+
subtransactions that have been categorized according to that tag
|
|
478
|
+
(the category).
|
|
479
|
+
|
|
405
480
|
Parameters
|
|
406
481
|
----------
|
|
407
482
|
category : database.models.TransactionTag, str
|
|
@@ -470,9 +545,9 @@ class RootCategoryTree(CategoryTree):
|
|
|
470
545
|
Given a subtransaction, add that subtransaction to the category
|
|
471
546
|
tree according to its tags. If multiple tags exist at the same
|
|
472
547
|
level of the tree (i.e., a subtransaction with tags in diverging
|
|
473
|
-
branches), the
|
|
474
|
-
tag is listed only as a member of the root tree and not
|
|
475
|
-
member of any other subcategory tree.
|
|
548
|
+
branches), the subtransaction is determined to be "uncategorizable"
|
|
549
|
+
and the tag is listed only as a member of the root tree and not
|
|
550
|
+
as a member of any other subcategory tree.
|
|
476
551
|
|
|
477
552
|
Parameters
|
|
478
553
|
----------
|
monopyly/core/actions.py
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
"""Module describing logical core actions (to be used in routes)."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
|
|
5
3
|
import markdown
|
|
6
4
|
|
|
7
5
|
|
|
8
|
-
def get_timestamp():
|
|
9
|
-
"""Get a timestamp for backup filenames."""
|
|
10
|
-
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
11
|
-
|
|
12
|
-
|
|
13
6
|
class MarkdownConverter:
|
|
7
|
+
"""An object to convert Markdown to HTML."""
|
|
14
8
|
|
|
15
9
|
replacements = {
|
|
16
10
|
"src": [
|
|
@@ -78,7 +72,7 @@ def convert_readme_to_html_template(readme_path):
|
|
|
78
72
|
'<div class="resource-links">'
|
|
79
73
|
" <h2>Links</h2>"
|
|
80
74
|
' <p><a href="{{ url_for("core.story") }}">Story</a></p>'
|
|
81
|
-
' <p><a href="{{ url_for("core.
|
|
75
|
+
' <p><a href="{{ url_for("core.application_credits") }}">Credits</a></p>'
|
|
82
76
|
"</div>"
|
|
83
77
|
),
|
|
84
78
|
)
|
monopyly/core/blueprint.py
CHANGED
monopyly/core/filters.py
CHANGED
monopyly/core/routes.py
CHANGED
monopyly/credit/accounts.py
CHANGED
monopyly/credit/actions.py
CHANGED
|
@@ -94,9 +94,8 @@ def get_potential_preceding_card(card):
|
|
|
94
94
|
card_ids=(other_card.id,),
|
|
95
95
|
)
|
|
96
96
|
latest_statement = statements.first()
|
|
97
|
-
if latest_statement:
|
|
98
|
-
|
|
99
|
-
return other_card
|
|
97
|
+
if latest_statement and latest_statement.balance > 0:
|
|
98
|
+
return other_card
|
|
100
99
|
# Card does not meet all of these conditions
|
|
101
100
|
return None
|
|
102
101
|
|
|
@@ -110,8 +109,8 @@ def transfer_credit_card_statement(form, card_id, prior_card_id):
|
|
|
110
109
|
statements = CreditStatementHandler.get_statements(card_ids=(prior_card_id,))
|
|
111
110
|
latest_statement = statements.first()
|
|
112
111
|
CreditStatementHandler.update_entry(latest_statement.id, card_id=card_id)
|
|
113
|
-
# Deactivate the old card
|
|
114
|
-
|
|
112
|
+
# Deactivate the old card (after ensuring it exists and is accessible)
|
|
113
|
+
CreditCardHandler.get_entry(prior_card_id)
|
|
115
114
|
CreditCardHandler.update_entry(prior_card_id, active=0)
|
|
116
115
|
|
|
117
116
|
|
monopyly/credit/blueprint.py
CHANGED
monopyly/credit/cards.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Tools for interacting with credit cards in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation.database.handler import DatabaseHandler
|
|
6
6
|
|
|
7
7
|
from ..common.forms.utils import execute_on_form_validation
|
|
8
8
|
from ..database.models import Bank, CreditAccount, CreditCard
|
|
@@ -99,8 +99,12 @@ class CreditCardHandler(DatabaseHandler, model=CreditCard):
|
|
|
99
99
|
return card
|
|
100
100
|
|
|
101
101
|
@classmethod
|
|
102
|
-
def _customize_entries_query(
|
|
103
|
-
|
|
102
|
+
def _customize_entries_query(
|
|
103
|
+
cls, query, filters, sort_order, offset=None, limit=None
|
|
104
|
+
):
|
|
105
|
+
query = super()._customize_entries_query(
|
|
106
|
+
query, filters, sort_order, offset=offset, limit=limit
|
|
107
|
+
)
|
|
104
108
|
# Order cards by active status (active cards first)
|
|
105
109
|
query = query.order_by(cls.model.active.desc())
|
|
106
110
|
return query
|
monopyly/credit/forms.py
CHANGED
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
Generate credit card forms for the user to complete.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from flask import abort
|
|
6
6
|
from wtforms.fields import (
|
|
7
7
|
BooleanField,
|
|
8
8
|
FieldList,
|
|
9
9
|
FormField,
|
|
10
10
|
IntegerField,
|
|
11
11
|
RadioField,
|
|
12
|
-
StringField,
|
|
13
12
|
SubmitField,
|
|
14
13
|
)
|
|
15
14
|
from wtforms.validators import DataRequired, Optional
|
|
16
15
|
|
|
17
|
-
from ..banking.
|
|
18
|
-
from ..banking.forms import BankSelectField, BankSubform
|
|
16
|
+
from ..banking.forms import BankSubform
|
|
19
17
|
from ..common.forms import AcquisitionSubform, EntryForm, EntrySubform, TransactionForm
|
|
20
18
|
from ..common.forms.fields import (
|
|
21
19
|
CustomChoiceSelectField,
|
|
@@ -335,7 +333,7 @@ class CreditTransactionForm(TransactionForm):
|
|
|
335
333
|
"""Gather data for the form from the given database entry."""
|
|
336
334
|
if isinstance(entry, CreditTransactionView):
|
|
337
335
|
data = self._gather_transaction_data(entry)
|
|
338
|
-
statement_info = entry.
|
|
336
|
+
statement_info = entry.statement_view
|
|
339
337
|
elif isinstance(entry, (CreditCard, CreditStatementView)):
|
|
340
338
|
data = {}
|
|
341
339
|
statement_info = entry
|
monopyly/credit/routes.py
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
Routes for credit card financials.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
|
|
5
|
+
from dry_foundation.database import db_transaction
|
|
7
6
|
from flask import (
|
|
7
|
+
abort,
|
|
8
8
|
flash,
|
|
9
|
-
g,
|
|
10
9
|
jsonify,
|
|
11
10
|
redirect,
|
|
12
11
|
render_template,
|
|
@@ -14,23 +13,18 @@ from flask import (
|
|
|
14
13
|
session,
|
|
15
14
|
url_for,
|
|
16
15
|
)
|
|
17
|
-
from fuisce.database import db_transaction
|
|
18
16
|
from sqlalchemy.exc import MultipleResultsFound
|
|
19
|
-
from werkzeug.exceptions import abort
|
|
20
|
-
from wtforms.validators import ValidationError
|
|
21
17
|
|
|
22
18
|
from ..auth.tools import login_required
|
|
23
19
|
from ..banking.accounts import BankAccountHandler
|
|
24
20
|
from ..banking.banks import BankHandler
|
|
25
|
-
from ..banking.transactions import BankTransactionHandler
|
|
26
|
-
from ..common.forms import form_err_msg
|
|
27
21
|
from ..common.forms.utils import extend_field_list_for_ajax
|
|
28
22
|
from ..common.transactions import (
|
|
29
23
|
categorize,
|
|
30
24
|
get_linked_transaction,
|
|
31
25
|
highlight_unmatched_transactions,
|
|
32
26
|
)
|
|
33
|
-
from ..common.utils import dedelimit_float, parse_date
|
|
27
|
+
from ..common.utils import dedelimit_float, parse_date
|
|
34
28
|
from .accounts import CreditAccountHandler
|
|
35
29
|
from .actions import (
|
|
36
30
|
get_card_statement_grouping,
|
|
@@ -44,13 +38,16 @@ from .blueprint import bp
|
|
|
44
38
|
from .cards import CreditCardHandler, save_card
|
|
45
39
|
from .forms import CardStatementTransferForm, CreditCardForm, CreditTransactionForm
|
|
46
40
|
from .statements import CreditStatementHandler
|
|
47
|
-
from .transactions import
|
|
41
|
+
from .transactions import CreditTransactionHandler, save_transaction
|
|
48
42
|
from .transactions.activity import (
|
|
49
43
|
ActivityMatchmaker,
|
|
50
44
|
TransactionActivities,
|
|
51
45
|
parse_transaction_activity_file,
|
|
52
46
|
)
|
|
53
47
|
|
|
48
|
+
# Set a limit on the number of transactions loaded at one time for certain routes
|
|
49
|
+
TRANSACTION_LIMIT = 100
|
|
50
|
+
|
|
54
51
|
|
|
55
52
|
@bp.route("/cards")
|
|
56
53
|
@login_required
|
|
@@ -206,7 +203,7 @@ def load_statement_details(statement_id):
|
|
|
206
203
|
return render_template(
|
|
207
204
|
"credit/statement_page.html",
|
|
208
205
|
statement=statement,
|
|
209
|
-
|
|
206
|
+
transactions=transactions,
|
|
210
207
|
bank_accounts=bank_accounts,
|
|
211
208
|
chart_data=categories.assemble_chart_data(exclude=["Credit payments"]),
|
|
212
209
|
)
|
|
@@ -261,7 +258,7 @@ def pay_credit_card(card_id, statement_id):
|
|
|
261
258
|
bank_accounts=bank_accounts,
|
|
262
259
|
)
|
|
263
260
|
transactions_table_template = render_template(
|
|
264
|
-
"credit/transactions_table/
|
|
261
|
+
"credit/transactions_table/table.html",
|
|
265
262
|
transactions=transactions,
|
|
266
263
|
)
|
|
267
264
|
return jsonify((summary_template, transactions_table_template))
|
|
@@ -299,7 +296,7 @@ def load_statement_reconciliation_details(statement_id):
|
|
|
299
296
|
return render_template(
|
|
300
297
|
"credit/statement_reconciliation/statement_reconciliation_page.html",
|
|
301
298
|
statement=statement,
|
|
302
|
-
|
|
299
|
+
transactions=transactions,
|
|
303
300
|
discrepant_records=matchmaker.match_discrepancies,
|
|
304
301
|
discrepant_amount=abs(statement_transaction_balance - activities.total),
|
|
305
302
|
unrecorded_activities=matchmaker.unmatched_activities,
|
|
@@ -321,6 +318,7 @@ def clear_reconciliation_info():
|
|
|
321
318
|
"credit.update_transaction",
|
|
322
319
|
"credit.infer_statement",
|
|
323
320
|
"credit.suggest_transaction_autocomplete",
|
|
321
|
+
"credit.add_subtransaction_fields",
|
|
324
322
|
"credit.delete_transaction",
|
|
325
323
|
"static",
|
|
326
324
|
None,
|
|
@@ -344,15 +342,57 @@ def load_transactions(card_id):
|
|
|
344
342
|
# Get all of the user's transactions for the selected cards
|
|
345
343
|
sort_order = "DESC"
|
|
346
344
|
transactions = CreditTransactionHandler.get_transactions(
|
|
347
|
-
card_ids=selected_card_ids,
|
|
348
|
-
|
|
349
|
-
)
|
|
345
|
+
card_ids=selected_card_ids, sort_order=sort_order
|
|
346
|
+
).all()
|
|
350
347
|
return render_template(
|
|
351
348
|
"credit/transactions_page.html",
|
|
352
349
|
filter_cards=cards,
|
|
353
350
|
selected_card_ids=selected_card_ids,
|
|
354
351
|
sort_order=sort_order,
|
|
355
|
-
transactions=
|
|
352
|
+
transactions=transactions[:TRANSACTION_LIMIT],
|
|
353
|
+
total_transactions=len(transactions),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@bp.route("/_extra_transactions", methods=("POST",))
|
|
358
|
+
@login_required
|
|
359
|
+
def load_more_transactions():
|
|
360
|
+
# Get info about the transactions being displayed from the AJAX request
|
|
361
|
+
post_args = request.get_json()
|
|
362
|
+
selected_card_ids = map(int, post_args["selected_card_ids"])
|
|
363
|
+
sort_order = "ASC" if post_args["sort_order"] == "asc" else "DESC"
|
|
364
|
+
block_index = post_args["block_count"] - 1
|
|
365
|
+
full_view = post_args["full_view"]
|
|
366
|
+
# Get a subset of the remaining transactions to load
|
|
367
|
+
more_transactions = CreditTransactionHandler.get_transactions(
|
|
368
|
+
card_ids=selected_card_ids,
|
|
369
|
+
sort_order=sort_order,
|
|
370
|
+
offset=block_index * TRANSACTION_LIMIT,
|
|
371
|
+
limit=TRANSACTION_LIMIT,
|
|
372
|
+
)
|
|
373
|
+
return render_template(
|
|
374
|
+
"credit/transactions_table/transactions.html",
|
|
375
|
+
transactions=more_transactions,
|
|
376
|
+
full_view=full_view,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@bp.route("/_update_transactions_display", methods=("POST",))
|
|
381
|
+
@login_required
|
|
382
|
+
def update_transactions_display():
|
|
383
|
+
# Separate the arguments of the POST method
|
|
384
|
+
post_args = request.get_json()
|
|
385
|
+
card_ids = map(int, post_args["card_ids"])
|
|
386
|
+
sort_order = "ASC" if post_args["sort_order"] == "asc" else "DESC"
|
|
387
|
+
# Filter selected transactions from the database
|
|
388
|
+
transactions = CreditTransactionHandler.get_transactions(
|
|
389
|
+
card_ids=card_ids, sort_order=sort_order, limit=100
|
|
390
|
+
)
|
|
391
|
+
return render_template(
|
|
392
|
+
"credit/transactions_table/table.html",
|
|
393
|
+
sort_order=sort_order,
|
|
394
|
+
transactions=transactions,
|
|
395
|
+
full_view=True,
|
|
356
396
|
)
|
|
357
397
|
|
|
358
398
|
|
|
@@ -385,26 +425,6 @@ def show_linked_transaction():
|
|
|
385
425
|
)
|
|
386
426
|
|
|
387
427
|
|
|
388
|
-
@bp.route("/_update_transactions_display", methods=("POST",))
|
|
389
|
-
@login_required
|
|
390
|
-
def update_transactions_display():
|
|
391
|
-
# Separate the arguments of the POST method
|
|
392
|
-
post_args = request.get_json()
|
|
393
|
-
card_ids = map(int, post_args["card_ids"])
|
|
394
|
-
sort_order = "ASC" if post_args["sort_order"] == "asc" else "DESC"
|
|
395
|
-
# Filter selected transactions from the database
|
|
396
|
-
transactions = CreditTransactionHandler.get_transactions(
|
|
397
|
-
card_ids=card_ids,
|
|
398
|
-
sort_order=sort_order,
|
|
399
|
-
)
|
|
400
|
-
return render_template(
|
|
401
|
-
"credit/transactions_table/transactions.html",
|
|
402
|
-
sort_order=sort_order,
|
|
403
|
-
transactions=islice(transactions, 100),
|
|
404
|
-
full_view=True,
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
408
428
|
@bp.route(
|
|
409
429
|
"/add_transaction",
|
|
410
430
|
defaults={"card_id": None, "statement_id": None},
|
|
@@ -497,59 +517,17 @@ def add_subtransaction_fields():
|
|
|
497
517
|
@db_transaction
|
|
498
518
|
def delete_transaction(transaction_id):
|
|
499
519
|
CreditTransactionHandler.delete_entry(transaction_id)
|
|
500
|
-
if statement_id := session.pop("statement_focus", None):
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
520
|
+
if (statement_id := session.pop("statement_focus", None)) is not None:
|
|
521
|
+
statement = CreditStatementHandler.get_entry(statement_id)
|
|
522
|
+
# Delete the statement if it has no more transactions
|
|
523
|
+
if statement.balance is not None:
|
|
524
|
+
return redirect(
|
|
525
|
+
url_for("credit.load_statement_details", statement_id=statement.id)
|
|
526
|
+
)
|
|
527
|
+
CreditStatementHandler.delete_entry(statement.id)
|
|
504
528
|
return redirect(url_for("credit.load_transactions"))
|
|
505
529
|
|
|
506
530
|
|
|
507
|
-
@bp.route("/tags")
|
|
508
|
-
@login_required
|
|
509
|
-
def load_tags():
|
|
510
|
-
# Get the tag hierarchy from the database
|
|
511
|
-
hierarchy = CreditTagHandler.get_hierarchy()
|
|
512
|
-
return render_template("credit/tags_page.html", tags_hierarchy=hierarchy)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
@bp.route("/_add_tag", methods=("POST",))
|
|
516
|
-
@login_required
|
|
517
|
-
@db_transaction
|
|
518
|
-
def add_tag():
|
|
519
|
-
# Get the new tag (and potentially parent category) from the AJAX request
|
|
520
|
-
post_args = request.get_json()
|
|
521
|
-
tag_name = post_args["tag_name"]
|
|
522
|
-
parent_name = post_args.get("parent")
|
|
523
|
-
# Check that the tag name does not already exist
|
|
524
|
-
if CreditTagHandler.get_tags(tag_names=(tag_name,)):
|
|
525
|
-
raise ValueError("The given tag name already exists. Tag names must be unique.")
|
|
526
|
-
if parent_name:
|
|
527
|
-
parent_id = CreditTagHandler.find_tag(parent_name).id
|
|
528
|
-
else:
|
|
529
|
-
parent_id = None
|
|
530
|
-
tag = CreditTagHandler.add_entry(
|
|
531
|
-
parent_id=parent_id,
|
|
532
|
-
user_id=g.user.id,
|
|
533
|
-
tag_name=tag_name,
|
|
534
|
-
)
|
|
535
|
-
return render_template(
|
|
536
|
-
"credit/tag_tree/subtag_tree.html", tag=tag, tags_hierarchy={}
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
@bp.route("/_delete_tag", methods=("POST",))
|
|
541
|
-
@login_required
|
|
542
|
-
@db_transaction
|
|
543
|
-
def delete_tag():
|
|
544
|
-
# Get the tag to be deleted from the AJAX request
|
|
545
|
-
post_args = request.get_json()
|
|
546
|
-
tag_name = post_args["tag_name"]
|
|
547
|
-
tag = CreditTagHandler.find_tag(tag_name)
|
|
548
|
-
# Remove the tag from the database
|
|
549
|
-
CreditTagHandler.delete_entry(tag.id)
|
|
550
|
-
return ""
|
|
551
|
-
|
|
552
|
-
|
|
553
531
|
@bp.route("/_suggest_transaction_autocomplete", methods=("POST",))
|
|
554
532
|
@login_required
|
|
555
533
|
def suggest_transaction_autocomplete():
|
monopyly/credit/statements.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Tools for interacting with the credit statements in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from authanor.database.handler import DatabaseViewHandler
|
|
6
5
|
from dateutil.relativedelta import relativedelta
|
|
6
|
+
from dry_foundation.database.handler import DatabaseViewHandler
|
|
7
7
|
|
|
8
8
|
from ..common.utils import get_next_occurrence_of_day
|
|
9
9
|
from ..database.models import (
|
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
Tools for interacting with the credit transactions in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation.database.handler import DatabaseViewHandler
|
|
6
6
|
|
|
7
7
|
from ...common.forms.utils import execute_on_form_validation
|
|
8
8
|
from ...common.transactions import TransactionHandler, TransactionTagHandler
|
|
9
9
|
from ...database.models import (
|
|
10
|
-
Bank,
|
|
11
|
-
CreditAccount,
|
|
12
10
|
CreditCard,
|
|
13
|
-
CreditStatementView,
|
|
14
11
|
CreditSubtransaction,
|
|
15
12
|
CreditTransaction,
|
|
16
13
|
CreditTransactionView,
|
|
@@ -38,7 +35,13 @@ class CreditTransactionHandler(
|
|
|
38
35
|
@classmethod
|
|
39
36
|
@DatabaseViewHandler.view_query
|
|
40
37
|
def get_transactions(
|
|
41
|
-
cls,
|
|
38
|
+
cls,
|
|
39
|
+
statement_ids=None,
|
|
40
|
+
card_ids=None,
|
|
41
|
+
active=None,
|
|
42
|
+
sort_order="DESC",
|
|
43
|
+
offset=None,
|
|
44
|
+
limit=None,
|
|
42
45
|
):
|
|
43
46
|
"""
|
|
44
47
|
Get credit card transactions from the database.
|
|
@@ -66,6 +69,13 @@ class CreditTransactionHandler(
|
|
|
66
69
|
An indicator of whether the transactions should be ordered
|
|
67
70
|
in ascending (oldest at top) or descending (newest at top)
|
|
68
71
|
order.
|
|
72
|
+
offset : int, optional
|
|
73
|
+
The number of transactions by which to offset the results
|
|
74
|
+
returned by this query. The default is `None`, in which case
|
|
75
|
+
no offset will be added.
|
|
76
|
+
limit : int, optional
|
|
77
|
+
A limit on the number of transactions retrieved from the
|
|
78
|
+
database.
|
|
69
79
|
|
|
70
80
|
Returns
|
|
71
81
|
-------
|
|
@@ -77,7 +87,7 @@ class CreditTransactionHandler(
|
|
|
77
87
|
criteria.add_match_filter(CreditCard, "id", card_ids)
|
|
78
88
|
criteria.add_match_filter(CreditCard, "active", active)
|
|
79
89
|
transactions = super()._get_transactions(
|
|
80
|
-
criteria=criteria, sort_order=sort_order
|
|
90
|
+
criteria=criteria, sort_order=sort_order, offset=offset, limit=limit
|
|
81
91
|
)
|
|
82
92
|
return transactions
|
|
83
93
|
|
|
@@ -196,7 +206,7 @@ class CreditTagHandler(TransactionTagHandler, model=TransactionTagHandler.model)
|
|
|
196
206
|
return tags
|
|
197
207
|
|
|
198
208
|
@classmethod
|
|
199
|
-
def _filter_entries(cls, query, criteria):
|
|
209
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
200
210
|
# Add a join to enable filtering by transaction ID or subtransaction ID
|
|
201
211
|
join_transaction = CreditTransactionView in criteria.discriminators
|
|
202
212
|
join_subtransaction = (
|
|
@@ -206,7 +216,7 @@ class CreditTagHandler(TransactionTagHandler, model=TransactionTagHandler.model)
|
|
|
206
216
|
query = query.join(credit_tag_link_table).join(CreditSubtransaction)
|
|
207
217
|
if join_transaction:
|
|
208
218
|
query = query.join(CreditTransactionView)
|
|
209
|
-
return super()._filter_entries(query, criteria)
|
|
219
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
210
220
|
|
|
211
221
|
|
|
212
222
|
@execute_on_form_validation
|
|
@@ -5,7 +5,6 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from flask import abort, current_app
|
|
8
|
-
from werkzeug.utils import secure_filename
|
|
9
8
|
|
|
10
9
|
from ....common.utils import parse_date
|
|
11
10
|
from .data import ActivityLoadingError, TransactionActivities, TransactionActivityLoader
|