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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 74de5928a11de82d6c3b389aec6fd92a4c63844eb55ad22bbfafecb805e4aca8
|
|
4
|
+
data.tar.gz: 2f11d97b104c0a58a37266b9792157d497ed30ff10fd39bb7bca964239893dc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|