06/02 Update account balance adjustment
This commit is contained in:
@@ -137,3 +137,59 @@ def delete(id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f'Account "{account.name}" removed.', 'info')
|
flash(f'Account "{account.name}" removed.', 'info')
|
||||||
return redirect(url_for('accounts.index'))
|
return redirect(url_for('accounts.index'))
|
||||||
|
|
||||||
|
|
||||||
|
@accounts_bp.route('/<int:id>/adjust', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def adjust_balance(id):
|
||||||
|
account = db.get_or_404(Account, id)
|
||||||
|
calc_balance(account.id)
|
||||||
|
db.session.refresh(account)
|
||||||
|
current_balance = float(account.balance)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
from app.models.transaction import Transaction
|
||||||
|
from app.models.category import Category
|
||||||
|
from wtforms import ValidationError
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_balance = float(request.form.get('new_balance', '').replace(',', ''))
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
flash('Invalid amount entered.', 'danger')
|
||||||
|
return redirect(url_for('accounts.adjust_balance', id=id))
|
||||||
|
|
||||||
|
difference = round(new_balance - current_balance, 2)
|
||||||
|
|
||||||
|
if difference == 0:
|
||||||
|
flash('Balance is already at that amount — no adjustment needed.', 'info')
|
||||||
|
return redirect(url_for('accounts.index',
|
||||||
|
tab='credit' if account.account_type == 'credit_card' else 'bank'))
|
||||||
|
|
||||||
|
txn_type = 'income' if difference > 0 else 'expense'
|
||||||
|
amount = abs(difference)
|
||||||
|
notes = request.form.get('notes', '').strip() or 'Manual balance adjustment'
|
||||||
|
|
||||||
|
txn = Transaction(
|
||||||
|
account_id=account.id,
|
||||||
|
transaction_type=txn_type,
|
||||||
|
amount=amount,
|
||||||
|
description='Balance Adjustment',
|
||||||
|
date=date.today(),
|
||||||
|
notes=notes,
|
||||||
|
)
|
||||||
|
db.session.add(txn)
|
||||||
|
db.session.commit()
|
||||||
|
calc_balance(account.id)
|
||||||
|
|
||||||
|
flash(
|
||||||
|
f'Balance adjusted by '
|
||||||
|
f'{"+" if difference > 0 else ""}{difference:,.2f}. '
|
||||||
|
f'New balance: {new_balance:,.2f}.',
|
||||||
|
'success'
|
||||||
|
)
|
||||||
|
return redirect(url_for('accounts.index',
|
||||||
|
tab='credit' if account.account_type == 'credit_card' else 'bank'))
|
||||||
|
|
||||||
|
return render_template('accounts/adjust.html',
|
||||||
|
account=account,
|
||||||
|
current_balance=current_balance)
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Adjust Balance — {{ account.name }}{% endblock %}
|
||||||
|
{% block page_title %}Adjust Balance{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-7 col-lg-5">
|
||||||
|
|
||||||
|
<div class="pcard mb-3" style="border-left: 4px solid {{ account.color }};">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<div style="width:36px;height:36px;border-radius:9px;background:{{ account.color }}22;color:{{ account.color }};display:flex;align-items:center;justify-content:center;font-size:18px;">
|
||||||
|
<i class="bi {{ account.icon }}"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="font-weight:600;font-size:14px;">{{ account.name }}</div>
|
||||||
|
<div style="font-size:11px;color:var(--muted);">{{ account.account_type | replace('_',' ') | title }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 d-flex gap-3">
|
||||||
|
<div>
|
||||||
|
<div style="font-size:11px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.04em;">Current Balance</div>
|
||||||
|
<div class="mono {% if current_balance >= 0 %}text-income{% else %}text-expense{% endif %}" style="font-size:22px;font-weight:700;">
|
||||||
|
{{ current_balance | abs | currency }}{% if current_balance < 0 %} <span style="font-size:13px;font-weight:400;">(owed)</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pcard" style="border-top: 4px solid {{ account.color }};">
|
||||||
|
<p style="font-size:13px;color:var(--muted);">
|
||||||
|
Enter the correct balance. An adjustment transaction will be created for the difference to bring the account in line.
|
||||||
|
</p>
|
||||||
|
<form method="POST" novalidate id="adjustForm">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-medium" style="font-size:13px;">Correct Balance</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text" style="font-size:13px;">{{ current_user.currency_symbol }}</span>
|
||||||
|
<input type="number" name="new_balance" id="newBalance" class="form-control"
|
||||||
|
step="0.01" placeholder="0.00" autofocus
|
||||||
|
style="font-size:15px;font-weight:600;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Live difference preview -->
|
||||||
|
<div id="diffPreview" class="mb-3 p-3" style="border-radius:8px;background:#f8fafc;display:none;">
|
||||||
|
<div style="font-size:12px;color:var(--muted);margin-bottom:4px;">Adjustment that will be created</div>
|
||||||
|
<div id="diffText" style="font-size:14px;font-weight:600;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-medium" style="font-size:13px;">Reason <span style="font-weight:400;color:var(--muted);">(optional)</span></label>
|
||||||
|
<input type="text" name="notes" class="form-control" style="font-size:13px;"
|
||||||
|
placeholder="e.g. Bank reconciliation, opening balance">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary" id="submitBtn" disabled>Apply Adjustment</button>
|
||||||
|
<a href="{{ url_for('accounts.index', tab='credit' if account.account_type == 'credit_card' else 'bank') }}"
|
||||||
|
class="btn btn-outline-secondary">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
const currentBalance = {{ current_balance }};
|
||||||
|
const symbol = '{{ current_user.currency_symbol }}';
|
||||||
|
|
||||||
|
document.getElementById('newBalance').addEventListener('input', function () {
|
||||||
|
const val = parseFloat(this.value);
|
||||||
|
const preview = document.getElementById('diffPreview');
|
||||||
|
const diffText = document.getElementById('diffText');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
|
||||||
|
if (isNaN(val)) {
|
||||||
|
preview.style.display = 'none';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = Math.round((val - currentBalance) * 100) / 100;
|
||||||
|
|
||||||
|
if (diff === 0) {
|
||||||
|
preview.style.background = '#f8fafc';
|
||||||
|
diffText.style.color = '#64748b';
|
||||||
|
diffText.textContent = 'No change — balance is already at this amount.';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
} else {
|
||||||
|
const isIncome = diff > 0;
|
||||||
|
preview.style.background = isIncome ? '#f0fdf4' : '#fef2f2';
|
||||||
|
diffText.style.color = isIncome ? '#10b981' : '#ef4444';
|
||||||
|
const sign = isIncome ? '+' : '-';
|
||||||
|
const absVal = Math.abs(diff).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
|
const type = isIncome ? 'Income (credit)' : 'Expense (debit)';
|
||||||
|
diffText.textContent = `${type}: ${sign}${symbol}${absVal}`;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
}
|
||||||
|
preview.style.display = 'block';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
<button class="btn btn-sm" style="background:none;border:none;color:var(--muted);padding:2px 6px;" data-bs-toggle="dropdown"><i class="bi bi-three-dots"></i></button>
|
<button class="btn btn-sm" style="background:none;border:none;color:var(--muted);padding:2px 6px;" data-bs-toggle="dropdown"><i class="bi bi-three-dots"></i></button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" style="font-size:13px;">
|
<ul class="dropdown-menu dropdown-menu-end" style="font-size:13px;">
|
||||||
<li><a class="dropdown-item" href="{{ url_for('accounts.edit', id=acct.id) }}"><i class="bi bi-pencil me-2"></i>Edit</a></li>
|
<li><a class="dropdown-item" href="{{ url_for('accounts.edit', id=acct.id) }}"><i class="bi bi-pencil me-2"></i>Edit</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('accounts.adjust_balance', id=acct.id) }}"><i class="bi bi-sliders me-2"></i>Adjust Balance</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<form method="POST" action="{{ url_for('accounts.delete', id=acct.id) }}" onsubmit="return confirm('Remove this account?')">
|
<form method="POST" action="{{ url_for('accounts.delete', id=acct.id) }}" onsubmit="return confirm('Remove this account?')">
|
||||||
|
|||||||
Reference in New Issue
Block a user