Class: PartnerService::Dashboards::CalculateService

Inherits:
ApplicationService show all
Includes:
ElasticAPM::SpanHelpers
Defined in:
app/services/partner_service/dashboards/calculate_service.rb

Overview

Service class for calculating dashboard metrics for partner restaurants.

Provides methods to calculate various metrics including revenue, bookings, and other dashboard statistics with proper filtering and caching.

Instance Attribute Summary collapse

Attributes inherited from ApplicationService

#object

Instance Method Summary collapse

Methods inherited from ApplicationService

#execute, #execute!

Constructor Details

#initialize(restaurants) ⇒ CalculateService

Initialize the service with a collection of restaurants.

Parameters:

  • restaurants (Array<Restaurant>)

    Collection of restaurant objects



17
18
19
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 17

def initialize(restaurants)
  @restaurant_ids = Array(restaurants).map(&:id)
end

Instance Attribute Details

#restaurant_idsObject

Returns the value of attribute restaurant_ids.



12
13
14
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 12

def restaurant_ids
  @restaurant_ids
end

Instance Method Details

#current_month_bookings_revenueString

Returns revenue for the current month.

Returns:

  • (String)

    Formatted revenue amount



168
169
170
171
172
173
174
175
176
177
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 168

def current_month_bookings_revenue
  start_date = Date.current_date.beginning_of_month
  end_date = Date.current_date.end_of_month
  cache_key = "current_month_revenue:#{start_date}:#{end_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    revenue = revenue_bookings_with_filtering(start_date, end_date)
    format_decimal(revenue)
  end
end

#current_restaurant_timeTime

Returns the current time in the first restaurant's timezone.

Returns:

  • (Time)

    Current time in restaurant timezone



428
429
430
431
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 428

def current_restaurant_time
  restaurant = Restaurant.find(restaurant_ids.first)
  Time.now_in_tz(restaurant.time_zone)
end

#identifiers(start_date, end_date) ⇒ Array<Hash>

Returns rating identifiers with scores for a date range.

Parameters:

  • start_date (Date)

    Start date for identifier calculation

  • end_date (Date)

    End date for identifier calculation

Returns:

  • (Array<Hash>)

    Array of identifier data with scores



307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 307

def identifiers(start_date, end_date)
  avg = []
  reviews = reviews(start_date, end_date).joins(rates: :dimension).
    select('rates.stars as stars, dimensions.identifier as identifier').
    group_by(&:identifier)

  reviews.transform_values do |reviews|
    avg << {
      score: (reviews.sum(&:stars) / reviews.size).round(1),
      identifier: reviews.first.identifier,
    }
  end
  avg
end

#last_8_weeks_bookings_revenueArray<Hash>

Returns revenue data for the last 8 weeks.

Returns:

  • (Array<Hash>)

    Array of weekly revenue data



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
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 213

def last_8_weeks_bookings_revenue
  current_date = Date.current_date
  cache_key = "last_8_weeks_revenue:#{current_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key) do
    weeks_data = []

    days_since_sunday = current_date.wday == 0 ? 0 : current_date.wday
    most_recent_sunday = current_date - days_since_sunday.days

    (0..7).each do |week_offset|
      week_end = most_recent_sunday - (week_offset * 7).days
      week_start = week_end - 6.days
      week_revenue = revenue_bookings_with_filtering(week_start, week_end)

      weeks_data << {
        week: week_offset + 1,
        start_date: week_start,
        end_date: week_end,
        revenue: format_decimal(week_revenue),
      }
    end

    {
      weeks: weeks_data.reverse,
      total_revenue: format_decimal(weeks_data.sum { |week| week[:revenue] }),
    }
  end
end

#last_month_bookingsHash

Returns booking statistics for the previous month.

Returns:

  • (Hash)

    Hash with :bookings count and :covers sum



120
121
122
123
124
125
126
127
128
129
130
131
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 120

def last_month_bookings
  start_date = Date.current_date.prev_month.beginning_of_month
  end_date = start_date.end_of_month
  cache_key = "last_month_bookings:#{start_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      bookings: total_confirm_bookings(start_date, end_date),
      covers: total_covers_bookings(start_date, end_date),
    }
  end
end

#last_month_bookings_revenueString

Returns revenue for the previous month.

Returns:

  • (String)

    Formatted revenue amount



183
184
185
186
187
188
189
190
191
192
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 183

def last_month_bookings_revenue
  start_date = Date.current_date.prev_month.beginning_of_month
  end_date = start_date.end_of_month
  cache_key = "last_month_revenue:#{start_date}:#{end_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    revenue = revenue_bookings_with_filtering(start_date, end_date)
    format_decimal(revenue)
  end
end

#package_best_revenue(start_date, end_date) ⇒ String?

Finds the package with the highest revenue for a given date range.

Parameters:

  • start_date (Date)

    Start date for the search period

  • end_date (Date)

    End date for the search period

Returns:

  • (String, nil)

    Name of the best performing package or nil if none found



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 407

def package_best_revenue(start_date, end_date)
  best_package = reservations.active_and_show_scope.
    exclude_temporary.
    where(date: start_date..end_date).
    joins(reservation_packages: :restaurant_package).
    where(hh_package_restaurant_packages: { active: true }).
    select('hh_package_restaurant_packages.id as rpid, SUM(reservations.total_price_v2_cents) as package_total_price_cents').
    group('hh_package_restaurant_packages.id').
    order('package_total_price_cents DESC').
    limit(1).
    first

  return nil if best_package.blank?

  restaurant_package = HhPackage::RestaurantPackage.find_by(id: best_package.rpid)
  restaurant_package&.package&.name
end

#package_summaryHash

Returns package summary with online count and best seller.

Returns:

  • (Hash)

    Hash with :online count and :best_seller name



94
95
96
97
98
99
100
101
102
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 94

def package_summary
  cache_key = "package_summary:#{Date.current_date}:#{restaurant_ids.join(',')}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      online: HhPackage::RestaurantPackage.where(restaurant_id: restaurant_ids).active_scope.exclude_preview.size,
      best_seller: package_best_revenue(Date.current - 30.days, Date.current),
    }
  end
end

#pending_bookingsHash

Calculates pending bookings count and revenue.

Returns:

  • (Hash)

    Hash with :bookings count and :revenue amount



31
32
33
34
35
36
37
38
39
40
41
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 31

def pending_bookings
  cache_key = "pending_bookings:#{Date.current_date}:#{restaurant_ids.join(',')}"
  data = Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    reservations.upcoming.
      exclude_temporary.
      exclude_cancel_scope.
      no_show.
      where(ack: false)
  end
  { bookings: data.size, revenue: revenue_with_filtering(data) }
end

#ratingsHash

Returns rating statistics for the last 30 days.

Returns:

  • (Hash)

    Hash with :score, :identifiers, and :count



260
261
262
263
264
265
266
267
268
269
270
271
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 260

def ratings
  end_date = current_restaurant_time
  start_date = 30.days.ago
  cache_key = "ratings:#{start_date.to_date}:#{restaurant_ids.join(',')}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      score: ratings_score(start_date, end_date),
      identifiers: identifiers(start_date, end_date),
      count: total_reviews(start_date, end_date),
    }
  end
end

#ratings_score(start_date, end_date) ⇒ Float

Calculates average rating score for a date range.

Parameters:

  • start_date (Date)

    Start date for rating calculation

  • end_date (Date)

    End date for rating calculation

Returns:

  • (Float)

    Average rating score rounded to 1 decimal place



278
279
280
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 278

def ratings_score(start_date, end_date)
  reviews(start_date, end_date).average(:rating).to_f.round(1)
end

#reservationsActiveRecord::Relation

Returns reservations for the configured restaurant IDs.

Returns:

  • (ActiveRecord::Relation)

    Reservation relation



24
25
26
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 24

def reservations
  @reservations ||= Reservation.where(restaurant_id: restaurant_ids)
end

#revenue_bookings_with_filtering(start_date, end_date) ⇒ String

Calculates total revenue for bookings within a specified date range.

Parameters:

  • start_date (Date, String)

    Start date for the revenue calculation period

  • end_date (Date, String)

    End date for the revenue calculation period

Returns:

  • (String)

    Formatted decimal revenue amount



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 359

def revenue_bookings_with_filtering(start_date, end_date)
  cache_key = "revenue_with_filtering:#{start_date}:#{end_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    query = ReservationProperty.select('SUM(price_cents) revenue').
      joins(:reservation).
      where.not(reservations: { restaurant_id: nil }).
      where(reservations: { restaurant_id: restaurant_ids, active: true, no_show: false,
                            is_temporary: false, for_locking_system: false }).
      where.not(package: nil).
      where('reservations.date between ? and ?', start_date, end_date)

    revenue_cents = query[0]['revenue']
    return format_decimal(0) if revenue_cents.nil?

    revenue = Money.new(revenue_cents, primary_currency).to_f
    format_decimal(revenue)
  end
end

#revenue_by_dateHash

Returns revenue comparison between current and previous month.

Returns:

  • (Hash)

    Hash with :this_month and :last_month revenue



152
153
154
155
156
157
158
159
160
161
162
163
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 152

def revenue_by_date
  beginning_of_month = Date.current_date.prev_month.beginning_of_month
  end_of_month = beginning_of_month.end_of_month
  cache_key = "revenue_by_date:#{beginning_of_month}:#{restaurant_ids.join(',')}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      this_month: format_decimal(revenue_bookings_with_filtering(Date.current_date.beginning_of_month,
                                                                 Date.current_date.end_of_month)),
      last_month: format_decimal(revenue_bookings_with_filtering(beginning_of_month, end_of_month)),
    }
  end
end

#revenue_with_filtering(reservations_collection) ⇒ String, Integer

Calculates total revenue for a given collection of reservations.

Parameters:

  • reservations_collection (ActiveRecord::Relation)

    Collection of Reservation objects

Returns:

  • (String, Integer)

    Formatted decimal revenue amount, or 0 if collection is blank



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 384

def revenue_with_filtering(reservations_collection)
  return 0 if reservations_collection.blank?

  reservation_ids = reservations_collection.pluck(:id)

  query = ReservationProperty.select('SUM(price_cents) revenue').
    joins(:reservation).
    where(reservations: { id: reservation_ids }).
    where.not(package: nil)

  revenue_cents = query[0]['revenue']
  return format_decimal(0) if revenue_cents.nil?

  revenue = Money.new(revenue_cents, primary_currency).to_f
  format_decimal(revenue)
end

#reviews(start_date, end_date) ⇒ ActiveRecord::Relation

Returns reviews for a date range.

Parameters:

  • start_date (Date)

    Start date for review search

  • end_date (Date)

    End date for review search

Returns:

  • (ActiveRecord::Relation)

    Review relation



287
288
289
290
291
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 287

def reviews(start_date, end_date)
  @reviews ||= Review.joins(:reservation).
    where(reservations: { active: true, restaurant_id: restaurant_ids }).
    where('reviews.created_at >= ? AND reviews.created_at <= ?', start_date.to_date.beginning_of_day, end_date.to_date.end_of_day)
end

#seat_available_percentInteger

Calculates seat availability percentage for the next 7 days.

Returns:

  • (Integer)

    Seat availability percentage



325
326
327
328
329
330
331
332
333
334
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 325

def seat_available_percent
  inventories = Inventory.where(restaurant_id: @restaurant_ids).
    next_7_days

  total_capacity = inventories.sum(:quantity_available)
  total_booked_seat = inventories.sum(:total_booked_seat)
  return 0 if total_capacity.zero?

  ((1 - (total_booked_seat.to_f / total_capacity)) * 100).round
end

#this_month_bookingsHash

Returns booking statistics for the current month.

Returns:

  • (Hash)

    Hash with :bookings count and :covers sum



107
108
109
110
111
112
113
114
115
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 107

def this_month_bookings
  cache_key = "this_month_bookings:#{Date.current_date.beginning_of_month}:#{restaurant_ids.join(',')}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      bookings: total_confirm_bookings(Date.current_date.beginning_of_month, Date.current_date.end_of_month),
      covers: total_covers_bookings(Date.current_date.beginning_of_month, Date.current_date.end_of_month),
    }
  end
end

#today_bookingsHash

Returns booking statistics for today.

Returns:

  • (Hash)

    Hash with :bookings count and :covers sum



247
248
249
250
251
252
253
254
255
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 247

def today_bookings
  cache_key = "today_bookings:#{Date.current_date}:#{restaurant_ids.join(',')}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      bookings: total_confirm_bookings(Date.current_date, Date.current_date),
      covers: total_covers_bookings(Date.current_date, Date.current_date),
    }
  end
end

#total_confirm_bookings(start_date, end_date) ⇒ Integer

Returns total confirmed bookings for a date range.

Parameters:

  • start_date (Date)

    Start date for booking count

  • end_date (Date)

    End date for booking count

Returns:

  • (Integer)

    Total confirmed bookings count



341
342
343
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 341

def total_confirm_bookings(start_date, end_date)
  PartnerService::Reservations::MetricsService.new(start_date, end_date, restaurant_ids).total_confirm_bookings
end

#total_covers_bookings(start_date, end_date) ⇒ Integer

Returns total covers for bookings in a date range.

Parameters:

  • start_date (Date)

    Start date for covers calculation

  • end_date (Date)

    End date for covers calculation

Returns:

  • (Integer)

    Total covers count



350
351
352
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 350

def total_covers_bookings(start_date, end_date)
  PartnerService::Reservations::MetricsService.new(start_date, end_date, restaurant_ids).total_covers_bookings
end

#total_reviews(start_date, end_date) ⇒ Integer

Returns total count of reviews for a date range.

Parameters:

  • start_date (Date)

    Start date for review count

  • end_date (Date)

    End date for review count

Returns:

  • (Integer)

    Total number of reviews



298
299
300
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 298

def total_reviews(start_date, end_date)
  reviews = reviews(start_date, end_date).group(:id).count.size
end

#two_months_before_bookingsHash

Returns booking statistics for two months ago.

Returns:

  • (Hash)

    Hash with :bookings count and :covers sum



136
137
138
139
140
141
142
143
144
145
146
147
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 136

def two_months_before_bookings
  start_date = 2.months.ago.beginning_of_month
  end_date = start_date.end_of_month
  cache_key = "two_months_before_bookings:#{start_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    {
      bookings: total_confirm_bookings(start_date, end_date),
      covers: total_covers_bookings(start_date, end_date),
    }
  end
end

#two_months_before_bookings_revenueString

Returns revenue for two months ago.

Returns:

  • (String)

    Formatted revenue amount



198
199
200
201
202
203
204
205
206
207
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 198

def two_months_before_bookings_revenue
  start_date = 2.months.ago.beginning_of_month
  end_date = start_date.end_of_month
  cache_key = "two_months_before_revenue:#{start_date}:#{end_date}:#{restaurant_ids.join(',')}"

  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    revenue = revenue_bookings_with_filtering(start_date, end_date)
    format_decimal(revenue)
  end
end

#upcoming_bookingsHash

Calculates upcoming bookings for the next 7 days.

Returns:

  • (Hash)

    Hash with :booking count, :covers sum, and :seat_available percentage



46
47
48
49
50
51
52
53
54
55
56
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 46

def upcoming_bookings
  cache_key = "upcoming_bookings:#{Date.current_date}:#{restaurant_ids.join(',')}"
  collections = Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    reservations.active_and_show_scope.
      exclude_temporary.
      where(date: Date.next_7_days)
  end
  { booking: collections.size,
    covers: collections.sum(:party_size),
    seat_available: seat_available_percent }
end

#voucher_salesHash

Calculates voucher sales for the current month.

Returns:

  • (Hash)

    Hash with voucher sales data and revenue



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
# File 'app/services/partner_service/dashboards/calculate_service.rb', line 61

def voucher_sales
  start_date = current_restaurant_time.beginning_of_month
  end_date = start_date.end_of_month
  cache_key = "voucher_sales:#{start_date}:#{restaurant_ids.join(',')}"
  vouchers = Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    Ticket.joins(:ticket_transaction).where(ticket_transactions: { restaurant_id: restaurant_ids }).where(
      'DATE(redeemed_at) >= ? AND DATE(redeemed_at) <= ?', start_date, end_date
    )
  end

  total_revenue = vouchers.sum do |ticket|
    ticket_money = Money.new(ticket.amount_cents, ticket.amount_currency)

    # If ticket currency matches primary currency, use as-is
    # Otherwise, convert to primary currency (this is a simplified approach)
    if ticket.amount_currency == primary_currency
      ticket_money.to_f
    else
      # For now, we'll use the amount as-is since currency conversion requires exchange rates
      # In a real implementation, you'd want to use a currency conversion service
      ticket_money.to_f
    end
  end

  {
    redeemed: vouchers.size,
    revenue: format_decimal(total_revenue),
  }
end