workarea-core 3.5.15 → 3.5.20

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/workarea/current_tracking.rb +4 -2
  3. data/app/controllers/workarea/impersonation.rb +4 -2
  4. data/app/mailers/workarea/application_mailer.rb +4 -1
  5. data/app/middleware/workarea/application_middleware.rb +5 -2
  6. data/app/models/workarea/checkout.rb +7 -7
  7. data/app/models/workarea/data_file/csv.rb +9 -1
  8. data/app/models/workarea/inquiry.rb +2 -1
  9. data/app/models/workarea/inventory/sku.rb +2 -2
  10. data/app/models/workarea/metrics/user.rb +24 -8
  11. data/app/models/workarea/order.rb +10 -0
  12. data/app/models/workarea/order/item.rb +4 -4
  13. data/app/models/workarea/payment.rb +1 -6
  14. data/app/models/workarea/search/admin/inventory_sku.rb +5 -1
  15. data/app/models/workarea/shipping/service.rb +12 -5
  16. data/app/models/workarea/tax/rate.rb +3 -1
  17. data/app/queries/workarea/search/admin_search.rb +4 -0
  18. data/app/queries/workarea/search/admin_sorting.rb +1 -1
  19. data/config/initializers/00_configuration.rb +23 -8
  20. data/config/initializers/22_session_store.rb +1 -1
  21. data/config/locales/en.yml +3 -0
  22. data/lib/generators/workarea/install/install_generator.rb +13 -0
  23. data/lib/generators/workarea/install/templates/initializer.rb.erb +1 -13
  24. data/lib/tasks/cache.rake +3 -33
  25. data/lib/tasks/help.rake +4 -43
  26. data/lib/tasks/insights.rake +3 -35
  27. data/lib/tasks/migrate.rake +3 -96
  28. data/lib/tasks/search.rake +6 -68
  29. data/lib/tasks/services.rake +4 -54
  30. data/lib/workarea/configuration.rb +11 -2
  31. data/lib/workarea/configuration/administrable_options.rb +1 -5
  32. data/lib/workarea/core/engine.rb +4 -0
  33. data/lib/workarea/tasks/cache.rb +43 -0
  34. data/lib/workarea/tasks/help.rb +55 -0
  35. data/lib/workarea/tasks/insights.rb +47 -0
  36. data/lib/workarea/tasks/migrate.rb +106 -0
  37. data/lib/workarea/tasks/search.rb +105 -0
  38. data/lib/workarea/tasks/services.rb +71 -0
  39. data/lib/workarea/version.rb +1 -1
  40. data/lib/workarea/visit.rb +8 -1
  41. data/test/generators/workarea/install_generator_test.rb +6 -2
  42. data/test/integration/workarea/authentication_test.rb +2 -1
  43. data/test/mailers/workarea/application_mailer_test.rb +10 -0
  44. data/test/models/workarea/checkout_test.rb +57 -0
  45. data/test/models/workarea/data_file/import_test.rb +40 -0
  46. data/test/models/workarea/order/item_test.rb +9 -0
  47. data/test/models/workarea/shipping/service_test.rb +26 -0
  48. data/test/queries/workarea/search/admin_search_test.rb +10 -0
  49. data/workarea-core.gemspec +1 -1
  50. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c18e8846e0d53604a413bfebe9180793059f63eef7478a6efb5e3c0762e95112
4
- data.tar.gz: 6bc004faf032a0172b6d6495ef3d666944cef5726f91a9d41fca8fc69ce03af3
3
+ metadata.gz: c6261fa607f871ebfb3c05fb63ba16c8fdd08cd08d3dd0e34971ad18b1868f21
4
+ data.tar.gz: de5b0e46393f51fd7b1259ed6acd2aac028a4f7d8f311e19f26ec0bf331bffb3
5
5
  SHA512:
6
- metadata.gz: b367c165ee4ead94e2969e1ced60328c3fe0696bcac53df7a0a67f0eea1faad9e6b82c1eb6a4d1de2530460a7424326357ab6173c70fb7f0362e5c6435bb7f20
7
- data.tar.gz: c7a378a34ebc05ee82b12bf92c85b89e22ca1035ed01a429986d4f82ad7cc664cc54ba411eef0419c9773848296dab6f5beae749e7206e33c5c88c9e032279ad
6
+ metadata.gz: 9c8b791f8733b306e0411a309550e32055716ffd6abb952b9ce9e171bd7b3ade86a7cc6868a2d18a586c1ab847c18552b4f024dd9b2a4280a7eb2cd446fbd825
7
+ data.tar.gz: 033113c7e285a55c87f1fbf9b4a108d50767b9fa83041e7a9bb6bb8391164fd5a1986aa59ee1bd7c14ad0f55398d72a094cbbad4ab2c3e93dc37293be28385be
@@ -1,7 +1,6 @@
1
1
  module Workarea
2
2
  module CurrentTracking
3
3
  extend ActiveSupport::Concern
4
- include HttpCaching
5
4
 
6
5
  included do
7
6
  before_action :ensure_current_metrics
@@ -27,7 +26,10 @@ module Workarea
27
26
  if email.blank?
28
27
  cookies.delete(:email)
29
28
  elsif email != cookies.signed[:email]
30
- Metrics::User.find_or_initialize_by(id: email).merge!(current_visit&.metrics)
29
+ unless impersonating?
30
+ Metrics::User.find_or_initialize_by(id: email).merge!(current_visit&.metrics)
31
+ end
32
+
31
33
  cookies.permanent.signed[:email] = email
32
34
  end
33
35
 
@@ -15,10 +15,11 @@ module Workarea
15
15
  session[:user_id] = user.id.to_s
16
16
 
17
17
  user.mark_impersonated_by!(current_user)
18
- @current_user = user
18
+ update_tracking!(email: user.email)
19
19
  end
20
20
 
21
21
  def stop_impersonation
22
+ update_tracking!(email: current_admin.email)
22
23
  session[:user_id] = current_admin.id.to_s
23
24
  session.delete(:admin_id)
24
25
  end
@@ -39,7 +40,8 @@ module Workarea
39
40
  end
40
41
 
41
42
  def current_impersonation
42
- @current_impersonation ||= User.find(session[:user_id])
43
+ return @current_impersonation if defined?(@current_impersonation)
44
+ @current_impersonation = User.find(session[:user_id]) rescue nil
43
45
  end
44
46
 
45
47
  def admin_browse_as_guest
@@ -8,7 +8,10 @@ module Workarea
8
8
  default from: -> (*) { Workarea.config.email_from }
9
9
 
10
10
  def default_url_options(options = {})
11
- super.merge(host: Workarea.config.host)
11
+ # super isn't returning the configured options, so manually merge them in
12
+ super
13
+ .merge(Rails.application.config.action_mailer.default_url_options.to_h)
14
+ .merge(host: Workarea.config.host)
12
15
  end
13
16
  end
14
17
  end
@@ -1,12 +1,15 @@
1
1
  module Workarea
2
2
  class ApplicationMiddleware
3
+ ASSET_REGEX = /(jpe?g|png|ico|gif|bmp|webp|tif?f|css|js|svg|otf|ttf|woff|woff2)$/
4
+
3
5
  def initialize(app)
4
6
  @app = app
5
7
  end
6
8
 
7
9
  def call(env)
8
10
  request = Rack::Request.new(env)
9
- return @app.call(env) if request.path =~ /(jpe?g|png|ico|gif|css|js|svg)$/
11
+ env['workarea.asset_request'] = request.path =~ ASSET_REGEX
12
+ return @app.call(env) if env['workarea.asset_request']
10
13
 
11
14
  set_locale(env, request)
12
15
  setup_environment(env, request)
@@ -25,7 +28,7 @@ module Workarea
25
28
  env['workarea.visit'] = Visit.new(env)
26
29
  env['workarea.cache_varies'] = Cache::Varies.new(env['workarea.visit']).to_s
27
30
  env['rack-cache.cache_key'] = Cache::RackCacheKey
28
- env['rack-cache.force-pass'] = env['workarea.visit'].admin?
31
+ env['rack-cache.force-pass'] = env['workarea.visit'].admin? && !env['workarea.asset_request']
29
32
  end
30
33
 
31
34
  def set_segment_request_headers(env)
@@ -47,11 +47,7 @@ module Workarea
47
47
  def inventory
48
48
  @inventory ||= Inventory::Transaction.from_order(
49
49
  order.id,
50
- order.items.inject({}) do |memo, item|
51
- memo[item.sku] ||= 0
52
- memo[item.sku] += item.quantity
53
- memo
54
- end
50
+ order.sku_quantities
55
51
  )
56
52
  end
57
53
 
@@ -158,10 +154,14 @@ module Workarea
158
154
  # Used in auto completing an order for a logged in user.
159
155
  #
160
156
  # @param [Hash] parameters for updating
161
- # @return [self]
157
+ # @return [Boolean] whether the update was successful.
162
158
  #
163
159
  def update(params = {})
164
- steps.each { |s| s.new(self).update(params) }
160
+ return true if params.blank?
161
+
162
+ steps.reduce(true) do |result, step|
163
+ result &= step.new(self).update(params)
164
+ end
165
165
  end
166
166
 
167
167
  # Whether this checkout needs any further information
@@ -17,7 +17,15 @@ module Workarea
17
17
  assign_attributes(root, attrs)
18
18
  assign_embedded_attributes(root, attrs)
19
19
 
20
- if root.save || failed_new_record_ids.exclude?(id)
20
+ possibly_affected_models = root.embedded_children + [root]
21
+ was_successful = true
22
+
23
+ possibly_affected_models.each do |model|
24
+ meaningful_changes = model.changes.except('updated_at')
25
+ was_successful &= model.save if model.changed? && meaningful_changes.present?
26
+ end
27
+
28
+ if was_successful || failed_new_record_ids.exclude?(id)
21
29
  log(index, root)
22
30
  else
23
31
  operation.total += 1 # ensure line numbers remain consistent
@@ -15,7 +15,8 @@ module Workarea
15
15
  validate :subject_exists
16
16
 
17
17
  def full_subject
18
- Workarea.config.inquiry_subjects[subject]
18
+ I18n.t('workarea.inquiry.subjects')[subject.optionize.to_sym].presence ||
19
+ Workarea.config.inquiry_subjects[subject]
19
20
  end
20
21
 
21
22
  private
@@ -142,11 +142,11 @@ module Workarea
142
142
  end
143
143
 
144
144
  def policy_class
145
- "Workarea::Inventory::Policies::#{policy.classify}".constantize
145
+ "Workarea::Inventory::Policies::#{policy.camelize}".constantize
146
146
  rescue NameError
147
147
  raise(
148
148
  InvalidPolicy,
149
- "Workarea::Inventory::Policies::#{policy.classify} must be a policy class"
149
+ "Workarea::Inventory::Policies::#{policy.camelize} must be a policy class"
150
150
  )
151
151
  end
152
152
 
@@ -110,14 +110,28 @@ module Workarea
110
110
  end
111
111
 
112
112
  def merge!(other)
113
- %w(orders revenue discounts cancellations refund).each do |field|
114
- self.send("#{field}=", send(field) + other.send(field))
115
- end
116
-
117
- self.first_order_at = [first_order_at, other.first_order_at].compact.min
118
- self.last_order_at = [last_order_at, other.last_order_at].compact.max
119
- self.average_order_value = average_order_value
120
- save!
113
+ # To recalculate average_order_value
114
+ self.orders += other.orders
115
+ self.revenue += other.revenue
116
+
117
+ update = {
118
+ '$set' => {
119
+ average_order_value: average_order_value,
120
+ updated_at: Time.current.utc
121
+ },
122
+ '$inc' => {
123
+ orders: other.orders,
124
+ revenue: other.revenue,
125
+ discounts: other.discounts,
126
+ cancellations: other.cancellations,
127
+ refund: other.refund
128
+ }
129
+ }
130
+
131
+ update['$min'] = { first_order_at: other.first_order_at.utc } if other.first_order_at.present?
132
+ update['$max'] = { last_order_at: other.last_order_at.utc } if other.last_order_at.present?
133
+
134
+ self.class.collection.update_one({ _id: id }, update, upsert: true)
121
135
 
122
136
  self.class.save_affinity(
123
137
  id: id,
@@ -133,6 +147,8 @@ module Workarea
133
147
  category_ids: other.purchased.category_ids,
134
148
  search_ids: other.purchased.search_ids
135
149
  )
150
+
151
+ reload
136
152
  end
137
153
  end
138
154
  end
@@ -375,6 +375,16 @@ module Workarea
375
375
  )
376
376
  end
377
377
 
378
+ # A hash with the quantity of each SKU in the order
379
+ #
380
+ # @return [Hash]
381
+ #
382
+ def sku_quantities
383
+ items.each_with_object(Hash.new(0)) do |item, quantities|
384
+ quantities[item.sku] += item.quantity
385
+ end
386
+ end
387
+
378
388
  private
379
389
 
380
390
  def item_count_limit
@@ -33,17 +33,17 @@ module Workarea
33
33
  # To allow for custom policies defining their own methods here
34
34
  Workarea.config.fulfillment_policies.each do |class_name|
35
35
  define_method "#{class_name.demodulize.underscore}?" do
36
- fulfillment == class_name.demodulize.underscore
36
+ fulfilled_by?(class_name.demodulize.underscore)
37
37
  end
38
38
  end
39
39
 
40
40
  # These methods exist for findability
41
41
  def shipping?
42
- fulfillment == 'shipping'
42
+ fulfilled_by?('shipping')
43
43
  end
44
44
 
45
45
  def download?
46
- fulfillment == 'download'
46
+ fulfilled_by?('download')
47
47
  end
48
48
 
49
49
  # Whether this order has any items that need to be fulfilled by a particular
@@ -53,7 +53,7 @@ module Workarea
53
53
  # @return [Boolean]
54
54
  #
55
55
  def fulfilled_by?(*types)
56
- types.any? { |t| send("#{t}?") }
56
+ types.map(&:to_s).include?(fulfillment)
57
57
  end
58
58
 
59
59
  # Whether this item is a digital (not-shipped) type of item.
@@ -80,12 +80,7 @@ module Workarea
80
80
  build_credit_card unless credit_card
81
81
  credit_card.saved_card_id = nil
82
82
  credit_card.attributes = attrs.slice(
83
- :month,
84
- :year,
85
- :saved_card_id,
86
- :number,
87
- :cvv,
88
- :amount
83
+ *Workarea.config.credit_card_attributes
89
84
  )
90
85
  save
91
86
  end
@@ -11,7 +11,11 @@ module Workarea
11
11
  end
12
12
 
13
13
  def jump_to_text
14
- "#{model.id} (#{model.available} available)"
14
+ I18n.t(
15
+ 'workarea.inventory_sku.jump_to_text',
16
+ id: model.id,
17
+ count: model.available_to_sell
18
+ )
15
19
  end
16
20
 
17
21
  def jump_to_position
@@ -37,15 +37,22 @@ module Workarea
37
37
  end
38
38
 
39
39
  def self.by_price(price)
40
- cache.select do |method|
41
- (method.subtotal_min.nil? || method.subtotal_min <= price) &&
42
- (method.subtotal_max.nil? || method.subtotal_max >= price)
40
+ cache.select do |service|
41
+ (service.subtotal_min.nil? || service.subtotal_min <= price) &&
42
+ (service.subtotal_max.nil? || service.subtotal_max >= price)
43
43
  end
44
44
  end
45
45
 
46
46
  def self.find_tax_code(carrier, name)
47
- method = find_by(carrier: carrier, name: name) rescue nil
48
- method.try(:tax_code)
47
+ service = find_by(carrier: carrier, name: name) rescue nil
48
+ service.present? ? service.tax_code : default_tax_code(carrier, name)
49
+ end
50
+
51
+ def self.default_tax_code(carrier, name)
52
+ default = Workarea.config.default_shipping_service_tax_code
53
+ return default unless default.respond_to?(:call)
54
+
55
+ default.call(carrier, name)
49
56
  end
50
57
 
51
58
  def find_rate(price = 0.to_m)
@@ -57,7 +57,9 @@ module Workarea
57
57
  percentage_field = super
58
58
  return percentage_field unless percentage_field.zero?
59
59
 
60
- country_percentage + region_percentage + postal_code_percentage
60
+ [country_percentage, region_percentage, postal_code_percentage]
61
+ .compact
62
+ .sum
61
63
  end
62
64
  end
63
65
  end
@@ -11,6 +11,10 @@ module Workarea
11
11
  def self.available_sorts
12
12
  AdminSorting.available_sorts
13
13
  end
14
+
15
+ def default_admin_sort
16
+ [{ _score: :desc }, { updated_at: :desc }]
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -16,7 +16,7 @@ module Workarea
16
16
  end
17
17
 
18
18
  def default_admin_sort
19
- [{ _score: :desc }, { updated_at: :desc }]
19
+ [{ updated_at: :desc }, { _score: :desc }]
20
20
  end
21
21
 
22
22
  def user_selected_sort
@@ -76,6 +76,17 @@ Workarea::Configuration.define_fields do
76
76
  zip: '19106'
77
77
  },
78
78
  description: 'Origin location for calculating shipping costs'
79
+
80
+ # This can be overwritten within the app to use a proc for more complex
81
+ # scenarios.
82
+ field 'Default Shipping Service Tax Code',
83
+ type: String,
84
+ allow_blank: true,
85
+ description: %(
86
+ Tax code assigned to shipping options when an existing service does
87
+ not exist. This is useful for third-party gateways to assign tax codes
88
+ to dynamically generated options.
89
+ ).squish
79
90
  end
80
91
 
81
92
  fieldset 'Payment', namespaced: false do
@@ -116,11 +127,6 @@ Workarea::Configuration.define_fields do
116
127
  end
117
128
 
118
129
  fieldset 'Search', namespaced: false do
119
- field 'Default Search Facet Result Sizes',
120
- type: :integer,
121
- default: 10,
122
- description: 'The number of filter results returned for each filter type.'
123
-
124
130
  field 'Search Facet Result Sizes',
125
131
  type: :hash,
126
132
  values_type: :integer,
@@ -128,7 +134,15 @@ Workarea::Configuration.define_fields do
128
134
  description: %(
129
135
  The number of filter results returned for any specified filter type. If no
130
136
  size is defined for a filter type, the default will be what is specified
131
- in the default config above.
137
+ in the default config below.
138
+ ).squish
139
+
140
+ field 'Default Search Facet Result Sizes',
141
+ type: :integer,
142
+ default: 10,
143
+ description: %(
144
+ The number of filter results returned for each filter type when not
145
+ specified above.
132
146
  ).squish
133
147
 
134
148
  field 'Search Size Facet Sort',
@@ -156,14 +170,15 @@ Workarea::Configuration.define_fields do
156
170
  end
157
171
 
158
172
  fieldset 'Communication', namespaced: false do
173
+
159
174
  field 'Email From',
160
175
  type: :string,
161
- default: 'noreply@example.com',
176
+ default: -> { "#{Workarea.config.site_name} <noreply@#{Workarea.config.host}>" },
162
177
  description: 'The email address used as the sender of system emails'
163
178
 
164
179
  field 'Email To',
165
180
  type: :string,
166
- default: 'customerservice@example.com',
181
+ default: -> { "#{Workarea.config.site_name} <customerservice@#{Workarea.config.host}>" },
167
182
  description: 'The email address that receives user generated emails, e.g. contact us inquiries'
168
183
 
169
184
  field 'Inquiry Subjects',
@@ -7,5 +7,5 @@ env_expire_after = ENV['WORKAREA_SESSION_STORE_EXPIRE_AFTER']
7
7
  Rails.application.config.session_store(
8
8
  :cookie_store,
9
9
  key: "_#{Rails.application.class.name.deconstantize.underscore}_session",
10
- expire_after: env_expire_after.present? ? env_expire_after.to_i : 2.weeks
10
+ expire_after: env_expire_after.present? ? env_expire_after.to_i : 30.minutes
11
11
  )
@@ -102,6 +102,9 @@ en:
102
102
  name: "Fulfillment SKU %{id}"
103
103
  inventory_sku:
104
104
  name: "Inventory %{id}"
105
+ jump_to_text: "%{id} (%{count} sellable)"
106
+ inquiry:
107
+ subjects: {}
105
108
  order:
106
109
  name: "Order %{id}"
107
110
  traffic_referrer:
@@ -55,6 +55,19 @@ module Workarea
55
55
  remove_file 'public/favicon.ico'
56
56
  end
57
57
 
58
+ def add_development_mailer_port
59
+ development_port = <<-CODE
60
+
61
+ config.action_mailer.default_url_options = { port: 3000 }
62
+ CODE
63
+
64
+ inject_into_file(
65
+ 'config/environments/development.rb',
66
+ development_port,
67
+ before: /^end/
68
+ )
69
+ end
70
+
58
71
  private
59
72
 
60
73
  def app_name