skytab_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.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/bin/simulate +433 -0
- data/lib/skytab_sandbox_simulator/configuration.rb +205 -0
- data/lib/skytab_sandbox_simulator/data/bar_nightclub/categories.json +9 -0
- data/lib/skytab_sandbox_simulator/data/bar_nightclub/items.json +28 -0
- data/lib/skytab_sandbox_simulator/data/bar_nightclub/tenders.json +19 -0
- data/lib/skytab_sandbox_simulator/data/cafe_bakery/categories.json +9 -0
- data/lib/skytab_sandbox_simulator/data/cafe_bakery/items.json +30 -0
- data/lib/skytab_sandbox_simulator/data/cafe_bakery/tenders.json +17 -0
- data/lib/skytab_sandbox_simulator/data/fine_dining/categories.json +9 -0
- data/lib/skytab_sandbox_simulator/data/fine_dining/items.json +30 -0
- data/lib/skytab_sandbox_simulator/data/fine_dining/tenders.json +18 -0
- data/lib/skytab_sandbox_simulator/data/pizzeria/categories.json +9 -0
- data/lib/skytab_sandbox_simulator/data/pizzeria/items.json +28 -0
- data/lib/skytab_sandbox_simulator/data/pizzeria/tenders.json +18 -0
- data/lib/skytab_sandbox_simulator/data/restaurant/categories.json +44 -0
- data/lib/skytab_sandbox_simulator/data/restaurant/items.json +59 -0
- data/lib/skytab_sandbox_simulator/data/restaurant/tenders.json +22 -0
- data/lib/skytab_sandbox_simulator/database.rb +192 -0
- data/lib/skytab_sandbox_simulator/db/factories/business_types.rb +102 -0
- data/lib/skytab_sandbox_simulator/db/factories/categories.rb +243 -0
- data/lib/skytab_sandbox_simulator/db/factories/items.rb +976 -0
- data/lib/skytab_sandbox_simulator/db/factories/simulated_orders.rb +120 -0
- data/lib/skytab_sandbox_simulator/db/factories/simulated_payments.rb +75 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000000_enable_pgcrypto.rb +7 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000001_create_business_types.rb +18 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000002_create_categories.rb +18 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000003_create_items.rb +23 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000004_create_simulated_orders.rb +35 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000005_create_simulated_payments.rb +26 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000006_create_api_requests.rb +27 -0
- data/lib/skytab_sandbox_simulator/db/migrate/20260316000007_create_daily_summaries.rb +24 -0
- data/lib/skytab_sandbox_simulator/generators/data_loader.rb +125 -0
- data/lib/skytab_sandbox_simulator/generators/entity_generator.rb +107 -0
- data/lib/skytab_sandbox_simulator/generators/order_generator.rb +390 -0
- data/lib/skytab_sandbox_simulator/models/api_request.rb +43 -0
- data/lib/skytab_sandbox_simulator/models/business_type.rb +25 -0
- data/lib/skytab_sandbox_simulator/models/category.rb +17 -0
- data/lib/skytab_sandbox_simulator/models/daily_summary.rb +67 -0
- data/lib/skytab_sandbox_simulator/models/item.rb +32 -0
- data/lib/skytab_sandbox_simulator/models/record.rb +14 -0
- data/lib/skytab_sandbox_simulator/models/simulated_order.rb +40 -0
- data/lib/skytab_sandbox_simulator/models/simulated_payment.rb +28 -0
- data/lib/skytab_sandbox_simulator/seeder.rb +167 -0
- data/lib/skytab_sandbox_simulator/services/base_service.rb +227 -0
- data/lib/skytab_sandbox_simulator/services/skytab/catalog_service.rb +130 -0
- data/lib/skytab_sandbox_simulator/services/skytab/location_service.rb +54 -0
- data/lib/skytab_sandbox_simulator/services/skytab/order_service.rb +139 -0
- data/lib/skytab_sandbox_simulator/services/skytab/payment_service.rb +94 -0
- data/lib/skytab_sandbox_simulator/services/skytab/service_manager.rb +62 -0
- data/lib/skytab_sandbox_simulator/version.rb +5 -0
- data/lib/skytab_sandbox_simulator.rb +45 -0
- metadata +305 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "House Lager", "price": 599, "category": "Draft Beer", "sku": "BAR-DRF-001" },
|
|
4
|
+
{ "name": "IPA", "price": 699, "category": "Draft Beer", "sku": "BAR-DRF-002" },
|
|
5
|
+
{ "name": "Stout", "price": 749, "category": "Draft Beer", "sku": "BAR-DRF-003" },
|
|
6
|
+
{ "name": "Wheat Beer", "price": 649, "category": "Draft Beer", "sku": "BAR-DRF-004" },
|
|
7
|
+
|
|
8
|
+
{ "name": "Margarita", "price": 1199, "category": "Cocktails", "sku": "BAR-COC-001" },
|
|
9
|
+
{ "name": "Old Fashioned", "price": 1399, "category": "Cocktails", "sku": "BAR-COC-002" },
|
|
10
|
+
{ "name": "Mojito", "price": 1199, "category": "Cocktails", "sku": "BAR-COC-003" },
|
|
11
|
+
{ "name": "Espresso Martini", "price": 1499, "category": "Cocktails", "sku": "BAR-COC-004" },
|
|
12
|
+
|
|
13
|
+
{ "name": "Whiskey Neat", "price": 1099, "category": "Spirits", "sku": "BAR-SPI-001" },
|
|
14
|
+
{ "name": "Vodka Soda", "price": 899, "category": "Spirits", "sku": "BAR-SPI-002" },
|
|
15
|
+
{ "name": "Tequila Shot", "price": 799, "category": "Spirits", "sku": "BAR-SPI-003" },
|
|
16
|
+
{ "name": "Rum and Coke", "price": 899, "category": "Spirits", "sku": "BAR-SPI-004" },
|
|
17
|
+
|
|
18
|
+
{ "name": "House Red", "price": 899, "category": "Wine", "sku": "BAR-WIN-001" },
|
|
19
|
+
{ "name": "House White", "price": 899, "category": "Wine", "sku": "BAR-WIN-002" },
|
|
20
|
+
{ "name": "Prosecco", "price": 999, "category": "Wine", "sku": "BAR-WIN-003" },
|
|
21
|
+
{ "name": "Rose", "price": 999, "category": "Wine", "sku": "BAR-WIN-004" },
|
|
22
|
+
|
|
23
|
+
{ "name": "Loaded Fries", "price": 899, "category": "Bar Snacks", "sku": "BAR-SNK-001" },
|
|
24
|
+
{ "name": "Sliders", "price": 1199, "category": "Bar Snacks", "sku": "BAR-SNK-002" },
|
|
25
|
+
{ "name": "Wings", "price": 1099, "category": "Bar Snacks", "sku": "BAR-SNK-003" },
|
|
26
|
+
{ "name": "Pretzel Bites", "price": 799, "category": "Bar Snacks", "sku": "BAR-SNK-004" }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "label": "Cash", "pos_ref": "CASH", "weight": 10 },
|
|
4
|
+
{ "label": "Visa", "pos_ref": "VISA", "weight": 30 },
|
|
5
|
+
{ "label": "Mastercard", "pos_ref": "MASTERCARD", "weight": 25 },
|
|
6
|
+
{ "label": "Amex", "pos_ref": "AMEX", "weight": 15 },
|
|
7
|
+
{ "label": "Discover", "pos_ref": "DISCOVER", "weight": 5 },
|
|
8
|
+
{ "label": "House Account", "pos_ref": "HOUSE_ACCOUNT", "weight": 15 }
|
|
9
|
+
],
|
|
10
|
+
"revenue_classes": [
|
|
11
|
+
{ "label": "Bar", "pos_ref": "BAR" },
|
|
12
|
+
{ "label": "Dine-In", "pos_ref": "DINE_IN" },
|
|
13
|
+
{ "label": "Takeout", "pos_ref": "TAKEOUT" }
|
|
14
|
+
],
|
|
15
|
+
"tax_rates": [
|
|
16
|
+
{ "name": "Sales Tax", "rate": 82500, "note": "8.25%" },
|
|
17
|
+
{ "name": "Alcohol Tax", "rate": 100000, "note": "10.0%" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": [
|
|
3
|
+
{ "name": "Coffee & Espresso", "sort_order": 1, "description": "Hot and cold coffee drinks" },
|
|
4
|
+
{ "name": "Pastries", "sort_order": 2, "description": "Fresh-baked goods" },
|
|
5
|
+
{ "name": "Breakfast", "sort_order": 3, "description": "Morning favorites" },
|
|
6
|
+
{ "name": "Sandwiches & Wraps", "sort_order": 4, "description": "Lunch fare" },
|
|
7
|
+
{ "name": "Smoothies & Juice", "sort_order": 5, "description": "Fresh blended drinks" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "House Drip Coffee", "price": 299, "category": "Coffee & Espresso", "sku": "CAFE-COF-001" },
|
|
4
|
+
{ "name": "Espresso", "price": 350, "category": "Coffee & Espresso", "sku": "CAFE-COF-002" },
|
|
5
|
+
{ "name": "Cappuccino", "price": 499, "category": "Coffee & Espresso", "sku": "CAFE-COF-003" },
|
|
6
|
+
{ "name": "Latte", "price": 549, "category": "Coffee & Espresso", "sku": "CAFE-COF-004" },
|
|
7
|
+
{ "name": "Cold Brew", "price": 499, "category": "Coffee & Espresso", "sku": "CAFE-COF-005" },
|
|
8
|
+
|
|
9
|
+
{ "name": "Croissant", "price": 399, "category": "Pastries", "sku": "CAFE-PAS-001" },
|
|
10
|
+
{ "name": "Blueberry Muffin", "price": 399, "category": "Pastries", "sku": "CAFE-PAS-002" },
|
|
11
|
+
{ "name": "Cinnamon Roll", "price": 449, "category": "Pastries", "sku": "CAFE-PAS-003" },
|
|
12
|
+
{ "name": "Chocolate Chip Cookie", "price": 299, "category": "Pastries", "sku": "CAFE-PAS-004" },
|
|
13
|
+
{ "name": "Almond Croissant", "price": 449, "category": "Pastries", "sku": "CAFE-PAS-005" },
|
|
14
|
+
|
|
15
|
+
{ "name": "Avocado Toast", "price": 1099, "category": "Breakfast", "sku": "CAFE-BRK-001" },
|
|
16
|
+
{ "name": "Breakfast Burrito", "price": 999, "category": "Breakfast", "sku": "CAFE-BRK-002" },
|
|
17
|
+
{ "name": "Acai Bowl", "price": 1199, "category": "Breakfast", "sku": "CAFE-BRK-003" },
|
|
18
|
+
{ "name": "Yogurt Parfait", "price": 699, "category": "Breakfast", "sku": "CAFE-BRK-004" },
|
|
19
|
+
|
|
20
|
+
{ "name": "Turkey Club", "price": 1199, "category": "Sandwiches & Wraps", "sku": "CAFE-SAN-001" },
|
|
21
|
+
{ "name": "Caprese Panini", "price": 1099, "category": "Sandwiches & Wraps", "sku": "CAFE-SAN-002" },
|
|
22
|
+
{ "name": "Chicken Caesar Wrap", "price": 1099, "category": "Sandwiches & Wraps", "sku": "CAFE-SAN-003" },
|
|
23
|
+
{ "name": "BLT", "price": 999, "category": "Sandwiches & Wraps", "sku": "CAFE-SAN-004" },
|
|
24
|
+
|
|
25
|
+
{ "name": "Berry Blast Smoothie", "price": 699, "category": "Smoothies & Juice", "sku": "CAFE-SMO-001" },
|
|
26
|
+
{ "name": "Green Detox Juice", "price": 799, "category": "Smoothies & Juice", "sku": "CAFE-SMO-002" },
|
|
27
|
+
{ "name": "Mango Tango Smoothie", "price": 699, "category": "Smoothies & Juice", "sku": "CAFE-SMO-003" },
|
|
28
|
+
{ "name": "Fresh Orange Juice", "price": 499, "category": "Smoothies & Juice", "sku": "CAFE-SMO-004" }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "label": "Cash", "pos_ref": "CASH", "weight": 20 },
|
|
4
|
+
{ "label": "Visa", "pos_ref": "VISA", "weight": 30 },
|
|
5
|
+
{ "label": "Mastercard", "pos_ref": "MASTERCARD", "weight": 20 },
|
|
6
|
+
{ "label": "Amex", "pos_ref": "AMEX", "weight": 10 },
|
|
7
|
+
{ "label": "Discover", "pos_ref": "DISCOVER", "weight": 5 },
|
|
8
|
+
{ "label": "Gift Card", "pos_ref": "GIFT_CARD", "weight": 15 }
|
|
9
|
+
],
|
|
10
|
+
"revenue_classes": [
|
|
11
|
+
{ "label": "Dine-In", "pos_ref": "DINE_IN" },
|
|
12
|
+
{ "label": "Takeout", "pos_ref": "TAKEOUT" }
|
|
13
|
+
],
|
|
14
|
+
"tax_rates": [
|
|
15
|
+
{ "name": "Sales Tax", "rate": 82500, "note": "8.25%" }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": [
|
|
3
|
+
{ "name": "First Course", "sort_order": 1, "description": "Opening courses" },
|
|
4
|
+
{ "name": "Main Course", "sort_order": 2, "description": "Principal courses" },
|
|
5
|
+
{ "name": "Desserts", "sort_order": 3, "description": "Artisan desserts" },
|
|
6
|
+
{ "name": "Wine List", "sort_order": 4, "description": "Curated wine selection" },
|
|
7
|
+
{ "name": "Cocktails", "sort_order": 5, "description": "Signature cocktails" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "Seared Foie Gras", "price": 3200, "category": "First Course", "sku": "FIN-FST-001" },
|
|
4
|
+
{ "name": "Lobster Bisque", "price": 1800, "category": "First Course", "sku": "FIN-FST-002" },
|
|
5
|
+
{ "name": "Tuna Tartare", "price": 2400, "category": "First Course", "sku": "FIN-FST-003" },
|
|
6
|
+
{ "name": "Burrata Salad", "price": 1900, "category": "First Course", "sku": "FIN-FST-004" },
|
|
7
|
+
{ "name": "Oysters Half Dozen", "price": 2800, "category": "First Course", "sku": "FIN-FST-005" },
|
|
8
|
+
|
|
9
|
+
{ "name": "Wagyu Ribeye", "price": 8500, "category": "Main Course", "sku": "FIN-MAN-001" },
|
|
10
|
+
{ "name": "Chilean Sea Bass", "price": 4200, "category": "Main Course", "sku": "FIN-MAN-002" },
|
|
11
|
+
{ "name": "Rack of Lamb", "price": 5200, "category": "Main Course", "sku": "FIN-MAN-003" },
|
|
12
|
+
{ "name": "Duck Breast", "price": 3800, "category": "Main Course", "sku": "FIN-MAN-004" },
|
|
13
|
+
{ "name": "Truffle Risotto", "price": 3200, "category": "Main Course", "sku": "FIN-MAN-005" },
|
|
14
|
+
|
|
15
|
+
{ "name": "Creme Brulee", "price": 1600, "category": "Desserts", "sku": "FIN-DES-001" },
|
|
16
|
+
{ "name": "Chocolate Souffle", "price": 1800, "category": "Desserts", "sku": "FIN-DES-002" },
|
|
17
|
+
{ "name": "Dessert Tasting Plate", "price": 2200, "category": "Desserts", "sku": "FIN-DES-003" },
|
|
18
|
+
{ "name": "Artisan Cheese Board", "price": 2600, "category": "Desserts", "sku": "FIN-DES-004" },
|
|
19
|
+
{ "name": "Tiramisu", "price": 1500, "category": "Desserts", "sku": "FIN-DES-005" },
|
|
20
|
+
|
|
21
|
+
{ "name": "Champagne", "price": 2200, "category": "Wine List", "sku": "FIN-WIN-001" },
|
|
22
|
+
{ "name": "Cabernet Sauvignon", "price": 1800, "category": "Wine List", "sku": "FIN-WIN-002" },
|
|
23
|
+
{ "name": "Pinot Noir", "price": 1600, "category": "Wine List", "sku": "FIN-WIN-003" },
|
|
24
|
+
{ "name": "Chardonnay", "price": 1500, "category": "Wine List", "sku": "FIN-WIN-004" },
|
|
25
|
+
|
|
26
|
+
{ "name": "Negroni", "price": 1800, "category": "Cocktails", "sku": "FIN-COC-001" },
|
|
27
|
+
{ "name": "Manhattan", "price": 1900, "category": "Cocktails", "sku": "FIN-COC-002" },
|
|
28
|
+
{ "name": "French 75", "price": 1700, "category": "Cocktails", "sku": "FIN-COC-003" }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "label": "Visa", "pos_ref": "VISA", "weight": 30 },
|
|
4
|
+
{ "label": "Mastercard", "pos_ref": "MASTERCARD", "weight": 20 },
|
|
5
|
+
{ "label": "Amex", "pos_ref": "AMEX", "weight": 25 },
|
|
6
|
+
{ "label": "Discover", "pos_ref": "DISCOVER", "weight": 5 },
|
|
7
|
+
{ "label": "Cash", "pos_ref": "CASH", "weight": 5 },
|
|
8
|
+
{ "label": "House Account", "pos_ref": "HOUSE_ACCOUNT", "weight": 15 }
|
|
9
|
+
],
|
|
10
|
+
"revenue_classes": [
|
|
11
|
+
{ "label": "Dine-In", "pos_ref": "DINE_IN" },
|
|
12
|
+
{ "label": "Bar", "pos_ref": "BAR" }
|
|
13
|
+
],
|
|
14
|
+
"tax_rates": [
|
|
15
|
+
{ "name": "Sales Tax", "rate": 82500, "note": "8.25%" },
|
|
16
|
+
{ "name": "Alcohol Tax", "rate": 100000, "note": "10.0%" }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": [
|
|
3
|
+
{ "name": "Pizzas", "sort_order": 1, "description": "Hand-tossed pizzas" },
|
|
4
|
+
{ "name": "Calzones", "sort_order": 2, "description": "Folded and baked" },
|
|
5
|
+
{ "name": "Sides", "sort_order": 3, "description": "Perfect with pizza" },
|
|
6
|
+
{ "name": "Drinks", "sort_order": 4, "description": "Beverages" },
|
|
7
|
+
{ "name": "Desserts", "sort_order": 5, "description": "Sweet endings" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "Margherita", "price": 1499, "category": "Pizzas", "sku": "PIZ-PIZ-001" },
|
|
4
|
+
{ "name": "Pepperoni", "price": 1599, "category": "Pizzas", "sku": "PIZ-PIZ-002" },
|
|
5
|
+
{ "name": "Supreme", "price": 1899, "category": "Pizzas", "sku": "PIZ-PIZ-003" },
|
|
6
|
+
{ "name": "Hawaiian", "price": 1599, "category": "Pizzas", "sku": "PIZ-PIZ-004" },
|
|
7
|
+
{ "name": "BBQ Chicken Pizza", "price": 1799, "category": "Pizzas", "sku": "PIZ-PIZ-005" },
|
|
8
|
+
{ "name": "Meat Lovers", "price": 1899, "category": "Pizzas", "sku": "PIZ-PIZ-006" },
|
|
9
|
+
|
|
10
|
+
{ "name": "Classic Calzone", "price": 1299, "category": "Calzones", "sku": "PIZ-CAL-001" },
|
|
11
|
+
{ "name": "Meat Calzone", "price": 1499, "category": "Calzones", "sku": "PIZ-CAL-002" },
|
|
12
|
+
{ "name": "Stromboli", "price": 1399, "category": "Calzones", "sku": "PIZ-CAL-003" },
|
|
13
|
+
{ "name": "Spinach Calzone", "price": 1299, "category": "Calzones", "sku": "PIZ-CAL-004" },
|
|
14
|
+
|
|
15
|
+
{ "name": "Garlic Bread", "price": 499, "category": "Sides", "sku": "PIZ-SID-001" },
|
|
16
|
+
{ "name": "Garden Salad", "price": 699, "category": "Sides", "sku": "PIZ-SID-002" },
|
|
17
|
+
{ "name": "Caesar Salad", "price": 799, "category": "Sides", "sku": "PIZ-SID-003" },
|
|
18
|
+
{ "name": "Garlic Knots", "price": 499, "category": "Sides", "sku": "PIZ-SID-004" },
|
|
19
|
+
|
|
20
|
+
{ "name": "Fountain Drink", "price": 299, "category": "Drinks", "sku": "PIZ-DRK-001" },
|
|
21
|
+
{ "name": "Iced Tea", "price": 299, "category": "Drinks", "sku": "PIZ-DRK-002" },
|
|
22
|
+
{ "name": "Lemonade", "price": 349, "category": "Drinks", "sku": "PIZ-DRK-003" },
|
|
23
|
+
|
|
24
|
+
{ "name": "Cannoli", "price": 599, "category": "Desserts", "sku": "PIZ-DES-001" },
|
|
25
|
+
{ "name": "Chocolate Brownie", "price": 499, "category": "Desserts", "sku": "PIZ-DES-002" },
|
|
26
|
+
{ "name": "Cheesecake", "price": 699, "category": "Desserts", "sku": "PIZ-DES-003" }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "label": "Cash", "pos_ref": "CASH", "weight": 20 },
|
|
4
|
+
{ "label": "Visa", "pos_ref": "VISA", "weight": 30 },
|
|
5
|
+
{ "label": "Mastercard", "pos_ref": "MASTERCARD", "weight": 20 },
|
|
6
|
+
{ "label": "Amex", "pos_ref": "AMEX", "weight": 10 },
|
|
7
|
+
{ "label": "Discover", "pos_ref": "DISCOVER", "weight": 5 },
|
|
8
|
+
{ "label": "Gift Card", "pos_ref": "GIFT_CARD", "weight": 15 }
|
|
9
|
+
],
|
|
10
|
+
"revenue_classes": [
|
|
11
|
+
{ "label": "Dine-In", "pos_ref": "DINE_IN" },
|
|
12
|
+
{ "label": "Takeout", "pos_ref": "TAKEOUT" },
|
|
13
|
+
{ "label": "Delivery", "pos_ref": "DELIVERY" }
|
|
14
|
+
],
|
|
15
|
+
"tax_rates": [
|
|
16
|
+
{ "name": "Sales Tax", "rate": 82500, "note": "8.25%" }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": [
|
|
3
|
+
{
|
|
4
|
+
"name": "Appetizers",
|
|
5
|
+
"sort_order": 1,
|
|
6
|
+
"description": "Starters and shareables"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "Entrees",
|
|
10
|
+
"sort_order": 2,
|
|
11
|
+
"description": "Main courses"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Sides",
|
|
15
|
+
"sort_order": 3,
|
|
16
|
+
"description": "Side dishes"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "Desserts",
|
|
20
|
+
"sort_order": 4,
|
|
21
|
+
"description": "Sweet finishes"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "Beverages",
|
|
25
|
+
"sort_order": 5,
|
|
26
|
+
"description": "Non-alcoholic drinks"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "Alcohol",
|
|
30
|
+
"sort_order": 6,
|
|
31
|
+
"description": "Beer, wine, and cocktails"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "Kids Menu",
|
|
35
|
+
"sort_order": 7,
|
|
36
|
+
"description": "For our younger guests"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "Specials",
|
|
40
|
+
"sort_order": 8,
|
|
41
|
+
"description": "Chef's daily specials"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "Buffalo Wings", "price": 1299, "category": "Appetizers", "sku": "REST-APP-001" },
|
|
4
|
+
{ "name": "Mozzarella Sticks", "price": 999, "category": "Appetizers", "sku": "REST-APP-002" },
|
|
5
|
+
{ "name": "Loaded Nachos", "price": 1199, "category": "Appetizers", "sku": "REST-APP-003" },
|
|
6
|
+
{ "name": "Spinach Artichoke Dip", "price": 1099, "category": "Appetizers", "sku": "REST-APP-004" },
|
|
7
|
+
{ "name": "Calamari", "price": 1399, "category": "Appetizers", "sku": "REST-APP-005" },
|
|
8
|
+
{ "name": "Bruschetta", "price": 999, "category": "Appetizers", "sku": "REST-APP-006" },
|
|
9
|
+
{ "name": "Shrimp Cocktail", "price": 1599, "category": "Appetizers", "sku": "REST-APP-007" },
|
|
10
|
+
|
|
11
|
+
{ "name": "Classic Burger", "price": 1499, "category": "Entrees", "sku": "REST-ENT-001" },
|
|
12
|
+
{ "name": "Grilled Salmon", "price": 2199, "category": "Entrees", "sku": "REST-ENT-002" },
|
|
13
|
+
{ "name": "NY Strip Steak", "price": 2899, "category": "Entrees", "sku": "REST-ENT-003" },
|
|
14
|
+
{ "name": "Chicken Parmesan", "price": 1899, "category": "Entrees", "sku": "REST-ENT-004" },
|
|
15
|
+
{ "name": "Fettuccine Alfredo", "price": 1599, "category": "Entrees", "sku": "REST-ENT-005" },
|
|
16
|
+
{ "name": "Fish and Chips", "price": 1699, "category": "Entrees", "sku": "REST-ENT-006" },
|
|
17
|
+
{ "name": "BBQ Ribs", "price": 2399, "category": "Entrees", "sku": "REST-ENT-007" },
|
|
18
|
+
{ "name": "Mushroom Risotto", "price": 1799, "category": "Entrees", "sku": "REST-ENT-008" },
|
|
19
|
+
{ "name": "Shrimp Scampi", "price": 2099, "category": "Entrees", "sku": "REST-ENT-009" },
|
|
20
|
+
|
|
21
|
+
{ "name": "French Fries", "price": 499, "category": "Sides", "sku": "REST-SID-001" },
|
|
22
|
+
{ "name": "Sweet Potato Fries", "price": 599, "category": "Sides", "sku": "REST-SID-002" },
|
|
23
|
+
{ "name": "Onion Rings", "price": 549, "category": "Sides", "sku": "REST-SID-003" },
|
|
24
|
+
{ "name": "Coleslaw", "price": 399, "category": "Sides", "sku": "REST-SID-004" },
|
|
25
|
+
{ "name": "Mashed Potatoes", "price": 499, "category": "Sides", "sku": "REST-SID-005" },
|
|
26
|
+
{ "name": "Steamed Vegetables", "price": 449, "category": "Sides", "sku": "REST-SID-006" },
|
|
27
|
+
{ "name": "Garlic Bread", "price": 449, "category": "Sides", "sku": "REST-SID-007" },
|
|
28
|
+
|
|
29
|
+
{ "name": "Chocolate Brownie", "price": 699, "category": "Desserts", "sku": "REST-DES-001" },
|
|
30
|
+
{ "name": "New York Cheesecake", "price": 799, "category": "Desserts", "sku": "REST-DES-002" },
|
|
31
|
+
{ "name": "Apple Pie", "price": 749, "category": "Desserts", "sku": "REST-DES-003" },
|
|
32
|
+
{ "name": "Tiramisu", "price": 899, "category": "Desserts", "sku": "REST-DES-004" },
|
|
33
|
+
{ "name": "Creme Brulee", "price": 949, "category": "Desserts", "sku": "REST-DES-005" },
|
|
34
|
+
|
|
35
|
+
{ "name": "Soft Drink", "price": 299, "category": "Beverages", "sku": "REST-BEV-001" },
|
|
36
|
+
{ "name": "Iced Tea", "price": 299, "category": "Beverages", "sku": "REST-BEV-002" },
|
|
37
|
+
{ "name": "Lemonade", "price": 349, "category": "Beverages", "sku": "REST-BEV-003" },
|
|
38
|
+
{ "name": "Coffee", "price": 349, "category": "Beverages", "sku": "REST-BEV-004" },
|
|
39
|
+
{ "name": "Hot Tea", "price": 299, "category": "Beverages", "sku": "REST-BEV-005" },
|
|
40
|
+
{ "name": "Sparkling Water", "price": 349, "category": "Beverages", "sku": "REST-BEV-006" },
|
|
41
|
+
{ "name": "Fresh Juice", "price": 499, "category": "Beverages", "sku": "REST-BEV-007" },
|
|
42
|
+
|
|
43
|
+
{ "name": "Draft Beer", "price": 599, "category": "Alcohol", "sku": "REST-ALC-001" },
|
|
44
|
+
{ "name": "Domestic Beer", "price": 499, "category": "Alcohol", "sku": "REST-ALC-002" },
|
|
45
|
+
{ "name": "Import Beer", "price": 649, "category": "Alcohol", "sku": "REST-ALC-003" },
|
|
46
|
+
{ "name": "House Wine", "price": 799, "category": "Alcohol", "sku": "REST-ALC-004" },
|
|
47
|
+
{ "name": "Margarita", "price": 999, "category": "Alcohol", "sku": "REST-ALC-005" },
|
|
48
|
+
{ "name": "Old Fashioned", "price": 1299, "category": "Alcohol", "sku": "REST-ALC-006" },
|
|
49
|
+
|
|
50
|
+
{ "name": "Chicken Tenders", "price": 799, "category": "Kids Menu", "sku": "REST-KID-001" },
|
|
51
|
+
{ "name": "Kids Mac and Cheese", "price": 649, "category": "Kids Menu", "sku": "REST-KID-002" },
|
|
52
|
+
{ "name": "Mini Burger", "price": 699, "category": "Kids Menu", "sku": "REST-KID-003" },
|
|
53
|
+
{ "name": "Grilled Cheese", "price": 599, "category": "Kids Menu", "sku": "REST-KID-004" },
|
|
54
|
+
{ "name": "Kids Quesadilla", "price": 649, "category": "Kids Menu", "sku": "REST-KID-005" },
|
|
55
|
+
|
|
56
|
+
{ "name": "Chef's Special", "price": 2499, "category": "Specials", "sku": "REST-SPE-001" },
|
|
57
|
+
{ "name": "Soup of the Day", "price": 599, "category": "Specials", "sku": "REST-SPE-002" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "label": "Cash", "pos_ref": "CASH", "weight": 15 },
|
|
4
|
+
{ "label": "Visa", "pos_ref": "VISA", "weight": 30 },
|
|
5
|
+
{ "label": "Mastercard", "pos_ref": "MASTERCARD", "weight": 20 },
|
|
6
|
+
{ "label": "Amex", "pos_ref": "AMEX", "weight": 10 },
|
|
7
|
+
{ "label": "Discover", "pos_ref": "DISCOVER", "weight": 5 },
|
|
8
|
+
{ "label": "Gift Card", "pos_ref": "GIFT_CARD", "weight": 10 },
|
|
9
|
+
{ "label": "House Account", "pos_ref": "HOUSE_ACCOUNT", "weight": 10 }
|
|
10
|
+
],
|
|
11
|
+
"revenue_classes": [
|
|
12
|
+
{ "label": "Dine-In", "pos_ref": "DINE_IN" },
|
|
13
|
+
{ "label": "Takeout", "pos_ref": "TAKEOUT" },
|
|
14
|
+
{ "label": "Delivery", "pos_ref": "DELIVERY" },
|
|
15
|
+
{ "label": "Bar", "pos_ref": "BAR" },
|
|
16
|
+
{ "label": "Catering", "pos_ref": "CATERING" }
|
|
17
|
+
],
|
|
18
|
+
"tax_rates": [
|
|
19
|
+
{ "name": "Sales Tax", "rate": 82500, "note": "8.25% — divide by 10,000 for percentage" },
|
|
20
|
+
{ "name": "Alcohol Tax", "rate": 100000, "note": "10.0% — divide by 10,000 for percentage" }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "logger"
|
|
5
|
+
|
|
6
|
+
module SkytabSandboxSimulator
|
|
7
|
+
# Standalone ActiveRecord connection manager for PostgreSQL.
|
|
8
|
+
#
|
|
9
|
+
# Provides database connectivity without requiring Rails.
|
|
10
|
+
# Used for persisting SkyTab sandbox data (locations, orders, etc.)
|
|
11
|
+
# alongside the existing JSON-file and API-based workflows.
|
|
12
|
+
#
|
|
13
|
+
# @example Connect and run migrations
|
|
14
|
+
# SkytabSandboxSimulator::Database.connect!("postgres://localhost:5432/skytab_simulator_development")
|
|
15
|
+
# SkytabSandboxSimulator::Database.migrate!
|
|
16
|
+
module Database
|
|
17
|
+
# Directory containing ActiveRecord migration files
|
|
18
|
+
MIGRATIONS_PATH = File.expand_path("db/migrate", __dir__).freeze
|
|
19
|
+
|
|
20
|
+
# Default test database name
|
|
21
|
+
TEST_DATABASE = "skytab_simulator_test"
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# Create the database specified in the connection URL.
|
|
25
|
+
#
|
|
26
|
+
# @param url [String] PostgreSQL connection URL
|
|
27
|
+
# @return [void]
|
|
28
|
+
def create!(url)
|
|
29
|
+
db_name = URI.parse(url).path.delete_prefix("/")
|
|
30
|
+
maintenance_url = url.sub(%r{/[^/]+\z}, "/postgres")
|
|
31
|
+
|
|
32
|
+
ActiveRecord::Base.establish_connection(maintenance_url)
|
|
33
|
+
ActiveRecord::Base.connection.create_database(db_name)
|
|
34
|
+
SkytabSandboxSimulator.logger.info("Database created: #{db_name}")
|
|
35
|
+
rescue ActiveRecord::DatabaseAlreadyExists, ActiveRecord::StatementInvalid => e
|
|
36
|
+
if e.message.include?("already exists")
|
|
37
|
+
SkytabSandboxSimulator.logger.info("Database already exists: #{db_name}")
|
|
38
|
+
else
|
|
39
|
+
raise
|
|
40
|
+
end
|
|
41
|
+
ensure
|
|
42
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Drop the database specified in the connection URL.
|
|
46
|
+
#
|
|
47
|
+
# @param url [String] PostgreSQL connection URL
|
|
48
|
+
# @return [void]
|
|
49
|
+
def drop!(url)
|
|
50
|
+
db_name = URI.parse(url).path.delete_prefix("/")
|
|
51
|
+
maintenance_url = url.sub(%r{/[^/]+\z}, "/postgres")
|
|
52
|
+
|
|
53
|
+
ActiveRecord::Base.establish_connection(maintenance_url)
|
|
54
|
+
ActiveRecord::Base.connection.drop_database(db_name)
|
|
55
|
+
SkytabSandboxSimulator.logger.info("Database dropped: #{db_name}")
|
|
56
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
57
|
+
if e.message.include?("does not exist")
|
|
58
|
+
SkytabSandboxSimulator.logger.info("Database does not exist: #{db_name}")
|
|
59
|
+
else
|
|
60
|
+
raise
|
|
61
|
+
end
|
|
62
|
+
ensure
|
|
63
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Return the configured database URL from .env.json.
|
|
67
|
+
#
|
|
68
|
+
# @return [String] PostgreSQL URL
|
|
69
|
+
# @raise [Error] if no DATABASE_URL is configured
|
|
70
|
+
def database_url
|
|
71
|
+
url = Configuration.database_url_from_file
|
|
72
|
+
raise Error, "No DATABASE_URL found in .env.json" unless url
|
|
73
|
+
|
|
74
|
+
url
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Establish a standalone ActiveRecord connection to PostgreSQL.
|
|
78
|
+
#
|
|
79
|
+
# @param url [String] A PostgreSQL connection URL
|
|
80
|
+
# @return [void]
|
|
81
|
+
def connect!(url)
|
|
82
|
+
unless url.match?(%r{\Apostgres(ql)?://}i)
|
|
83
|
+
raise ArgumentError, "Expected a PostgreSQL URL (postgres:// or postgresql://), got: #{url.split('://').first}://"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
ActiveRecord::Base.establish_connection(url)
|
|
87
|
+
|
|
88
|
+
# Verify the connection is actually usable
|
|
89
|
+
ActiveRecord::Base.connection.execute("SELECT 1")
|
|
90
|
+
|
|
91
|
+
ActiveRecord::Base.logger = SkytabSandboxSimulator.logger
|
|
92
|
+
|
|
93
|
+
SkytabSandboxSimulator.logger.info("Database connected: #{sanitize_url(url)}")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Run pending migrations from lib/skytab_sandbox_simulator/db/migrate/.
|
|
97
|
+
#
|
|
98
|
+
# @return [void]
|
|
99
|
+
def migrate!
|
|
100
|
+
ensure_connected!
|
|
101
|
+
|
|
102
|
+
SkytabSandboxSimulator.logger.info("Running migrations from #{MIGRATIONS_PATH}")
|
|
103
|
+
|
|
104
|
+
context = ActiveRecord::MigrationContext.new(MIGRATIONS_PATH)
|
|
105
|
+
context.migrate
|
|
106
|
+
|
|
107
|
+
SkytabSandboxSimulator.logger.info("Migrations complete")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Seed the database with realistic SkyTab data using FactoryBot.
|
|
111
|
+
#
|
|
112
|
+
# @param business_type [Symbol, String, nil] Optional business type.
|
|
113
|
+
# Seeds all types if nil.
|
|
114
|
+
# @return [Hash] Summary counts
|
|
115
|
+
def seed!(business_type: nil)
|
|
116
|
+
ensure_connected!
|
|
117
|
+
load_factories!
|
|
118
|
+
|
|
119
|
+
business_type ||= SkytabSandboxSimulator.configuration.business_type
|
|
120
|
+
Seeder.seed!(business_type: business_type)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check whether a database connection is established and usable.
|
|
124
|
+
#
|
|
125
|
+
# @return [Boolean]
|
|
126
|
+
def connected?
|
|
127
|
+
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
|
128
|
+
conn.active?
|
|
129
|
+
end
|
|
130
|
+
rescue StandardError
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Disconnect from the database and clean up the connection pool.
|
|
135
|
+
#
|
|
136
|
+
# @return [void]
|
|
137
|
+
def disconnect!
|
|
138
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
139
|
+
SkytabSandboxSimulator.logger.info("Database disconnected")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Build the test database URL.
|
|
143
|
+
#
|
|
144
|
+
# @param base_url [String, nil] Base URL to derive test URL from.
|
|
145
|
+
# @return [String] PostgreSQL URL pointing to the test database
|
|
146
|
+
def test_database_url(base_url: nil)
|
|
147
|
+
url = base_url || Configuration.database_url_from_file
|
|
148
|
+
return "postgres://localhost:5432/#{TEST_DATABASE}" if url.nil?
|
|
149
|
+
|
|
150
|
+
uri = URI.parse(url)
|
|
151
|
+
uri.path = "/#{TEST_DATABASE}"
|
|
152
|
+
uri.to_s
|
|
153
|
+
rescue URI::InvalidURIError
|
|
154
|
+
"postgres://localhost:5432/#{TEST_DATABASE}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
# Raise if no database connection has been established yet.
|
|
160
|
+
def ensure_connected!
|
|
161
|
+
return if connected?
|
|
162
|
+
|
|
163
|
+
raise SkytabSandboxSimulator::Error,
|
|
164
|
+
"Database not connected. Call Database.connect!(url) first."
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Load FactoryBot factory definitions from the factories directory.
|
|
168
|
+
def load_factories!
|
|
169
|
+
return if @factories_loaded
|
|
170
|
+
|
|
171
|
+
require "factory_bot"
|
|
172
|
+
|
|
173
|
+
factories_path = File.expand_path("db/factories", __dir__)
|
|
174
|
+
FactoryBot.definition_file_paths = [factories_path] if Dir.exist?(factories_path)
|
|
175
|
+
FactoryBot.find_definitions
|
|
176
|
+
@factories_loaded = true
|
|
177
|
+
rescue StandardError => e
|
|
178
|
+
SkytabSandboxSimulator.logger.warn("Could not load factories: #{e.message}")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Strip credentials from a database URL for safe logging.
|
|
182
|
+
def sanitize_url(url)
|
|
183
|
+
uri = URI.parse(url)
|
|
184
|
+
uri.user = "***" if uri.user
|
|
185
|
+
uri.password = "***" if uri.password
|
|
186
|
+
uri.to_s
|
|
187
|
+
rescue URI::InvalidURIError
|
|
188
|
+
url.gsub(%r{://[^@]+@}, "://***:***@")
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|