square_sandbox_simulator 0.1.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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +176 -0
  4. data/bin/simulate +388 -0
  5. data/lib/square_sandbox_simulator/configuration.rb +193 -0
  6. data/lib/square_sandbox_simulator/data/cafe_bakery/categories.json +54 -0
  7. data/lib/square_sandbox_simulator/data/cafe_bakery/combos.json +33 -0
  8. data/lib/square_sandbox_simulator/data/cafe_bakery/coupon_codes.json +133 -0
  9. data/lib/square_sandbox_simulator/data/cafe_bakery/discounts.json +113 -0
  10. data/lib/square_sandbox_simulator/data/cafe_bakery/items.json +55 -0
  11. data/lib/square_sandbox_simulator/data/cafe_bakery/modifiers.json +73 -0
  12. data/lib/square_sandbox_simulator/data/cafe_bakery/tax_rates.json +26 -0
  13. data/lib/square_sandbox_simulator/data/cafe_bakery/tenders.json +41 -0
  14. data/lib/square_sandbox_simulator/data/restaurant/categories.json +54 -0
  15. data/lib/square_sandbox_simulator/data/restaurant/combos.json +265 -0
  16. data/lib/square_sandbox_simulator/data/restaurant/coupon_codes.json +266 -0
  17. data/lib/square_sandbox_simulator/data/restaurant/discounts.json +198 -0
  18. data/lib/square_sandbox_simulator/data/restaurant/gift_cards.json +82 -0
  19. data/lib/square_sandbox_simulator/data/restaurant/items.json +388 -0
  20. data/lib/square_sandbox_simulator/data/restaurant/modifiers.json +62 -0
  21. data/lib/square_sandbox_simulator/data/restaurant/tax_rates.json +38 -0
  22. data/lib/square_sandbox_simulator/data/restaurant/tenders.json +41 -0
  23. data/lib/square_sandbox_simulator/data/salon_spa/categories.json +24 -0
  24. data/lib/square_sandbox_simulator/data/salon_spa/combos.json +88 -0
  25. data/lib/square_sandbox_simulator/data/salon_spa/coupon_codes.json +96 -0
  26. data/lib/square_sandbox_simulator/data/salon_spa/discounts.json +93 -0
  27. data/lib/square_sandbox_simulator/data/salon_spa/gift_cards.json +47 -0
  28. data/lib/square_sandbox_simulator/data/salon_spa/items.json +100 -0
  29. data/lib/square_sandbox_simulator/data/salon_spa/modifiers.json +49 -0
  30. data/lib/square_sandbox_simulator/data/salon_spa/tax_rates.json +17 -0
  31. data/lib/square_sandbox_simulator/data/salon_spa/tenders.json +41 -0
  32. data/lib/square_sandbox_simulator/database.rb +224 -0
  33. data/lib/square_sandbox_simulator/db/factories/api_requests.rb +95 -0
  34. data/lib/square_sandbox_simulator/db/factories/business_types.rb +178 -0
  35. data/lib/square_sandbox_simulator/db/factories/categories.rb +379 -0
  36. data/lib/square_sandbox_simulator/db/factories/daily_summaries.rb +56 -0
  37. data/lib/square_sandbox_simulator/db/factories/items.rb +1526 -0
  38. data/lib/square_sandbox_simulator/db/factories/simulated_orders.rb +112 -0
  39. data/lib/square_sandbox_simulator/db/factories/simulated_payments.rb +61 -0
  40. data/lib/square_sandbox_simulator/db/migrate/20260312000000_enable_pgcrypto.rb +7 -0
  41. data/lib/square_sandbox_simulator/db/migrate/20260312000001_create_business_types.rb +18 -0
  42. data/lib/square_sandbox_simulator/db/migrate/20260312000002_create_categories.rb +18 -0
  43. data/lib/square_sandbox_simulator/db/migrate/20260312000003_create_items.rb +23 -0
  44. data/lib/square_sandbox_simulator/db/migrate/20260312000004_create_simulated_orders.rb +36 -0
  45. data/lib/square_sandbox_simulator/db/migrate/20260312000005_create_simulated_payments.rb +26 -0
  46. data/lib/square_sandbox_simulator/db/migrate/20260312000006_create_api_requests.rb +27 -0
  47. data/lib/square_sandbox_simulator/db/migrate/20260312000007_create_daily_summaries.rb +24 -0
  48. data/lib/square_sandbox_simulator/generators/data_loader.rb +202 -0
  49. data/lib/square_sandbox_simulator/generators/entity_generator.rb +248 -0
  50. data/lib/square_sandbox_simulator/generators/order_generator.rb +632 -0
  51. data/lib/square_sandbox_simulator/models/api_request.rb +43 -0
  52. data/lib/square_sandbox_simulator/models/business_type.rb +25 -0
  53. data/lib/square_sandbox_simulator/models/category.rb +18 -0
  54. data/lib/square_sandbox_simulator/models/daily_summary.rb +68 -0
  55. data/lib/square_sandbox_simulator/models/item.rb +33 -0
  56. data/lib/square_sandbox_simulator/models/record.rb +16 -0
  57. data/lib/square_sandbox_simulator/models/simulated_order.rb +42 -0
  58. data/lib/square_sandbox_simulator/models/simulated_payment.rb +28 -0
  59. data/lib/square_sandbox_simulator/seeder.rb +242 -0
  60. data/lib/square_sandbox_simulator/services/base_service.rb +253 -0
  61. data/lib/square_sandbox_simulator/services/square/catalog_service.rb +203 -0
  62. data/lib/square_sandbox_simulator/services/square/customer_service.rb +130 -0
  63. data/lib/square_sandbox_simulator/services/square/order_service.rb +121 -0
  64. data/lib/square_sandbox_simulator/services/square/payment_service.rb +136 -0
  65. data/lib/square_sandbox_simulator/services/square/services_manager.rb +68 -0
  66. data/lib/square_sandbox_simulator/services/square/team_service.rb +108 -0
  67. data/lib/square_sandbox_simulator/version.rb +5 -0
  68. data/lib/square_sandbox_simulator.rb +47 -0
  69. metadata +348 -0
@@ -0,0 +1,379 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :category, class: "SquareSandboxSimulator::Models::Category" do
5
+ association :business_type
6
+ sequence(:name) { |n| "Category #{n}" }
7
+ sort_order { 0 }
8
+
9
+ # ── Restaurant ────────────────────────────────────────────
10
+
11
+ trait :appetizers do
12
+ name { "Appetizers" }
13
+ sort_order { 1 }
14
+ description { "Starters and shareables" }
15
+ tax_group { "food" }
16
+ association :business_type, factory: %i[business_type restaurant]
17
+ end
18
+
19
+ trait :entrees do
20
+ name { "Entrees" }
21
+ sort_order { 2 }
22
+ description { "Main courses" }
23
+ tax_group { "food" }
24
+ association :business_type, factory: %i[business_type restaurant]
25
+ end
26
+
27
+ trait :sides do
28
+ name { "Sides" }
29
+ sort_order { 3 }
30
+ description { "Side dishes" }
31
+ tax_group { "food" }
32
+ association :business_type, factory: %i[business_type restaurant]
33
+ end
34
+
35
+ trait :desserts do
36
+ name { "Desserts" }
37
+ sort_order { 4 }
38
+ description { "Sweet finishes" }
39
+ tax_group { "food" }
40
+ association :business_type, factory: %i[business_type restaurant]
41
+ end
42
+
43
+ trait :beverages do
44
+ name { "Beverages" }
45
+ sort_order { 5 }
46
+ description { "Non-alcoholic drinks" }
47
+ tax_group { "beverage" }
48
+ association :business_type, factory: %i[business_type restaurant]
49
+ end
50
+
51
+ # ── Cafe & Bakery ─────────────────────────────────────────
52
+ # NOTE: Intentional duplicates to mirror real Square merchants!
53
+ # Many categories share names, and items appear in multiple places.
54
+
55
+ trait :coffee_espresso do
56
+ name { "Coffee & Espresso" }
57
+ sort_order { 1 }
58
+ description { "Hot and cold coffee drinks" }
59
+ tax_group { "beverage" }
60
+ association :business_type, factory: %i[business_type cafe_bakery]
61
+ end
62
+
63
+ trait :pastries do
64
+ name { "Pastries & Baked Goods" }
65
+ sort_order { 2 }
66
+ description { "Freshly baked daily" }
67
+ tax_group { "food" }
68
+ association :business_type, factory: %i[business_type cafe_bakery]
69
+ end
70
+
71
+ trait :breakfast do
72
+ name { "Breakfast" }
73
+ sort_order { 3 }
74
+ description { "Morning plates and bowls" }
75
+ tax_group { "food" }
76
+ association :business_type, factory: %i[business_type cafe_bakery]
77
+ end
78
+
79
+ trait :sandwiches do
80
+ name { "Sandwiches & Wraps" }
81
+ sort_order { 4 }
82
+ description { "Lunch staples" }
83
+ tax_group { "food" }
84
+ association :business_type, factory: %i[business_type cafe_bakery]
85
+ end
86
+
87
+ trait :smoothies do
88
+ name { "Smoothies & Juices" }
89
+ sort_order { 5 }
90
+ description { "Blended drinks and fresh-pressed juices" }
91
+ tax_group { "beverage" }
92
+ association :business_type, factory: %i[business_type cafe_bakery]
93
+ end
94
+
95
+ # ── Cafe & Bakery DUPLICATES (mirrors real Square mess) ─
96
+ # Duplicate "Coffee & Espresso" category — employee created a second one
97
+ trait :coffee_espresso_dup do
98
+ name { "Coffee & Espresso" }
99
+ sort_order { 6 }
100
+ description { "More coffee drinks (duplicate category)" }
101
+ tax_group { "beverage" }
102
+ association :business_type, factory: %i[business_type cafe_bakery]
103
+ end
104
+
105
+ # Duplicate "Pastries & Baked Goods" — imported via Square integration
106
+ trait :pastries_dup do
107
+ name { "Pastries & Baked Goods" }
108
+ sort_order { 7 }
109
+ description { "Pastries imported from integration" }
110
+ tax_group { "food" }
111
+ association :business_type, factory: %i[business_type cafe_bakery]
112
+ end
113
+
114
+ # "Breakfast" appears again — seasonal menu never cleaned up
115
+ trait :breakfast_dup do
116
+ name { "Breakfast" }
117
+ sort_order { 8 }
118
+ description { "Weekend brunch specials (stale copy)" }
119
+ tax_group { "food" }
120
+ association :business_type, factory: %i[business_type cafe_bakery]
121
+ end
122
+
123
+ # Generic "Drinks" that overlaps with both Coffee and Smoothies
124
+ trait :cafe_drinks do
125
+ name { "Drinks" }
126
+ sort_order { 9 }
127
+ description { "All drinks catch-all" }
128
+ tax_group { "beverage" }
129
+ association :business_type, factory: %i[business_type cafe_bakery]
130
+ end
131
+
132
+ # "Grab & Go" has items that also exist in Pastries and Sandwiches
133
+ trait :cafe_grab_and_go do
134
+ name { "Grab & Go" }
135
+ sort_order { 10 }
136
+ description { "Pre-packaged items for takeout" }
137
+ tax_group { "food" }
138
+ association :business_type, factory: %i[business_type cafe_bakery]
139
+ end
140
+
141
+ # ── Bar & Nightclub ───────────────────────────────────────
142
+
143
+ trait :draft_beer do
144
+ name { "Draft Beer" }
145
+ sort_order { 1 }
146
+ description { "On tap" }
147
+ tax_group { "alcohol" }
148
+ association :business_type, factory: %i[business_type bar_nightclub]
149
+ end
150
+
151
+ trait :cocktails do
152
+ name { "Cocktails" }
153
+ sort_order { 2 }
154
+ description { "Handcrafted cocktails" }
155
+ tax_group { "alcohol" }
156
+ association :business_type, factory: %i[business_type bar_nightclub]
157
+ end
158
+
159
+ trait :spirits do
160
+ name { "Spirits" }
161
+ sort_order { 3 }
162
+ description { "Neat, on the rocks, or mixed" }
163
+ tax_group { "alcohol" }
164
+ association :business_type, factory: %i[business_type bar_nightclub]
165
+ end
166
+
167
+ trait :wine do
168
+ name { "Wine" }
169
+ sort_order { 4 }
170
+ description { "By the glass" }
171
+ tax_group { "alcohol" }
172
+ association :business_type, factory: %i[business_type bar_nightclub]
173
+ end
174
+
175
+ trait :bar_snacks do
176
+ name { "Bar Snacks" }
177
+ sort_order { 5 }
178
+ description { "Late-night bites" }
179
+ tax_group { "food" }
180
+ association :business_type, factory: %i[business_type bar_nightclub]
181
+ end
182
+
183
+ # ── Food Truck ────────────────────────────────────────────
184
+
185
+ trait :tacos do
186
+ name { "Tacos" }
187
+ sort_order { 1 }
188
+ description { "Street-style tacos" }
189
+ tax_group { "food" }
190
+ association :business_type, factory: %i[business_type food_truck]
191
+ end
192
+
193
+ trait :burritos_bowls do
194
+ name { "Burritos & Bowls" }
195
+ sort_order { 2 }
196
+ description { "Burritos, bowls, and quesadillas" }
197
+ tax_group { "food" }
198
+ association :business_type, factory: %i[business_type food_truck]
199
+ end
200
+
201
+ trait :truck_sides_drinks do
202
+ name { "Sides & Drinks" }
203
+ sort_order { 3 }
204
+ description { "Extras and beverages" }
205
+ tax_group { "food" }
206
+ association :business_type, factory: %i[business_type food_truck]
207
+ end
208
+
209
+ # ── Fine Dining ───────────────────────────────────────────
210
+
211
+ trait :first_course do
212
+ name { "First Course" }
213
+ sort_order { 1 }
214
+ description { "Opening plates" }
215
+ tax_group { "food" }
216
+ association :business_type, factory: %i[business_type fine_dining]
217
+ end
218
+
219
+ trait :main_course do
220
+ name { "Main Course" }
221
+ sort_order { 2 }
222
+ description { "Chef's signature mains" }
223
+ tax_group { "food" }
224
+ association :business_type, factory: %i[business_type fine_dining]
225
+ end
226
+
227
+ trait :fine_desserts do
228
+ name { "Desserts & Petit Fours" }
229
+ sort_order { 3 }
230
+ description { "Sweet courses and cheese" }
231
+ tax_group { "food" }
232
+ association :business_type, factory: %i[business_type fine_dining]
233
+ end
234
+
235
+ # ── Pizzeria ──────────────────────────────────────────────
236
+
237
+ trait :pizzas do
238
+ name { "Pizzas" }
239
+ sort_order { 1 }
240
+ description { "Hand-tossed pies" }
241
+ tax_group { "food" }
242
+ association :business_type, factory: %i[business_type pizzeria]
243
+ end
244
+
245
+ trait :calzones do
246
+ name { "Calzones & Stromboli" }
247
+ sort_order { 2 }
248
+ description { "Folded and rolled" }
249
+ tax_group { "food" }
250
+ association :business_type, factory: %i[business_type pizzeria]
251
+ end
252
+
253
+ trait :pizza_sides_drinks do
254
+ name { "Sides & Drinks" }
255
+ sort_order { 3 }
256
+ description { "Extras and beverages" }
257
+ tax_group { "food" }
258
+ association :business_type, factory: %i[business_type pizzeria]
259
+ end
260
+
261
+ # ── Retail Clothing ───────────────────────────────────────
262
+
263
+ trait :tops do
264
+ name { "Tops" }
265
+ sort_order { 1 }
266
+ description { "T-shirts, shirts, and hoodies" }
267
+ tax_group { "retail" }
268
+ association :business_type, factory: %i[business_type retail_clothing]
269
+ end
270
+
271
+ trait :bottoms do
272
+ name { "Bottoms" }
273
+ sort_order { 2 }
274
+ description { "Jeans, pants, and shorts" }
275
+ tax_group { "retail" }
276
+ association :business_type, factory: %i[business_type retail_clothing]
277
+ end
278
+
279
+ trait :outerwear do
280
+ name { "Outerwear" }
281
+ sort_order { 3 }
282
+ description { "Jackets and vests" }
283
+ tax_group { "retail" }
284
+ association :business_type, factory: %i[business_type retail_clothing]
285
+ end
286
+
287
+ trait :accessories do
288
+ name { "Accessories" }
289
+ sort_order { 4 }
290
+ description { "Hats, belts, and scarves" }
291
+ tax_group { "retail" }
292
+ association :business_type, factory: %i[business_type retail_clothing]
293
+ end
294
+
295
+ trait :footwear do
296
+ name { "Footwear" }
297
+ sort_order { 5 }
298
+ description { "Shoes and boots" }
299
+ tax_group { "retail" }
300
+ association :business_type, factory: %i[business_type retail_clothing]
301
+ end
302
+
303
+ # ── Retail General ────────────────────────────────────────
304
+
305
+ trait :electronics do
306
+ name { "Electronics" }
307
+ sort_order { 1 }
308
+ description { "Gadgets and accessories" }
309
+ tax_group { "retail" }
310
+ association :business_type, factory: %i[business_type retail_general]
311
+ end
312
+
313
+ trait :home_kitchen do
314
+ name { "Home & Kitchen" }
315
+ sort_order { 2 }
316
+ description { "Home decor and kitchen essentials" }
317
+ tax_group { "retail" }
318
+ association :business_type, factory: %i[business_type retail_general]
319
+ end
320
+
321
+ trait :personal_care do
322
+ name { "Personal Care" }
323
+ sort_order { 3 }
324
+ description { "Skincare and body care" }
325
+ tax_group { "retail" }
326
+ association :business_type, factory: %i[business_type retail_general]
327
+ end
328
+
329
+ trait :office_supplies do
330
+ name { "Office Supplies" }
331
+ sort_order { 4 }
332
+ description { "Desk and stationery" }
333
+ tax_group { "retail" }
334
+ association :business_type, factory: %i[business_type retail_general]
335
+ end
336
+
337
+ trait :snacks_beverages do
338
+ name { "Snacks & Beverages" }
339
+ sort_order { 5 }
340
+ description { "Grab-and-go food and drinks" }
341
+ tax_group { "food" }
342
+ association :business_type, factory: %i[business_type retail_general]
343
+ end
344
+
345
+ # ── Salon & Spa ───────────────────────────────────────────
346
+
347
+ trait :haircuts do
348
+ name { "Haircuts" }
349
+ sort_order { 1 }
350
+ description { "Cuts and trims" }
351
+ tax_group { "service" }
352
+ association :business_type, factory: %i[business_type salon_spa]
353
+ end
354
+
355
+ trait :color_services do
356
+ name { "Color Services" }
357
+ sort_order { 2 }
358
+ description { "Color, highlights, and balayage" }
359
+ tax_group { "service" }
360
+ association :business_type, factory: %i[business_type salon_spa]
361
+ end
362
+
363
+ trait :spa_treatments do
364
+ name { "Spa Treatments" }
365
+ sort_order { 3 }
366
+ description { "Massage and facials" }
367
+ tax_group { "service" }
368
+ association :business_type, factory: %i[business_type salon_spa]
369
+ end
370
+
371
+ trait :nail_services do
372
+ name { "Nail Services" }
373
+ sort_order { 4 }
374
+ description { "Manicures and pedicures" }
375
+ tax_group { "service" }
376
+ association :business_type, factory: %i[business_type salon_spa]
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :daily_summary, class: "SquareSandboxSimulator::Models::DailySummary" do
5
+ sequence(:merchant_id) { |n| "MERCHANT#{n}" }
6
+ business_date { Date.today }
7
+ order_count { 0 }
8
+ payment_count { 0 }
9
+ refund_count { 0 }
10
+ total_revenue { 0 } # cents
11
+ total_tax { 0 } # cents
12
+ total_tips { 0 } # cents
13
+ total_discounts { 0 } # cents
14
+ breakdown { {} }
15
+
16
+ # A realistic busy-day summary
17
+ trait :busy_day do
18
+ order_count { 85 }
19
+ payment_count { 90 }
20
+ refund_count { 2 }
21
+ total_revenue { 425_000 } # $4,250.00
22
+ total_tax { 37_719 } # $377.19
23
+ total_tips { 63_750 } # $637.50
24
+ total_discounts { 12_500 } # $125.00
25
+ breakdown do
26
+ {
27
+ "by_meal_period" => { "breakfast" => 15, "lunch" => 40, "dinner" => 30 },
28
+ "by_dining_option" => { "HERE" => 55, "TO_GO" => 20, "DELIVERY" => 10 },
29
+ "by_tender" => { "Cash" => 25, "Credit Card" => 55, "Debit Card" => 10 },
30
+ "revenue_by_meal_period" => { "breakfast" => 45_000, "lunch" => 180_000, "dinner" => 200_000 },
31
+ "revenue_by_dining_option" => { "HERE" => 280_000, "TO_GO" => 95_000, "DELIVERY" => 50_000 },
32
+ }
33
+ end
34
+ end
35
+
36
+ # A slow-day summary
37
+ trait :slow_day do
38
+ order_count { 12 }
39
+ payment_count { 12 }
40
+ refund_count { 0 }
41
+ total_revenue { 48_000 } # $480.00
42
+ total_tax { 4_260 } # $42.60
43
+ total_tips { 7_200 } # $72.00
44
+ total_discounts { 0 }
45
+ breakdown do
46
+ {
47
+ "by_meal_period" => { "lunch" => 5, "dinner" => 7 },
48
+ "by_dining_option" => { "HERE" => 8, "TO_GO" => 4 },
49
+ "by_tender" => { "Cash" => 4, "Credit Card" => 8 },
50
+ "revenue_by_meal_period" => { "lunch" => 18_000, "dinner" => 30_000 },
51
+ "revenue_by_dining_option" => { "HERE" => 32_000, "TO_GO" => 16_000 },
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end