Class: HhPackage::RestaurantPackage
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- HhPackage::RestaurantPackage
- Includes:
- IdentityCache, SoftDelete
- Defined in:
- app/models/hh_package/restaurant_package.rb
Constant Summary collapse
- AGENDA_MODEL_LAST_MODIFIED_AT =
main logic for each package availability on the first level is in Agenda model so we need to refresh the cache if we update #available_times logic
'v2025-03-08'- SHOW_TO_ALL_USERS_MODE =
'Show to all users'.freeze
- PREVIEW_MODE =
'Preview Mode'.freeze
- HIDDEN_MODE =
'Hidden'.freeze
Instance Attribute Summary collapse
-
#is_visible_for_staff ⇒ Boolean
is_visible_for_staff column is in preview mode.
-
#skip_validation ⇒ Object
Returns the value of attribute skip_validation.
Instance Method Summary collapse
- #agenda_setting ⇒ Object
- #available_at?(date, time, time_zone) ⇒ Boolean
-
#available_times(date, time_zone) ⇒ Array
This method returns available times for the given date.
-
#available_times_by_dates(dates, time_zone) ⇒ Array
Available times for the given date.
- #comemore_payless_expiry ⇒ Object
- #determine_inventory_class(restaurant) ⇒ Object
- #fetch_package ⇒ Object
- #inventory_cache_key ⇒ Object
- #inventory_cache_ttl ⇒ Object
- #setup_slug ⇒ Object
- #status ⇒ Object
-
#write_available_times(redis_connection = nil) ⇒ Object
store available times in redis in UTC time zone.
Methods included from SoftDelete
Methods inherited from ApplicationRecord
Instance Attribute Details
#is_visible_for_staff ⇒ Boolean
is_visible_for_staff column is in preview mode. It's a symbol indicating that the admin is still reviewing a package. The package has not been published yet.
72 |
# File 'app/models/hh_package/restaurant_package.rb', line 72 before_save :setup_slug, if: :slug_is_blank? |
#skip_validation ⇒ Object
Returns the value of attribute skip_validation.
64 65 66 |
# File 'app/models/hh_package/restaurant_package.rb', line 64 def skip_validation @skip_validation end |
Instance Method Details
#agenda_setting ⇒ Object
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'app/models/hh_package/restaurant_package.rb', line 326 def agenda_setting package.agendas.flat_map do |agenda| attributes = agenda.attributes.slice('start_time', 'end_time', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', 'exception_occurrences', 'single_occurrences') attributes['all_day'] = agenda.all_day.presence || false attributes['exception_occurrences'].map! do |exception_occurrence| if exception_occurrence.respond_to?(:[]) && exception_occurrence['start_date'].present? exception_occurrence['start_date'] = exception_occurrence['start_date'].to_date end if exception_occurrence.respond_to?(:[]) && exception_occurrence['end_date'].present? exception_occurrence['end_date'] = exception_occurrence['end_date'].to_date end exception_occurrence['all_day'] = ActiveModel::Type::Boolean.new.cast(exception_occurrence['all_day']) exception_occurrence end attributes['single_occurrences'].map! do |single_occurrence| if single_occurrence['start_date'].present? single_occurrence['start_date'] = single_occurrence['start_date'].to_date end if single_occurrence['end_date'].present? single_occurrence['end_date'] = single_occurrence['end_date'].to_date end single_occurrence['all_day'] = ActiveModel::Type::Boolean.new.cast(single_occurrence['all_day']) single_occurrence end attributes end end |
#available_at?(date, time, time_zone) ⇒ Boolean
357 358 359 360 361 362 363 364 |
# File 'app/models/hh_package/restaurant_package.rb', line 357 def available_at?(date, time, time_zone) requested_time = Time.use_zone time_zone do Time.zone.parse("#{date} #{time}") end package.agendas.select do |agenda| agenda.available_at?(start_date, end_date, time_zone, requested_time) end.present? end |
#available_times(date, time_zone) ⇒ Array
This method returns available times for the given date. without checking restaurant level availability It uses the package's agendas to determine the available times.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'app/models/hh_package/restaurant_package.rb', line 211 def available_times(date, time_zone) ElasticAPM.with_span('RestaurantPackage#available_times', 'app', subtype: 'method', action: 'query') do |_span| # Add context for better tracing ElasticAPM.set_label(:restaurant_package_id, id) ElasticAPM.set_label(:restaurant_id, restaurant_id) ElasticAPM.set_label(:date, date.to_s) ElasticAPM.set_label(:timezone, time_zone) # get start time and end time in UTC start_time = end_time = nil Time.use_zone(time_zone) do unless date.is_a?(Date) date = Time.zone.parse(date).to_date end start_time = Time.zone.parse(date.to_s).beginning_of_day.in_time_zone('UTC').to_i end_time = Time.zone.parse(date.to_s).end_of_day.in_time_zone('UTC').to_i iso8601_strings = [] ElasticAPM.with_span('Redis fetch available times', 'db', subtype: 'redis', action: 'query') do $inv_redis.with do |redis| write_available_times(redis) if redis.zcard(inventory_cache_key).zero? # Retrieve ISO8601 strings within the given date range iso8601_strings = redis.zrangebyscore(inventory_cache_key, start_time, end_time) end end # Parse ISO8601 strings to Time objects and format as HH:MM mapped_iso8601_strings = ElasticAPM.with_span('Format available times', 'app', subtype: 'method') do iso8601_strings.map do |iso8601| Time.zone.parse(iso8601).strftime('%H:%M') end end # Sort the times in ascending order and return mapped_iso8601_strings.sort.uniq end end end |
#available_times_by_dates(dates, time_zone) ⇒ Array
Returns available times for the given date.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'app/models/hh_package/restaurant_package.rb', line 175 def available_times_by_dates(dates, time_zone) ElasticAPM.with_span('RestaurantPackage#available_times_by_dates', 'app', subtype: 'method', action: 'query') do |_span| # Add metadata to help with debugging ElasticAPM.set_label(:restaurant_package_id, id) ElasticAPM.set_label(:restaurant_id, restaurant_id) ElasticAPM.set_label(:dates_count, dates.size) # Initialize result hash result = {} # Process each date and collect available times dates.each do |date| # Normalize date to string format if it's already a Date object date_str = date.is_a?(Date) ? date.to_s : date # Get available times for this specific date using the existing method ElasticAPM.with_span("Get available times for #{date_str}", 'app', subtype: 'method') do times = available_times(date, time_zone) result[date_str] = times if times.present? end end result end end |
#comemore_payless_expiry ⇒ Object
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 |
# File 'app/models/hh_package/restaurant_package.rb', line 396 def comemore_payless_expiry return nil unless restaurant return nil unless package.dynamic_price_comemore_payless? return nil unless package.pricing_model_and_dynamic_pricing_type == :per_person_and_normal_price pricing_groups = package&.pricing_groups current_date = Time.now_in_tz(restaurant.time_zone).to_date pricing_group = pricing_groups&.find_by('end_date >= ?', current_date) || pricing_groups&.find_by(end_date: nil) # use the end date of the pricing group if it is not nil # else use the end date of the restaurant package pricing_group&.end_date.presence || end_date end |
#determine_inventory_class(restaurant) ⇒ Object
391 392 393 394 |
# File 'app/models/hh_package/restaurant_package.rb', line 391 def determine_inventory_class(restaurant) is_dine_in = restaurant.use_third_party_inventory? ? nil : package.for_dine_in? InventoryWrapper.new(restaurant_id: restaurant.id, is_dine_in: is_dine_in).inv_model end |
#fetch_package ⇒ Object
156 157 158 159 160 161 162 163 164 |
# File 'app/models/hh_package/restaurant_package.rb', line 156 def fetch_package @fetch_package ||= begin Rails.cache.fetch "#{cache_key}/fetch_package", expires_in: CACHEFLOW.generate_expiry do package end rescue TypeError package end end |
#inventory_cache_key ⇒ Object
319 320 321 322 323 324 |
# File 'app/models/hh_package/restaurant_package.rb', line 319 def inventory_cache_key @inventory_cache_key ||= "available_times_v2_version_#{AGENDA_MODEL_LAST_MODIFIED_AT}:{restaurant_package_#{id}}:variables_#{CityHash.hash32([ agenda_setting, start_date, end_date ])}" end |
#inventory_cache_ttl ⇒ Object
315 316 317 |
# File 'app/models/hh_package/restaurant_package.rb', line 315 def inventory_cache_ttl (end_date + 1.day).to_time end |
#setup_slug ⇒ Object
366 367 368 369 370 371 372 373 374 |
# File 'app/models/hh_package/restaurant_package.rb', line 366 def setup_slug new_slug = nil loop do new_slug = "#{restaurant_id}-#{package.slug_code}-#{package_order_number}" break if HhPackage::RestaurantPackage.find_by(slug: new_slug).blank? end self.slug = new_slug end |
#status ⇒ Object
380 381 382 383 384 385 386 387 388 389 |
# File 'app/models/hh_package/restaurant_package.rb', line 380 def status # Show to all user = Show on Client Side # Preview Mode = Show on client Side only if log-in by @hungryhub email account # Hidden = Hide on Client Side return PREVIEW_MODE if active? && is_visible_for_staff? return SHOW_TO_ALL_USERS_MODE if active? && !is_visible_for_staff? HIDDEN_MODE end |
#write_available_times(redis_connection = nil) ⇒ Object
store available times in redis in UTC time zone
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 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 |
# File 'app/models/hh_package/restaurant_package.rb', line 254 def write_available_times(redis_connection = nil) ElasticAPM.with_span('RestaurantPackage#write_available_times', 'app', subtype: 'method', action: 'write') do |_span| # Add context for better tracing ElasticAPM.set_label(:restaurant_package_id, id) ElasticAPM.set_label(:restaurant_id, restaurant_id) ElasticAPM.set_label(:date_range, "#{start_date} to #{end_date}") time_zone = ActiveSupport::TimeZone['UTC'] Time.use_zone(time_zone) do # need to use self.start_date because this method is called within a block # which causing `start_date` is nil # rubocop:disable Style/RedundantSelf if self.start_date.past? self.start_date = Time.now_in_tz(time_zone).to_date end data = ElasticAPM.with_span('Generate agenda occurrences', 'app', subtype: 'method') do package.agendas.flat_map do |agenda| # need to use self.start_date because this method is called within a block # which causing `start_date` is nil # we use `restaurant.time_zone` because admin input the data in restaurant's time zone agenda.schedule_obj(self.start_date, self.end_date, restaurant.time_zone)&.all_occurrences end.compact end # rubocop:enable Style/RedundantSelf logic = lambda do |redis| data.each do |available_time| # Convert available_time to UTC first available_time = available_time.in_time_zone(time_zone) # Store timestamp as score for sorting score = available_time.to_i # Store ISO8601 string as member (preserves timezone) member = available_time.iso8601 redis.zadd(inventory_cache_key, score, member) end redis.expire(inventory_cache_key, inventory_cache_ttl.to_i) end ElasticAPM.with_span('Write to Redis', 'db', subtype: 'redis', action: 'write') do if redis_connection.present? redis_connection.pipelined do logic.call(redis_connection) end else $inv_redis.with do |redis| redis.pipelined do logic.call(redis) end end end end end end end |