clover_sandbox_simulator 1.0.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 +7 -0
- data/Gemfile +10 -0
- data/README.md +316 -0
- data/bin/simulate +209 -0
- data/lib/clover_sandbox_simulator/configuration.rb +51 -0
- data/lib/clover_sandbox_simulator/data/restaurant/categories.json +39 -0
- data/lib/clover_sandbox_simulator/data/restaurant/discounts.json +39 -0
- data/lib/clover_sandbox_simulator/data/restaurant/items.json +238 -0
- data/lib/clover_sandbox_simulator/data/restaurant/modifiers.json +62 -0
- data/lib/clover_sandbox_simulator/data/restaurant/tenders.json +41 -0
- data/lib/clover_sandbox_simulator/generators/data_loader.rb +54 -0
- data/lib/clover_sandbox_simulator/generators/entity_generator.rb +164 -0
- data/lib/clover_sandbox_simulator/generators/order_generator.rb +540 -0
- data/lib/clover_sandbox_simulator/services/base_service.rb +111 -0
- data/lib/clover_sandbox_simulator/services/clover/customer_service.rb +82 -0
- data/lib/clover_sandbox_simulator/services/clover/discount_service.rb +58 -0
- data/lib/clover_sandbox_simulator/services/clover/employee_service.rb +82 -0
- data/lib/clover_sandbox_simulator/services/clover/inventory_service.rb +120 -0
- data/lib/clover_sandbox_simulator/services/clover/order_service.rb +170 -0
- data/lib/clover_sandbox_simulator/services/clover/payment_service.rb +123 -0
- data/lib/clover_sandbox_simulator/services/clover/services_manager.rb +49 -0
- data/lib/clover_sandbox_simulator/services/clover/tax_service.rb +53 -0
- data/lib/clover_sandbox_simulator/services/clover/tender_service.rb +117 -0
- data/lib/clover_sandbox_simulator.rb +43 -0
- metadata +195 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dc73877a0d3dbbce2dfe5b038167864972933519265d341665ee23646f37f5c1
|
|
4
|
+
data.tar.gz: 808ede5d13382014d7c2ab5f58379160c0983b7db8ead184032b70a5f39b453a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: afdb2f5ddd9026721f63328fb2efd4cc97799d952fea3288d8b090ca4daab696719df353eb8d515aafdfe5b62b165798cbde650cb9ffabaf7e827fae1487a321
|
|
7
|
+
data.tar.gz: 1218800960726b410b98d5b8d0ca7cfed1aed9a1f916214a257d456ca08373123724764ec04680d1eabde7ebd9f25d98d77a43301d54ee5d0fa2ca74000b7873
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Clover Sandbox Simulator
|
|
2
|
+
|
|
3
|
+
A Ruby gem for simulating Point of Sale operations in Clover sandbox environments. Generates realistic restaurant orders, payments, and transaction data for testing integrations with Clover's API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Realistic Restaurant Data**: Complete menu with 39 items across 7 categories (appetizers, entrees, sides, desserts, drinks, alcoholic beverages, specials)
|
|
8
|
+
- **Safe Sandbox Payments**: Uses Cash, Check, Gift Card, and other safe tenders (avoids broken Credit/Debit cards in Clover sandbox)
|
|
9
|
+
- **Split Payments**: Supports 1-4 tender splits per order, more common for larger parties
|
|
10
|
+
- **Meal Period Simulation**: Orders distributed across breakfast, lunch, happy hour, dinner, and late night with realistic weights
|
|
11
|
+
- **Dining Options**: Dine-in, To-Go, and Delivery with period-appropriate distributions
|
|
12
|
+
- **Dynamic Order Volume**: Different order counts for weekdays, Friday, Saturday, Sunday (40-120 orders/day)
|
|
13
|
+
- **Tips & Taxes**: Variable tip rates by dining option (15-25% dine-in, 0-15% takeout, 10-20% delivery)
|
|
14
|
+
- **Discounts**: 7 discount types including Happy Hour, Senior, Military, Employee, Birthday, and fixed amounts
|
|
15
|
+
- **Employees & Customers**: Auto-generated with realistic names and contact info
|
|
16
|
+
- **Party Size Variation**: 1-6 guests affecting item counts and split payment probability
|
|
17
|
+
- **Order Notes**: Random special instructions (allergies, modifications, VIP customers)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add this line to your application's Gemfile:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'clover_sandbox_simulator'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
And then execute:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install it yourself as:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
gem install clover_sandbox_simulator
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
Copy the sample environment file and configure your Clover credentials:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cp .env.sample .env
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Edit `.env` with your Clover sandbox credentials:
|
|
48
|
+
|
|
49
|
+
```env
|
|
50
|
+
CLOVER_MERCHANT_ID=your_merchant_id
|
|
51
|
+
CLOVER_API_TOKEN=your_api_token
|
|
52
|
+
CLOVER_ENVIRONMENT=https://sandbox.dev.clover.com/
|
|
53
|
+
LOG_LEVEL=INFO
|
|
54
|
+
TAX_RATE=8.25
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### Quick Start
|
|
60
|
+
|
|
61
|
+
Run a full simulation (setup + generate orders):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
./bin/simulate full
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Commands
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Set up restaurant entities (categories, items, discounts, etc.)
|
|
71
|
+
./bin/simulate setup
|
|
72
|
+
|
|
73
|
+
# Generate orders for today (random count based on day of week)
|
|
74
|
+
./bin/simulate generate
|
|
75
|
+
|
|
76
|
+
# Generate a specific number of orders
|
|
77
|
+
./bin/simulate generate -n 25
|
|
78
|
+
|
|
79
|
+
# Generate a realistic full day of restaurant operations
|
|
80
|
+
./bin/simulate day
|
|
81
|
+
|
|
82
|
+
# Generate a busy day (2x normal volume)
|
|
83
|
+
./bin/simulate day -m 2.0
|
|
84
|
+
|
|
85
|
+
# Generate a slow day (0.5x normal volume)
|
|
86
|
+
./bin/simulate day -m 0.5
|
|
87
|
+
|
|
88
|
+
# Generate a lunch or dinner rush
|
|
89
|
+
./bin/simulate rush -p lunch -n 20
|
|
90
|
+
./bin/simulate rush -p dinner -n 30
|
|
91
|
+
|
|
92
|
+
# Run full simulation (setup + orders)
|
|
93
|
+
./bin/simulate full
|
|
94
|
+
|
|
95
|
+
# Check current status
|
|
96
|
+
./bin/simulate status
|
|
97
|
+
|
|
98
|
+
# Delete all entities (requires confirmation)
|
|
99
|
+
./bin/simulate delete --confirm
|
|
100
|
+
|
|
101
|
+
# Enable verbose logging
|
|
102
|
+
./bin/simulate generate -v
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Menu Structure
|
|
106
|
+
|
|
107
|
+
### Categories
|
|
108
|
+
- Appetizers
|
|
109
|
+
- Entrees
|
|
110
|
+
- Sides
|
|
111
|
+
- Desserts
|
|
112
|
+
- Drinks
|
|
113
|
+
- Alcoholic Beverages
|
|
114
|
+
- Specials
|
|
115
|
+
|
|
116
|
+
### Sample Items
|
|
117
|
+
|
|
118
|
+
| Category | Item | Price |
|
|
119
|
+
|----------|------|-------|
|
|
120
|
+
| Appetizers | Buffalo Wings | $12.99 |
|
|
121
|
+
| Appetizers | Loaded Nachos | $10.99 |
|
|
122
|
+
| Entrees | Classic Burger | $14.99 |
|
|
123
|
+
| Entrees | NY Strip Steak | $28.99 |
|
|
124
|
+
| Entrees | Grilled Salmon | $21.99 |
|
|
125
|
+
| Sides | French Fries | $4.99 |
|
|
126
|
+
| Desserts | Cheesecake | $7.99 |
|
|
127
|
+
| Drinks | Soft Drink | $2.99 |
|
|
128
|
+
| Alcoholic | Draft Beer | $5.99 |
|
|
129
|
+
|
|
130
|
+
## Payment Tenders
|
|
131
|
+
|
|
132
|
+
**IMPORTANT**: Credit Card and Debit Card are **broken** in Clover sandbox. This gem intentionally avoids them.
|
|
133
|
+
|
|
134
|
+
Safe tenders used:
|
|
135
|
+
- Cash (preferred for orders under $20)
|
|
136
|
+
- Check
|
|
137
|
+
- Gift Card
|
|
138
|
+
- External Payment
|
|
139
|
+
- Mobile Payment
|
|
140
|
+
- Store Credit
|
|
141
|
+
|
|
142
|
+
The simulator uses whatever safe tenders are available in the Clover merchant account.
|
|
143
|
+
|
|
144
|
+
## Tips
|
|
145
|
+
|
|
146
|
+
Tips vary by dining option to simulate realistic customer behavior:
|
|
147
|
+
|
|
148
|
+
| Dining Option | Min Tip | Max Tip |
|
|
149
|
+
|---------------|---------|---------|
|
|
150
|
+
| Dine-In (HERE) | 15% | 25% |
|
|
151
|
+
| To-Go | 0% | 15% |
|
|
152
|
+
| Delivery | 10% | 20% |
|
|
153
|
+
|
|
154
|
+
- Large parties (6+) automatically receive 18% auto-gratuity
|
|
155
|
+
- Split payments divide tips proportionally across tenders
|
|
156
|
+
|
|
157
|
+
## Order Patterns
|
|
158
|
+
|
|
159
|
+
### Daily Volume
|
|
160
|
+
|
|
161
|
+
| Day | Min Orders | Max Orders |
|
|
162
|
+
|-----|------------|------------|
|
|
163
|
+
| Weekday | 40 | 60 |
|
|
164
|
+
| Friday | 70 | 100 |
|
|
165
|
+
| Saturday | 80 | 120 |
|
|
166
|
+
| Sunday | 50 | 80 |
|
|
167
|
+
|
|
168
|
+
### Meal Periods
|
|
169
|
+
|
|
170
|
+
Orders are distributed across realistic meal periods with weighted distribution:
|
|
171
|
+
|
|
172
|
+
| Period | Hours | Weight | Avg Items | Avg Party Size |
|
|
173
|
+
|--------|-------|--------|-----------|----------------|
|
|
174
|
+
| Breakfast | 7-10 AM | 15% | 2-4 | 1-2 |
|
|
175
|
+
| Lunch | 11 AM-2 PM | 30% | 2-5 | 1-4 |
|
|
176
|
+
| Happy Hour | 3-5 PM | 10% | 2-4 | 2-4 |
|
|
177
|
+
| Dinner | 5-9 PM | 35% | 3-6 | 2-6 |
|
|
178
|
+
| Late Night | 9-11 PM | 10% | 2-4 | 1-3 |
|
|
179
|
+
|
|
180
|
+
### Dining Options
|
|
181
|
+
|
|
182
|
+
Each meal period has different dining option distributions:
|
|
183
|
+
|
|
184
|
+
| Period | Dine-In | To-Go | Delivery |
|
|
185
|
+
|--------|---------|-------|----------|
|
|
186
|
+
| Breakfast | 40% | 50% | 10% |
|
|
187
|
+
| Lunch | 35% | 45% | 20% |
|
|
188
|
+
| Happy Hour | 80% | 15% | 5% |
|
|
189
|
+
| Dinner | 70% | 15% | 15% |
|
|
190
|
+
| Late Night | 50% | 30% | 20% |
|
|
191
|
+
|
|
192
|
+
## Architecture
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
clover_sandbox_simulator/
|
|
196
|
+
├── bin/simulate # CLI entry point
|
|
197
|
+
├── lib/
|
|
198
|
+
│ ├── clover_sandbox_simulator.rb # Gem entry point
|
|
199
|
+
│ └── clover_sandbox_simulator/
|
|
200
|
+
│ ├── configuration.rb # Environment config
|
|
201
|
+
│ ├── services/
|
|
202
|
+
│ │ ├── base_service.rb
|
|
203
|
+
│ │ └── clover/ # Clover API services
|
|
204
|
+
│ │ ├── inventory_service.rb
|
|
205
|
+
│ │ ├── order_service.rb
|
|
206
|
+
│ │ ├── payment_service.rb
|
|
207
|
+
│ │ ├── tender_service.rb
|
|
208
|
+
│ │ ├── tax_service.rb
|
|
209
|
+
│ │ ├── discount_service.rb
|
|
210
|
+
│ │ ├── employee_service.rb
|
|
211
|
+
│ │ ├── customer_service.rb
|
|
212
|
+
│ │ └── services_manager.rb
|
|
213
|
+
│ ├── generators/
|
|
214
|
+
│ │ ├── data_loader.rb
|
|
215
|
+
│ │ ├── entity_generator.rb
|
|
216
|
+
│ │ └── order_generator.rb
|
|
217
|
+
│ └── data/
|
|
218
|
+
│ └── restaurant/ # JSON data files
|
|
219
|
+
│ ├── categories.json
|
|
220
|
+
│ ├── items.json
|
|
221
|
+
│ ├── discounts.json
|
|
222
|
+
│ ├── tenders.json
|
|
223
|
+
│ └── modifiers.json
|
|
224
|
+
└── spec/ # RSpec tests
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Run all tests
|
|
231
|
+
bundle exec rspec
|
|
232
|
+
|
|
233
|
+
# Run tests with documentation format
|
|
234
|
+
bundle exec rspec --format documentation
|
|
235
|
+
|
|
236
|
+
# Run specific test file
|
|
237
|
+
bundle exec rspec spec/services/clover/tender_service_spec.rb
|
|
238
|
+
|
|
239
|
+
# Run linter
|
|
240
|
+
bundle exec rubocop
|
|
241
|
+
|
|
242
|
+
# Open console
|
|
243
|
+
bundle exec irb -r ./lib/clover_sandbox_simulator
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Testing
|
|
247
|
+
|
|
248
|
+
The gem includes comprehensive RSpec tests with WebMock for HTTP stubbing.
|
|
249
|
+
|
|
250
|
+
### Test Coverage
|
|
251
|
+
|
|
252
|
+
- **268 examples, 0 failures**
|
|
253
|
+
- Configuration validation
|
|
254
|
+
- Data loading from JSON files
|
|
255
|
+
- All Clover API services:
|
|
256
|
+
- InventoryService (categories, items)
|
|
257
|
+
- OrderService (create, line items, dining options)
|
|
258
|
+
- PaymentService (single and split payments)
|
|
259
|
+
- TenderService (safe tenders, split selection)
|
|
260
|
+
- TaxService (rates, calculation)
|
|
261
|
+
- DiscountService (percentage and fixed)
|
|
262
|
+
- EmployeeService (CRUD, random selection)
|
|
263
|
+
- CustomerService (CRUD, anonymous orders)
|
|
264
|
+
- ServicesManager (memoization, lazy loading)
|
|
265
|
+
- Entity generator idempotency
|
|
266
|
+
- Order generator (meal periods, dining options, tips)
|
|
267
|
+
- Edge cases (nil handling, empty arrays, API errors)
|
|
268
|
+
|
|
269
|
+
### Test Files
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
spec/
|
|
273
|
+
├── configuration_spec.rb
|
|
274
|
+
├── generators/
|
|
275
|
+
│ ├── data_loader_spec.rb
|
|
276
|
+
│ ├── entity_generator_spec.rb
|
|
277
|
+
│ └── order_generator_spec.rb
|
|
278
|
+
└── services/clover/
|
|
279
|
+
├── customer_service_spec.rb
|
|
280
|
+
├── discount_service_spec.rb
|
|
281
|
+
├── employee_service_spec.rb
|
|
282
|
+
├── inventory_service_spec.rb
|
|
283
|
+
├── order_service_spec.rb
|
|
284
|
+
├── payment_service_spec.rb
|
|
285
|
+
├── services_manager_spec.rb
|
|
286
|
+
├── tax_service_spec.rb
|
|
287
|
+
└── tender_service_spec.rb
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Idempotency Verification
|
|
291
|
+
|
|
292
|
+
All setup operations are idempotent - running them multiple times will not create duplicates:
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
# This is safe to run multiple times
|
|
296
|
+
generator = CloverSandboxSimulator::Generators::EntityGenerator.new
|
|
297
|
+
generator.setup_all # First run: creates entities
|
|
298
|
+
generator.setup_all # Second run: skips existing, returns same results
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The tests verify:
|
|
302
|
+
- Categories are not duplicated
|
|
303
|
+
- Items are not duplicated
|
|
304
|
+
- Discounts are not duplicated
|
|
305
|
+
- Employees/customers only created if count threshold not met
|
|
306
|
+
|
|
307
|
+
## Clover API Notes
|
|
308
|
+
|
|
309
|
+
- **Sandbox URL**: `https://sandbox.dev.clover.com/`
|
|
310
|
+
- **API Version**: v3
|
|
311
|
+
- **Authentication**: Bearer token (OAuth)
|
|
312
|
+
- **Date Limitation**: Clover sandbox only allows creating orders for TODAY
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT License
|
data/bin/simulate
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "clover_sandbox_simulator"
|
|
6
|
+
require "thor"
|
|
7
|
+
|
|
8
|
+
module CloverSandboxSimulator
|
|
9
|
+
# Command-line interface for Clover Sandbox Simulator
|
|
10
|
+
class CLI < Thor
|
|
11
|
+
class_option :verbose, type: :boolean, aliases: "-v", desc: "Enable verbose logging"
|
|
12
|
+
|
|
13
|
+
desc "setup", "Set up restaurant entities (categories, items, discounts, employees, customers)"
|
|
14
|
+
option :business_type, type: :string, default: "restaurant", desc: "Business type (restaurant, retail, salon)"
|
|
15
|
+
def setup
|
|
16
|
+
configure_logging
|
|
17
|
+
|
|
18
|
+
puts "🍽️ Clover Sandbox Simulator - Entity Setup"
|
|
19
|
+
puts "=" * 50
|
|
20
|
+
|
|
21
|
+
generator = Generators::EntityGenerator.new(business_type: options[:business_type].to_sym)
|
|
22
|
+
generator.setup_all
|
|
23
|
+
|
|
24
|
+
puts "\n✅ Setup complete!"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc "generate", "Generate orders for today"
|
|
28
|
+
option :count, type: :numeric, aliases: "-n", desc: "Number of orders to generate"
|
|
29
|
+
def generate
|
|
30
|
+
configure_logging
|
|
31
|
+
|
|
32
|
+
puts "🍽️ Clover Sandbox Simulator - Order Generation"
|
|
33
|
+
puts "=" * 50
|
|
34
|
+
|
|
35
|
+
generator = Generators::OrderGenerator.new
|
|
36
|
+
count = options[:count]
|
|
37
|
+
|
|
38
|
+
orders = generator.generate_today(count: count)
|
|
39
|
+
|
|
40
|
+
puts "\n✅ Generated #{orders.size} orders!"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "day", "Generate a realistic full day of restaurant operations"
|
|
44
|
+
option :multiplier, type: :numeric, aliases: "-m", default: 1.0, desc: "Order multiplier (0.5 = slow day, 2.0 = busy day)"
|
|
45
|
+
def day
|
|
46
|
+
configure_logging
|
|
47
|
+
|
|
48
|
+
puts "🍽️ Clover Sandbox Simulator - Realistic Restaurant Day"
|
|
49
|
+
puts "=" * 50
|
|
50
|
+
|
|
51
|
+
generator = Generators::OrderGenerator.new
|
|
52
|
+
orders = generator.generate_realistic_day(multiplier: options[:multiplier])
|
|
53
|
+
|
|
54
|
+
puts "\n✅ Generated #{orders.size} orders!"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "rush", "Generate a lunch or dinner rush"
|
|
58
|
+
option :period, type: :string, aliases: "-p", default: "dinner", desc: "Meal period (breakfast, lunch, happy_hour, dinner, late_night)"
|
|
59
|
+
option :count, type: :numeric, aliases: "-n", default: 15, desc: "Number of orders"
|
|
60
|
+
def rush
|
|
61
|
+
configure_logging
|
|
62
|
+
|
|
63
|
+
period = options[:period].to_sym
|
|
64
|
+
count = options[:count]
|
|
65
|
+
|
|
66
|
+
puts "🍽️ Clover Sandbox Simulator - #{period.to_s.upcase} Rush"
|
|
67
|
+
puts "=" * 50
|
|
68
|
+
|
|
69
|
+
generator = Generators::OrderGenerator.new
|
|
70
|
+
data = generator.send(:fetch_required_data)
|
|
71
|
+
|
|
72
|
+
unless data
|
|
73
|
+
puts "❌ Failed to fetch data. Run setup first."
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
orders = []
|
|
78
|
+
count.times do |i|
|
|
79
|
+
order = generator.send(:create_realistic_order,
|
|
80
|
+
period: period,
|
|
81
|
+
data: data,
|
|
82
|
+
order_num: i + 1,
|
|
83
|
+
total_in_period: count
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if order
|
|
87
|
+
orders << order
|
|
88
|
+
generator.send(:update_stats, order, period)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
generator.send(:print_summary)
|
|
93
|
+
puts "\n✅ Generated #{orders.size} #{period} orders!"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
desc "full", "Run full simulation (setup + generate orders)"
|
|
97
|
+
option :count, type: :numeric, aliases: "-n", desc: "Number of orders to generate"
|
|
98
|
+
option :business_type, type: :string, default: "restaurant", desc: "Business type"
|
|
99
|
+
def full
|
|
100
|
+
configure_logging
|
|
101
|
+
|
|
102
|
+
puts "🍽️ Clover Sandbox Simulator - Full Simulation"
|
|
103
|
+
puts "=" * 50
|
|
104
|
+
|
|
105
|
+
# Setup entities
|
|
106
|
+
entity_gen = Generators::EntityGenerator.new(business_type: options[:business_type].to_sym)
|
|
107
|
+
entity_gen.setup_all
|
|
108
|
+
|
|
109
|
+
puts "\n"
|
|
110
|
+
|
|
111
|
+
# Generate orders
|
|
112
|
+
order_gen = Generators::OrderGenerator.new
|
|
113
|
+
orders = order_gen.generate_today(count: options[:count])
|
|
114
|
+
|
|
115
|
+
puts "\n✅ Full simulation complete!"
|
|
116
|
+
puts " Orders generated: #{orders.size}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
desc "delete", "Delete all entities (use with caution!)"
|
|
120
|
+
option :confirm, type: :boolean, desc: "Confirm deletion"
|
|
121
|
+
def delete
|
|
122
|
+
unless options[:confirm]
|
|
123
|
+
puts "⚠️ This will delete ALL entities from Clover!"
|
|
124
|
+
puts " Run with --confirm to proceed."
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
configure_logging
|
|
129
|
+
|
|
130
|
+
puts "🗑️ Deleting all entities..."
|
|
131
|
+
|
|
132
|
+
generator = Generators::EntityGenerator.new
|
|
133
|
+
generator.delete_all
|
|
134
|
+
|
|
135
|
+
puts "\n✅ All entities deleted!"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
desc "status", "Show current Clover merchant status"
|
|
139
|
+
def status
|
|
140
|
+
configure_logging
|
|
141
|
+
|
|
142
|
+
puts "🍽️ Clover Sandbox Simulator - Status"
|
|
143
|
+
puts "=" * 50
|
|
144
|
+
|
|
145
|
+
services = Services::Clover::ServicesManager.new
|
|
146
|
+
|
|
147
|
+
categories = services.inventory.get_categories
|
|
148
|
+
items = services.inventory.get_items
|
|
149
|
+
tenders = services.tender.get_safe_tenders
|
|
150
|
+
employees = services.employee.get_employees
|
|
151
|
+
customers = services.customer.get_customers
|
|
152
|
+
discounts = services.discount.get_discounts
|
|
153
|
+
|
|
154
|
+
puts "Categories: #{categories.size}"
|
|
155
|
+
puts "Menu Items: #{items.size}"
|
|
156
|
+
puts "Tenders: #{tenders.size} (sandbox-safe)"
|
|
157
|
+
puts "Employees: #{employees.size}"
|
|
158
|
+
puts "Customers: #{customers.size}"
|
|
159
|
+
puts "Discounts: #{discounts.size}"
|
|
160
|
+
|
|
161
|
+
puts "\n📋 Categories:"
|
|
162
|
+
categories.each { |c| puts " - #{c['name']}" }
|
|
163
|
+
|
|
164
|
+
puts "\n💳 Safe Tenders (no credit/debit):"
|
|
165
|
+
tenders.each { |t| puts " - #{t['label']}" }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
desc "version", "Show version"
|
|
169
|
+
def version
|
|
170
|
+
puts "Clover Sandbox Simulator v1.0.0"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
def configure_logging
|
|
176
|
+
if options[:verbose]
|
|
177
|
+
CloverSandboxSimulator.configuration.logger.level = Logger::DEBUG
|
|
178
|
+
else
|
|
179
|
+
CloverSandboxSimulator.configuration.logger.level = Logger::INFO
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def print_order_summary(orders)
|
|
184
|
+
return if orders.empty?
|
|
185
|
+
|
|
186
|
+
total_revenue = 0
|
|
187
|
+
total_tips = 0
|
|
188
|
+
total_tax = 0
|
|
189
|
+
|
|
190
|
+
orders.each do |order|
|
|
191
|
+
next unless order && order["payments"]&.dig("elements")
|
|
192
|
+
|
|
193
|
+
order["payments"]["elements"].each do |payment|
|
|
194
|
+
total_revenue += payment["amount"] || 0
|
|
195
|
+
total_tips += payment["tipAmount"] || 0
|
|
196
|
+
total_tax += payment["taxAmount"] || 0
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
puts "\n📊 Summary:"
|
|
201
|
+
puts " Revenue: $#{total_revenue / 100.0}"
|
|
202
|
+
puts " Tips: $#{total_tips / 100.0}"
|
|
203
|
+
puts " Tax: $#{total_tax / 100.0}"
|
|
204
|
+
puts " Total: $#{(total_revenue + total_tips + total_tax) / 100.0}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
CloverSandboxSimulator::CLI.start(ARGV)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloverSandboxSimulator
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :merchant_id, :api_token, :environment, :log_level, :tax_rate, :business_type
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@merchant_id = ENV.fetch("CLOVER_MERCHANT_ID", nil)
|
|
9
|
+
@api_token = ENV.fetch("CLOVER_API_TOKEN", nil)
|
|
10
|
+
@environment = normalize_url(ENV.fetch("CLOVER_ENVIRONMENT", "https://sandbox.dev.clover.com/"))
|
|
11
|
+
@log_level = parse_log_level(ENV.fetch("LOG_LEVEL", "INFO"))
|
|
12
|
+
@tax_rate = ENV.fetch("TAX_RATE", "8.25").to_f
|
|
13
|
+
@business_type = ENV.fetch("BUSINESS_TYPE", "restaurant").to_sym
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate!
|
|
17
|
+
raise ConfigurationError, "CLOVER_MERCHANT_ID is required" if merchant_id.nil? || merchant_id.empty?
|
|
18
|
+
raise ConfigurationError, "CLOVER_API_TOKEN is required" if api_token.nil? || api_token.empty?
|
|
19
|
+
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def logger
|
|
24
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
|
25
|
+
log.level = @log_level
|
|
26
|
+
log.formatter = proc do |severity, datetime, _progname, msg|
|
|
27
|
+
timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S")
|
|
28
|
+
"[#{timestamp}] #{severity.ljust(5)} | #{msg}\n"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def normalize_url(url)
|
|
36
|
+
url = url.strip
|
|
37
|
+
url.end_with?("/") ? url : "#{url}/"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_log_level(level)
|
|
41
|
+
case level.to_s.upcase
|
|
42
|
+
when "DEBUG" then Logger::DEBUG
|
|
43
|
+
when "INFO" then Logger::INFO
|
|
44
|
+
when "WARN" then Logger::WARN
|
|
45
|
+
when "ERROR" then Logger::ERROR
|
|
46
|
+
when "FATAL" then Logger::FATAL
|
|
47
|
+
else Logger::INFO
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": [
|
|
3
|
+
{
|
|
4
|
+
"name": "Appetizers",
|
|
5
|
+
"sort_order": 1,
|
|
6
|
+
"description": "Start your meal right"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "Entrees",
|
|
10
|
+
"sort_order": 2,
|
|
11
|
+
"description": "Main courses"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Sides",
|
|
15
|
+
"sort_order": 3,
|
|
16
|
+
"description": "Perfect accompaniments"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "Desserts",
|
|
20
|
+
"sort_order": 4,
|
|
21
|
+
"description": "Sweet endings"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "Drinks",
|
|
25
|
+
"sort_order": 5,
|
|
26
|
+
"description": "Non-alcoholic beverages"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "Alcoholic Beverages",
|
|
30
|
+
"sort_order": 6,
|
|
31
|
+
"description": "Beer, wine, and cocktails"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Specials",
|
|
35
|
+
"sort_order": 7,
|
|
36
|
+
"description": "Chef's daily specials"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"discounts": [
|
|
3
|
+
{
|
|
4
|
+
"name": "Happy Hour",
|
|
5
|
+
"percentage": 15,
|
|
6
|
+
"description": "15% off during happy hour (3-6 PM)"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "Senior Discount",
|
|
10
|
+
"percentage": 10,
|
|
11
|
+
"description": "10% off for seniors 65+"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Military Discount",
|
|
15
|
+
"percentage": 15,
|
|
16
|
+
"description": "15% off for active duty and veterans"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "Employee Discount",
|
|
20
|
+
"percentage": 25,
|
|
21
|
+
"description": "25% off for employees"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "Birthday Special",
|
|
25
|
+
"percentage": 20,
|
|
26
|
+
"description": "20% off for birthday celebrations"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "$5 Off",
|
|
30
|
+
"amount": 500,
|
|
31
|
+
"description": "$5 off orders over $30"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "$10 Off",
|
|
35
|
+
"amount": 1000,
|
|
36
|
+
"description": "$10 off orders over $50"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|