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') BANK_TYPES = {'checking', 'savings', 'cash', 'investment', 'crypto', 'other'} @accounts_bp.route('/') @login_required def index(): tab = request.args.get('tab', 'bank') if tab not in ('bank', 'credit'): tab = 'bank' all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all() for a in all_accounts: calc_balance(a.id) all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all() bank_accounts = [a for a in all_accounts if a.account_type in BANK_TYPES] credit_accounts = [a for a in all_accounts if a.account_type == 'credit_card'] accounts = credit_accounts if tab == 'credit' else bank_accounts return render_template('accounts/index.html', accounts=accounts, tab=tab, bank_count=len(bank_accounts), credit_count=len(credit_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('//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('//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'))