monopyly 1.4.7__py3-none-any.whl → 1.5.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 +15 -0
- monopyly/README.md +1 -1
- monopyly/__init__.py +3 -3
- monopyly/_version.py +2 -2
- monopyly/auth/actions.py +7 -2
- monopyly/banking/actions.py +51 -10
- monopyly/banking/routes.py +2 -1
- monopyly/cli/apps.py +26 -24
- monopyly/cli/{run.py → launch.py} +19 -8
- monopyly/common/forms/_forms.py +56 -2
- monopyly/common/transactions.py +162 -0
- monopyly/credit/actions.py +29 -0
- monopyly/credit/forms.py +25 -0
- monopyly/credit/routes.py +97 -7
- monopyly/credit/transactions/_transactions.py +15 -0
- monopyly/credit/transactions/activity/__init__.py +3 -0
- monopyly/credit/transactions/activity/data.py +161 -0
- monopyly/credit/transactions/activity/parser.py +274 -0
- monopyly/credit/transactions/activity/reconciliation.py +456 -0
- monopyly/database/models.py +6 -0
- monopyly/static/css/style.css +1146 -263
- monopyly/static/img/icons/statement-pair.png +0 -0
- monopyly/static/img/icons/statement-thick.png +0 -0
- monopyly/static/img/icons/statement.png +0 -0
- monopyly/static/js/bind-tag-actions.js +1 -1
- monopyly/static/js/create-balance-chart.js +1 -1
- monopyly/static/js/create-category-chart.js +27 -0
- monopyly/static/js/define-filter.js +1 -1
- monopyly/static/js/expand-transaction.js +10 -0
- monopyly/static/js/highlight-discrepant-transactions.js +124 -0
- monopyly/static/js/modules/expand-transaction.js +12 -3
- monopyly/static/js/modules/form-suggestions.js +60 -0
- monopyly/static/js/modules/manage-overlays.js +1 -3
- monopyly/static/js/show-credit-activity-loader.js +29 -0
- monopyly/static/js/toggle-navigation.js +35 -0
- monopyly/static/js/update-card-status.js +1 -1
- monopyly/static/js/use-suggested-amount.js +11 -0
- monopyly/static/js/use-suggested-merchant.js +11 -0
- monopyly/templates/banking/account_page.html +3 -1
- monopyly/templates/banking/account_summaries.html +1 -1
- monopyly/templates/banking/accounts_page.html +11 -15
- monopyly/templates/banking/transactions_table/expanded_row_content.html +18 -20
- monopyly/templates/common/transaction_form/subtransaction_subform.html +10 -1
- monopyly/templates/common/transactions_table/linked_bank_transaction.html +1 -1
- monopyly/templates/common/transactions_table/linked_credit_transaction.html +1 -1
- monopyly/templates/common/transactions_table/transaction_condensed.html +2 -2
- monopyly/templates/common/transactions_table/transaction_expanded.html +3 -3
- monopyly/templates/common/transactions_table/transactions.html +1 -1
- monopyly/templates/core/credits.html +10 -8
- monopyly/templates/core/index.html +10 -0
- monopyly/templates/core/profile.html +1 -1
- monopyly/templates/core/story.html +42 -27
- monopyly/templates/credit/statement_page.html +33 -0
- monopyly/templates/credit/statement_reconciliation/discrepant_records.html +25 -0
- monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +23 -0
- monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +86 -0
- monopyly/templates/credit/statement_reconciliation/summary.html +45 -0
- monopyly/templates/credit/statement_reconciliation/unrecorded_activities.html +24 -0
- monopyly/templates/credit/statement_summary.html +2 -2
- monopyly/templates/credit/statements.html +1 -1
- monopyly/templates/credit/transaction_form/transaction_form.html +9 -1
- monopyly/templates/credit/transaction_form/transaction_form_page.html +2 -0
- monopyly/templates/credit/transaction_form/transaction_form_page_update.html +9 -0
- monopyly/templates/credit/transaction_submission_page.html +8 -0
- monopyly/templates/credit/transactions_table/expanded_row_content.html +18 -12
- monopyly/templates/layout.html +46 -29
- {monopyly-1.4.7.dist-info → monopyly-1.5.0.dist-info}/METADATA +3 -2
- {monopyly-1.4.7.dist-info → monopyly-1.5.0.dist-info}/RECORD +72 -56
- monopyly-1.5.0.dist-info/entry_points.txt +2 -0
- monopyly/static/img/icons/statement-pair.svg +0 -281
- monopyly/static/img/icons/statement.svg +0 -294
- monopyly-1.4.7.dist-info/entry_points.txt +0 -2
- {monopyly-1.4.7.dist-info → monopyly-1.5.0.dist-info}/WHEEL +0 -0
- {monopyly-1.4.7.dist-info → monopyly-1.5.0.dist-info}/licenses/COPYING +0 -0
- {monopyly-1.4.7.dist-info → monopyly-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{% extends "layout.html" %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
+
|
|
4
5
|
{% if session["show_homepage_block"] %}
|
|
5
6
|
<div id="homepage-block">
|
|
6
7
|
<div class="buttons">
|
|
@@ -147,7 +148,16 @@
|
|
|
147
148
|
</div>
|
|
148
149
|
|
|
149
150
|
</div>
|
|
151
|
+
{% else %}
|
|
152
|
+
<div id="homepage-suggestion">
|
|
153
|
+
<p class="login">
|
|
154
|
+
<a href="{{ url_for('auth.login') }}">Login</a> to get started.
|
|
155
|
+
</p>
|
|
156
|
+
<p class="register">
|
|
157
|
+
Not yet registered? <a href="{{ url_for('auth.register') }}">Create an account</a> to begin using the app.
|
|
158
|
+
</p>
|
|
150
159
|
{% endif %}
|
|
160
|
+
|
|
151
161
|
{% endblock %}
|
|
152
162
|
|
|
153
163
|
{% block javascript %}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
<div class="bank-list box-table">
|
|
42
42
|
{% for bank in banks %}
|
|
43
43
|
|
|
44
|
-
<div class="bank-block box-row update-db-widget" data-bank-id="{{ bank.id }}">
|
|
44
|
+
<div class="bank-block box-row action update-db-widget" data-bank-id="{{ bank.id }}">
|
|
45
45
|
|
|
46
46
|
<div class="widget-text">
|
|
47
47
|
<div class="bank widget-display">{{ bank.bank_name }}</div>
|
|
@@ -10,36 +10,51 @@
|
|
|
10
10
|
|
|
11
11
|
{% block content %}
|
|
12
12
|
<div id="story" class="about">
|
|
13
|
-
<p>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
<p>
|
|
14
|
+
It started with Microsoft Excel.
|
|
15
|
+
</p>
|
|
16
|
+
<p>
|
|
17
|
+
When I got my first bank account, my dad taught me how to keep track of all my finances in an Excel workbook.
|
|
18
|
+
It worked for a while, and as I acquired a handful of credit cards, investments, and other types of bank accounts, I kept adding sheets to that file.
|
|
19
|
+
Of course, since I was tracking all that data, I wanted to wring as much information out of it as I possibly could—and so I started working rudimentary analytics into the spreadsheet.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p>
|
|
23
|
+
This all kept me satisfied until about halfway through college when I began learning Python.
|
|
24
|
+
Excel was getting cumbersome, and I wanted to be able to sort and tag types of transactions, link credit card payments with bank withdrawals, and create spending reports.
|
|
25
|
+
Seeing the potential ways I could use a standalone programming language to up my game, I realized that what I really needed was a database.
|
|
26
|
+
As I transitioned to graduate school, I also transitioned to tracking my finances in a MySQL database, accessed with Python through a set of Jupyter notebooks.
|
|
27
|
+
That approach gave me so much more power and flexibilty, but it still felt a little clunky.
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<p>
|
|
31
|
+
My ideal situation?
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p>
|
|
35
|
+
I ultimately wanted a system that keep track of everything—credit card transactions, bank accounts, investments, retirement accounts, all that $—in one place.
|
|
36
|
+
Obviously there are mega-apps that already do all this, but I never wanted to go that route.
|
|
37
|
+
I'd always managed my finances independently, and had zero interest in ceding that control.
|
|
38
|
+
For one, I wanted to maintain some privacy regarding my financial data, because who knows how those apps are using that information.
|
|
39
|
+
Two, I didn't want <em>all</em> the bells and whistles (and advertisements) that those mega-apps would force upon me, but I also wanted something I could customize when I didn't like a feature... or when I wanted more.
|
|
40
|
+
And three, most importantly, it seemed like a fun challenge that would keep me deeply engaged in managing my own financial picture.
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<p>
|
|
44
|
+
That's how this app came about.
|
|
45
|
+
I started by trying to just create a GUI for my Python database interface, but that didn't offer the graphic flexibility of a web app.
|
|
46
|
+
Why fight to build a nice Tkinter GUI if I'd eventually get sick of that too?
|
|
47
|
+
(Tkinter is also a bit of a pain to code, and a bigger pain to test, and <em>that</em> certainly wouldn't do.)
|
|
48
|
+
Anyway, I'd always wanted to create an app, and I figured this was as good a chance as any to get started.
|
|
49
|
+
Win-win.
|
|
17
50
|
</p>
|
|
18
51
|
|
|
52
|
+
<p>
|
|
53
|
+
If you're reading this, I suppose it means you've stumbled across the program out of interest (or maybe you're just curious what the hell this nutjob is wasting his time on).
|
|
54
|
+
If the former, cool!
|
|
55
|
+
Feel free to look around or take it for a spin.
|
|
56
|
+
I intend to always keep the basic concept open source, so play around as much as you'd like!
|
|
19
57
|
</p>
|
|
20
|
-
This all kept me satisfied until about halfway through college when I began learning Python.
|
|
21
|
-
Excel was getting cumbersome, and I wanted to be able to sort and tag types of transactions, link credit card payments with bank withdrawals, and create spending reports.
|
|
22
|
-
Seeing the potential ways I could use a standalone programming language to up my game, I realized that what I really needed was a database.
|
|
23
|
-
As I transitioned to graduate school, I also transitioned to tracking my finances in a MySQL database, accessed with Python through a set of Jupyter notebooks.
|
|
24
|
-
That approach gave me so much more power and flexibilty, but it still felt a little clunky.</p>
|
|
25
|
-
|
|
26
|
-
<p>My ideal situation?</p>
|
|
27
|
-
|
|
28
|
-
<p>I ultimately wanted a system that keep track of everything—credit card transactions, bank accounts, investments, retirement accounts, all that $—in one place.
|
|
29
|
-
Obviously there are mega-apps that already do all this, but I never wanted to go that route.
|
|
30
|
-
I'd always managed my finances independently, and had zero interest in ceding that control.
|
|
31
|
-
For one, I wanted to maintain some privacy regarding my financial data, because who knows how those apps are using that information.
|
|
32
|
-
Two, I didn't want <em>all</em> the bells and whistles (and advertisements) that those mega-apps would force upon me, but I also wanted something I could customize when I didn't like a feature... or when I wanted more.
|
|
33
|
-
And three, most importantly, it seemed like a fun challenge that would keep me deeply engaged in managing my own financial picture.
|
|
34
|
-
|
|
35
|
-
<p>That's how this app came about.
|
|
36
|
-
I started by trying to just create a GUI for my Python database interface, but that didn't offer the graphic flexibility of a web app.
|
|
37
|
-
Why fight to build a nice Tkinter GUI if I'd eventually get sick of that too?
|
|
38
|
-
(Tkinter is also a bit of a pain to code, and a bigger pain to test, and <em>that</em> certainly wouldn't do.)
|
|
39
|
-
Anyway, I'd always wanted to create an app, and I figured this was as good a chance as any to get started.
|
|
40
|
-
Win-win.</p>
|
|
41
|
-
|
|
42
|
-
<p>If you're reading this, I suppose it means you've stumbled across the program out of interest (or maybe you're just curious what the hell this nutjob is wasting his time on). If the former, cool! Feel free to look around or take it for a spin. I intend to always keep the basic concept open source, so play around as much as you'd like!</p>
|
|
43
58
|
|
|
44
59
|
<p class="signature">-Mitch</p>
|
|
45
60
|
</div>
|
|
@@ -28,8 +28,13 @@
|
|
|
28
28
|
{% endwith %}
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
|
+
<div id="category-chart-container">
|
|
32
|
+
<div id="category-chart" class="ct-chart ct-octave"></div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
31
35
|
</div>
|
|
32
36
|
|
|
37
|
+
|
|
33
38
|
</div>
|
|
34
39
|
|
|
35
40
|
{% endblock %}
|
|
@@ -44,6 +49,11 @@
|
|
|
44
49
|
<img class="icon" src="{{ url_for('static', filename='img/icons/plus.png') }}" />
|
|
45
50
|
</a>
|
|
46
51
|
</div>
|
|
52
|
+
<div class="button">
|
|
53
|
+
<div class="action">
|
|
54
|
+
<img id="reconciliation-button" class="icon" src="{{ url_for('static', filename='img/icons/statement-pair.png') }}" />
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
47
57
|
|
|
48
58
|
</div>
|
|
49
59
|
|
|
@@ -53,11 +63,32 @@
|
|
|
53
63
|
{% block javascript %}
|
|
54
64
|
|
|
55
65
|
<script>
|
|
66
|
+
const CREDIT_ACTIVITY_RECONCILIATION_ENDPOINT = "{{ url_for('credit.reconcile_activity', statement_id=statement.id) }}";
|
|
56
67
|
const UPDATE_STATEMENT_DUE_DATE_ENDPOINT = "{{ url_for('credit.update_statement_due_date', statement_id=statement.id) }}";
|
|
57
68
|
const MAKE_PAYMENT_ENDPOINT = "{{ url_for('credit.pay_credit_card', card_id=statement.card_id, statement_id=statement.id) }}";
|
|
58
69
|
const EXPAND_TRANSACTION_ENDPOINT = "{{ url_for('credit.expand_transaction') }}";
|
|
59
70
|
const LINKED_TRANSACTION_ENDPOINT = "{{ url_for('credit.show_linked_transaction') }}";
|
|
60
71
|
</script>
|
|
72
|
+
<script>
|
|
73
|
+
const CATEGORY_CHART_DATA = {
|
|
74
|
+
labels: [
|
|
75
|
+
{% for label in chart_data['labels'] %}
|
|
76
|
+
{% if label == '' %}
|
|
77
|
+
"Other",
|
|
78
|
+
{% else %}
|
|
79
|
+
"{{ label }}",
|
|
80
|
+
{% endif %}
|
|
81
|
+
{% endfor %}
|
|
82
|
+
],
|
|
83
|
+
series: [
|
|
84
|
+
{% for subtotal in chart_data['subtotals'] %}
|
|
85
|
+
{{ subtotal }},
|
|
86
|
+
{% endfor %}
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
</script>
|
|
90
|
+
<script type="module" src="{{ url_for('static', filename='js/show-credit-activity-loader.js') }}">
|
|
91
|
+
</script>
|
|
61
92
|
<script type="module" src="{{ url_for('static', filename='js/update-statement-parameters.js') }}">
|
|
62
93
|
</script>
|
|
63
94
|
<script type="module" src="{{ url_for('static', filename='js/make-payment.js') }}">
|
|
@@ -66,6 +97,8 @@
|
|
|
66
97
|
</script>
|
|
67
98
|
<script type="module" src="{{ url_for('static', filename='js/show-linked-transaction.js') }}">
|
|
68
99
|
</script>
|
|
100
|
+
<script type="module" src="{{ url_for('static', filename='js/create-category-chart.js') }}">
|
|
101
|
+
</script>
|
|
69
102
|
|
|
70
103
|
{% endblock %}
|
|
71
104
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<div class="discrepant-activities transactions-table">
|
|
2
|
+
|
|
3
|
+
{% for transaction, activity in discrepant_records.items() %}
|
|
4
|
+
|
|
5
|
+
<div class="reconciliation-activity discrepant-activity" data-transaction-id="{{ transaction.id }}">
|
|
6
|
+
|
|
7
|
+
<div class="activity-info">
|
|
8
|
+
<div class="date">{{ activity.transaction_date }}</div>
|
|
9
|
+
<div class="text">{{ activity.description }}</div>
|
|
10
|
+
<div class="amount">
|
|
11
|
+
<span class="dollar-sign">$</span>
|
|
12
|
+
{{ activity.total|currency }}
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<a class="button" href="{{ url_for('credit.update_transaction', transaction_id=transaction.id, transaction_date=activity.transaction_date, total=activity.total) }}">
|
|
17
|
+
<img class="icon" src="{{ url_for('static', filename='img/icons/edit.png') }}" />
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
{% endfor %}
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<div class="overlay">
|
|
2
|
+
<div id="statement-reconciliation" class="modal">
|
|
3
|
+
|
|
4
|
+
<div class="modal-box">
|
|
5
|
+
|
|
6
|
+
<div class="buttons">
|
|
7
|
+
<img class="close button" src="/static/img/icons/x-thick.png">
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<p class="instructions">
|
|
11
|
+
Load a CSV file containing credit activity for comparison against this statement. The CSV file must be located in the <code>Downloads</code> directory on the machine running the Monopyly app.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<form action="{{ url_for('credit.load_statement_reconciliation_details', statement_id=statement_id) }}" method="post" enctype="multipart/form-data">
|
|
15
|
+
<label for="activity-file-upload">Activity Filename</label>
|
|
16
|
+
<input id="activity-file-upload" type="file" name="activity-file" accept="text/csv" />
|
|
17
|
+
<input class="button" type="submit" value="Reconcile Activity" />
|
|
18
|
+
</form>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{% extends 'layout.html' %}
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
{% block header %}
|
|
5
|
+
|
|
6
|
+
<h1>
|
|
7
|
+
{% block title %}
|
|
8
|
+
Statement Reconciliation
|
|
9
|
+
{% endblock %}
|
|
10
|
+
</h1>
|
|
11
|
+
|
|
12
|
+
{% endblock %}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
{% block content %}
|
|
16
|
+
{% set highlighted_transactions = statement_transactions|selectattr("highlight")|first %}
|
|
17
|
+
|
|
18
|
+
<div id="credit-statement-reconciliation-details" class="details">
|
|
19
|
+
|
|
20
|
+
<div id="credit-statement-reconciliation-info" class="primary-info">
|
|
21
|
+
|
|
22
|
+
<div id="statement-reconciliation-summary-container" class="summary-container">
|
|
23
|
+
{% include 'credit/statement_reconciliation/summary.html' %}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div id="statement-transactions-container" class="transactions-container">
|
|
27
|
+
{% if highlighted_transactions %}
|
|
28
|
+
<p class="note">
|
|
29
|
+
<span class="highlight">Highlighted</span> transactions indicate that the transaction was not identified in the reported activity file.
|
|
30
|
+
</p>
|
|
31
|
+
{% endif %}
|
|
32
|
+
|
|
33
|
+
{% with transactions = statement_transactions, full_view = False %}
|
|
34
|
+
{% include 'credit/transactions_table/transactions.html' %}
|
|
35
|
+
{% endwith %}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{% endblock %}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
{% block right_sidebar %}
|
|
46
|
+
|
|
47
|
+
<div class="sidebar-menu">
|
|
48
|
+
|
|
49
|
+
<div class="button">
|
|
50
|
+
<a class="action" href="{{ url_for('credit.add_transaction', card_id=statement.card_id, statement_id=statement.id) }}">
|
|
51
|
+
<img class="icon" src="{{ url_for('static', filename='img/icons/plus.png') }}" />
|
|
52
|
+
</a>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="button">
|
|
55
|
+
<a class="action" href="{{ url_for('credit.load_statement_details', statement_id=statement.id) }}">
|
|
56
|
+
<img class="icon" src="{{ url_for('static', filename='img/icons/statement.png') }}" />
|
|
57
|
+
</a>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="button">
|
|
60
|
+
<div class="action">
|
|
61
|
+
<img id="reconciliation-button" class="icon" src="{{ url_for('static', filename='img/icons/statement-pair.png') }}" />
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{% endblock %}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
{% block javascript %}
|
|
71
|
+
|
|
72
|
+
<script>
|
|
73
|
+
const CREDIT_ACTIVITY_RECONCILIATION_ENDPOINT = "{{ url_for('credit.reconcile_activity', statement_id=statement.id) }}";
|
|
74
|
+
const EXPAND_TRANSACTION_ENDPOINT = "{{ url_for('credit.expand_transaction') }}";
|
|
75
|
+
const LINKED_TRANSACTION_ENDPOINT = "{{ url_for('credit.show_linked_transaction') }}";
|
|
76
|
+
</script>
|
|
77
|
+
<script type="module" src="{{ url_for('static', filename='js/show-credit-activity-loader.js') }}">
|
|
78
|
+
</script>
|
|
79
|
+
<script type="module" src="{{ url_for('static', filename='js/highlight-discrepant-transactions.js') }}">
|
|
80
|
+
</script>
|
|
81
|
+
<script type="module" src="{{ url_for('static', filename='js/expand-transaction.js') }}">
|
|
82
|
+
</script>
|
|
83
|
+
<script type="module" src="{{ url_for('static', filename='js/show-linked-transaction.js') }}">
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
{% endblock %}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<div id="statement-reconciliation-summary" class="summary-box">
|
|
2
|
+
|
|
3
|
+
{% if not (highlighted_transactions or discrepant_records or unrecorded_activities) %}
|
|
4
|
+
|
|
5
|
+
<img class="reconciliation-indicator" src="{{ url_for('static', filename='img/icons/checkmark.png') }}" />
|
|
6
|
+
|
|
7
|
+
{% else %}
|
|
8
|
+
|
|
9
|
+
{% if highlighted_transactions or discrepant_records or unrecorded_activities %}
|
|
10
|
+
<div id="statement-discrepancies-container">
|
|
11
|
+
<h2>Discrepancies</h3>
|
|
12
|
+
|
|
13
|
+
<div class="balance">
|
|
14
|
+
<div class="dollar-sign">$</div>
|
|
15
|
+
<div>{{ discrepant_amount|currency }}</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
{% if discrepant_records %}
|
|
19
|
+
<div class="discrepancy-category">
|
|
20
|
+
<h3>Discrepant Records</h3>
|
|
21
|
+
<p class="note">
|
|
22
|
+
{% set plural = discrepant_records|length != 1 %}
|
|
23
|
+
The following {% if plural %}activities were{% else %}activity was{% endif %} reported in the credit activity file and matched in the database, but the charge amount{%if plural %}s do{% else %} does{% endif %} not match:
|
|
24
|
+
</p>
|
|
25
|
+
{% include 'credit/statement_reconciliation/discrepant_records.html' %}
|
|
26
|
+
</div>
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
{% if unrecorded_activities %}
|
|
30
|
+
<div class="discrepancy-category">
|
|
31
|
+
<h3>Unrecorded Activity</h3>
|
|
32
|
+
<p class="note">
|
|
33
|
+
{% set plural = unrecorded_activities|length != 1 %}
|
|
34
|
+
The following {% if plural %}activities were{% else %}activity was{% endif %} reported in the credit activity file, but {% if plural %}do{% else %}does{% endif %} not exist in the Monopyly database:
|
|
35
|
+
</p>
|
|
36
|
+
{% include 'credit/statement_reconciliation/unrecorded_activities.html' %}
|
|
37
|
+
</div>
|
|
38
|
+
{% endif %}
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
{% endif %}
|
|
44
|
+
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="unrecorded-activities transactions-table">
|
|
2
|
+
|
|
3
|
+
{% for activity in unrecorded_activities %}
|
|
4
|
+
|
|
5
|
+
<div class="reconciliation-activity unrecorded-activity">
|
|
6
|
+
|
|
7
|
+
<div class=" activity-info">
|
|
8
|
+
<div class="date">{{ activity.transaction_date }}</div>
|
|
9
|
+
<div class="text">{{ activity.description }}</div>
|
|
10
|
+
<div class="amount">
|
|
11
|
+
<span class="dollar-sign">$</span>
|
|
12
|
+
{{ activity.total|currency }}
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<a class="button" href="{{ url_for('credit.add_transaction', card_id=statement.card.id, statement_id=statement.id, transaction_date=activity.transaction_date, total=activity.total, description=activity.description) }}">
|
|
17
|
+
<img class="icon" src="{{ url_for('static', filename='img/icons/plus-thick.png') }}" />
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
{% endfor %}
|
|
23
|
+
|
|
24
|
+
</div>
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
|
|
53
53
|
<div class="form-line">
|
|
54
54
|
<div class="dollar-sign">$</div>
|
|
55
|
-
<input id="pay-amount" type="
|
|
56
|
-
<input id="pay-date" type="
|
|
55
|
+
<input id="pay-amount" type="number" name="pay_amount" value="{{ statement.balance }}" />
|
|
56
|
+
<input id="pay-date" type="date" name="pay_date" value="{{ date_today }}" />
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
59
|
<div class="form-line">
|
|
@@ -23,9 +23,17 @@
|
|
|
23
23
|
{{ form.transaction_date }}
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
|
-
<div class="form-field merchant-field autocomplete">
|
|
26
|
+
<div class="form-field merchant-field autocomplete suggestable">
|
|
27
27
|
{{ form.merchant.label }}
|
|
28
28
|
{{ form.merchant }}
|
|
29
|
+
|
|
30
|
+
{% if form.suggestions.get('merchant') %}
|
|
31
|
+
<div class="form-suggestion merchant-suggestion">
|
|
32
|
+
<span>Suggested: </span>
|
|
33
|
+
<span class="suggested-value merchant">{{ form.suggestions['merchant'] }}</span>
|
|
34
|
+
</div>
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
29
37
|
</div>
|
|
30
38
|
|
|
31
39
|
</div>
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
</script>
|
|
14
14
|
<script type="module" src="{{ url_for('static', filename='js/infer-statement.js') }}">
|
|
15
15
|
</script>
|
|
16
|
+
<script type="module" src="{{ url_for('static', filename='js/use-suggested-merchant.js') }}">
|
|
17
|
+
</script>
|
|
16
18
|
{{ super() }}
|
|
17
19
|
|
|
18
20
|
{% endblock %}
|
|
@@ -81,6 +81,14 @@
|
|
|
81
81
|
<a href="{{ url_for('credit.add_transaction', card_id=transaction.statement.card_id, statement_id=transaction.statement_id) }}">
|
|
82
82
|
Create a new transaction on this statement
|
|
83
83
|
</a>
|
|
84
|
+
{% with reconciliation_info = session.get('reconciliation_info', None) %}
|
|
85
|
+
{% if reconciliation_info %}
|
|
86
|
+
<br>
|
|
87
|
+
<a href="{{ url_for('credit.load_statement_reconciliation_details', statement_id=reconciliation_info[0]) }}">
|
|
88
|
+
Return to the in-progress statement reconciliation
|
|
89
|
+
</a>
|
|
90
|
+
{% endif %}
|
|
91
|
+
{% endwith %}
|
|
84
92
|
{% endif %}
|
|
85
93
|
|
|
86
94
|
</div>
|
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
<div class="setting">
|
|
1
|
+
<div class="setting column">
|
|
2
2
|
|
|
3
3
|
<div class="date column">
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
|
|
5
|
+
<div class="stacked-date">
|
|
6
|
+
<div class="month">
|
|
7
|
+
{{ transaction.transaction_date.strftime('%b')|upper }}
|
|
8
|
+
</div>
|
|
9
|
+
<div class="day">
|
|
10
|
+
{{ transaction.transaction_date.strftime('%d')|upper }}
|
|
11
|
+
</div>
|
|
12
|
+
<div class="year">
|
|
13
|
+
{{ transaction.transaction_date.year }}
|
|
14
|
+
</div>
|
|
12
15
|
</div>
|
|
13
|
-
<div class="full">
|
|
16
|
+
<div class="full-date">
|
|
14
17
|
{{ transaction.transaction_date }}
|
|
15
18
|
</div>
|
|
19
|
+
|
|
16
20
|
</div>
|
|
17
21
|
|
|
18
|
-
<div class="description">
|
|
22
|
+
<div class="description column">
|
|
19
23
|
|
|
20
24
|
<div class="description-header">
|
|
21
25
|
<div class="merchant">
|
|
@@ -31,9 +35,11 @@
|
|
|
31
35
|
</div>
|
|
32
36
|
|
|
33
37
|
</div>
|
|
38
|
+
|
|
34
39
|
</div>
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
|
|
42
|
+
<div class="payment column">
|
|
37
43
|
|
|
38
44
|
{% if full_view %}
|
|
39
45
|
<div class="statement">
|
monopyly/templates/layout.html
CHANGED
|
@@ -7,34 +7,36 @@
|
|
|
7
7
|
{% if request.path != '/' %} – {% endif %}
|
|
8
8
|
Monopyly
|
|
9
9
|
</title>
|
|
10
|
-
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"
|
|
10
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
|
|
11
|
+
<!-- Ensure emulators also display the page properly -->
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
|
|
11
13
|
<!-- ****** faviconit.com favicons ****** -->
|
|
12
|
-
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon/favicon.ico') }}"
|
|
13
|
-
<link rel="icon" sizes="16x16 32x32 64x64" href="{{ url_for('static', filename='favicon/favicon.ico') }}"
|
|
14
|
-
<link rel="icon" type="image/png" sizes="196x196" href="{{ url_for('static', filename='favicon/favicon-192.png') }}"
|
|
15
|
-
<link rel="icon" type="image/png" sizes="160x160" href="{{ url_for('static', filename='favicon/favicon-160.png') }}"
|
|
16
|
-
<link rel="icon" type="image/png" sizes="96x96" href="{{ url_for('static', filename='favicon/favicon-96.png') }}"
|
|
17
|
-
<link rel="icon" type="image/png" sizes="64x64" href="{{ url_for('static', filename='favicon/favicon-64.png') }}"
|
|
18
|
-
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon/favicon-32.png') }}"
|
|
19
|
-
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon/favicon-16.png') }}"
|
|
20
|
-
<link rel="apple-touch-icon" href="{{ url_for('static', filename='favicon/favicon-57.png') }}"
|
|
21
|
-
<link rel="apple-touch-icon" sizes="114x114" href="{{ url_for('static', filename='favicon/favicon-114.png') }}"
|
|
22
|
-
<link rel="apple-touch-icon" sizes="72x72" href="{{ url_for('static', filename='favicon/favicon-72.png') }}"
|
|
23
|
-
<link rel="apple-touch-icon" sizes="144x144" href="{{ url_for('static', filename='favicon/favicon-144.png') }}"
|
|
24
|
-
<link rel="apple-touch-icon" sizes="60x60" href="{{ url_for('static', filename='favicon/favicon-60.png') }}"
|
|
25
|
-
<link rel="apple-touch-icon" sizes="120x120" href="{{ url_for('static', filename='favicon/favicon-120.png') }}"
|
|
26
|
-
<link rel="apple-touch-icon" sizes="76x76" href="{{ url_for('static', filename='favicon/favicon-76.png') }}"
|
|
27
|
-
<link rel="apple-touch-icon" sizes="152x152" href="{{ url_for('static', filename='favicon/favicon-152.png') }}"
|
|
28
|
-
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='favicon/favicon-180.png') }}"
|
|
29
|
-
<meta name="msapplication-TileColor" content="#FFFFFF"
|
|
30
|
-
<meta name="msapplication-TileImage" content="{{ url_for('static', filename='favicon/favicon-144.png') }}"
|
|
31
|
-
<meta name="msapplication-config" content="{{ url_for('static', filename='favicon/browserconfig.xml') }}"
|
|
14
|
+
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon/favicon.ico') }}" />
|
|
15
|
+
<link rel="icon" sizes="16x16 32x32 64x64" href="{{ url_for('static', filename='favicon/favicon.ico') }}" />
|
|
16
|
+
<link rel="icon" type="image/png" sizes="196x196" href="{{ url_for('static', filename='favicon/favicon-192.png') }}" />
|
|
17
|
+
<link rel="icon" type="image/png" sizes="160x160" href="{{ url_for('static', filename='favicon/favicon-160.png') }}" />
|
|
18
|
+
<link rel="icon" type="image/png" sizes="96x96" href="{{ url_for('static', filename='favicon/favicon-96.png') }}" />
|
|
19
|
+
<link rel="icon" type="image/png" sizes="64x64" href="{{ url_for('static', filename='favicon/favicon-64.png') }}" />
|
|
20
|
+
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon/favicon-32.png') }}" />
|
|
21
|
+
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon/favicon-16.png') }}" />
|
|
22
|
+
<link rel="apple-touch-icon" href="{{ url_for('static', filename='favicon/favicon-57.png') }}" />
|
|
23
|
+
<link rel="apple-touch-icon" sizes="114x114" href="{{ url_for('static', filename='favicon/favicon-114.png') }}" />
|
|
24
|
+
<link rel="apple-touch-icon" sizes="72x72" href="{{ url_for('static', filename='favicon/favicon-72.png') }}" />
|
|
25
|
+
<link rel="apple-touch-icon" sizes="144x144" href="{{ url_for('static', filename='favicon/favicon-144.png') }}" />
|
|
26
|
+
<link rel="apple-touch-icon" sizes="60x60" href="{{ url_for('static', filename='favicon/favicon-60.png') }}" />
|
|
27
|
+
<link rel="apple-touch-icon" sizes="120x120" href="{{ url_for('static', filename='favicon/favicon-120.png') }}" />
|
|
28
|
+
<link rel="apple-touch-icon" sizes="76x76" href="{{ url_for('static', filename='favicon/favicon-76.png') }}" />
|
|
29
|
+
<link rel="apple-touch-icon" sizes="152x152" href="{{ url_for('static', filename='favicon/favicon-152.png') }}" />
|
|
30
|
+
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='favicon/favicon-180.png') }}" />
|
|
31
|
+
<meta name="msapplication-TileColor" content="#FFFFFF" />
|
|
32
|
+
<meta name="msapplication-TileImage" content="{{ url_for('static', filename='favicon/favicon-144.png') }}" />
|
|
33
|
+
<meta name="msapplication-config" content="{{ url_for('static', filename='favicon/browserconfig.xml') }}" />
|
|
32
34
|
<!-- ****** faviconit.com favicons ****** -->
|
|
33
35
|
<!-- Use Google JQuery CDN -->
|
|
34
36
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
|
|
35
37
|
<script>window.jQuery || document.write('<script src="{{ url_for('static', filename='jquery-3.7.0.min.js') }}"><\/script>')</script>
|
|
36
38
|
<!-- Use Chartist via CDN -->
|
|
37
|
-
<link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css"
|
|
39
|
+
<link rel="stylesheet" href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css" />
|
|
38
40
|
<script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
|
|
39
41
|
</head>
|
|
40
42
|
<body>
|
|
@@ -43,22 +45,34 @@
|
|
|
43
45
|
<div class="container">
|
|
44
46
|
|
|
45
47
|
<a href="{{ url_for('core.index') }}">
|
|
46
|
-
<h1 class="monopyly-logo">
|
|
48
|
+
<h1 class="monopyly-logo">
|
|
49
|
+
Monopyly
|
|
50
|
+
{%- if config['DEBUG'] -%}
|
|
51
|
+
<span class="development-mode">.dev</span>
|
|
52
|
+
{%- endif -%}
|
|
53
|
+
</h1>
|
|
47
54
|
</a>
|
|
48
55
|
|
|
49
|
-
<nav id="
|
|
50
|
-
<
|
|
51
|
-
|
|
56
|
+
<nav id="header-menu">
|
|
57
|
+
<svg class="mobile-menu mobile-menu-rotate toggle-button" viewBox="0 0 100 100">
|
|
58
|
+
<path class="line top" d="m 30,33 h 40 c 3.722839,0 7.5,3.126468 7.5,8.578427 0,5.451959 -2.727029,8.421573 -7.5,8.421573 h -20" />
|
|
59
|
+
<path class="line middle" d="m 30,50 h 40" />
|
|
60
|
+
<path class="line bottom" d="m 70,67 h -40 c 0,0 -7.5,-0.802118 -7.5,-8.365747 0,-7.563629 7.5,-8.634253 7.5,-8.634253 h 20" />
|
|
61
|
+
</svg>
|
|
62
|
+
<ul class="menu-links"></strong>
|
|
52
63
|
<li><a href="{{ url_for('core.index') }}">Home</a></li>
|
|
53
64
|
<li><a href="{{ url_for('core.about') }}">About</a></li>
|
|
54
65
|
{% if g.user %}
|
|
55
|
-
<li
|
|
66
|
+
<li>
|
|
67
|
+
<a href="{{url_for('core.load_profile') }}" class="username">
|
|
68
|
+
{{ g.user.username }}
|
|
69
|
+
</a>
|
|
70
|
+
</li>
|
|
56
71
|
<li><a href="{{ url_for('auth.logout') }}">Log Out</a></li>
|
|
57
72
|
{% else %}
|
|
58
73
|
<li><a href="{{ url_for('auth.register') }}">Register</a></li>
|
|
59
74
|
<li><a href="{{ url_for('auth.login') }}">Log In</a></li>
|
|
60
75
|
{% endif %}
|
|
61
|
-
|
|
62
76
|
</ul></strong>
|
|
63
77
|
</nav>
|
|
64
78
|
|
|
@@ -98,7 +112,10 @@
|
|
|
98
112
|
</p>
|
|
99
113
|
</footer>
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
<script type="module" src="{{ url_for('static', filename='js/toggle-navigation.js') }}">
|
|
116
|
+
</script>
|
|
117
|
+
{% block javascript %}
|
|
118
|
+
{% endblock %}
|
|
102
119
|
|
|
103
120
|
</body>
|
|
104
121
|
</html>
|