06/01 Review and optimize on UI/UX, security, and functionality
This commit is contained in:
@@ -398,4 +398,5 @@ def delete_receipt(txn_id):
|
||||
@login_required
|
||||
def view_receipt(filename):
|
||||
upload_dir = current_app.config.get('UPLOAD_FOLDER', '/home/pfm/app/uploads')
|
||||
return send_from_directory(upload_dir, filename)
|
||||
# Strip any path components to prevent directory traversal
|
||||
return send_from_directory(upload_dir, os.path.basename(filename))
|
||||
|
||||
@@ -128,7 +128,10 @@ def map_accounts(enrollment_id):
|
||||
db.session.flush()
|
||||
ta.pfm_account_id = new_acct.id
|
||||
elif val.isdigit():
|
||||
ta.pfm_account_id = int(val)
|
||||
acct_id = int(val)
|
||||
# Verify the account actually exists and belongs to this app
|
||||
if Account.query.filter_by(id=acct_id, is_active=True).first():
|
||||
ta.pfm_account_id = acct_id
|
||||
# val == '' means skip this account
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@@ -69,10 +69,13 @@ def index():
|
||||
|
||||
if search:
|
||||
query = query.filter(Transaction.description.ilike(f'%{search}%'))
|
||||
if category_id:
|
||||
query = query.filter(Transaction.category_id == int(category_id))
|
||||
if account_id:
|
||||
query = query.filter(Transaction.account_id == int(account_id))
|
||||
try:
|
||||
if category_id:
|
||||
query = query.filter(Transaction.category_id == int(category_id))
|
||||
if account_id:
|
||||
query = query.filter(Transaction.account_id == int(account_id))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if date_from:
|
||||
try:
|
||||
query = query.filter(Transaction.date >= datetime.strptime(date_from, '%Y-%m-%d').date())
|
||||
|
||||
@@ -137,12 +137,17 @@ def import_rows(rows, skip_duplicates=True):
|
||||
|
||||
for row in rows:
|
||||
if skip_duplicates:
|
||||
existing = Transaction.query.filter_by(
|
||||
q = Transaction.query.filter_by(
|
||||
date=row['date'],
|
||||
description=row['description'],
|
||||
amount=row['amount'],
|
||||
transaction_type=row['transaction_type'],
|
||||
).first()
|
||||
)
|
||||
# Scope to the same account when one is known, so identical
|
||||
# transactions on different accounts are not incorrectly skipped.
|
||||
if row.get('account_id'):
|
||||
q = q.filter_by(account_id=row['account_id'])
|
||||
existing = q.first()
|
||||
if existing:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
@@ -53,8 +53,14 @@ def process_due_rules(dry_run=False):
|
||||
db.session.commit()
|
||||
continue
|
||||
|
||||
# Create transaction for each missed occurrence up to today
|
||||
# Create transaction for each missed occurrence up to today.
|
||||
# Cap catchup at 90 days to prevent runaway loops on long-dormant rules.
|
||||
catchup_floor = today - timedelta(days=90)
|
||||
run_date = rule.next_run or rule.start_date
|
||||
if run_date < catchup_floor:
|
||||
log.warning('[recurring] rule "%s" is >90 days overdue; starting catchup from %s',
|
||||
rule.description, catchup_floor)
|
||||
run_date = catchup_floor
|
||||
affected_accounts = set()
|
||||
|
||||
while run_date <= today:
|
||||
|
||||
Reference in New Issue
Block a user