mirror of
https://github.com/we-promise/sure.git
synced 2026-04-11 16:24:51 +00:00
94 lines
3.5 KiB
Ruby
94 lines
3.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# This migration cleans up orphaned balance records that have a currency different
|
|
# from their account's current currency. This can happen when linked accounts
|
|
# (SimpleFIN, Lunchflow, Enable Banking, Plaid) were created with an initial sync
|
|
# before the correct currency was known from the provider.
|
|
#
|
|
# The fix in Account.create_and_sync with skip_initial_sync: true prevents this
|
|
# going forward, but existing data needs to be cleaned up.
|
|
class CleanupOrphanedCurrencyBalances < ActiveRecord::Migration[7.2]
|
|
def up
|
|
# Skip in test environment with empty database (CI)
|
|
return say "Skipping in test environment - no data to clean" if Rails.env.test? && account_count.zero?
|
|
|
|
# First, identify affected accounts for logging
|
|
affected_accounts = execute(<<~SQL).to_a
|
|
SELECT DISTINCT
|
|
a.id,
|
|
a.name,
|
|
a.currency as account_currency,
|
|
b.currency as orphaned_currency,
|
|
COUNT(b.id) as orphaned_balance_count
|
|
FROM accounts a
|
|
JOIN balances b ON a.id = b.account_id
|
|
WHERE b.currency != a.currency
|
|
AND (
|
|
a.simplefin_account_id IS NOT NULL
|
|
OR a.plaid_account_id IS NOT NULL
|
|
OR EXISTS (SELECT 1 FROM account_providers WHERE account_id = a.id)
|
|
)
|
|
GROUP BY a.id, a.name, a.currency, b.currency
|
|
ORDER BY a.name
|
|
SQL
|
|
|
|
if affected_accounts.any?
|
|
say "Found #{affected_accounts.size} account-currency combinations with orphaned balances:"
|
|
affected_accounts.each do |row|
|
|
say " - #{row['name']}: #{row['orphaned_balance_count']} balances in #{row['orphaned_currency']} (account is #{row['account_currency']})"
|
|
end
|
|
|
|
# Delete orphaned balances where currency doesn't match account currency
|
|
# Only for linked accounts (provider-connected accounts)
|
|
execute(<<~SQL)
|
|
DELETE FROM balances
|
|
WHERE id IN (
|
|
SELECT b.id
|
|
FROM balances b
|
|
JOIN accounts a ON b.account_id = a.id
|
|
WHERE b.currency != a.currency
|
|
AND (
|
|
a.simplefin_account_id IS NOT NULL
|
|
OR a.plaid_account_id IS NOT NULL
|
|
OR EXISTS (SELECT 1 FROM account_providers WHERE account_id = a.id)
|
|
)
|
|
)
|
|
SQL
|
|
|
|
say "Deleted orphaned balances from linked accounts"
|
|
|
|
# Get unique account IDs that need re-sync
|
|
account_ids = affected_accounts.map { |row| row["id"] }.uniq
|
|
|
|
# Schedule re-sync for affected accounts to regenerate correct balances
|
|
# Only if Account model is available and responds to sync_later
|
|
if defined?(Account) && Account.respond_to?(:where)
|
|
say "Scheduling re-sync for #{account_ids.size} affected accounts..."
|
|
Account.where(id: account_ids).find_each do |account|
|
|
account.sync_later if account.respond_to?(:sync_later)
|
|
end
|
|
say "Scheduled re-sync for #{account_ids.size} affected accounts"
|
|
else
|
|
say "Skipping re-sync scheduling (Account model not available)"
|
|
say "Please manually sync affected accounts: #{account_ids.join(', ')}"
|
|
end
|
|
else
|
|
say "No orphaned currency balances found - database is clean"
|
|
end
|
|
end
|
|
|
|
def down
|
|
say "This migration cannot be fully reversed."
|
|
say "The deleted balances will be regenerated by the scheduled syncs."
|
|
say "If syncs haven't run yet, you may need to manually trigger them."
|
|
end
|
|
|
|
private
|
|
|
|
def account_count
|
|
execute("SELECT COUNT(*) FROM accounts").first["count"].to_i
|
|
rescue
|
|
0
|
|
end
|
|
end
|