workarea-wish_lists 3.0.4

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.eslintignore +2 -0
  4. data/.eslintrc +24 -0
  5. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  6. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/.gitignore +15 -0
  9. data/.scss-lint.yml +188 -0
  10. data/.yardopts +1 -0
  11. data/CHANGELOG.md +610 -0
  12. data/CODE_OF_CONDUCT.md +3 -0
  13. data/CONTRIBUTING.md +3 -0
  14. data/Gemfile +6 -0
  15. data/LICENSE +52 -0
  16. data/README.md +58 -0
  17. data/Rakefile +53 -0
  18. data/app/assets/javascripts/workarea/storefront/wish_lists/modules/wish_list_button.js +35 -0
  19. data/app/assets/javascripts/workarea/storefront/wish_lists/modules/wish_list_public_quantity_fields.js +42 -0
  20. data/app/assets/javascripts/workarea/storefront/wish_lists/templates/hidden_input.jst.ejs +1 -0
  21. data/app/assets/stylesheets/workarea/storefront/wish_lists/components/_wish_list_button.scss +6 -0
  22. data/app/assets/stylesheets/workarea/storefront/wish_lists/components/_wish_lists.scss +24 -0
  23. data/app/controllers/workarea/admin/users_controller.decorator +20 -0
  24. data/app/controllers/workarea/storefront/users/wish_lists_controller.rb +128 -0
  25. data/app/controllers/workarea/storefront/wish_lists_controller.rb +22 -0
  26. data/app/models/workarea/wish_list.rb +212 -0
  27. data/app/models/workarea/wish_list/item.rb +76 -0
  28. data/app/models/workarea/wish_list/pricing.rb +14 -0
  29. data/app/models/workarea/wish_list/pricing/calculators/totals_calculator.rb +26 -0
  30. data/app/models/workarea/wish_list/public_search.rb +34 -0
  31. data/app/models/workarea/wish_list/request.rb +36 -0
  32. data/app/seeds/workarea/wish_list_seeds.rb +24 -0
  33. data/app/services/workarea/set_wish_list_details.rb +49 -0
  34. data/app/services/workarea/wish_list_session.rb +53 -0
  35. data/app/view_models/workarea/admin/user_view_model.decorator +12 -0
  36. data/app/view_models/workarea/storefront/user_view_model.decorator +31 -0
  37. data/app/view_models/workarea/storefront/wish_list_item_view_model.rb +35 -0
  38. data/app/view_models/workarea/storefront/wish_list_view_model.rb +75 -0
  39. data/app/views/layouts/workarea/storefront/_wish_lists_link.html.haml +6 -0
  40. data/app/views/workarea/admin/users/_wish_list_card.html.haml +24 -0
  41. data/app/views/workarea/admin/users/_wish_list_item_summary.html.haml +8 -0
  42. data/app/views/workarea/admin/users/wish_list.html.haml +26 -0
  43. data/app/views/workarea/storefront/carts/_move_to_wish_list.html.haml +4 -0
  44. data/app/views/workarea/storefront/products/_add_to_wish_list.html.haml +4 -0
  45. data/app/views/workarea/storefront/style_guides/_wish_lists_product_list_docs.html.haml +158 -0
  46. data/app/views/workarea/storefront/users/accounts/_wish_list_summary.html.haml +15 -0
  47. data/app/views/workarea/storefront/users/wish_lists/show.html.haml +125 -0
  48. data/app/views/workarea/storefront/wish_lists/_wish_lists_link.html.haml +4 -0
  49. data/app/views/workarea/storefront/wish_lists/index.html.haml +32 -0
  50. data/app/views/workarea/storefront/wish_lists/show.html.haml +97 -0
  51. data/app/workers/workarea/mark_wish_list_items_purchased.rb +18 -0
  52. data/app/workers/workarea/update_wish_list_details.rb +19 -0
  53. data/bin/rails +18 -0
  54. data/config/initializers/append_points.rb +46 -0
  55. data/config/initializers/configuration.rb +10 -0
  56. data/config/locales/en.yml +60 -0
  57. data/config/routes.rb +23 -0
  58. data/lib/workarea/testing/factories/wish_lists.rb +22 -0
  59. data/lib/workarea/wish_lists.rb +13 -0
  60. data/lib/workarea/wish_lists/engine.rb +15 -0
  61. data/lib/workarea/wish_lists/version.rb +5 -0
  62. data/test/dummy/Rakefile +6 -0
  63. data/test/dummy/app/assets/config/manifest.js +4 -0
  64. data/test/dummy/app/assets/images/.keep +0 -0
  65. data/test/dummy/app/assets/javascripts/application.js +13 -0
  66. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  67. data/test/dummy/app/controllers/application_controller.rb +3 -0
  68. data/test/dummy/app/controllers/concerns/.keep +0 -0
  69. data/test/dummy/app/helpers/application_helper.rb +2 -0
  70. data/test/dummy/app/jobs/application_job.rb +2 -0
  71. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  72. data/test/dummy/app/models/concerns/.keep +0 -0
  73. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  74. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  75. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  76. data/test/dummy/bin/bundle +3 -0
  77. data/test/dummy/bin/rails +4 -0
  78. data/test/dummy/bin/rake +4 -0
  79. data/test/dummy/bin/setup +34 -0
  80. data/test/dummy/bin/update +29 -0
  81. data/test/dummy/config.ru +5 -0
  82. data/test/dummy/config/application.rb +24 -0
  83. data/test/dummy/config/boot.rb +5 -0
  84. data/test/dummy/config/cable.yml +9 -0
  85. data/test/dummy/config/environment.rb +5 -0
  86. data/test/dummy/config/environments/development.rb +54 -0
  87. data/test/dummy/config/environments/production.rb +86 -0
  88. data/test/dummy/config/environments/test.rb +43 -0
  89. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  90. data/test/dummy/config/initializers/assets.rb +11 -0
  91. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  92. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  93. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  94. data/test/dummy/config/initializers/inflections.rb +16 -0
  95. data/test/dummy/config/initializers/mime_types.rb +4 -0
  96. data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
  97. data/test/dummy/config/initializers/session_store.rb +3 -0
  98. data/test/dummy/config/initializers/workarea.rb +5 -0
  99. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  100. data/test/dummy/config/locales/en.yml +23 -0
  101. data/test/dummy/config/puma.rb +47 -0
  102. data/test/dummy/config/routes.rb +5 -0
  103. data/test/dummy/config/secrets.yml +22 -0
  104. data/test/dummy/config/spring.rb +6 -0
  105. data/test/dummy/db/seeds.rb +3 -0
  106. data/test/dummy/lib/assets/.keep +0 -0
  107. data/test/dummy/log/.keep +0 -0
  108. data/test/dummy/public/404.html +67 -0
  109. data/test/dummy/public/422.html +67 -0
  110. data/test/dummy/public/500.html +66 -0
  111. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  112. data/test/dummy/public/apple-touch-icon.png +0 -0
  113. data/test/dummy/public/favicon.ico +0 -0
  114. data/test/integration/workarea/storefront/wish_lists_integration_test.rb +211 -0
  115. data/test/models/workarea/wish_list/public_search_test.rb +82 -0
  116. data/test/models/workarea/wish_list_test.rb +158 -0
  117. data/test/services/workarea/set_wish_list_details_test.rb +62 -0
  118. data/test/services/workarea/wish_list_session_test.rb +47 -0
  119. data/test/system/workarea/admin/wish_lists_system_test.rb +46 -0
  120. data/test/system/workarea/storefront/wish_lists_system_test.rb +287 -0
  121. data/test/test_helper.rb +10 -0
  122. data/test/view_models/workarea/storefront/user_view_model_test.rb +46 -0
  123. data/test/view_models/workarea/storefront/wish_list_item_view_model_test.rb +47 -0
  124. data/test/view_models/workarea/storefront/wish_list_view_model_test.rb +74 -0
  125. data/test/workers/workarea/mark_wish_list_items_purchased_test.rb +21 -0
  126. data/workarea-wish_lists.gemspec +21 -0
  127. metadata +188 -0
@@ -0,0 +1,22 @@
1
+ module Workarea
2
+ module Storefront
3
+ class WishListsController < Storefront::ApplicationController
4
+ def index
5
+ if params[:wish_list_query].present?
6
+ @wish_lists = WishList
7
+ .search(params[:wish_list_query], params[:location])
8
+ .page(params[:page] || 1)
9
+ end
10
+ end
11
+
12
+ def show
13
+ wish_list = WishList.find_by_token(params[:id])
14
+ WishList::Pricing.perform(wish_list)
15
+ @wish_list = WishListViewModel.new(
16
+ wish_list,
17
+ view_model_options
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,212 @@
1
+ module Workarea
2
+ class WishList
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include UrlToken
6
+
7
+ class InvalidToken < StandardError; end
8
+
9
+ field :user_id, type: String
10
+ field :email, type: String
11
+ field :first_name, type: String
12
+ field :last_name, type: String
13
+ field :name, type: String
14
+ field :location, type: String
15
+ field :privacy, type: String, default: 'public'
16
+
17
+ index({ user_id: 1 }, { unique: true })
18
+ index(first_name: 1)
19
+ index(last_name: 1)
20
+ index(name: 1)
21
+ index(email: 1)
22
+
23
+ index(token: 1, privacy: 1)
24
+
25
+ embeds_many :items, class_name: 'Workarea::WishList::Item'
26
+
27
+ validates :privacy,
28
+ presence: true,
29
+ inclusion: { in: %w(public shared private) }
30
+
31
+ scope :named, -> { where(:name.exists => true, :name.ne => '') }
32
+
33
+ before_create :set_user_details
34
+
35
+ # Gets a wish list for a user.
36
+ #
37
+ # @param [String] user_id
38
+ #
39
+ # @return [WishList]
40
+ #
41
+ def self.for_user(user_id)
42
+ existing = Workarea::WishList.where(user_id: user_id).first
43
+ existing || Workarea::WishList.create!(user_id: user_id)
44
+ end
45
+
46
+ # Find wish lists for public viewing.
47
+ # Only returns wish lists with public privacy setting.
48
+ #
49
+ # @param [String] query
50
+ # name or email
51
+ # @param [String] location
52
+ # city, state, postal code
53
+ #
54
+ # @return [Array]
55
+ #
56
+ def self.search(query, location = '')
57
+ Workarea::WishList::PublicSearch.new(
58
+ query,
59
+ location
60
+ ).results
61
+ end
62
+
63
+ # Find a wish list by the share token
64
+ #
65
+ # @param [String] token
66
+ #
67
+ # @return [WishList]
68
+ #
69
+ def self.find_by_token(token)
70
+ raise(InvalidToken, token) if token.blank?
71
+
72
+ Workarea::WishList.where(
73
+ token: token,
74
+ :privacy.ne => 'private'
75
+ ).first || raise(InvalidToken, token)
76
+ end
77
+
78
+ # Find a list of product ids in a user's wish list
79
+ #
80
+ # @param [String, BSON::ObjectId] user_id
81
+ #
82
+ # @return [Array]
83
+ #
84
+ def self.find_products(user_id)
85
+ wish_list = Workarea::WishList.where(user_id: user_id).first
86
+ wish_list ? wish_list.items.map(&:product_id) : []
87
+ end
88
+
89
+ %w(public shared private).each do |privacy_level|
90
+ define_method "#{privacy_level}?" do
91
+ privacy == privacy_level
92
+ end
93
+ end
94
+
95
+ # How many valid units are in the wish list
96
+ #
97
+ # @return [Integer]
98
+ #
99
+ def quantity
100
+ items.select(&:valid?).sum(&:quantity)
101
+ end
102
+
103
+ # Get unpurchased items
104
+ #
105
+ # @return [Array]
106
+ #
107
+ def unpurchased_items
108
+ items.reject(&:purchased?)
109
+ end
110
+
111
+ # Get items from this list that have been purchased
112
+ #
113
+ # @return [Array]
114
+ #
115
+ def purchased_items
116
+ items.select(&:purchased?)
117
+ end
118
+
119
+ # Add a wish list item
120
+ #
121
+ # @param [String] product_id
122
+ # @param [String] sku
123
+ # @param [Integer] quantity
124
+ #
125
+ # @return [Boolean]
126
+ # whether the item was saved
127
+ #
128
+ def add_item(product_id, sku, quantity = 1, details = {}, customizations = {})
129
+ if existing_item = items.where(sku: sku, customizations: customizations).first
130
+ change_quantity(sku, existing_item.quantity + quantity)
131
+ else
132
+ items.build(
133
+ product_id: product_id,
134
+ sku: sku,
135
+ quantity: quantity,
136
+ details: details,
137
+ customizations: customizations
138
+ )
139
+ end
140
+
141
+ save
142
+ end
143
+
144
+ # Update a wish list item's quantity
145
+ #
146
+ # @param [String] item_id
147
+ # @param [Integer] quantity
148
+ #
149
+ # @return [Boolean]
150
+ # whether the item was saved
151
+ #
152
+
153
+ def update_item_quantity(item_id, quantity)
154
+ item = items.where(id: item_id).first
155
+ return false if item.nil?
156
+ item.update_attributes(quantity: quantity)
157
+ end
158
+
159
+ # Mark the items as being purchased
160
+ #
161
+ # @param [Array] skus
162
+ #
163
+ # @return [self]
164
+ #
165
+ def mark_item_purchased(sku, quantity)
166
+ item = items.detect { |i| i.sku == sku }
167
+ if item
168
+ item.received = item.received + quantity
169
+ item.purchased = true
170
+ end
171
+ save!
172
+ end
173
+
174
+ # Remove a SKU from the wish list
175
+ #
176
+ # @param [String] sku
177
+ #
178
+ # @return [self]
179
+ #
180
+ def remove_item(sku)
181
+ items.where(sku: sku).first.delete
182
+ save
183
+ end
184
+
185
+ def price_adjustments
186
+ PriceAdjustmentSet.new(
187
+ (items.map(&:price_adjustments).flatten || [])
188
+ )
189
+ end
190
+
191
+ def reset_pricing!
192
+ items.each(&:reset_pricing!)
193
+ self
194
+ end
195
+
196
+ def segment_ids
197
+ []
198
+ end
199
+
200
+ private
201
+
202
+ def change_quantity(sku, quantity)
203
+ item = items.where(sku: sku).first
204
+ item.quantity = quantity
205
+ end
206
+
207
+ def set_user_details
208
+ return unless user_id.present?
209
+ SetWishListDetails.new(self).perform
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,76 @@
1
+ module Workarea
2
+ class WishList::Item
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+
6
+ field :product_id, type: String
7
+ field :quantity, type: Integer, default: 1
8
+ field :received, type: Integer, default: 0
9
+ field :sku, type: String
10
+ field :product_details, type: Hash, default: {}
11
+ field :sku_details, type: Hash, default: {}
12
+ field :customizations, type: Hash, default: {}
13
+ field :purchased, type: Boolean, default: false
14
+ field :discountable, type: Boolean, default: true
15
+ field :contributes_to_shipping, type: Boolean, default: true
16
+ field :product_attributes, type: Hash, default: {}
17
+ field :total_price, type: Money, default: 0
18
+
19
+ embedded_in :wish_list, inverse_of: :items
20
+
21
+ embeds_many :price_adjustments,
22
+ class_name: 'Workarea::PriceAdjustment',
23
+ extend: Workarea::PriceAdjustmentExtension
24
+
25
+ validates :product_id, presence: true
26
+ validates :sku, presence: true
27
+ validates :quantity, presence: true,
28
+ numericality: {
29
+ greater_than_or_equal_to: 1,
30
+ only_integer: true
31
+ }
32
+
33
+ def details=(details)
34
+ write_attributes(
35
+ details.slice(
36
+ :product_details,
37
+ :sku_details,
38
+ :product_attributes
39
+ )
40
+ )
41
+
42
+ self
43
+ end
44
+
45
+ # Adds a price adjustment to the item. Does not persist.
46
+ #
47
+ # @return [self]
48
+ #
49
+ def adjust_pricing(options = {})
50
+ price_adjustments.build(options)
51
+ end
52
+
53
+ # Clears out all pricing for this item.
54
+ #
55
+ def reset_pricing!
56
+ price_adjustments.delete_all
57
+ end
58
+
59
+ # The base price per-unit for this item.
60
+ #
61
+ # @return [Money]
62
+ #
63
+ def unit_price
64
+ price_adjustments.first.unit.to_m
65
+ end
66
+
67
+ # Whether this item is on sale (as of the last time the
68
+ # order was priced).
69
+ #
70
+ # @return [Boolean]
71
+ #
72
+ def on_sale?
73
+ !!price_adjustments.first.data['on_sale']
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,14 @@
1
+ module Workarea
2
+ module WishList::Pricing
3
+ # Build price adjustments and set order total prices.
4
+ #
5
+ # @param [Workarea::WishList] wish_list
6
+ # @return [self]
7
+ #
8
+ def self.perform(wish_list)
9
+ wish_list.reset_pricing!
10
+ WishList::Request.new(wish_list).run
11
+ self
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Workarea
2
+ class WishList::Pricing::Calculators::TotalsCalculator
3
+ attr_reader :wish_list, :request
4
+
5
+ def initialize(wish_list, request = nil)
6
+ @wish_list = wish_list
7
+ @request = request
8
+ end
9
+
10
+ def adjust
11
+ set_item_totals
12
+ end
13
+
14
+ private
15
+
16
+ def price_adjustments
17
+ @price_adjustments ||= @wish_list.price_adjustments # Memoize for perf
18
+ end
19
+
20
+ def set_item_totals
21
+ @wish_list.items.each do |item|
22
+ item.total_price = item.price_adjustments.adjusting('item').sum
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ module Workarea
2
+ class WishList::PublicSearch
3
+ attr_reader :query, :location
4
+
5
+ def initialize(query, location = '')
6
+ @query = query
7
+ @location = location
8
+ end
9
+
10
+ def results
11
+ regex = /^#{Regexp.quote(query)}/i
12
+ criteria = [
13
+ { name: regex },
14
+ { first_name: regex },
15
+ { last_name: regex },
16
+ { email: query } # we don't want partial matching on email
17
+ ]
18
+
19
+ if location.present?
20
+ loc_regex = /.*#{Regexp.quote(location)}.*/i
21
+ Workarea::WishList.where(
22
+ privacy: 'public',
23
+ location: loc_regex,
24
+ '$or' => criteria
25
+ )
26
+ else
27
+ Workarea::WishList.where(
28
+ privacy: 'public',
29
+ '$or' => criteria
30
+ )
31
+ end.named
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module Workarea
2
+ module Pricing
3
+ class WishList::Request
4
+ delegate :items, to: :order
5
+
6
+ def initialize(wish_list)
7
+ @wish_list = wish_list
8
+ end
9
+
10
+ def pricing
11
+ @pricing ||= Pricing::Collection.new(all_skus)
12
+ end
13
+
14
+ def order
15
+ @wish_list
16
+ end
17
+
18
+ def run
19
+ Workarea.config.wish_list_pricing_calculators.each do |klass|
20
+ klass.constantize.new(self).adjust
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def all_skus
27
+ skus = [
28
+ @wish_list.items.map(&:sku),
29
+ @wish_list.items.map { |i| i.customizations['pricing_sku'] }
30
+ ]
31
+
32
+ skus.flatten.reject(&:blank?).uniq
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ module Workarea
2
+ class WishListSeeds
3
+ def perform
4
+ puts 'Adding wish lists...'
5
+
6
+ 5.times do
7
+ user = Workarea::User.sample
8
+ wish_list = Workarea::WishList.for_user(user.id)
9
+ Workarea::Catalog::Product.limit(5).each do |product|
10
+ sku = product.skus.sample
11
+ quantity = rand(3) + 1
12
+
13
+ next unless sku
14
+ wish_list.add_item(product.id, sku, quantity)
15
+
16
+ if rand(2).zero?
17
+ purchased_quantity = rand(quantity) + 1
18
+ wish_list.mark_item_purchased(sku, purchased_quantity)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end