clover_sandbox_simulator 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cbba3e7549afdbb163ab890407d2627bc9fda35641bf4b56395d11ff36a89c6
4
- data.tar.gz: a97d72dc86001e853542ca055142f788b00c79f15c60f5bb13cd662b56c5a4e1
3
+ metadata.gz: 74de5928a11de82d6c3b389aec6fd92a4c63844eb55ad22bbfafecb805e4aca8
4
+ data.tar.gz: 2f11d97b104c0a58a37266b9792157d497ed30ff10fd39bb7bca964239893dc9
5
5
  SHA512:
6
- metadata.gz: f2c979158af6fea16d2d603583a2079ab63eaf6506922baa5c4885d2467c7d828edf140ddd9297657f9735d8ac46bb1ca15ebb1087c4d45d77d4572ef0ebc354
7
- data.tar.gz: c3247b3bbd45f6b2cff03453c7a63b7594a80450f36e4d94d2e832579812231a0c3a61db89fc7841312fbbae258e488cc0fa4a54be4e96a4f1cdab28ecbdc59e
6
+ metadata.gz: 5ff02a1e8a9e90d8230c6af743887851e65c0eb266f9574571cdbcc3e973f06c719abb5fa64b164cdc14b473ee48355f0f822149ba89a0c543c36afa5b97eb68
7
+ data.tar.gz: 2ae75ceca9d7d77e762fa5fbb94361cd85bea8bdd3e4b8a8c15bc9461aa228f941096e7c079d681bbce4722068b068ecae27496562875fc9019e715dfe8e1167
data/README.md CHANGED
@@ -160,6 +160,14 @@ Run a full simulation (setup + generate orders):
160
160
  ./bin/simulate cash_open_drawer -a 10000 # Open drawer with $100 starting cash
161
161
  ./bin/simulate cash_close_drawer -e EMP_ID # Close drawer for employee
162
162
 
163
+ # List recent orders
164
+ ./bin/simulate orders
165
+ ./bin/simulate orders -l 50 # Show 50 orders
166
+
167
+ # Reset orders (delete all orders, keep menu/employees)
168
+ ./bin/simulate reset_orders --confirm # Delete today's orders
169
+ ./bin/simulate reset_orders --confirm --no-today-only # Delete ALL orders
170
+
163
171
  # Delete all entities (requires confirmation)
164
172
  ./bin/simulate delete --confirm
165
173
 
data/bin/simulate CHANGED
@@ -167,6 +167,79 @@ module CloverSandboxSimulator
167
167
  puts "\n✅ All entities deleted!"
168
168
  end
169
169
 
170
+ desc "reset_orders", "Delete all orders (keeps menu items, employees, etc.)"
171
+ option :confirm, type: :boolean, desc: "Confirm deletion"
172
+ option :today_only, type: :boolean, default: true, desc: "Only delete today's orders (default: true)"
173
+ def reset_orders
174
+ unless options[:confirm]
175
+ puts "⚠️ This will delete all orders from Clover!"
176
+ puts " Your menu items, employees, customers, and other entities will be kept."
177
+ puts " Run with --confirm to proceed."
178
+ return
179
+ end
180
+
181
+ configure_logging
182
+
183
+ puts "🗑️ Clover Sandbox Simulator - Reset Orders"
184
+ puts "=" * 50
185
+
186
+ services = Services::Clover::ServicesManager.new
187
+
188
+ # First, show what will be deleted
189
+ if options[:today_only]
190
+ puts "Deleting today's orders..."
191
+ count = services.order.delete_orders_since
192
+ else
193
+ puts "Deleting ALL orders..."
194
+ count = services.order.delete_all_orders
195
+ end
196
+
197
+ puts "\n✅ Deleted #{count} orders!"
198
+ puts " Your menu, employees, and customers are still intact."
199
+ puts " Run 'simulate generate' to create new orders."
200
+ end
201
+
202
+ desc "orders", "List recent orders"
203
+ option :limit, type: :numeric, aliases: "-l", default: 20, desc: "Number of orders to show"
204
+ def orders
205
+ configure_logging
206
+
207
+ puts "📋 Clover Sandbox Simulator - Recent Orders"
208
+ puts "=" * 50
209
+
210
+ services = Services::Clover::ServicesManager.new
211
+ orders = services.order.get_orders(limit: options[:limit])
212
+
213
+ if orders.empty?
214
+ puts "No orders found."
215
+ puts "\nRun 'simulate generate' to create orders."
216
+ return
217
+ end
218
+
219
+ puts "\nFound #{orders.size} orders:\n\n"
220
+
221
+ total_revenue = 0
222
+ orders.each do |order|
223
+ created = order["createdTime"] ? Time.at(order["createdTime"] / 1000).strftime("%H:%M:%S") : "N/A"
224
+ total = order["total"] || 0
225
+ total_revenue += total
226
+ state = order["state"] || "unknown"
227
+ title = order["title"] || "Order"
228
+
229
+ state_emoji = case state
230
+ when "open" then "🟡"
231
+ when "locked" then "🔒"
232
+ when "paid" then "✅"
233
+ else "⚪"
234
+ end
235
+
236
+ puts "#{state_emoji} #{title} | $#{'%.2f' % (total / 100.0)} | #{created} | #{order['id']}"
237
+ end
238
+
239
+ puts "\n" + "-" * 50
240
+ puts "Total: $#{'%.2f' % (total_revenue / 100.0)} across #{orders.size} orders"
241
+ end
242
+
170
243
  desc "status", "Show current Clover merchant status"
171
244
  def status
172
245
  configure_logging
@@ -4,7 +4,10 @@ module CloverSandboxSimulator
4
4
  class Configuration
5
5
  attr_accessor :merchant_id, :merchant_name, :api_token, :environment, :log_level, :tax_rate, :business_type,
6
6
  :public_token, :private_token, :ecommerce_environment, :tokenizer_environment,
7
- :app_id, :app_secret, :refresh_token
7
+ :app_id, :app_secret, :refresh_token, :merchant_timezone
8
+
9
+ # Default timezone if not fetched from Clover
10
+ DEFAULT_TIMEZONE = "America/Los_Angeles"
8
11
 
9
12
  # Path to merchants JSON file
10
13
  MERCHANTS_FILE = File.join(File.dirname(__FILE__), "..", "..", ".env.json")
@@ -124,6 +127,51 @@ module CloverSandboxSimulator
124
127
  end
125
128
  end
126
129
 
130
+ # Fetch merchant timezone from Clover API
131
+ # @return [String] IANA timezone identifier (e.g., "America/Los_Angeles")
132
+ def fetch_merchant_timezone
133
+ return @merchant_timezone if @merchant_timezone
134
+
135
+ require "rest-client"
136
+ require "json"
137
+
138
+ url = "#{environment}v3/merchants/#{merchant_id}?expand=properties"
139
+ response = RestClient.get(url, { Authorization: "Bearer #{api_token}" })
140
+ data = JSON.parse(response.body)
141
+
142
+ @merchant_timezone = data.dig("properties", "timezone") || DEFAULT_TIMEZONE
143
+ logger.info "Merchant timezone: #{@merchant_timezone}"
144
+ @merchant_timezone
145
+ rescue StandardError => e
146
+ logger.warn "Failed to fetch merchant timezone: #{e.message}. Using default: #{DEFAULT_TIMEZONE}"
147
+ @merchant_timezone = DEFAULT_TIMEZONE
148
+ end
149
+
150
+ # Get current time in merchant's timezone
151
+ # @return [Time] Current time in merchant timezone
152
+ def merchant_time_now
153
+ require "time"
154
+ tz = fetch_merchant_timezone
155
+ # Use TZInfo if available, otherwise use ENV['TZ'] trick
156
+ begin
157
+ require "tzinfo"
158
+ TZInfo::Timezone.get(tz).now
159
+ rescue LoadError
160
+ # Fallback: temporarily set TZ environment variable
161
+ old_tz = ENV["TZ"]
162
+ ENV["TZ"] = tz
163
+ time = Time.now
164
+ ENV["TZ"] = old_tz
165
+ time
166
+ end
167
+ end
168
+
169
+ # Get today's date in merchant's timezone
170
+ # @return [Date] Today's date in merchant timezone
171
+ def merchant_date_today
172
+ merchant_time_now.to_date
173
+ end
174
+
127
175
  private
128
176
 
129
177
  def load_from_merchants_file
@@ -89,7 +89,12 @@ module CloverSandboxSimulator
89
89
  end
90
90
 
91
91
  # Generate a realistic day of restaurant operations
92
- def generate_realistic_day(date: Date.today, multiplier: 1.0, simulated_time: nil)
92
+ # @param date [Date] Date to generate orders for (defaults to today in merchant timezone)
93
+ # @param multiplier [Float] Multiplier for order count (0.5 = slow day, 2.0 = busy day)
94
+ # @param simulated_time [Time] Override time for all orders (for testing)
95
+ def generate_realistic_day(date: nil, multiplier: 1.0, simulated_time: nil)
96
+ # Use merchant timezone for "today"
97
+ date ||= config.merchant_date_today
93
98
  count = (order_count_for_date(date) * multiplier).to_i
94
99
 
95
100
  logger.info "=" * 60
@@ -208,11 +213,13 @@ module CloverSandboxSimulator
208
213
  end
209
214
 
210
215
  # Generate orders for today (simple mode)
216
+ # Uses merchant timezone for "today"
211
217
  def generate_today(count: nil)
218
+ today = config.merchant_date_today
212
219
  if count
213
- generate_for_date(Date.today, count: count)
220
+ generate_for_date(today, count: count)
214
221
  else
215
- generate_realistic_day
222
+ generate_realistic_day(date: today)
216
223
  end
217
224
  end
218
225
 
@@ -357,7 +364,17 @@ module CloverSandboxSimulator
357
364
  hours = MEAL_PERIODS[period][:hours]
358
365
  hour = rand(hours)
359
366
  minute = rand(60)
360
- Time.new(date.year, date.month, date.day, hour, minute, 0)
367
+
368
+ # Use merchant timezone for generating order times
369
+ tz_identifier = config.fetch_merchant_timezone
370
+ begin
371
+ require "tzinfo"
372
+ tz = TZInfo::Timezone.get(tz_identifier)
373
+ tz.local_time(date.year, date.month, date.day, hour, minute, 0)
374
+ rescue LoadError
375
+ # Fallback if TZInfo not available
376
+ Time.new(date.year, date.month, date.day, hour, minute, 0)
377
+ end
361
378
  end
362
379
 
363
380
  def create_realistic_order(period:, data:, order_num:, total_in_period:, order_time: nil)
@@ -250,6 +250,73 @@ module CloverSandboxSimulator
250
250
  logger.info "Deleting order: #{order_id}"
251
251
  request(:delete, endpoint("orders/#{order_id}"))
252
252
  end
253
+
254
+ # Delete all orders for today
255
+ # @param batch_size [Integer] Number of orders to fetch per batch
256
+ # @return [Integer] Total number of orders deleted
257
+ def delete_all_orders(batch_size: 100)
258
+ logger.warn "Deleting all orders..."
259
+ total_deleted = 0
260
+ offset = 0
261
+
262
+ loop do
263
+ orders = get_orders(limit: batch_size, offset: 0)
264
+ break if orders.empty?
265
+
266
+ orders.each do |order|
267
+ begin
268
+ delete_order(order["id"])
269
+ total_deleted += 1
270
+ logger.debug "Deleted order #{order['id']}"
271
+ rescue StandardError => e
272
+ logger.warn "Failed to delete order #{order['id']}: #{e.message}"
273
+ end
274
+ end
275
+
276
+ # Safety: if we got fewer than batch_size, we're done
277
+ break if orders.size < batch_size
278
+ end
279
+
280
+ logger.info "Deleted #{total_deleted} orders"
281
+ total_deleted
282
+ end
283
+
284
+ # Delete orders created within a time range
285
+ # @param since [Time] Start time (default: beginning of today)
286
+ # @param until_time [Time] End time (default: now)
287
+ # @return [Integer] Total number of orders deleted
288
+ def delete_orders_since(since: nil, until_time: nil)
289
+ since ||= Time.now.to_date.to_time
290
+ until_time ||= Time.now
291
+
292
+ logger.warn "Deleting orders from #{since} to #{until_time}..."
293
+
294
+ # Clover uses millisecond timestamps
295
+ since_ms = (since.to_f * 1000).to_i
296
+
297
+ total_deleted = 0
298
+
299
+ loop do
300
+ # Filter by createdTime - only use >= filter (simpler and works)
301
+ filter = "createdTime>=#{since_ms}"
302
+ orders = get_orders(limit: 100, filter: filter)
303
+ break if orders.empty?
304
+
305
+ orders.each do |order|
306
+ begin
307
+ delete_order(order["id"])
308
+ total_deleted += 1
309
+ rescue StandardError => e
310
+ logger.warn "Failed to delete order #{order['id']}: #{e.message}"
311
+ end
312
+ end
313
+
314
+ break if orders.size < 100
315
+ end
316
+
317
+ logger.info "Deleted #{total_deleted} orders"
318
+ total_deleted
319
+ end
253
320
  end
254
321
  end
255
322
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clover_sandbox_simulator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dan1d
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-04 00:00:00.000000000 Z
11
+ date: 2026-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: tzinfo
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rspec
113
127
  requirement: !ruby/object:Gem::Requirement