workarea-orderbot 1.0.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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/ci.yml +58 -0
  6. data/.gitignore +23 -0
  7. data/.rubocop.yml +3 -0
  8. data/CHANGELOG.md +58 -0
  9. data/Gemfile +17 -0
  10. data/LICENSE +52 -0
  11. data/README.md +100 -0
  12. data/Rakefile +59 -0
  13. data/app/assets/images/workarea/admin/orderbot/.keep +0 -0
  14. data/app/assets/images/workarea/storefront/orderbot/.keep +0 -0
  15. data/app/assets/javascripts/workarea/admin/orderbot/.keep +0 -0
  16. data/app/assets/javascripts/workarea/storefront/orderbot/.keep +0 -0
  17. data/app/assets/stylesheets/workarea/admin/orderbot/.keep +0 -0
  18. data/app/assets/stylesheets/workarea/storefront/orderbot/.keep +0 -0
  19. data/app/controllers/.keep +0 -0
  20. data/app/helpers/.keep +0 -0
  21. data/app/mailers/.keep +0 -0
  22. data/app/models/workarea/order.decorator +19 -0
  23. data/app/models/workarea/orderbot/import_log.rb +19 -0
  24. data/app/models/workarea/orderbot/pricing_import_data.rb +13 -0
  25. data/app/models/workarea/orderbot/product_import_data.rb +32 -0
  26. data/app/models/workarea/user.decorator +7 -0
  27. data/app/services/workarea/orderbot/child_product.rb +108 -0
  28. data/app/services/workarea/orderbot/filters.rb +33 -0
  29. data/app/services/workarea/orderbot/order.rb +146 -0
  30. data/app/services/workarea/orderbot/order/item.rb +69 -0
  31. data/app/services/workarea/orderbot/order/tender/credit_card.rb +43 -0
  32. data/app/services/workarea/orderbot/order/tender/general.rb +32 -0
  33. data/app/services/workarea/orderbot/order/tender/gift_card.rb +32 -0
  34. data/app/services/workarea/orderbot/order/tender/store_credit.rb +32 -0
  35. data/app/services/workarea/orderbot/parent_product.rb +87 -0
  36. data/app/views/.keep +0 -0
  37. data/app/workers/workarea/orderbot/fulfillment/import_fulfillments.rb +37 -0
  38. data/app/workers/workarea/orderbot/fulfillment_importer.rb +50 -0
  39. data/app/workers/workarea/orderbot/inventory/import_inventory.rb +50 -0
  40. data/app/workers/workarea/orderbot/inventory_importer.rb +57 -0
  41. data/app/workers/workarea/orderbot/pricing/import_pricing.rb +42 -0
  42. data/app/workers/workarea/orderbot/pricing_importer.rb +70 -0
  43. data/app/workers/workarea/orderbot/product/import_child_products.rb +24 -0
  44. data/app/workers/workarea/orderbot/product/import_parent_products.rb +32 -0
  45. data/app/workers/workarea/orderbot/product_importer.rb +72 -0
  46. data/app/workers/workarea/orderbot/save_order.rb +38 -0
  47. data/bin/rails +25 -0
  48. data/config/initializers/configurations.rb +60 -0
  49. data/config/initializers/scheduled_jobs.rb +28 -0
  50. data/config/initializers/workarea.rb +16 -0
  51. data/config/routes.rb +2 -0
  52. data/lib/workarea/orderbot.rb +43 -0
  53. data/lib/workarea/orderbot/authentication.rb +41 -0
  54. data/lib/workarea/orderbot/bogus_gateway.rb +629 -0
  55. data/lib/workarea/orderbot/engine.rb +10 -0
  56. data/lib/workarea/orderbot/gateway.rb +102 -0
  57. data/lib/workarea/orderbot/response.rb +29 -0
  58. data/lib/workarea/orderbot/version.rb +5 -0
  59. data/test/dummy/Rakefile +6 -0
  60. data/test/dummy/app/assets/config/manifest.js +3 -0
  61. data/test/dummy/app/assets/images/.keep +0 -0
  62. data/test/dummy/app/assets/javascripts/application.js +14 -0
  63. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  64. data/test/dummy/app/controllers/application_controller.rb +2 -0
  65. data/test/dummy/app/controllers/concerns/.keep +0 -0
  66. data/test/dummy/app/helpers/application_helper.rb +2 -0
  67. data/test/dummy/app/jobs/application_job.rb +2 -0
  68. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  69. data/test/dummy/app/models/concerns/.keep +0 -0
  70. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  71. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  72. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  73. data/test/dummy/bin/bundle +3 -0
  74. data/test/dummy/bin/rails +4 -0
  75. data/test/dummy/bin/rake +4 -0
  76. data/test/dummy/bin/setup +25 -0
  77. data/test/dummy/bin/update +25 -0
  78. data/test/dummy/config.ru +5 -0
  79. data/test/dummy/config/application.rb +34 -0
  80. data/test/dummy/config/boot.rb +5 -0
  81. data/test/dummy/config/environment.rb +5 -0
  82. data/test/dummy/config/environments/development.rb +52 -0
  83. data/test/dummy/config/environments/production.rb +83 -0
  84. data/test/dummy/config/environments/test.rb +45 -0
  85. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  86. data/test/dummy/config/initializers/assets.rb +12 -0
  87. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  88. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  89. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  90. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/test/dummy/config/initializers/inflections.rb +16 -0
  92. data/test/dummy/config/initializers/mime_types.rb +4 -0
  93. data/test/dummy/config/initializers/workarea.rb +5 -0
  94. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  95. data/test/dummy/config/locales/en.yml +33 -0
  96. data/test/dummy/config/puma.rb +34 -0
  97. data/test/dummy/config/routes.rb +5 -0
  98. data/test/dummy/config/spring.rb +6 -0
  99. data/test/dummy/db/seeds.rb +2 -0
  100. data/test/dummy/lib/assets/.keep +0 -0
  101. data/test/dummy/log/.keep +0 -0
  102. data/test/services/workarea/orderbot/order_test.rb +151 -0
  103. data/test/services/workarea/orderbot/product/child_product_test.rb +255 -0
  104. data/test/services/workarea/orderbot/product/parent_product_test.rb +106 -0
  105. data/test/teaspoon_env.rb +6 -0
  106. data/test/test_helper.rb +10 -0
  107. data/test/workers/workarea/orderbot/fulfillment/import_fulfillments_test.rb +41 -0
  108. data/test/workers/workarea/orderbot/fulfillment_importer_test.rb +29 -0
  109. data/test/workers/workarea/orderbot/inventory/import_inventory_test.rb +49 -0
  110. data/test/workers/workarea/orderbot/inventory_importer_test.rb +21 -0
  111. data/test/workers/workarea/orderbot/pricing/import_pricing_test.rb +61 -0
  112. data/test/workers/workarea/orderbot/pricing_importer_test.rb +29 -0
  113. data/test/workers/workarea/orderbot/product_importer_test.rb +23 -0
  114. data/test/workers/workarea/orderbot/save_order_test.rb +41 -0
  115. data/workarea-orderbot.gemspec +20 -0
  116. metadata +172 -0
@@ -0,0 +1,43 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class Order
4
+ module Tender
5
+ class CreditCard
6
+ attr_reader :tender, :options
7
+
8
+ def initialize(tender, options = {})
9
+ @tender = tender
10
+ @option = options
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ payment_reference_id: tender.id.to_s,
16
+ payment_type: "credit_card",
17
+ payment_method_type: tender.issuer.optionize,
18
+ amount_paid: tender.amount.to_f,
19
+ payment_date: transaction.created_at.iso8601,
20
+ auth_code: transaction.response.authorization,
21
+ credit_card: {
22
+ transaction_id: transaction_id,
23
+ authorization_code: transaction.response.authorization,
24
+ last_four_digits: tender.display_number.last(4)
25
+ }
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def transaction
32
+ tender.transactions.successful.sort_by(&:created_at).last
33
+ end
34
+
35
+ def transaction_id
36
+ gateway_class = Workarea.config.gateways.credit_card.class.to_s
37
+ Workarea.config.orderbot.transaction_id[gateway_class].call(transaction) rescue nil
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class Order
4
+ module Tender
5
+ class General
6
+ attr_reader :tender, :options
7
+
8
+ def initialize(tender, options = {})
9
+ @tender = tender
10
+ @option = options
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ payment_reference_id: tender.id.to_s,
16
+ payment_type: "unknown",
17
+ payment_method_type: "paid_from_web",
18
+ amount_paid: tender.amount.to_f,
19
+ payment_date: transaction.created_at.iso8601
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def transaction
26
+ tender.transactions.successful.sort_by(&:created_at).last
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class Order
4
+ module Tender
5
+ class GiftCard
6
+ attr_reader :tender, :options
7
+
8
+ def initialize(tender, options = {})
9
+ @tender = tender
10
+ @option = options
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ payment_reference_id: tender.id.to_s,
16
+ payment_type: "cheque",
17
+ payment_method_type: "gift_card",
18
+ amount_paid: tender.amount.to_f,
19
+ payment_date: transaction.created_at.iso8601
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def transaction
26
+ tender.transactions.successful.sort_by(&:created_at).last
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class Order
4
+ module Tender
5
+ class StoreCredit
6
+ attr_reader :tender, :options
7
+
8
+ def initialize(tender, options = {})
9
+ @tender = tender
10
+ @option = options
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ payment_reference_id: tender.id.to_s,
16
+ payment_type: "cheque",
17
+ payment_method_type: "paid_from_web",
18
+ amount_paid: tender.amount.to_f,
19
+ payment_date: transaction.created_at.iso8601
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def transaction
26
+ tender.transactions.successful.sort_by(&:created_at).last
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,87 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class ParentProduct
4
+ include Filters
5
+
6
+ attr_reader :parent_product
7
+
8
+ def initialize(parent_product)
9
+ @parent_product = parent_product
10
+ end
11
+
12
+ def process
13
+ product.name = product_details[:name]
14
+ product.description = product_details[:description]
15
+ product.active = product_details[:active]
16
+
17
+ product.filters = build_product_filters
18
+ product.template = product_template
19
+ set_details
20
+ product.save!
21
+ end
22
+
23
+ private
24
+
25
+ def product
26
+ @product ||= Workarea::Catalog::Product.find_or_initialize_by(id: id)
27
+ end
28
+
29
+ def id
30
+ @id ||= product_details[:product_id]
31
+ end
32
+
33
+ def product_details
34
+ parent_product.product_data.deep_symbolize_keys
35
+ end
36
+
37
+ def build_product_filters
38
+ existing_filters = product.filters || {}
39
+ new_filters = {
40
+ orderbot_category: product_details[:category],
41
+ orderbot_group: product_details[:group]
42
+ }
43
+
44
+ filters = existing_filters.merge(new_filters)
45
+ filters = add_filter_values(filters, first_variable) if first_variable.present?
46
+ filters = add_filter_values(filters, second_variable) if second_variable.present?
47
+
48
+ filters
49
+ end
50
+
51
+ def set_details
52
+ new_details = {
53
+ upc: product_details[:upc].presence
54
+ }.merge(get_product_details)
55
+ product.update_details(new_details)
56
+ end
57
+
58
+ # getting custom attributes for a product requires a seperate API call
59
+ def get_product_details
60
+ attrs = {
61
+ response_model: "CustomField",
62
+ product_ids: product_details[:product_id]
63
+ }
64
+
65
+ response = gateway.get_products(attrs)
66
+
67
+ return {} unless response.success? && response.body.present?
68
+
69
+ response.body.first["custom_fields"].inject({}) do |memo, field|
70
+ memo[field["name"]] = field["value"]
71
+ memo
72
+ end
73
+ end
74
+
75
+ def product_template
76
+ template = product_details[:workarea_info][:template]
77
+ return "generic" unless template.present?
78
+ return "generic" unless Workarea.config.product_templates.include?(template.to_sym)
79
+ template
80
+ end
81
+
82
+ def gateway
83
+ Workarea::Orderbot.gateway
84
+ end
85
+ end
86
+ end
87
+ end
data/app/views/.keep ADDED
File without changes
@@ -0,0 +1,37 @@
1
+ module Workarea
2
+ module Orderbot
3
+ module Fulfillment
4
+ class ImportFulfillments
5
+ include Sidekiq::Worker
6
+
7
+ def perform(attrs = {})
8
+ fulfillment_data = attrs.deep_symbolize_keys
9
+ order_id = fulfillment_data[:reference_id]
10
+
11
+ order = Workarea::Order.find(order_id)
12
+ fulfillment = Workarea::Fulfillment.find(order_id)
13
+
14
+ fulfillment_data[:packages].each do |package|
15
+ ship_package(package, order, fulfillment)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def ship_package(package, order, fulfillment)
22
+ tracking_number = package[:tracking_number]
23
+
24
+ items = package[:items].map do |item|
25
+ order_item_id = order.items.detect { |oi| oi.sku = item[:sku] }.id.to_s
26
+ {
27
+ id: order_item_id,
28
+ quantity: item[:quantity],
29
+ orderbot_package_id: item[:package_id]
30
+ }
31
+ end
32
+ fulfillment.ship_items(tracking_number, items)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class FulfillmentImporter
4
+ include Sidekiq::Worker
5
+ sidekiq_options retry: 5
6
+
7
+ def perform(options = {})
8
+ return unless Orderbot.api_configured?
9
+
10
+ Orderbot::ImportLog.log('fulfillment') do |last_imported_at|
11
+ from_updated_on = options[:from_updated_on] || last_imported_at
12
+
13
+ fulfillment_filters = {
14
+ last_updated_date_min: from_updated_on.iso8601
15
+ }
16
+
17
+ fulfillment_response = gateway.get_fulfillments(fulfillment_filters)
18
+ raise 'get fulfillment error' unless fulfillment_response.success?
19
+
20
+ import_fulfillment_records(fulfillment_response.body)
21
+
22
+ if fulfillment_response.total_pages.to_i > 1
23
+ count = 2
24
+
25
+ while count <= fulfillment_response.total_pages.to_i
26
+ page_filters = fulfillment_filters.merge(page: count)
27
+ response = gateway.get_products(page_filters)
28
+
29
+ import_fulfillment_records(response.body)
30
+
31
+ count = count + 1
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def gateway
40
+ Workarea::Orderbot.gateway
41
+ end
42
+
43
+ def import_fulfillment_records(fulfillment_records)
44
+ fulfillment_records.each do |fulfillment|
45
+ Orderbot::Fulfillment::ImportFulfillments.new.perform(fulfillment)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ module Workarea
2
+ module Orderbot
3
+ module Inventory
4
+ class ImportInventory
5
+ include Sidekiq::Worker
6
+
7
+ def perform(attrs = {})
8
+ inventory = attrs.deep_symbolize_keys
9
+
10
+ return unless inventory[:sku].present?
11
+
12
+ sku = Workarea::Inventory::Sku.find_or_initialize_by(id: inventory[:sku])
13
+
14
+ sku.available = inventory[:quantity_on_hand] || 0
15
+ sku.policy = get_policy(inventory[:sku])
16
+
17
+ sku.save!
18
+ end
19
+
20
+ private
21
+
22
+ def get_policy(sku)
23
+ return "allow_backorder" if is_backorderable?(sku)
24
+ Workarea.config.default_inventory_import_policy || "standard"
25
+ end
26
+
27
+ def is_backorderable?(sku)
28
+ gateway = Workarea::Orderbot.gateway
29
+
30
+ attrs = {
31
+ response_model: "CustomField",
32
+ sku: sku
33
+ }
34
+
35
+ response = gateway.get_products(attrs)
36
+
37
+ return unless response.success? && response.body.present?
38
+
39
+ fields = response.body.first["custom_fields"]
40
+
41
+ backorder_field = fields.detect { |f| f["name"] == "backorderable" }
42
+
43
+ return unless backorder_field.present?
44
+
45
+ backorder_field["value"]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,57 @@
1
+ module Workarea
2
+ module Orderbot
3
+ class InventoryImporter
4
+ include Sidekiq::Worker
5
+ sidekiq_options retry: 5
6
+
7
+ def perform(options = {})
8
+ return unless Orderbot.api_configured?
9
+
10
+ Orderbot::ImportLog.log('inventory') do |last_imported_at|
11
+ from_updated_on = options[:from_updated_on] || last_imported_at
12
+
13
+ inventory_filters = {
14
+ from_updated_on: from_updated_on.iso8601,
15
+ distribution_center_id: Workarea.config.inventory_distribution_center_id
16
+ }
17
+
18
+ inventory_response = gateway.get_inventory(inventory_filters)
19
+ raise 'get inventory error' unless inventory_response.success?
20
+
21
+ inventory_records = inventory_response.body
22
+
23
+ import_inventory_records(inventory_records)
24
+
25
+ if inventory_response.total_pages.to_i > 1
26
+ count = 2
27
+
28
+ while count <= inventory_response.total_pages.to_i
29
+ page_filters = inventory_filters.merge(page: count)
30
+ response = gateway.get_products(page_filters)
31
+
32
+ import_inventory_records(response.body)
33
+
34
+ count = count + 1
35
+ end
36
+ end
37
+
38
+ inventory_records.each do |inventory|
39
+ Orderbot::Inventory::ImportInventory.new.perform(inventory)
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def gateway
47
+ Workarea::Orderbot.gateway
48
+ end
49
+
50
+ def import_inventory_records(inventory_records)
51
+ inventory_records.each do |inventory|
52
+ Orderbot::Inventory::ImportInventory.new.perform(inventory)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ module Workarea
2
+ module Orderbot
3
+ module Pricing
4
+ class ImportPricing
5
+ include Sidekiq::Worker
6
+
7
+ def perform
8
+ Workarea::Orderbot::PricingImportData.each do |sku_price|
9
+ pricing_data = sku_price.pricing_data.deep_symbolize_keys
10
+
11
+ next unless pricing_data[:sku].present?
12
+ sku = Workarea::Pricing::Sku.find_or_initialize_by(id: pricing_data[:sku])
13
+
14
+ original_price = pricing_data[:original_price]
15
+ price = pricing_data[:price]
16
+
17
+ regular = regular_price(price, original_price)
18
+ sale = sale_price(price, original_price)
19
+
20
+ sku.on_sale = sale.present?
21
+ sku.prices = [{ regular: regular, sale: sale, on_sale: sale.present? }]
22
+ sku.save!
23
+
24
+ sku_price.delete
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def regular_price(price, original_price)
31
+ return original_price if original_price.present? && original_price > price
32
+ price
33
+ end
34
+
35
+ def sale_price(price, original_price)
36
+ return nil if original_price.blank? || original_price == price
37
+ price
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end