Class: Voucher
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Voucher
- Extended by:
- Enumerize
- Includes:
- IdentityCache
- Defined in:
- app/models/voucher.rb
Overview
Schema Information
Table name: vouchers
id :integer not null, primary key
active :boolean
amount_cap_cents :integer default(0), not null
amount_cap_currency :string(191) default("THB"), not null
amount_cents :integer default(0), not null
amount_currency :string(191) default("USD"), not null
apply_for :string(191) default("package"), not null
currency_code :string(191)
delivery_fee_discount :boolean default(FALSE)
delivery_voucher :boolean default(FALSE)
description :text(65535)
discount_type :string(191)
end_date :date
expiry_date :date
expiry_range_by :string(191) default("created_at")
expiry_type :string(191) default("single")
first_booking_only :boolean default(FALSE)
for_android :boolean default(TRUE)
for_ios :boolean default(TRUE)
for_web :boolean default(TRUE)
fri_active :boolean default(TRUE)
max_pax :integer
max_usage :integer default(1)
max_usage_user :integer default(0)
min_total_price_cents :integer default(0)
min_total_price_currency :string(191) default("THB")
minimal_distance :decimal(10, 6)
mix_with_points :boolean default(FALSE)
mon_active :boolean default(TRUE)
name :string(191)
non_refundable :boolean default(FALSE)
payment_type :string(191)
percentage :integer
prefix_number :string(1000)
quota :integer
require_full_prepayment :boolean default(FALSE)
sat_active :boolean default(TRUE)
start_date :date
subsidized_by :string(191) default("hungryhub"), not null
sun_active :boolean default(TRUE)
thu_active :boolean default(TRUE)
tue_active :boolean default(TRUE)
usage_type :string(191)
voucher_category :string(191) default("marketing"), not null
voucher_code :string(191)
voucher_type :string(191) not null
wed_active :boolean default(TRUE)
created_at :datetime
updated_at :datetime
adaptive_points_ratio_id :bigint
branch_id :integer
city_id :bigint
country_id :bigint
early_bird_reservation_id :integer
early_bird_restaurant_id :integer
guest_id :bigint
restaurant_id :integer
restaurant_tag_id :bigint
user_id :integer
voucher_group_id :integer
Indexes
index_vouchers_on_adaptive_points_ratio_id (adaptive_points_ratio_id)
index_vouchers_on_branch_id (branch_id)
index_vouchers_on_city_id (city_id)
index_vouchers_on_country_id (country_id)
index_vouchers_on_early_bird_restaurant_id (early_bird_restaurant_id)
index_vouchers_on_guest_id (guest_id)
index_vouchers_on_restaurant_id (restaurant_id)
index_vouchers_on_restaurant_tag_id (restaurant_tag_id)
index_vouchers_on_user_id (user_id)
index_vouchers_on_voucher_code (voucher_code)
Foreign Keys
fk_rails_... (branch_id => branches.id)
fk_rails_... (early_bird_restaurant_id => restaurants.id)
fk_rails_... (restaurant_id => restaurants.id)
fk_rails_... (user_id => users.id)
Constant Summary collapse
- TYPES =
REMOVE VOUCHER TYPE (specific_guest)
%I[ all_restaurants specific_branch specific_restaurant specific_restaurants specific_customer specific_restaurant_and_customer specific_restaurant_and_guest specific_branch_and_customer specific_restaurant_tag specific_restaurant_packages hungryhub_voucher ].freeze
- PAYMENT_TYPES =
TODO: REMOVE VOUCHER PAYMENT TYPE
%i[qrcode creditcard qrcode_and_creditcard].freeze
- CATEGORIES =
%i[gift marketing redemption first_app_voucher refund_guarantee].freeze
- APPLY_FOR =
%i[package delivery_fee package_and_delivery_fee].freeze
- USAGE_TYPES =
%i[one_time deductible].freeze
- DISCOUNT_TYPES =
%i[amount percentage pack person].freeze
- FIRST_APP_CODE =
'APPFIRST'.freeze
- REGISTRATION_REWARD_AMOUNT_SGD =
Registration reward amounts
5- REGISTRATION_REWARD_AMOUNT_THB =
50- REGISTRATION_REWARD_AMOUNT_MYR =
10
Instance Attribute Summary collapse
-
#custom_amount ⇒ Object
Returns the value of attribute custom_amount.
-
#device_type ⇒ Object
Returns the value of attribute device_type.
Class Method Summary collapse
- .default_country ⇒ Object
- .generate_referral_code(referrer_code) ⇒ Object
- .generate_voucher_code ⇒ Object
- .static_template(template, locale = MyLocaleManager.normalize_locale, package = nil) ⇒ Object
- .valid_referral_code?(voucher_code) ⇒ Boolean
Instance Method Summary collapse
-
#country_code ⇒ Object
return country code, set default to TH if not set.
- #delivery_fee_and_package? ⇒ Boolean
- #fetch_country_name ⇒ Object
-
#fetch_currency_code ⇒ Object
return default THB if not set.
- #for_all_device? ⇒ Boolean
- #for_delivery_fee? ⇒ Boolean
- #for_package? ⇒ Boolean
- #handle_device_type ⇒ Object
- #invalid_prefix_number_message ⇒ Object
- #require_cc ⇒ Object
- #require_payment_provider? ⇒ Boolean
- #stakeholder ⇒ Object
-
#subsidized_by_hungryhub? ⇒ Boolean
Checks if the voucher is subsidized by HungryHub.
-
#subsidized_by_restaurant? ⇒ Boolean
Checks if the voucher is subsidized by the restaurant.
- #token ⇒ Object
- #total_used_amount ⇒ Object
- #usage ⇒ Object
- #valid_discount_type ⇒ Object
- #valid_mix_with_points ⇒ Object
- #valid_percentage_type ⇒ Object
- #valid_specific_device_types ⇒ Object
- #validate_amount_based_on_discount_type ⇒ Object
- #validate_amount_cap_based_on_discount_type ⇒ Object
- #validate_percentage_based_on_discount_type ⇒ Object
Methods inherited from ApplicationRecord
Instance Attribute Details
#custom_amount ⇒ Object
Returns the value of attribute custom_amount.
100 101 102 |
# File 'app/models/voucher.rb', line 100 def custom_amount @custom_amount end |
#device_type ⇒ Object
Returns the value of attribute device_type.
100 101 102 |
# File 'app/models/voucher.rb', line 100 def device_type @device_type end |
Class Method Details
.default_country ⇒ Object
361 362 363 364 |
# File 'app/models/voucher.rb', line 361 def self.default_country # Thailand is default country Country.find_by alpha3: 'THA' end |
.generate_referral_code(referrer_code) ⇒ Object
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'app/models/voucher.rb', line 388 def self.generate_referral_code(referrer_code) if referrer_code.blank? || referrer_code.to_s.length != User::MIN_REFERRAL_CODE_LENGTH raise NotImplementedError, "Referral code must be #{User::MIN_REFERRAL_CODE_LENGTH} characters" end attempts = 0 max_attempts = 1000 codes = Set.new code = nil loop do code = SecureRandom.alphanumeric(3).upcase code = "#{User::REFERRAL_CODE_PREFIX}#{referrer_code}-#{code}".upcase codes.add(code) unless Voucher.exists?(voucher_code: code) || User.exists?(short_my_r_code: code) break end attempts += 1 raise 'Too many attempts' if attempts > max_attempts end code end |
.generate_voucher_code ⇒ Object
366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'app/models/voucher.rb', line 366 def self.generate_voucher_code @codes ||= [] code = nil loop do code = SecureRandom.hex(5).upcase next if @codes.include? code @codes.push code break if Voucher.find_by(voucher_code: code).nil? end code end |
.static_template(template, locale = MyLocaleManager.normalize_locale, package = nil) ⇒ Object
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'app/models/voucher.rb', line 413 def self.static_template(template, locale = MyLocaleManager.normalize_locale, package = nil) return '' if template.blank? return '' unless MyLocaleManager.available_locales.include?(locale.to_sym) template_with_locale = "#{template}_#{locale}" render_template = ActionView::Base.new render_template.view_paths = Rails.root.join('app', 'views', 'vouchers').to_s # Try to render template with requested locale, fallback to English if template doesn't exist begin render_template.render(template: template_with_locale, layout: false, locals: { package: package }).gsub("\n", '') rescue ActionView::MissingTemplate => e # Fallback to English template if the requested locale template doesn't exist fallback_template = "#{template}_en" HH_LOGGER.warn("Voucher template not found for locale #{locale}, falling back to English", { original_template: template_with_locale, fallback_template: fallback_template, error: e. }) render_template.render(template: fallback_template, layout: false, locals: { package: package }).gsub("\n", '') end end |
.valid_referral_code?(voucher_code) ⇒ Boolean
379 380 381 382 383 384 385 |
# File 'app/models/voucher.rb', line 379 def self.valid_referral_code?(voucher_code) return false if voucher_code.blank? # in .generate_referral_code method, we add prefix to the code voucher_code.to_s.start_with?(User::REFERRAL_CODE_PREFIX) || User.match_by_referral_code(voucher_code).present? end |
Instance Method Details
#country_code ⇒ Object
return country code, set default to TH if not set
314 315 316 317 |
# File 'app/models/voucher.rb', line 314 def country_code country = ISO3166::Country.find_country_by_name(fetch_country_name) country&.alpha2 end |
#delivery_fee_and_package? ⇒ Boolean
296 297 298 |
# File 'app/models/voucher.rb', line 296 def delivery_fee_and_package? apply_for == 'package_and_delivery_fee' end |
#fetch_country_name ⇒ Object
319 320 321 322 323 324 |
# File 'app/models/voucher.rb', line 319 def fetch_country_name return country.name if country&.name.present? return city.country.name if city&.country&.name.present? Country.find_thailand.name end |
#fetch_currency_code ⇒ Object
return default THB if not set
309 310 311 |
# File 'app/models/voucher.rb', line 309 def fetch_currency_code currency_code.presence || country&.currency_code || Country::THAI_CURRENCY_CODE end |
#for_all_device? ⇒ Boolean
300 301 302 |
# File 'app/models/voucher.rb', line 300 def for_all_device? for_web? && for_ios? && for_android? end |
#for_delivery_fee? ⇒ Boolean
288 289 290 |
# File 'app/models/voucher.rb', line 288 def for_delivery_fee? apply_for == 'delivery_fee' end |
#for_package? ⇒ Boolean
292 293 294 |
# File 'app/models/voucher.rb', line 292 def for_package? apply_for == 'package' end |
#handle_device_type ⇒ Object
245 246 247 248 249 250 251 |
# File 'app/models/voucher.rb', line 245 def handle_device_type return unless device_type == 'all_device' self.for_ios = true self.for_web = true self.for_android = true end |
#invalid_prefix_number_message ⇒ Object
334 335 336 |
# File 'app/models/voucher.rb', line 334 def prefix_number.to_s.split(',').to_sentence(words_connector: ' or ') end |
#require_cc ⇒ Object
326 327 328 |
# File 'app/models/voucher.rb', line 326 def require_cc payment_types.map(&:name).include? 'creditcard' end |
#require_payment_provider? ⇒ Boolean
330 331 332 |
# File 'app/models/voucher.rb', line 330 def require_payment_provider? payment_types.present? end |
#stakeholder ⇒ Object
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'app/models/voucher.rb', line 338 def stakeholder case voucher_type.to_s.to_sym when :all_restaurants '-' when :specific_restaurants '-' when :specific_branch branch.name when :specific_restaurant "#{restaurant.name} (#{restaurant_id})" when :specific_customer "#{user.name} (#{user.id})" when :specific_restaurant_and_customer "restaurant: #{restaurant.name} (#{restaurant_id}) - user: #{user&.name} (#{user.id})" when :specific_branch_and_customer "branch: #{branch.name} - user: #{user&.name} (#{user.id})" end end |
#subsidized_by_hungryhub? ⇒ Boolean
Checks if the voucher is subsidized by HungryHub.
459 460 461 |
# File 'app/models/voucher.rb', line 459 def subsidized_by_hungryhub? subsidized_by == 'hungryhub' end |
#subsidized_by_restaurant? ⇒ Boolean
Checks if the voucher is subsidized by the restaurant.
467 468 469 |
# File 'app/models/voucher.rb', line 467 def subsidized_by_restaurant? subsidized_by == 'restaurant' end |
#token ⇒ Object
357 358 359 |
# File 'app/models/voucher.rb', line 357 def token CityHash.hash32([id, created_at]).to_s end |
#total_used_amount ⇒ Object
447 448 449 450 451 452 |
# File 'app/models/voucher.rb', line 447 def total_used_amount reservations. joins(:reservation_vouchers). where(reservation_vouchers: { active: true }). sum('reservation_vouchers.amount_cents') end |
#usage ⇒ Object
304 305 306 |
# File 'app/models/voucher.rb', line 304 def usage @usage ||= reservations.where(reservation_vouchers: { active: true }).size end |
#valid_discount_type ⇒ Object
253 254 255 256 257 258 259 |
# File 'app/models/voucher.rb', line 253 def valid_discount_type return true if discount_type == 'amount' return true if one_time? errors.add(:base, 'Cannot set pack/person discount type with deductible') false end |
#valid_mix_with_points ⇒ Object
279 280 281 282 283 284 285 286 |
# File 'app/models/voucher.rb', line 279 def valid_mix_with_points return true if marketing? || redemption? unless mix_with_points.to_s == 'false' errors.add(:base, 'By default, non-marketing category vouchers can be combined with points, cannot disable this config') end end |
#valid_percentage_type ⇒ Object
269 270 271 272 273 274 275 276 277 |
# File 'app/models/voucher.rb', line 269 def valid_percentage_type return true unless percentage? if deductible? errors.add(:base, "Can't combine percentage type with deductible usage type") elsif voucher_category == 'gift' errors.add(:base, 'vouchers with a percentage discount type cannot be made as gift vouchers') end end |
#valid_specific_device_types ⇒ Object
261 262 263 264 265 266 267 |
# File 'app/models/voucher.rb', line 261 def valid_specific_device_types return true if device_type == 'all_device' return true unless !for_ios? && !for_android? && !for_web? errors.add(:base, 'Please select specific device type') false end |
#validate_amount_based_on_discount_type ⇒ Object
227 228 229 230 231 |
# File 'app/models/voucher.rb', line 227 def validate_amount_based_on_discount_type if discount_type == 'amount' && (amount.blank? || amount <= 0) errors.add(:amount, 'must be greater than 0 for amount discount type') end end |
#validate_amount_cap_based_on_discount_type ⇒ Object
233 234 235 236 237 |
# File 'app/models/voucher.rb', line 233 def validate_amount_cap_based_on_discount_type if discount_type.in?(%w[pack person]) && (amount_cap.blank? || amount_cap <= 0) errors.add(:amount_cap, 'must be greater than 0 for pack or person discount type') end end |
#validate_percentage_based_on_discount_type ⇒ Object
239 240 241 242 243 |
# File 'app/models/voucher.rb', line 239 def validate_percentage_based_on_discount_type if discount_type == 'percentage' && (percentage.blank? || percentage <= 0) errors.add(:percentage, 'must be greater than 0 for percentage discount type') end end |