06/03 Optimize codes, fix Schwab Account
This commit is contained in:
+16
-6
@@ -60,19 +60,28 @@ def index():
|
|||||||
tab = 'bank'
|
tab = 'bank'
|
||||||
|
|
||||||
from app.models.teller_enrollment import TellerAccount
|
from app.models.teller_enrollment import TellerAccount
|
||||||
|
from app.models.schwab_connection import SchwabAccount
|
||||||
all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
|
all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
|
||||||
|
pfm_ids = [a.id for a in all_accounts]
|
||||||
|
|
||||||
# Build Teller map first so we can skip calc_balance for Teller-linked accounts.
|
# Build sync-provider maps before the calc_balance loop.
|
||||||
# Their balance comes from Teller (live refresh or transaction sync) and must not
|
# Accounts linked to Teller or Schwab get their balance from the provider,
|
||||||
# be overwritten by the transaction-computed sum on every page load.
|
# not from transaction summation, so we skip calc_balance for them.
|
||||||
teller_accounts = TellerAccount.query.filter(
|
teller_accounts = TellerAccount.query.filter(
|
||||||
TellerAccount.pfm_account_id.in_([a.id for a in all_accounts]),
|
TellerAccount.pfm_account_id.in_(pfm_ids),
|
||||||
TellerAccount.is_active == True,
|
TellerAccount.is_active == True,
|
||||||
).all()
|
).all()
|
||||||
teller_map = {ta.pfm_account_id: ta for ta in teller_accounts}
|
teller_map = {ta.pfm_account_id: ta for ta in teller_accounts}
|
||||||
|
|
||||||
|
schwab_accounts = SchwabAccount.query.filter(
|
||||||
|
SchwabAccount.pfm_account_id.in_(pfm_ids),
|
||||||
|
SchwabAccount.is_active == True,
|
||||||
|
).all()
|
||||||
|
schwab_map = {sa.pfm_account_id: sa for sa in schwab_accounts}
|
||||||
|
|
||||||
|
provider_ids = set(teller_map) | set(schwab_map)
|
||||||
for a in all_accounts:
|
for a in all_accounts:
|
||||||
if a.id not in teller_map:
|
if a.id not in provider_ids:
|
||||||
calc_balance(a.id)
|
calc_balance(a.id)
|
||||||
all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
|
all_accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
|
||||||
|
|
||||||
@@ -101,7 +110,8 @@ def index():
|
|||||||
bank_count=len(bank_accounts),
|
bank_count=len(bank_accounts),
|
||||||
credit_count=len(credit_accounts),
|
credit_count=len(credit_accounts),
|
||||||
monthly_charges=monthly_charges,
|
monthly_charges=monthly_charges,
|
||||||
teller_map=teller_map)
|
teller_map=teller_map,
|
||||||
|
schwab_map=schwab_map)
|
||||||
|
|
||||||
|
|
||||||
@accounts_bp.route('/new', methods=['GET', 'POST'])
|
@accounts_bp.route('/new', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -292,6 +292,9 @@ def sync_snapshot(schwab_account_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('[schwab] sync_snapshot failed for id=%s: %s', schwab_account_id, e, exc_info=True)
|
log.error('[schwab] sync_snapshot failed for id=%s: %s', schwab_account_id, e, exc_info=True)
|
||||||
flash(f'Snapshot sync failed: {e}', 'danger')
|
flash(f'Snapshot sync failed: {e}', 'danger')
|
||||||
|
next_url = request.form.get('next', '')
|
||||||
|
if next_url and next_url.startswith('/'):
|
||||||
|
return redirect(next_url)
|
||||||
return redirect(url_for('schwab.index'))
|
return redirect(url_for('schwab.index'))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
{% for acct in accounts %}
|
{% for acct in accounts %}
|
||||||
{% set ta = teller_map.get(acct.id) %}
|
{% set ta = teller_map.get(acct.id) %}
|
||||||
|
{% set sa = schwab_map.get(acct.id) %}
|
||||||
<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 }};">
|
||||||
|
|
||||||
@@ -41,6 +42,9 @@
|
|||||||
{% if ta %}
|
{% if ta %}
|
||||||
<span style="font-size:10px;background:#dbeafe;color:#1e40af;border-radius:4px;padding:1px 5px;margin-left:4px;vertical-align:middle;">Teller</span>
|
<span style="font-size:10px;background:#dbeafe;color:#1e40af;border-radius:4px;padding:1px 5px;margin-left:4px;vertical-align:middle;">Teller</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if sa %}
|
||||||
|
<span style="font-size:10px;background:#d1fae5;color:#065f46;border-radius:4px;padding:1px 5px;margin-left:4px;vertical-align:middle;">Schwab</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:11px;color:var(--muted);">{{ acct.account_type | replace('_',' ') | title }}</div>
|
<div style="font-size:11px;color:var(--muted);">{{ acct.account_type | replace('_',' ') | title }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +123,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Teller action buttons (only for accounts linked to Teller) -->
|
<!-- Teller action buttons -->
|
||||||
{% if ta %}
|
{% if ta %}
|
||||||
<div class="d-flex gap-2 mt-2 pt-2" style="border-top:1px solid var(--border);">
|
<div class="d-flex gap-2 mt-2 pt-2" style="border-top:1px solid var(--border);">
|
||||||
<button id="refbtn-{{ acct.id }}"
|
<button id="refbtn-{{ acct.id }}"
|
||||||
@@ -150,6 +154,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Schwab action buttons -->
|
||||||
|
{% if sa %}
|
||||||
|
<div class="d-flex gap-2 mt-2 pt-2" style="border-top:1px solid var(--border);">
|
||||||
|
<form method="POST" action="{{ url_for('schwab.sync_snapshot', schwab_account_id=sa.id) }}" style="flex:1;">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="next" value="{{ url_for('accounts.index', tab=tab) }}">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-success w-100" style="font-size:11px;"
|
||||||
|
title="Pull live balance and investment positions from Schwab">
|
||||||
|
<i class="bi bi-arrow-clockwise me-1"></i>Balance & Positions
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<a href="{{ url_for('schwab.sync_preview_view', schwab_account_id=sa.id) }}"
|
||||||
|
class="btn btn-sm btn-outline-primary flex-fill" style="font-size:11px;"
|
||||||
|
title="Import new transactions from Schwab">
|
||||||
|
<i class="bi bi-cloud-download me-1"></i>Transactions
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if sa.last_sync_date %}
|
||||||
|
<div style="font-size:10px;color:var(--muted);margin-top:4px;text-align:right;">
|
||||||
|
Last synced {{ sa.last_sync_date.strftime('%b %d') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -57,7 +57,14 @@
|
|||||||
<i class="bi bi-graph-up-arrow text-muted" style="font-size:3rem;"></i>
|
<i class="bi bi-graph-up-arrow text-muted" style="font-size:3rem;"></i>
|
||||||
<h5 class="mt-3 mb-1">No investments yet</h5>
|
<h5 class="mt-3 mb-1">No investments yet</h5>
|
||||||
<p class="text-muted small mb-3">Track stocks, ETFs, crypto, real estate, and more.</p>
|
<p class="text-muted small mb-3">Track stocks, ETFs, crypto, real estate, and more.</p>
|
||||||
<a href="{{ url_for('investments.new') }}" class="btn btn-primary btn-sm">Add First Holding</a>
|
<div class="d-flex gap-2 justify-content-center flex-wrap">
|
||||||
|
<a href="{{ url_for('investments.new') }}" class="btn btn-primary btn-sm">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i>Add Holding
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('schwab.index') }}" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i class="bi bi-bank2 me-1"></i>Sync from Schwab
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user