revel_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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +305 -0
  4. data/bin/simulate +187 -0
  5. data/lib/revel_sandbox_simulator/configuration.rb +109 -0
  6. data/lib/revel_sandbox_simulator/data/bar_nightclub/categories.json +7 -0
  7. data/lib/revel_sandbox_simulator/data/bar_nightclub/items.json +27 -0
  8. data/lib/revel_sandbox_simulator/data/bar_nightclub/tenders.json +7 -0
  9. data/lib/revel_sandbox_simulator/data/cafe_bakery/categories.json +7 -0
  10. data/lib/revel_sandbox_simulator/data/cafe_bakery/items.json +27 -0
  11. data/lib/revel_sandbox_simulator/data/cafe_bakery/tenders.json +7 -0
  12. data/lib/revel_sandbox_simulator/data/restaurant/categories.json +7 -0
  13. data/lib/revel_sandbox_simulator/data/restaurant/items.json +27 -0
  14. data/lib/revel_sandbox_simulator/data/restaurant/tenders.json +7 -0
  15. data/lib/revel_sandbox_simulator/data/retail_general/categories.json +7 -0
  16. data/lib/revel_sandbox_simulator/data/retail_general/items.json +27 -0
  17. data/lib/revel_sandbox_simulator/data/retail_general/tenders.json +7 -0
  18. data/lib/revel_sandbox_simulator/database.rb +92 -0
  19. data/lib/revel_sandbox_simulator/db/factories/api_requests.rb +15 -0
  20. data/lib/revel_sandbox_simulator/db/factories/business_types.rb +34 -0
  21. data/lib/revel_sandbox_simulator/db/factories/categories.rb +10 -0
  22. data/lib/revel_sandbox_simulator/db/factories/items.rb +12 -0
  23. data/lib/revel_sandbox_simulator/db/factories/simulated_orders.rb +25 -0
  24. data/lib/revel_sandbox_simulator/db/factories/simulated_payments.rb +14 -0
  25. data/lib/revel_sandbox_simulator/db/migrate/20260313000001_enable_pgcrypto.rb +7 -0
  26. data/lib/revel_sandbox_simulator/db/migrate/20260313000002_create_business_types.rb +16 -0
  27. data/lib/revel_sandbox_simulator/db/migrate/20260313000003_create_categories.rb +16 -0
  28. data/lib/revel_sandbox_simulator/db/migrate/20260313000004_create_items.rb +19 -0
  29. data/lib/revel_sandbox_simulator/db/migrate/20260313000005_create_simulated_orders.rb +27 -0
  30. data/lib/revel_sandbox_simulator/db/migrate/20260313000006_create_simulated_payments.rb +22 -0
  31. data/lib/revel_sandbox_simulator/db/migrate/20260313000007_create_api_requests.rb +22 -0
  32. data/lib/revel_sandbox_simulator/db/migrate/20260313000008_create_daily_summaries.rb +21 -0
  33. data/lib/revel_sandbox_simulator/generators/data_loader.rb +80 -0
  34. data/lib/revel_sandbox_simulator/generators/entity_generator.rb +61 -0
  35. data/lib/revel_sandbox_simulator/generators/order_generator.rb +260 -0
  36. data/lib/revel_sandbox_simulator/models/api_request.rb +11 -0
  37. data/lib/revel_sandbox_simulator/models/business_type.rb +14 -0
  38. data/lib/revel_sandbox_simulator/models/category.rb +15 -0
  39. data/lib/revel_sandbox_simulator/models/daily_summary.rb +53 -0
  40. data/lib/revel_sandbox_simulator/models/item.rb +18 -0
  41. data/lib/revel_sandbox_simulator/models/record.rb +9 -0
  42. data/lib/revel_sandbox_simulator/models/simulated_order.rb +20 -0
  43. data/lib/revel_sandbox_simulator/models/simulated_payment.rb +16 -0
  44. data/lib/revel_sandbox_simulator/seeder.rb +115 -0
  45. data/lib/revel_sandbox_simulator/services/base_service.rb +143 -0
  46. data/lib/revel_sandbox_simulator/services/revel/customer_service.rb +53 -0
  47. data/lib/revel_sandbox_simulator/services/revel/establishment_service.rb +19 -0
  48. data/lib/revel_sandbox_simulator/services/revel/order_service.rb +77 -0
  49. data/lib/revel_sandbox_simulator/services/revel/payment_service.rb +52 -0
  50. data/lib/revel_sandbox_simulator/services/revel/product_service.rb +87 -0
  51. data/lib/revel_sandbox_simulator/services/revel/services_manager.rb +45 -0
  52. data/lib/revel_sandbox_simulator/version.rb +5 -0
  53. data/lib/revel_sandbox_simulator.rb +38 -0
  54. metadata +335 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ module Services
5
+ module Revel
6
+ class CustomerService < BaseService
7
+ CUSTOMER_PATH = "resources/Customer/"
8
+
9
+ def fetch_customers
10
+ fetch_all_pages(CUSTOMER_PATH)
11
+ end
12
+
13
+ def get_customer(id)
14
+ request(:get, "#{CUSTOMER_PATH}#{id}/", resource_type: "Customer", resource_id: id)
15
+ end
16
+
17
+ def create_customer(first_name:, last_name:, email: nil, phone: nil)
18
+ payload = {
19
+ first_name: first_name,
20
+ last_name: last_name,
21
+ email: email,
22
+ phone_number: phone
23
+ }
24
+
25
+ request(:post, CUSTOMER_PATH, payload: payload.compact, resource_type: "Customer")
26
+ end
27
+
28
+ def find_customer_by_email(email)
29
+ result = request(:get, CUSTOMER_PATH, params: { email: email, limit: 1 })
30
+ objects = result["objects"] || []
31
+ objects.first
32
+ end
33
+
34
+ def ensure_customers(count: 20)
35
+ existing = fetch_customers
36
+ needed = count - existing.size
37
+ return existing if needed <= 0
38
+
39
+ created = needed.times.map do |i|
40
+ create_customer(
41
+ first_name: Faker::Name.first_name,
42
+ last_name: Faker::Name.last_name,
43
+ email: "customer#{existing.size + i + 1}@example.com",
44
+ phone: Faker::PhoneNumber.phone_number
45
+ )
46
+ end
47
+
48
+ existing + created
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ module Services
5
+ module Revel
6
+ class EstablishmentService < BaseService
7
+ ESTABLISHMENT_PATH = "enterprise/Establishment/"
8
+
9
+ def fetch_establishments
10
+ fetch_all_pages(ESTABLISHMENT_PATH)
11
+ end
12
+
13
+ def get_establishment(id)
14
+ request(:get, "#{ESTABLISHMENT_PATH}#{id}/", resource_type: "Establishment", resource_id: id)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ module Services
5
+ module Revel
6
+ class OrderService < BaseService
7
+ ORDER_PATH = "resources/Order/"
8
+ ORDER_ITEM_PATH = "resources/OrderItem/"
9
+
10
+ DINING_OPTIONS = {
11
+ dine_in: 0,
12
+ takeout: 1,
13
+ delivery: 2
14
+ }.freeze
15
+
16
+ def fetch_orders(params: {})
17
+ fetch_all_pages(ORDER_PATH, params: params)
18
+ end
19
+
20
+ def get_order(id)
21
+ request(:get, "#{ORDER_PATH}#{id}/", resource_type: "Order", resource_id: id)
22
+ end
23
+
24
+ def create_order(items:, dining_option: :dine_in, discount_amount: 0, establishment_id: nil)
25
+ est_id = establishment_id || config.establishment_id
26
+ payload = {
27
+ establishment: "/enterprise/Establishment/#{est_id}/",
28
+ dining_option: DINING_OPTIONS.fetch(dining_option.to_sym, 0),
29
+ discount: format("%.2f", discount_amount / 100.0),
30
+ orderInfo_id: SecureRandom.uuid
31
+ }
32
+
33
+ result = request(:post, ORDER_PATH, payload: payload, resource_type: "Order")
34
+ order_id = extract_id(result)
35
+
36
+ items.each do |item|
37
+ create_order_item(order_id: order_id, product_id: item[:product_id],
38
+ quantity: item.fetch(:quantity, 1), unit_price: item[:unit_price])
39
+ end
40
+
41
+ result.merge("id" => order_id, "items_created" => items.size)
42
+ end
43
+
44
+ def create_order_item(order_id:, product_id:, quantity: 1, unit_price: nil)
45
+ payload = {
46
+ order: "/resources/Order/#{order_id}/",
47
+ product: "/resources/Product/#{product_id}/",
48
+ quantity: quantity
49
+ }
50
+ payload[:price] = format("%.2f", unit_price / 100.0) if unit_price
51
+
52
+ request(:post, ORDER_ITEM_PATH, payload: payload, resource_type: "OrderItem")
53
+ end
54
+
55
+ def fetch_orders_by_date(start_date:, end_date: nil)
56
+ end_date ||= start_date
57
+ params = {
58
+ created_date__gte: start_date.to_s,
59
+ created_date__lte: "#{end_date}T23:59:59"
60
+ }
61
+ fetch_orders(params: params)
62
+ end
63
+
64
+ private
65
+
66
+ def extract_id(result)
67
+ return result["id"] if result["id"]
68
+
69
+ uri = result["resource_uri"]
70
+ return nil unless uri
71
+
72
+ uri.split("/").reject(&:empty?).last
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ module Services
5
+ module Revel
6
+ class PaymentService < BaseService
7
+ PAYMENT_PATH = "resources/Payment/"
8
+
9
+ PAYMENT_TYPES = {
10
+ cash: 0,
11
+ credit_card: 1,
12
+ debit_card: 2,
13
+ gift_card: 3,
14
+ check: 4,
15
+ mobile_pay: 5
16
+ }.freeze
17
+
18
+ def fetch_payments(params: {})
19
+ fetch_all_pages(PAYMENT_PATH, params: params)
20
+ end
21
+
22
+ def get_payment(id)
23
+ request(:get, "#{PAYMENT_PATH}#{id}/", resource_type: "Payment", resource_id: id)
24
+ end
25
+
26
+ def create_payment(order_id:, amount:, payment_type: :credit_card, tip_amount: 0)
27
+ payload = {
28
+ order: "/resources/Order/#{order_id}/",
29
+ amount: format("%.2f", amount / 100.0),
30
+ tip: format("%.2f", tip_amount / 100.0),
31
+ type: PAYMENT_TYPES.fetch(payment_type.to_sym, 1)
32
+ }
33
+
34
+ request(:post, PAYMENT_PATH, payload: payload, resource_type: "Payment")
35
+ end
36
+
37
+ def create_cash_payment(order_id:, amount:, tip_amount: 0)
38
+ create_payment(order_id: order_id, amount: amount, payment_type: :cash, tip_amount: tip_amount)
39
+ end
40
+
41
+ def refund_payment(payment_id:, amount:, reason: nil)
42
+ payload = {
43
+ amount: format("%.2f", -(amount / 100.0).abs),
44
+ reason: reason || "Refund"
45
+ }
46
+
47
+ request(:post, PAYMENT_PATH, payload: payload, resource_type: "Payment", resource_id: payment_id)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ module Services
5
+ module Revel
6
+ class ProductService < BaseService
7
+ CATEGORY_PATH = "products/ProductCategory/"
8
+ PRODUCT_PATH = "resources/Product/"
9
+
10
+ def fetch_categories
11
+ fetch_all_pages(CATEGORY_PATH)
12
+ end
13
+
14
+ def get_category(id)
15
+ request(:get, "#{CATEGORY_PATH}#{id}/", resource_type: "ProductCategory", resource_id: id)
16
+ end
17
+
18
+ def create_category(name:, description: nil, sort_order: 0, parent_id: nil)
19
+ payload = {
20
+ name: name,
21
+ description: description,
22
+ sorting: sort_order
23
+ }
24
+ payload[:parent] = parent_id if parent_id
25
+
26
+ request(:post, CATEGORY_PATH, payload: payload, resource_type: "ProductCategory")
27
+ end
28
+
29
+ def update_category(id, attributes)
30
+ request(:patch, "#{CATEGORY_PATH}#{id}/", payload: attributes, resource_type: "ProductCategory", resource_id: id)
31
+ end
32
+
33
+ def find_category_by_name(name)
34
+ result = request(:get, CATEGORY_PATH, params: { name: name, limit: 1 })
35
+ objects = result["objects"] || []
36
+ objects.first
37
+ end
38
+
39
+ def find_or_create_category(name:, description: nil, sort_order: 0)
40
+ existing = find_category_by_name(name)
41
+ return existing if existing
42
+
43
+ create_category(name: name, description: description, sort_order: sort_order)
44
+ end
45
+
46
+ def fetch_products
47
+ fetch_all_pages(PRODUCT_PATH)
48
+ end
49
+
50
+ def get_product(id)
51
+ request(:get, "#{PRODUCT_PATH}#{id}/", resource_type: "Product", resource_id: id)
52
+ end
53
+
54
+ def create_product(name:, price:, category_id: nil, sku: nil, barcode: nil, description: nil, cost_price: 0)
55
+ payload = {
56
+ name: name,
57
+ original_price: format("%.2f", price / 100.0),
58
+ cost: format("%.2f", cost_price / 100.0),
59
+ sku: sku,
60
+ barcode: barcode,
61
+ description: description
62
+ }
63
+ payload[:categories] = ["/products/ProductCategory/#{category_id}/"] if category_id
64
+
65
+ request(:post, PRODUCT_PATH, payload: payload.compact, resource_type: "Product")
66
+ end
67
+
68
+ def update_product(id, attributes)
69
+ request(:patch, "#{PRODUCT_PATH}#{id}/", payload: attributes, resource_type: "Product", resource_id: id)
70
+ end
71
+
72
+ def find_product_by_name(name)
73
+ result = request(:get, PRODUCT_PATH, params: { name: name, limit: 1 })
74
+ objects = result["objects"] || []
75
+ objects.first
76
+ end
77
+
78
+ def find_or_create_product(name:, price:, category_id: nil, sku: nil)
79
+ existing = find_product_by_name(name)
80
+ return existing if existing
81
+
82
+ create_product(name: name, price: price, category_id: category_id, sku: sku)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ module Services
5
+ module Revel
6
+ class ServicesManager
7
+ attr_reader :config
8
+
9
+ def initialize(config: nil)
10
+ @config = config || RevelSandboxSimulator.configuration
11
+ @mutex = Mutex.new
12
+ @services = {}
13
+ end
14
+
15
+ def product
16
+ thread_safe_service(:product) { ProductService.new(config: config) }
17
+ end
18
+
19
+ def order
20
+ thread_safe_service(:order) { OrderService.new(config: config) }
21
+ end
22
+
23
+ def payment
24
+ thread_safe_service(:payment) { PaymentService.new(config: config) }
25
+ end
26
+
27
+ def customer
28
+ thread_safe_service(:customer) { CustomerService.new(config: config) }
29
+ end
30
+
31
+ def establishment
32
+ thread_safe_service(:establishment) { EstablishmentService.new(config: config) }
33
+ end
34
+
35
+ private
36
+
37
+ def thread_safe_service(key)
38
+ @mutex.synchronize do
39
+ @services[key] ||= yield
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RevelSandboxSimulator
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require "dotenv"
5
+
6
+ module RevelSandboxSimulator
7
+ class Error < StandardError; end
8
+ class ConfigurationError < Error; end
9
+ class ApiError < Error; end
10
+
11
+ class << self
12
+ def configuration
13
+ @configuration ||= Configuration.new
14
+ end
15
+
16
+ def configure
17
+ yield(configuration)
18
+ end
19
+
20
+ def reset_configuration!
21
+ @configuration = Configuration.new
22
+ end
23
+
24
+ def root
25
+ File.expand_path("..", __dir__)
26
+ end
27
+
28
+ def logger
29
+ configuration.logger
30
+ end
31
+ end
32
+ end
33
+
34
+ loader = Zeitwerk::Loader.for_gem
35
+ loader.inflector.inflect("revel_sandbox_simulator" => "RevelSandboxSimulator")
36
+ loader.setup
37
+
38
+ Dotenv.load if File.exist?(".env")