Class: Agents::UpdateForAdmin

Inherits:
Update show all
Includes:
SectionTrees
Defined in:
app/my_lib/agents/update_for_admin.rb

Overview

Admin-specific reservation update agent that handles special permissions for booking modifications.

This class extends the base Update agent with admin-specific functionality including:

  • Conditional use of “sneaky” save mode based on user permissions

  • Enhanced logging and APM instrumentation for admin operations

  • Support for bypassing audit trails for approved users

The sneaky save feature allows approved administrators (configured in AdminSetting.approved_emails_to_change_booking_date) to make certain reservation changes without creating audit trail entries, which is useful for sensitive operations like changing booking dates to past dates.

Examples:

Usage in controller

operation = Agents::UpdateForAdmin.new(reservation_id, params, force_update, current_user)
operation.assign_vouchers(voucher_codes)
operation.update_booking!

Constant Summary

Constants inherited from Base

Base::WORKER_DELAY_TIME

Instance Attribute Summary collapse

Attributes inherited from Base

#audit_comment, #errors, #executor, #owner, #restaurant, #user

Instance Method Summary collapse

Methods included from SectionTrees

#build_group_section_trees, #build_section_trees

Methods inherited from Update

#execute!, #update_booking

Methods included from ErrorType

#fatal_error?, #inventory_error?, #normal_error?, #overwrite_error_type!

Methods inherited from Base

#error_message, #hotline, #inventory_available?, #save_reservation!, #status=

Methods included from SharedJobs

#give_campaign_reward, #send_rating_email

Constructor Details

#initialize(reservation, reservation_params = nil, force_update = nil, current_user = nil) ⇒ UpdateForAdmin

Initialize the admin update agent with user context

Parameters:

  • reservation (Reservation, Integer)

    The reservation object or ID to update

  • reservation_params (Hash, nil) (defaults to: nil)

    Parameters to update the reservation with

  • force_update (String, Boolean, nil) (defaults to: nil)

    Whether to force update without external API calls

  • current_user (User, nil) (defaults to: nil)

    The current admin user performing the update (for permission checks)



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'app/my_lib/agents/update_for_admin.rb', line 36

def initialize(reservation, reservation_params = nil, force_update = nil, current_user = nil)
  @reservation = if reservation.is_a?(::Reservation)
                   reservation
                 else
                   ::Reservation.find(reservation)
                 end
  @vendor_booking_id = reservation_params.delete(:vendor_booking_id) if reservation_params.present?
  @reservation_params = reservation_params
  @force_update = force_update.to_s == 'true'
  @current_user = current_user

  after_initialize
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Agents::Base

Instance Attribute Details

#current_userObject

Returns the value of attribute current_user.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def current_user
  @current_user
end

#force_updateObject

Returns the value of attribute force_update.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def force_update
  @force_update
end

#modify_voucher_modeObject

Returns the value of attribute modify_voucher_mode.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def modify_voucher_mode
  @modify_voucher_mode
end

#reservationObject

Returns the value of attribute reservation.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def reservation
  @reservation
end

#reservation_paramsObject

Returns the value of attribute reservation_params.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def reservation_params
  @reservation_params
end

#supplier_typeObject

Returns the value of attribute supplier_type.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def supplier_type
  @supplier_type
end

#vendor_booking_idObject

Returns the value of attribute vendor_booking_id.



27
28
29
# File 'app/my_lib/agents/update_for_admin.rb', line 27

def vendor_booking_id
  @vendor_booking_id
end

Instance Method Details

#after_initializeObject



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/my_lib/agents/update_for_admin.rb', line 50

def after_initialize
  reservation.audit_comment = 'Updated by admin'
  reservation.modified_by = :admin
  @modify_voucher_mode = reservation.cancelled? ? 'ignore_quota' : 'modify'
  @supplier_type = if reservation.by_tablecheck?
                     'TableCheck'
                   elsif reservation.by_seven_rooms?
                     'SevenRooms'
                   elsif reservation.by_weeloy?
                     'Weeloy'
                   elsif reservation.by_bistrochat?
                     'Bistrochat'
                   elsif reservation.by_mymenu?
                     'MyMenu'
                   else
                     'HungryHub'
                   end
end

#assign_add_ons(add_on_params, reservation_params) ⇒ Object

System don't validate add-on if created by admin

Parameters:

  • add_on_params (Array)

    Example: [

    {
      id: 1,
      quantity: 2,
    }
    

    ]

  • reservation_params (Hash)

    Example:

    adult: 2,
    kids: 1,
    



209
210
211
212
213
214
# File 'app/my_lib/agents/update_for_admin.rb', line 209

def assign_add_ons(add_on_params, reservation_params)
  .add_on_params = add_on_params
  .adult = reservation_params[:adult]&.to_i || reservation.adult
  .kids = reservation_params[:kids]&.to_i || reservation.kids
  self.selected_add_on = .generate
end

#assign_existing_add_onsObject

Assign existing add-ons when no new add-ons are provided in admin update This prevents losing existing add-on data during partial updates



226
227
228
229
230
# File 'app/my_lib/agents/update_for_admin.rb', line 226

def assign_existing_add_ons
  return if reservation.property&.add_on.blank?

  self.selected_add_on = reservation.property.add_on
end

#assign_existing_packagesObject

Assign existing packages when no new packages are provided in admin update This prevents losing existing package data during partial updates



218
219
220
221
222
# File 'app/my_lib/agents/update_for_admin.rb', line 218

def assign_existing_packages
  return if reservation.property&.package.blank?

  self.selected_package = reservation.property.package
end

#assign_packages(package_params, reservation_params, covered_by_hh = false) ⇒ Object

System don't validate package if created by admin

Parameters:

  • package_params (Array)

    Example: [

    {
      id: 1,
      quantity: 2,
    }
    

    ]

  • reservation_params (Hash)

    Example:

    date: '2024-12-31',
    adult: 2,
    kids: 1,
    

  • covered_by_hh (Boolean) (defaults to: false)

    whether the packages are covered by HungryHub promotions



185
186
187
188
189
190
191
192
193
# File 'app/my_lib/agents/update_for_admin.rb', line 185

def assign_packages(package_params, reservation_params, covered_by_hh = false)
  .package_params = package_params
  .date = reservation_params[:date]&.to_date || reservation.date
  .adult = reservation_params[:adult]&.to_i || reservation.adult
  .kids = reservation_params[:kids]&.to_i || reservation.kids
  .covered_by_hh = covered_by_hh
  .use_dynamic_pricing = reservation.booking_channel.support_dynamic_pricing
  self.selected_package = .generate
end

#assign_vouchers(voucher_codes) ⇒ Object

Assign vouchers to the reservation for admin update

Parameters:

  • voucher_codes (Array<String>)

    Example: [

    "VOUCHER123",
    "VOUCHER456"
    

    ]



239
240
241
# File 'app/my_lib/agents/update_for_admin.rb', line 239

def assign_vouchers(voucher_codes)
  self.voucher_codes = voucher_codes
end

#find_reservation_distanceObject



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
327
328
329
330
331
332
333
334
335
336
# File 'app/my_lib/agents/update_for_admin.rb', line 286

def find_reservation_distance
  return unless reservation.service_type == 'delivery'

  ElasticAPM.with_span('admin_find_reservation_distance', 'app', subtype: 'delivery', action: 'calculate') do
    restaurant = reservation.restaurant
    address = DeliveryAddress.find(reservation.address.id)
    destination = {
      lat: address.lat.to_s.gsub(',', '.'),
      lng: address.lon.to_s.gsub(',', '.'),
    }

    calculator = DeliveryChannel::DistanceCalculator.new
    cal_distance = calculator.find_distance_to_restaurant(destination, restaurant)
    calc_delivery = calc_delivery_fee(cal_distance[:distance])
    reservation.original_delivery_fee = calc_delivery[:original_delivery_fee]
    reservation.delivery_fee = calc_delivery[:delivery_fee]
    reservation.distance_to_restaurant = cal_distance[:distance]

    # Use sneaky save if user is approved for booking date changes
    save_method = save_mode

    # Log the save operation for business tracking
    BUSINESS_LOGGER.set_business_context({
                                           reservation_id: reservation.id,
                                           user_email: current_user&.email,
                                           save_method: save_method || 'normal',
                                           operation: 'distance_calculation',
                                         })

    if save_method
      BUSINESS_LOGGER.info('Admin distance calculation using sneaky save', {
                             save_method: save_method,
                             approved_user: true,
                             distance: cal_distance[:distance],
                           })
      reservation.save(save_method)
    else
      BUSINESS_LOGGER.info('Admin distance calculation using normal save', {
                             save_method: 'normal',
                             approved_user: false,
                             distance: cal_distance[:distance],
                           })
      reservation.save
    end

    if reservation.errors.present?
      errors.add(:base, reservation.errors.full_messages.to_sentence)
      raise ActiveRecord::Rollback
    end
  end
end

#update_booking!Object



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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'app/my_lib/agents/update_for_admin.rb', line 69

def update_booking!
  ElasticAPM.with_span('admin_reservation_update', 'app', subtype: 'booking', action: 'update') do
    result = false

    validate_owner_parameters!
    validate_refund_guarantee!

    # Assign reservation params after validations for prevent reservation data loss
    reservation.assign_attributes(reservation_params) if reservation_params.present?

    ActiveRecord::Base.transaction do
      save_reservation!(:sneaky)
      save_property!
      update_voucher
      find_reservation_distance
      reservation.assign_charged_data
      reservation.reservation_vouchers.each(&:assign_amount_voucher)
      reservation.save(:sneaky) if reservation.changes.present?

      # skip update on TableCheck/SevenRooms if force_update is true
      # so the reservation in their side will not be updated
      unless force_update
        update_tc_result = update_tablecheck_reservation
        unless update_tc_result.success?
          error_msg = Tablecheck::ErrorMessages.error_message('admin', update_tc_result.message)
          errors.add(:base, error_msg)
          raise Agents::InvalidReservation, error_msg
        end

        update_sr_result = update_seven_rooms_reservation
        unless update_sr_result.success?
          error_msg = SevenRooms::ErrorMessages.error_message('admin', update_sr_result.message)
          errors.add(:base, error_msg)
          raise Agents::InvalidReservation, error_msg
        end

        update_weeloy_result = update_weeloy_reservation
        unless update_weeloy_result.success?
          error_msg = Weeloy::ErrorMessages.error_message('admin', update_weeloy_result.message)
          errors.add(:base, error_msg)
          raise Agents::InvalidReservation, error_msg
        end

        update_bistrochat_result = update_bistrochat_reservation
        unless update_bistrochat_result.success?
          errors.add(:base, Bistrochat::ErrorMessages.error_message('admin', update_bistrochat_result.message))
          raise Agents::InvalidReservation
        end

        update_mymenu_result = update_mymenu_reservation
        unless update_mymenu_result.success?
          errors.add(:base, MyMenu::ErrorMessages.error_message('admin', update_mymenu_result.message))
          raise Agents::InvalidReservation
        end
      end

      create_update_vendor_booking
      update_voucher
      find_reservation_distance
      reservation.assign_charged_data
      reservation.reservation_vouchers.each(&:assign_amount_voucher)

      # Use sneaky save if user is approved for booking date changes and there are changes
      if reservation.changes.present?
        save_method = save_mode

        # Log the final save operation for business tracking
        BUSINESS_LOGGER.set_business_context({
                                               reservation_id: reservation.id,
                                               user_email: current_user&.email,
                                               save_method: save_method || 'sneaky',
                                               operation: 'final_reservation_save',
                                               has_changes: true,
                                             })

        if save_method
          BUSINESS_LOGGER.info('Admin reservation final save using approved sneaky save', {
                                 save_method: save_method,
                                 approved_user: true,
                                 changes: reservation.changes.keys,
                               })
          reservation.save(save_method)
        else
          BUSINESS_LOGGER.info('Admin reservation final save using default sneaky save', {
                                 save_method: 'sneaky',
                                 approved_user: false,
                                 changes: reservation.changes.keys,
                               })
          reservation.save(:sneaky)
        end
      end
      result = true
    end

    create_update_vendor_booking
    reservation.trigger_immediate_sync
    result = true
  end
end

#update_order_now_reminderObject



338
339
340
341
# File 'app/my_lib/agents/update_for_admin.rb', line 338

def update_order_now_reminder
  worker = UponReservationCreationWorker.new
  worker.send_order_now_reminder_to_staff(reservation)
end

#update_voucherObject



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'app/my_lib/agents/update_for_admin.rb', line 243

def update_voucher
  ElasticAPM.with_span('admin_update_voucher', 'app', subtype: 'voucher', action: 'modify') do
    service = VoucherService::Modify.new(reservation, voucher_codes, modify_voucher_mode)
    result = service.override_vouchers!
    unless result.success?
      errors.add(:base, result.message)
      raise ActiveRecord::Rollback
    end

    reservation = result.data

    # Use sneaky save if user is approved for booking date changes
    save_method = save_mode

    # Log the save operation for business tracking
    BUSINESS_LOGGER.set_business_context({
                                           reservation_id: reservation.id,
                                           user_email: current_user&.email,
                                           save_method: save_method || 'normal',
                                           operation: 'voucher_update',
                                         })

    if save_method
      BUSINESS_LOGGER.info('Admin voucher update using sneaky save', {
                             save_method: save_method,
                             approved_user: true,
                           })
      reservation.save(save_method)
    else
      BUSINESS_LOGGER.info('Admin voucher update using normal save', {
                             save_method: 'normal',
                             approved_user: false,
                           })
      reservation.save
    end

    if reservation.errors.present?
      errors.add(:base, reservation.errors.full_messages.to_sentence)
      raise ActiveRecord::Rollback
    end
  end
end