lightspeed_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 +5 -0
- data/LICENSE +21 -0
- data/README.md +215 -0
- data/bin/simulate +162 -0
- data/lib/lightspeed_sandbox_simulator/configuration.rb +151 -0
- data/lib/lightspeed_sandbox_simulator/data/bar_nightclub/categories.json +9 -0
- data/lib/lightspeed_sandbox_simulator/data/bar_nightclub/items.json +26 -0
- data/lib/lightspeed_sandbox_simulator/data/bar_nightclub/tenders.json +8 -0
- data/lib/lightspeed_sandbox_simulator/data/cafe_bakery/categories.json +9 -0
- data/lib/lightspeed_sandbox_simulator/data/cafe_bakery/items.json +28 -0
- data/lib/lightspeed_sandbox_simulator/data/cafe_bakery/tenders.json +8 -0
- data/lib/lightspeed_sandbox_simulator/data/restaurant/categories.json +9 -0
- data/lib/lightspeed_sandbox_simulator/data/restaurant/items.json +29 -0
- data/lib/lightspeed_sandbox_simulator/data/restaurant/tenders.json +9 -0
- data/lib/lightspeed_sandbox_simulator/data/retail_general/categories.json +9 -0
- data/lib/lightspeed_sandbox_simulator/data/retail_general/items.json +17 -0
- data/lib/lightspeed_sandbox_simulator/data/retail_general/tenders.json +8 -0
- data/lib/lightspeed_sandbox_simulator/database.rb +116 -0
- data/lib/lightspeed_sandbox_simulator/db/factories/api_requests.rb +10 -0
- data/lib/lightspeed_sandbox_simulator/db/factories/business_types.rb +9 -0
- data/lib/lightspeed_sandbox_simulator/db/factories/categories.rb +9 -0
- data/lib/lightspeed_sandbox_simulator/db/factories/items.rb +11 -0
- data/lib/lightspeed_sandbox_simulator/db/factories/simulated_orders.rb +17 -0
- data/lib/lightspeed_sandbox_simulator/db/factories/simulated_payments.rb +13 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000001_enable_pgcrypto.rb +7 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000002_create_business_types.rb +14 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000003_create_categories.rb +13 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000004_create_items.rb +14 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000005_create_simulated_orders.rb +23 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000006_create_simulated_payments.rb +16 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000007_create_api_requests.rb +18 -0
- data/lib/lightspeed_sandbox_simulator/db/migrate/20260313000008_create_daily_summaries.rb +20 -0
- data/lib/lightspeed_sandbox_simulator/generators/data_loader.rb +75 -0
- data/lib/lightspeed_sandbox_simulator/generators/entity_generator.rb +96 -0
- data/lib/lightspeed_sandbox_simulator/generators/order_generator.rb +293 -0
- data/lib/lightspeed_sandbox_simulator/models/api_request.rb +9 -0
- data/lib/lightspeed_sandbox_simulator/models/business_type.rb +12 -0
- data/lib/lightspeed_sandbox_simulator/models/category.rb +12 -0
- data/lib/lightspeed_sandbox_simulator/models/daily_summary.rb +44 -0
- data/lib/lightspeed_sandbox_simulator/models/item.rb +11 -0
- data/lib/lightspeed_sandbox_simulator/models/simulated_order.rb +15 -0
- data/lib/lightspeed_sandbox_simulator/models/simulated_payment.rb +13 -0
- data/lib/lightspeed_sandbox_simulator/seeder.rb +79 -0
- data/lib/lightspeed_sandbox_simulator/services/base_service.rb +158 -0
- data/lib/lightspeed_sandbox_simulator/services/lightspeed/business_service.rb +21 -0
- data/lib/lightspeed_sandbox_simulator/services/lightspeed/menu_service.rb +70 -0
- data/lib/lightspeed_sandbox_simulator/services/lightspeed/order_service.rb +74 -0
- data/lib/lightspeed_sandbox_simulator/services/lightspeed/payment_method_service.rb +30 -0
- data/lib/lightspeed_sandbox_simulator/services/lightspeed/payment_service.rb +55 -0
- data/lib/lightspeed_sandbox_simulator/services/lightspeed/services_manager.rb +54 -0
- data/lib/lightspeed_sandbox_simulator.rb +30 -0
- metadata +332 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "Buffalo Wings", "price": 12.99, "category": "Appetizers", "sku": "REST-APP-001" },
|
|
4
|
+
{ "name": "Mozzarella Sticks", "price": 9.99, "category": "Appetizers", "sku": "REST-APP-002" },
|
|
5
|
+
{ "name": "Loaded Nachos", "price": 13.99, "category": "Appetizers", "sku": "REST-APP-003" },
|
|
6
|
+
{ "name": "Bruschetta", "price": 10.99, "category": "Appetizers", "sku": "REST-APP-004" },
|
|
7
|
+
{ "name": "Calamari", "price": 11.99, "category": "Appetizers", "sku": "REST-APP-005" },
|
|
8
|
+
{ "name": "Grilled Salmon", "price": 24.99, "category": "Entrees", "sku": "REST-ENT-001" },
|
|
9
|
+
{ "name": "NY Strip Steak", "price": 29.99, "category": "Entrees", "sku": "REST-ENT-002" },
|
|
10
|
+
{ "name": "Chicken Parmesan", "price": 18.99, "category": "Entrees", "sku": "REST-ENT-003" },
|
|
11
|
+
{ "name": "Fish and Chips", "price": 16.99, "category": "Entrees", "sku": "REST-ENT-004" },
|
|
12
|
+
{ "name": "Pasta Alfredo", "price": 15.99, "category": "Entrees", "sku": "REST-ENT-005" },
|
|
13
|
+
{ "name": "BBQ Ribs", "price": 22.99, "category": "Entrees", "sku": "REST-ENT-006" },
|
|
14
|
+
{ "name": "Caesar Salad", "price": 12.99, "category": "Entrees", "sku": "REST-ENT-007" },
|
|
15
|
+
{ "name": "French Fries", "price": 5.99, "category": "Sides", "sku": "REST-SID-001" },
|
|
16
|
+
{ "name": "Coleslaw", "price": 4.99, "category": "Sides", "sku": "REST-SID-002" },
|
|
17
|
+
{ "name": "Mac and Cheese", "price": 6.99, "category": "Sides", "sku": "REST-SID-003" },
|
|
18
|
+
{ "name": "Garden Salad", "price": 5.49, "category": "Sides", "sku": "REST-SID-004" },
|
|
19
|
+
{ "name": "Onion Rings", "price": 6.49, "category": "Sides", "sku": "REST-SID-005" },
|
|
20
|
+
{ "name": "Coca-Cola", "price": 2.99, "category": "Drinks", "sku": "REST-DRK-001" },
|
|
21
|
+
{ "name": "Iced Tea", "price": 2.99, "category": "Drinks", "sku": "REST-DRK-002" },
|
|
22
|
+
{ "name": "Draft Beer", "price": 6.99, "category": "Drinks", "sku": "REST-DRK-003" },
|
|
23
|
+
{ "name": "House Wine", "price": 8.99, "category": "Drinks", "sku": "REST-DRK-004" },
|
|
24
|
+
{ "name": "Lemonade", "price": 3.49, "category": "Drinks", "sku": "REST-DRK-005" },
|
|
25
|
+
{ "name": "Chocolate Cake", "price": 8.99, "category": "Desserts", "sku": "REST-DES-001" },
|
|
26
|
+
{ "name": "Cheesecake", "price": 9.99, "category": "Desserts", "sku": "REST-DES-002" },
|
|
27
|
+
{ "name": "Ice Cream Sundae", "price": 7.99, "category": "Desserts", "sku": "REST-DES-003" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "name": "Cash", "description": "Cash payment", "weight": 25 },
|
|
4
|
+
{ "name": "Credit Card", "description": "Credit card payment", "weight": 45 },
|
|
5
|
+
{ "name": "Debit Card", "description": "Debit card payment", "weight": 20 },
|
|
6
|
+
{ "name": "Gift Card", "description": "Gift card payment", "weight": 5 },
|
|
7
|
+
{ "name": "Check", "description": "Check payment", "weight": 5 }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": [
|
|
3
|
+
{ "name": "Electronics", "sort_order": 1, "description": "Electronic devices and accessories" },
|
|
4
|
+
{ "name": "Clothing", "sort_order": 2, "description": "Apparel and accessories" },
|
|
5
|
+
{ "name": "Home & Garden", "sort_order": 3, "description": "Home goods and garden supplies" },
|
|
6
|
+
{ "name": "Health & Beauty", "sort_order": 4, "description": "Health and beauty products" },
|
|
7
|
+
{ "name": "Groceries", "sort_order": 5, "description": "Food and grocery items" }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{ "name": "Phone Charger", "price": 14.99, "category": "Electronics", "sku": "RET-ELC-001" },
|
|
4
|
+
{ "name": "Bluetooth Speaker", "price": 29.99, "category": "Electronics", "sku": "RET-ELC-002" },
|
|
5
|
+
{ "name": "USB Cable", "price": 9.99, "category": "Electronics", "sku": "RET-ELC-003" },
|
|
6
|
+
{ "name": "T-Shirt", "price": 19.99, "category": "Clothing", "sku": "RET-CLO-001" },
|
|
7
|
+
{ "name": "Baseball Cap", "price": 14.99, "category": "Clothing", "sku": "RET-CLO-002" },
|
|
8
|
+
{ "name": "Socks Pack", "price": 8.99, "category": "Clothing", "sku": "RET-CLO-003" },
|
|
9
|
+
{ "name": "Candle", "price": 12.99, "category": "Home & Garden", "sku": "RET-HOM-001" },
|
|
10
|
+
{ "name": "Plant Pot", "price": 9.99, "category": "Home & Garden", "sku": "RET-HOM-002" },
|
|
11
|
+
{ "name": "Picture Frame", "price": 11.99, "category": "Home & Garden", "sku": "RET-HOM-003" },
|
|
12
|
+
{ "name": "Hand Cream", "price": 7.99, "category": "Health & Beauty", "sku": "RET-HLT-001" },
|
|
13
|
+
{ "name": "Lip Balm", "price": 4.99, "category": "Health & Beauty", "sku": "RET-HLT-002" },
|
|
14
|
+
{ "name": "Snack Bar", "price": 2.99, "category": "Groceries", "sku": "RET-GRC-001" },
|
|
15
|
+
{ "name": "Water Bottle", "price": 1.99, "category": "Groceries", "sku": "RET-GRC-002" }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tenders": [
|
|
3
|
+
{ "name": "Cash", "description": "Cash payment", "weight": 20 },
|
|
4
|
+
{ "name": "Credit Card", "description": "Credit card payment", "weight": 50 },
|
|
5
|
+
{ "name": "Debit Card", "description": "Debit card payment", "weight": 25 },
|
|
6
|
+
{ "name": "Gift Card", "description": "Gift card payment", "weight": 5 }
|
|
7
|
+
]
|
|
8
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module LightspeedSandboxSimulator
|
|
7
|
+
class Database
|
|
8
|
+
MIGRATIONS_PATH = File.expand_path('db/migrate', __dir__)
|
|
9
|
+
TEST_DATABASE = 'lightspeed_simulator_test'
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def connect!(url)
|
|
13
|
+
raise ArgumentError, 'Only PostgreSQL URLs are supported' unless url.match?(%r{\Apostgres(ql)?://})
|
|
14
|
+
|
|
15
|
+
ActiveRecord::Base.establish_connection(url)
|
|
16
|
+
ActiveRecord::Base.connection.execute('SELECT 1')
|
|
17
|
+
ActiveRecord::Base.logger = LightspeedSandboxSimulator.logger
|
|
18
|
+
LightspeedSandboxSimulator.logger.info("Connected to #{sanitize_url(url)}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def connected?
|
|
22
|
+
ActiveRecord::Base.connection_pool.connected? && ActiveRecord::Base.connection.active?
|
|
23
|
+
rescue StandardError
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def disconnect!
|
|
28
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create!(url)
|
|
32
|
+
db_name = URI.parse(url).path.delete_prefix('/')
|
|
33
|
+
admin_url = url.sub(%r{/[^/]+\z}, '/postgres')
|
|
34
|
+
|
|
35
|
+
ActiveRecord::Base.establish_connection(admin_url)
|
|
36
|
+
ActiveRecord::Base.connection.create_database(db_name)
|
|
37
|
+
LightspeedSandboxSimulator.logger.info("Created database: #{db_name}")
|
|
38
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
39
|
+
raise unless e.message.include?('already exists')
|
|
40
|
+
|
|
41
|
+
LightspeedSandboxSimulator.logger.info("Database already exists: #{db_name}")
|
|
42
|
+
ensure
|
|
43
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def drop!(url)
|
|
47
|
+
db_name = URI.parse(url).path.delete_prefix('/')
|
|
48
|
+
admin_url = url.sub(%r{/[^/]+\z}, '/postgres')
|
|
49
|
+
|
|
50
|
+
ActiveRecord::Base.establish_connection(admin_url)
|
|
51
|
+
ActiveRecord::Base.connection.drop_database(db_name)
|
|
52
|
+
LightspeedSandboxSimulator.logger.info("Dropped database: #{db_name}")
|
|
53
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
54
|
+
raise unless e.message.include?('does not exist')
|
|
55
|
+
|
|
56
|
+
LightspeedSandboxSimulator.logger.info("Database does not exist: #{db_name}")
|
|
57
|
+
ensure
|
|
58
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def migrate!
|
|
62
|
+
raise Error, 'Database not connected' unless connected?
|
|
63
|
+
|
|
64
|
+
ActiveRecord::MigrationContext.new(MIGRATIONS_PATH).migrate
|
|
65
|
+
LightspeedSandboxSimulator.logger.info('Migrations complete')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def seed!(business_type: :restaurant)
|
|
69
|
+
raise Error, 'Database not connected' unless connected?
|
|
70
|
+
|
|
71
|
+
load_factories!
|
|
72
|
+
Seeder.seed!(business_type: business_type)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def database_url
|
|
76
|
+
url = Configuration.database_url_from_file
|
|
77
|
+
raise Error, 'DATABASE_URL not found in .env.json' unless url
|
|
78
|
+
|
|
79
|
+
url
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_database_url(base_url: nil)
|
|
83
|
+
return "postgres://localhost:5432/#{TEST_DATABASE}" unless base_url
|
|
84
|
+
|
|
85
|
+
uri = URI.parse(base_url)
|
|
86
|
+
uri.path = "/#{TEST_DATABASE}"
|
|
87
|
+
uri.to_s
|
|
88
|
+
rescue URI::InvalidURIError
|
|
89
|
+
"postgres://localhost:5432/#{TEST_DATABASE}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def sanitize_url(url)
|
|
95
|
+
uri = URI.parse(url)
|
|
96
|
+
has_password = !uri.password.nil?
|
|
97
|
+
uri.user = '***' if uri.user
|
|
98
|
+
uri.password = '***' if has_password
|
|
99
|
+
uri.to_s
|
|
100
|
+
rescue URI::InvalidURIError
|
|
101
|
+
url.gsub(%r{://[^@]+@}, '://***:***@')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def load_factories!
|
|
105
|
+
return if @factories_loaded
|
|
106
|
+
|
|
107
|
+
factories_dir = File.expand_path('db/factories', __dir__)
|
|
108
|
+
FactoryBot.definition_file_paths = [factories_dir] if Dir.exist?(factories_dir)
|
|
109
|
+
FactoryBot.find_definitions
|
|
110
|
+
@factories_loaded = true
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
LightspeedSandboxSimulator.logger.warn("Failed to load factories: #{e.message}")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :api_request, class: 'LightspeedSandboxSimulator::Models::ApiRequest' do
|
|
5
|
+
http_method { 'GET' }
|
|
6
|
+
url { 'https://api.lsk.lightspeed.app/api/v2/businesses/123/menu/categories' }
|
|
7
|
+
response_status { 200 }
|
|
8
|
+
duration_ms { 150 }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :simulated_order, class: 'LightspeedSandboxSimulator::Models::SimulatedOrder' do
|
|
5
|
+
order_id { rand(1000..9999).to_s }
|
|
6
|
+
order_type { 'local' }
|
|
7
|
+
status { 'paid' }
|
|
8
|
+
meal_period { 'lunch' }
|
|
9
|
+
dining_option { 'eat_in' }
|
|
10
|
+
order_date { Date.today }
|
|
11
|
+
total { 2500 }
|
|
12
|
+
tax_amount { 500 }
|
|
13
|
+
tip_amount { 375 }
|
|
14
|
+
discount_amount { 0 }
|
|
15
|
+
item_count { 3 }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :simulated_payment, class: 'LightspeedSandboxSimulator::Models::SimulatedPayment' do
|
|
5
|
+
association :simulated_order
|
|
6
|
+
payment_id { rand(1000..9999).to_s }
|
|
7
|
+
tender_name { 'Cash' }
|
|
8
|
+
tender_type { 'CASH' }
|
|
9
|
+
status { 'successful' }
|
|
10
|
+
amount { 2500 }
|
|
11
|
+
tip_amount { 375 }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateBusinessTypes < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :business_types, id: :uuid do |t|
|
|
6
|
+
t.string :key, null: false
|
|
7
|
+
t.string :name, null: false
|
|
8
|
+
t.string :industry, default: 'food'
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :business_types, :key, unique: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateCategories < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :categories, id: :uuid do |t|
|
|
6
|
+
t.references :business_type, type: :uuid, null: false, foreign_key: true
|
|
7
|
+
t.string :name, null: false
|
|
8
|
+
t.string :description
|
|
9
|
+
t.integer :sort_order, default: 0
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateItems < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :items, id: :uuid do |t|
|
|
6
|
+
t.references :category, type: :uuid, null: false, foreign_key: true
|
|
7
|
+
t.string :name, null: false
|
|
8
|
+
t.integer :price, null: false
|
|
9
|
+
t.string :sku
|
|
10
|
+
t.boolean :taxable, default: true
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSimulatedOrders < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :simulated_orders, id: :uuid do |t|
|
|
6
|
+
t.string :order_id
|
|
7
|
+
t.string :order_type
|
|
8
|
+
t.string :status, default: 'paid'
|
|
9
|
+
t.string :meal_period
|
|
10
|
+
t.string :dining_option
|
|
11
|
+
t.date :order_date
|
|
12
|
+
t.integer :total, default: 0
|
|
13
|
+
t.integer :tax_amount, default: 0
|
|
14
|
+
t.integer :tip_amount, default: 0
|
|
15
|
+
t.integer :discount_amount, default: 0
|
|
16
|
+
t.integer :item_count, default: 0
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :simulated_orders, :order_date
|
|
21
|
+
add_index :simulated_orders, :status
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSimulatedPayments < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :simulated_payments, id: :uuid do |t|
|
|
6
|
+
t.references :simulated_order, type: :uuid, null: false, foreign_key: true
|
|
7
|
+
t.string :payment_id
|
|
8
|
+
t.string :tender_name
|
|
9
|
+
t.string :tender_type
|
|
10
|
+
t.string :status, default: 'successful'
|
|
11
|
+
t.integer :amount, default: 0
|
|
12
|
+
t.integer :tip_amount, default: 0
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateApiRequests < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :api_requests, id: :uuid do |t|
|
|
6
|
+
t.string :http_method
|
|
7
|
+
t.text :url
|
|
8
|
+
t.text :request_payload
|
|
9
|
+
t.integer :response_status
|
|
10
|
+
t.text :response_payload
|
|
11
|
+
t.integer :duration_ms
|
|
12
|
+
t.string :resource_type
|
|
13
|
+
t.string :resource_id
|
|
14
|
+
t.text :error_message
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateDailySummaries < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :daily_summaries, id: :uuid do |t|
|
|
6
|
+
t.date :summary_date, null: false
|
|
7
|
+
t.integer :order_count, default: 0
|
|
8
|
+
t.integer :payment_count, default: 0
|
|
9
|
+
t.integer :refund_count, default: 0
|
|
10
|
+
t.integer :total_revenue, default: 0
|
|
11
|
+
t.integer :total_tax, default: 0
|
|
12
|
+
t.integer :total_tips, default: 0
|
|
13
|
+
t.integer :total_discounts, default: 0
|
|
14
|
+
t.jsonb :breakdown, default: {}
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
add_index :daily_summaries, :summary_date, unique: true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module LightspeedSandboxSimulator
|
|
6
|
+
module Generators
|
|
7
|
+
class DataLoader
|
|
8
|
+
DATA_DIR = File.expand_path('../data', __dir__)
|
|
9
|
+
BUSINESS_TYPES = %i[restaurant cafe_bakery bar_nightclub retail_general].freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :business_type
|
|
12
|
+
|
|
13
|
+
def initialize(business_type: :restaurant)
|
|
14
|
+
@business_type = business_type
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def load_categories
|
|
18
|
+
if Database.connected?
|
|
19
|
+
bt = Models::BusinessType.find_by(key: business_type.to_s)
|
|
20
|
+
return load_categories_from_db(bt) if bt
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
data = load_from_json('categories')
|
|
24
|
+
data.fetch('categories', [])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_items
|
|
28
|
+
if Database.connected?
|
|
29
|
+
bt = Models::BusinessType.find_by(key: business_type.to_s)
|
|
30
|
+
return load_items_from_db(bt) if bt
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
data = load_from_json('items')
|
|
34
|
+
data.fetch('items', [])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def load_tenders
|
|
38
|
+
data = load_from_json('tenders')
|
|
39
|
+
data.fetch('tenders', [])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_items_by_category
|
|
43
|
+
load_items.group_by { |i| i['category'] }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def load_categories_from_db(btype)
|
|
49
|
+
btype.categories.order(:sort_order).map do |cat|
|
|
50
|
+
{ 'name' => cat.name, 'sort_order' => cat.sort_order, 'description' => cat.description }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_items_from_db(btype)
|
|
55
|
+
btype.items.includes(:category).map do |item|
|
|
56
|
+
{
|
|
57
|
+
'name' => item.name,
|
|
58
|
+
'price' => item.price / 100.0,
|
|
59
|
+
'category' => item.category&.name,
|
|
60
|
+
'sku' => item.sku
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def load_from_json(type)
|
|
66
|
+
path = File.join(DATA_DIR, business_type.to_s, "#{type}.json")
|
|
67
|
+
return {} unless File.exist?(path)
|
|
68
|
+
|
|
69
|
+
JSON.parse(File.read(path))
|
|
70
|
+
rescue JSON::ParserError
|
|
71
|
+
{}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LightspeedSandboxSimulator
|
|
4
|
+
module Generators
|
|
5
|
+
class EntityGenerator
|
|
6
|
+
def initialize(config: nil, business_type: :restaurant)
|
|
7
|
+
@config = config || LightspeedSandboxSimulator.configuration
|
|
8
|
+
@business_type = business_type
|
|
9
|
+
@loader = DataLoader.new(business_type: business_type)
|
|
10
|
+
@manager = Services::Lightspeed::ServicesManager.new(config: @config)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def setup_all
|
|
14
|
+
categories = setup_categories
|
|
15
|
+
items = setup_items(categories)
|
|
16
|
+
payment_methods = setup_payment_methods
|
|
17
|
+
|
|
18
|
+
LightspeedSandboxSimulator.logger.info("Setup complete: #{categories.size} categories, " \
|
|
19
|
+
"#{items.size} items, #{payment_methods.size} payment methods")
|
|
20
|
+
|
|
21
|
+
{ categories: categories, items: items, payment_methods: payment_methods }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def setup_categories
|
|
25
|
+
data = @loader.load_categories
|
|
26
|
+
created = []
|
|
27
|
+
|
|
28
|
+
data.each do |cat|
|
|
29
|
+
existing = @manager.menu.find_category_by_name(cat['name'])
|
|
30
|
+
if existing
|
|
31
|
+
created << existing
|
|
32
|
+
else
|
|
33
|
+
result = @manager.menu.create_category(name: cat['name'], sort_order: cat['sort_order'])
|
|
34
|
+
created << result if result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
created
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def setup_items(categories)
|
|
42
|
+
data = @loader.load_items
|
|
43
|
+
category_map = build_category_map(categories)
|
|
44
|
+
created = []
|
|
45
|
+
|
|
46
|
+
data.each do |item|
|
|
47
|
+
existing = @manager.menu.find_item_by_name(item['name'])
|
|
48
|
+
if existing
|
|
49
|
+
created << existing
|
|
50
|
+
else
|
|
51
|
+
category_id = category_map[item['category']]
|
|
52
|
+
result = @manager.menu.create_item(
|
|
53
|
+
name: item['name'],
|
|
54
|
+
price: item['price'],
|
|
55
|
+
category_id: category_id,
|
|
56
|
+
sku: item['sku']
|
|
57
|
+
)
|
|
58
|
+
created << result if result
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
created
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def setup_payment_methods
|
|
66
|
+
data = @loader.load_tenders
|
|
67
|
+
created = []
|
|
68
|
+
|
|
69
|
+
data.each do |tender|
|
|
70
|
+
existing = @manager.payment_methods.find_payment_method_by_name(tender['name'])
|
|
71
|
+
if existing
|
|
72
|
+
created << existing
|
|
73
|
+
else
|
|
74
|
+
result = @manager.payment_methods.create_payment_method(
|
|
75
|
+
name: tender['name'],
|
|
76
|
+
type: tender.fetch('type', 'OTHER')
|
|
77
|
+
)
|
|
78
|
+
created << result if result
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
created
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def build_category_map(categories)
|
|
88
|
+
categories.each_with_object({}) do |cat, map|
|
|
89
|
+
name = cat['name'] || cat[:name]
|
|
90
|
+
id = cat['id'] || cat[:id]
|
|
91
|
+
map[name] = id if name && id
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|