workarea-registries 1.0.0

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