app/models/user.rb
# recalculate user balance
# currency and balance is not updated if one or more exchange rates are missing
# missing exchange rates is put in queue for bank and looked up batch
# batch job started at after returning actual page to user
recalculate user balance
currency and balance is not updated if one or more exchange rates are missing
missing exchange rates is put in queue for bank and looked up batch
batch job started at after returning actual page to user
recalculate user balance currency and balance is not updated if one or more exchange rates are missing missing exchange rates is put in queue for bank and looked up batch batch job started at after returning actual page to user
def recalculate_balance
    if !last_login_at
      logger.warn2 "recalculate_balance method should not be called for non app user #{debug_info}"
      return true
    end
    user_ids = [user_id]
    api_gifts = ApiGift.where('user_id_giver in (?) and user_id_receiver is not null or ' +
                                  'user_id_receiver in (?) and user_id_giver is not null',
                              user_ids, user_ids).includes(:gift)
    api_gifts = api_gifts.find_all do |api_gift|
      (api_gift.user_id_giver and api_gift.gift.price and api_gift.gift.price != 0.00 and !api_gift.gift.deleted_at)
    end
    api_gifts = api_gifts.delete_if do |api_gift|
      (user_ids.index(api_gift.user_id_giver) and user_ids.index(api_gift.user_id_receiver))
    end
    api_gifts = api_gifts.sort_by { |ag| [ag.gift.received_at, ag.gift.id] }
    old_gid = -1
    api_gifts = api_gifts.delete_if do |api_gift|
      if api_gift.gift.id == old_gid
        true
      else
        old_gid = api_gift.gift.id
        false
      end
    end 
    user_balance_hash = {BALANCE_KEY => 0.0} 
    user_negative_interest_hash = {BALANCE_KEY => 0.0}  (USD)
    missing_exchange_rates = false
    logger.debug2 "user #{self.short_user_name}. #{api_gifts.size} gifts"
    previous_date = nil
    date = nil
    exchange_rates_hash = {} 
    api_gifts.each do |api_gift|
      balance_doc_hash = {}
      previous_date = api_gift.gift.received_at.to_date unless previous_date
      previous_balance_hash = user_balance_hash.clone
      balance_doc_hash[:previous_balance] = previous_balance_hash
      if previous_date != api_gift.gift.received_at.to_date
        user_balance_hash.keys.each do |balance_hash_currency|
          next if balance_hash_currency == BALANCE_KEY
          exchange_rates_hash[balance_hash_currency] = ExchangeRate.exchange(1.0, BASE_CURRENCY, balance_hash_currency, previous_date)
        end
      end
      previous_exchange_rates_hash = exchange_rates_hash.clone
      balance_doc_hash[:previous_exchange_rates] = previous_exchange_rates_hash
      balance_doc_hash[:previous_date] = previous_date.to_yyyymmdd
      balance_sum = user_balance_hash[BALANCE_KEY] 
      date = previous_date
      while (date < api_gift.gift.received_at.to_date) do
        date = 1.day.since(date)
        factor = (balance_sum >= 0 ? FACTOR_POS_BALANCE_PER_DAY : FACTOR_NEG_BALANCE_PER_DAY) 
        balance_sum = 0.0
        user_balance_hash.keys.each do |balance_hash_currency|
          next if balance_hash_currency == BALANCE_KEY
          user_balance_hash[balance_hash_currency] *= factor
          exchange_rate = ExchangeRate.exchange(1.0, balance_hash_currency, BASE_CURRENCY, date)
          balance_sum += user_balance_hash[balance_hash_currency] * exchange_rate
          exchange_rates_hash[balance_hash_currency] = 1.0 / exchange_rate if date == api_gift.gift.received_at.to_date
        end 
      end 
      user_balance_hash[BALANCE_KEY] = balance_sum
      logger.debug2 "gift id #{api_gift.id}: initialize and save negative interest hash"
      gift_negative_interest_hash = {}
      user_balance_hash.keys.each do |balance_hash_currency|
        next if balance_hash_currency == BALANCE_KEY
        gift_negative_interest = (previous_balance_hash[balance_hash_currency] - user_balance_hash[balance_hash_currency]).abs
        logger.debug2 "gift id #{api_gift.id}, currency = #{balance_hash_currency}, old = #{previous_balance_hash[balance_hash_currency]}, new = #{user_balance_hash[balance_hash_currency]}, neg.int. = #{gift_negative_interest}"
        gift_negative_interest_hash[balance_hash_currency] = gift_negative_interest
        user_negative_interest_hash[balance_hash_currency] = 0.0 unless user_negative_interest_hash.has_key?(balance_hash_currency)
        user_negative_interest_hash[balance_hash_currency] += gift_negative_interest
      end
      balance_sum = 0.0
      gift_negative_interest_hash.keys.each do |balance_hash_currency|
        balance_sum += ExchangeRate.exchange(gift_negative_interest_hash[balance_hash_currency], balance_hash_currency, BASE_CURRENCY, previous_date)
      end
      gift_negative_interest_hash[BALANCE_KEY] = balance_sum
      balance_doc_hash[:exchange_rates] = exchange_rates_hash
      balance_doc_hash[:negative_interest] = gift_negative_interest_hash
      previous_date = date
      sign = user_ids.index(api_gift.user_id_giver) ? 1 : -1
      user_balance_hash[api_gift.gift.currency] = 0.0 unless user_balance_hash.has_key?(api_gift.gift.currency)
      user_balance_hash[api_gift.gift.currency] += api_gift.gift.price * sign
      user_balance_hash[BALANCE_KEY] += ExchangeRate.exchange((api_gift.gift.price*sign), api_gift.gift.currency, BASE_CURRENCY, date)
      balance_doc_hash[:sign] = sign > 0 ? '+' : '-'
      balance_doc_hash[:balance] = user_balance_hash
      api_gift.gift.set_balance(user_ids, user_balance_hash[BALANCE_KEY], balance_doc_hash)
      logger.debug2 "recalculate_balance. gift.id = #{api_gift.gift.id}, gift.received_at = #{api_gift.gift.received_at}, balance_hash = #{user_balance_hash.to_s}, balance_doc_hash = #{balance_doc_hash}"
    end 
    return false if missing_exchange_rates 
    today = Date.parse(Sequence.get_last_exchange_rate_date)
    if date
      previous_balance_hash = user_balance_hash.clone
      while (date < today) do
        date = 1.day.since(date)
        factor = (user_balance_hash[BALANCE_KEY] >= 0 ? FACTOR_POS_BALANCE_PER_DAY : FACTOR_NEG_BALANCE_PER_DAY)
        balance_sum = 0.0
        user_balance_hash.keys.each do |balance_hash_currency|
          next if balance_hash_currency == BALANCE_KEY
          user_balance_hash[balance_hash_currency] *= factor
          balance_sum += ExchangeRate.exchange(user_balance_hash[balance_hash_currency], balance_hash_currency, BASE_CURRENCY, date)
        end 
        user_balance_hash[BALANCE_KEY] = balance_sum
      end
      user_balance_hash.keys.each do |balance_hash_currency|
        user_negative_interest_hash[balance_hash_currency] = 0.0 unless user_negative_interest_hash.has_key?(balance_hash_currency)
        user_negative_interest_hash[balance_hash_currency] += (previous_balance_hash[balance_hash_currency] - user_balance_hash[balance_hash_currency])
      end
    end
    logger.debug2 "user balance = #{user_balance_hash}"
    logger.debug2 "user negative_interest #{user_negative_interest_hash}"
    self.balance = user_balance_hash
    self.balance_at = today
    self.negative_interest = user_negative_interest_hash
    Gift.check_gift_and_api_gift_rel
    transaction do
      api_gifts.each do |api_gift|
        logger.debug2 "api gift id = #{api_gift.id}"
        api_gift.gift.save!
      end
      if user_ids.size > 1
        User.where(:user_id => (user_ids - [self.user_id])).each do |other_user|
          other_user.balance = user_balance_hash
          other_user.balance_at = today
          other_user.negative_interest = user_negative_interest_hash
          other_user.save!
        end  other_user
      end 
      self.save!
    end
    true
  end
def recalculate_balance if ! last_login_at logger . warn2 "recalculate_balance method should not be called for non app user #{debug_info}" return true end user_ids = [ user_id ] api_gifts = ApiGift . where ( 'user_id_giver in (?) and user_id_receiver is not null or ' + 'user_id_receiver in (?) and user_id_giver is not null' , user_ids , user_ids ) . includes ( :gift ) api_gifts = api_gifts . find_all do | api_gift | ( api_gift . user_id_giver and api_gift . gift . price and api_gift . gift . price != 0.00 and ! api_gift . gift . deleted_at ) end api_gifts = api_gifts . delete_if do | api_gift | ( user_ids . index ( api_gift . user_id_giver ) and user_ids . index ( api_gift . user_id_receiver ) ) end api_gifts = api_gifts . sort_by { | ag | [ ag . gift . received_at , ag . gift . id ] } old_gid = - 1 api_gifts = api_gifts . delete_if do | api_gift | if api_gift . gift . id == old_gid true else old_gid = api_gift . gift . id false end end user_balance_hash = { BALANCE_KEY => 0.0 } user_negative_interest_hash = { BALANCE_KEY => 0.0 } missing_exchange_rates = false logger . debug2 "user #{self.short_user_name}. #{api_gifts.size} gifts" previous_date = nil date = nil exchange_rates_hash = { } api_gifts . each do | api_gift | balance_doc_hash = { } previous_date = api_gift . gift . received_at . to_date unless previous_date previous_balance_hash = user_balance_hash . clone balance_doc_hash [ :previous_balance ] = previous_balance_hash if previous_date != api_gift . gift . received_at . to_date user_balance_hash . keys . each do | balance_hash_currency | next if balance_hash_currency == BALANCE_KEY exchange_rates_hash [ balance_hash_currency ] = ExchangeRate . exchange ( 1.0 , BASE_CURRENCY , balance_hash_currency , previous_date ) end end previous_exchange_rates_hash = exchange_rates_hash . clone balance_doc_hash [ :previous_exchange_rates ] = previous_exchange_rates_hash balance_doc_hash [ :previous_date ] = previous_date . to_yyyymmdd balance_sum = user_balance_hash [ BALANCE_KEY ] date = previous_date while ( date < api_gift . gift . received_at . to_date ) do date = 1 . day . since ( date ) factor = ( balance_sum >= 0 ? FACTOR_POS_BALANCE_PER_DAY : FACTOR_NEG_BALANCE_PER_DAY ) balance_sum = 0.0 user_balance_hash . keys . each do | balance_hash_currency | next if balance_hash_currency == BALANCE_KEY user_balance_hash [ balance_hash_currency ] *= factor exchange_rate = ExchangeRate . exchange ( 1.0 , balance_hash_currency , BASE_CURRENCY , date ) balance_sum += user_balance_hash [ balance_hash_currency ] * exchange_rate exchange_rates_hash [ balance_hash_currency ] = 1.0 / exchange_rate if date == api_gift . gift . received_at . to_date end end user_balance_hash [ BALANCE_KEY ] = balance_sum logger . debug2 "gift id #{api_gift.id}: initialize and save negative interest hash" gift_negative_interest_hash = { } user_balance_hash . keys . each do | balance_hash_currency | next if balance_hash_currency == BALANCE_KEY gift_negative_interest = ( previous_balance_hash [ balance_hash_currency ] - user_balance_hash [ balance_hash_currency ] ) . abs logger . debug2 "gift id #{api_gift.id}, currency = #{balance_hash_currency}, old = #{previous_balance_hash[balance_hash_currency]}, new = #{user_balance_hash[balance_hash_currency]}, neg.int. = #{gift_negative_interest}" gift_negative_interest_hash [ balance_hash_currency ] = gift_negative_interest user_negative_interest_hash [ balance_hash_currency ] = 0.0 unless user_negative_interest_hash . has_key? ( balance_hash_currency ) user_negative_interest_hash [ balance_hash_currency ] += gift_negative_interest end balance_sum = 0.0 gift_negative_interest_hash . keys . each do | balance_hash_currency | balance_sum += ExchangeRate . exchange ( gift_negative_interest_hash [ balance_hash_currency ] , balance_hash_currency , BASE_CURRENCY , previous_date ) end gift_negative_interest_hash [ BALANCE_KEY ] = balance_sum balance_doc_hash [ :exchange_rates ] = exchange_rates_hash balance_doc_hash [ :negative_interest ] = gift_negative_interest_hash previous_date = date sign = user_ids . index ( api_gift . user_id_giver ) ? 1 : - 1 user_balance_hash [ api_gift . gift . currency ] = 0.0 unless user_balance_hash . has_key? ( api_gift . gift . currency ) user_balance_hash [ api_gift . gift . currency ] += api_gift . gift . price * sign user_balance_hash [ BALANCE_KEY ] += ExchangeRate . exchange ( ( api_gift . gift . price * sign ) , api_gift . gift . currency , BASE_CURRENCY , date ) balance_doc_hash [ :sign ] = sign > 0 ? '+' : '-' balance_doc_hash [ :balance ] = user_balance_hash api_gift . gift . set_balance ( user_ids , user_balance_hash [ BALANCE_KEY ] , balance_doc_hash ) logger . debug2 "recalculate_balance. gift.id = #{api_gift.gift.id}, gift.received_at = #{api_gift.gift.received_at}, balance_hash = #{user_balance_hash.to_s}, balance_doc_hash = #{balance_doc_hash}" end return false if missing_exchange_rates today = Date . parse ( Sequence . get_last_exchange_rate_date ) if date previous_balance_hash = user_balance_hash . clone while ( date < today ) do date = 1 . day . since ( date ) factor = ( user_balance_hash [ BALANCE_KEY ] >= 0 ? FACTOR_POS_BALANCE_PER_DAY : FACTOR_NEG_BALANCE_PER_DAY ) balance_sum = 0.0 user_balance_hash . keys . each do | balance_hash_currency | next if balance_hash_currency == BALANCE_KEY user_balance_hash [ balance_hash_currency ] *= factor balance_sum += ExchangeRate . exchange ( user_balance_hash [ balance_hash_currency ] , balance_hash_currency , BASE_CURRENCY , date ) end user_balance_hash [ BALANCE_KEY ] = balance_sum end user_balance_hash . keys . each do | balance_hash_currency | user_negative_interest_hash [ balance_hash_currency ] = 0.0 unless user_negative_interest_hash . has_key? ( balance_hash_currency ) user_negative_interest_hash [ balance_hash_currency ] += ( previous_balance_hash [ balance_hash_currency ] - user_balance_hash [ balance_hash_currency ] ) end end logger . debug2 "user balance = #{user_balance_hash}" logger . debug2 "user negative_interest #{user_negative_interest_hash}" self . balance = user_balance_hash self . balance_at = today self . negative_interest = user_negative_interest_hash Gift . check_gift_and_api_gift_rel transaction do api_gifts . each do | api_gift | logger . debug2 "api gift id = #{api_gift.id}" api_gift . gift . save! end if user_ids . size > 1 User . where ( :user_id => ( user_ids - [ self . user_id ] ) ) . each do | other_user | other_user . balance = user_balance_hash other_user . balance_at = today other_user . negative_interest = user_negative_interest_hash other_user . save! end end self . save! end true end
recalculate user balance
currency and balance is not updated if one or more exchange rates are missing
missing exchange rates is put in queue for bank and looked up batch
batch job started at after returning actual page to user
recalculate user balance currency and balance is not updated if one or more exchange rates are missing missing exchange rates is put in queue for bank and looked up batch batch job started at after returning actual page to user
# never calculate balance for non app users # find user(s) # find all closed deals for this user # find closed deals # remove closed deals without a price and remove delete marked deals # remove closed deals with user(s) as giver AND receiver # sort: 1 received_at, 2 id # delete gift doublets # delete_if # BASE_CURRENCY # BASE_CURRENCY (USD) # used as help variables for exchange rate gains/losses calculation in view # update user.balance hash and save balance in gift.balance for documentation # previous balance >= 0 - use FACTOR_POS_BALANCE_PER_DAY to calculate new price # previous balance < 0 - use FACTOR_NEG_BALANCE_PER_DAY to calculate new price # note a small problem as balance in BASE_CURRENCY is a sum of different currencies and sum changes when currency rates changes # balance in BASE_CURRENCY can change between >= 0 and < 0 in period between two deals # but only previous balance is used when selection negative interest rate # save old exchange rates for exchange rate difference calculation # logger.debug2  "balance_doc_hash[:previous_balance] = #{balance_doc_hash[:previous_balance]}" # balance_doc_hash[:number_of_days] = (g.received_at.to_date - previous_date).to_i # step 1 - calculate negative interest from previous gift to this gift # use FACTOR_POS_BALANCE_PER_DAY for positive balance - 0.9998594803001535 per day <=>  5 % per year # use FACTOR_NEG_BALANCE_PER_DAY for negative balance - 0.9997113827109777 per day <=> 10 % per year # current user balance in BASE_CURRENCY (USD) # 5 OR 10 % in negative interest # each # while # initialize negative interest hash # step 2: calculate previous_balance + negative_interest hash # used for exchange rate gains/losses calculation in view #previous_balance_neg_int_hash = {} #previous_balance_hash.keys.each do |balance_hash_currency| #  next if balance_hash_currency == BALANCE_KEY #  previous_balance_neg_int_hash[balance_hash_currency] = previous_balance_hash[balance_hash_currency] + gift_negative_interest_hash[balance_hash_currency] #end #balance_doc_hash[:previous_balance_and_negative_interest] = previous_balance_neg_int_hash # step 3 - new balance with this gift # save balance and balance documentation # g.save # each # error - one or more missing currency rates # calculate negative interest from last gift and up to "today" # "today" as last date with known exchange rates from default money bank # each # calculation ok - all needed exchange rates was found # todo: catch any exception and return false if transaction fails # user with shared accounts - # each other_user # if