Class: Restaurant

Overview

This class has many reservations, and has reservations_count attribute but we don't use Rails `counter_cache` feature to increment/decrement reservations_count data the data is generated every night instead attributes:

custom_text:
  is a field to add short text that will be displayed on home page
  CompactRestaurant will merge all Restaurant's custom_text data into this field
call_driver_time_limit_duration:
  system will use global level if this is 0. This is a how long system will wait from the first driver call

Constant Summary collapse

AVAILABILITY_PREORDER =

Constants

'preorder'
AVAILABILITY_IN_STOCK =
'in stock'
AVAILABILITY_OUT_OF_STOCK =
'out of stock'

Instance Attribute Summary collapse

Attributes included from ModelExt::Restaurants::CreditCard

#cc_hold_amount, #cc_immediate_charge_amount

Instance Method Summary collapse

Methods included from HungryHub::DynamicTime

#time_zone

Methods included from ModelExt::Restaurants::PrimaryTags

#primary_tag_by_category

Methods included from ModelExt::Restaurants::ImenuPro

#imenu_pro_link, #imenu_pro_link=

Methods included from ModelExt::Restaurants::CreditCard

#adult_cc_hold_amount=, #adult_cc_immediate_charge_amount=, #assert_charge_immediately?, #assert_on_hold_charge?, #kids_cc_hold_amount=, #kids_cc_immediate_charge_amount=, #party_require_credit_card?, #require_credit_card?

Methods included from ModelExt::Restaurants::Relations

#award_badge_tags, #award_type_tags, #bts_route_tags, #cuisine_tags, #dining_style_tags, #facility_tags, #fetch_restaurant_tags, #hashtag_tags, #location_tags, #mrt_route_tags, #popular_zone_tags, #shopping_mall_tags

Methods included from ModelExt::Restaurants::FriendlyIdSetup

#city_country_name, #city_name, #city_name_changed?, #name_en_with_city, #slug_candidates, #slug_includes_city_name?

Methods included from ModelExt::Restaurants::Callbacks

#bump_inventory_checker_cache_versions, #create_or_update_restaurant_tags, #refresh_inv_checker, #refresh_view_cache_key

Methods included from ModelExt::Restaurants::InstanceMethods

#active_add_ons_with_auto_extend, #active_and_not_expired?, #active_packages_with_auto_extend, #active_staff_emails, #availability_status, #bookable_and_not_expired?, #default_max_dine_in_booking_cutoff_time, #default_min_booking_time, #default_min_booking_time_admin_level_for_delivery, #default_min_booking_time_admin_level_for_dine_in, #default_provider_for, #determine_max_availability_date, #determine_min_booking_time, #eligible_for_rewards?, #inv_cache_key, #inventory_bistrochat?, #inventory_my_menu?, #inventory_seven_rooms?, #inventory_tablecheck?, #inventory_weeloy?, #override_delivery_min_booking?, #override_dine_in_min_booking?, #provider_for, #selected_alipay_payment_provider, #selected_cc_payment_provider, #selected_payment_provider, #selected_promptpay_payment_provider, #selected_wechat_pay_payment_provider, #today_in_tz, #view_cache_key, #view_cache_key_redis_key

Methods included from SoftDelete

#soft_deleted?, #soft_destroy

Methods inherited from ApplicationRecord

sync_carrierwave_url

Instance Attribute Details

#earliest_timeObject



355
356
357
358
359
360
# File 'app/models/restaurant.rb', line 355

def earliest_time
  inv = inventories.by_date.first
  return inv.start_time.strftime('%H:%M') if inv.present?

  'No inventories today'
end

#image_count=(value) ⇒ Object (writeonly)

Sets the attribute image_count

Parameters:

  • value

    the value to set the attribute image_count to.



242
243
244
# File 'app/models/restaurant.rb', line 242

def image_count=(value)
  @image_count = value
end

#images=(value) ⇒ Object (writeonly)

Sets the attribute images

Parameters:

  • value

    the value to set the attribute images to.



242
243
244
# File 'app/models/restaurant.rb', line 242

def images=(value)
  @images = value
end

#latFloat

Returns (latitude) When this value has been updated/created, system run Tagging::RestaurantBasedOnGeoWorker to update all secondary tags.

Returns:



# File 'app/models/restaurant.rb', line 201

#latest_timeObject



362
363
364
365
366
367
368
369
370
# File 'app/models/restaurant.rb', line 362

def latest_time
  inv = inventories.by_date.last
  if inv.present?
    time = inv.end_time - res_duration.minutes
    return time.strftime('%H:%M')
  end

  'No inventories today'
end

#lngFloat

Returns (longitude) When this value has been updated/created, system run Tagging::RestaurantBasedOnGeoWorker to update all secondary tags.

Returns:



209
# File 'app/models/restaurant.rb', line 209

audited

Sets the attribute menu_images

Parameters:

  • value

    the value to set the attribute menu_images to.



242
243
244
# File 'app/models/restaurant.rb', line 242

def menu_images=(value)
  @menu_images = value
end

Instance Method Details

#any_voucher_offers?Boolean

Returns:

  • (Boolean)


566
567
568
569
# File 'app/models/restaurant.rb', line 566

def any_voucher_offers?
  date = Time.now_in_tz(time_zone).to_date
  voucher_offer_date.present? && voucher_offer_date >= date
end

#avg_reviewFloat

get average from review stat

Returns:

  • (Float)


730
731
732
733
734
# File 'app/models/restaurant.rb', line 730

def avg_review
  key = branch_id? ? :branch_id : :restaurant_id
  review_stat = Restaurants::ReviewStat.find_by(key => branch_id || id)
  review_stat&.average
end

#come_pay_label(get_comemore_payless) ⇒ Object



716
717
718
719
720
721
722
723
724
725
# File 'app/models/restaurant.rb', line 716

def come_pay_label(get_comemore_payless)
  package_type = get_comemore_payless[:package_type].to_s

  # Use safe_constantize to avoid crashing if the class is missing
  klass = package_type.safe_constantize
  promo_type = klass ? klass::TYPE_SHORT : ''
  I18n.t("views.package.promo.come_pay_less_dynamic.#{promo_type}",
         group_size: get_comemore_payless[:group_size],
         group_size_to_pay: get_comemore_payless[:group_size_to_pay], locale: :en)
end

#create_inv_checkersObject

if is_dine_in only, then returns one instance only if is_take_away only, then returns one instance only if both of attributes are true, then returns two instances



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'app/models/restaurant.rb', line 288

def create_inv_checkers
  instances = []

  if is_dine_in
    instances << create_checker_instance(for_dine_in: true, for_delivery: false)
  end

  if is_take_away
    instances << create_checker_instance(for_dine_in: false, for_delivery: true)
  end

  if instances.empty?
    # default to dine in
    instances << create_checker_instance(for_dine_in: true, for_delivery: false)
  end

  instances
end

#date_offer_expired?(date) ⇒ Boolean

Parameters:

  • date (Date)

Returns:

  • (Boolean)


444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'app/models/restaurant.rb', line 444

def date_offer_expired?(date)
  return true if date.blank?

  d = date.is_a?(::Date) || date.is_a?(::Time) ? date : Time.now_in_tz(time_zone).to_date

  return true if expiry_date.blank? && voucher_offer_date.blank?
  return false if any_voucher_offers?
  return false if expiry_date >= d

  true
rescue StandardError
  HH_LOGGER.error('restaurant has invalid expiry date', id: id)
  true
end

#default_currencyObject



532
533
534
# File 'app/models/restaurant.rb', line 532

def default_currency
  @default_currency ||= country.currency_code_upcase
end

#get_best_promotion(package) ⇒ Object

Determine the best promotion for a package based on the promotion_badge_percentage and comemore_payless_percentage



628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
# File 'app/models/restaurant.rb', line 628

def get_best_promotion(package)
  promotion_badge_percentage = get_promotion_badge_percentage(package)&.to_i
  get_comemore_payless = get_comemore_payless_percentage(package)

  comemore_payless_percentage = get_comemore_payless&.dig(:discount_percentage)&.to_i || 0

  # Determine the highest discount percentage
  percentage_discount = [promotion_badge_percentage, comemore_payless_percentage].compact.max.to_i

  # Determine the promotion type
  promotion_type = get_promotion_type(percentage_discount, promotion_badge_percentage, comemore_payless_percentage)

  return if percentage_discount.zero? || promotion_type.nil?

  {
    percentage_discount: percentage_discount,
    promotion_type: promotion_type,
    promotion_text: promotion_text(promotion_type, percentage_discount, get_comemore_payless),
  }
end

#get_comemore_payless_percentage(package) ⇒ Object



670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
# File 'app/models/restaurant.rb', line 670

def get_comemore_payless_percentage(package)
  current_time = Time.now_in_tz(time_zone)
  today = current_time.to_date

  pricing_groups = package&.pricing_groups
  active_pricing_groups = pricing_groups&.where('end_date >= ?', today)
  empty_date_pricing_groups = pricing_groups&.where('end_date IS NULL')

  comemore_payless = if active_pricing_groups.present?
                       active_pricing_groups
                     elsif package.dynamic_price_comemore_payless? && empty_date_pricing_groups.present?
                       empty_date_pricing_groups
                     end

  discount_percentage = comemore_payless&.select("group_size, group_size_to_pay,
                          ((group_size - group_size_to_pay) * 100 / CAST(group_size AS FLOAT)) AS discount_percentage")&.
    order(Arel.sql('discount_percentage DESC'))&.limit(1)&.first

  return { group_size: nil, group_size_to_pay: nil, discount_percentage: 0 } if discount_percentage.blank?

  {
    group_size: discount_percentage.group_size,
    group_size_to_pay: discount_percentage.group_size_to_pay,
    discount_percentage: discount_percentage.discount_percentage,
    package_type: package.package_attr.package_type,
  }
end

#get_hashed_mobile_images(options = { v2_api: false, }) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'app/models/restaurant.rb', line 329

def get_hashed_mobile_images(options = {
  v2_api: false,
})
  Rails.cache.fetch("#{cache_key}|hashed_mobile_images|#{options}", expires_in: CACHEFLOW.generate_expiry) do
    im_h = {}
    files = Dir.glob("#{Rails.root}/public/#{id}-*-mobile.jpg")

    files.sort! do |x, y|
      file_a = x.match("#{id}-(\\d*)-mobile.jpg")[1].to_i
      file_b = y.match("#{id}-(\\d*)-mobile.jpg")[1].to_i
      file_a <=> file_b
    end

    files.each_with_index do |p, ind|
      im_h[ind.to_s] = p[(p.rindex(%r{/}) + 1)..]
    end

    im_h['mobile_images'] = files.count unless options.delete(:v2_api)
    im_h
  end
end

#get_hashed_mobile_images_threeObject



351
352
353
# File 'app/models/restaurant.rb', line 351

def get_hashed_mobile_images_three
  Dir.glob("#{Rails.root}/public/#{id}-*-mobile.jpg").count
end

#get_parking_idObject



435
436
437
438
439
440
# File 'app/models/restaurant.rb', line 435

def get_parking_id
  tag = restaurant_tags.where("title_#{MyLocaleManager.normalize_locale} LIKE ?", 'Parking:%').first
  return tag.id if tag.present?

  RestaurantTag.where('title_en LIKE ?', 'Parking:N%').first.try(:id)
end

#get_promotion_badge_percentage(package) ⇒ Object



649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
# File 'app/models/restaurant.rb', line 649

def get_promotion_badge_percentage(package)
  package_attr = package.package_attr
  dynamic_pricing_type = package_attr&.dynamic_pricing_type
  dynamic_pricings = package.pricings.where(dynamic_pricing_type: dynamic_pricing_type)

  case dynamic_pricing_type.to_sym
  when :normal
    package_attr&.promotion_badge_percentage
  else
    current_time = Time.now_in_tz(time_zone)

    query_dynamic_pricings = dynamic_pricings.where.not(promotion_badge_percentage: nil)
    active_or_empty_date = query_dynamic_pricings.where('end_date >= ?', current_time.to_date).
      or(query_dynamic_pricings.where(end_date: nil))

    active_or_empty_date.order(promotion_badge_percentage: :desc).limit(1).
      pluck(:promotion_badge_percentage).first

  end
end

#get_promotion_type(percentage_discount, promotion_badge_percentage, comemore_payless_percentage) ⇒ Object

Determine the promotion type based on the presence of promotion_badge_percentage or comemore_payless_percentage



699
700
701
702
703
704
705
# File 'app/models/restaurant.rb', line 699

def get_promotion_type(percentage_discount, promotion_badge_percentage, comemore_payless_percentage)
  if percentage_discount == promotion_badge_percentage
    'percentage_discount'
  elsif percentage_discount == comemore_payless_percentage
    'comemore_payless'
  end
end

#get_text_tags_by_group(group) ⇒ Object



323
324
325
326
327
# File 'app/models/restaurant.rb', line 323

def get_text_tags_by_group(group)
  restaurant_tags.where("title_#{MyLocaleManager.normalize_locale} LIKE ?", "#{group}:%").map do |t|
    t.title.gsub(/.*:/, '')
  end
end

#has_delivery_inventories?Boolean

Returns:

  • (Boolean)


554
555
556
557
558
559
560
561
562
563
564
# File 'app/models/restaurant.rb', line 554

def has_delivery_inventories?
  return true if mon_take_away.presence ||
    tue_take_away.presence ||
    wed_take_away.presence ||
    thu_take_away.presence ||
    fri_take_away.presence ||
    sat_take_away.presence ||
    sun_take_away.presence

  false
end

#has_no_offers?Boolean

Returns:

  • (Boolean)


459
460
461
# File 'app/models/restaurant.rb', line 459

def has_no_offers?
  !any_offers && !any_voucher_offers?
end

#has_self_delivery?Boolean

Returns:

  • (Boolean)


536
537
538
539
540
541
542
# File 'app/models/restaurant.rb', line 536

def has_self_delivery?
  @has_self_delivery ||= begin
    self_delivery = delivery_channels.where(is_self_delivery: true)
    self_delivery.count.positive? ? true : false
  end
  @has_self_delivery == true
end

#inv_checker_service_classInventory::InvCheckerHungryHubService, ...

Return one of Inventory::InvCheckerBase subclasses



309
310
311
# File 'app/models/restaurant.rb', line 309

def inv_checker_service_class
  InvCheckerFactory.new(id, time_zone).inv_checker_service_class
end

#is_blocked?(options = {}) ⇒ Boolean

options = { day: :future or :today, date: custom date}

Returns:

  • (Boolean)


464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'app/models/restaurant.rb', line 464

def is_blocked?(options = {})
  at = options.delete(:at)
  date = options.delete(:date) || Date.current_date
  inv = case at.to_s
        when 'future'
          inventories.where('date >= ?', date)
        when 'today'
          inventories.where('date = ?', date)
        else
          inventories.where('date >= ?', date)
        end

  inv.where(quantity_available: 0).count.positive?
end

#it_quantitiesObject



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'app/models/restaurant.rb', line 385

def it_quantities
  @quantity = []
  4.times do |i|
    @quantity[i] = []
  end

  inventory_template_groups.each do |itg|
    itg.inventory_templates.each do |inv|
      m1 = inv.start_time.strftime('%M').to_i
      m1.zero? ? 0 : m1 /= 15
      m2 = inv.end_time.strftime('%M').to_i
      m2.zero? ? 0 : m2 /= 15
      h1 = inv.start_time.strftime('%H').to_i
      h2 = inv.end_time.strftime('%H').to_i

      # works only for 15 min inventory_templates
      if (h1 == h2) && (m2 - m1).positive?
        (m2 - m1).times do |t|
          @quantity[m1 + t][h1] = inv.quantity_available
        end
      elsif h1 != h2

        (h2 - h1 + 1).times do |th|
          if th == h2 - h1

            (m2 - m1).times do |tm|
              @quantity[m1 + tm][h1 + th] = inv.quantity_available
            end

          else

            4.times do |tm|
              @quantity[tm - m1][h1 + th] = inv.quantity_available
            end
          end
        end
      end
    end
  end

  @quantity
end

#location_formatObject



428
429
430
431
432
433
# File 'app/models/restaurant.rb', line 428

def location_format
  tag = restaurant_tags.where("title_#{MyLocaleManager.normalize_locale} LIKE ?", 'Location:%').first
  return tag.title.slice(9..tag.title.length) if tag.present?

  'None'
end

#name(lang = MyLocaleManager.normalize_locale, for_api: false) ⇒ Object



589
590
591
592
593
# File 'app/models/restaurant.rb', line 589

def name(lang = MyLocaleManager.normalize_locale, for_api: false)
  return send("name_#{lang}") unless for_api

  send("name_#{lang}").presence || send("name_#{MyLocaleManager.default_locale}")
end

#old_link=(link) ⇒ Object



495
496
497
498
499
500
501
502
503
# File 'app/models/restaurant.rb', line 495

def old_link=(link)
  link = link.to_s.split('ayce/hh/').last
  if link.blank?
    errors.add(:base, 'Invalid wordpress restaurant link')
    return
  end

  write_attribute :old_link, link
end

#opening_hoursObject



372
373
374
375
376
377
378
379
380
381
382
383
# File 'app/models/restaurant.rb', line 372

def opening_hours
  return nil if id.nil?

  oh = self[:opening_hours]
  return oh if oh.present?
  return nil if misc.nil?

  is_any = misc.split(/opening hours:?/i)
  is_any.length > 1 ? "Opening Hours: #{is_any.last}" : nil
rescue StandardError
  nil
end

#package_soldsObject



313
314
315
316
# File 'app/models/restaurant.rb', line 313

def package_solds
  rps = RestaurantPackageSold.new(id)
  rps.data
end

#parkingObject



507
508
509
510
511
512
513
514
# File 'app/models/restaurant.rb', line 507

def parking
  parking_tag = Rails.cache.fetch("#{cache_key}|has_parking?", expires_in: CACHEFLOW.generate_expiry) do
    parking_tag = ::RestaurantTag.find_by(title_en: 'Facility:Have Parking')
    restaurant_tags.include?(parking_tag)
  end

  parking_tag == true
end

#parking=(val) ⇒ Object



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'app/models/restaurant.rb', line 516

def parking=(val)
  parking_tag = ::RestaurantTag.find_by(title_en: 'Facility:Have Parking')
  if val.to_i.zero?
    if parking_tag.present?
      restaurant_tags.delete(parking_tag)
      touch if persisted?
      save
    end

  elsif parking_tag.present? && !restaurant_tag_ids.include?(parking_tag.id)
    restaurant_tags << parking_tag
    touch if persisted?
    save
  end
end

#party_size_rangeObject



491
492
493
# File 'app/models/restaurant.rb', line 491

def party_size_range
  (min_party_size..largest_table).to_a
end

#promotion_badge(with_percentage: false) ⇒ Object

get highest promotion discount percentage for a restaurant



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'app/models/restaurant.rb', line 605

def promotion_badge(with_percentage: false)
  packages = restaurant_packages.exclude_preview.includes(:package).map(&:package)
  promotion = packages.reject { |package| package.respond_to?(:is_add_on) && package.is_add_on }.
    map { |package| get_best_promotion(package) }.
    reject(&:nil?).
    max_by { |x| x[:percentage_discount] }

  unless promotion
    result = { promotion_type: nil, promotion_text: nil }
    result[:percentage_discount] = 0 if with_percentage
    return result
  end

  result = {
    promotion_type: promotion[:promotion_type],
    promotion_text: promotion[:promotion_text],
  }

  result[:percentage_discount] = promotion[:percentage_discount] if with_percentage
  result
end

#promotion_text(promotion_type, percentage_discount, get_comemore_payless) ⇒ Object

Determine the promotion text based on the presence of promotion_badge_percentage or comemore_payless_percentage



708
709
710
711
712
713
714
# File 'app/models/restaurant.rb', line 708

def promotion_text(promotion_type, percentage_discount, get_comemore_payless)
  if promotion_type == 'percentage_discount'
    "#{percentage_discount}% OFF"
  else
    come_pay_label(get_comemore_payless)
  end
end

#request_choice_optionsObject



479
480
481
482
483
484
485
486
487
488
489
# File 'app/models/restaurant.rb', line 479

def request_choice_options
  return [] if request_choice.blank?

  options = []
  request_choice.split(/(,|\|)/).map(&:strip).select do |o|
    o != ',' && o != '|'
  end.each do |o|
    options.push(o)
  end
  options
end

#res_duration=(time) ⇒ Object



318
319
320
321
# File 'app/models/restaurant.rb', line 318

def res_duration=(time)
  time = time / 60 if time.is_a?(ActiveSupport::Duration)
  self[:res_duration] = time
end

#selected_inventory_modelObject



600
601
602
# File 'app/models/restaurant.rb', line 600

def selected_inventory_model
  InventoryWrapper.new(restaurant_id: id).inv_model
end

#self_delivery_channelObject



544
545
546
547
548
# File 'app/models/restaurant.rb', line 544

def self_delivery_channel
  return nil unless has_self_delivery?

  delivery_channels.find_by!(is_self_delivery: true)
end

#set_default_minimum_seat_allotmentObject



277
278
279
# File 'app/models/restaurant.rb', line 277

def set_default_minimum_seat_allotment
  self.minimum_seat_allotment = 0
end

#short_name_smsObject



550
551
552
# File 'app/models/restaurant.rb', line 550

def short_name_sms
  short_name.presence || name_en.presence || name_th
end

#sum_party_sizeInteger

sum of reservations party size

Returns:

  • (Integer)


237
238
239
# File 'app/models/restaurant.rb', line 237

define_counter_cache :sum_party_size do |_|
  _.reservations.reached_goal_scope.sum(:party_size)
end

#to_paramObject



281
282
283
# File 'app/models/restaurant.rb', line 281

def to_param
  id
end

#use_third_party_inventory?Boolean

Returns:

  • (Boolean)


595
596
597
598
# File 'app/models/restaurant.rb', line 595

def use_third_party_inventory?
  inv_third_party = InventoryWrapper.new(restaurant_id: id).inv_model
  !['Inventory', 'InventoryTakeAway'].include?(inv_third_party.to_s)
end

#validate_commission_for_packages(commision) ⇒ Object



571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# File 'app/models/restaurant.rb', line 571

def validate_commission_for_packages(commision)
  unique_commissions = restaurant_packages.map { |rp| rp.package.commision }.uniq
  tolerance = 0.0001
  if unique_commissions.count == 1
    if commision.blank?
      true
    elsif commision.present? && (unique_commissions.last - commision.to_f).abs > tolerance
      errors.add(:commision,
                 'error. Account level commission and package level commission must be the same or else Account level should be blank')
    end
  elsif unique_commissions.count > 1 && commision.blank?
    true
  elsif unique_commissions.count > 1 && commision.present?
    errors.add(:commision,
               'error. Account level commission and package level commission must be the same or else Account level should be blank')
  end
end