Class: Reward

Inherits:
ApplicationRecord show all
Extended by:
Enumerize
Includes:
IdentityCache
Defined in:
app/models/reward.rb

Overview

typed: ignore frozen_string_literal: true

Constant Summary collapse

MINIMUM_REWARD_REDEMPTION =
100
CODE =
'REDEEMCODE'

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

sync_carrierwave_url

Class Method Details

.count_credits(user_id, country_id = nil) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/reward.rb', line 118

def count_credits(user_id, country_id = nil)
  # set default country_id 218, which is Thailand
  user = User.fetch(user_id)
  country_id = country_id.presence || user.country_id

  query = "SELECT GREATEST(SUM(rewards.points_total) - SUM(rewards.points_pending),0) AS total
           FROM rewards
           WHERE rewards.user_id = #{user_id}
           AND rewards.country_id = #{country_id}"

  # query = "SELECT COALESCE((SUM(t.points_total) - SUM(t.points_pending)), 0) as total FROM (
  #           SELECT SUM(points_total) as points_total, 0 as points_pending  FROM rewards WHERE points_total <= 0 AND user_id = #{user_id}
  #           union
  #           SELECT SUM(points_total) as points_total, 0 as points_pending  FROM rewards WHERE points_total > 0 AND user_id = #{user_id} and expiry_date >= #{Time.current.to_date}
  #           union
  #           SELECT 0 as points_total, SUM(points_pending) as points_pending  FROM rewards WHERE user_id = #{user_id} and expiry_date >= #{Time.current.to_date}
  #         ) as t"

  Reward.find_by_sql([query]).first['total'].to_i
rescue StandardError => e
  APMErrorHandler.report 'failed calculate user rewards credit', error: e, user_id: user_id
  0
end

.effective_date(reservation, today) ⇒ Object



171
172
173
174
175
176
177
# File 'app/models/reward.rb', line 171

def effective_date(reservation, today)
  if reservation.date.present? && reservation.date < today
    reservation.date
  else
    reservation.created_at.to_date
  end
end

.last_reservation(last_reservation_payonsite, last_reservation_prepaid, current_date) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
# File 'app/models/reward.rb', line 179

def last_reservation(last_reservation_payonsite, last_reservation_prepaid, current_date)
  if last_reservation_payonsite.present? && last_reservation_prepaid.present?
    if effective_date(last_reservation_payonsite,
                      current_date) < effective_date(last_reservation_prepaid, current_date)
      last_reservation_prepaid
    else
      last_reservation_payonsite
    end
  else
    last_reservation_payonsite || last_reservation_prepaid
  end
end

.last_reservation_payonsite(current_date, reservations) ⇒ Object

Get the last reservation pay onsite



148
149
150
151
152
153
154
155
156
157
# File 'app/models/reward.rb', line 148

def last_reservation_payonsite(current_date, reservations)
  reservations.past.order('reservations.id DESC').limit(30).
    max_by do |res|
      if res.date.present? && res.date < current_date
        res.date
      else
        res.created_at.to_date
      end
    end
end

.last_reservation_prepaid(current_date, reservations) ⇒ Object

Get the last reservation prepaid



160
161
162
163
164
165
166
167
168
169
# File 'app/models/reward.rb', line 160

def last_reservation_prepaid(current_date, reservations)
  reservations.joins(:charges).order('reservations.id DESC').limit(30).
    max_by do |res|
      if res.date.present? && res.date < current_date
        res.date
      else
        res.created_at.to_date
      end
    end
end

.points_expiry_date(user) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'app/models/reward.rb', line 192

def points_expiry_date(user)
  # Get the last reservation
  user_reservations = user.reservations.reached_goal_scope
  current_date = Time.use_zone(user.time_zone) { Date.current }

  last_reservation = last_reservation(last_reservation_payonsite(current_date, user_reservations),
                                      last_reservation_prepaid(current_date, user_reservations), current_date)

  # Set the minimum expiry date
  # Since this rules will be announced at 1st september 2024,
  # all of the expiration dates in august will be extended until 30th september 2024
  # https://app.clickup.com/9003122396/v/dc/8ca1fpw-7922/8ca1fpw-40216
  min_expiry_date = Date.new(2024, 10, 31)

  return user.get_credits.positive? ? min_expiry_date : nil if last_reservation.blank?

  time_zone = last_reservation.restaurant.time_zone

  if last_reservation.payment_type_provider.present?
    reservation_time_past = (last_reservation.reservation_time + 24.hours).past?
    start_date = reservation_time_past ? last_reservation.date : last_reservation.created_at.in_time_zone(time_zone).to_date
  else
    start_date = last_reservation.date
  end

  # Calculate expiry date as end of the month 4 months after the last booking date
  points_expiry_date = [(start_date + 4.months).in_time_zone(time_zone).end_of_month.to_date,
                        min_expiry_date.to_date].max

  # Check if last downgrade date should override the calculated points expiry date
  last_downgrade_date = user.user_benefit_expiry&.expires_at&.to_date
  [points_expiry_date, last_downgrade_date].compact.max
end

.user_points_by_country(user_id) ⇒ Object

get total points by country for a user



109
110
111
112
113
114
115
116
# File 'app/models/reward.rb', line 109

def user_points_by_country(user_id)
  rewards_map = grouped_rewards_by_country(user_id)

  build_country_credits(rewards_map)
rescue StandardError => e
  APMErrorHandler.report 'failed calculate user rewards credit', error: e, user_id: user_id
  []
end

.user_redeemed_total(user) ⇒ Object



142
143
144
145
# File 'app/models/reward.rb', line 142

def user_redeemed_total(user)
  amount = user.rewards.redeemed_points.inject(0) { |n, r| n + r.points_total.to_i - r.points_pending.to_i }
  amount * -1
end

Instance Method Details

#cancelled?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'app/models/reward.rb', line 60

def cancelled?
  points_total.negative? && points_pending.negative?
end

#confirmed?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'app/models/reward.rb', line 52

def confirmed?
  redeemed == true
end

#redeemed_amountObject



96
97
98
# File 'app/models/reward.rb', line 96

def redeemed_amount
  points_total * -1
end

#to_apiObject



100
101
102
103
104
105
# File 'app/models/reward.rb', line 100

def to_api
  data = attributes.dup
  excluded_attributes = %w[created_at updated_at]
  data.delete_if { |k, _v| excluded_attributes.include?(k) }
  data.merge(credits: user.get_credits)
end

#touch_relationsObject



46
47
48
49
50
# File 'app/models/reward.rb', line 46

def touch_relations
  TouchWorker.perform_async('User', user_id) if user_id.present?
  TouchWorker.perform_async('Reservation', reservation_id) if reservation_id.present?
  TouchWorker.perform_async('TicketTransaction', ticket_transaction_id) if ticket_transaction_id.present?
end

#unconfirmed?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'app/models/reward.rb', line 56

def unconfirmed?
  !confirmed?
end

#update_journal_entryObject

Update the journal entry for the user



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'app/models/reward.rb', line 65

def update_journal_entry
  return if user.blank?

  entry = journal_entry.presence || {}
  previous_total = calculate_previous_total

  new_points_pending = points_pending.to_i

  # there's possibility that the points_pending (total points) is not valid
  # when race condition happens
  # points_pending (total points) already calculated in PointsExpired
  # so we need to check if the points_pending is not equal to the total points
  is_inactive_points = description&.include?('Inactive account points expiry')
  points_total_not_match = new_points_pending != total_from_previous_rewards

  if is_inactive_points && points_total_not_match
    new_points_pending = total_from_previous_rewards
  end

  reward_points = points_total.to_i - new_points_pending
  total = [previous_total + reward_points, 0].max

  entry[Time.zone.now] = {
    previous_points: previous_total,
    this_reward_point: reward_points,
    total: total,
  }

  update_column(:journal_entry, entry)
end