workarea-orderbot 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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