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,67 @@
1
+ module Workarea
2
+ module Storefront
3
+ class Users::RegistryItemsController < ApplicationController
4
+ before_action :require_login
5
+ before_action :find_registry, except: [:new]
6
+ before_action :find_item, except: [:new, :create, :update]
7
+
8
+ def new
9
+ @registries = RegistryViewModel.wrap(
10
+ Registry.for_user(current_user.id),
11
+ view_model_options
12
+ )
13
+
14
+ @product = ProductViewModel.wrap(
15
+ Catalog::Product.find(params[:product_id]),
16
+ view_model_options
17
+ )
18
+ end
19
+
20
+ def create
21
+ if @registry.add_item(item_params)
22
+ flash[:success] = t('workarea.storefront.flash_messages.registry_item_created')
23
+ else
24
+ flash[:error] = t('workarea.storefront.flash_messages.registry_item_not_created')
25
+ end
26
+
27
+ redirect_to users_registry_path(@registry)
28
+ end
29
+
30
+ def update
31
+ if @registry.update_item(params[:id], item_params)
32
+ flash[:success] = t('workarea.storefront.flash_messages.registry_item_updated')
33
+ else
34
+ flash[:error] = t('workarea.storefront.flash_messages.registry_item_not_updated')
35
+ end
36
+
37
+ redirect_to users_registry_path(@registry)
38
+ end
39
+
40
+ def destroy
41
+ if @item.unpurchased?
42
+ @registry.remove_item(params[:id])
43
+ flash[:success] = t('workarea.storefront.flash_messages.registry_item_destroyed')
44
+ else
45
+ flash[:error] = t('workarea.storefront.flash_messages.registry_item_not_destroyed')
46
+ end
47
+
48
+ redirect_to users_registry_path(@registry)
49
+ end
50
+
51
+ private
52
+
53
+ def item_params
54
+ params.permit(:product_id, :sku, :quantity, :purchased_by)
55
+ end
56
+
57
+ def find_registry
58
+ @registry =
59
+ Registry.for_user(current_user.id).find(params[:registry_id])
60
+ end
61
+
62
+ def find_item
63
+ @item = @registry.items.find(params[:id])
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,43 @@
1
+ module Workarea
2
+ module Storefront
3
+ class Users::RegistrySharesController < Storefront::ApplicationController
4
+ before_action :find_registry_share
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ if @registry_share.save
11
+ flash[:success] = t(
12
+ 'workarea.storefront.flash_messages.registry_share_message_sent',
13
+ recipient: @registry_share.friendly_to
14
+ )
15
+ RegistriesMailer.share(@registry_share.id.to_s).deliver_later
16
+ redirect_to users_registry_path(@registry_share.registry_id)
17
+ else
18
+ render :new
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def find_registry_share
25
+ model = Registry::Share.new(share_params)
26
+ @registry_share = RegistryShareViewModel.wrap(model, view_model_options)
27
+ end
28
+
29
+ def share_params
30
+ params.permit(
31
+ :url,
32
+ :registry_id,
33
+ :registry_name,
34
+ :to_email,
35
+ :to_name,
36
+ :from_email,
37
+ :from_name,
38
+ :message
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ module Workarea
2
+ module Storefront
3
+ class Users::RegistryThanksController < Storefront::ApplicationController
4
+ before_action :find_registry_thank
5
+
6
+ def new
7
+ end
8
+
9
+ def create
10
+ if @registry_thank.save
11
+ flash[:success] = t(
12
+ 'workarea.storefront.flash_messages.registry_thank_message_sent',
13
+ recipient: @registry_thank.friendly_to
14
+ )
15
+ RegistriesMailer.thank(@registry_thank.id.to_s).deliver_later
16
+ redirect_to users_registry_path(@registry_thank.registry_id, state: 'unpurchased')
17
+ else
18
+ render :new
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def find_registry_thank
25
+ model = Registry::Thank.new(thank_params)
26
+ @registry_thank = RegistryThankViewModel.wrap(model, view_model_options)
27
+ end
28
+
29
+ def thank_params
30
+ params.permit(
31
+ :registry_id,
32
+ :to_email,
33
+ :to_name,
34
+ :from_email,
35
+ :from_name,
36
+ :message
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module Workarea
2
+ module RegistriesHelper
3
+ def minimum_quantity(received)
4
+ received.positive? ? received : 1
5
+ end
6
+
7
+ def share_registry_url(registry, options = {})
8
+ registry_url(registry, options.merge(host: Workarea.config.host))
9
+ end
10
+
11
+ def facebook_query_string(url)
12
+ hash_without_blanks_to_query(u: url)
13
+ end
14
+
15
+ def twitter_query_string(url, message = nil)
16
+ hash_without_blanks_to_query(url: url, text: message)
17
+ end
18
+
19
+ def share_query_string(url, message = nil, image = nil)
20
+ hash_without_blanks_to_query(
21
+ url: url,
22
+ description: message,
23
+ media: image
24
+ )
25
+ end
26
+
27
+ def share_analytics_data(type, url)
28
+ {
29
+ event: 'share',
30
+ domEvent: 'click',
31
+ payload: { type: type, url: url }
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def hash_without_blanks_to_query(hash)
38
+ hash.delete_if { |_k, v| v.blank? }.to_query
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
1
+ module Workarea
2
+ module Storefront
3
+ class RegistriesMailer < ApplicationMailer
4
+ def creation(id)
5
+ @registry = RegistryViewModel.wrap(Registry.find(id))
6
+ @content = Content::Email.find_content('registry_creation')
7
+
8
+ mail(
9
+ to: @registry.email,
10
+ from: Workarea.config.email_from,
11
+ subject: t('workarea.storefront.email.registries.creation.subject')
12
+ )
13
+ end
14
+
15
+ def share(id)
16
+ @share = RegistryShareViewModel.wrap(Registry::Share.find(id))
17
+ @content = Content::Email.find_content('registry_share')
18
+
19
+ mail(
20
+ to: @share.friendly_to,
21
+ from: Workarea.config.email_from,
22
+ reply_to: @share.from,
23
+ subject: t('workarea.storefront.email.registries.share.subject', from: @share.friendly_from)
24
+ )
25
+ end
26
+
27
+ def thank(id)
28
+ @thank = RegistryThankViewModel.wrap(Registry::Thank.find(id))
29
+ @content = Content::Email.find_content('registry_thank')
30
+
31
+ mail(
32
+ to: @thank.friendly_to,
33
+ from: Workarea.config.email_from,
34
+ reply_to: @thank.from,
35
+ subject: t('workarea.storefront.email.registries.thank.subject', from: @thank.friendly_from)
36
+ )
37
+ end
38
+
39
+ def gift_receipt(order_id)
40
+ order = Order.find(order_id)
41
+ @order = Storefront::OrderViewModel.wrap(order)
42
+ @content = Content::Email.find_content('registry_gift_receipt')
43
+
44
+ mail(
45
+ to: @order.registry_email,
46
+ from: Workarea.config.email_from,
47
+ subject: t('workarea.storefront.email.registries.gift_receipt.subject')
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,28 @@
1
+ module Workarea
2
+ decorate Checkout::Steps::Addresses, with: :registries do
3
+
4
+ def update(params = {})
5
+ set_ship_to_registrant(params)
6
+ super
7
+ end
8
+
9
+ private
10
+
11
+ def set_shipping_address(params)
12
+ if params[:ship_to_registrant].present? && params[:ship_to_registrant] == 'true'
13
+ if order.requires_shipping? && params[:registry_shipping_address].present?
14
+ shipping.set_address(params[:registry_shipping_address])
15
+ set_default_shipping_option
16
+ end
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def set_ship_to_registrant(params)
23
+ if params[:ship_to_registrant].present?
24
+ order.ship_to_registrant = params[:ship_to_registrant]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module Workarea
2
+ decorate Order, with: :registries do
3
+ decorated do
4
+ field :ship_to_registrant, type: Boolean, default: false
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Workarea
2
+ decorate Order::Item, with: :registries do
3
+ decorated do
4
+ field :registry_id, type: String
5
+ end
6
+
7
+ def registry_item?
8
+ registry_id.present?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,129 @@
1
+ module Workarea
2
+ class Registry
3
+ include ApplicationDocument
4
+
5
+ field :user_id, type: String
6
+ field :email, type: String
7
+ field :name, type: String
8
+ field :description, type: String
9
+ field :privacy, type: String, default: 'public'
10
+ field :event_date, type: Date
11
+
12
+ embeds_many :items,
13
+ class_name: 'Workarea::Registry::Item',
14
+ cascade_callbacks: true,
15
+ inverse_of: :registry
16
+
17
+ embeds_one :address,
18
+ class_name: 'Workarea::Address',
19
+ as: :addressable
20
+
21
+ validates :name, presence: true
22
+ validates :user_id, presence: true
23
+ validates :email, presence: true
24
+ validates :privacy, presence: true,
25
+ inclusion: { in: %w(public shared private) }
26
+
27
+ scope :for_user, ->(user_id) { where(user_id: user_id.to_s) }
28
+ scope :by_activity, -> { order(updated_at: :desc) }
29
+
30
+ index({ user_id: 1 })
31
+ index('items.product_id' => 1)
32
+ index(name: 1)
33
+ index(email: 1)
34
+ index(id: 1)
35
+
36
+ def self.search(query)
37
+ Workarea::Registry::PublicSearch.new(
38
+ query
39
+ ).results
40
+ end
41
+
42
+ %w(public shared private).each do |privacy_level|
43
+ define_method "#{privacy_level}?" do
44
+ privacy == privacy_level
45
+ end
46
+ end
47
+
48
+ def default_privacy?
49
+ public? || privacy.blank?
50
+ end
51
+
52
+ def purchased_items
53
+ items.select(&:purchased_by?)
54
+ end
55
+
56
+ def unpurchased_items
57
+ items.reject(&:purchased_by?)
58
+ end
59
+
60
+ def available_items
61
+ items.reject(&:purchased_by?).select(&:available?)
62
+ end
63
+
64
+ def add_item(params)
65
+ quantity = params.fetch(:quantity, 1).to_i
66
+ existing_item = find_existing_item(sku: params[:sku])
67
+
68
+ if existing_item.present?
69
+ update_item(
70
+ existing_item.id,
71
+ quantity: existing_item.quantity + quantity
72
+ )
73
+ else
74
+ items.build(params)
75
+ end
76
+
77
+ save
78
+ end
79
+
80
+ def add_purchased_item(params)
81
+ existing_purchased_item = find_existing_purchased_item(params)
82
+
83
+ if existing_purchased_item
84
+ quantity = existing_purchased_item.quantity + params[:quantity]
85
+ existing_purchased_item.update(quantity: quantity)
86
+ else
87
+ items.build(params)
88
+ end
89
+
90
+ save
91
+ end
92
+
93
+ def update_item(id, params)
94
+ sku = params[:sku]
95
+ existing_item = find_existing_item(sku: sku) if sku.present?
96
+
97
+ if existing_item.present? && existing_item.id.to_s != id.to_s
98
+ item = items.find(id)
99
+ quantity = existing_item.quantity + (params[:quantity] || item.quantity)
100
+
101
+ existing_item.update(quantity: quantity) && item.delete
102
+ else
103
+ items.find(id).update(params)
104
+ end
105
+ end
106
+
107
+ def remove_item(id)
108
+ item = items.detect { |item| item.id.to_s == id.to_s }
109
+ item.nil? || item.destroy!
110
+ end
111
+
112
+ def mark_item_received(item)
113
+ existing_item = find_existing_item(sku: item.sku)
114
+
115
+ if existing_item
116
+ received = existing_item.received + item.quantity
117
+ existing_item.update(received: received)
118
+ end
119
+ end
120
+
121
+ def find_existing_item(params = {})
122
+ unpurchased_items.detect { |item| item.sku == params[:sku] }
123
+ end
124
+
125
+ def find_existing_purchased_item(params = {})
126
+ items.detect { |item| item.sku == params[:sku] && item.purchased_by == params[:purchased_by] }
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,39 @@
1
+ module Workarea
2
+ class Registry
3
+ class Item
4
+ include ApplicationDocument
5
+
6
+ field :product_id, type: String
7
+ field :sku, type: String
8
+ field :quantity, type: Integer, default: 1
9
+ field :received, type: Integer, default: 0
10
+ field :purchased_by, type: String
11
+
12
+ embedded_in :registry,
13
+ class_name: 'Workarea::Registry',
14
+ touch: true
15
+
16
+ validates :product_id, presence: true
17
+ validates :sku, presence: true
18
+ validates :quantity, presence: true, numericality: { greater_than: 0 }
19
+
20
+ index(product_id: 1)
21
+
22
+ def available?
23
+ quantity > received
24
+ end
25
+
26
+ def received?
27
+ received.positive?
28
+ end
29
+
30
+ def purchased?
31
+ purchased_by.present?
32
+ end
33
+
34
+ def unpurchased?
35
+ !purchased? && !received?
36
+ end
37
+ end
38
+ end
39
+ end