Class: Api::Partner::V1::ReservationsController

Inherits:
BaseController
  • Object
show all
Includes:
ElasticAPM::SpanHelpers, ReservationHistory
Defined in:
app/controllers/api/partner/v1/reservations_controller.rb

Instance Method Summary collapse

Methods included from ReservationHistory

#history, #history_result

Methods inherited from BaseController

#default_restaurant, #identity_cache_memoization, #render_unauthorize_action, #restaurants, #set_options

Methods included from LogrageCustomLogger

#append_info_to_payload

Methods included from ControllerHelpers

#check_boolean_param, #get_banners, #inventory_params

Methods included from ResponseCacheConcern

#my_response_cache

Instance Method Details

#_action_for(action) ⇒ Object



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 338

def _action_for(action)
  service = ReservationConfirmationService.new(@reservation.id, confirmed_by: :owner)
  success = if action == 'confirm'
              message = I18n.t('partner.reservations.confirmed')
              service.accept
            else
              message = I18n.t('partner.reservations.rejected')
              service.reject
            end

  data = Api::Partner::ReservationSerializer.new(@reservation.reload).as_json

  if success
    render json: { success: true, message: message }.merge(data), status: :ok
  else
    error(service.error_message_simple, :unprocessable_entity)
  end
end

#booking_summariesObject



111
112
113
114
115
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 111

def booking_summaries
  data = @reservation&.user&.formatted_booking_summary(default_restaurant.id) || BookingSummary.default_data

  render json: { success: true, data: data }
end

#check_inventory_statusObject



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 195

def check_inventory_status
  restaurant = default_restaurant
  query = params.require(:reservation)
  date = query.require(:date)
  start_time = query.require(:start_time)
  adult = query.require(:adult)
  kids = query.fetch(:kids, 0)
  date = date.is_a?(Date) ? date : date.to_date

  inv_checker = InvCheckerFactory.new(restaurant.id, restaurant.time_zone).create_inv_checker_service
  if inv_checker.bookable?(date: date, start_time: start_time, adult: adult.to_i, kids: kids.to_i)
    render json: { available: true, message: nil }
  else
    message = inv_checker.check_unavailability_reason(date: date, start_time: start_time)
    render json: { available: false, message: message }
  end
end

#confirmObject



329
330
331
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 329

def confirm
  _action_for('confirm')
end

#createObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 132

def create
  if default_restaurant.nil?
    return render json: { success: false, data: nil,
                          errors: I18n.t('partner.errors.reservations.invalid_restaurant') }
  end

  has_packages = package_params.present? && package_params.any? { |pkg| pkg[:id].present? }

  if has_packages && default_restaurant.allow_booking_without_package?
    return render json: { success: false,
                          message: 'This restaurant does not offer packages' }, status: :bad_request
  end

  if has_packages
    create_package_reservation(default_restaurant)
  elsif default_restaurant.allow_booking_without_package?
    # No packages provided but restaurant allows non-package bookings
    create_non_package_reservation(default_restaurant)
  else
    # No packages provided and restaurant doesn't allow non-package bookings
    render json: { success: false, data: nil,
                   message: 'This restaurant requires package selection for bookings' }, status: :bad_request
  end
end

#email_owner_managerObject



260
261
262
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 260

def email_owner_manager
  render json: manager_emails
end

#exportObject



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 265

def export
  permitted_params = fetch_permitted_params
  emails = fetch_emails(permitted_params[:emails])
  messages = validate_inputs(permitted_params, emails)

  return render_failed(messages) if messages.present?

  restaurant = default_restaurant
  begin
    # Validate raw parameters first
    validate_date_params(permitted_params[:start_date], permitted_params[:end_date])

    # Then parse the dates
    start_date = parse_date(permitted_params[:start_date], restaurant.time_zone)
    end_date = parse_date(permitted_params[:end_date], restaurant.time_zone)

    # Only default end_date when date_filter_type is 'range' and end_date is nil
    if end_date.nil? && permitted_params[:date_filter_type] == 'range'
      end_date = Time.now_in_tz(restaurant.time_zone)
    end

    # Final validation with parsed dates
    validate_parsed_dates(start_date, end_date)
  rescue ArgumentError, TypeError, Date::Error => e
    return render_failed([e.message])
  end

  return if start_date.nil? || end_date.nil?

  # Validate that reservations exist for the criteria before scheduling worker
  # Use shared method to build export parameters consistently
  export_params = PartnerService::Reservations::Exports::ValidationService.build_export_params(
    permitted_params, start_date, end_date
  )

  validation_service = PartnerService::Reservations::Exports::ValidationService.new(restaurant, export_params)
  validation_result = validation_service.validate_data_availability

  unless validation_result[:valid]
    return render_failed([validation_result[:message]], validation_result[:count])
  end

  # this params to indicate report request by group restaurants based on staff to get staff.restaurants if single restaurant_id is not provided
  if params[:reservation][:branch] == 'all'
    by_group_staff_id = current_staff.id
  end

  schedule_report(
    emails: emails,
    restaurant: restaurant,
    start_date: start_date,
    end_date: end_date,
    params: permitted_params,
    by_group_staff_id: by_group_staff_id,
  )

  render json: {
    status: 'success',
    message: I18n.t('partner.reservations.export_booking'),
    count: validation_result[:count],
  }
end

#export_progressObject



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 358

def export_progress
  service = Api::Partner::AttachmentsFilter.new(Attachment.where(restaurant_id: restaurants.pluck(:id)))
  service.filter(params)
  service.order_by(params[:order_by])

  attachments = service.to_collections

  begin
    @pagy, @attachments = pagy(attachments, items: params[:per_page] || 10, page: params[:page] || 1)
  rescue Pagy::OverflowError
    params[:page] = 1
    @pagy, @attachments = pagy(Attachment.where('1 = 0'))
  end

  # Enhanced serialization with improved progress tracking for reservation exports
  serializer_options = set_options(@pagy).merge(
    include_enhanced_progress: true,
    current_user: current_staff,
  )

  render json: Api::Partner::AttachmentSerializer.new(@attachments, serializer_options).as_json
end

#indexObject



16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 16

def index
  # Use optimized summary tables if enabled via AdminSetting
  optimization_enabled = AdminSetting.enable_partners_reservation_summary_optimization

  # Set APM label to track which mode is being used
  ElasticAPM.set_label(:reservation_index_mode, optimization_enabled ? 'optimized' : 'legacy')

  if optimization_enabled
    index_optimized
  else
    index_legacy
  end
end

#index_legacyObject



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
95
96
97
98
99
100
101
102
103
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 65

def index_legacy
  # Set APM labels for tracking performance
  ElasticAPM.set_label(:index_type, 'legacy')

  # Track restaurant IDs for debugging
  restaurant_ids = restaurants.ids
  ElasticAPM.set_label(:restaurant_ids, restaurant_ids.join(','))

  reservations = Reservation.joins(:restaurant).
    includes(:user,
             :customer,
             :property,
             :charges,
             :booking_channel,
             :reward,
             restaurant: :translations,
             user: :user_loyalty). # Eager load user_loyalty and its loyalty_level
    where(restaurant_id: restaurant_ids).
    last_3_months(default_restaurant.time_zone).
    exclude_temporary

  filter_params = params.fetch(:filter, {})
  reservations = reservations.where(adjusted: false) if filter_params[:booking_id].blank?
  order_params = params.fetch(:order, '').split(',') || []
  filter = Api::Partner::ReservationsFilter.new(default_restaurant, reservations)
  filter.order_by(order_params)
  filter.filter(filter_params) if filter_params.present?
  filter.build_collections

  reservations = nil
  begin
    pagy, reservations = pagy(filter.collections, items: params[:per_page] || 10, page: params[:page] || 1)
  rescue Pagy::OverflowError
    params[:page] = 1
    pagy, reservations = pagy(Reservation.where('1 = 0'))
  end

  render json: filter.as_json(reservations, set_options(pagy))
end

#index_optimizedObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 31

def index_optimized
  # Set APM labels for tracking performance
  ElasticAPM.set_label(:index_type, 'optimized')

  filter_params = params.fetch(:filter, {})
  order_params = params.fetch(:order, '').split(',') || []

  # Get restaurant IDs once to avoid redundant queries
  restaurant_ids = restaurants.pluck(:id)
  ElasticAPM.set_label(:restaurant_ids, restaurant_ids.join(','))

  # Force eager loading with explicit joins to ensure translations are loaded
  base_summaries = Partners::ReservationSummary.
    eager_load(:summary_packages, :summary_add_ons, restaurant: :translations).
    where(restaurant_id: restaurant_ids).
    where(is_temporary: false, for_locking_system: false, adjusted: false)

  # Pass restaurant_ids to filter to optimize multilingual cache queries
  filter = Api::Partner::ReservationSummariesFilter.new(base_summaries, restaurant_ids)
  filter.filter(filter_params) if filter_params.present?
  filter.order_by(order_params)
  filter.build_collections

  begin
    pagy, summaries = pagy(filter.collections, items: params[:per_page] || 10, page: params[:page] || 1)
  rescue Pagy::OverflowError
    params[:page] = 1
    pagy, summaries = pagy(Partners::ReservationSummary.where('1 = 0'))
  end

  render json: filter.as_json(summaries, set_options(pagy))
end

#packagesObject



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 158

def packages
  # Use aggressive caching for optimal performance
  package_types = ['HhPackage::Package::Ayce', 'HhPackage::Package::PartyPack',
                   'HhPackage::Package::Xperience', 'HhPackage::Package::Diy']
  base_scope = default_restaurant.restaurant_packages.where(package_type: package_types)
  cache_key = "#{self.class}:packages:#{base_scope.cache_key}:#{I18n.locale}"

  packages_data = Rails.cache.fetch(cache_key, expires_in: CACHEFLOW.generate_expiry) do
    # Optimized single query with proper filtering
    rest_packs = base_scope.includes(:package) # Eager load to avoid N+1

    {
      success: true,
      data: ActiveModel::Serializer::CollectionSerializer.new(rest_packs,
                                                              serializer: Websites::RestaurantPackageSerializer),
    }
  end

  render json: packages_data
end

#rejectObject



334
335
336
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 334

def reject
  _action_for('reject')
end

#reservation_historyObject



180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 180

def reservation_history
  reservation_id = params[:id]
  history(reservation_id)
  reservations = Reservation.where(id: history_result.to_a)
  begin
    pagy, reservations = pagy(reservations, items: params[:per_page] || 10, page: params[:page] || 1)
  rescue Pagy::OverflowError
    params[:page] = 1
    pagy, reservations = pagy(Reservation.where('1 = 0'))
  end

  render json: Api::Partner::ReservationSerializer.new(reservations, set_options(pagy)).as_json
end

#send_custom_smsObject



229
230
231
232
233
234
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 229

def send_custom_sms
  ids = params.require(:ids)
  message = params.require(:message)
  Workers::Reservations::SendCustomSmsWorker.perform_async(ids, message)
  render json: { success: true, message: I18n.t('partner.reservations.send_custom_sms') }, status: :ok
end

#send_reminderObject



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 214

def send_reminder
  model_policy = ::ReservationPolicy.new(current_staff, @reservation)
  return error(I18n.t('partner.errors.reservations.unauthorized'), :bad_request) unless model_policy.be_reminded?

  reminder = BookingReminder::ManualQueue.new(@reservation.id)
  result = reminder.remind

  if result.success
    render json: Api::Dashboard::ReservationSerializer.new(@reservation).as_json, status: :ok
  else
    render json: { errors: [detail: result.message] }, status: :unprocessable_entity
  end
end


237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 237

def send_review_link
  if @reservation.is_past?
    if @reservation.id.blank?
      APMErrorHandler.report('Nil reservation id detected before enqueuing rating reservation worker')
    else
      Workers::Reservations::RatingReservationWorker.perform_async(@reservation.id)
    end

    render json: {
      success: true,
      message: I18n.t('send_review_link_success'),
      data: @reservation,
    }
  else
    render json: {
      success: false,
      message: I18n.t('send_review_link_failed'),
      data: @reservation,
    }
  end
end

#showObject



106
107
108
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 106

def show
  render json: Api::Partner::ReservationSerializer.new(@reservation, set_options).as_json, status: :ok
end

#updateObject



118
119
120
121
122
123
124
125
126
127
128
129
# File 'app/controllers/api/partner/v1/reservations_controller.rb', line 118

def update
  model_policy = ::ReservationPolicy.new(current_staff, @reservation)
  return error(I18n.t('partner.errors.reservations.unauthorized'), :bad_request) unless model_policy.editable?

  params[:reservation][:active] = false if parse_param(:cancel)

  if default_restaurant.allow_booking_without_package?
    update_non_package_booking
  else
    update_package_booking
  end
end