1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-19 13:19:39 +02:00

Show an error notification if account cannot be manually synced (#761)

This commit is contained in:
Jakub Kottnauer 2024-05-20 16:34:48 +02:00 committed by GitHub
parent 34811d8fd8
commit 30c19b9d2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 84 deletions

View file

@ -74,12 +74,20 @@ class AccountsController < ApplicationController
end
def sync
@account.sync_later if @account.can_sync?
respond_to do |format|
format.html { redirect_to account_path(@account), notice: t(".success") }
format.turbo_stream do
render turbo_stream: turbo_stream.append("notification-tray", partial: "shared/notification", locals: { type: "success", content: { body: t(".success") } })
if @account.can_sync?
@account.sync_later
respond_to do |format|
format.html { redirect_to account_path(@account), notice: t(".success") }
format.turbo_stream do
render turbo_stream: turbo_stream.append("notification-tray", partial: "shared/notification", locals: { type: "success", content: { body: t(".success") } })
end
end
else
respond_to do |format|
format.html { redirect_to account_path(@account), notice: t(".cannot_sync") }
format.turbo_stream do
render turbo_stream: turbo_stream.append("notification-tray", partial: "shared/notification", locals: { type: "error", content: { body: t(".cannot_sync") } })
end
end
end
end

View file

@ -1,88 +1,86 @@
module Account::Syncable
extend ActiveSupport::Concern
extend ActiveSupport::Concern
def sync_later(start_date = nil)
AccountSyncJob.perform_later(self, start_date)
def sync_later(start_date = nil)
AccountSyncJob.perform_later(self, start_date)
end
def sync(start_date = nil)
update!(status: "syncing")
sync_exchange_rates
calc_start_date = start_date - 1.day if start_date.present? && self.balance_on(start_date - 1.day).present?
calculator = Account::Balance::Calculator.new(self, { calc_start_date: })
calculator.calculate
self.balances.upsert_all(calculator.daily_balances, unique_by: :index_account_balances_on_account_id_date_currency_unique)
self.balances.where("date < ?", effective_start_date).delete_all
new_balance = calculator.daily_balances.select { |b| b[:currency] == self.currency }.last[:balance]
update!(status: "ok", last_sync_date: Date.today, balance: new_balance)
rescue => e
update!(status: "error")
Rails.logger.error("Failed to sync account #{id}: #{e.message}")
end
def can_sync?
# Skip account sync if account is not active or the sync process is already running
return false unless is_active
return false if syncing?
# If last_sync_date is blank (i.e. the account has never been synced before) allow syncing
return true if last_sync_date.blank?
# If last_sync_date is not today, allow syncing
last_sync_date != Date.today
end
# The earliest date we can calculate a balance for
def effective_start_date
first_valuation_date = self.valuations.order(:date).pluck(:date).first
first_transaction_date = self.transactions.order(:date).pluck(:date).first
[ first_valuation_date, first_transaction_date&.prev_day ].compact.min || Date.current
end
# Finds all the rate pairs that are required to calculate balances for an account and syncs them
def sync_exchange_rates
rate_candidates = []
if multi_currency?
transactions_in_foreign_currency = self.transactions.where.not(currency: self.currency).pluck(:currency, :date).uniq
transactions_in_foreign_currency.each do |currency, date|
rate_candidates << { date: date, from_currency: currency, to_currency: self.currency }
end
end
def sync(start_date = nil)
update!(status: "syncing")
sync_exchange_rates
calc_start_date = start_date - 1.day if start_date.present? && self.balance_on(start_date - 1.day).present?
calculator = Account::Balance::Calculator.new(self, { calc_start_date: })
calculator.calculate
self.balances.upsert_all(calculator.daily_balances, unique_by: :index_account_balances_on_account_id_date_currency_unique)
self.balances.where("date < ?", effective_start_date).delete_all
new_balance = calculator.daily_balances.select { |b| b[:currency] == self.currency }.last[:balance]
self.balance = new_balance
self.save!
update!(status: "ok", last_sync_date: Date.today)
rescue => e
update!(status: "error")
Rails.logger.error("Failed to sync account #{id}: #{e.message}")
if foreign_currency?
(effective_start_date..Date.current).each do |date|
rate_candidates << { date: date, from_currency: self.currency, to_currency: self.family.currency }
end
end
def can_sync?
# Skip account sync if account is not active or the sync process is already running
return false unless is_active
return false if syncing?
# If last_sync_date is blank (i.e. the account has never been synced before) allow syncing
return true if last_sync_date.blank?
existing_rates = ExchangeRate.where(
base_currency: rate_candidates.map { |rc| rc[:from_currency] },
converted_currency: rate_candidates.map { |rc| rc[:to_currency] },
date: rate_candidates.map { |rc| rc[:date] }
).pluck(:base_currency, :converted_currency, :date)
# If last_sync_date is not today, allow syncing
last_sync_date != Date.today
# Convert to a set for faster lookup
existing_rates_set = existing_rates.map { |er| [ er[0], er[1], er[2].to_s ] }.to_set
rate_candidates.each do |rate_candidate|
rc_from = rate_candidate[:from_currency]
rc_to = rate_candidate[:to_currency]
rc_date = rate_candidate[:date]
next if existing_rates_set.include?([ rc_from, rc_to, rc_date.to_s ])
logger.info "Fetching exchange rate from provider for account #{self.name}: #{self.id} (#{rc_from} to #{rc_to} on #{rc_date})"
rate = ExchangeRate.find_rate_or_fetch from: rc_from, to: rc_to, date: rc_date
ExchangeRate.create! base_currency: rc_from, converted_currency: rc_to, date: rc_date, rate: rate if rate
end
# The earliest date we can calculate a balance for
def effective_start_date
first_valuation_date = self.valuations.order(:date).pluck(:date).first
first_transaction_date = self.transactions.order(:date).pluck(:date).first
[ first_valuation_date, first_transaction_date&.prev_day ].compact.min || Date.current
end
# Finds all the rate pairs that are required to calculate balances for an account and syncs them
def sync_exchange_rates
rate_candidates = []
if multi_currency?
transactions_in_foreign_currency = self.transactions.where.not(currency: self.currency).pluck(:currency, :date).uniq
transactions_in_foreign_currency.each do |currency, date|
rate_candidates << { date: date, from_currency: currency, to_currency: self.currency }
end
end
if foreign_currency?
(effective_start_date..Date.current).each do |date|
rate_candidates << { date: date, from_currency: self.currency, to_currency: self.family.currency }
end
end
existing_rates = ExchangeRate.where(
base_currency: rate_candidates.map { |rc| rc[:from_currency] },
converted_currency: rate_candidates.map { |rc| rc[:to_currency] },
date: rate_candidates.map { |rc| rc[:date] }
).pluck(:base_currency, :converted_currency, :date)
# Convert to a set for faster lookup
existing_rates_set = existing_rates.map { |er| [ er[0], er[1], er[2].to_s ] }.to_set
rate_candidates.each do |rate_candidate|
rc_from = rate_candidate[:from_currency]
rc_to = rate_candidate[:to_currency]
rc_date = rate_candidate[:date]
next if existing_rates_set.include?([ rc_from, rc_to, rc_date.to_s ])
logger.info "Fetching exchange rate from provider for account #{self.name}: #{self.id} (#{rc_from} to #{rc_to} on #{rc_date})"
rate = ExchangeRate.find_rate_or_fetch from: rc_from, to: rc_to, date: rc_date
ExchangeRate.create! base_currency: rc_from, converted_currency: rc_to, date: rc_date, rate: rate if rate
end
nil
end
nil
end
end

View file

@ -38,6 +38,7 @@ en:
summary:
new: New account
sync:
cannot_sync: Account cannot be synced at the moment
success: Account sync started
update:
success: Account updated successfully