Class: Reservation
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Reservation
- Extended by:
- Enumerize
- Includes:
- BookingReminder::SetupCallback, ElasticAPM::SpanHelpers, ModelExt::CreatedFromWebsite, ModelExt::Reservations::Callbacks, ModelExt::Reservations::ClassMethods, ModelExt::Reservations::CreditCard, ModelExt::Reservations::InstanceMethods, ModelExt::Reservations::PartnerInstanceMethods, ModelExt::Reservations::Relations, Reservations::UpdateMirageCache
- Defined in:
- app/models/reservation.rb
Direct Known Subclasses
Constant Summary collapse
- PERIODS =
%w[00:00 00:15 00:30 00:45 01:00 01:15 01:30 01:45 02:00 02:15 02:30 02:45 03:00 03:15 03:30 03:45 04:00 04:15 04:30 04:45 05:00 05:15 05:30 05:45 06:00 06:15 06:30 06:45 07:00 07:15 07:30 07:45 08:00 08:15 08:30 08:45 09:00 09:15 09:30 09:45 10:00 10:15 10:30 10:45 11:00 11:15 11:30 11:45 12:00 12:15 12:30 12:45 13:00 13:15 13:30 13:45 14:00 14:15 14:30 14:45 15:00 15:15 15:30 15:45 16:00 16:15 16:30 16:45 17:00 17:15 17:30 17:45 18:00 18:15 18:30 18:45 19:00 19:15 19:30 19:45 20:00 20:15 20:30 20:45 21:00 21:15 21:30 21:45 22:00 22:15 22:30 22:45 23:00 23:15 23:30 23:45 24:00].freeze
- PERIODS_WITHOUT_00_AM =
%w[00:00 00:15 00:30 00:45 01:00 01:15 01:30 01:45 02:00 02:15 02:30 02:45 03:00 03:15 03:30 03:45 04:00 04:15 04:30 04:45 05:00 05:15 05:30 05:45 06:00 06:15 06:30 06:45 07:00 07:15 07:30 07:45 08:00 08:15 08:30 08:45 09:00 09:15 09:30 09:45 10:00 10:15 10:30 10:45 11:00 11:15 11:30 11:45 12:00 12:15 12:30 12:45 13:00 13:15 13:30 13:45 14:00 14:15 14:30 14:45 15:00 15:15 15:30 15:45 16:00 16:15 16:30 16:45 17:00 17:15 17:30 17:45 18:00 18:15 18:30 18:45 19:00 19:15 19:30 19:45 20:00 20:15 20:30 20:45 21:00 21:15 21:30 21:45 22:00 22:15 22:30 22:45 23:00 23:15 23:30 23:45].freeze
- LUNCH_PERIODS =
%w[12:00 12:15 12:30 12:45 13:00 13:15 13:30 13:45 14:00 14:15 14:30 14:45 15:00 15:15 15:30 15:45 16:00].freeze
- DINNER_PERIODS =
%w[16:00 16:15 16:30 16:45 17:00 17:15 17:30 17:45 18:00 18:15 18:30 18:45 19:00 19:15 19:30 19:45 20:00 20:15 20:30 20:45 21:00 21:15 21:30 21:45 22:00 22:15 22:30 22:45 23:00 23:15 23:30 23:45].freeze
- RATING_DELAY_TIME =
3.hours
- AMOUNT_REFERRER_REWARD_POINT_TH =
100- AMOUNT_REFERRER_REWARD_POINT_SG =
10- AMOUNT_REFERRER_REWARD_POINT_MY =
15- THANK_YOU_MSG_DELAY =
(2.hours + 30.minutes).freeze
- DEFAULT_END_TIME =
1.hour.freeze
- CREATED_BY =
%I[user admin owner].freeze
Constants included from ModelExt::Reservations::InstanceMethods
ModelExt::Reservations::InstanceMethods::RAddOn, ModelExt::Reservations::InstanceMethods::Rpackage
Class Method Summary collapse
-
.remove_phone_prefix(phone) ⇒ Object
to make phone searchable we need to remove the prefix of the phone.
Instance Method Summary collapse
- #additional_special_request ⇒ Object
- #additional_special_request=(sr = nil) ⇒ Object
- #adult ⇒ Object
- #adult=(amount) ⇒ Object
- #business_booking ⇒ Object
- #business_booking=(val) ⇒ Object
- #cache_key ⇒ Object
- #channel=(c) ⇒ Object
-
#charge_price ⇒ Object
charge_price is overridden by the charge_price_v2 that implements decimal pricing.
-
#charge_price_float ⇒ Object
charge_price_float is used for serializer because we need to return Float, BigDecimal will be render as String in JSON.
-
#charge_price_v1 ⇒ Object
charge_price_v1 is used in the old system that uses integer pricing the charge_price_v1 will be rounded-up to the nearest integer.
- #dummy? ⇒ Boolean
- #email_menu_template ⇒ Object
-
#find_package_by_name(name) ⇒ Hash?
Finds a package by name from the selected packages.
- #kids=(amount) ⇒ Object
- #last_minute? ⇒ Boolean
- #modified? ⇒ Boolean
-
#modified_reservations ⇒ Object
Returns array of all reservations in the modification chain Starting from the current reservation, follows new_reservation_id links Returns reservations ordered from oldest to newest Example: if reservation was modified 10 times, returns 10 reservations - First 9 will have active=false (adjusted=true) - Last one will have active=true (unless it was cancelled).
- #origin_save ⇒ Object
- #origin_save! ⇒ Object
- #reduced_with_promo?(restaurant_package_id) ⇒ Boolean
-
#reservation_packages_with_menus ⇒ Object
this method and reservation_single_package_with_menus method are used to generate the package & menu list for the email template.
- #reservation_single_package_with_menus(formatted_package, packages: []) ⇒ Object
- #save(*args) ⇒ Object
- #save!(*args) ⇒ Object
-
#selected_packages ⇒ Array
Returns the selected packages array from reservation property.
- #selected_promo_package(restaurant_package_id) ⇒ Object
-
#show_delivery_fee?(is_admin:, original_delivery:) ⇒ Boolean
Determines if delivery fee should be displayed based on admin status and package type.
-
#show_hah_delivery_details? ⇒ Boolean
Determines if delivery details should be shown for HAH packages (excluding pickup).
- #special_request_for_staff ⇒ Object (also: #special_request_for_restaurant)
-
#spending_tier_discount ⇒ OpenStruct?
Finds the spending tier discount package as an OpenStruct for easier access.
- #start_time=(time) ⇒ Object
- #to_json(options = {}) ⇒ Object
- #to_url_hash ⇒ Object
-
#total_price ⇒ Object
total_price is overridden by the total_price_v2 that implements decimal pricing.
-
#total_price_float ⇒ Object
total_price_float is used for serializer because we need to return Float, BigDecimal will be render as String in JSON.
-
#total_price_v1 ⇒ Object
total_price_v1 and charge_price are used in the old system that uses integer pricing the total_price_v1 will be rounded-up to the nearest integer.
-
#validate_date? ⇒ Boolean
Custom method for conditional validation.
Methods included from ModelExt::CreatedFromWebsite
Methods included from ModelExt::Reservations::PartnerInstanceMethods
#customer_icon, #delivery_detail, #delivery_status_partner, #dine_in_status_partner, #driver_details, #format_percentage, #note_to_driver, #package_type, #paid_by_admin_status, #paid_status, #partner_status_property, #payment_method, #payment_status, #payment_summary, #self_pickup_status_partner
Methods included from ProgressStatus
#available_delivery_status, #convert_from_grab_as_symbol, #convert_from_lalamove, #convert_from_lalamove_as_symbol, #convert_from_reservation, #owner_delivery_progress
Methods included from ModelExt::Reservations::Callbacks
#push_booking_notification, #push_inventory_notification, #trigger_creation_sync, #trigger_destroy_sync, #trigger_immediate_sync, #trigger_priority_sync, #trigger_summary_sync, #update_inv_relations
Methods included from ModelExt::Reservations::CreditCard
#change_payment_type_provider!, #change_promptpay_provider, #charge_amount, #charged_amount, #charged_amount_in_baht, #charged_by_gb_primepay?, #charged_by_omise?, #could_charge_again?, #paid_amount, #paid_at, #paid_cc?, #paid_charges, #paid_non_cc?, #payment_gateway, #payment_gateway_account, #payment_provider, #payment_type, #payment_type_as_symbol, #payment_type_provider, #payment_type_record, #pending_payment_non_cc?, #prepayment_percent, #require_prepayment?, #using_charge_channel?, #using_on_hold_channel?
Methods included from ModelExt::Reservations::InstanceMethods
#add_on_bought, #add_on_data_for_report, #add_on_obj, #allowed_to_rectify?, #aoa_channel?, #assign_charged_data, #assign_refund_guarantee_data, #by_bistrochat?, #by_corporate_employee?, #by_marketplace?, #by_mymenu?, #by_seven_rooms?, #by_tablecheck?, #by_weeloy?, #call_order_later_driver_at, #call_order_now_driver_at, #can_request_more_bike?, #cancel_preparing!, #cancelled?, #channel_group_name, #channel_name, #channel_uri_name, #corporate_order?, #courier_tracking_link, #created_at_format, #created_by_admin_staff_or_owner?, #date_format, #date_format_ext, #delivery?, #delivery_address_humanize, #delivery_address_link, #delivery_fee_money, #delivery_status, #delivery_type?, #dine_in?, #dine_in_type?, #dont_mark_as_arrived!, #dont_mark_as_no_show!, #eligible_to_get_instant_reward?, #eligible_to_get_reward?, #email, #end_time=, #end_time_format, #firebase_tracking_key, #food_ready_by, #formatted_menus, #full_datetime, #generate_tag_list, #get_driving_duration, #google_reserve_channel?, #has_cancelled_with_refund?, #inventory_class, #inventory_klass, #inventory_reservation_klass, #inventory_times, #is_past?, #mark_as_arrived!, #mark_as_canceled!, #mark_as_for_locking_system!, #mark_as_invalid_temporary_booking!, #mark_as_no_show!, #mark_as_prepared!, #mark_as_valid_reservation!, #mark_voucher_as_active!, #mark_voucher_as_inactive!, #name, #need_request_choice?, #new_formatted_menus, #no_show?, #omise_payment_gateway?, #openrice_channel?, #original_delivery_fee_money, #owner_phone, #package_bought, #package_data_for_report, #package_obj, #package_price_currency, #party_size_changed?, #payment_failed_url, #payment_success_url, #pending?, #phone, #phone=, #phone_intl, #private_channel?, #qr_code_for_payment, #qr_code_for_payment_expired_at, #reached_goal?, #ready_to_cook?, #redeemed_points, #refund_fee_amount_float, #refundable_amount_float, #rejected?, #reservation_time, #reservation_time_24h_passed?, #revenue_amount, #review, #self_pickup?, #service_type_humanize, #shopee_pay_url, #skip_sending_netcore_event?, #special_request_without_package_menus, #start_time_and_date_changed?, #start_time_format, #status, #status_as_symbol, #status_changed?, #status_for_owner, #tagthai_channel?, #temporary_lock?, #true_wallet_for_payment_expired_at, #true_wallet_url, #upcoming?, #use_third_party_reservation?, #username, #valid_to_cancel_temporary_booking?, #voucher_codes_humanize, #voucher_names_humanize, #vouchers_amount, #web_url, #web_v2_host_vendor
Methods included from RefundGuaranteeHelper
#calculate_min_refund_hours, #calculate_refundable_until_time
Methods inherited from ApplicationRecord
Class Method Details
.remove_phone_prefix(phone) ⇒ Object
to make phone searchable we need to remove the prefix of the phone
530 531 532 533 534 535 536 537 538 539 |
# File 'app/models/reservation.rb', line 530 def self.remove_phone_prefix(phone) case phone when /^668/ # If the phone starts with '668' phone.gsub(/^66/, '') # Remove '66' when /^6608/ # If the phone starts with '6608' phone.gsub(/^660/, '') # Remove '660' else phone # Return the phone unchanged if no prefix match end end |
Instance Method Details
#additional_special_request ⇒ Object
418 419 420 |
# File 'app/models/reservation.rb', line 418 def additional_special_request special_request end |
#additional_special_request=(sr = nil) ⇒ Object
414 415 416 |
# File 'app/models/reservation.rb', line 414 def additional_special_request=(sr = nil) self.special_request = "#{special_request} (#{sr})" if sr.present? end |
#adult ⇒ Object
404 405 406 407 408 409 410 411 412 |
# File 'app/models/reservation.rb', line 404 def adult if self[:adult].to_i.positive? self[:adult] elsif !self[:adult].to_i.positive? && self[:kids].to_i.positive? 0 else party_size end end |
#adult=(amount) ⇒ Object
399 400 401 402 |
# File 'app/models/reservation.rb', line 399 def adult=(amount) self.party_size = amount.to_i + kids.to_i self[:adult] = amount.to_i end |
#business_booking ⇒ Object
271 272 273 274 275 |
# File 'app/models/reservation.rb', line 271 def business_booking return property.business_booking if property.present? nil end |
#business_booking=(val) ⇒ Object
277 278 279 280 |
# File 'app/models/reservation.rb', line 277 def business_booking=(val) build_property if property.nil? property.business_booking = val end |
#cache_key ⇒ Object
282 283 284 |
# File 'app/models/reservation.rb', line 282 def cache_key "#{super}|#{I18n.locale}|" end |
#channel=(c) ⇒ Object
386 387 388 389 390 391 392 |
# File 'app/models/reservation.rb', line 386 def channel=(c) if c.is_a?(Channel) self[:channel] = c.channel_id else super end end |
#charge_price ⇒ Object
charge_price is overridden by the charge_price_v2 that implements decimal pricing
239 240 241 242 243 |
# File 'app/models/reservation.rb', line 239 def charge_price return 0 if charge_price_v2.blank? charge_price_v2.amount end |
#charge_price_float ⇒ Object
charge_price_float is used for serializer because we need to return Float, BigDecimal will be render as String in JSON
266 267 268 269 |
# File 'app/models/reservation.rb', line 266 def charge_price_float price = charge_price price % 1 == 0 ? price.to_i : price.to_f.round(2) end |
#charge_price_v1 ⇒ Object
charge_price_v1 is used in the old system that uses integer pricing the charge_price_v1 will be rounded-up to the nearest integer
253 254 255 |
# File 'app/models/reservation.rb', line 253 def charge_price_v1 charge_price.ceil end |
#dummy? ⇒ Boolean
286 287 288 |
# File 'app/models/reservation.rb', line 286 def dummy? email == 'dummy@hungryhub.com' end |
#email_menu_template ⇒ Object
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'app/models/reservation.rb', line 427 def reservation = self reservation.package_obj&.formatted_packages&.map do |p| = [] = reservation..select do |section, | package = section.&.package&.presence || HhPackage::Package::Ayce.new restaurant_package = HhPackage::RestaurantPackage.fetch(p['restaurant_package_id']) package == restaurant_package.package end .each do |section, | = section..group(:package_menu_id) .each do || << { name: ..name, quantity: .subsections.present? ? box_quantity : .quantity, } end end p.slice(:name, :amount, :kids_amount, :net_price, :quantity, :total_kids_price, :total_adult_price).merge(menus: ) end end |
#find_package_by_name(name) ⇒ Hash?
Finds a package by name from the selected packages
507 508 509 510 511 512 513 514 |
# File 'app/models/reservation.rb', line 507 def find_package_by_name(name) return nil if property.blank? || property.selected_packages.blank? selected_packages = parse_selected_packages return nil if selected_packages.blank? selected_packages.detect { |package| package['name'] == name } end |
#kids=(amount) ⇒ Object
394 395 396 397 |
# File 'app/models/reservation.rb', line 394 def kids=(amount) self.party_size = amount.to_i + adult.to_i if adult.to_i.positive? self[:kids] = amount.to_i end |
#last_minute? ⇒ Boolean
322 323 324 |
# File 'app/models/reservation.rb', line 322 def last_minute? self&.property&.last_minute == true end |
#modified? ⇒ Boolean
326 327 328 329 330 331 332 333 334 |
# File 'app/models/reservation.rb', line 326 def modified? if changed? return false if changes.include?('special_request') && special_request.empty? return true end false end |
#modified_reservations ⇒ Object
Returns array of all reservations in the modification chain Starting from the current reservation, follows new_reservation_id links Returns reservations ordered from oldest to newest Example: if reservation was modified 10 times, returns 10 reservations
-
First 9 will have active=false (adjusted=true)
-
Last one will have active=true (unless it was cancelled)
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'app/models/reservation.rb', line 342 def modified_reservations reservations = [] current = self max_iterations = 50 iterations = 0 visited_ids = Set.new([id]) # Traverse forward through the modification chain while current.present? && iterations < max_iterations reservations << current break if current.new_reservation_id.blank? break if visited_ids.include?(current.new_reservation_id) next_reservation = Reservation.find_by(id: current.new_reservation_id) break if next_reservation.nil? visited_ids.add(next_reservation.id) current = next_reservation iterations += 1 end # Log warning if we hit the iteration limit if iterations >= max_iterations BUSINESS_LOGGER.set_business_context( original_reservation_id: id, last_checked_reservation_id: current&.id, warning: 'max_iterations_reached_in_modified_reservations', ) BUSINESS_LOGGER.warn('Reservation modification chain traversal reached maximum iterations limit') end reservations end |
#origin_save ⇒ Object
208 |
# File 'app/models/reservation.rb', line 208 alias origin_save save |
#origin_save! ⇒ Object
209 |
# File 'app/models/reservation.rb', line 209 alias origin_save! save! |
#reduced_with_promo?(restaurant_package_id) ⇒ Boolean
488 489 490 491 492 493 494 |
# File 'app/models/reservation.rb', line 488 def reduced_with_promo?(restaurant_package_id) return false if selected_packages.blank? selected_packages.detect do |p| p['type'] == 'promo' && p['restaurant_package_id'] == restaurant_package_id end.present? end |
#reservation_packages_with_menus ⇒ Object
this method and reservation_single_package_with_menus method are used to generate the package & menu list for the email template
452 453 454 455 456 |
# File 'app/models/reservation.rb', line 452 def package_obj&.formatted_packages&.map do |formatted_package| (formatted_package, packages: package['package_data']) end end |
#reservation_single_package_with_menus(formatted_package, packages: []) ⇒ Object
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'app/models/reservation.rb', line 459 def (formatted_package, packages: []) restaurant_package_id = formatted_package['restaurant_package_id'] package = HhPackage::RestaurantPackage.fetch(restaurant_package_id).package result = formatted_package.slice( :name, :amount, :kids_amount, :net_price, :quantity, :total_kids_price, :total_adult_price, :restaurant_package_id ) price_finder = HhPackage::ReservationPackages::PriceFinder.new(use_dynamic_pricing: booking_channel.support_dynamic_pricing, time_zone: restaurant.time_zone) # we need to set default packages to prevent crash in Mix N Match code packages = packages || self.package['package_data'] kids_price_v2 = price_finder.find_kids_price(package, adult, date, packages: packages) use_kids_price = price_finder.use_kids_price?(package) result.merge!( kids_price_rate: 0, # TODO: remove this kids_price_v2: HhMoney.new(kids_price_v2[:price].cents, package_obj.currency).default_format, use_kids_price: use_kids_price, ) if reduced_with_promo?(restaurant_package_id) promo_package = selected_promo_package(restaurant_package_id) original_total_adult_price = result['total_adult_price'] + promo_package['price'].abs result['total_adult_price'] = original_total_adult_price end result end |
#save(*args) ⇒ Object
211 212 213 214 215 216 217 218 219 |
# File 'app/models/reservation.rb', line 211 def save(*args) if args.first == :sneaky perform_sneaky_operation else origin_save(*args) end rescue StandardError => e handle_error(e) end |
#save!(*args) ⇒ Object
221 222 223 224 225 226 227 228 229 |
# File 'app/models/reservation.rb', line 221 def save!(*args) if args.first == :sneaky perform_sneaky_operation(bang: true) else origin_save!(*args) end rescue StandardError, SystemStackError => e handle_error(e) end |
#selected_packages ⇒ Array
Returns the selected packages array from reservation property
525 526 527 |
# File 'app/models/reservation.rb', line 525 def selected_packages parse_selected_packages end |
#selected_promo_package(restaurant_package_id) ⇒ Object
496 497 498 499 500 501 502 |
# File 'app/models/reservation.rb', line 496 def selected_promo_package(restaurant_package_id) return {} if selected_packages.blank? (selected_packages.detect do |p| p['type'] == 'promo' && p['restaurant_package_id'] == restaurant_package_id end.presence || {}).with_indifferent_access end |
#show_delivery_fee?(is_admin:, original_delivery:) ⇒ Boolean
Determines if delivery fee should be displayed based on admin status and package type
303 304 305 306 307 308 309 310 |
# File 'app/models/reservation.rb', line 303 def show_delivery_fee?(is_admin:, original_delivery:) return false unless original_delivery.present? && original_delivery > 0 return true if is_admin package.present? && package[:package_type].present? && package[:package_type] == 'hah' end |
#show_hah_delivery_details? ⇒ Boolean
Determines if delivery details should be shown for HAH packages (excluding pickup)
314 315 316 317 318 319 320 |
# File 'app/models/reservation.rb', line 314 def show_hah_delivery_details? package.present? && package[:package_type].present? && package[:package_type] == 'hah' && service_type.present? && service_type.to_sym != :pick_up end |
#special_request_for_staff ⇒ Object Also known as: special_request_for_restaurant
290 291 292 293 294 295 |
# File 'app/models/reservation.rb', line 290 def special_request_for_staff return special_request if charges.blank? return special_request if charges.success_scope.blank? "#{payment_type} #{charged_amount_in_baht} - #{special_request}" end |
#spending_tier_discount ⇒ OpenStruct?
Finds the spending tier discount package as an OpenStruct for easier access
518 519 520 521 |
# File 'app/models/reservation.rb', line 518 def spending_tier_discount discount = find_package_by_name('Spending Tier Discount') discount ? OpenStruct.new(discount) : nil end |
#start_time=(time) ⇒ Object
422 423 424 425 |
# File 'app/models/reservation.rb', line 422 def start_time=(time) self[:start_time] = time set_end_time end |
#to_json(options = {}) ⇒ Object
382 383 384 |
# File 'app/models/reservation.rb', line 382 def to_json( = {}) super(.merge(methods: [[:methods], :username].flatten.compact)) end |
#to_url_hash ⇒ Object
378 379 380 |
# File 'app/models/reservation.rb', line 378 def to_url_hash HungryHub::Reservation::Codec.encode(id) end |
#total_price ⇒ Object
total_price is overridden by the total_price_v2 that implements decimal pricing
232 233 234 235 236 |
# File 'app/models/reservation.rb', line 232 def total_price return 0 if total_price_v2.blank? total_price_v2.amount end |
#total_price_float ⇒ Object
total_price_float is used for serializer because we need to return Float, BigDecimal will be render as String in JSON
259 260 261 262 |
# File 'app/models/reservation.rb', line 259 def total_price_float price = total_price price % 1 == 0 ? price.to_i : price.to_f.round(2) end |
#total_price_v1 ⇒ Object
total_price_v1 and charge_price are used in the old system that uses integer pricing the total_price_v1 will be rounded-up to the nearest integer
247 248 249 |
# File 'app/models/reservation.rb', line 247 def total_price_v1 total_price.ceil end |
#validate_date? ⇒ Boolean
Custom method for conditional validation
542 543 544 |
# File 'app/models/reservation.rb', line 542 def validate_date? new_record? || date_changed? end |