workarea-registries 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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE +21 -0
  5. data/README.md +1 -0
  6. data/Rakefile +58 -0
  7. data/app/assets/images/workarea/storefront/registries/icons/email.svg +4 -0
  8. data/app/assets/images/workarea/storefront/registries/icons/link.svg +1 -0
  9. data/app/assets/javascripts/workarea/storefront/registries/modules/add_to_registry_button.js +49 -0
  10. data/app/assets/javascripts/workarea/storefront/registries/modules/copy_registry_url_button.js +51 -0
  11. data/app/assets/javascripts/workarea/storefront/registries/modules/toggle_registry_address.js +58 -0
  12. data/app/assets/javascripts/workarea/storefront/registries/templates/add_to_registry_button.jst.ejs +3 -0
  13. data/app/controllers/workarea/admin/registries_controller.rb +28 -0
  14. data/app/controllers/workarea/storefront/cart_items_controller.decorator +14 -0
  15. data/app/controllers/workarea/storefront/registries_controller.rb +47 -0
  16. data/app/controllers/workarea/storefront/users/registries_controller.rb +94 -0
  17. data/app/controllers/workarea/storefront/users/registry_items_controller.rb +67 -0
  18. data/app/controllers/workarea/storefront/users/registry_shares_controller.rb +43 -0
  19. data/app/controllers/workarea/storefront/users/registry_thanks_controller.rb +41 -0
  20. data/app/helpers/workarea/registries_helper.rb +41 -0
  21. data/app/mailers/workarea/storefront/registries_mailer.rb +52 -0
  22. data/app/models/workarea/checkout/steps/addresses.decorator +28 -0
  23. data/app/models/workarea/order.decorator +7 -0
  24. data/app/models/workarea/order/item.decorator +11 -0
  25. data/app/models/workarea/registry.rb +129 -0
  26. data/app/models/workarea/registry/item.rb +39 -0
  27. data/app/models/workarea/registry/public_search.rb +23 -0
  28. data/app/models/workarea/registry/share.rb +46 -0
  29. data/app/models/workarea/registry/thank.rb +26 -0
  30. data/app/models/workarea/search/admin/registry.rb +29 -0
  31. data/app/queries/workarea/search/admin_orders.decorator +7 -0
  32. data/app/queries/workarea/search/admin_registries.rb +16 -0
  33. data/app/seeds/workarea/registry_content_seeds.rb +50 -0
  34. data/app/services/workarea/inventory_adjustment.decorator +43 -0
  35. data/app/view_models/workarea/admin/registry_view_model.rb +19 -0
  36. data/app/view_models/workarea/storefront/checkout/addresses_view_model.decorator +17 -0
  37. data/app/view_models/workarea/storefront/order_item_view_model.decorator +9 -0
  38. data/app/view_models/workarea/storefront/order_view_model.decorator +8 -0
  39. data/app/view_models/workarea/storefront/registry_email_view_model.rb +37 -0
  40. data/app/view_models/workarea/storefront/registry_item_view_model.rb +72 -0
  41. data/app/view_models/workarea/storefront/registry_share_view_model.rb +6 -0
  42. data/app/view_models/workarea/storefront/registry_thank_view_model.rb +6 -0
  43. data/app/view_models/workarea/storefront/registry_view_model.rb +74 -0
  44. data/app/view_models/workarea/storefront/user_view_model.decorator +12 -0
  45. data/app/views/workarea/admin/registries/_menu.html.haml +1 -0
  46. data/app/views/workarea/admin/registries/index.html.haml +70 -0
  47. data/app/views/workarea/storefront/carts/_registry_details.html.haml +3 -0
  48. data/app/views/workarea/storefront/checkouts/_registry_address.html.haml +13 -0
  49. data/app/views/workarea/storefront/checkouts/_registry_address_fields.html.haml +28 -0
  50. data/app/views/workarea/storefront/checkouts/_registry_details.html.haml +3 -0
  51. data/app/views/workarea/storefront/products/_add_to_registry_placeholder.html.haml +1 -0
  52. data/app/views/workarea/storefront/registries/_registries_link.html.haml +4 -0
  53. data/app/views/workarea/storefront/registries/index.html.haml +39 -0
  54. data/app/views/workarea/storefront/registries/search.html.haml +35 -0
  55. data/app/views/workarea/storefront/registries/show.html.haml +63 -0
  56. data/app/views/workarea/storefront/registries_mailer/creation.html.haml +13 -0
  57. data/app/views/workarea/storefront/registries_mailer/gift_receipt.html.haml +51 -0
  58. data/app/views/workarea/storefront/registries_mailer/share.html.haml +21 -0
  59. data/app/views/workarea/storefront/registries_mailer/thank.html.haml +19 -0
  60. data/app/views/workarea/storefront/users/_registry_count.json.jbuilder +3 -0
  61. data/app/views/workarea/storefront/users/accounts/_registries.html.haml +14 -0
  62. data/app/views/workarea/storefront/users/registries/_form.html.haml +56 -0
  63. data/app/views/workarea/storefront/users/registries/_radio_list.html.haml +8 -0
  64. data/app/views/workarea/storefront/users/registries/_summary.html.haml +12 -0
  65. data/app/views/workarea/storefront/users/registries/edit.html.haml +4 -0
  66. data/app/views/workarea/storefront/users/registries/index.html.haml +24 -0
  67. data/app/views/workarea/storefront/users/registries/new.html.haml +4 -0
  68. data/app/views/workarea/storefront/users/registries/show.html.haml +75 -0
  69. data/app/views/workarea/storefront/users/registry_items/new.html.haml +10 -0
  70. data/app/views/workarea/storefront/users/registry_items/templates/_purchased.html.haml +29 -0
  71. data/app/views/workarea/storefront/users/registry_items/templates/_unpurchased.html.haml +35 -0
  72. data/app/views/workarea/storefront/users/registry_shares/_share_buttons.html.haml +18 -0
  73. data/app/views/workarea/storefront/users/registry_shares/new.html.haml +49 -0
  74. data/app/views/workarea/storefront/users/registry_thanks/new.html.haml +47 -0
  75. data/app/workers/workarea/send_gift_receipt.rb +15 -0
  76. data/app/workers/workarea/send_registry_creation_email.rb +12 -0
  77. data/app/workers/workarea/update_registry_items.rb +24 -0
  78. data/bin/rails +20 -0
  79. data/config/initializers/appends.rb +54 -0
  80. data/config/initializers/fields.rb +19 -0
  81. data/config/locales/en.yml +133 -0
  82. data/config/routes.rb +26 -0
  83. data/lib/workarea/mailer_previews/storefront/registry_mailer_preview.rb +25 -0
  84. data/lib/workarea/registries.rb +11 -0
  85. data/lib/workarea/registries/engine.rb +17 -0
  86. data/lib/workarea/registries/version.rb +5 -0
  87. data/test/dummy/config/application.rb +27 -0
  88. data/test/factories/workarea/registry.rb +67 -0
  89. data/test/integration/workarea/storefront/cart_item_registry_integration_test.rb +104 -0
  90. data/test/integration/workarea/storefront/checkout_registry_integration_test.rb +156 -0
  91. data/test/integration/workarea/storefront/registry_privacy_integration_test.rb +48 -0
  92. data/test/integration/workarea/storefront/user_registries_integration_test.rb +50 -0
  93. data/test/integration/workarea/storefront/user_registry_items_integration_test.rb +106 -0
  94. data/test/models/workarea/registry/item_test.rb +22 -0
  95. data/test/models/workarea/registry/public_search_test.rb +48 -0
  96. data/test/models/workarea/registry_test.rb +113 -0
  97. data/test/system/workarea/admin/registry_system_test.rb +20 -0
  98. data/test/workers/update_registry_items_test.rb +45 -0
  99. data/workarea-registries.gemspec +20 -0
  100. metadata +156 -0
@@ -0,0 +1,23 @@
1
+ module Workarea
2
+ class Registry::PublicSearch
3
+ attr_reader :query
4
+
5
+ def initialize(query)
6
+ @query = query
7
+ end
8
+
9
+ def results
10
+ regex = /^#{Regexp.quote(query)}/i
11
+ criteria = [
12
+ { name: regex },
13
+ { id: query }, # we don't want partial matching on id
14
+ { email: query } # we don't want partial matching on email
15
+ ]
16
+
17
+ Workarea::Registry.where(
18
+ privacy: 'public',
19
+ '$or' => criteria
20
+ )
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ module Workarea
2
+ class Registry
3
+ class Share
4
+ include ApplicationDocument
5
+
6
+ field :to_name, type: String
7
+ field :to_email, type: String
8
+ field :from_name, type: String
9
+ field :from_email, type: String
10
+ field :message, type: String
11
+ field :registry_name, type: String
12
+ field :url, type: String
13
+ field :registry_id, type: String
14
+
15
+ validates :to_email, presence: true, email: true
16
+ validates :from_email, presence: true, email: true
17
+ validates :message, length: {
18
+ maximum: :max_email_share_message_length
19
+ }
20
+ validates :url, presence: true
21
+
22
+ after_initialize :sanitize_url
23
+ before_validation :sanitize_url
24
+
25
+ private
26
+
27
+ def max_email_share_message_length
28
+ Workarea.config.max_email_registry_share_message_length
29
+ end
30
+
31
+ def uri
32
+ URI.parse(url)
33
+ end
34
+
35
+ def sanitize_url
36
+ return unless url.present?
37
+ self.url =
38
+ if uri.query.present?
39
+ [uri.path, uri.query].join('?')
40
+ else
41
+ uri.path
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ module Workarea
2
+ class Registry
3
+ class Thank
4
+ include ApplicationDocument
5
+
6
+ field :to_name, type: String
7
+ field :to_email, type: String
8
+ field :from_name, type: String
9
+ field :from_email, type: String
10
+ field :message, type: String
11
+ field :registry_id, type: String
12
+
13
+ validates :to_email, presence: true, email: true
14
+ validates :from_email, presence: true, email: true
15
+ validates :message, length: {
16
+ maximum: :max_email_thank_message_length
17
+ }
18
+
19
+ private
20
+
21
+ def max_email_thank_message_length
22
+ Workarea.config.max_email_registry_thanks_message_length
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module Workarea
2
+ module Search
3
+ class Admin
4
+ class Registry < Search::Admin
5
+ def type
6
+ 'registry'
7
+ end
8
+
9
+ def jump_to_text
10
+ model.name
11
+ end
12
+
13
+ def jump_to_position
14
+ 3
15
+ end
16
+
17
+ def search_text
18
+ [
19
+ 'registry',
20
+ model.id,
21
+ model.name,
22
+ *model.items.map(&:product_id),
23
+ *model.items.map(&:sku)
24
+ ].join(' ')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ module Workarea
2
+ decorate Search::AdminOrders, with: :registries do
3
+ def facets
4
+ super + [Search::TermsFacet.new(self, 'registry')]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Workarea
2
+ module Search
3
+ class AdminRegistries
4
+ include Query
5
+ include AdminIndexSearch
6
+ include AdminSorting
7
+ include Pagination
8
+
9
+ document Search::Admin
10
+
11
+ def initialize(params = {})
12
+ super(params.merge(type: 'registry'))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,50 @@
1
+ module Workarea
2
+ class RegistryContentSeeds
3
+ def perform
4
+ puts 'Adding registry content...'
5
+
6
+ create_system_page
7
+ create_email_content
8
+ end
9
+
10
+ def create_system_page
11
+ content = Content.for('registries')
12
+ content.blocks.create!(
13
+ area: 'default',
14
+ type: 'html',
15
+ data: { html: default_page_html }
16
+ )
17
+ end
18
+
19
+ def create_email_content
20
+ Content::Email.create!(
21
+ type: 'registry_creation',
22
+ content: 'Congratulations on your new registry!'
23
+ )
24
+
25
+ Content::Email.create!(
26
+ type: 'registry_share',
27
+ content: 'Check out my Registry.'
28
+ )
29
+
30
+ Content::Email.create!(
31
+ type: 'registry_thank',
32
+ content: 'Thank you for your Gift.'
33
+ )
34
+
35
+ Content::Email.create!(
36
+ type: 'registry_gift_receipt',
37
+ content: 'The following items have been purchased for your Registry.'
38
+ )
39
+ end
40
+
41
+ def default_page_html
42
+ <<-TEXT
43
+ <h1>Registry Welcome Page</h1>
44
+ <p>
45
+ This page will act as a portal for users to begin and continue their Registry Journey.
46
+ </p>
47
+ TEXT
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ module Workarea
2
+ decorate InventoryAdjustment, with: :registries do
3
+ def perform
4
+ super
5
+
6
+ cart.items.each do |item|
7
+ next unless item.registry_item?
8
+ available_quantity = find_available_quantity(item)
9
+
10
+ next if available_quantity >= item.quantity
11
+
12
+ if available_quantity == 0
13
+ cart.remove_item(item.id)
14
+ @errors << I18n.t('workarea.errors.messages.registry_sku_unavailable', sku: item[:sku], registry: @registry.name)
15
+ else
16
+ cart.update_item(item.id, quantity: available_quantity)
17
+ @errors << I18n.t(
18
+ 'workarea.errors.messages.registry_sku_limited_quantity',
19
+ quantity: available_quantity,
20
+ sku: item[:sku],
21
+ registry: @registry.name
22
+ )
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def find_available_quantity(item)
30
+ registry_item = find_registry_item(item)
31
+
32
+ registry_item&.available_quantity || 0
33
+ end
34
+
35
+ def find_registry_item(item)
36
+ @registry = Storefront::RegistryViewModel.wrap(
37
+ Registry.find_by(id: item.registry_id)
38
+ )
39
+
40
+ @registry.items.select { |registry_item| registry_item.sku == item.sku }.first
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Workarea
2
+ module Admin
3
+ class RegistryViewModel < ApplicationViewModel
4
+ include CommentableViewModel
5
+
6
+ def timeline
7
+ @timeline ||= TimelineViewModel.new(model)
8
+ end
9
+
10
+ def user
11
+ @user ||= UserViewModel.new(
12
+ User.find(user_id)
13
+ )
14
+ rescue Mongoid::Errors::DocumentNotFound
15
+ @user = nil
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ decorate Storefront::Checkout::AddressesViewModel, with: :registries do
3
+ def registry_address
4
+ Registry.where(id: registry_id).first.address
5
+ end
6
+
7
+ def registry_address?
8
+ order.items.map{ | item | item.registry_id }.uniq.one?
9
+ end
10
+
11
+ private
12
+
13
+ def registry_id
14
+ order.items.detect(&:registry_id?).registry_id
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Workarea
2
+ decorate Storefront::OrderItemViewModel, with: :registries do
3
+ def registry_label
4
+ Storefront::RegistryViewModel.new(
5
+ Registry.find(registry_id)
6
+ ).registry_label
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Workarea
2
+ decorate Storefront::OrderViewModel, with: :registries do
3
+ def registry_email
4
+ registry_id = items.detect(&:registry_item?).registry_id
5
+ Registry.find(registry_id).email
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ module Workarea
2
+ module Storefront
3
+ class RegistryEmailViewModel < ApplicationViewModel
4
+ def to
5
+ to_email
6
+ end
7
+
8
+ def from
9
+ from_email
10
+ end
11
+
12
+ def from_name
13
+ model.from_name || options[:user].try(:name)
14
+ end
15
+
16
+ def from_email
17
+ model.from_email || options[:user].try(:email)
18
+ end
19
+
20
+ def friendly_from
21
+ if from_name.blank?
22
+ from_email
23
+ else
24
+ from_name
25
+ end
26
+ end
27
+
28
+ def friendly_to
29
+ if to_name.present?
30
+ "#{to_name} (#{to_email})"
31
+ else
32
+ to_email
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ module Workarea
2
+ module Storefront
3
+ class RegistryItemViewModel < CartItemViewModel
4
+ delegate :on_sale?, to: :price
5
+ delegate :has_prices?, :primary_image, :purchasable?, to: :product
6
+
7
+ def product
8
+ @product ||= ProductViewModel.wrap(
9
+ catalog_product,
10
+ options.merge(sku: sku)
11
+ )
12
+ end
13
+
14
+ def catalog_product
15
+ @catalog_product ||= Catalog::Product.find(product_id)
16
+ end
17
+
18
+ def template
19
+ if purchased_by?
20
+ I18n.t('workarea.storefront.registry.item.purchased')
21
+ else
22
+ I18n.t('workarea.storefront.registry.item.unpurchased')
23
+ end
24
+ end
25
+
26
+ # maintain compatibility with OrderItemViewModel
27
+ def customized?
28
+ false
29
+ end
30
+
31
+ def original_price
32
+ price.regular
33
+ end
34
+
35
+ def original_unit_price
36
+ price.sell
37
+ end
38
+
39
+ def total_price
40
+ original_unit_price * quantity
41
+ end
42
+
43
+ def price
44
+ @pricing ||=
45
+ options[:pricing]&.for_sku(sku, quantity: quantity) ||
46
+ Pricing::Sku.find_or_create_by(id: sku).find_price(quantity: quantity)
47
+ end
48
+
49
+ def inventory
50
+ @inventory ||=
51
+ options[:inventory]&.for_sku(sku) ||
52
+ Inventory::Sku.find_or_create_by(id: sku)
53
+ end
54
+
55
+ def inventory_status
56
+ InventoryStatusViewModel.new(inventory).message
57
+ end
58
+
59
+ def insufficient_inventory?
60
+ inventory.available_to_sell < quantity
61
+ end
62
+
63
+ def received?
64
+ received.positive?
65
+ end
66
+
67
+ def available_quantity
68
+ quantity - received
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,6 @@
1
+ module Workarea
2
+ module Storefront
3
+ class RegistryShareViewModel < RegistryEmailViewModel
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Workarea
2
+ module Storefront
3
+ class RegistryThankViewModel < RegistryEmailViewModel
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,74 @@
1
+ module Workarea
2
+ module Storefront
3
+ class RegistryViewModel < ApplicationViewModel
4
+ def user
5
+ @user ||=
6
+ options[:user] ||
7
+ UserViewModel.wrap(User.find(user_id))
8
+ end
9
+
10
+ def address
11
+ model.address ||
12
+ user.try(:default_shipping_address) ||
13
+ Address.new
14
+ end
15
+
16
+ def email
17
+ model.email || user.try(:email)
18
+ end
19
+
20
+ def owner
21
+ user.name.present? ? user.name : user.email
22
+ end
23
+
24
+ def items
25
+ @items ||=
26
+ begin
27
+ items = if options[:state] == 'purchased'
28
+ model.purchased_items
29
+ elsif options[:state] == 'available'
30
+ model.available_items
31
+ else
32
+ model.unpurchased_items
33
+ end
34
+
35
+ items.map do |item|
36
+ RegistryItemViewModel.wrap(
37
+ item,
38
+ options.merge(inventory: inventory, pricing: pricing)
39
+ )
40
+ end
41
+ end
42
+ end
43
+
44
+ def state_options
45
+ [
46
+ [I18n.t('workarea.storefront.registries.unpurchased_items'), :unpurchased],
47
+ [I18n.t('workarea.storefront.registries.purchased_items'), :purchased]
48
+ ]
49
+ end
50
+
51
+ def event_date
52
+ model.event_date.try(:to_date)
53
+ end
54
+
55
+ def registry_label
56
+ I18n.t('workarea.storefront.registries.registry_label', name: model.name)
57
+ end
58
+
59
+ private
60
+
61
+ def inventory
62
+ @inventory ||= Inventory::Collection.new(skus)
63
+ end
64
+
65
+ def pricing
66
+ @pricing ||= Pricing::Collection.new(skus)
67
+ end
68
+
69
+ def skus
70
+ model.items.map(&:sku)
71
+ end
72
+ end
73
+ end
74
+ end