05/31 Phase 2

This commit is contained in:
2026-05-31 11:17:53 -04:00
parent 1c34dcbd28
commit 9f2a0acc38
18 changed files with 1817 additions and 391 deletions
+105
View File
@@ -0,0 +1,105 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
from flask_login import login_required
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length, Optional
from app.extensions import db
from app.models.account import Account
from app.services.account_service import calc_balance
accounts_bp = Blueprint('accounts', __name__, url_prefix='/accounts')
ACCOUNT_TYPES = [
('checking', 'Checking'),
('savings', 'Savings'),
('cash', 'Cash'),
('credit_card', 'Credit Card'),
('crypto', 'Crypto Wallet'),
('investment', 'Investment'),
('other', 'Other'),
]
ACCOUNT_ICONS = [
('bi-bank', 'Bank'),
('bi-wallet2', 'Wallet'),
('bi-cash-stack', 'Cash'),
('bi-credit-card', 'Credit Card'),
('bi-currency-bitcoin', 'Crypto'),
('bi-graph-up', 'Investment'),
('bi-safe', 'Safe'),
]
ACCOUNT_COLORS = [
'#4F81C7', '#10B981', '#F59E0B', '#EF4444',
'#8B5CF6', '#EC4899', '#06B6D4', '#64748B',
]
class AccountForm(FlaskForm):
name = StringField('Account Name', validators=[DataRequired(), Length(1, 100)])
account_type = SelectField('Type', choices=ACCOUNT_TYPES, validators=[DataRequired()])
color = StringField('Color', default='#4F81C7')
icon = StringField('Icon', default='bi-bank')
notes = TextAreaField('Notes', validators=[Optional(), Length(max=500)])
submit = SubmitField('Save')
@accounts_bp.route('/')
@login_required
def index():
accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
# Recalc balances on page load
for a in accounts:
calc_balance(a.id)
accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
return render_template('accounts/index.html', accounts=accounts)
@accounts_bp.route('/new', methods=['GET', 'POST'])
@login_required
def new():
form = AccountForm()
if form.validate_on_submit():
account = Account(
name=form.name.data.strip(),
account_type=form.account_type.data,
color=form.color.data or '#4F81C7',
icon=form.icon.data or 'bi-bank',
notes=form.notes.data,
balance=0,
)
db.session.add(account)
db.session.commit()
flash(f'Account "{account.name}" created.', 'success')
return redirect(url_for('accounts.index'))
return render_template('accounts/form.html', form=form, title='New Account',
colors=ACCOUNT_COLORS, icons=ACCOUNT_ICONS)
@accounts_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
@login_required
def edit(id):
account = db.get_or_404(Account, id)
form = AccountForm(obj=account)
if form.validate_on_submit():
account.name = form.name.data.strip()
account.account_type = form.account_type.data
account.color = form.color.data or account.color
account.icon = form.icon.data or account.icon
account.notes = form.notes.data
db.session.commit()
flash(f'Account "{account.name}" updated.', 'success')
return redirect(url_for('accounts.index'))
return render_template('accounts/form.html', form=form, title='Edit Account',
account=account, colors=ACCOUNT_COLORS, icons=ACCOUNT_ICONS)
@accounts_bp.route('/<int:id>/delete', methods=['POST'])
@login_required
def delete(id):
account = db.get_or_404(Account, id)
# Soft delete
account.is_active = False
db.session.commit()
flash(f'Account "{account.name}" removed.', 'info')
return redirect(url_for('accounts.index'))