Class: CloverSandboxSimulator::Generators::OrderGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/clover_sandbox_simulator/generators/order_generator.rb

Overview

Generates realistic restaurant orders and payments with enhanced discount support

Constant Summary collapse

MEAL_PERIODS =

Meal periods with realistic distributions

{
  breakfast: { hours: 7..10, weight: 15, avg_items: 2..4, avg_party: 1..2 },
  lunch: { hours: 11..14, weight: 30, avg_items: 2..5, avg_party: 1..4 },
  happy_hour: { hours: 15..17, weight: 10, avg_items: 2..4, avg_party: 2..4 },
  dinner: { hours: 17..21, weight: 35, avg_items: 3..6, avg_party: 2..6 },
  late_night: { hours: 21..23, weight: 10, avg_items: 2..4, avg_party: 1..3 }
}.freeze
DINING_BY_PERIOD =

Dining option distributions by meal period

{
  breakfast: { "HERE" => 40, "TO_GO" => 50, "DELIVERY" => 10 },
  lunch: { "HERE" => 35, "TO_GO" => 45, "DELIVERY" => 20 },
  happy_hour: { "HERE" => 80, "TO_GO" => 15, "DELIVERY" => 5 },
  dinner: { "HERE" => 70, "TO_GO" => 15, "DELIVERY" => 15 },
  late_night: { "HERE" => 50, "TO_GO" => 30, "DELIVERY" => 20 }
}.freeze
TIP_RATES =

Tip percentages by dining option

{
  "HERE" => { min: 15, max: 25 },      # Dine-in tips higher
  "TO_GO" => { min: 0, max: 15 },       # Takeout tips lower
  "DELIVERY" => { min: 10, max: 20 }    # Delivery tips moderate
}.freeze
ORDER_PATTERNS =

Order patterns by day of week

{
  weekday: { min: 40, max: 60 },
  friday: { min: 70, max: 100 },
  saturday: { min: 80, max: 120 },
  sunday: { min: 50, max: 80 }
}.freeze
CATEGORY_PREFERENCES =

Category preferences by meal period

{
  breakfast: ["Drinks", "Sides"],
  lunch: ["Appetizers", "Entrees", "Sides", "Drinks"],
  happy_hour: ["Appetizers", "Alcoholic Beverages", "Drinks"],
  dinner: ["Appetizers", "Entrees", "Sides", "Desserts", "Alcoholic Beverages", "Drinks"],
  late_night: ["Appetizers", "Entrees", "Alcoholic Beverages", "Desserts"]
}.freeze
DISCOUNT_PROBABILITIES =

Discount application probabilities

{
  promo_code: 0.08,        # 8% use a promo code
  loyalty: 0.15,           # 15% are loyalty members
  combo: 0.12,             # 12% get combo discount
  time_based: 0.90,        # 90% of eligible get time discounts
  line_item: 0.10,         # 10% get line item discounts
  threshold: 0.20          # 20% get threshold discounts
}.freeze
GIFT_CARD_CONFIG =

Gift card configuration

{
  payment_chance: 10,       # 10% of orders may use gift cards for payment
  purchase_chance: 5,       # 5% of orders may include gift card purchase
  full_payment_chance: 60   # 60% of gift card payments cover full amount
}.freeze
REFUND_REASONS =

Refund reasons

%w[customer_request quality_issue wrong_order duplicate_charge].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(services: nil, refund_percentage: 5) ⇒ OrderGenerator

Returns a new instance of OrderGenerator.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 71

def initialize(services: nil, refund_percentage: 5)
  @services = services || Services::Clover::ServicesManager.new
  @logger = CloverSandboxSimulator.logger
  @refund_percentage = refund_percentage
  @stats = {
    orders: 0,
    revenue: 0,
    tips: 0,
    tax: 0,
    discounts: 0,
    by_period: {},
    by_dining: {},
    by_discount_type: {},
    by_order_type: {},
    gift_cards: { payments: 0, full_payments: 0, partial_payments: 0, purchases: 0, amount_redeemed: 0 },
    refunds: { total: 0, full: 0, partial: 0, amount: 0 },
    cash_events: { payments: 0, amount: 0 }
  }
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



69
70
71
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 69

def logger
  @logger
end

#refund_percentageObject (readonly)

Returns the value of attribute refund_percentage.



69
70
71
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 69

def refund_percentage
  @refund_percentage
end

#servicesObject (readonly)

Returns the value of attribute services.



69
70
71
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 69

def services
  @services
end

#statsObject (readonly)

Returns the value of attribute stats.



69
70
71
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 69

def stats
  @stats
end

Instance Method Details

#generate_for_date(date, count:) ⇒ Object

Generate specific count of orders



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
252
253
254
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 220

def generate_for_date(date, count:)
  logger.info "=" * 60
  logger.info "Generating #{count} orders for #{date}"
  logger.info "=" * 60

  data = fetch_required_data
  return [] unless data

  orders = []
  count.times do |i|
    period = weighted_random_period
    order_time = generate_order_time(date, period)
    logger.info "-" * 40
    logger.info "Creating order #{i + 1}/#{count} (#{period})"

    order = create_realistic_order(
      period: period,
      data: data,
      order_num: i + 1,
      total_in_period: count,
      order_time: order_time
    )

    if order
      orders << order
      update_stats(order, period)
    end
  end

  # Process refunds for some orders
  process_refunds(orders) if refund_percentage > 0

  print_summary
  orders
end

#generate_realistic_day(date: Date.today, multiplier: 1.0, simulated_time: nil) ⇒ Object

Generate a realistic day of restaurant operations



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
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 92

def generate_realistic_day(date: Date.today, multiplier: 1.0, simulated_time: nil)
  count = (order_count_for_date(date) * multiplier).to_i

  logger.info "=" * 60
  logger.info "Generating realistic restaurant day: #{date}"
  logger.info "    Target orders: #{count}"
  logger.info "    Day: #{date.strftime('%A')}"
  logger.info "=" * 60

  # Fetch required data
  data = fetch_required_data
  return [] unless data

  # Distribute orders across meal periods
  period_orders = distribute_orders_by_period(count)

  orders = []
  period_orders.each do |period, period_count|
    logger.info "-" * 40
    logger.info "#{period.to_s.upcase} SERVICE: #{period_count} orders"

    period_count.times do |i|
      # Generate simulated time for the order
      order_time = simulated_time || generate_order_time(date, period)

      order = create_realistic_order(
        period: period,
        data: data,
        order_num: i + 1,
        total_in_period: period_count,
        order_time: order_time
      )

      if order
        orders << order
        update_stats(order, period)
      end
    end
  end

  # Process refunds for some orders
  process_refunds(orders) if refund_percentage > 0

  print_summary
  orders
end

#generate_today(count: nil) ⇒ Object

Generate orders for today (simple mode)



211
212
213
214
215
216
217
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 211

def generate_today(count: nil)
  if count
    generate_for_date(Date.today, count: count)
  else
    generate_realistic_day
  end
end

#process_order_refund(order) ⇒ Object

Process a refund for a single order



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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
201
202
203
204
205
206
207
208
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 158

def process_order_refund(order)
  order_id = order["id"]
  payments = order.dig("payments", "elements") || []

  return if payments.empty?

  payment = payments.first
  payment_id = payment["id"]
  payment_amount = payment["amount"] || 0

  return if payment_amount <= 0

  # 60% full refunds, 40% partial refunds
  is_full_refund = rand < 0.6
  reason = REFUND_REASONS.sample

  if is_full_refund
    # Full refund
    begin
      result = services.refund.create_full_refund(payment_id: payment_id, reason: reason)
      if result
        @stats[:refunds][:total] += 1
        @stats[:refunds][:full] += 1
        @stats[:refunds][:amount] += payment_amount
        logger.info "  💸 Full refund: Order #{order_id} - $#{'%.2f' % (payment_amount / 100.0)} (#{reason})"
      end
    rescue StandardError => e
      logger.warn "  Failed to refund order #{order_id}: #{e.message}"
    end
  else
    # Partial refund (25-75% of payment)
    refund_percent = rand(25..75)
    refund_amount = (payment_amount * refund_percent / 100.0).round

    begin
      result = services.refund.create_partial_refund(
        payment_id: payment_id,
        amount: refund_amount,
        reason: reason
      )
      if result
        @stats[:refunds][:total] += 1
        @stats[:refunds][:partial] += 1
        @stats[:refunds][:amount] += refund_amount
        logger.info "  💸 Partial refund: Order #{order_id} - $#{'%.2f' % (refund_amount / 100.0)} of $#{'%.2f' % (payment_amount / 100.0)} (#{reason})"
      end
    rescue StandardError => e
      logger.warn "  Failed to partially refund order #{order_id}: #{e.message}"
    end
  end
end

#process_refunds(orders) ⇒ Object

Process refunds for a percentage of completed orders



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/clover_sandbox_simulator/generators/order_generator.rb', line 140

def process_refunds(orders)
  return if orders.empty? || refund_percentage <= 0

  refund_count = (orders.size * refund_percentage / 100.0).ceil
  refund_count = [refund_count, orders.size].min

  logger.info "-" * 40
  logger.info "PROCESSING REFUNDS: #{refund_count} orders (#{refund_percentage}%)"

  # Select random orders to refund
  orders_to_refund = orders.sample(refund_count)

  orders_to_refund.each do |order|
    process_order_refund(order)
  end
end