diff --git a/app/routes/plaid.py b/app/routes/plaid.py index 71139a9..aa61634 100644 --- a/app/routes/plaid.py +++ b/app/routes/plaid.py @@ -369,6 +369,25 @@ def refresh_liabilities_view(item_db_id): return redirect(url_for('plaid.index')) +# ── Full Resync (clear cursor so next sync fetches full history) ────────────── + +@plaid_bp.route('/resync/', methods=['POST']) +@login_required +def full_resync(item_db_id): + item = db.get_or_404(PlaidItem, item_db_id) + item.cursor = None + item.last_synced_at = None + for pa in item.accounts.filter_by(is_active=True).all(): + pa.last_sync_date = None + db.session.commit() + flash( + f'{item.institution_name}: cursor reset. ' + f'Next sync will fetch full available history — duplicates will be skipped automatically.', + 'info' + ) + return redirect(url_for('plaid.index')) + + # ── Disconnect ──────────────────────────────────────────────────────────────── @plaid_bp.route('/disconnect/', methods=['POST']) diff --git a/app/routes/transactions.py b/app/routes/transactions.py index 480213c..0cff435 100644 --- a/app/routes/transactions.py +++ b/app/routes/transactions.py @@ -222,6 +222,51 @@ def edit(id): title='Edit Transaction') +@transactions_bp.route('/bulk-action', methods=['POST']) +@login_required +def bulk_action(): + """AJAX endpoint: delete or set-category for a list of transaction IDs.""" + data = request.get_json(silent=True) or {} + ids = data.get('ids', []) + action = data.get('action', '') + + if not ids or not isinstance(ids, list): + return jsonify({'ok': False, 'error': 'No IDs provided'}), 400 + + # Validate all IDs are integers + try: + ids = [int(i) for i in ids] + except (ValueError, TypeError): + return jsonify({'ok': False, 'error': 'Invalid IDs'}), 400 + + txns = Transaction.query.filter(Transaction.id.in_(ids)).all() + if not txns: + return jsonify({'ok': False, 'error': 'No transactions found'}), 404 + + if action == 'delete': + affected_accounts = {t.account_id for t in txns} + for txn in txns: + db.session.delete(txn) + db.session.commit() + for acct_id in affected_accounts: + if acct_id: + calc_balance(acct_id) + return jsonify({'ok': True, 'deleted': len(txns)}) + + elif action == 'set_category': + cat_raw = data.get('category_id') + cat_id = int(cat_raw) if cat_raw else None + # Validate category exists + if cat_id and not Category.query.get(cat_id): + return jsonify({'ok': False, 'error': 'Invalid category'}), 400 + for txn in txns: + txn.category_id = cat_id + db.session.commit() + return jsonify({'ok': True, 'updated': len(txns)}) + + return jsonify({'ok': False, 'error': 'Unknown action'}), 400 + + @transactions_bp.route('//set-category', methods=['POST']) @login_required def set_category(id): diff --git a/app/templates/plaid/index.html b/app/templates/plaid/index.html index 907262e..b925cf7 100644 --- a/app/templates/plaid/index.html +++ b/app/templates/plaid/index.html @@ -60,6 +60,13 @@ Billing Details +
+ + +
diff --git a/app/templates/transactions/index.html b/app/templates/transactions/index.html index 303cb0f..ad1c808 100644 --- a/app/templates/transactions/index.html +++ b/app/templates/transactions/index.html @@ -59,6 +59,28 @@
+ + + {% set has_filter = search or category_id or account_id or date_from or date_to %}
@@ -66,7 +88,10 @@ - + + @@ -76,8 +101,11 @@ {% for txn in transactions %} - - + + +
Date + + Date Description Category Account
{{ txn.date.strftime('%b %d, %Y') }}
+ + {{ txn.date.strftime('%b %d, %Y') }}
{{ txn.description }}
{% if txn.notes %}
{{ txn.notes | truncate(60) }}
{% endif %} @@ -158,10 +186,12 @@ {% endblock %}