06/02 Update functionalities
This commit is contained in:
+20
-1
@@ -1,10 +1,13 @@
|
|||||||
|
from datetime import date
|
||||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
|
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SelectField, TextAreaField, SubmitField
|
from wtforms import StringField, SelectField, TextAreaField, SubmitField
|
||||||
from wtforms.validators import DataRequired, Length, Optional
|
from wtforms.validators import DataRequired, Length, Optional
|
||||||
|
from sqlalchemy import func, extract
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.models.account import Account
|
from app.models.account import Account
|
||||||
|
from app.models.transaction import Transaction
|
||||||
from app.services.account_service import calc_balance
|
from app.services.account_service import calc_balance
|
||||||
|
|
||||||
accounts_bp = Blueprint('accounts', __name__, url_prefix='/accounts')
|
accounts_bp = Blueprint('accounts', __name__, url_prefix='/accounts')
|
||||||
@@ -63,11 +66,27 @@ def index():
|
|||||||
credit_accounts = [a for a in all_accounts if a.account_type == 'credit_card']
|
credit_accounts = [a for a in all_accounts if a.account_type == 'credit_card']
|
||||||
accounts = credit_accounts if tab == 'credit' else bank_accounts
|
accounts = credit_accounts if tab == 'credit' else bank_accounts
|
||||||
|
|
||||||
|
# Monthly charges per credit card (current month expenses)
|
||||||
|
today = date.today()
|
||||||
|
monthly_charges = {}
|
||||||
|
if credit_accounts:
|
||||||
|
rows = db.session.query(
|
||||||
|
Transaction.account_id,
|
||||||
|
func.sum(Transaction.amount)
|
||||||
|
).filter(
|
||||||
|
Transaction.transaction_type == 'expense',
|
||||||
|
extract('year', Transaction.date) == today.year,
|
||||||
|
extract('month', Transaction.date) == today.month,
|
||||||
|
Transaction.account_id.in_([a.id for a in credit_accounts])
|
||||||
|
).group_by(Transaction.account_id).all()
|
||||||
|
monthly_charges = {row[0]: float(row[1]) for row in rows}
|
||||||
|
|
||||||
return render_template('accounts/index.html',
|
return render_template('accounts/index.html',
|
||||||
accounts=accounts,
|
accounts=accounts,
|
||||||
tab=tab,
|
tab=tab,
|
||||||
bank_count=len(bank_accounts),
|
bank_count=len(bank_accounts),
|
||||||
credit_count=len(credit_accounts))
|
credit_count=len(credit_accounts),
|
||||||
|
monthly_charges=monthly_charges)
|
||||||
|
|
||||||
|
|
||||||
@accounts_bp.route('/new', methods=['GET', 'POST'])
|
@accounts_bp.route('/new', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -241,6 +241,14 @@ def transfer():
|
|||||||
form.from_account_id.choices = _account_choices()
|
form.from_account_id.choices = _account_choices()
|
||||||
form.to_account_id.choices = _account_choices()
|
form.to_account_id.choices = _account_choices()
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
prefill_to = request.args.get('to_account_id', '')
|
||||||
|
prefill_desc = request.args.get('description', '')
|
||||||
|
if prefill_to:
|
||||||
|
form.to_account_id.data = prefill_to
|
||||||
|
if prefill_desc:
|
||||||
|
form.description.data = prefill_desc
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if form.from_account_id.data == form.to_account_id.data:
|
if form.from_account_id.data == form.to_account_id.data:
|
||||||
flash('Source and destination accounts must be different.', 'warning')
|
flash('Source and destination accounts must be different.', 'warning')
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
{% for acct in accounts %}
|
{% for acct in accounts %}
|
||||||
<div class="col-12 col-md-6 col-xl-4">
|
<div class="col-12 col-md-6 col-xl-4">
|
||||||
<div class="pcard" style="border-left: 4px solid {{ acct.color }};">
|
<div class="pcard" style="border-left: 4px solid {{ acct.color }};">
|
||||||
|
|
||||||
|
<!-- Header: icon + name + menu -->
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<div style="width:36px;height:36px;border-radius:9px;background:{{ acct.color }}22;color:{{ acct.color }};display:flex;align-items:center;justify-content:center;font-size:18px;">
|
<div style="width:36px;height:36px;border-radius:9px;background:{{ acct.color }}22;color:{{ acct.color }};display:flex;align-items:center;justify-content:center;font-size:18px;">
|
||||||
@@ -52,33 +54,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if tab == 'credit' %}
|
||||||
|
<!-- Amount Owed + Monthly Charges -->
|
||||||
|
{% set owed = [0, -acct.balance] | max %}
|
||||||
|
{% set charges = monthly_charges.get(acct.id, 0) %}
|
||||||
|
<div class="d-flex gap-3 mt-3">
|
||||||
|
<div style="flex:1;background:#fff5f5;border-radius:8px;padding:10px 12px;">
|
||||||
|
<div style="font-size:10px;color:#991b1b;font-weight:600;text-transform:uppercase;letter-spacing:.04em;margin-bottom:2px;">Amount Owed</div>
|
||||||
|
<div class="mono {% if owed > 0 %}text-expense{% else %}" style="color:#10b981;{% endif %}font-size:20px;font-weight:700;">
|
||||||
|
{% if owed > 0 %}-{% endif %}{{ owed | currency }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;background:#fafafa;border-radius:8px;padding:10px 12px;">
|
||||||
|
<div style="font-size:10px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em;margin-bottom:2px;">This Month</div>
|
||||||
|
<div class="mono text-expense" style="font-size:20px;font-weight:700;">
|
||||||
|
{% if charges > 0 %}-{% endif %}{{ charges | currency }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if acct.notes %}
|
{% if acct.notes %}
|
||||||
<div style="font-size:12px;color:var(--muted);margin-top:10px;">{{ acct.notes }}</div>
|
<div style="font-size:12px;color:var(--muted);margin-top:10px;">{{ acct.notes }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
{% if tab == 'credit' %}
|
{% if tab == 'credit' %}
|
||||||
<div class="d-flex gap-2 mt-3">
|
<div class="d-flex gap-2 mt-3">
|
||||||
|
<a href="{{ url_for('transactions.transfer', to_account_id=acct.id, description='Credit Card Payment — ' ~ acct.name) }}"
|
||||||
|
class="btn btn-sm flex-fill btn-danger" style="font-size:12px;">
|
||||||
|
<i class="bi bi-send me-1"></i>Pay Card
|
||||||
|
</a>
|
||||||
<a href="{{ url_for('transactions.index', tab='expense', account_id=acct.id) }}"
|
<a href="{{ url_for('transactions.index', tab='expense', account_id=acct.id) }}"
|
||||||
class="btn btn-sm flex-fill" style="font-size:11px;background:#fee2e2;color:#991b1b;border:none;">
|
class="btn btn-sm flex-fill btn-outline-secondary" style="font-size:12px;">
|
||||||
<i class="bi bi-arrow-up-circle me-1"></i>Charges
|
<i class="bi bi-list-ul me-1"></i>Charges
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('transactions.new', type='expense') }}"
|
<a href="{{ url_for('transactions.new', type='expense') }}"
|
||||||
class="btn btn-sm flex-fill" style="font-size:11px;background:#f1f5f9;color:#475569;border:none;">
|
class="btn btn-sm" style="font-size:12px;background:#f1f5f9;color:#475569;border:none;" title="Add Charge">
|
||||||
<i class="bi bi-plus-lg me-1"></i>Add Charge
|
<i class="bi bi-plus-lg"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="d-flex gap-2 mt-3">
|
<div class="d-flex gap-2 mt-3">
|
||||||
<a href="{{ url_for('transactions.index', tab='expense', account_id=acct.id) }}"
|
<a href="{{ url_for('transactions.index', tab='expense', account_id=acct.id) }}"
|
||||||
class="btn btn-sm flex-fill" style="font-size:11px;background:#f1f5f9;color:#475569;border:none;">
|
class="btn btn-sm flex-fill btn-outline-secondary" style="font-size:12px;">
|
||||||
<i class="bi bi-list-ul me-1"></i>Transactions
|
<i class="bi bi-list-ul me-1"></i>Transactions
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('transactions.new', type='income') }}"
|
<a href="{{ url_for('transactions.new', type='income') }}"
|
||||||
class="btn btn-sm flex-fill" style="font-size:11px;background:#d1fae5;color:#065f46;border:none;">
|
class="btn btn-sm flex-fill" style="font-size:12px;background:#d1fae5;color:#065f46;border:none;">
|
||||||
<i class="bi bi-plus-lg me-1"></i>Add Income
|
<i class="bi bi-plus-lg me-1"></i>Add Income
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user