epos_now_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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/LICENSE +21 -0
  4. data/README.md +380 -0
  5. data/bin/simulate +309 -0
  6. data/lib/epos_now_sandbox_simulator/configuration.rb +173 -0
  7. data/lib/epos_now_sandbox_simulator/data/bar_nightclub/categories.json +9 -0
  8. data/lib/epos_now_sandbox_simulator/data/bar_nightclub/items.json +26 -0
  9. data/lib/epos_now_sandbox_simulator/data/bar_nightclub/tenders.json +8 -0
  10. data/lib/epos_now_sandbox_simulator/data/cafe_bakery/categories.json +9 -0
  11. data/lib/epos_now_sandbox_simulator/data/cafe_bakery/items.json +28 -0
  12. data/lib/epos_now_sandbox_simulator/data/cafe_bakery/tenders.json +8 -0
  13. data/lib/epos_now_sandbox_simulator/data/restaurant/categories.json +9 -0
  14. data/lib/epos_now_sandbox_simulator/data/restaurant/items.json +29 -0
  15. data/lib/epos_now_sandbox_simulator/data/restaurant/tenders.json +9 -0
  16. data/lib/epos_now_sandbox_simulator/data/retail_general/categories.json +9 -0
  17. data/lib/epos_now_sandbox_simulator/data/retail_general/items.json +17 -0
  18. data/lib/epos_now_sandbox_simulator/data/retail_general/tenders.json +8 -0
  19. data/lib/epos_now_sandbox_simulator/database.rb +136 -0
  20. data/lib/epos_now_sandbox_simulator/db/factories/api_requests.rb +13 -0
  21. data/lib/epos_now_sandbox_simulator/db/factories/business_types.rb +34 -0
  22. data/lib/epos_now_sandbox_simulator/db/factories/categories.rb +10 -0
  23. data/lib/epos_now_sandbox_simulator/db/factories/items.rb +12 -0
  24. data/lib/epos_now_sandbox_simulator/db/factories/simulated_orders.rb +25 -0
  25. data/lib/epos_now_sandbox_simulator/db/factories/simulated_payments.rb +14 -0
  26. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000001_enable_pgcrypto.rb +7 -0
  27. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000002_create_business_types.rb +16 -0
  28. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000003_create_categories.rb +16 -0
  29. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000004_create_items.rb +19 -0
  30. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000005_create_simulated_orders.rb +27 -0
  31. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000006_create_simulated_payments.rb +22 -0
  32. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000007_create_api_requests.rb +22 -0
  33. data/lib/epos_now_sandbox_simulator/db/migrate/20260312000008_create_daily_summaries.rb +21 -0
  34. data/lib/epos_now_sandbox_simulator/generators/data_loader.rb +100 -0
  35. data/lib/epos_now_sandbox_simulator/generators/entity_generator.rb +103 -0
  36. data/lib/epos_now_sandbox_simulator/generators/order_generator.rb +336 -0
  37. data/lib/epos_now_sandbox_simulator/models/api_request.rb +16 -0
  38. data/lib/epos_now_sandbox_simulator/models/business_type.rb +16 -0
  39. data/lib/epos_now_sandbox_simulator/models/category.rb +18 -0
  40. data/lib/epos_now_sandbox_simulator/models/daily_summary.rb +43 -0
  41. data/lib/epos_now_sandbox_simulator/models/item.rb +20 -0
  42. data/lib/epos_now_sandbox_simulator/models/simulated_order.rb +21 -0
  43. data/lib/epos_now_sandbox_simulator/models/simulated_payment.rb +17 -0
  44. data/lib/epos_now_sandbox_simulator/seeder.rb +119 -0
  45. data/lib/epos_now_sandbox_simulator/services/base_service.rb +248 -0
  46. data/lib/epos_now_sandbox_simulator/services/epos_now/inventory_service.rb +178 -0
  47. data/lib/epos_now_sandbox_simulator/services/epos_now/services_manager.rb +56 -0
  48. data/lib/epos_now_sandbox_simulator/services/epos_now/tax_service.rb +45 -0
  49. data/lib/epos_now_sandbox_simulator/services/epos_now/tender_service.rb +90 -0
  50. data/lib/epos_now_sandbox_simulator/services/epos_now/transaction_service.rb +171 -0
  51. data/lib/epos_now_sandbox_simulator.rb +49 -0
  52. metadata +334 -0
data/bin/simulate ADDED
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "epos_now_sandbox_simulator"
6
+ require "thor"
7
+
8
+ module EposNowSandboxSimulator
9
+ class CLI < Thor
10
+ class_option :verbose, type: :boolean, aliases: "-v", desc: "Enable verbose logging"
11
+ class_option :merchant_index, type: :numeric, aliases: "-i", desc: "Device index (0-based, from .env.json)"
12
+
13
+ desc "merchants", "List available devices from .env.json"
14
+ def merchants
15
+ puts "Epos Now Sandbox Simulator - Available Devices"
16
+ puts "=" * 55
17
+
18
+ config = EposNowSandboxSimulator.configuration
19
+ devices = config.available_merchants
20
+
21
+ if devices.empty?
22
+ puts "No devices found in .env.json"
23
+ return
24
+ end
25
+
26
+ puts "\n#{"Index".ljust(6)} #{"Device Name".ljust(30)} Credentials"
27
+ puts "-" * 55
28
+
29
+ devices.each_with_index do |d, idx|
30
+ creds = d[:has_credentials] ? "OK" : "MISSING"
31
+ puts "#{idx.to_s.ljust(6)} #{(d[:name] || "unnamed").ljust(30)} #{creds}"
32
+ end
33
+
34
+ puts "\nUse: simulate <command> -i <index>"
35
+ end
36
+
37
+ desc "setup", "Set up POS entities (categories, products, tender types)"
38
+ option :business_type, type: :string, default: "restaurant",
39
+ desc: "Business type (restaurant, cafe_bakery, bar_nightclub, retail_general)"
40
+ def setup
41
+ configure_logging
42
+
43
+ puts "Epos Now Sandbox Simulator - Entity Setup"
44
+ puts "=" * 50
45
+
46
+ generator = Generators::EntityGenerator.new(business_type: options[:business_type].to_sym)
47
+ stats = generator.setup_all
48
+
49
+ puts "\nSetup complete!"
50
+ puts " Tender types: #{stats[:tender_types]}"
51
+ puts " Categories: #{stats[:categories]}"
52
+ puts " Products: #{stats[:products]}"
53
+ end
54
+
55
+ desc "generate", "Generate orders for today"
56
+ option :count, type: :numeric, aliases: "-n", desc: "Number of orders to generate"
57
+ option :refund_percentage, type: :numeric, aliases: "-r", default: 5,
58
+ desc: "Percentage of orders to refund (0-100, default 5)"
59
+ def generate
60
+ configure_logging
61
+
62
+ puts "Epos Now Sandbox Simulator - Order Generation"
63
+ puts "=" * 50
64
+
65
+ generator = Generators::OrderGenerator.new(refund_percentage: options[:refund_percentage])
66
+ orders = generator.generate_today(count: options[:count])
67
+
68
+ puts "\nGenerated #{orders.size} orders!"
69
+ end
70
+
71
+ desc "day", "Generate a realistic full day of operations"
72
+ option :multiplier, type: :numeric, aliases: "-x", default: 1.0, desc: "Order multiplier (0.5 = slow, 2.0 = busy)"
73
+ option :refund_percentage, type: :numeric, aliases: "-r", default: 5,
74
+ desc: "Percentage of orders to refund (0-100, default 5)"
75
+ def day
76
+ configure_logging
77
+
78
+ puts "Epos Now Sandbox Simulator - Full Day Generation"
79
+ puts "=" * 50
80
+
81
+ generator = Generators::OrderGenerator.new(refund_percentage: options[:refund_percentage])
82
+ orders = generator.generate_realistic_day(multiplier: options[:multiplier])
83
+
84
+ puts "\nGenerated #{orders.size} orders!"
85
+ end
86
+
87
+ desc "rush", "Generate a meal period rush"
88
+ option :period, type: :string, aliases: "-p", default: "dinner", desc: "Meal period (breakfast, lunch, happy_hour, dinner, late_night)"
89
+ option :count, type: :numeric, aliases: "-n", default: 15, desc: "Number of orders"
90
+ def rush
91
+ configure_logging
92
+
93
+ period = options[:period].to_sym
94
+ count = options[:count]
95
+
96
+ puts "Epos Now Sandbox Simulator - #{period.to_s.upcase} Rush"
97
+ puts "=" * 50
98
+
99
+ generator = Generators::OrderGenerator.new
100
+ orders = generator.generate_rush(period: period, count: count)
101
+
102
+ puts "\nGenerated #{orders.size} orders!"
103
+ end
104
+
105
+ desc "full", "Full setup + order generation"
106
+ option :business_type, type: :string, default: "restaurant", desc: "Business type"
107
+ option :multiplier, type: :numeric, aliases: "-x", default: 1.0, desc: "Order multiplier"
108
+ option :refund_percentage, type: :numeric, aliases: "-r", default: 5
109
+ def full
110
+ configure_logging
111
+
112
+ puts "Epos Now Sandbox Simulator - Full Setup + Generation"
113
+ puts "=" * 50
114
+
115
+ # Setup
116
+ generator = Generators::EntityGenerator.new(business_type: options[:business_type].to_sym)
117
+ stats = generator.setup_all
118
+ puts "Setup: #{stats[:categories]} categories, #{stats[:products]} products"
119
+
120
+ # Generate
121
+ order_gen = Generators::OrderGenerator.new(refund_percentage: options[:refund_percentage])
122
+ orders = order_gen.generate_realistic_day(multiplier: options[:multiplier])
123
+ puts "\nGenerated #{orders.size} orders!"
124
+ end
125
+
126
+ desc "status", "Show current entity counts"
127
+ def status
128
+ configure_logging
129
+
130
+ puts "Epos Now Sandbox Simulator - Status"
131
+ puts "=" * 50
132
+
133
+ services = Services::EposNow::ServicesManager.new
134
+
135
+ categories = services.inventory.fetch_categories
136
+ products = services.inventory.fetch_products
137
+ tender_types = services.tender.fetch_tender_types
138
+
139
+ puts "\nEpos Now API:"
140
+ puts " Categories: #{categories.size}"
141
+ puts " Products: #{products.size}"
142
+ puts " Tender Types: #{tender_types.size}"
143
+
144
+ return unless Database.connected?
145
+
146
+ puts "\nLocal Database:"
147
+ puts " Orders: #{Models::SimulatedOrder.count}"
148
+ puts " Payments: #{Models::SimulatedPayment.count}"
149
+ puts " API Logs: #{Models::ApiRequest.count}"
150
+ end
151
+
152
+ desc "summary", "Show daily summary"
153
+ option :date, type: :string, aliases: "-d", desc: "Date (YYYY-MM-DD, default today)"
154
+ def summary
155
+ configure_logging
156
+ connect_db
157
+
158
+ date = options[:date] ? Date.parse(options[:date]) : Date.today
159
+
160
+ puts "Epos Now Sandbox Simulator - Daily Summary (#{date})"
161
+ puts "=" * 50
162
+
163
+ summary = Models::DailySummary.find_by(summary_date: date)
164
+
165
+ unless summary
166
+ puts "No summary found for #{date}. Generating..."
167
+ summary = Models::DailySummary.generate_for!(date)
168
+ end
169
+
170
+ puts "\n Orders: #{summary.order_count}"
171
+ puts " Payments: #{summary.payment_count}"
172
+ puts " Refunds: #{summary.refund_count}"
173
+ puts " Revenue: $#{format("%.2f", summary.total_revenue / 100.0)}"
174
+ puts " Tax: $#{format("%.2f", summary.total_tax / 100.0)}"
175
+ puts " Tips: $#{format("%.2f", summary.total_tips / 100.0)}"
176
+ puts " Discounts: $#{format("%.2f", summary.total_discounts / 100.0)}"
177
+
178
+ return unless summary.breakdown.present?
179
+
180
+ puts "\n Breakdown:"
181
+ summary.breakdown.each do |key, values|
182
+ puts " #{key}: #{values}"
183
+ end
184
+ end
185
+
186
+ desc "orders", "List recent orders"
187
+ option :limit, type: :numeric, aliases: "-l", default: 20, desc: "Number of orders to show"
188
+ def orders
189
+ configure_logging
190
+ connect_db
191
+
192
+ puts "Epos Now Sandbox Simulator - Recent Orders"
193
+ puts "=" * 70
194
+
195
+ records = Models::SimulatedOrder.order(created_at: :desc).limit(options[:limit])
196
+
197
+ puts "\n#{"ID".ljust(8)} #{"Txn ID".ljust(10)} #{"Status".ljust(10)} #{"Total".ljust(10)} #{"Period".ljust(12)} Date"
198
+ puts "-" * 70
199
+
200
+ records.each do |o|
201
+ total = "$#{format("%.2f", o.total / 100.0)}"
202
+ period = (o.meal_period || "-").ljust(12)
203
+ txn_id = o.epos_now_transaction_id.to_s.ljust(10)
204
+ puts "#{o.id[0..6].ljust(8)} #{txn_id} #{o.status.ljust(10)} #{total.ljust(10)} #{period} #{o.business_date}"
205
+ end
206
+
207
+ puts "\nTotal: #{records.size} orders shown"
208
+ end
209
+
210
+ desc "audit", "Show recent API requests"
211
+ option :limit, type: :numeric, aliases: "-l", default: 20, desc: "Number of requests to show"
212
+ def audit
213
+ configure_logging
214
+ connect_db
215
+
216
+ puts "Epos Now Sandbox Simulator - API Audit Log"
217
+ puts "=" * 80
218
+
219
+ records = Models::ApiRequest.recent.limit(options[:limit])
220
+
221
+ puts "\n#{"Method".ljust(7)} #{"Status".ljust(7)} #{"Duration".ljust(10)} #{"Resource".ljust(20)} URL"
222
+ puts "-" * 80
223
+
224
+ records.each do |r|
225
+ short_url = r.url.sub("https://api.eposnowhq.com/", "")
226
+ status = (r.response_status || "-").to_s.ljust(7)
227
+ duration = "#{r.duration_ms}ms".ljust(10)
228
+ resource = (r.resource_type || "-").ljust(20)
229
+ puts "#{r.http_method.ljust(7)} #{status} #{duration} #{resource} #{short_url}"
230
+ end
231
+ end
232
+
233
+ desc "business_types", "List available business types"
234
+ def business_types
235
+ puts "Epos Now Sandbox Simulator - Business Types"
236
+ puts "=" * 50
237
+
238
+ Generators::DataLoader::BUSINESS_TYPES.each do |bt|
239
+ loader = Generators::DataLoader.new(business_type: bt)
240
+ cats = loader.load_categories
241
+ items = loader.load_items
242
+ puts "\n #{bt}: #{cats.size} categories, #{items.size} items"
243
+ cats.each { |c| puts " - #{c["name"]}" }
244
+ end
245
+ end
246
+
247
+ # ==========================================
248
+ # DATABASE MANAGEMENT
249
+ # ==========================================
250
+
251
+ desc "db SUBCOMMAND", "Database management (create, migrate, seed, reset)"
252
+ def db(subcommand)
253
+ configure_logging
254
+
255
+ case subcommand
256
+ when "create"
257
+ url = Database.database_url
258
+ Database.create!(url)
259
+ puts "Database created"
260
+ when "migrate"
261
+ connect_db
262
+ Database.migrate!
263
+ puts "Migrations complete"
264
+ when "seed"
265
+ connect_db
266
+ Database.seed!
267
+ puts "Seeding complete"
268
+ when "reset"
269
+ puts "This will DROP and recreate the database. Are you sure? (yes/no)"
270
+ confirm = $stdin.gets.chomp
271
+ if confirm == "yes"
272
+ url = Database.database_url
273
+ Database.drop!(url)
274
+ Database.create!(url)
275
+ Database.connect!(url)
276
+ Database.migrate!
277
+ Database.seed!
278
+ puts "Database reset complete"
279
+ else
280
+ puts "Cancelled"
281
+ end
282
+ else
283
+ puts "Unknown subcommand: #{subcommand}. Use: create, migrate, seed, reset"
284
+ end
285
+ end
286
+
287
+ private
288
+
289
+ def configure_logging
290
+ EposNowSandboxSimulator.configuration.log_level = Logger::DEBUG if options[:verbose]
291
+
292
+ # Load specific merchant if index given
293
+ return unless options[:merchant_index]
294
+
295
+ EposNowSandboxSimulator.configuration.load_merchant(index: options[:merchant_index])
296
+ end
297
+
298
+ def connect_db
299
+ url = Database.database_url
300
+ Database.connect!(url)
301
+ rescue StandardError => e
302
+ puts "Database not available: #{e.message}"
303
+ puts "Some commands require a database. Run: simulate db create && simulate db migrate"
304
+ exit 1
305
+ end
306
+ end
307
+ end
308
+
309
+ EposNowSandboxSimulator::CLI.start(ARGV)
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EposNowSandboxSimulator
4
+ class Configuration
5
+ attr_accessor :api_key, :api_secret, :environment, :log_level, :tax_rate, :business_type,
6
+ :device_name, :merchant_timezone
7
+
8
+ # Default timezone if not configured
9
+ DEFAULT_TIMEZONE = "America/Los_Angeles"
10
+
11
+ # Path to merchants JSON file
12
+ MERCHANTS_FILE = File.join(File.dirname(__FILE__), "..", "..", ".env.json")
13
+
14
+ # Epos Now API base URL
15
+ DEFAULT_API_URL = "https://api.eposnowhq.com"
16
+
17
+ def initialize
18
+ @api_key = ENV.fetch("EPOS_NOW_API_KEY", nil)
19
+ @api_secret = ENV.fetch("EPOS_NOW_API_SECRET", nil)
20
+ @device_name = ENV.fetch("EPOS_NOW_DEVICE_NAME", nil)
21
+ @environment = normalize_url(ENV.fetch("EPOS_NOW_API_URL", DEFAULT_API_URL))
22
+ @log_level = parse_log_level(ENV.fetch("LOG_LEVEL", "INFO"))
23
+ @tax_rate = ENV.fetch("TAX_RATE", "8.25").to_f
24
+ @business_type = ENV.fetch("BUSINESS_TYPE", "restaurant").to_sym
25
+ @merchant_timezone = ENV.fetch("MERCHANT_TIMEZONE", DEFAULT_TIMEZONE)
26
+
27
+ # Load from .env.json if api_key not set in ENV
28
+ load_from_merchants_file if @api_key.nil? || @api_key.empty?
29
+ end
30
+
31
+ # Build the Basic Auth token from API Key + Secret
32
+ # Epos Now: Base64(api_key:api_secret)
33
+ #
34
+ # @return [String] Base64-encoded credentials
35
+ def auth_token
36
+ require "base64"
37
+ Base64.strict_encode64("#{api_key}:#{api_secret}")
38
+ end
39
+
40
+ # Load configuration for a specific device from .env.json
41
+ #
42
+ # @param device_name [String, nil] Device name to load
43
+ # @param index [Integer, nil] Index of device in the list (0-based)
44
+ # @return [self]
45
+ def load_merchant(device_name: nil, index: nil)
46
+ merchants = load_merchants_file
47
+ return self if merchants.empty?
48
+
49
+ merchant = if device_name
50
+ merchants.find { |m| m["EPOS_NOW_DEVICE_NAME"] == device_name }
51
+ elsif index
52
+ merchants[index]
53
+ else
54
+ merchants.first
55
+ end
56
+
57
+ if merchant
58
+ apply_merchant_config(merchant)
59
+ logger.info "Loaded device: #{@device_name}"
60
+ else
61
+ logger.warn "Device not found: #{device_name || "index #{index}"}"
62
+ end
63
+
64
+ self
65
+ end
66
+
67
+ # List all available devices from .env.json
68
+ #
69
+ # @return [Array<Hash>] Array of device configs
70
+ def available_merchants
71
+ load_merchants_file.map do |m|
72
+ {
73
+ name: m["EPOS_NOW_DEVICE_NAME"],
74
+ has_credentials: !m["EPOS_NOW_API_KEY"].to_s.empty? && !m["EPOS_NOW_API_SECRET"].to_s.empty?
75
+ }
76
+ end
77
+ end
78
+
79
+ def validate!
80
+ raise ConfigurationError, "EPOS_NOW_API_KEY is required" if api_key.nil? || api_key.empty?
81
+ raise ConfigurationError, "EPOS_NOW_API_SECRET is required" if api_secret.nil? || api_secret.empty?
82
+
83
+ true
84
+ end
85
+
86
+ def logger
87
+ @logger ||= Logger.new($stdout).tap do |log|
88
+ log.level = @log_level
89
+ log.formatter = proc do |severity, datetime, _progname, msg|
90
+ timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S")
91
+ "[#{timestamp}] #{severity.ljust(5)} | #{msg}\n"
92
+ end
93
+ end
94
+ end
95
+
96
+ # Get current time in merchant's timezone
97
+ # @return [Time] Current time in merchant timezone
98
+ def merchant_time_now
99
+ require "time"
100
+ begin
101
+ require "tzinfo"
102
+ TZInfo::Timezone.get(merchant_timezone).now
103
+ rescue LoadError
104
+ old_tz = ENV.fetch("TZ", nil)
105
+ ENV["TZ"] = merchant_timezone
106
+ time = Time.now
107
+ ENV["TZ"] = old_tz
108
+ time
109
+ end
110
+ end
111
+
112
+ # Get today's date in merchant's timezone
113
+ # @return [Date] Today's date in merchant timezone
114
+ def merchant_date_today
115
+ merchant_time_now.to_date
116
+ end
117
+
118
+ private
119
+
120
+ def load_from_merchants_file
121
+ merchants = load_merchants_file
122
+ return if merchants.empty?
123
+
124
+ apply_merchant_config(merchants.first)
125
+ end
126
+
127
+ # Parse .env.json, supporting both array and object formats
128
+ def load_merchants_file
129
+ return [] unless File.exist?(MERCHANTS_FILE)
130
+
131
+ data = JSON.parse(File.read(MERCHANTS_FILE))
132
+ return data if data.is_a?(Array)
133
+
134
+ data.fetch("merchants", [])
135
+ rescue JSON::ParserError => e
136
+ warn "Failed to parse #{MERCHANTS_FILE}: #{e.message}"
137
+ []
138
+ end
139
+
140
+ # Read the top-level DATABASE_URL from .env.json
141
+ def self.database_url_from_file
142
+ return nil unless File.exist?(MERCHANTS_FILE)
143
+
144
+ data = JSON.parse(File.read(MERCHANTS_FILE))
145
+ return nil if data.is_a?(Array)
146
+
147
+ data["DATABASE_URL"]
148
+ rescue JSON::ParserError
149
+ nil
150
+ end
151
+
152
+ def apply_merchant_config(merchant)
153
+ @api_key = merchant["EPOS_NOW_API_KEY"] unless merchant["EPOS_NOW_API_KEY"].to_s.empty?
154
+ @api_secret = merchant["EPOS_NOW_API_SECRET"] unless merchant["EPOS_NOW_API_SECRET"].to_s.empty?
155
+ @device_name = merchant["EPOS_NOW_DEVICE_NAME"] unless merchant["EPOS_NOW_DEVICE_NAME"].to_s.empty?
156
+ end
157
+
158
+ def normalize_url(url)
159
+ url = url.strip
160
+ url.end_with?("/") ? url : "#{url}/"
161
+ end
162
+
163
+ def parse_log_level(level)
164
+ {
165
+ "DEBUG" => Logger::DEBUG,
166
+ "INFO" => Logger::INFO,
167
+ "WARN" => Logger::WARN,
168
+ "ERROR" => Logger::ERROR,
169
+ "FATAL" => Logger::FATAL
170
+ }.fetch(level.to_s.upcase, Logger::INFO)
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,9 @@
1
+ {
2
+ "categories": [
3
+ { "name": "Draft Beer", "sort_order": 1, "description": "Beers on tap" },
4
+ { "name": "Bottled Beer", "sort_order": 2, "description": "Bottled and canned beers" },
5
+ { "name": "Cocktails", "sort_order": 3, "description": "Mixed drinks" },
6
+ { "name": "Wine", "sort_order": 4, "description": "Wine by the glass" },
7
+ { "name": "Bar Snacks", "sort_order": 5, "description": "Bar food and snacks" }
8
+ ]
9
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "items": [
3
+ { "name": "IPA Pint", "price": 7.50, "category": "Draft Beer", "sku": "BAR-DRF-001" },
4
+ { "name": "Lager Pint", "price": 6.50, "category": "Draft Beer", "sku": "BAR-DRF-002" },
5
+ { "name": "Stout Pint", "price": 7.00, "category": "Draft Beer", "sku": "BAR-DRF-003" },
6
+ { "name": "Wheat Beer", "price": 7.50, "category": "Draft Beer", "sku": "BAR-DRF-004" },
7
+ { "name": "Pale Ale", "price": 7.00, "category": "Draft Beer", "sku": "BAR-DRF-005" },
8
+ { "name": "Corona", "price": 6.00, "category": "Bottled Beer", "sku": "BAR-BTL-001" },
9
+ { "name": "Heineken", "price": 5.50, "category": "Bottled Beer", "sku": "BAR-BTL-002" },
10
+ { "name": "Budweiser", "price": 5.00, "category": "Bottled Beer", "sku": "BAR-BTL-003" },
11
+ { "name": "Seltzer", "price": 5.50, "category": "Bottled Beer", "sku": "BAR-BTL-004" },
12
+ { "name": "Margarita", "price": 12.00, "category": "Cocktails", "sku": "BAR-COK-001" },
13
+ { "name": "Old Fashioned", "price": 14.00, "category": "Cocktails", "sku": "BAR-COK-002" },
14
+ { "name": "Mojito", "price": 12.00, "category": "Cocktails", "sku": "BAR-COK-003" },
15
+ { "name": "Espresso Martini", "price": 13.00, "category": "Cocktails", "sku": "BAR-COK-004" },
16
+ { "name": "Long Island", "price": 14.00, "category": "Cocktails", "sku": "BAR-COK-005" },
17
+ { "name": "House Red Wine", "price": 9.00, "category": "Wine", "sku": "BAR-WIN-001" },
18
+ { "name": "House White Wine", "price": 9.00, "category": "Wine", "sku": "BAR-WIN-002" },
19
+ { "name": "Prosecco", "price": 10.00, "category": "Wine", "sku": "BAR-WIN-003" },
20
+ { "name": "Chicken Wings", "price": 10.99, "category": "Bar Snacks", "sku": "BAR-SNK-001" },
21
+ { "name": "Loaded Fries", "price": 8.99, "category": "Bar Snacks", "sku": "BAR-SNK-002" },
22
+ { "name": "Nachos", "price": 11.99, "category": "Bar Snacks", "sku": "BAR-SNK-003" },
23
+ { "name": "Slider Trio", "price": 12.99, "category": "Bar Snacks", "sku": "BAR-SNK-004" },
24
+ { "name": "Mixed Nuts", "price": 5.99, "category": "Bar Snacks", "sku": "BAR-SNK-005" }
25
+ ]
26
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tenders": [
3
+ { "name": "Cash", "description": "Cash payment", "weight": 20 },
4
+ { "name": "Credit Card", "description": "Credit card payment", "weight": 55 },
5
+ { "name": "Debit Card", "description": "Debit card payment", "weight": 20 },
6
+ { "name": "Gift Card", "description": "Gift card payment", "weight": 5 }
7
+ ]
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "categories": [
3
+ { "name": "Hot Drinks", "sort_order": 1, "description": "Coffee, tea, and hot beverages" },
4
+ { "name": "Cold Drinks", "sort_order": 2, "description": "Iced and cold beverages" },
5
+ { "name": "Pastries", "sort_order": 3, "description": "Fresh baked pastries" },
6
+ { "name": "Sandwiches", "sort_order": 4, "description": "Fresh sandwiches and wraps" },
7
+ { "name": "Cakes", "sort_order": 5, "description": "Cakes and slices" }
8
+ ]
9
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "items": [
3
+ { "name": "Espresso", "price": 3.50, "category": "Hot Drinks", "sku": "CAFE-HOT-001" },
4
+ { "name": "Cappuccino", "price": 4.50, "category": "Hot Drinks", "sku": "CAFE-HOT-002" },
5
+ { "name": "Latte", "price": 4.75, "category": "Hot Drinks", "sku": "CAFE-HOT-003" },
6
+ { "name": "Hot Chocolate", "price": 4.25, "category": "Hot Drinks", "sku": "CAFE-HOT-004" },
7
+ { "name": "English Breakfast Tea", "price": 3.25, "category": "Hot Drinks", "sku": "CAFE-HOT-005" },
8
+ { "name": "Iced Latte", "price": 5.25, "category": "Cold Drinks", "sku": "CAFE-CLD-001" },
9
+ { "name": "Iced Americano", "price": 4.75, "category": "Cold Drinks", "sku": "CAFE-CLD-002" },
10
+ { "name": "Smoothie", "price": 5.99, "category": "Cold Drinks", "sku": "CAFE-CLD-003" },
11
+ { "name": "Fresh Orange Juice", "price": 4.50, "category": "Cold Drinks", "sku": "CAFE-CLD-004" },
12
+ { "name": "Croissant", "price": 3.50, "category": "Pastries", "sku": "CAFE-PAS-001" },
13
+ { "name": "Pain au Chocolat", "price": 3.75, "category": "Pastries", "sku": "CAFE-PAS-002" },
14
+ { "name": "Blueberry Muffin", "price": 3.99, "category": "Pastries", "sku": "CAFE-PAS-003" },
15
+ { "name": "Cinnamon Roll", "price": 4.25, "category": "Pastries", "sku": "CAFE-PAS-004" },
16
+ { "name": "Scone", "price": 3.25, "category": "Pastries", "sku": "CAFE-PAS-005" },
17
+ { "name": "Club Sandwich", "price": 8.99, "category": "Sandwiches", "sku": "CAFE-SAN-001" },
18
+ { "name": "BLT", "price": 7.99, "category": "Sandwiches", "sku": "CAFE-SAN-002" },
19
+ { "name": "Avocado Toast", "price": 9.50, "category": "Sandwiches", "sku": "CAFE-SAN-003" },
20
+ { "name": "Panini", "price": 8.50, "category": "Sandwiches", "sku": "CAFE-SAN-004" },
21
+ { "name": "Chicken Wrap", "price": 8.99, "category": "Sandwiches", "sku": "CAFE-SAN-005" },
22
+ { "name": "Carrot Cake", "price": 5.50, "category": "Cakes", "sku": "CAFE-CAK-001" },
23
+ { "name": "Victoria Sponge", "price": 5.25, "category": "Cakes", "sku": "CAFE-CAK-002" },
24
+ { "name": "Brownie", "price": 4.50, "category": "Cakes", "sku": "CAFE-CAK-003" },
25
+ { "name": "Lemon Drizzle", "price": 4.99, "category": "Cakes", "sku": "CAFE-CAK-004" },
26
+ { "name": "Red Velvet Slice", "price": 5.75, "category": "Cakes", "sku": "CAFE-CAK-005" }
27
+ ]
28
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tenders": [
3
+ { "name": "Cash", "description": "Cash payment", "weight": 30 },
4
+ { "name": "Credit Card", "description": "Credit card payment", "weight": 50 },
5
+ { "name": "Debit Card", "description": "Debit card payment", "weight": 15 },
6
+ { "name": "Gift Card", "description": "Gift card payment", "weight": 5 }
7
+ ]
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "categories": [
3
+ { "name": "Appetizers", "sort_order": 1, "description": "Starters and small plates" },
4
+ { "name": "Entrees", "sort_order": 2, "description": "Main course dishes" },
5
+ { "name": "Sides", "sort_order": 3, "description": "Side dishes" },
6
+ { "name": "Drinks", "sort_order": 4, "description": "Beverages" },
7
+ { "name": "Desserts", "sort_order": 5, "description": "Sweets and treats" }
8
+ ]
9
+ }
@@ -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
+ }