Class: PartnerService::Reservations::RestaurantSyncService
- Inherits:
-
ApplicationService
- Object
- ApplicationService
- PartnerService::Reservations::RestaurantSyncService
- Defined in:
- app/services/partner_service/reservations/restaurant_sync_service.rb
Instance Attribute Summary
Attributes inherited from ApplicationService
Class Method Summary collapse
-
.bulk_status(restaurant_ids) ⇒ Array<Hash>
Quick status check for multiple restaurants Useful for monitoring and health checks.
-
.bulk_sync(restaurant_ids, options = {}) ⇒ Array<Hash>
Sync multiple restaurants with progress tracking Useful for bulk operations and data migrations.
-
.calculate_percentage(numerator, denominator) ⇒ Float
Calculate percentage with safe division.
-
.check_status(restaurant_id, days_back = 90) ⇒ Hash
Status check: Analyze sync coverage and identify missing summaries.
-
.generate_sync_recommendations(restaurants_needing_sync_count, total_restaurants) ⇒ Array<Hash>
Generate sync recommendations based on statistics.
-
.get_recommendations(restaurant_id) ⇒ Hash
Get sync recommendations based on current status.
-
.get_reservation_ids(restaurant_id, days_back = 90) ⇒ Array<Integer>
Get reservation IDs for a restaurant ordered by ID DESC.
-
.help ⇒ void
Display helpful usage examples for Rails console Useful for support team and developers.
-
.queue_batch_jobs(restaurant_id, restaurant_name, reservation_ids, options) ⇒ Hash
Queue batch jobs for reservation sync.
-
.restaurant_booking_stats(months_back = 3) ⇒ Hash
Get restaurant booking statistics for last months.
-
.sync_multiple_restaurants(restaurant_ids, options = {}) ⇒ Array<Hash>
Sync multiple restaurants.
-
.sync_non_synced_bookings(restaurant_ids, options = {}) ⇒ Hash
Sync non-synced bookings for multiple restaurants based on last 3 months.
-
.sync_recent_bookings(restaurant_id, options = {}) ⇒ Hash
Quick sync: Sync recent bookings only (last 7 days) for immediate fixes.
-
.sync_restaurant(restaurant_id, options = {}) ⇒ Hash
Main sync method: Sync reservation summaries for a restaurant using batch workers.
-
.sync_single_restaurant_non_synced(restaurant_id, start_date, end_date, options) ⇒ Hash
Sync non-synced bookings for a single restaurant.
Methods inherited from ApplicationService
Class Method Details
.bulk_status(restaurant_ids) ⇒ Array<Hash>
Quick status check for multiple restaurants Useful for monitoring and health checks
355 356 357 358 359 360 361 362 363 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 355 def self.bulk_status(restaurant_ids) results = [] restaurant_ids.each do |restaurant_id| status = check_status(restaurant_id) results << status end results end |
.bulk_sync(restaurant_ids, options = {}) ⇒ Array<Hash>
Sync multiple restaurants with progress tracking Useful for bulk operations and data migrations
334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 334 def self.bulk_sync(restaurant_ids, = {}) results = [] restaurant_ids.each_with_index do |restaurant_id, index| result = sync_restaurant(restaurant_id, ) results << result.merge(restaurant_id: restaurant_id) # Small delay between restaurants to prevent overwhelming the system sleep(1) if [:delay_between_restaurants] && restaurant_ids.size > 1 end results end |
.calculate_percentage(numerator, denominator) ⇒ Float
Calculate percentage with safe division
307 308 309 310 311 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 307 def self.calculate_percentage(numerator, denominator) return 0.0 if denominator == 0 (numerator.to_f / denominator * 100).round(1) end |
.check_status(restaurant_id, days_back = 90) ⇒ Hash
Status check: Analyze sync coverage and identify missing summaries
PURPOSE: Provides comprehensive sync status analysis and health metrics. Compares reservation count vs summary count to identify gaps.
WHEN TO USE:
-
Before running sync operations to assess scope
-
Monitoring sync health and coverage
-
Troubleshooting data inconsistencies
-
Regular health checks for critical restaurants
197 198 199 200 201 202 203 204 205 206 207 208 209 210 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 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 197 def self.check_status(restaurant_id, days_back = 90) restaurant = ::Restaurant.find_by(id: restaurant_id) return { success: false, error: "Restaurant #{restaurant_id} not found" } unless restaurant end_date = Date.current start_date = end_date - days_back.days # Count reservations and summaries total_reservations = Reservation.where(restaurant_id: restaurant_id). where(created_at: start_date.beginning_of_day..end_date.end_of_day). where(for_locking_system: false). count total_summaries = Partners::ReservationSummary.where(restaurant_id: restaurant_id). where(created_at: start_date.beginning_of_day..end_date.end_of_day). count sync_percentage = total_reservations > 0 ? (total_summaries.to_f / total_reservations * 100).round(1) : 0 missing_count = total_reservations - total_summaries # Check recent activity (last 24 hours) recent_reservations = Reservation.where(restaurant_id: restaurant_id). where(created_at: 24.hours.ago..Time.current). where(for_locking_system: false). count recent_summaries = Partners::ReservationSummary.where(restaurant_id: restaurant_id). where(created_at: 24.hours.ago..Time.current). count { success: true, restaurant_id: restaurant_id, restaurant_name: restaurant.name, date_range: "#{start_date} to #{end_date}", total_reservations: total_reservations, total_summaries: total_summaries, sync_percentage: sync_percentage, missing_count: missing_count, recent_activity: { reservations_24h: recent_reservations, summaries_24h: recent_summaries, recent_sync_percentage: calculate_percentage(recent_summaries, recent_reservations), }, needs_sync: sync_percentage < 100 || recent_summaries < recent_reservations, } rescue StandardError => e { success: false, error: "Status check failed: #{e.}" } end |
.generate_sync_recommendations(restaurants_needing_sync_count, total_restaurants) ⇒ Array<Hash>
Generate sync recommendations based on statistics
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 584 def self.generate_sync_recommendations(restaurants_needing_sync_count, total_restaurants) recommendations = [] if restaurants_needing_sync_count == 0 recommendations << { priority: 'low', action: 'none', message: 'All restaurants are fully synced', command: nil, } elsif restaurants_needing_sync_count < (total_restaurants * 0.1) # Less than 10% recommendations << { priority: 'medium', action: 'selective_sync', message: "#{restaurants_needing_sync_count} restaurants need sync (#{((restaurants_needing_sync_count.to_f / total_restaurants) * 100).round(1)}%)", command: 'Use sync_non_synced_bookings with specific restaurant IDs', } else recommendations << { priority: 'high', action: 'bulk_sync', message: "#{restaurants_needing_sync_count} restaurants need sync (#{((restaurants_needing_sync_count.to_f / total_restaurants) * 100).round(1)}%) - consider bulk operation", command: 'Use sync_non_synced_bookings or bulk_sync for affected restaurants', } end recommendations end |
.get_recommendations(restaurant_id) ⇒ Hash
Get sync recommendations based on current status
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 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 251 def self.get_recommendations(restaurant_id) status = check_status(restaurant_id) return status unless status[:success] recommendations = [] if status[:sync_percentage] < 90 recommendations << { priority: 'high', action: 'full_sync', reason: "Only #{status[:sync_percentage]}% of reservations have summaries", command: "PartnerService::Reservations::RestaurantSyncService.sync_restaurant(#{restaurant_id})", } elsif status[:recent_activity][:recent_sync_percentage] < 100 recommendations << { priority: 'medium', action: 'recent_sync', reason: 'Recent bookings missing summaries', command: "PartnerService::Reservations::RestaurantSyncService.sync_recent_bookings(#{restaurant_id})", } else recommendations << { priority: 'low', action: 'none', reason: 'Sync status looks good', command: nil, } end status.merge(recommendations: recommendations) end |
.get_reservation_ids(restaurant_id, days_back = 90) ⇒ Array<Integer>
Get reservation IDs for a restaurant ordered by ID DESC
100 101 102 103 104 105 106 107 108 109 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 100 def self.get_reservation_ids(restaurant_id, days_back = 90) end_date = Date.current start_date = end_date - days_back.days Reservation.where(restaurant_id: restaurant_id). where(created_at: start_date.beginning_of_day..end_date.end_of_day). where(for_locking_system: false). order(id: :desc). # Most recent first for immediate processing pluck(:id) end |
.help ⇒ void
This method returns an undefined value.
Display helpful usage examples for Rails console Useful for support team and developers
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 533 def self.help Rails.logger.info <<~HELP 🔧 RESTAURANT SYNC SERVICE COMMANDS =================================== Basic Sync Operations: # Sync recent bookings (last 7 days) - recommended for immediate fixes PartnerService::Reservations::RestaurantSyncService.sync_recent_bookings(restaurant_id) # Full sync all reservations (90 days) PartnerService::Reservations::RestaurantSyncService.sync_restaurant(restaurant_id) # Sync with custom options PartnerService::Reservations::RestaurantSyncService.sync_restaurant(restaurant_id, { days_back: 30, # Look back 30 days instead of 90 batch_size: 50, # Process 50 reservations per batch sync_type: 'full' # Type of sync to perform }) Status & Monitoring: # Check sync status and get recommendations PartnerService::Reservations::RestaurantSyncService.check_status(restaurant_id) # Get detailed recommendations PartnerService::Reservations::RestaurantSyncService.get_recommendations(restaurant_id) New Statistics & Bulk Sync: # Get restaurant booking statistics for last months PartnerService::Reservations::RestaurantSyncService.restaurant_booking_stats(3) # Sync only non-synced bookings for multiple restaurants PartnerService::Reservations::RestaurantSyncService.sync_non_synced_bookings([restaurant_id_1, restaurant_id_2]) Bulk Operations: # Sync multiple restaurants PartnerService::Reservations::RestaurantSyncService.bulk_sync([restaurant_id_1, restaurant_id_2]) # Check status for multiple restaurants PartnerService::Reservations::RestaurantSyncService.bulk_status([restaurant_id_1, restaurant_id_2]) For more details, see the method documentation in the service file. HELP end |
.queue_batch_jobs(restaurant_id, restaurant_name, reservation_ids, options) ⇒ Hash
Queue batch jobs for reservation sync
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 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 118 def self.queue_batch_jobs(restaurant_id, restaurant_name, reservation_ids, ) total_count = reservation_ids.size total_batches = (total_count.to_f / [:batch_size]).ceil batch_number = 0 reservation_ids.each_slice([:batch_size]) do |batch_ids| batch_number += 1 delay = [:immediate] ? 0 : (batch_number - 1) * [:delay_seconds] batch_info = { 'batch_number' => batch_number, 'total_batches' => total_batches, 'restaurant_id' => restaurant_id, 'restaurant_name' => restaurant_name, 'sync_type' => [:sync_type], 'immediate' => [:immediate], 'ordered_by_recent' => true, } if delay > 0 Partner::ReservationSummaryBatchSyncWorker.perform_in(delay, batch_ids, [:sync_type], batch_info) else Partner::ReservationSummaryBatchSyncWorker.perform_async(batch_ids, [:sync_type], batch_info) end end { success: true, restaurant_id: restaurant_id, restaurant_name: restaurant_name, total_count: total_count, total_batches: total_batches, batch_size: [:batch_size], sync_type: [:sync_type], immediate: [:immediate], message: "Queued #{total_batches} batches for #{total_count} reservations (ordered by recent first)", } end |
.restaurant_booking_stats(months_back = 3) ⇒ Hash
Get restaurant booking statistics for last months
Provides comprehensive statistics about restaurants with bookings and their sync status. Useful for monitoring overall sync health and identifying restaurants that need attention.
378 379 380 381 382 383 384 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 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 378 def self.restaurant_booking_stats(months_back = 3) end_date = Date.current start_date = end_date - months_back.months # Get restaurants with bookings in the period restaurants_with_bookings = Reservation. where(created_at: start_date.beginning_of_day..end_date.end_of_day). where(for_locking_system: false). group(:restaurant_id). count total_restaurants_with_bookings = restaurants_with_bookings.size total_bookings = restaurants_with_bookings.values.sum # Get restaurants with summary records restaurants_with_summaries = Partners::ReservationSummary. where(created_at_reservation: start_date.beginning_of_day..end_date.end_of_day). group(:restaurant_id). count total_restaurants_with_summaries = restaurants_with_summaries.size total_summaries = restaurants_with_summaries.values.sum # Calculate restaurants with incomplete sync restaurants_needing_sync = [] restaurants_with_bookings.each do |restaurant_id, booking_count| summary_count = restaurants_with_summaries[restaurant_id] || 0 sync_percentage = (summary_count.to_f / booking_count * 100).round(1) if sync_percentage < 100 restaurants_needing_sync << { restaurant_id: restaurant_id, booking_count: booking_count, summary_count: summary_count, sync_percentage: sync_percentage, missing_summaries: booking_count - summary_count, } end end # Sort by missing summaries count (most problematic first) restaurants_needing_sync.sort_by! { |r| -r[:missing_summaries] } { success: true, generated_at: Time.current, period: { months_back: months_back, start_date: start_date, end_date: end_date, }, overall_statistics: { total_restaurants_with_bookings: total_restaurants_with_bookings, total_restaurants_with_summaries: total_restaurants_with_summaries, total_bookings: total_bookings, total_summaries: total_summaries, overall_sync_percentage: calculate_percentage(total_summaries, total_bookings), restaurants_fully_synced: total_restaurants_with_bookings - restaurants_needing_sync.size, restaurants_needing_sync: restaurants_needing_sync.size, }, restaurants_needing_sync: restaurants_needing_sync.first(20), # Top 20 most problematic recommendations: generate_sync_recommendations(restaurants_needing_sync.size, total_restaurants_with_bookings), } rescue StandardError => e { success: false, error: "Failed to generate restaurant booking stats: #{e.}" } end |
.sync_multiple_restaurants(restaurant_ids, options = {}) ⇒ Array<Hash>
Sync multiple restaurants
288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 288 def self.sync_multiple_restaurants(restaurant_ids, = {}) results = [] restaurant_ids.each do |restaurant_id| result = sync_restaurant(restaurant_id, ) results << result.merge(restaurant_id: restaurant_id) # Small delay between restaurants to prevent overwhelming the system sleep(1) if [:delay_between_restaurants] end results end |
.sync_non_synced_bookings(restaurant_ids, options = {}) ⇒ Hash
Sync non-synced bookings for multiple restaurants based on last 3 months
Identifies and syncs only the missing reservation summaries for specified restaurants. More efficient than full sync as it only processes reservations without summaries.
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 466 def self.sync_non_synced_bookings(restaurant_ids, = {}) return { success: false, error: 'No restaurant IDs provided' } if restaurant_ids.blank? opts = { months_back: 3, batch_size: 100, sync_type: 'full', immediate: true, }.merge() end_date = Date.current start_date = end_date - opts[:months_back].months results = { success: true, generated_at: Time.current, period: { months_back: opts[:months_back], start_date: start_date, end_date: end_date, }, sync_options: opts, restaurant_results: [], summary: { total_restaurants: restaurant_ids.size, successful_restaurants: 0, failed_restaurants: 0, total_missing_reservations: 0, total_batches_queued: 0, }, } restaurant_ids.each do |restaurant_id| restaurant_result = sync_single_restaurant_non_synced(restaurant_id, start_date, end_date, opts) results[:restaurant_results] << restaurant_result if restaurant_result[:success] results[:summary][:successful_restaurants] += 1 results[:summary][:total_missing_reservations] += restaurant_result[:missing_count] results[:summary][:total_batches_queued] += restaurant_result[:total_batches] else results[:summary][:failed_restaurants] += 1 end # Small delay between restaurants to prevent overwhelming the system sleep(0.5) if restaurant_ids.size > 1 end results rescue StandardError => e error_msg = "Non-synced bookings sync failed: #{e.}" APMErrorHandler.report('Non-synced bookings sync service failed', { restaurant_ids: restaurant_ids, options: , error_message: e., exception: e, }) { success: false, error: error_msg } end |
.sync_recent_bookings(restaurant_id, options = {}) ⇒ Hash
Quick sync: Sync recent bookings only (last 7 days) for immediate fixes
PURPOSE: Fast sync for troubleshooting recent booking issues. Optimized for immediate processing with smaller batch sizes.
WHEN TO USE:
-
Fixing recent booking display issues in Partner API
-
Quick troubleshooting for customer support
-
After system issues that affected recent reservations
-
Regular maintenance for active restaurants
172 173 174 175 176 177 178 179 180 181 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 172 def self.sync_recent_bookings(restaurant_id, = {}) opts = { days_back: 7, batch_size: 50, sync_type: 'full', immediate: true, }.merge() sync_restaurant(restaurant_id, opts) end |
.sync_restaurant(restaurant_id, options = {}) ⇒ Hash
Main sync method: Sync reservation summaries for a restaurant using batch workers
PURPOSE: Synchronizes reservation data to summary tables for Partner API performance. Orders by ID DESC to prioritize recent bookings for immediate processing.
WHEN TO USE:
-
Full data migration or backfill operations
-
Fixing data inconsistencies across all reservations
-
Initial setup for new restaurants
53 54 55 56 57 58 59 60 61 62 63 64 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 |
# File 'app/services/partner_service/reservations/restaurant_sync_service.rb', line 53 def self.sync_restaurant(restaurant_id, = {}) # Validate restaurant restaurant = ::Restaurant.find_by(id: restaurant_id) return { success: false, error: "Restaurant #{restaurant_id} not found" } unless restaurant # Default options optimized for immediate processing opts = { sync_type: 'full', days_back: 90, batch_size: 100, delay_seconds: 0, immediate: true, }.merge() # Get reservation IDs ordered by ID DESC (most recent first) reservation_ids = get_reservation_ids(restaurant_id, opts[:days_back]) if reservation_ids.empty? return { success: true, message: 'No reservations found', restaurant_id: restaurant_id, restaurant_name: restaurant.name, total_count: 0, } end # Queue batches using the existing batch worker result = queue_batch_jobs(restaurant_id, restaurant.name, reservation_ids, opts) result rescue StandardError => e error_msg = "Restaurant sync failed for restaurant #{restaurant_id}: #{e.}" APMErrorHandler.report('Restaurant sync service failed', { restaurant_id: restaurant_id, options: , error_message: e., exception: e, }) { success: false, error: error_msg } end |
.sync_single_restaurant_non_synced(restaurant_id, start_date, end_date, options) ⇒ Hash
Sync non-synced bookings for a single restaurant
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 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/services/partner_service/reservations/restaurant_sync_service.rb', line 620 def self.sync_single_restaurant_non_synced(restaurant_id, start_date, end_date, ) restaurant = ::Restaurant.find_by(id: restaurant_id) unless restaurant return { success: false, restaurant_id: restaurant_id, error: "Restaurant #{restaurant_id} not found", } end # Get all reservations for the period all_reservation_ids = Reservation.where(restaurant_id: restaurant_id). where(created_at: start_date.beginning_of_day..end_date.end_of_day). where(for_locking_system: false). pluck(:id) # Get existing summary reservation IDs existing_summary_ids = Partners::ReservationSummary.where(restaurant_id: restaurant_id). where(created_at_reservation: start_date.beginning_of_day..end_date.end_of_day). pluck(:reservation_id) # Find missing reservation IDs (reservations without summaries) missing_reservation_ids = all_reservation_ids - existing_summary_ids if missing_reservation_ids.empty? return { success: true, restaurant_id: restaurant_id, restaurant_name: restaurant.name, total_reservations: all_reservation_ids.size, existing_summaries: existing_summary_ids.size, missing_count: 0, total_batches: 0, message: 'All reservations already have summaries', } end # Order missing IDs by DESC (most recent first) for immediate processing missing_reservation_ids = missing_reservation_ids.sort.reverse # Queue batch jobs for missing reservations only result = queue_batch_jobs( restaurant_id, restaurant.name, missing_reservation_ids, .merge(sync_type: [:sync_type]), ) if result[:success] { success: true, restaurant_id: restaurant_id, restaurant_name: restaurant.name, total_reservations: all_reservation_ids.size, existing_summaries: existing_summary_ids.size, missing_count: missing_reservation_ids.size, total_batches: result[:total_batches], batch_size: result[:batch_size], sync_type: result[:sync_type], message: "Queued #{result[:total_batches]} batches for #{missing_reservation_ids.size} missing summaries", } else { success: false, restaurant_id: restaurant_id, restaurant_name: restaurant.name, error: result[:error] || 'Failed to queue batch jobs', } end rescue StandardError => e { success: false, restaurant_id: restaurant_id, restaurant_name: restaurant&.name, error: "Sync failed: #{e.}", } end |